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
-
- Thank You
- } />
+
+ Donate
+
+ Thank You
+ } />
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"