putting it all together

This commit is contained in:
or-else
2022-03-15 18:20:24 -07:00
parent 2ab56887b7
commit e121b1645f
21 changed files with 461 additions and 168 deletions
+2
View File
@@ -2666,10 +2666,12 @@ ul.tabbar li.active > a {
.audio-player .material-icons.large { .audio-player .material-icons.large {
font-size: 2.5rem; font-size: 2.5rem;
color: #009688;
} }
.audio-player .material-icons.disabled { .audio-player .material-icons.disabled {
color: #999; color: #999;
cursor: default;
} }
.audio-player .visualiser { .audio-player .visualiser {
+1 -1
View File
File diff suppressed because one or more lines are too long
+9 -2
View File
@@ -1362,10 +1362,17 @@
"obsolete": false "obsolete": false
}, },
"drafty_unknown": { "drafty_unknown": {
"translation": "", "translation": "Nicht unterstützt",
"defaultMessage": "Unsupported", "defaultMessage": "Unsupported",
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": true, "missing": false,
"obsolete": false
},
"preview_unavailable": {
"translation": "nicht verfügbar",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false "obsolete": false
} }
} }
+7
View File
@@ -1367,5 +1367,12 @@
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
},
"preview_unavailable": {
"translation": "unavailable",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false
} }
} }
+9 -2
View File
@@ -1362,10 +1362,17 @@
"obsolete": false "obsolete": false
}, },
"drafty_unknown": { "drafty_unknown": {
"translation": "", "translation": "No soportado",
"defaultMessage": "Unsupported", "defaultMessage": "Unsupported",
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": true, "missing": false,
"obsolete": false
},
"preview_unavailable": {
"translation": "indisponible",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false "obsolete": false
} }
} }
+4
View File
@@ -608,6 +608,10 @@
"defaultMessage": "phone", "defaultMessage": "phone",
"description": "Dative case of 'phone', i.e. 'phone' in 'by phone'" "description": "Dative case of 'phone', i.e. 'phone' in 'by phone'"
}, },
"preview_unavailable": {
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available"
},
"private_editing_placeholder": { "private_editing_placeholder": {
"defaultMessage": "Visible to you only", "defaultMessage": "Visible to you only",
"description": "Placeholder for editing 'private'" "description": "Placeholder for editing 'private'"
+9 -2
View File
@@ -1362,10 +1362,17 @@
"obsolete": false "obsolete": false
}, },
"drafty_unknown": { "drafty_unknown": {
"translation": "", "translation": "지원되지 않음",
"defaultMessage": "Unsupported", "defaultMessage": "Unsupported",
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": true, "missing": false,
"obsolete": false
},
"preview_unavailable": {
"translation": "없는",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false "obsolete": false
} }
} }
+9 -2
View File
@@ -1362,10 +1362,17 @@
"obsolete": false "obsolete": false
}, },
"drafty_unknown": { "drafty_unknown": {
"translation": "", "translation": "Neacceptat",
"defaultMessage": "Unsupported", "defaultMessage": "Unsupported",
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": true, "missing": false,
"obsolete": false
},
"preview_unavailable": {
"translation": "indisponibil",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false "obsolete": false
} }
} }
+9 -2
View File
@@ -1362,10 +1362,17 @@
"obsolete": false "obsolete": false
}, },
"drafty_unknown": { "drafty_unknown": {
"translation": "", "translation": "Не поддерживается",
"defaultMessage": "Unsupported", "defaultMessage": "Unsupported",
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": true, "missing": false,
"obsolete": false
},
"preview_unavailable": {
"translation": "недоступно",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false "obsolete": false
} }
} }
+9 -2
View File
@@ -1362,10 +1362,17 @@
"obsolete": false "obsolete": false
}, },
"drafty_unknown": { "drafty_unknown": {
"translation": "", "translation": "不支持",
"defaultMessage": "Unsupported", "defaultMessage": "Unsupported",
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": true, "missing": false,
"obsolete": false
},
"preview_unavailable": {
"translation": "不可用",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false "obsolete": false
} }
} }
+9 -2
View File
@@ -1362,10 +1362,17 @@
"obsolete": false "obsolete": false
}, },
"drafty_unknown": { "drafty_unknown": {
"translation": "", "translation": "不支持",
"defaultMessage": "Unsupported", "defaultMessage": "Unsupported",
"description": "Unsupported entity in drafty", "description": "Unsupported entity in drafty",
"missing": true, "missing": false,
"obsolete": false
},
"preview_unavailable": {
"translation": "不可用",
"defaultMessage": "unavailable",
"description": "Message shown when media preview is not available",
"missing": false,
"obsolete": false "obsolete": false
} }
} }
+18 -10
View File
@@ -115,6 +115,9 @@ export function fullFormatter(style, data, values, key, stack) {
case 'BR': case 'BR':
values = null; values = null;
break; break;
case 'EX':
// Ignore.
break;
case 'HL': case 'HL':
// Highlighted text. Assign class name. // Highlighted text. Assign class name.
attr.className = 'highlight'; attr.className = 'highlight';
@@ -161,15 +164,17 @@ export function fullFormatter(style, data, values, key, stack) {
attr.onClick = this.onQuoteClick; attr.onClick = this.onQuoteClick;
break; break;
default: default:
// Unknown element. if (!el) {
el = React.Fragment; // Unknown element.
attr = {key: key}; el = React.Fragment;
// Generate comment for unknown element. attr = {key: key};
let body = values; // Generate comment for unknown element.
if (!Array.isArray(values) || !values.join('').trim()) { let body = values;
body = [<span key="x1" className="gray">{this.formatMessage(messages.drafty_unknown)}</span>]; if (!Array.isArray(values) || !values.join('').trim()) {
body = [<span key="x1" className="gray">{this.formatMessage(messages.drafty_unknown)}</span>];
}
values = [<i key="x0" className="material-icons gray">extension</i>, ' '].concat(body);
} }
values = [<i key="x0" className="material-icons gray">extension</i>, ' '].concat(body);
break; break;
} }
if (!el) { if (!el) {
@@ -246,8 +251,11 @@ export function previewFormatter(style, data, values, key) {
values = null; values = null;
break; break;
default: default:
el = React.Fragment; if (!el) {
values = [<i key="x0" className="material-icons gray">extension</i>, ' ', this.formatMessage(messages.drafty_unknown)]; // Unknown element.
el = React.Fragment;
values = [<i key="x0" className="material-icons gray">extension</i>, ' ', this.formatMessage(messages.drafty_unknown)];
}
break; break;
} }
if (!el) { if (!el) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -21,7 +21,7 @@ import SendMessage from '../widgets/send-message.jsx';
import { DEFAULT_P2P_ACCESS_MODE, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY, import { DEFAULT_P2P_ACCESS_MODE, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY,
MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE,
READ_DELAY, QUOTED_REPLY_LENGTH } from '../config.js'; READ_DELAY, QUOTED_REPLY_LENGTH } from '../config.js';
import { blobToBase64, fileToBase64, imageScaled, intArrayToBase64, makeImageUrl } from '../lib/blob-helpers.js'; import { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
import HashNavigation from '../lib/navigation.js'; import HashNavigation from '../lib/navigation.js';
import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js'; import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';
@@ -889,7 +889,7 @@ class MessagesView extends React.Component {
mime: blob.type, mime: blob.type,
size: blob.size, size: blob.size,
duration: duration, duration: duration,
preview: intArrayToBase64(preview), preview: preview,
urlPromise: uploadCompletionPromise urlPromise: uploadCompletionPromise
}); });
// Pass data and the uploader to the TinodeWeb. // Pass data and the uploader to the TinodeWeb.
@@ -903,7 +903,7 @@ class MessagesView extends React.Component {
size: blob.size, size: blob.size,
data: b64.bits, data: b64.bits,
duration: duration, duration: duration,
preview: intArrayToBase64(preview), preview: preview,
})) }))
}) })
} }
+115 -41
View File
@@ -1,6 +1,7 @@
// Audio recorder widget. // Audio recorder widget.
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl';
import { secondsToTime } from '../lib/strformat'; import { secondsToTime } from '../lib/strformat';
import { base64ToIntArray } from '../lib/blob-helpers'; import { base64ToIntArray } from '../lib/blob-helpers';
@@ -11,9 +12,10 @@ const CANVAS_UPSCALING = 2.0;
const LINE_WIDTH = 3 * CANVAS_UPSCALING; const LINE_WIDTH = 3 * CANVAS_UPSCALING;
// Spacing between two visualization bars. // Spacing between two visualization bars.
const SPACING = 2 * CANVAS_UPSCALING; const SPACING = 2 * CANVAS_UPSCALING;
// Color of histogram bars // Color of histogram bars (base color #009688).
const BAR_COLOR = '#8fbed6'; const BAR_COLOR = '#85c7c5';
const BAR_COLOR_DARK = '#5f8ea5'; const BAR_COLOR_DARK = '#559695';
const THUMB_COLOR = '#246867';
// Background color // Background color
const BKG_COLOR = '#fff'; const BKG_COLOR = '#fff';
@@ -37,6 +39,8 @@ export default class AudioPlayer extends React.PureComponent {
preview: preview preview: preview
}; };
this.initAudio = this.initAudio.bind(this);
this.initCanvas = this.initCanvas.bind(this);
this.calcPreviewBars = this.calcPreviewBars.bind(this); this.calcPreviewBars = this.calcPreviewBars.bind(this);
this.visualize = this.visualize.bind(this); this.visualize = this.visualize.bind(this);
@@ -52,60 +56,109 @@ export default class AudioPlayer extends React.PureComponent {
componentDidMount() { componentDidMount() {
if (this.props.src) { if (this.props.src) {
this.audioPlayer = new Audio(this.props.src); this.initAudio();
this.audioPlayer.addEventListener('canplay', _ => this.setState({canPlay: true}));
this.audioPlayer.addEventListener('timeupdate', _ => this.setState({currentTime: secondsToTime(this.audioPlayer.currentTime)}));
this.audioPlayer.addEventListener('ended', _ => this.setState({playing: false, currentTime: secondsToTime(0)}));
} }
if (this.state.preview) { if (this.state.preview) {
// Force canvas aspect ration to match one of the element + upscale canvas to reduce blurring. this.initCanvas();
this.canvasRef.current.width = this.canvasRef.current.offsetWidth * CANVAS_UPSCALING;
this.canvasRef.current.height = this.canvasRef.current.offsetHeight * CANVAS_UPSCALING;
this.canvasContext = this.canvasRef.current.getContext('2d');
this.canvasContext.lineCap = 'round';
this.viewBuffer = this.calcPreviewBars(this.state.preview);
this.visualize();
} }
} }
componentWillUnmount() {
if (this.audioPlayer) {
this.audioPlayer.oncanplay = null;
this.audioPlayer.ontimeupdate = null;
this.audioPlayer.onended = null;
this.audioPlayer.pause();
this.audioPlayer = null;
}
}
componentDidUpdate(prevProps) {
if (this.props.src != prevProps.src) {
this.initAudio();
}
if (this.props.preview != prevProps.preview) {
let preview = base64ToIntArray(this.props.preview);
if (Array.isArray(preview) && preview.length < MIN_PREVIEW_LENGTH) {
preview = null;
}
this.setState({preview: preview}, this.initCanvas);
}
}
initAudio() {
this.audioPlayer = new Audio(this.props.src);
this.audioPlayer.oncanplay = _ => this.setState({canPlay: true});
this.audioPlayer.ontimeupdate = _ => this.setState({currentTime: secondsToTime(this.audioPlayer.currentTime)});
this.audioPlayer.onended = _ => {
this.audioPlayer.currentTime = 0;
this.setState({playing: false, currentTime: secondsToTime(0)})
}
}
initCanvas() {
// Force canvas aspect ration to match one of the element + upscale canvas to reduce blurring.
this.canvasRef.current.width = this.canvasRef.current.offsetWidth * CANVAS_UPSCALING;
this.canvasRef.current.height = this.canvasRef.current.offsetHeight * CANVAS_UPSCALING;
this.canvasContext = this.canvasRef.current.getContext('2d');
this.canvasContext.lineCap = 'round';
this.viewBuffer = this.calcPreviewBars(this.state.preview);
this.visualize();
}
// Draw amplitude of sound. // Draw amplitude of sound.
visualize() { visualize() {
const width = this.canvasRef.current.width; const width = this.effectiveWidth;
const height = this.canvasRef.current.height; const height = this.canvasRef.current.height;
this.canvasContext.lineWidth = LINE_WIDTH; this.canvasContext.lineWidth = LINE_WIDTH;
const drawFrame = () => { const drawFrame = () => {
this.canvasContext.clearRect(0, 0, width, height); if (!this.canvasRef.current) {
// The component is unmounted.
return;
}
// Start new drawing. this.canvasContext.clearRect(0, 0, this.canvasRef.current.width, height);
this.canvasContext.beginPath();
if (this.viewBuffer) { if (this.viewBuffer) {
window.requestAnimationFrame(drawFrame); if (this.state.playing) {
window.requestAnimationFrame(drawFrame);
}
// Current pplayback position.
const thumbAt = this.props.duration ?
Math.max(0, Math.min(this.audioPlayer.currentTime * 1000 / this.props.duration, 1)) * (width - LINE_WIDTH * 2) : -1;
this.canvasContext.strokeStyle = BAR_COLOR;
// Draw amplitude bars. // Draw amplitude bars.
this.canvasContext.beginPath();
this.canvasContext.strokeStyle = BAR_COLOR_DARK;
for (let i = 0; i < this.viewBuffer.length; i++) { for (let i = 0; i < this.viewBuffer.length; i++) {
let x = 1 + i * (LINE_WIDTH + SPACING) + LINE_WIDTH * 0.5; let x = 1 + i * (LINE_WIDTH + SPACING) + LINE_WIDTH * 0.5;
let y = this.viewBuffer[i] * height * 0.9; let y = this.viewBuffer[i] * height * 0.9;
const color = x < thumbAt ? BAR_COLOR_DARK : BAR_COLOR;
if (this.canvasContext.strokeStyle != color) {
this.canvasContext.stroke();
this.canvasContext.beginPath();
this.canvasContext.strokeStyle = color;
}
this.canvasContext.moveTo(x, (height - y) * 0.5); this.canvasContext.moveTo(x, (height - y) * 0.5);
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5); this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
} }
// Actually draw the bars on canvas. // Actually draw the bars on canvas.
this.canvasContext.stroke(); this.canvasContext.stroke();
// Draw thumb.
if (this.props.duration) { if (this.props.duration) {
this.canvasContext.beginPath(); this.canvasContext.beginPath();
this.canvasContext.arc(thumbAt + LINE_WIDTH * 2, height * 0.5, LINE_WIDTH * 2, 0, 2 * Math.PI);
const x = Math.max(0, Math.min(this.audioPlayer.currentTime * 1000 / this.props.duration, 1)) * width; this.canvasContext.fillStyle = THUMB_COLOR;
this.canvasContext.arc(x + LINE_WIDTH * 0.5, height * 0.5, LINE_WIDTH * 2, 0, 2 * Math.PI);
this.canvasContext.fillStyle = BAR_COLOR_DARK;
this.canvasContext.fill(); this.canvasContext.fill();
} }
} }
@@ -116,7 +169,7 @@ export default class AudioPlayer extends React.PureComponent {
// Quick and dirty downsampling of the original preview bars into a smaller (or equal) number of bars we can display here. // Quick and dirty downsampling of the original preview bars into a smaller (or equal) number of bars we can display here.
calcPreviewBars(original) { calcPreviewBars(original) {
const barCount = Math.min(original.length, ((this.canvasRef.current.width - 1) / (LINE_WIDTH + SPACING)) | 0); const barCount = Math.min(original.length, ((this.canvasRef.current.width - SPACING) / (LINE_WIDTH + SPACING)) | 0);
const factor = original.length / barCount; const factor = original.length / barCount;
let amps = []; let amps = [];
let max = -1; let max = -1;
@@ -132,6 +185,8 @@ export default class AudioPlayer extends React.PureComponent {
} }
if (max > 0) { if (max > 0) {
// Remove extra padding on the right due to fractional bar which is not drawn.
this.effectiveWidth = barCount * (LINE_WIDTH + SPACING) + SPACING;
return amps.map(a => a / max); return amps.map(a => a / max);
} }
return null; return null;
@@ -144,12 +199,11 @@ export default class AudioPlayer extends React.PureComponent {
} }
if (this.state.playing) { if (this.state.playing) {
this.audioPlayer.pause(); this.audioPlayer.pause();
this.startedOn = null; this.setState({playing: false});
} else { } else {
this.audioPlayer.play(); this.audioPlayer.play();
this.startedOn = Date.now(); this.setState({playing: true}, this.visualize);
} }
this.setState({playing: !this.state.playing});
} }
handleError(err) { handleError(err) {
@@ -160,27 +214,47 @@ export default class AudioPlayer extends React.PureComponent {
e.preventDefault(); e.preventDefault();
if (e.target && this.props.duration) { if (e.target && this.props.duration) {
const rect = e.target.getBoundingClientRect(); const rect = e.target.getBoundingClientRect();
const offset = (e.clientX - rect.left) / rect.width; const offset = (e.clientX - rect.left) / this.effectiveWidth * CANVAS_UPSCALING;
this.audioPlayer.currentTime = this.props.duration * offset / 1000; this.audioPlayer.currentTime = this.props.duration * offset / 1000;
this.setState({currentTime: secondsToTime(this.audioPlayer.currentTime)}); this.setState({currentTime: secondsToTime(this.audioPlayer.currentTime)});
if (!this.state.playing && this.state.preview) {
this.visualize();
}
} }
} }
render() { render() {
const playClass = 'material-icons large' + (this.state.canPlay ? '' : ' disabled'); const playClass = 'material-icons' +
return ( (this.props.short ? '' : ' large') +
<div className="audio-player"> (this.state.canPlay ? '' : ' disabled');
<a href="#" onClick={this.handlePlay} title="Play"> const play = (<a href="#" onClick={this.handlePlay} title="Play">
<i className={playClass}>{this.state.playing ? 'pause_circle' : 'play_circle'}</i> <i className={playClass}>{this.state.playing ? 'pause_circle' : 'play_circle'}</i>
</a> </a>);
return (<div className="audio-player">{this.props.short ?
<>
<div> <div>
{this.state.preview ? {this.state.preview ?
<canvas className="visualiser" ref={this.canvasRef} onClick={this.handleSeek} /> : <canvas className="visualiser" ref={this.canvasRef} onClick={this.handleSeek} /> :
<div className="visualiser">unavailable</div> <div className="visualiser"> - </div>
}
</div>
{play}
</>
:
<>
{play}
<div>
{this.state.preview ?
<canvas className="visualiser" ref={this.canvasRef} onClick={this.handleSeek} /> :
<div className="visualiser">
<FormattedMessage
id="preview_unavailable" defaultMessage="unavailable" description="Message shown when media preview is not available" />
</div>
} }
<div className="timer">{this.state.currentTime}/{this.state.duration}</div> <div className="timer">{this.state.currentTime}/{this.state.duration}</div>
</div> </div>
</div> </>
); }
</div>);
} }
} }
+40 -9
View File
@@ -2,6 +2,9 @@
import React from 'react'; import React from 'react';
import AudioPlayer from './audio-player.jsx';
import { intArrayToBase64 } from '../lib/blob-helpers.js'
import { secondsToTime } from '../lib/strformat'; import { secondsToTime } from '../lib/strformat';
// FFT resolution. // FFT resolution.
@@ -34,11 +37,14 @@ export default class AudioRecorder extends React.PureComponent {
audioRecord: null, audioRecord: null,
recording: true, recording: true,
paused: false, paused: false,
duration: '0:00' duration: '0:00',
blobUrl: null,
preview: null
}; };
this.visualize = this.visualize.bind(this); this.visualize = this.visualize.bind(this);
this.initMediaRecording = this.initMediaRecording.bind(this); this.initMediaRecording = this.initMediaRecording.bind(this);
this.getRecording = this.getRecording.bind(this);
this.cleanUp = this.cleanUp.bind(this); this.cleanUp = this.cleanUp.bind(this);
this.handleResume = this.handleResume.bind(this); this.handleResume = this.handleResume.bind(this);
@@ -159,9 +165,10 @@ export default class AudioRecorder extends React.PureComponent {
handlePause(e) { handlePause(e) {
e.preventDefault(); e.preventDefault();
this.mediaRecorder.pause(); this.mediaRecorder.pause();
this.mediaRecorder.requestData();
this.durationMillis += Date.now() - this.startedOn; this.durationMillis += Date.now() - this.startedOn;
this.startedOn = null; this.startedOn = null;
this.setState({recording: false, duration: secondsToTime(this.durationMillis / 1000)}); this.setState({recording: false});
} }
handleResume(e) { handleResume(e) {
@@ -207,13 +214,9 @@ export default class AudioRecorder extends React.PureComponent {
this.audioInput.connect(this.analyser); this.audioInput.connect(this.analyser);
this.mediaRecorder.onstop = _ => { this.mediaRecorder.onstop = _ => {
const blob = new Blob(this.audioChunks, {'type' : 'audio/mp3; codecs=mp3'});
this.audioChunks = [];
const url = window.URL.createObjectURL(blob);
if (this.durationMillis > MIN_DURATION) { if (this.durationMillis > MIN_DURATION) {
blob.arrayBuffer().then(ab => this.audioContext.decodeAudioData(ab)) this.getRecording()
.then(data => this.createPreview(data)) .then(result => this.props.onFinished(result.url, result.preview, this.durationMillis));
.then(preview => this.props.onFinished(url, preview, this.durationMillis));
} else { } else {
this.props.onDeleted(); this.props.onDeleted();
} }
@@ -224,6 +227,15 @@ export default class AudioRecorder extends React.PureComponent {
if (e.data.size > 0) { if (e.data.size > 0) {
this.audioChunks.push(e.data); this.audioChunks.push(e.data);
} }
if (this.mediaRecorder.state != 'inactive') {
this.getRecording().then(result => {
this.setState({
blobUrl: result.url,
preview: result.preview
});
});
}
} }
this.durationMillis = 0; this.durationMillis = 0;
@@ -232,6 +244,17 @@ export default class AudioRecorder extends React.PureComponent {
this.visualize(); this.visualize();
} }
// Obtain data in a form sutable for sending or playing back.
getRecording() {
const blob = new Blob(this.audioChunks, {'type' : 'audio/mp3; codecs=mp3'});
const url = window.URL.createObjectURL(blob);
return blob.arrayBuffer().then(ab => this.audioContext.decodeAudioData(ab))
.then(data => this.createPreview(data))
.then(preview => {
return {url: url, preview: intArrayToBase64(preview)}
});
}
// Preview must be calculated at the source: Chrome does not allow background AudioContext. // Preview must be calculated at the source: Chrome does not allow background AudioContext.
createPreview(audio) { createPreview(audio) {
const data = audio.getChannelData(0); const data = audio.getChannelData(0);
@@ -269,7 +292,15 @@ export default class AudioRecorder extends React.PureComponent {
<a href="#" onClick={this.handleDelete} title="Delete"> <a href="#" onClick={this.handleDelete} title="Delete">
<i className="material-icons">delete_outline</i> <i className="material-icons">delete_outline</i>
</a> </a>
<canvas className="visualiser" ref={this.canvasRef} /> {this.state.recording ?
<canvas className="visualiser" ref={this.canvasRef} />
:
<AudioPlayer
src={this.state.blobUrl}
preview={this.state.preview}
duration={this.durationMillis}
short={true} />
}
<div className="duration">{this.state.duration}</div> <div className="duration">{this.state.duration}</div>
{this.state.recording ? {this.state.recording ?
<a href="#" onClick={this.handlePause} title="Pause"> <a href="#" onClick={this.handlePause} title="Pause">
+1 -1
View File
@@ -45,7 +45,7 @@ class SendMessage extends React.PureComponent {
quote: null, quote: null,
message: '', message: '',
audioRec: false, audioRec: false,
audioEnabled: !!navigator.mediaDevices.getUserMedia, audioEnabled: !!navigator.mediaDevices.getUserMedia && this.props.onAttachAudio,
// Make initial keypress time as if it happened 5001 milliseconds in the past. // Make initial keypress time as if it happened 5001 milliseconds in the past.
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1 keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
}; };
+203 -85
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long