diff --git a/backend/controllers/calls.js b/backend/controllers/calls.js index e20c61e..573a829 100644 --- a/backend/controllers/calls.js +++ b/backend/controllers/calls.js @@ -1,6 +1,6 @@ const { ObjectId } = require('mongodb'); -var { callModel: Call } = require("../models/call"); +const Call = require("../models/call"); const Group = require("../models/group"); const opentelemetry = require('@opentelemetry/api'); var defaultNumResults = 50; diff --git a/backend/controllers/events.js b/backend/controllers/events.js index 09614d0..b8a3203 100644 --- a/backend/controllers/events.js +++ b/backend/controllers/events.js @@ -6,7 +6,8 @@ var Podcast = require("../models/podcast"); var System = require("../models/system"); var Talkgroup = require("../models/talkgroup"); const AdmZip = require("adm-zip"); -var { callModel: Call, frozenCallModel: FrozenCall } = require("../models/call"); +const Call = require("../models/call"); +const FrozenCall = require("../models/frozenCall"); var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com'; const os = require('os'); diff --git a/backend/controllers/uploads.js b/backend/controllers/uploads.js index 41e9ab0..8bd3d78 100644 --- a/backend/controllers/uploads.js +++ b/backend/controllers/uploads.js @@ -2,11 +2,11 @@ const path = require('path'); const fs = require('fs'); const opentelemetry = require('@opentelemetry/api'); const sysStats = require("../sys_stats"); -const System = require("../models/system"); -const Talkgroup = require("../models/talkgroup"); -const { callModel: Call } = require("../models/call"); +const systemSchema = require("../models/systemSchema"); +const talkgroupSchema = require("../models/talkgroupSchema"); +const callSchema = require("../models/callSchema"); const { trace, context } = opentelemetry; - +const mongoose = require("mongoose"); const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3"); const { fromIni } = require("@aws-sdk/credential-providers"); const { NodeHttpHandler } = require('@smithy/node-http-handler'); @@ -21,6 +21,31 @@ const s3_region = process.env['S3_REGION'] ?? 'us-west-1'; const s3_bucket = process.env['S3_BUCKET'] ?? 'openmhz-west'; const s3_profile = process.env['S3_PROFILE'] ?? 'wasabi-account'; const s3_public_url = process.env['S3_PUBLIC_URL'] ?? `${s3_endpoint}/${s3_bucket}`; +const host = process.env['MONGO_NODE_DRIVER_HOST'] != null ? process.env['MONGO_NODE_DRIVER_HOST'] : 'mongo'; +const port = process.env['MONGO_NODE_DRIVER_PORT'] != null ? process.env['MONGO_NODE_DRIVER_PORT'] : 27017; +const mongoUrl = 'mongodb://' + host + ':' + port + '/scanner'; + + +// `asPromise()` returns a promise that resolves to the connection +// once the connection succeeds, or rejects if connection failed. +const mongo_conn_slow = mongoose.createConnection(mongoUrl, { maxPoolSize: 150 }); +const mongo_conn_fast = mongoose.createConnection(mongoUrl, { maxPoolSize: 150 }); +mongo_conn_slow.on('error', console.error); +mongo_conn_fast.on('error', console.error); +mongo_conn_slow.on('disconnected', () => { + console.log('Mongo Slow Disconnected'); + mongo_conn_slow.openUri(mongoUrl, { maxPoolSize: 150 }).catch(console.error); +}); +mongo_conn_fast.on('disconnected', () => { + console.log('Mongo Fast Disconnected'); + mongo_conn_fast.openUri(mongoUrl, { maxPoolSize: 150 }).catch(console.error); +}); + +mongo_conn_slow.model('Call', callSchema); +mongo_conn_fast.model('System', systemSchema); +mongo_conn_fast.model('Talkgroup', talkgroupSchema); + + const client = new S3Client({ requestHandler: new NodeHttpHandler({ @@ -87,7 +112,7 @@ exports.upload = async function (req, res, next) { await tracer.startActiveSpan('validate_system', { parent: trace.getActiveSpan(context.active()) }, async (validateSpan) => { try { - item = await System.findOne({ shortName }, ["key", "ignoreUnknownTalkgroup"]); + item = await mongo_conn_fast.model("System").findOne({ shortName }, ["key", "ignoreUnknownTalkgroup"]); validateSpan.setAttribute('system.exists', !!item); } catch (err) { validateSpan.recordException(err); @@ -117,7 +142,7 @@ exports.upload = async function (req, res, next) { } validateSystemTime = Date.now(); if (item.ignoreUnknownTalkgroup) { - const talkgroupExists = await Talkgroup.exists({ + const talkgroupExists = await mongo_conn_fast.model("Talkgroup").exists({ shortName, num: talkgroupNum, }); @@ -139,7 +164,7 @@ exports.upload = async function (req, res, next) { const object_key = `media/${shortName}/${talkgroupNum}/${shortName}-${talkgroupNum}-${startTime}${path.extname(req.file.originalname)}`; const url = `${s3_public_url}/${object_key}`; - const call = new Call({ + const call = new (mongo_conn_slow.model("Call"))({ shortName, talkgroupNum, objectKey: object_key, diff --git a/backend/models/call.js b/backend/models/call.js index 06d6b55..ff464e0 100644 --- a/backend/models/call.js +++ b/backend/models/call.js @@ -1,45 +1,13 @@ var mongoose = require('mongoose'); +const callSchema = require('./callSchema'); /* { "_id" : ObjectId("5bbc148de26adf00068a0076"), "shortName" : "dcfd", "talkgroupNum" : 11619, "objectKey" : "dcfd-11619-1539052672.m4a", "endpoint" : "nyc3.digitaloceanspaces.com", "bucket" : "openmhz", "time" : ISODate("2018-10-09T02:37:52Z"), "name" : "11619-1539052672.m4a", "freq" : 856987500, "url" : "https://media.openmhz.com/dcfd/2018/10/9/11619-1539052672.m4a", "emergency" : 0, "path" : "/dcfd/2018/10/9/", "srcList" : [ { "pos" : 0, "src" : 1116743 }, { "pos" : 1.8, "src" : 1102467 }, { "pos" : 3.6, "src" : 1116743 } ], "freqList" : [ { "pos" : 0, "freq" : 856987500, "len" : 51744, "errors" : 170, "spikes" : 29 } ], "len" : 6.142 } */ -var srcSchema = mongoose.Schema({ pos: Number, src: String }); -var freqSchema = mongoose.Schema({ pos: Number, freq: Number, len: Number, error: Number, spikes: Number}); -var callSchema = mongoose.Schema({ - talkgroupNum: Number, - shortName: String, - objectKey: String, - endpoint: String, - bucket: String, - time: Date, - name: String, - freq: Number, - errorCount: Number, - spikeCount: Number, - url: String, - emergency: Boolean, - path: String, - len: Number, - star: { - type: Number, - default: 0 - }, - srcList: [srcSchema] -}); -var frozenCallSchema = mongoose.Schema(); -frozenCallSchema.add(callSchema).add({ - systemName: String, - systemDescription: String, - talkgroupDescription: String, - talkgroupAlpha: String, - -}); -exports.frozenCallSchema = frozenCallSchema; -exports.frozenCallModel = mongoose.model('FrozenCall', frozenCallSchema); -exports.callSchema = callSchema; -exports.callModel = mongoose.model('Call', callSchema); + +module.exports = mongoose.model('Call', callSchema); diff --git a/backend/models/callSchema.js b/backend/models/callSchema.js new file mode 100644 index 0000000..6dbcebf --- /dev/null +++ b/backend/models/callSchema.js @@ -0,0 +1,27 @@ +var mongoose = require('mongoose'); + +var srcSchema = mongoose.Schema({ pos: Number, src: String }); + +const callSchema = mongoose.Schema({ + talkgroupNum: Number, + shortName: String, + objectKey: String, + endpoint: String, + bucket: String, + time: Date, + name: String, + freq: Number, + errorCount: Number, + spikeCount: Number, + url: String, + emergency: Boolean, + path: String, + len: Number, + star: { + type: Number, + default: 0 + }, + srcList: [srcSchema] +}); + +module.exports = callSchema; \ No newline at end of file diff --git a/backend/models/event.js b/backend/models/event.js index da0169b..ed9bbc4 100644 --- a/backend/models/event.js +++ b/backend/models/event.js @@ -1,5 +1,5 @@ var mongoose = require('mongoose'); -var {frozenCallSchema} = require('./call'); +const frozenCallSchema = require('./frozenCallSchema'); var eventSchema = mongoose.Schema({ diff --git a/backend/models/frozenCall.js b/backend/models/frozenCall.js new file mode 100644 index 0000000..06139d3 --- /dev/null +++ b/backend/models/frozenCall.js @@ -0,0 +1,4 @@ +var mongoose = require('mongoose'); +const frozenCallSchema = require('./frozenCallSchema'); + +module.exports = mongoose.model('FrozenCall', frozenCallSchema); \ No newline at end of file diff --git a/backend/models/frozenCallSchema.js b/backend/models/frozenCallSchema.js new file mode 100644 index 0000000..801c262 --- /dev/null +++ b/backend/models/frozenCallSchema.js @@ -0,0 +1,14 @@ +var mongoose = require('mongoose'); +const callSchema = require('./callSchema'); + +var frozenCallSchema = mongoose.Schema(); + +frozenCallSchema.add(callSchema).add({ + systemName: String, + systemDescription: String, + talkgroupDescription: String, + talkgroupAlpha: String, + +}); + +module.exports = frozenCallSchema; \ No newline at end of file diff --git a/backend/models/system.js b/backend/models/system.js index c761751..fa19fb6 100644 --- a/backend/models/system.js +++ b/backend/models/system.js @@ -1,37 +1,6 @@ var mongoose = require('mongoose'); -var bcrypt = require('bcrypt-nodejs'); -var User = require('./user'); +const systemSchema = require('./systemSchema'); -var systemSchema = mongoose.Schema({ - name: String, - shortName: String, - systemType: String, - county: String, - country: String, - city: String, - state: String, - description: String, - status: String, - showScreenName: Boolean, - allowContact: { - type: Boolean, - default: true - }, - callAvg: Number, - callCount: Number, - ignoreUnknownTalkgroup : Boolean, - active: {type: Boolean, default: false}, - lastActive: Date, - userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, - key: String -}); - -systemSchema.methods.generateHash = function(password) { - return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); -}; - -systemSchema.methods.validPassword = function(password) { - return bcrypt.compareSync(password, this.local.password); -}; +exports.systemSchema = systemSchema; module.exports = mongoose.model('System', systemSchema); diff --git a/backend/models/systemSchema.js b/backend/models/systemSchema.js new file mode 100644 index 0000000..0f1557b --- /dev/null +++ b/backend/models/systemSchema.js @@ -0,0 +1,37 @@ +var mongoose = require('mongoose'); +var bcrypt = require('bcrypt-nodejs'); +var User = require('./user'); + +const systemSchema = mongoose.Schema({ + name: String, + shortName: String, + systemType: String, + county: String, + country: String, + city: String, + state: String, + description: String, + status: String, + showScreenName: Boolean, + allowContact: { + type: Boolean, + default: true + }, + callAvg: Number, + callCount: Number, + ignoreUnknownTalkgroup : Boolean, + active: {type: Boolean, default: false}, + lastActive: Date, + userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, + key: String + }); + + systemSchema.methods.generateHash = function(password) { + return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); + }; + + systemSchema.methods.validPassword = function(password) { + return bcrypt.compareSync(password, this.local.password); + }; + + module.exports = systemSchema; \ No newline at end of file diff --git a/backend/models/talkgroup.js b/backend/models/talkgroup.js index 0e9a812..44211d2 100644 --- a/backend/models/talkgroup.js +++ b/backend/models/talkgroup.js @@ -1,16 +1,4 @@ var mongoose = require('mongoose'); - -var talkgroupSchema = mongoose.Schema({ - shortName: String, - userId: String, - num: Number, - mode: String, - alpha: String, - description: String, - tag: String, - group: String, - priority: Number -}); - +const talkgroupSchema = require('./talkgroupSchema'); module.exports = mongoose.model('Talkgroup', talkgroupSchema); diff --git a/backend/models/talkgroupSchema.js b/backend/models/talkgroupSchema.js new file mode 100644 index 0000000..ce0fa39 --- /dev/null +++ b/backend/models/talkgroupSchema.js @@ -0,0 +1,15 @@ +var mongoose = require('mongoose'); + +const talkgroupSchema = mongoose.Schema({ + shortName: String, + userId: String, + num: Number, + mode: String, + alpha: String, + description: String, + tag: String, + group: String, + priority: Number +}); + +module.exports = talkgroupSchema; \ No newline at end of file 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/public/silence.m4a b/frontend/public/silence.m4a new file mode 100644 index 0000000..c5036b2 Binary files /dev/null and b/frontend/public/silence.m4a differ diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 2da2997..cb71813 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, @@ -18,7 +18,11 @@ import { Message, MessageHeader, Button, - ButtonContent + ButtonContent, + Checkbox, + GridColumn, Grid, + GridRow, + Form } from "semantic-ui-react"; import "./CallPlayer.css"; import queryString from '../query-string'; @@ -96,13 +100,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 +136,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 +157,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 +173,7 @@ useEffect(() => { useEffect(() => { if (system && system.status && system.status.length > 0) { - setStatusVisible(true); + setStatusVisible(true); } }, [system]) @@ -177,13 +181,17 @@ useEffect(() => { setStatusVisible(false); } + let autoplayClassName = "button-item"; + if (autoPlay) { + autoplayClassName += " active"; + } return (
{statusVisible && - + System Status{system.status} - + } { >
- +
@@ -203,19 +211,41 @@ useEffect(() => { - - handleAutoPlay(autoPlay)}>Autoplay + +
+
handleAutoPlay(autoPlay)}>Autoplay
+
+ +
+ + {/*
+ + Donate + + + } />
*/} +
+ Download +
+
+ Link +
+ +
+ + + {/* handleAutoPlay(autoPlay)}>Autoplay - - Donate - - - } /> + + Donate + + + } /> Download Link - + */}
diff --git a/frontend/src/Call/components/GroupModal.js b/frontend/src/Call/components/GroupModal.js index da70d4b..f2423fe 100644 --- a/frontend/src/Call/components/GroupModal.js +++ b/frontend/src/Call/components/GroupModal.js @@ -72,7 +72,7 @@ function GroupModal(props) { return ( - + handleDone(onClose)} size='tiny'> {system && system.name} diff --git a/frontend/src/Call/components/MediaPlayer.css b/frontend/src/Call/components/MediaPlayer.css new file mode 100644 index 0000000..121f82f --- /dev/null +++ b/frontend/src/Call/components/MediaPlayer.css @@ -0,0 +1,94 @@ +.mediaplayer-container { + /* We first create a flex layout context */ + display: flex; + + + + padding: 0; + margin: 0; + list-style: none; + } + .item-container { + /* We first create a flex layout context */ + display: flex; + + + + padding: 0; + margin: 0; + list-style: none; + } + + .label-item { + padding: 9px 2px 5px 2px; + width: 130px; + color: white; + font-weight: bold; + text-align: center; + } + .mediaplayer { + flex-grow: 1; + color: white; + font-weight: bold; + text-align: center; + } + .mediaplayer-item { + padding: 9px 5px 5px 5px; + flex-grow: 1; + color: white; + font-weight: bold; + text-align: center; + } + .link-item { + display: flex; + justify-content: center; + align-items: center; + padding: 5px; + width: 100px; + color: white; + font-weight: bold; + text-align: center; + } + .button-item { + cursor:pointer; + display: flex; + justify-content: center; + align-items: center; + padding: 5px; + width: 100px; + color: white; + font-weight: bold; + } + .active{ +background-color: #5c5c5c; + } + + .link-item a { + + color: white; + text-align: center; +} + +.button-item a { + + color: white; + text-align: center; +} + + @media only screen and (max-width: 767px) { + + .button-item { + + width: 40px; + color: white; + + } + .label-item { + width: 50px; + color: white; + } +} + +i.icon { + font-size: inherit; +} \ No newline at end of file diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index b840af0..1e7f652 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -1,71 +1,42 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import { Menu, Icon, Progress, - Label + Label, + Grid, + GridRow, + GridColumn, + LabelGroup, + Button } from "semantic-ui-react"; import ReactAudioPlayer from 'react-audio-player' +import WavesurferPlayer from '@wavesurfer/react' +import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.js'; + + +import { is } from "date-fns/locale"; +import "./MediaPlayer.css"; + const MediaPlayer = (props) => { - const audioRef = React.createRef(); - const [sourceIndex, setSourceIndex] = useState(0); - const [playProgress, setPlayProgress] = useState(0); - 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 call = props.call; + const [sourceIndex, setSourceIndex] = useState(0); + const [wavesurfer, setWavesurfer] = useState(null) + const [isPlaying, setIsPlaying] = useState(false) + const [playTime, setPlayTime] = useState(0); + const playSilence = props.playSilence; + const parentHandlePlayPause = props.onPlayPause + const regionsPlugin = useMemo(() => RegionsPlugin.create(), []); + const plugins = useMemo(() => [regionsPlugin], [regionsPlugin]); + 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...", @@ -77,44 +48,82 @@ const MediaPlayer = (props) => { ] }); } - 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 () { + wavesurfer && wavesurfer.load("/silence.m4a"); + regionsPlugin.clearRegions(); - }).catch(function (error) { - console.log("Automatic playback failed: " + error); - // Show a UI element to let the user manually start playback. - }); - } else { - audio.src = false; - } + // // 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; + useEffect(() => { + setSourceIndex(0); + }, [call]); - // 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); + const onReady = (ws) => { + setWavesurfer(ws) + setIsPlaying(false) + regionsPlugin.clearRegions(); + if (call) { + call.srcList.forEach(src => { + regionsPlugin.addRegion({ + start: src.pos, + color: "rgba(128, 128, 128, 1.0)", + drag: false, + resize: false + }); + }); } + } - // updates the play percentage progress and current playing time - setPlayProgress(currentTime / duration * 100); - setPlayTime(Math.floor(currentTime)); - + const onPlay = () => { + setIsPlaying(true); + parentHandlePlayPause(true); } + const onPause = () => { + setIsPlaying(false); + parentHandlePlayPause(false); + } + const onPlayPause = () => { + wavesurfer && wavesurfer.playPause() + } + + const updatePlayProgress = () => { + + 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); + console.log("sourceIndex: " + sourceIndex); + } + + setPlayTime(Math.floor(currentTime)); + } + } let playEnabled = { "disabled": true } let sourceId = "-"; @@ -125,40 +134,202 @@ const MediaPlayer = (props) => { } playEnabled = {}; } + return ( - - - +
- { - isPlaying - ? () - : () - } - - - - - - - + +
+ { + isPlaying + ? () + : () + } +
+
+ + +
+ +
+ + + + + +
+
) + /* + 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" diff --git a/nginx-proxy/prod/vhosts/site.template b/nginx-proxy/prod/vhosts/site.template index 5d0f33a..c57f7bc 100644 --- a/nginx-proxy/prod/vhosts/site.template +++ b/nginx-proxy/prod/vhosts/site.template @@ -38,13 +38,27 @@ server { location /favicon.ico { proxy_pass http://frontend:3000; + expires 1M; + add_header Surrogate-Control "public, max-age=2629746"; + add_header Cache-Control "max-age=2629746, public"; access_log off; } location /*.png { - proxy_pass http://frontend:3000; - access_log off; - } + proxy_pass http://frontend:3000; + access_log off; + expires 1M; + add_header Surrogate-Control "public, max-age=2629746"; + add_header Cache-Control "max-age=2629746, public"; + } + + location /*.m4a { + proxy_pass http://frontend:3000; + access_log off; + expires 1M; + add_header Surrogate-Control "public, max-age=2629746"; + add_header Cache-Control "max-age=2629746, public"; + } location / { proxy_pass http://frontend:3000; diff --git a/nginx-proxy/test/vhosts/site.template b/nginx-proxy/test/vhosts/site.template index aa12004..e4d96d7 100644 --- a/nginx-proxy/test/vhosts/site.template +++ b/nginx-proxy/test/vhosts/site.template @@ -16,13 +16,29 @@ server { location /favicon.ico { proxy_pass http://frontend:3000; - access_log off; + expires 1M; + add_header Surrogate-Control "public, max-age=604800"; + add_header Cache-Control "max-age=604800, public"; + access_log /dev/stdout main; } - location /*.png { - proxy_pass http://frontend:3000; - access_log off; - } + location ~* \.(gif|jpg|png)$ { + proxy_pass http://frontend:3000; + + add_header Pragma public; + add_header Surrogate-Control "public, max-age=604800"; + add_header Cache-Control "max-age=604800, public"; + expires 1M; + } + + location /silence.m4a { + proxy_pass http://frontend:3000; + access_log /dev/stdout main; + + add_header Surrogate-Control "public, max-age=604800"; + add_header Cache-Control "max-age=604800, public"; + expires 1M; + } location / { proxy_pass http://frontend:3000; @@ -80,6 +96,6 @@ server { proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; - access_log off; + access_log /dev/stdout main; } }