diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e0c6a8e..25583d8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "@visx/scale": "^3.0.0", "@visx/shape": "^3.0.0", "@visx/tooltip": "^3.1.2", + "@wavesurfer/react": "^1.0.7", "axios": "1.2.1", "d3-array": "^3.2.3", "d3-time-format": "^4.1.0", @@ -5783,6 +5784,15 @@ "react-dom": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, + "node_modules/@wavesurfer/react": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@wavesurfer/react/-/react-1.0.7.tgz", + "integrity": "sha512-Bw2rd2zqVL1ERy8N9mciacbQW3N5Gn9oRjhq3kjqgtLeE8a1WSTU+4kU0E37541jf432eNZ8lNzuodFFSiNccw==", + "peerDependencies": { + "react": "^18.2.0", + "wavesurfer.js": ">=7.7.14" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -17748,6 +17758,12 @@ "node": ">=10.13.0" } }, + "node_modules/wavesurfer.js": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.8.6.tgz", + "integrity": "sha512-EDexkMwkkQBTWruhfWQRkTtvRggtKFTPuJX/oZ5wbIZEfyww9EBeLr2mtkxzA1S8TlWPx6adY5WyjOlNYNyHSg==", + "peer": true + }, "node_modules/wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index b435a5d..0daeee0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@visx/scale": "^3.0.0", "@visx/shape": "^3.0.0", "@visx/tooltip": "^3.1.2", + "@wavesurfer/react": "^1.0.7", "axios": "1.2.1", "d3-array": "^3.2.3", "d3-time-format": "^4.1.0", diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 2da2997..9ed91e3 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -6,7 +6,7 @@ import CallInfo from "./components/CallInfo"; import ListCalls from "./components/ListCalls"; import { useSelector, useDispatch } from 'react-redux' import { useGetTalkgroupsQuery } from '../features/api/apiSlice' -import { playedCall } from "../features/calls/callsSlice"; +import { playedCall } from "../features/calls/callsSlice"; import { useInView } from 'react-intersection-observer'; import { Container, @@ -96,13 +96,13 @@ function CallPlayer(props) { if (currentIndex > 0) { const nextCallId = callsData.ids[currentIndex - 1]; const nextCall = callsData.entities[nextCallId]; - console.log("Autoplaying next call, current Index is: " + currentIndex + " isPlaying is: " + isPlaying ) + console.log("Autoplaying next call, current Index is: " + currentIndex + " isPlaying is: " + isPlaying) setCurrentCall(nextCall); dispatch(playedCall(nextCall._id)); setIsPlaying(true); } else if (backgroundAutoplay) { // there are no more calls to play, but we want to keep playing something so the webpage is not put to sleep, so we play silence! - setSilenceCount(silenceCount+1); + setSilenceCount(silenceCount + 1); } else { // there are no more calls to play and backgroundAutoplay is disabled setIsPlaying(false); @@ -132,20 +132,20 @@ function CallPlayer(props) { }, [loadOlderInView]); // This handles when a Call is specified in the URI -useEffect(() => { - if (props.initialCallId && callsData) { - const call = callsData.entities[props.initialCallId]; - if (call && !selectedInitialCall) { - const time = new Date(call.time); + useEffect(() => { + if (props.initialCallId && callsData) { + const call = callsData.entities[props.initialCallId]; + if (call && !selectedInitialCall) { + const time = new Date(call.time); - console.log("Playing Initial Call: " + call._id + " Start Time: " + time.toLocaleTimeString() ); - setIsPlaying(true); - setCurrentCall(call); - setSelectedInitialCall(true); // signals that it has selected the initial call - you only want to do this once - dispatch(playedCall(call._id)); + console.log("Playing Initial Call: " + call._id + " Start Time: " + time.toLocaleTimeString()); + setIsPlaying(true); + setCurrentCall(call); + setSelectedInitialCall(true); // signals that it has selected the initial call - you only want to do this once + dispatch(playedCall(call._id)); + } } - } -}, [props.initialCallId, callsData]) + }, [props.initialCallId, callsData]) // Triggered when a new call is selected in the parent component. // This happens when a new call comes over the socket @@ -153,13 +153,13 @@ useEffect(() => { // we should only set the selectCallId to be the current call when AutoPlay is selected // and when there isn't another call already playing useEffect(() => { - console.log("New selected call: " + selectCallId + " IsPlaying: " + isPlaying + " autoPlay: " + autoPlay ); + console.log("New selected call: " + selectCallId + " IsPlaying: " + isPlaying + " autoPlay: " + autoPlay); if (selectCallId && callsData && !isPlaying && autoPlay) { const call = callsData.entities[selectCallId]; if (call) { const time = new Date(call.time); - console.log("Playing Selected Call: " + call._id + " Start Time: " + time.toLocaleTimeString() ); + console.log("Playing Selected Call: " + call._id + " Start Time: " + time.toLocaleTimeString()); setIsPlaying(true); setCurrentCall(call); dispatch(playedCall(call._id)); @@ -169,7 +169,7 @@ useEffect(() => { useEffect(() => { if (system && system.status && system.status.length > 0) { - setStatusVisible(true); + setStatusVisible(true); } }, [system]) @@ -181,9 +181,9 @@ useEffect(() => {
{statusVisible && - + System Status{system.status} - + } { >
- +
@@ -208,11 +208,11 @@ useEffect(() => { - - Donate - - - } /> + + Donate + + + } /> Download Link diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index b840af0..e01682e 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -6,64 +6,33 @@ import { Label } from "semantic-ui-react"; import ReactAudioPlayer from 'react-audio-player' +import WavesurferPlayer from '@wavesurfer/react' +import { is } from "date-fns/locale"; + const MediaPlayer = (props) => { - const audioRef = React.createRef(); + const call = props.call; const [sourceIndex, setSourceIndex] = useState(0); - const [playProgress, setPlayProgress] = useState(0); + const [wavesurfer, setWavesurfer] = useState(null) + const [isPlaying, setIsPlaying] = useState(false) const [playTime, setPlayTime] = useState(0); - const [isPlaying, setIsPlaying] = useState(false); - const parentHandlePlayPause = props.onPlayPause const playSilence = props.playSilence; - const handlePause = () => { setIsPlaying(false); } - const handlePlay = () => { setIsPlaying(true); } - const playPause = () => { - const audio = audioRef.current.audioEl.current; - if (isPlaying) { - setIsPlaying(false); - parentHandlePlayPause(false); - audio.pause(); - } else { - setIsPlaying(true); - parentHandlePlayPause(true); - audio.play(); - } + + const onReady = (ws) => { + setWavesurfer(ws) + setIsPlaying(false) } - const call = props.call; + const onPlayPause = () => { + wavesurfer && wavesurfer.playPause() + } useEffect(() => { - - const audio = audioRef.current.audioEl.current; - const onEnded = props.onEnded; - setSourceIndex(0); - if (call ) { - audio.src = call.url; - const playPromise = audio.play(); - - // In browsers that don’t yet support this functionality, - // playPromise won’t be defined. - if (playPromise !== undefined) { - playPromise.then(function () { - - }).catch(function (error) { - console.log("Automatic playback failed: " + error); - handlePause(); - //onEnded(); - // Show a UI element to let the user manually start playback. - }); - } else { - audio.src = false; - } - } - }, [call]); - - useEffect(() => { - const audio = audioRef.current.audioEl.current; + //const audio = audioRef.current.audioEl.current; setSourceIndex(0); if ('mediaSession' in navigator) { @@ -77,8 +46,26 @@ const MediaPlayer = (props) => { ] }); } - audio.src = "data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA/+M4wAAAAAAAAAAAAEluZm8AAAAPAAAAAwAAAbAAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV////////////////////////////////////////////AAAAAExhdmM1OC4xMwAAAAAAAAAAAAAAACQDkAAAAAAAAAGw9wrNaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+MYxAAAAANIAAAAAExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxDsAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxHYAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV"; - const playPromise = audio.play(); + if (wavesurfer) { + var reader = new FileReader(); + + reader.onload = function (evt) { + // Create a Blob providing as first argument a typed array with the file buffer + var blob = new window.Blob([new Uint8Array(evt.target.result)]); + + // Load the blob into Wavesurfer + wavesurfer.loadBlob(blob); + }; + + reader.onerror = function (evt) { + console.error("An error ocurred reading the file: ", evt); + }; + + // Read File as an ArrayBuffer + reader.readAsArrayBuffer("data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA/+M4wAAAAAAAAAAAAEluZm8AAAAPAAAAAwAAAbAAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV////////////////////////////////////////////AAAAAExhdmM1OC4xMwAAAAAAAAAAAAAAACQDkAAAAAAAAAGw9wrNaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+MYxAAAAANIAAAAAExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxDsAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxHYAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV"); + + } + /*const playPromise = audio.play(); // In browsers that don’t yet support this functionality, // playPromise won’t be defined. @@ -91,31 +78,64 @@ const MediaPlayer = (props) => { }); } else { audio.src = false; - } + }*/ }, [playSilence]); + + /* + useEffect(() => { + + const audio = audioRef.current.audioEl.current; + const onEnded = props.onEnded; + setSourceIndex(0); + if (call ) { + audio.src = call.url; + const playPromise = audio.play(); + + // In browsers that don’t yet support this functionality, + // playPromise won’t be defined. + if (playPromise !== undefined) { + playPromise.then(function () { + + }).catch(function (error) { + console.log("Automatic playback failed: " + error); + handlePause(); + //onEnded(); + // Show a UI element to let the user manually start playback. + }); + } else { + audio.src = false; + } + } + }, [call]);*/ + + + const updatePlayProgress = () => { - const audio = audioRef.current.audioEl.current; - const { currentTime, duration } = audio; + + if (wavesurfer && wavesurfer.isPlaying()) { + var totalTime = wavesurfer.getDuration(), + currentTime = wavesurfer.getCurrentTime(), + remainingTime = totalTime - currentTime; + + if (!isPlaying) { + setIsPlaying(true); + } + //console.log("totalTime: " + totalTime + " currentTime: " + currentTime + " remainingTime: " + remainingTime); - // this checks to see if it should display the next Source ID - if (call && ((call.srcList.length - 1) >= (sourceIndex + 1)) && (currentTime > call.srcList[sourceIndex + 1].pos)) { - setSourceIndex(sourceIndex + 1); + // this checks to see if it should display the next Source ID + if (call && ((call.srcList.length - 1) >= (sourceIndex + 1)) && (currentTime > call.srcList[sourceIndex + 1].pos)) { + setSourceIndex(sourceIndex + 1); + console.log("sourceIndex: " + sourceIndex); + } + + setPlayTime(Math.floor(currentTime)); } - - // updates the play percentage progress and current playing time - setPlayProgress(currentTime / duration * 100); - setPlayTime(Math.floor(currentTime)); - - } - - - let playEnabled = { "disabled": true } let sourceId = "-"; @@ -125,20 +145,12 @@ const MediaPlayer = (props) => { } playEnabled = {}; } + return ( + + - - - - + { isPlaying @@ -147,7 +159,21 @@ const MediaPlayer = (props) => { } - + setIsPlaying(true)} + onPause={() => setIsPlaying(false)} + onAudioprocess={updatePlayProgress} + onFinish={props.onEnded} + + /> + + ) + /* + const audioRef = React.createRef(); + const [sourceIndex, setSourceIndex] = useState(0); + const [playProgress, setPlayProgress] = useState(0); + const [playTime, setPlayTime] = useState(0); + const parentHandlePlayPause = props.onPlayPause + const playSilence = props.playSilence; + + const handlePause = () => { setIsPlaying(false); } + const handlePlay = () => { setIsPlaying(true); } + const playPause = () => { + const audio = audioRef.current.audioEl.current; + if (isPlaying) { + setIsPlaying(false); + parentHandlePlayPause(false); + audio.pause(); + } else { + setIsPlaying(true); + parentHandlePlayPause(true); + audio.play(); + } + } + + const call = props.call; + + useEffect(() => { + + const audio = audioRef.current.audioEl.current; + const onEnded = props.onEnded; + setSourceIndex(0); + if (call ) { + audio.src = call.url; + const playPromise = audio.play(); + + // In browsers that don’t yet support this functionality, + // playPromise won’t be defined. + if (playPromise !== undefined) { + playPromise.then(function () { + + }).catch(function (error) { + console.log("Automatic playback failed: " + error); + handlePause(); + //onEnded(); + // Show a UI element to let the user manually start playback. + }); + } else { + audio.src = false; + } + } + }, [call]); + + useEffect(() => { + const audio = audioRef.current.audioEl.current; + + setSourceIndex(0); + if ('mediaSession' in navigator) { + navigator.mediaSession.metadata = new MediaMetadata({ + title: "Waiting for Call...", + album: 'OpenMHz', + artwork: [ + { src: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' }, + { src: '/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' }, + { src: '/android-chrome-192x192.png', sizes: '512x512', type: 'image/png' }, + ] + }); + } + audio.src = "data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA/+M4wAAAAAAAAAAAAEluZm8AAAAPAAAAAwAAAbAAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV////////////////////////////////////////////AAAAAExhdmM1OC4xMwAAAAAAAAAAAAAAACQDkAAAAAAAAAGw9wrNaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+MYxAAAAANIAAAAAExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxDsAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxHYAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV"; + const playPromise = audio.play(); + + // In browsers that don’t yet support this functionality, + // playPromise won’t be defined. + if (playPromise !== undefined) { + playPromise.then(function () { + + }).catch(function (error) { + console.log("Automatic playback failed: " + error); + // Show a UI element to let the user manually start playback. + }); + } else { + audio.src = false; + } + + }, [playSilence]); + + + const updatePlayProgress = () => { + const audio = audioRef.current.audioEl.current; + const { currentTime, duration } = audio; + + + // this checks to see if it should display the next Source ID + if (call && ((call.srcList.length - 1) >= (sourceIndex + 1)) && (currentTime > call.srcList[sourceIndex + 1].pos)) { + setSourceIndex(sourceIndex + 1); + } + + // updates the play percentage progress and current playing time + setPlayProgress(currentTime / duration * 100); + setPlayTime(Math.floor(currentTime)); + + + } + + + + + let playEnabled = { "disabled": true } + let sourceId = "-"; + + if (call) { + if (call.srcList.length > sourceIndex) { + sourceId = call.srcList[sourceIndex].src; + } + playEnabled = {}; + } + return ( + + + + + + + { + isPlaying + ? () + : () + } + + + + + + + + + )*/ } export default MediaPlayer; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 7adaca4..b3a4808 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3226,6 +3226,11 @@ prop-types "^15.5.10" react-use-measure "^2.0.4" +"@wavesurfer/react@^1.0.7": + version "1.0.7" + resolved "https://registry.npmjs.org/@wavesurfer/react/-/react-1.0.7.tgz" + integrity sha512-Bw2rd2zqVL1ERy8N9mciacbQW3N5Gn9oRjhq3kjqgtLeE8a1WSTU+4kU0E37541jf432eNZ8lNzuodFFSiNccw== + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz" @@ -10358,6 +10363,11 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +wavesurfer.js@>=7.7.14: + version "7.8.6" + resolved "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.8.6.tgz" + integrity sha512-EDexkMwkkQBTWruhfWQRkTtvRggtKFTPuJX/oZ5wbIZEfyww9EBeLr2mtkxzA1S8TlWPx6adY5WyjOlNYNyHSg== + wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz"