diff --git a/app.js b/app.js new file mode 100644 index 0000000..3f80105 --- /dev/null +++ b/app.js @@ -0,0 +1,199 @@ +// dom refs +const video=document.getElementById("video") +const play=document.getElementById("play") +const mute=document.getElementById("mute") +const seek=document.getElementById("seek") +const volume=document.getElementById("volume") +const controls=document.getElementById("controls") +const player=document.getElementById("player") +const tapUnmute=document.getElementById("tapUnmute") +const rotate=document.getElementById("rotate") + +// player state +let hideTimer,resizeTimer,lastSeek=-1 +let controlsVisible=false +let autoplayStartedMuted=true +let rotated=false + +// device check +const isMobile=matchMedia("(pointer:coarse)").matches||window.innerWidth<=768 +controls.classList.add(isMobile?"mobile-mode":"desktop-mode") + +// ui sync +const syncPlayState=()=>play.textContent=video.paused?"x":"▶" +const syncRotateState=()=>rotate.textContent=rotated?"⟲":"⟳" + +function syncMuteState(){ + mute.textContent=video.muted||video.volume===0?"u":"a" + volume.value=video.muted?0:video.volume +} + +// controls +function showControls(){ + if(!controlsVisible){ + controls.classList.add("visible") + controlsVisible=true + } + clearTimeout(hideTimer) + hideTimer=setTimeout(()=>{ + controls.classList.remove("visible") + controlsVisible=false + },isMobile?2200:1700) +} + +// prompt +const showUnmutePrompt=show=>tapUnmute.classList.toggle("visible",!!show) + +// layout +function applyVideoLayout(){ + if(rotated){ + video.style.objectFit="contain" + video.style.transform="translateZ(0) rotate(90deg) scale(1.15)" + return + } + + const landscape=video.videoWidth&&video.videoHeight?video.videoWidth>video.videoHeight:true + const portraitScreen=window.innerHeight>window.innerWidth + + video.style.objectFit=isMobile&&landscape&&portraitScreen?"contain":"cover" + video.style.transform="translateZ(0) scale(1.01)" +} + +// rotate +function toggleRotate(){ + rotated=!rotated + syncRotateState() + applyVideoLayout() + showControls() +} + +// autoplay +async function startPlayback(){ + video.volume=1 + video.muted=true + + try{ + await video.play() + }catch(e){ + console.error(e) + } + + showUnmutePrompt(true) + syncPlayState() + syncMuteState() + syncRotateState() +} + +// audio enable +function enableSoundAndPlay(){ + video.muted=false + if(video.volume===0)video.volume=1 + video.play().catch(()=>{}) + autoplayStartedMuted=false + showUnmutePrompt(false) + syncMuteState() + syncPlayState() +} + +// video events +video.addEventListener("play",()=>{syncPlayState();showControls()}) +video.addEventListener("pause",()=>{syncPlayState();showControls()}) +video.addEventListener("ended",()=>{syncPlayState();showControls()}) + +video.addEventListener("loadedmetadata",()=>{ + applyVideoLayout() + + requestIdleCallback?.(()=>{ + const t=document.querySelector(".video-text") + if(t)t.textContent="" + }) + + syncMuteState() + syncPlayState() +}) + +video.addEventListener("timeupdate",()=>{ + if(!video.duration)return + const value=((video.currentTime/video.duration)*100)|0 + if(value===lastSeek)return + lastSeek=value + seek.value=value +}) + +// controls +play.addEventListener("click",async e=>{ + e.stopPropagation() + if(video.paused){ + try{await video.play()}catch{} + }else{ + video.pause() + } +}) + +mute.addEventListener("click",e=>{ + e.stopPropagation() + + if(video.muted||video.volume===0){ + video.muted=false + if(video.volume===0)video.volume=.5 + autoplayStartedMuted=false + showUnmutePrompt(false) + }else{ + video.muted=true + } + + syncMuteState() +}) + +seek.addEventListener("input",e=>{ + e.stopPropagation() + if(video.duration)video.currentTime=(e.target.value/100)*video.duration +}) + +volume.addEventListener("input",e=>{ + e.stopPropagation() + video.volume=parseFloat(e.target.value) + video.muted=video.volume===0 + + if(video.volume>0){ + autoplayStartedMuted=false + showUnmutePrompt(false) + } + + syncMuteState() +}) + +rotate.addEventListener("click",e=>{ + e.stopPropagation() + toggleRotate() +}) + +tapUnmute.addEventListener("click",e=>{ + e.stopPropagation() + enableSoundAndPlay() +}) + +controls.addEventListener("click",e=>e.stopPropagation()) + +player.addEventListener("click",()=>{ + showControls() + if(autoplayStartedMuted&&video.muted)enableSoundAndPlay() +},{passive:true}) + +player.addEventListener("mousemove",showControls) +player.addEventListener("mouseenter",showControls) +player.addEventListener("touchstart",showControls,{passive:true}) + +// resize +window.addEventListener("resize",()=>{ + clearTimeout(resizeTimer) + resizeTimer=setTimeout(applyVideoLayout,100) +}) + +window.addEventListener("orientationchange",applyVideoLayout) + +// init +syncPlayState() +syncMuteState() +syncRotateState() +startPlayback() \ No newline at end of file