{ }); } - private playAudio() { + private async playAudio() { // Generate audio element if it doesn't exist const generateAudioElement = () => { const { mediaBlob, recordDuration } = this.state; @@ -324,11 +326,11 @@ export class SessionRecording extends React.Component { audioElement.loop = false; - audioElement.oncanplaythrough = () => { + audioElement.oncanplaythrough = async () => { const duration = recordDuration; if (duration && audioElement.currentTime < duration) { - audioElement.play(); + await audioElement.play(); } }; @@ -336,7 +338,9 @@ export class SessionRecording extends React.Component { }; const audioElement = this.state.audioElement || generateAudioElement(); - if (!audioElement) return; + if (!audioElement) { + return; + } // Draw sweeping timeline const drawSweepingTimeline = () => { @@ -344,16 +348,21 @@ export class SessionRecording extends React.Component { const { width, height, barColorPlay } = this.state.canvasParams; const canvas = this.playbackCanvas.current; - if (!canvas || isPaused) return; + if (!canvas || isPaused) { + return; + } // Once audioElement is fully buffered, we get the true duration let audioDuration = this.state.recordDuration; - if (audioElement.duration !== Infinity) + if (audioElement.duration !== Infinity) { audioDuration = audioElement.duration; + } const progress = width * (audioElement.currentTime / audioDuration); - const canvasContext = canvas.getContext(`2d`); - if (!canvasContext) return; + const canvasContext = canvas.getContext('2d'); + if (!canvasContext) { + return; + } canvasContext.beginPath(); canvasContext.fillStyle = barColorPlay; @@ -384,10 +393,10 @@ export class SessionRecording extends React.Component { audioElement.duration && audioElement.currentTime === audioElement.duration ) { - this.initPlaybackView(); + await this.initPlaybackView(); } - audioElement.play(); + await audioElement.play(); requestAnimationFrame(drawSweepingTimeline); } @@ -400,9 +409,9 @@ export class SessionRecording extends React.Component { }); } - private onDeleteVoiceMessage() { + private async onDeleteVoiceMessage() { this.pauseAudio(); - this.stopRecordingStream(); + await this.stopRecordingStream(); this.props.onExitVoiceNoteView(); } @@ -410,7 +419,9 @@ export class SessionRecording extends React.Component { console.log(`[vince][mic] Sending voice message to composition box1`); const audioBlob = this.state.mediaBlob.data; - if (!audioBlob) return; + if (!audioBlob) { + return; + } // Is the audio file > attachment filesize limit if (audioBlob.size > window.CONSTANTS.MAX_ATTACHMENT_FILESIZE) { @@ -433,7 +444,7 @@ export class SessionRecording extends React.Component { ); } - private stopRecordingStream() { + private async stopRecordingStream() { const { streamParams } = this.state; // Exit if parameters aren't yet set @@ -442,28 +453,31 @@ export class SessionRecording extends React.Component { } // Stop the stream - if (streamParams.media.state !== 'inactive') streamParams.media.stop(); + if (streamParams.media.state !== 'inactive') { + streamParams.media.stop(); + } + streamParams.input.disconnect(); streamParams.processor.disconnect(); streamParams.stream.getTracks().forEach((track: any) => track.stop); // Stop recording - this.stopRecording(); + await this.stopRecording(); } - private onRecordingStream(stream: any) { + private async onRecordingStream(stream: any) { // If not recording, stop stream if (!this.state.isRecording) { - this.stopRecordingStream(); + await this.stopRecordingStream(); return; } // Start recording the stream const media = new window.MediaRecorder(stream, { mimeType: 'audio/webm' }); media.ondataavailable = (mediaBlob: any) => { - this.setState({ mediaBlob }, () => { + this.setState({ mediaBlob }, async () => { // Generate PCM waveform for playback - this.initPlaybackView(); + await this.initPlaybackView(); }); }; media.start(); @@ -526,9 +540,12 @@ export class SessionRecording extends React.Component { // Chop off values which exceed the bounds of the container volumeArray = volumeArray.slice(0, numBars); - canvas && (canvas.height = height); - canvas && (canvas.width = width); - const canvasContext = canvas && canvas.getContext(`2d`); + if (canvas) { + canvas.width = width; + canvas.height = height; + } + + const canvasContext = canvas && canvas.getContext('2d'); for (var i = 0; i < volumeArray.length; i++) { const barHeight = Math.ceil(volumeArray[i]); @@ -566,8 +583,8 @@ export class SessionRecording extends React.Component { const groupSize = Math.floor(array.length / numGroups); - let compacted = new Float32Array(numGroups); let sum = 0; + const compacted = new Float32Array(numGroups); for (let i = 0; i < array.length; i++) { sum += array[i]; @@ -602,7 +619,7 @@ export class SessionRecording extends React.Component { const arrayBuffer = await new Response(blob).arrayBuffer(); const audioContext = new window.AudioContext(); - audioContext.decodeAudioData(arrayBuffer, (buffer: AudioBuffer) => { + await audioContext.decodeAudioData(arrayBuffer, (buffer: AudioBuffer) => { this.setState({ recordDuration: buffer.duration, }); @@ -631,22 +648,26 @@ export class SessionRecording extends React.Component { // CANVAS CONTEXT const drawPlaybackCanvas = () => { const canvas = this.playbackCanvas.current; - if (!canvas) return; + if (!canvas){ + return; + } canvas.height = height; canvas.width = width; - const canvasContext = canvas.getContext(`2d`); - if (!canvasContext) return; + const canvasContext = canvas.getContext('2d'); + if (!canvasContext) { + return; + } for (let i = 0; i < barSizeArray.length; i++) { const barHeight = Math.ceil(barSizeArray[i]); - const offset_x = Math.ceil(i * (barWidth + barPadding)); - const offset_y = Math.ceil(height / 2 - barHeight / 2); + const offsetX = Math.ceil(i * (barWidth + barPadding)); + const offsetY = Math.ceil(height / 2 - barHeight / 2); // FIXME VINCE - Globalise JS references to colors canvasContext.fillStyle = barColorInit; - this.drawRoundedRect(canvasContext, offset_x, offset_y, barHeight); + this.drawRoundedRect(canvasContext, offsetX, offsetY, barHeight); } }; @@ -663,8 +684,12 @@ export class SessionRecording extends React.Component { let r = this.state.canvasParams.barRadius; const w = this.state.canvasParams.barWidth; - if (w < 2 * r) r = w / 2; - if (h < 2 * r) r = h / 2; + if (w < r * 2) { + r = w / 2; + } + if (h < r * 2) { + r = h / 2; + } ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); diff --git a/ts/window.d.ts b/ts/window.d.ts index bd50f7e5d..b6cab59b8 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -85,5 +85,6 @@ declare global { ContactBuffer: any; GroupBuffer: any; SwarmPolling: SwarmPolling; + MediaRecorder: any; } } diff --git a/yarn.lock b/yarn.lock index e6dccf9e2..b3ac4e504 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,6 +40,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.2": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" + integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== + dependencies: + regenerator-runtime "^0.13.4" + "@develar/schema-utils@~2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.1.0.tgz#eceb1695bfbed6f6bb84666d5d3abe5e1fd54e17" @@ -64,6 +71,16 @@ global-agent "^2.0.2" global-tunnel-ng "^2.7.1" +"@iconify/icons-mdi@^1.0.46": + version "1.0.117" + resolved "https://registry.yarnpkg.com/@iconify/icons-mdi/-/icons-mdi-1.0.117.tgz#f0f3dd76c77f3bc403c4dbd7f90762550d38b311" + integrity sha512-bFmPdJVJxW7aQPdZVlZuKQfYBEpYhRf+KDnW8Qj+xrJsrIyJI7QajOLXNARTnLebNKMPktz7l7CQXbeB+UmUIg== + +"@iconify/react@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@iconify/react/-/react-1.1.3.tgz#ebe4d3e5f5dea23cd6a9634acd7a0a25c7414a59" + integrity sha512-ReZNJyr89AfED6XZIXkhsJiNv2taT3j1cTo1HVPHMsrKOz6gAYdXtD2kDWF6+GVoFUxpmnO0fMcA6yCyTf6Tow== + "@journeyapps/sqlcipher@https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b": version "4.0.0" resolved "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b" @@ -2858,7 +2875,12 @@ diff@3.3.1: resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww== -diff@^4.0.1, diff@^4.0.2: +diff@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +diff@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== @@ -6474,13 +6496,6 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: dependencies: minimist "0.0.8" -mkdirp@^0.5.3: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - mkdirp@^0.5.4, mkdirp@~0.5.1: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" @@ -8319,6 +8334,15 @@ react-group@^1.0.5: dependencies: prop-types "^15.6.0" +react-h5-audio-player@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-h5-audio-player/-/react-h5-audio-player-3.2.0.tgz#bce677d0b3ee44373f2e5d9baf0d7cba263cf4be" + integrity sha512-u2nBMwo+5SpRjfDUEH79LmzbbVPJRBZNRLqpWZ/i6vanCQow+UaEZmSoKiwXamAejSXLXJa8+DFgnOskllJgHw== + dependencies: + "@babel/runtime" "^7.10.2" + "@iconify/icons-mdi" "^1.0.46" + "@iconify/react" "^1.1.3" + react-icon-base@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d"