You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1 line
5.4 KiB
JavaScript

const nodes=document.querySelectorAll("audio,video"),playlists={},prefetchedTracks=new Map,MAX_PREFETCH_KEEP=10,MAX_PLAYLIST_LENGTH=1e3,PLAYLIST_MIME_TYPES=["audio/x-mpegurl","audio/mpegurl","application/vnd.apple.mpegurl","application/mpegurl","application/x-mpegurl"];function stripUrlParameters(e){const t=new URL(e,window.location);return t.search="",t.hash="",t.href}function isPlaylist(e){const t=stripUrlParameters(e);return t.endsWith(".m3u")||t.endsWith(".m3u8")}function isBlob(e){return new URL(e,window.location).protocol=="blob"}function parsePlaylist(e){return e.match(/^(?!#)(?!\s).*$/mg).filter(e=>e)}function fetchPlaylist(e,t,n){const s=new XMLHttpRequest;s.open("GET",e,!0),s.responseType="blob",s.onload=()=>{if(PLAYLIST_MIME_TYPES.includes(s.response.type)){const n=new FileReader,o=t;n.addEventListener("loadend",s=>{playlists[e]=parsePlaylist(n.result),t()}),n.readAsText(s.response)}else console.error("playlist must have one of the playlist MIME type '"+PLAYLIST_MIME_TYPES+"' but it had MIME type '"+s.response.type+"'."),n()},s.onerror=n,s.abort=n,s.send()}function servedPartialDataAndCanRequestAll(e){return!!(e.status===206&&e.getResponseHeader("content-range").includes("/")&&!e.getResponseHeader("content-range").includes("/*"))}function prefetchTrack(e,t){if(prefetchedTracks.has(e))return;for(;prefetchedTracks.size>MAX_PREFETCH_KEEP;){const e=prefetchedTracks.keys().next().value,t=prefetchedTracks.get(e);prefetchedTracks.delete(e)}prefetchedTracks.set(e,e);const n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="blob",n.onload=()=>{if(servedPartialDataAndCanRequestAll(n)){const o=Number(n.getResponseHeader("content-range").split("/")[1])-1,s=new XMLHttpRequest;s.open("GET",e,!0),s.responseType="blob",s.setRequestHeader("range","bytes=0-"+o),s.onload=()=>{prefetchedTracks.set(e,s.response),t&&t()},s.send()}else prefetchedTracks.set(e,n.response),t&&t()},n.send()}function showStaticOverlay(e,t){if(e instanceof Audio)return;const n=e.getBoundingClientRect().width,s=e.getBoundingClientRect().height;t.width=n,t.height=s;const o=e.videoHeight/e.videoWidth,i=s/n,a=i>o*1.01,r=o>i*1.01;a?t.height=n*o:r&&(t.width=s/o);const c=t.getContext("2d");c.scale(t.width/e.videoWidth,t.height/e.videoHeight),c.drawImage(e,0,0),t.hidden=!0,e.parentNode.insertBefore(t,e.nextSibling),t.style.position="absolute",r?t.style.marginLeft="-"+(n+t.width)/2+"px":t.style.marginLeft="-"+n+"px",a&&(t.style.marginTop=(s-t.height)/2+"px"),t.hidden=!1}function updateSrc(e,t){const i=e.getAttribute("playlist"),o=e.getAttribute("track-index");let s=[...playlists[i]],n=s[o];if(isPlaylist(n))s.length>=MAX_PLAYLIST_LENGTH?changeTrack(e,+1):fetchPlaylist(n,()=>{s.splice(o,1,...playlists[n]),playlists[i]=s,updateSrc(e,t)},()=>t());else{let i=prefetchedTracks.has(n)&&prefetchedTracks.get(n)instanceof Blob?URL.createObjectURL(prefetchedTracks.get(n)):n;const r=e.getAttribute("src"),a=document.createElement("canvas");if(!isNaN(e.duration)&&document.fullscreen!==!0)try{showStaticOverlay(e,a)}catch(e){console.log(e)}e.style.height=e.getBoundingClientRect().height.toString()+"px",e.style.width=e.getBoundingClientRect().width.toString()+"px",e.setAttribute("src",i),e.oncanplaythrough=()=>{isNaN(e.duration)||(e.style.height=null,e.style.width=null),a.hidden=!0,a.remove()},setTimeout(()=>a.remove(),300),i==n&&prefetchTrack(n,()=>{e.paused&&i==e.getAttribute("src")&&e.currentTime===0&&e.setAttribute("src",URL.createObjectURL(prefetchedTracks.get(i)))}),isBlob(r)&&URL.revokeObjectURL(r),e.parentElement.querySelector(".m3u-player--title").title=n,e.parentElement.querySelector(".m3u-player--title").textContent=n;for(const e of[1,2,3])s.length>Number(o)+e&&prefetchTrack(s[Number(o)+e]);t()}}function changeTrack(e,t){const s=Number(e.getAttribute("track-index")),n=s+t,o=playlists[e.getAttribute("playlist")];n>=0&&o.length>n&&(e.setAttribute("track-index",n),updateSrc(e,()=>e.play()))}function initPlayer(e){e.setAttribute("playlist",e.getAttribute("src")),e.setAttribute("track-index",0);const r=e.getAttribute("playlist"),i=e.parentElement.insertBefore(document.createElement("div"),e),n=document.createElement("div"),s=document.createElement("span"),t=document.createElement("span"),o=document.createElement("span");n.appendChild(s),n.appendChild(t),n.appendChild(o),s.classList.add("m3u-player--left"),o.classList.add("m3u-player--right"),t.classList.add("m3u-player--title"),t.style.overflow="hidden",t.style.textOverflow="ellipsis",t.style.whiteSpace="nowrap",t.style.opacity="0.3",t.style.direction="rtl",t.style.paddingLeft="0.5em",t.style.paddingRight="0.5em",n.style.display="flex",n.style.justifyContent="space-between";const a=document.createElement("style");a.innerHTML=".m3u-player--left:hover, .m3u-player--right:hover {color: wheat; background-color: DarkSlateGray}",i.appendChild(a),i.appendChild(n),n.style.width=e.getBoundingClientRect().width.toString()+"px",i.appendChild(e),s.innerHTML="<",s.onclick=()=>changeTrack(e,-1),o.innerHTML=">",o.onclick=()=>changeTrack(e,+1),fetchPlaylist(r,()=>{updateSrc(e,()=>null),e.addEventListener("ended",t=>{e.currentTime>=e.duration&&changeTrack(e,+1)})},()=>null),e.resizeObserver=new ResizeObserver(e=>{n.style.width=e[0].contentRect.width.toString()+"px"}),e.resizeObserver.observe(e)}function processTag(e){const t=e.canPlayType("audio/x-mpegurl");let n=!!t;t=="maybe"&&(n=!1),n||isPlaylist(e.getAttribute("src"))&&initPlayer(e)}document.addEventListener("DOMContentLoaded",()=>{const e=document.querySelectorAll("audio,video");e.forEach(processTag)})