@splicemood/react-music-player
Version:
The music player that sync all settings between multiple tabs on a single client-side browser session
2 lines (1 loc) • 16.6 kB
JavaScript
import{useCallback as e,useEffect as m,useRef as dA,useState as AA}from"react";import{useDebouncedState as tA,useLocalStorage as FA}from"@mantine/hooks";import{createContext as iA}from"react";var XA=iA(void 0);var H=0,h=1,bA=0.5,oA=100,DA=50,MA=200,LA=65536,CA=5,fA=3072;var uA;((W)=>{W[W.PlayAll=0]="PlayAll";W[W.LoopAll=1]="LoopAll";W[W.LoopCue=2]="LoopCue"})(uA||={});import{parseWebStream as lA}from"music-metadata";function sA(w){return new Promise((v,P)=>{let W=new Audio,j=()=>{W.removeEventListener("loadedmetadata",Y),W.removeEventListener("error",Q)},Y=()=>{v(W.duration),j()},Q=(f)=>{P(f),j()};W.addEventListener("loadedmetadata",Y),W.addEventListener("error",Q),W.src=w})}var aA=({src:w,duration:v})=>{if(v)return v;return new Promise(async(P,W)=>{try{let j=await fetch(w,{headers:{Range:"bytes=0-"+fA}});if(!j.ok)W(`HTTP error! status: ${j.status}`);if(j.headers.get("Accept-Ranges")!=="bytes")W("Server does not support range requests.");let Q=j.body;if(Q===null){W("webStream is null");return}let f=await lA(Q,"audio/mpeg");if(f.format.duration)P(f.format.duration);else{let d=await sA(w);P(d)}}catch(j){W(j)}})},o=(w)=>Promise.all(w.map(aA)).catch((v)=>{console.error("Errors occurred while fetching durations:",v)}),EA=(w)=>{return Math.round(w)/oA},hA=(w)=>{if("mediaSession"in navigator)navigator.mediaSession.metadata=new MediaMetadata({title:w.title||"Unknown Title",artist:w.author||"Unknown Artist",album:w.album||"",artwork:w.cover?[{src:w.cover}]:[{src:"/react-music-player/icons/thumbnail.svg"}]})};import{jsx as eA}from"react/jsx-runtime";var BA=window.localStorage,B0=({children:w})=>{let[v,P]=AA(!1),[W,j]=tA(void 0,MA),[Y,Q]=AA([]),[f,d]=AA([]),K=BA.getItem("player-playlist-id"),[b,N]=FA({key:"player-playlist-id",defaultValue:K||void 0}),Z=BA.getItem("player-current-track-index"),i=Z?Number(Z):H,[_,V]=AA(i),[O,q]=AA(!1),[wA,B]=AA(H),[HA,u]=FA({key:"player-updated-time",defaultValue:0,getInitialValueInEffect:!0}),[l,QA]=FA({key:"player-volume",defaultValue:bA,getInitialValueInEffect:!0}),[c,SA]=AA(DA),[gA,WA]=AA(H),[D,s]=AA(H),[NA,F]=FA({key:"player-muted",defaultValue:!1}),[x,OA]=FA({key:"player-shuffle",defaultValue:!1}),[p,PA]=FA({key:"player-repeat",defaultValue:0}),[$A,U]=FA({key:"player-shuffle-queue",defaultValue:[]}),A=dA(new Audio),M=dA(null),jA=e((G,J)=>{if(M.current)M.current.postMessage({type:G,value:J})},[M.current]);m(()=>{jA("UPDATE_TRACK",_)},[_]),m(()=>{if(O)if(!x)U([]);else U((G)=>{return[_,...G]})},[x]),m(()=>{let G=Math.round(l*100);SA(G)},[l]),m(()=>{if(A.current!==null&&v){let G=A.current.currentTime;if(Math.abs(G-HA)>1)A.current.currentTime=HA,B(HA)}},[HA]),m(()=>{if(A.current&&Y.length>H){let G=Y[_];A.current.src=G?.src,A.current.currentTime=H}},[Y,_]),m(()=>{if(A.current&&Y.length>H){let G=BA.getItem("player-current-track-index"),J=G&&Number(G)===_;if(!J)BA.setItem("player-current-track-index",String(_));if(O)jA("PAUSE_PLAYING"),j(!0),A.current.play().then(()=>{if(A.current&&J){let E=BA.getItem("player-sync-time");A.current.currentTime=Number(E)}j(void 0)}).catch(()=>{j(void 0)});else A.current.pause()}},[_,Y,O]),m(()=>{if(A.current)A.current.volume=l,A.current.muted=NA},[l,NA]);let UA=(G)=>{let J=Array(G.length).fill(0);if(d(J),G.length>0)o(G).then((E)=>{if(Array.isArray(E))d(E)})},qA=(G)=>{o([G]).then((J)=>{if(J)d((E)=>{return[...E,J[0]]})})},n=(G)=>{if(String(G)!==b)V(H),N(G)};m(()=>{if(Y.length>0)o(Y).then((G)=>{if(Array.isArray(G))d(G)})},[Y]);let T=()=>{if(A.current!==null){let G=A.current.currentTime;B(G),jA("UPDATE_TIME",G),BA.setItem("player-sync-time",String(G))}},r=e(()=>{if(A.current!==null)s(A.current.duration)},[A.current?.duration]),R=e(()=>{if(Y.length===H)return;q(!0)},[Y]),L=e(()=>q(!1),[q]),a=e(()=>q((G)=>!G),[q]),I=e((G=!1)=>{if(Y.length===H)return;let J;if(x){let t=Y.map((KA,rA)=>rA).filter((KA)=>KA!==_);J=t[Math.floor(Math.random()*t.length)],U((KA)=>{return KA=KA.slice(H,LA),[J,...KA]})}else J=_+h;if(J>=Y.length){if(q(G),J=H,!G)return}if(WA(H),!O)q(!0);V(J)},[Y,_,x,O,V]),zA=e(()=>{switch(p){case 0:I();break;case 1:I(!0);break;case 2:A.current?.play();break}},[p,I]),JA=e(()=>{if(Y.length===H||!A.current)return;if(wA>CA){A.current.currentTime=H;return}let G=H;if(x){if($A.length===h)return;U((J)=>{let[,...E]=J;return G=E[H],E})}else G=_-h;if(G<H)G=Y.length-h;V(G),WA(H),q(!0)},[Y,$A,_,wA,u]),xA=(G)=>V(G),RA=(G)=>QA(G),mA=(G)=>{let J=EA(G);QA(J)},kA=()=>F(!0),vA=()=>F(!1),TA=()=>F((G)=>!G),IA=()=>OA(!0),yA=()=>OA(!1),cA=()=>OA((G)=>!G),VA=()=>{PA((G)=>{if(G===0)return 1;if(G===1)return 2;if(G===2)return 0;return 0})},pA=(G)=>{Q((J)=>[...J,G]),qA(G)},z=(G,J)=>{let E=J!==void 0||O;if(L(),U([H]),WA(H),Q(G),UA(G),E)V(J||H),R()},X=e((G)=>{let{data:J}=G;switch(J?.type){case"PAUSE_PLAYING":q(!1);break;case"UPDATE_TIME":if(!O&&J.value!==H)B(J.value);break;case"UPDATE_TRACK":if(!O)V(J.value);break}},[O,V,q,_]),C=()=>{if(A.current&&A.current.buffered.length>0){let G=A.current.buffered.end(A.current.buffered.length-1);WA(G/A.current.duration*100)}};m(()=>{if(A.current)A.current.addEventListener("ended",zA);return()=>{A.current?.removeEventListener("ended",zA)}},[zA]),m(()=>{if(A.current){let G=BA.getItem("player-sync-time"),J=Number(G);if(u(J),A.current.currentTime=J,B(J),q(!1),A.current.preload="metadata",A.current.addEventListener("progress",C),A.current.addEventListener("timeupdate",T),A.current.addEventListener("durationchange",r),A.current.addEventListener("pause",()=>{q(!1)}),A.current.addEventListener("play",()=>{q(!0)}),A.current.readyState>=1)r();P(!0)}if("BroadcastChannel"in window&&!M.current)M.current=new BroadcastChannel("global-audio-player"),M.current.onmessage=X;return()=>{if(A.current)A.current.pause(),A.current.removeEventListener("progress",C),A.current.removeEventListener("timeupdate",T),A.current.removeEventListener("durationchange",r),A.current=null}},[]),m(()=>{if("mediaSession"in navigator)navigator.mediaSession.setActionHandler("play",R),navigator.mediaSession.setActionHandler("pause",L),navigator.mediaSession.setActionHandler("previoustrack",JA),navigator.mediaSession.setActionHandler("nexttrack",()=>I()),navigator.mediaSession.setActionHandler("seekto",(G)=>{if(G.seekTime!==void 0)u(G.seekTime)})},[R,L,JA,I,u]),m(()=>{if(Y.length>0&&Y[_])hA(Y[_])},[Y,_]),m(()=>{if("mediaSession"in navigator)navigator.mediaSession.playbackState=O?"playing":"paused"},[O,_]);let $={play:R,pause:L,togglePlayPause:a,next:I,previous:JA,setVolume:RA,setVolumePercent:mA,mute:kA,unmute:vA,toggleMute:TA,shuffleOn:IA,shuffleOff:yA,toggleShuffle:cA,toggleLoop:VA,addToPlaylist:pA,replacePlaylist:z,setCurrentTrack:xA,setUpdateTime:u,setPlaylistId:n,volume:l,volumePercent:c,bufferedPercentage:gA,currentPlaylistId:b,maxTime:D,playlist:Y,durations:f,isPlaying:O,currentTime:wA,currentTrackIndex:_,repeatMode:p,isShuffled:x,isLoading:W,isMuted:NA};return eA(XA.Provider,{value:$,children:w})};import{useCallback as GA,useEffect as y,useRef as nA,useState as S}from"react";import{useDebouncedState as A0}from"@mantine/hooks";import{jsx as G0}from"react/jsx-runtime";var YA=window.localStorage,C0=({children:w})=>{let[v,P]=A0(void 0,MA),[W,j]=S([]),[Y,Q]=S([]),[f,d]=S(""),[K,b]=S(H),[N,Z]=S(!1),[i,_]=S(H),[V,O]=S(0),q=YA.getItem("player-volume"),wA=q?Number(q):bA,[B,HA]=S(wA),[u,l]=S(DA),[QA,c]=S(H),[SA,gA]=S(H),WA=YA.getItem("player-muted")==="true",[D,s]=S(WA),NA=YA.getItem("player-shuffle")==="true",[F,x]=S(NA),OA=YA.getItem("player-repeat"),[p,PA]=S(Number(OA)||0),[$A,U]=S([]),A=nA(new Audio),M=nA(null),jA=GA(($,G)=>{if(M.current)M.current.postMessage({type:$,value:G})},[M.current]);y(()=>{if(N)if(!F)U([]);else U(($)=>{return[K,...$]});YA.setItem("player-shuffle",String(F))},[F]),y(()=>{let $=Math.round(B*100);l($)},[B]),y(()=>{if(A.current!==null)A.current.currentTime=V,_(V)},[V]),y(()=>{if(A.current&&W.length>H){let $=W[K];A.current.src=$?.src,A.current.currentTime=H}},[W,K]),y(()=>{if(A.current&&W.length>H)if(N)jA("PAUSE_PLAYING"),P(!0),A.current.play().then(()=>{P(void 0)}).catch(()=>{P(void 0)});else A.current.pause()},[K,W,N]),y(()=>{if(A.current)A.current.volume=B,A.current.muted=D},[B,D]);let UA=($)=>{let G=Array($.length).fill(0);if(Q(G),$.length>0)o($).then((J)=>{if(Array.isArray(J))Q(J)})},qA=($)=>{o([$]).then((G)=>{if(G)Q((J)=>{return[...J,G[0]]})})},n=GA(()=>{if(A.current!==null)gA(A.current.duration)},[A.current?.duration]),T=GA(()=>{if(W.length===H)return;Z(!0)},[W]),r=GA(()=>Z(!1),[Z]),R=GA(()=>Z(($)=>!$),[Z]),L=GA(($=!1)=>{if(W.length===H)return;let G;if(F){let E=W.map((t,KA)=>KA).filter((t)=>t!==K);G=E[Math.floor(Math.random()*E.length)],U((t)=>{return t=t.slice(H,LA),[G,...t]})}else G=K+h;if(G>=W.length){if(Z($),G=H,!$)return}if(c(H),!N)Z(!0);b(G)},[W,K,F,N,b]),a=GA(()=>{switch(p){case 0:L();break;case 1:L(!0);break;case 2:A.current?.play();break}},[p,L]),I=GA(()=>{if(W.length===H||!A.current)return;if(i>CA){A.current.currentTime=H;return}let $=H;if(F){if($A.length===h)return;U((G)=>{let[,...J]=G;return $=J[H],J})}else $=K-h;if($<H)$=W.length-h;b($),c(H),Z(!0)},[W,$A,K,i,O]),zA=($)=>b($),JA=($)=>{HA($),YA.setItem("player-volume",String($))},xA=($)=>{let G=EA($);JA(G)},RA=()=>s(!0),mA=()=>s(!1),kA=()=>s(($)=>!$),vA=()=>x(!0),TA=()=>x(!1),IA=()=>x(($)=>!$),yA=()=>{PA(($)=>{let G=0;if($===0)G=1;if($===1)G=2;if($===2)G=0;return YA.setItem("player-repeat",String(G)),G})},cA=($)=>{j((G)=>[...G,$]),qA($)},VA=($,G)=>{let J=G!==void 0||N;if(r(),U([H]),c(H),j($),UA($),J)b(G||H),T()};y(()=>{YA.setItem("player-muted",String(D))},[D]);let pA=GA(($)=>{let{data:G}=$;switch(G?.type){case"PAUSE_PLAYING":Z(!1);break}},[N,b,Z,K]),z=()=>{if(A.current&&A.current.buffered.length>0){let $=A.current.buffered.end(A.current.buffered.length-1);c($/A.current.duration*100)}},X=()=>{if(A.current!==null){let $=A.current.currentTime;_($)}};y(()=>{if(A.current)A.current.addEventListener("ended",a);return()=>{A.current?.removeEventListener("ended",a)}},[a]),y(()=>{if(A.current){if(A.current.muted=D,A.current.volume=B,Z(!1),A.current.preload="metadata","BroadcastChannel"in window&&!M.current)M.current=new BroadcastChannel("playpause-audio-player"),M.current.onmessage=pA;if(A.current.addEventListener("progress",z),A.current.addEventListener("timeupdate",X),A.current.addEventListener("durationchange",n),A.current.addEventListener("pause",()=>{Z(!1)}),A.current.addEventListener("play",()=>{Z(!0)}),A.current.readyState>=1)n()}return()=>{if(A.current)A.current.pause(),A.current.removeEventListener("progress",z),A.current.removeEventListener("timeupdate",X),A.current.removeEventListener("durationchange",n),A.current=null}},[]),y(()=>{if("mediaSession"in navigator)navigator.mediaSession.setActionHandler("play",T),navigator.mediaSession.setActionHandler("pause",r),navigator.mediaSession.setActionHandler("previoustrack",I),navigator.mediaSession.setActionHandler("nexttrack",()=>L()),navigator.mediaSession.setActionHandler("seekto",($)=>{if($.seekTime!==void 0)O($.seekTime)})},[T,r,I,L,O]),y(()=>{if(W.length>0&&W[K])hA(W[K])},[W,K]),y(()=>{if("mediaSession"in navigator)navigator.mediaSession.playbackState=N?"playing":"paused"},[N,K]);let C={play:T,pause:r,togglePlayPause:R,next:L,previous:I,setVolume:JA,setVolumePercent:xA,mute:RA,unmute:mA,toggleMute:kA,shuffleOn:vA,shuffleOff:TA,toggleShuffle:IA,toggleLoop:yA,addToPlaylist:cA,replacePlaylist:VA,setCurrentTrack:zA,setUpdateTime:O,setPlaylistId:d,volume:B,volumePercent:u,bufferedPercentage:QA,currentPlaylistId:f,maxTime:SA,playlist:W,durations:Y,isPlaying:N,currentTime:i,currentTrackIndex:K,repeatMode:p,isShuffled:F,isLoading:v,isMuted:D};return G0(XA.Provider,{value:C,children:w})};import{useCallback as ZA,useEffect as k,useRef as H0,useState as g}from"react";import{useDebouncedState as W0}from"@mantine/hooks";import{jsx as $0}from"react/jsx-runtime";var _A=window.localStorage,m0=({children:w})=>{let[v,P]=W0(void 0,MA),[W,j]=g([]),[Y,Q]=g([]),[f,d]=g(""),[K,b]=g(H),[N,Z]=g(!1),[i,_]=g(H),[V,O]=g(0),q=_A.getItem("player-volume"),wA=q?Number(q):bA,[B,HA]=g(wA),[u,l]=g(DA),[QA,c]=g(H),[SA,gA]=g(H),WA=_A.getItem("player-muted")==="true",[D,s]=g(WA),NA=_A.getItem("player-shuffle")==="true",[F,x]=g(NA),OA=_A.getItem("player-repeat"),[p,PA]=g(Number(OA)||0),[$A,U]=g([]),A=H0(new Audio);k(()=>{if(N)if(!F)U([]);else U((z)=>{return[K,...z]});_A.setItem("player-shuffle",String(F))},[F]),k(()=>{let z=Math.round(B*100);l(z)},[B]),k(()=>{if(A.current!==null)A.current.currentTime=V,_(V)},[V]),k(()=>{if(A.current&&W.length>H){let z=W[K];A.current.src=z?.src,A.current.currentTime=H}},[W,K]),k(()=>{if(A.current&&W.length>H)if(N)P(!0),A.current.play().then(()=>{P(void 0)}).catch(()=>{P(void 0)});else A.current.pause()},[K,W,N]),k(()=>{if(A.current)A.current.volume=B,A.current.muted=D},[B,D]);let M=(z)=>{let X=Array(z.length).fill(0);if(Q(X),z.length>0)o(z).then((C)=>{if(Array.isArray(C))Q(C)})},jA=(z)=>{o([z]).then((X)=>{if(X)Q((C)=>{return[...C,X[0]]})})};k(()=>{if(W.length>0)o(W).then((z)=>{if(Array.isArray(z))Q(z)})},[W]);let UA=ZA(()=>{if(A.current!==null){let z=A.current.currentTime;_(z)}},[_]),qA=ZA(()=>{if(A.current!==null)gA(A.current.duration)},[A.current?.duration]),n=ZA(()=>{if(W.length===H)return;Z(!0)},[W]),T=ZA(()=>Z(!1),[Z]),r=ZA(()=>Z((z)=>!z),[Z]),R=ZA((z=!1)=>{if(W.length===H)return;let X;if(F){let $=W.map((G,J)=>J).filter((G)=>G!==K);X=$[Math.floor(Math.random()*$.length)],U((G)=>{return G=G.slice(H,LA),[X,...G]})}else X=K+h;if(X>=W.length){if(Z(z),X=H,!z)return}if(c(H),!N)Z(!0);b(X)},[W,K,F,N,b]),L=ZA(()=>{switch(p){case 0:R();break;case 1:R(!0);break;case 2:A.current?.play();break}},[p,R]),a=ZA(()=>{if(W.length===H||!A.current)return;if(i>CA){A.current.currentTime=H;return}let z=H;if(F){if($A.length===h)return;U((X)=>{let[,...C]=X;return z=C[H],C})}else z=K-h;if(z<H)z=W.length-h;b(z),c(H),Z(!0)},[W,$A,K,i,O]),I=(z)=>b(z),zA=(z)=>{HA(z),_A.setItem("player-volume",String(z))},JA=(z)=>{let X=EA(z);zA(X)},xA=()=>s(!0),RA=()=>s(!1),mA=()=>s((z)=>!z),kA=()=>x(!0),vA=()=>x(!1),TA=()=>x((z)=>!z),IA=()=>{PA((z)=>{let X=0;if(z===0)X=1;if(z===1)X=2;if(z===2)X=0;return _A.setItem("player-repeat",String(X)),X})},yA=(z)=>{j((X)=>[...X,z]),jA(z)},cA=(z,X)=>{let C=X!==void 0||N;if(T(),U([H]),c(H),j(z),M(z),C)b(X||H),n()};k(()=>{_A.setItem("player-muted",String(D))},[D]);let VA=()=>{if(A.current&&A.current.buffered.length>0){let z=A.current.buffered.end(A.current.buffered.length-1);c(z/A.current.duration*100)}};k(()=>{if(A.current)A.current.addEventListener("ended",L);return()=>{A.current?.removeEventListener("ended",L)}},[L]),k(()=>{if(A.current){if(A.current.muted=D,A.current.volume=B,Z(!1),A.current.preload="metadata",A.current.addEventListener("progress",VA),A.current.addEventListener("timeupdate",UA),A.current.addEventListener("durationchange",qA),A.current.addEventListener("pause",()=>{Z(!1)}),A.current.addEventListener("play",()=>{Z(!0)}),A.current.readyState>=1)qA()}return()=>{if(A.current)A.current.pause(),A.current.removeEventListener("progress",VA),A.current.removeEventListener("timeupdate",UA),A.current.removeEventListener("durationchange",qA),A.current=null}},[]),k(()=>{if("mediaSession"in navigator)navigator.mediaSession.setActionHandler("play",n),navigator.mediaSession.setActionHandler("pause",T),navigator.mediaSession.setActionHandler("previoustrack",a),navigator.mediaSession.setActionHandler("nexttrack",()=>R()),navigator.mediaSession.setActionHandler("seekto",(z)=>{if(z.seekTime!==void 0)O(z.seekTime)})},[n,T,a,R,O]),k(()=>{if(W.length>0&&W[K])hA(W[K])},[W,K]),k(()=>{if("mediaSession"in navigator)navigator.mediaSession.playbackState=N?"playing":"paused"},[N,K]);let pA={play:n,pause:T,togglePlayPause:r,next:R,previous:a,setVolume:zA,setVolumePercent:JA,mute:xA,unmute:RA,toggleMute:mA,shuffleOn:kA,shuffleOff:vA,toggleShuffle:TA,toggleLoop:IA,addToPlaylist:yA,replacePlaylist:cA,setCurrentTrack:I,setUpdateTime:O,setPlaylistId:d,volume:B,volumePercent:u,bufferedPercentage:QA,currentPlaylistId:f,maxTime:SA,playlist:W,durations:Y,isPlaying:N,currentTime:i,currentTrackIndex:K,repeatMode:p,isShuffled:F,isLoading:v,isMuted:D};return $0(XA.Provider,{value:pA,children:w})};import{useContext as z0}from"react";var y0=()=>{let w=z0(XA);if(w===void 0)throw Error("useAudio must be used within an PlayerContext");return w};export{y0 as useAudio,hA as updateMediaSessionMetadata,LA as truncateBackStackQueue,h as step,EA as percentToValue,CA as offsetTimeBumpToStart,fA as metadataBytesLength,oA as maxPercentage,H as firstElement,o as fetchDuration,DA as defaultVolumePercent,bA as defaultVolume,MA as debounceLoadingState,C0 as PlayerPlayPauseSyncProvider,m0 as PlayerNoSyncProvider,B0 as PlayerFullSyncProvider,uA as LoopState};