mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-15 02:07:50 -05:00
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
const { ObjectId } = require('mongodb');
|
const { ObjectId } = require('mongodb');
|
||||||
var { callModel: Call } = require("../models/call");
|
const Call = require("../models/call");
|
||||||
const Group = require("../models/group");
|
const Group = require("../models/group");
|
||||||
const opentelemetry = require('@opentelemetry/api');
|
const opentelemetry = require('@opentelemetry/api');
|
||||||
var defaultNumResults = 50;
|
var defaultNumResults = 50;
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ var Podcast = require("../models/podcast");
|
|||||||
var System = require("../models/system");
|
var System = require("../models/system");
|
||||||
var Talkgroup = require("../models/talkgroup");
|
var Talkgroup = require("../models/talkgroup");
|
||||||
const AdmZip = require("adm-zip");
|
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';
|
var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com';
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ const path = require('path');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const opentelemetry = require('@opentelemetry/api');
|
const opentelemetry = require('@opentelemetry/api');
|
||||||
const sysStats = require("../sys_stats");
|
const sysStats = require("../sys_stats");
|
||||||
const System = require("../models/system");
|
const systemSchema = require("../models/systemSchema");
|
||||||
const Talkgroup = require("../models/talkgroup");
|
const talkgroupSchema = require("../models/talkgroupSchema");
|
||||||
const { callModel: Call } = require("../models/call");
|
const callSchema = require("../models/callSchema");
|
||||||
const { trace, context } = opentelemetry;
|
const { trace, context } = opentelemetry;
|
||||||
|
const mongoose = require("mongoose");
|
||||||
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
|
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
|
||||||
const { fromIni } = require("@aws-sdk/credential-providers");
|
const { fromIni } = require("@aws-sdk/credential-providers");
|
||||||
const { NodeHttpHandler } = require('@smithy/node-http-handler');
|
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_bucket = process.env['S3_BUCKET'] ?? 'openmhz-west';
|
||||||
const s3_profile = process.env['S3_PROFILE'] ?? 'wasabi-account';
|
const s3_profile = process.env['S3_PROFILE'] ?? 'wasabi-account';
|
||||||
const s3_public_url = process.env['S3_PUBLIC_URL'] ?? `${s3_endpoint}/${s3_bucket}`;
|
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({
|
const client = new S3Client({
|
||||||
requestHandler: new NodeHttpHandler({
|
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) => {
|
await tracer.startActiveSpan('validate_system', { parent: trace.getActiveSpan(context.active()) }, async (validateSpan) => {
|
||||||
try {
|
try {
|
||||||
item = await System.findOne({ shortName }, ["key", "ignoreUnknownTalkgroup"]);
|
item = await mongo_conn_fast.model("System").findOne({ shortName }, ["key", "ignoreUnknownTalkgroup"]);
|
||||||
validateSpan.setAttribute('system.exists', !!item);
|
validateSpan.setAttribute('system.exists', !!item);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
validateSpan.recordException(err);
|
validateSpan.recordException(err);
|
||||||
@@ -117,7 +142,7 @@ exports.upload = async function (req, res, next) {
|
|||||||
}
|
}
|
||||||
validateSystemTime = Date.now();
|
validateSystemTime = Date.now();
|
||||||
if (item.ignoreUnknownTalkgroup) {
|
if (item.ignoreUnknownTalkgroup) {
|
||||||
const talkgroupExists = await Talkgroup.exists({
|
const talkgroupExists = await mongo_conn_fast.model("Talkgroup").exists({
|
||||||
shortName,
|
shortName,
|
||||||
num: talkgroupNum,
|
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 object_key = `media/${shortName}/${talkgroupNum}/${shortName}-${talkgroupNum}-${startTime}${path.extname(req.file.originalname)}`;
|
||||||
const url = `${s3_public_url}/${object_key}`;
|
const url = `${s3_public_url}/${object_key}`;
|
||||||
|
|
||||||
const call = new Call({
|
const call = new (mongo_conn_slow.model("Call"))({
|
||||||
shortName,
|
shortName,
|
||||||
talkgroupNum,
|
talkgroupNum,
|
||||||
objectKey: object_key,
|
objectKey: object_key,
|
||||||
|
|||||||
@@ -1,45 +1,13 @@
|
|||||||
var mongoose = require('mongoose');
|
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 }
|
{ "_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);
|
||||||
|
|||||||
27
backend/models/callSchema.js
Normal file
27
backend/models/callSchema.js
Normal file
@@ -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;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
var mongoose = require('mongoose');
|
var mongoose = require('mongoose');
|
||||||
var {frozenCallSchema} = require('./call');
|
const frozenCallSchema = require('./frozenCallSchema');
|
||||||
|
|
||||||
|
|
||||||
var eventSchema = mongoose.Schema({
|
var eventSchema = mongoose.Schema({
|
||||||
|
|||||||
4
backend/models/frozenCall.js
Normal file
4
backend/models/frozenCall.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
var mongoose = require('mongoose');
|
||||||
|
const frozenCallSchema = require('./frozenCallSchema');
|
||||||
|
|
||||||
|
module.exports = mongoose.model('FrozenCall', frozenCallSchema);
|
||||||
14
backend/models/frozenCallSchema.js
Normal file
14
backend/models/frozenCallSchema.js
Normal file
@@ -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;
|
||||||
@@ -1,37 +1,6 @@
|
|||||||
var mongoose = require('mongoose');
|
var mongoose = require('mongoose');
|
||||||
var bcrypt = require('bcrypt-nodejs');
|
const systemSchema = require('./systemSchema');
|
||||||
var User = require('./user');
|
|
||||||
|
|
||||||
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);
|
module.exports = mongoose.model('System', systemSchema);
|
||||||
|
|||||||
37
backend/models/systemSchema.js
Normal file
37
backend/models/systemSchema.js
Normal file
@@ -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;
|
||||||
@@ -1,16 +1,4 @@
|
|||||||
var mongoose = require('mongoose');
|
var mongoose = require('mongoose');
|
||||||
|
const talkgroupSchema = require('./talkgroupSchema');
|
||||||
var talkgroupSchema = mongoose.Schema({
|
|
||||||
shortName: String,
|
|
||||||
userId: String,
|
|
||||||
num: Number,
|
|
||||||
mode: String,
|
|
||||||
alpha: String,
|
|
||||||
description: String,
|
|
||||||
tag: String,
|
|
||||||
group: String,
|
|
||||||
priority: Number
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = mongoose.model('Talkgroup', talkgroupSchema);
|
module.exports = mongoose.model('Talkgroup', talkgroupSchema);
|
||||||
|
|||||||
15
backend/models/talkgroupSchema.js
Normal file
15
backend/models/talkgroupSchema.js
Normal file
@@ -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;
|
||||||
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
@@ -24,6 +24,7 @@
|
|||||||
"@visx/scale": "^3.0.0",
|
"@visx/scale": "^3.0.0",
|
||||||
"@visx/shape": "^3.0.0",
|
"@visx/shape": "^3.0.0",
|
||||||
"@visx/tooltip": "^3.1.2",
|
"@visx/tooltip": "^3.1.2",
|
||||||
|
"@wavesurfer/react": "^1.0.7",
|
||||||
"axios": "1.2.1",
|
"axios": "1.2.1",
|
||||||
"d3-array": "^3.2.3",
|
"d3-array": "^3.2.3",
|
||||||
"d3-time-format": "^4.1.0",
|
"d3-time-format": "^4.1.0",
|
||||||
@@ -5783,6 +5784,15 @@
|
|||||||
"react-dom": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0"
|
"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": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||||
@@ -17748,6 +17758,12 @@
|
|||||||
"node": ">=10.13.0"
|
"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": {
|
"node_modules/wbuf": {
|
||||||
"version": "1.7.3",
|
"version": "1.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@visx/scale": "^3.0.0",
|
"@visx/scale": "^3.0.0",
|
||||||
"@visx/shape": "^3.0.0",
|
"@visx/shape": "^3.0.0",
|
||||||
"@visx/tooltip": "^3.1.2",
|
"@visx/tooltip": "^3.1.2",
|
||||||
|
"@wavesurfer/react": "^1.0.7",
|
||||||
"axios": "1.2.1",
|
"axios": "1.2.1",
|
||||||
"d3-array": "^3.2.3",
|
"d3-array": "^3.2.3",
|
||||||
"d3-time-format": "^4.1.0",
|
"d3-time-format": "^4.1.0",
|
||||||
|
|||||||
BIN
frontend/public/silence.m4a
Normal file
BIN
frontend/public/silence.m4a
Normal file
Binary file not shown.
@@ -6,7 +6,7 @@ import CallInfo from "./components/CallInfo";
|
|||||||
import ListCalls from "./components/ListCalls";
|
import ListCalls from "./components/ListCalls";
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import { useGetTalkgroupsQuery } from '../features/api/apiSlice'
|
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 { useInView } from 'react-intersection-observer';
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
@@ -18,7 +18,11 @@ import {
|
|||||||
Message,
|
Message,
|
||||||
MessageHeader,
|
MessageHeader,
|
||||||
Button,
|
Button,
|
||||||
ButtonContent
|
ButtonContent,
|
||||||
|
Checkbox,
|
||||||
|
GridColumn, Grid,
|
||||||
|
GridRow,
|
||||||
|
Form
|
||||||
} from "semantic-ui-react";
|
} from "semantic-ui-react";
|
||||||
import "./CallPlayer.css";
|
import "./CallPlayer.css";
|
||||||
import queryString from '../query-string';
|
import queryString from '../query-string';
|
||||||
@@ -96,13 +100,13 @@ function CallPlayer(props) {
|
|||||||
if (currentIndex > 0) {
|
if (currentIndex > 0) {
|
||||||
const nextCallId = callsData.ids[currentIndex - 1];
|
const nextCallId = callsData.ids[currentIndex - 1];
|
||||||
const nextCall = callsData.entities[nextCallId];
|
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);
|
setCurrentCall(nextCall);
|
||||||
dispatch(playedCall(nextCall._id));
|
dispatch(playedCall(nextCall._id));
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
} else if (backgroundAutoplay) {
|
} 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!
|
// 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 {
|
} else {
|
||||||
// there are no more calls to play and backgroundAutoplay is disabled
|
// there are no more calls to play and backgroundAutoplay is disabled
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
@@ -132,20 +136,20 @@ function CallPlayer(props) {
|
|||||||
}, [loadOlderInView]);
|
}, [loadOlderInView]);
|
||||||
|
|
||||||
// This handles when a Call is specified in the URI
|
// This handles when a Call is specified in the URI
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.initialCallId && callsData) {
|
if (props.initialCallId && callsData) {
|
||||||
const call = callsData.entities[props.initialCallId];
|
const call = callsData.entities[props.initialCallId];
|
||||||
if (call && !selectedInitialCall) {
|
if (call && !selectedInitialCall) {
|
||||||
const time = new Date(call.time);
|
const time = new Date(call.time);
|
||||||
|
|
||||||
console.log("Playing Initial Call: " + call._id + " Start Time: " + time.toLocaleTimeString() );
|
console.log("Playing Initial Call: " + call._id + " Start Time: " + time.toLocaleTimeString());
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
setCurrentCall(call);
|
setCurrentCall(call);
|
||||||
setSelectedInitialCall(true); // signals that it has selected the initial call - you only want to do this once
|
setSelectedInitialCall(true); // signals that it has selected the initial call - you only want to do this once
|
||||||
dispatch(playedCall(call._id));
|
dispatch(playedCall(call._id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, [props.initialCallId, callsData])
|
||||||
}, [props.initialCallId, callsData])
|
|
||||||
|
|
||||||
// Triggered when a new call is selected in the parent component.
|
// Triggered when a new call is selected in the parent component.
|
||||||
// This happens when a new call comes over the socket
|
// 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
|
// we should only set the selectCallId to be the current call when AutoPlay is selected
|
||||||
// and when there isn't another call already playing
|
// and when there isn't another call already playing
|
||||||
useEffect(() => {
|
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) {
|
if (selectCallId && callsData && !isPlaying && autoPlay) {
|
||||||
const call = callsData.entities[selectCallId];
|
const call = callsData.entities[selectCallId];
|
||||||
if (call) {
|
if (call) {
|
||||||
const time = new Date(call.time);
|
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);
|
setIsPlaying(true);
|
||||||
setCurrentCall(call);
|
setCurrentCall(call);
|
||||||
dispatch(playedCall(call._id));
|
dispatch(playedCall(call._id));
|
||||||
@@ -169,7 +173,7 @@ useEffect(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (system && system.status && system.status.length > 0) {
|
if (system && system.status && system.status.length > 0) {
|
||||||
setStatusVisible(true);
|
setStatusVisible(true);
|
||||||
}
|
}
|
||||||
}, [system])
|
}, [system])
|
||||||
|
|
||||||
@@ -177,13 +181,17 @@ useEffect(() => {
|
|||||||
setStatusVisible(false);
|
setStatusVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let autoplayClassName = "button-item";
|
||||||
|
if (autoPlay) {
|
||||||
|
autoplayClassName += " active";
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div ref={positionRef}>
|
<div ref={positionRef}>
|
||||||
<Container className="main" >
|
<Container className="main" >
|
||||||
{statusVisible &&
|
{statusVisible &&
|
||||||
<Sticky offset={60} context={positionRef}>
|
<Sticky offset={60} context={positionRef}>
|
||||||
<Message floating onDismiss={handleStatusDismiss} warning> <MessageHeader>System Status</MessageHeader>{system.status}</Message>
|
<Message floating onDismiss={handleStatusDismiss} warning> <MessageHeader>System Status</MessageHeader>{system.status}</Message>
|
||||||
</Sticky>
|
</Sticky>
|
||||||
}
|
}
|
||||||
<Sidebar.Pushable>
|
<Sidebar.Pushable>
|
||||||
<Sidebar.Pusher
|
<Sidebar.Pusher
|
||||||
@@ -191,7 +199,7 @@ useEffect(() => {
|
|||||||
>
|
>
|
||||||
|
|
||||||
<div ref={loadNewerRef} />
|
<div ref={loadNewerRef} />
|
||||||
|
|
||||||
<ListCalls callsData={callsData} activeCallId={currentCallId} talkgroups={talkgroupsData ? talkgroupsData.talkgroups : false} playCall={playCall} />
|
<ListCalls callsData={callsData} activeCallId={currentCallId} talkgroups={talkgroupsData ? talkgroupsData.talkgroups : false} playCall={playCall} />
|
||||||
<div ref={loadOlderRef} style={{ height: 50 }} />
|
<div ref={loadOlderRef} style={{ height: 50 }} />
|
||||||
</Sidebar.Pusher>
|
</Sidebar.Pusher>
|
||||||
@@ -203,19 +211,41 @@ useEffect(() => {
|
|||||||
</Rail>
|
</Rail>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<Menu fixed="bottom" compact inverted >
|
<Menu fixed="bottom" inverted vertical fluid>
|
||||||
<Menu.Item active={autoPlay} onClick={() => handleAutoPlay(autoPlay)}><Icon name="level up" /><span className="desktop-only">Autoplay</span></Menu.Item>
|
<div className="item-container">
|
||||||
|
<div className={autoplayClassName} onClick={() => handleAutoPlay(autoPlay)}><Icon name="level up" /><span className="desktop-only">Autoplay</span></div>
|
||||||
|
<div className="mediaplayer">
|
||||||
|
<MediaPlayer call={currentCall} playSilence={silenceCount} onEnded={callEnded} onPlayPause={handlePlayPause} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="button-item desktop-only"><SupportModal trigger={<Button color='grey' animated='fade' size={"small"}>
|
||||||
|
<ButtonContent visible color="red">
|
||||||
|
<Icon name='heart' /> Donate
|
||||||
|
</ButtonContent>
|
||||||
|
<ButtonContent hidden>Thank You</ButtonContent>
|
||||||
|
</Button>} /></div> */}
|
||||||
|
<div className="link-item desktop-only" >
|
||||||
|
<a href={callDownload}><Icon name="download" />Download</a>
|
||||||
|
</div>
|
||||||
|
<div className="link-item desktop-only">
|
||||||
|
<a href={callLink}><Icon name="at" />Link</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* <Menu.Item active={autoPlay} onClick={() => handleAutoPlay(autoPlay)}><Icon name="level up" /><span className="desktop-only">Autoplay</span></Menu.Item>
|
||||||
<MediaPlayer call={currentCall} playSilence={silenceCount} onEnded={callEnded} onPlayPause={handlePlayPause} />
|
<MediaPlayer call={currentCall} playSilence={silenceCount} onEnded={callEnded} onPlayPause={handlePlayPause} />
|
||||||
<Menu.Menu position="right" className="desktop-only">
|
<Menu.Menu position="right" className="desktop-only">
|
||||||
<Menu.Item><SupportModal trigger={<Button color='grey' animated='fade'>
|
<Menu.Item><SupportModal trigger={<Button color='grey' animated='fade'>
|
||||||
<ButtonContent visible color="red">
|
<ButtonContent visible color="red">
|
||||||
<Icon name='heart' /> Donate
|
<Icon name='heart' /> Donate
|
||||||
</ButtonContent>
|
</ButtonContent>
|
||||||
<ButtonContent hidden>Thank You</ButtonContent>
|
<ButtonContent hidden>Thank You</ButtonContent>
|
||||||
</Button>} /></Menu.Item>
|
</Button>} /></Menu.Item>
|
||||||
<Menu.Item><a href={callDownload}><Icon name="download" />Download</a></Menu.Item>
|
<Menu.Item><a href={callDownload}><Icon name="download" />Download</a></Menu.Item>
|
||||||
<Menu.Item><a href={callLink}><Icon name="at" />Link</a></Menu.Item>
|
<Menu.Item><a href={callLink}><Icon name="at" />Link</a></Menu.Item>
|
||||||
</Menu.Menu>
|
</Menu.Menu> */}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ function GroupModal(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Modal open={props.open} onClose={onClose} size='tiny'>
|
<Modal open={props.open} onClose={() => handleDone(onClose)} size='tiny'>
|
||||||
|
|
||||||
<Modal.Header>{system && system.name}</Modal.Header>
|
<Modal.Header>{system && system.name}</Modal.Header>
|
||||||
<Modal.Content >
|
<Modal.Content >
|
||||||
|
|||||||
94
frontend/src/Call/components/MediaPlayer.css
Normal file
94
frontend/src/Call/components/MediaPlayer.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,71 +1,42 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
Menu,
|
Menu,
|
||||||
Icon,
|
Icon,
|
||||||
Progress,
|
Progress,
|
||||||
Label
|
Label,
|
||||||
|
Grid,
|
||||||
|
GridRow,
|
||||||
|
GridColumn,
|
||||||
|
LabelGroup,
|
||||||
|
Button
|
||||||
} from "semantic-ui-react";
|
} from "semantic-ui-react";
|
||||||
import ReactAudioPlayer from 'react-audio-player'
|
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 MediaPlayer = (props) => {
|
||||||
|
|
||||||
const audioRef = React.createRef();
|
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 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(() => {
|
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);
|
setSourceIndex(0);
|
||||||
|
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
title: "Waiting for Call...",
|
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,
|
wavesurfer && wavesurfer.load("/silence.m4a");
|
||||||
// playPromise won’t be defined.
|
regionsPlugin.clearRegions();
|
||||||
if (playPromise !== undefined) {
|
|
||||||
playPromise.then(function () {
|
|
||||||
|
|
||||||
}).catch(function (error) {
|
// // In browsers that don’t yet support this functionality,
|
||||||
console.log("Automatic playback failed: " + error);
|
// // playPromise won’t be defined.
|
||||||
// Show a UI element to let the user manually start playback.
|
// if (playPromise !== undefined) {
|
||||||
});
|
// playPromise.then(function () {
|
||||||
} else {
|
|
||||||
audio.src = false;
|
// }).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]);
|
}, [playSilence]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const updatePlayProgress = () => {
|
setSourceIndex(0);
|
||||||
const audio = audioRef.current.audioEl.current;
|
}, [call]);
|
||||||
const { currentTime, duration } = audio;
|
|
||||||
|
|
||||||
|
|
||||||
// this checks to see if it should display the next Source ID
|
const onReady = (ws) => {
|
||||||
if (call && ((call.srcList.length - 1) >= (sourceIndex + 1)) && (currentTime > call.srcList[sourceIndex + 1].pos)) {
|
setWavesurfer(ws)
|
||||||
setSourceIndex(sourceIndex + 1);
|
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
|
const onPlay = () => {
|
||||||
setPlayProgress(currentTime / duration * 100);
|
setIsPlaying(true);
|
||||||
setPlayTime(Math.floor(currentTime));
|
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 playEnabled = { "disabled": true }
|
||||||
let sourceId = "-";
|
let sourceId = "-";
|
||||||
@@ -125,40 +134,202 @@ const MediaPlayer = (props) => {
|
|||||||
}
|
}
|
||||||
playEnabled = {};
|
playEnabled = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu.Menu>
|
|
||||||
<ReactAudioPlayer
|
|
||||||
ref={audioRef}
|
|
||||||
onPause={handlePause}
|
|
||||||
onPlay={handlePlay}
|
|
||||||
listenInterval={100}
|
|
||||||
onListen={updatePlayProgress}
|
|
||||||
onEnded={props.onEnded}
|
|
||||||
autoPlay
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<Menu.Item onClick={playPause} >
|
<div className="mediaplayer-container">
|
||||||
|
|
||||||
{
|
|
||||||
isPlaying
|
|
||||||
? (<Icon name="pause" />)
|
|
||||||
: (<Icon name="play" />)
|
|
||||||
}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
<Progress inverted percent={playProgress} />
|
|
||||||
<Label color="black">
|
|
||||||
{playTime}
|
|
||||||
Sec
|
|
||||||
</Label>
|
|
||||||
<Label color="black" className="desktop-only">
|
|
||||||
{sourceId}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Menu>
|
<div className="button-item" onClick={onPlayPause}>
|
||||||
|
{
|
||||||
|
isPlaying
|
||||||
|
? (<Icon name="pause" />)
|
||||||
|
: (<Icon name="play" />)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="mediaplayer-item">
|
||||||
|
|
||||||
|
<WavesurferPlayer
|
||||||
|
autoplay={true}
|
||||||
|
height={25}
|
||||||
|
barWidth ={3}
|
||||||
|
barGap={3}
|
||||||
|
barRadius={6}
|
||||||
|
waveColor="#E81B39"
|
||||||
|
url={call.url}
|
||||||
|
onReady={onReady}
|
||||||
|
onPlay={onPlay}
|
||||||
|
onPause={onPause}
|
||||||
|
onAudioprocess={updatePlayProgress}
|
||||||
|
onFinish={props.onEnded}
|
||||||
|
plugins={plugins}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="label-item">
|
||||||
|
<LabelGroup size="small" >
|
||||||
|
<Label color="black">
|
||||||
|
{playTime}
|
||||||
|
Sec
|
||||||
|
</Label>
|
||||||
|
<Label color="black" className="desktop-only">
|
||||||
|
{sourceId}
|
||||||
|
</Label>
|
||||||
|
</LabelGroup>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
<Menu.Menu>
|
||||||
|
<ReactAudioPlayer
|
||||||
|
ref={audioRef}
|
||||||
|
onPause={handlePause}
|
||||||
|
onPlay={handlePlay}
|
||||||
|
listenInterval={100}
|
||||||
|
onListen={updatePlayProgress}
|
||||||
|
onEnded={props.onEnded}
|
||||||
|
autoPlay
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Menu.Item onClick={playPause} >
|
||||||
|
|
||||||
|
{
|
||||||
|
isPlaying
|
||||||
|
? (<Icon name="pause" />)
|
||||||
|
: (<Icon name="play" />)
|
||||||
|
}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
<Progress inverted percent={playProgress} />
|
||||||
|
<Label color="black">
|
||||||
|
{playTime}
|
||||||
|
Sec
|
||||||
|
</Label>
|
||||||
|
<Label color="black" className="desktop-only">
|
||||||
|
{sourceId}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Menu>
|
||||||
|
)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MediaPlayer;
|
export default MediaPlayer;
|
||||||
|
|||||||
@@ -3226,6 +3226,11 @@
|
|||||||
prop-types "^15.5.10"
|
prop-types "^15.5.10"
|
||||||
react-use-measure "^2.0.4"
|
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":
|
"@webassemblyjs/ast@1.11.1":
|
||||||
version "1.11.1"
|
version "1.11.1"
|
||||||
resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz"
|
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"
|
glob-to-regexp "^0.4.1"
|
||||||
graceful-fs "^4.1.2"
|
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:
|
wbuf@^1.1.0, wbuf@^1.7.3:
|
||||||
version "1.7.3"
|
version "1.7.3"
|
||||||
resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz"
|
resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz"
|
||||||
|
|||||||
@@ -38,13 +38,27 @@ server {
|
|||||||
|
|
||||||
location /favicon.ico {
|
location /favicon.ico {
|
||||||
proxy_pass http://frontend:3000;
|
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;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /*.png {
|
location /*.png {
|
||||||
proxy_pass http://frontend:3000;
|
proxy_pass http://frontend:3000;
|
||||||
access_log off;
|
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 / {
|
location / {
|
||||||
proxy_pass http://frontend:3000;
|
proxy_pass http://frontend:3000;
|
||||||
|
|||||||
@@ -16,13 +16,29 @@ server {
|
|||||||
|
|
||||||
location /favicon.ico {
|
location /favicon.ico {
|
||||||
proxy_pass http://frontend:3000;
|
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 {
|
location ~* \.(gif|jpg|png)$ {
|
||||||
proxy_pass http://frontend:3000;
|
proxy_pass http://frontend:3000;
|
||||||
access_log off;
|
|
||||||
}
|
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 / {
|
location / {
|
||||||
proxy_pass http://frontend:3000;
|
proxy_pass http://frontend:3000;
|
||||||
@@ -80,6 +96,6 @@ server {
|
|||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
access_log off;
|
access_log /dev/stdout main;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user