audio recording widget is reasonably functional
This commit is contained in:
+20
-2
@@ -2639,10 +2639,28 @@ ul.tabbar li.active > a {
|
|||||||
height: 80%;
|
height: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audio-visualiser {
|
.audio {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: none;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
width: 12rem;
|
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio canvas.visualiser {
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
width: 12rem;
|
||||||
|
height: 2.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio .duration {
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio .material-icons {
|
||||||
|
font-size: 2rem;
|
||||||
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -17,9 +17,13 @@ export function shortDateFormat(then, locale) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert seconds to minutes:seconds, i.e. 156 sec -> 2:36.
|
// Convert seconds to minutes:seconds, i.e. 156 sec -> 2:36.
|
||||||
export function secondsToTime(seconds) {
|
// If <code>fixedMins</code> is true, then minutes are represented by at least two digits.
|
||||||
const min = Math.floor(seconds / 60);
|
export function secondsToTime(seconds, fixedMin) {
|
||||||
let sec = seconds % 60;
|
let min = Math.floor(seconds / 60);
|
||||||
|
if (fixedMin) {
|
||||||
|
min = min < 10 ? `0${min}` : min;
|
||||||
|
}
|
||||||
|
let sec = (seconds % 60) | 0;
|
||||||
sec = sec < 10 ? `0${sec}` : sec;
|
sec = sec < 10 ? `0${sec}` : sec;
|
||||||
return `${min}:${sec}`;
|
return `${min}:${sec}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,20 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { secondsToTime } from '../lib/strformat';
|
||||||
|
|
||||||
|
// FFT resolution.
|
||||||
const BUFFER_SIZE = 256;
|
const BUFFER_SIZE = 256;
|
||||||
|
// Thickness of a visualization bar.
|
||||||
|
const LINE_WIDTH = 5;
|
||||||
|
// Spacing between two visualization bars.
|
||||||
|
const SPACING = 2;
|
||||||
|
// Duration represented by one visualization bar.
|
||||||
|
const MILLIS_PER_BAR = 100;
|
||||||
|
// Color of histogram bars
|
||||||
|
const BAR_COLOR = '#8fbed6';
|
||||||
|
// Background color
|
||||||
|
const BKG_COLOR = '#eeeeee';
|
||||||
|
|
||||||
export default class AudioRecorder extends React.PureComponent {
|
export default class AudioRecorder extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -12,20 +25,19 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
audioRecord: null,
|
audioRecord: null,
|
||||||
length: 0
|
recording: false,
|
||||||
|
paused: false,
|
||||||
|
duration: '0:00'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.recording = false;
|
|
||||||
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.handleStart = this.handleStart.bind(this);
|
this.handleStart = this.handleStart.bind(this);
|
||||||
this.handleStop = this.handleStop.bind(this);
|
this.handleStop = this.handleStop.bind(this);
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
this.handleDelete = this.handleDelete.bind(this);
|
||||||
|
|
||||||
// DEBUG
|
this.durationMillis = 0;
|
||||||
this.min = 1000000;
|
this.startedOn = null;
|
||||||
this.max = 0;
|
|
||||||
|
|
||||||
this.canvasRef = React.createRef();
|
this.canvasRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +47,9 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.canvasContext = this.canvasRef.current.getContext('2d');
|
this.canvasContext = this.canvasRef.current.getContext('2d');
|
||||||
|
this.canvasContext.lineCap = 'round';
|
||||||
|
// To reduce line blurring.
|
||||||
|
this.canvasContext.translate(0.5, 0.5);
|
||||||
|
|
||||||
this.stream = null;
|
this.stream = null;
|
||||||
this.mediaRecorder = null;
|
this.mediaRecorder = null;
|
||||||
@@ -48,74 +63,93 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
|
|
||||||
// Draw amplitude of sound.
|
// Draw amplitude of sound.
|
||||||
visualize() {
|
visualize() {
|
||||||
const record = [];
|
const pcmData = new Uint8Array(this.analyser.frequencyBinCount);
|
||||||
const pcmData = new Uint8Array(BUFFER_SIZE);
|
|
||||||
const width = this.canvasRef.current.width;
|
const width = this.canvasRef.current.width;
|
||||||
const height = this.canvasRef.current.height;
|
const height = this.canvasRef.current.height;
|
||||||
|
const viewBuffer = [];
|
||||||
|
const viewLength = (width / (LINE_WIDTH + SPACING)) | 0;
|
||||||
|
const viewDuration = MILLIS_PER_BAR * viewLength;
|
||||||
|
|
||||||
|
// Take each N-th sample:
|
||||||
|
const samples = (0.1 * this.sampleRate) | 0;
|
||||||
|
|
||||||
|
this.canvasContext.fillStyle = BKG_COLOR;
|
||||||
|
this.canvasContext.lineWidth = LINE_WIDTH;
|
||||||
|
this.canvasContext.strokeStyle = BAR_COLOR;
|
||||||
|
|
||||||
|
let prevBarCount = 0;
|
||||||
const drawFrame = () => {
|
const drawFrame = () => {
|
||||||
if (this.recording) {
|
const duration = this.durationMillis + Date.now() - this.startedOn;
|
||||||
|
// Update record length timer.
|
||||||
|
this.setState({duration: secondsToTime(duration / 1000)});
|
||||||
|
|
||||||
|
if (this.state.recording) {
|
||||||
window.requestAnimationFrame(drawFrame);
|
window.requestAnimationFrame(drawFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current waveform.
|
// Draw histogram.
|
||||||
|
|
||||||
|
// Get current waveform and calculate its amplitude.
|
||||||
this.analyser.getByteTimeDomainData(pcmData);
|
this.analyser.getByteTimeDomainData(pcmData);
|
||||||
|
|
||||||
this.canvasContext.fillStyle = 'rgb(200, 200, 200)';
|
|
||||||
this.canvasContext.fillRect(0, 0, width, height);
|
|
||||||
|
|
||||||
this.canvasContext.lineWidth = 2;
|
|
||||||
this.canvasContext.strokeStyle = 'rgb(0, 0, 0)';
|
|
||||||
|
|
||||||
let volume = 0.0;
|
let volume = 0.0;
|
||||||
for (const amplitude of pcmData) {
|
for (const amplitude of pcmData) {
|
||||||
volume += (amplitude - 127) ** 2;
|
volume += (amplitude - 127) ** 2;
|
||||||
}
|
}
|
||||||
console.log("Amp sum, sqrt(sum), ave, sqrt(ave):", volume, Math.sqrt(volume) | 0, (volume/pcmData.length) | 0, Math.sqrt(volume/pcmData.length) | 0);
|
volume = Math.sqrt(volume/pcmData.length);
|
||||||
|
|
||||||
this.min = Math.min(this.min, Math.sqrt(volume/pcmData.length))
|
let barCount = (duration / MILLIS_PER_BAR) | 0;
|
||||||
this.max = Math.max(this.max, Math.sqrt(volume/pcmData.length));
|
// Shift of the histogram along x-axis to make scrolling smooth. No need to shift if recording is too short.
|
||||||
|
const dx = viewDuration > duration ? 0 :
|
||||||
|
(duration - MILLIS_PER_BAR * barCount) / MILLIS_PER_BAR * (LINE_WIDTH + SPACING);
|
||||||
|
|
||||||
record.push(volume);
|
if (prevBarCount != barCount) {
|
||||||
if (record.length > BUFFER_SIZE) {
|
prevBarCount = barCount;
|
||||||
record.shift();
|
// Add new amplitude visualization bar.
|
||||||
|
viewBuffer.push(volume);
|
||||||
|
if (viewBuffer.length > viewLength) {
|
||||||
|
// Keep at most 'viewLength' amplitude bars.
|
||||||
|
viewBuffer.shift();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear canvas.
|
||||||
|
this.canvasContext.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// Draw amplitude bars.
|
||||||
this.canvasContext.beginPath();
|
this.canvasContext.beginPath();
|
||||||
|
for (let i = 0; i < viewBuffer.length; i++) {
|
||||||
|
let x = i * (LINE_WIDTH + SPACING) - dx;
|
||||||
|
let y = viewBuffer[i] / 64 * height;
|
||||||
|
|
||||||
const sliceWidth = width / BUFFER_SIZE;
|
this.canvasContext.moveTo(x, (height - y) * 0.5);
|
||||||
let x = 0;
|
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
|
||||||
for (let i = 0; i < record.length; i++) {
|
|
||||||
let v = record[i] / 128.0;
|
|
||||||
let y = v * height * 0.5;
|
|
||||||
|
|
||||||
if (i == 0) {
|
|
||||||
this.canvasContext.moveTo(x, y);
|
|
||||||
} else {
|
|
||||||
this.canvasContext.lineTo(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
x += sliceWidth;
|
|
||||||
}
|
}
|
||||||
|
// Actually draw the bars on canvas.
|
||||||
|
this.canvasContext.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawFrame();
|
drawFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStop() {
|
handleStop() {
|
||||||
this.recording = false;
|
this.mediaRecorder.pause();
|
||||||
this.mediaRecorder.stop();
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
console.log(this.mediaRecorder.state);
|
this.setState({recording: false, duration: secondsToTime(this.durationMillis / 1000)});
|
||||||
console.log("recorder stopped");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStart() {
|
handleStart() {
|
||||||
|
if (this.mediaRecorder) {
|
||||||
|
this.startedOn = Date.now();
|
||||||
|
this.mediaRecorder.resume();
|
||||||
|
this.setState({recording: true}, this.visualize);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(this.initMediaRecording, this.props.onError);
|
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(this.initMediaRecording, this.props.onError);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.props.onError(err);
|
this.props.onError(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleDelete() {
|
handleDelete() {
|
||||||
// Delete current recording.
|
// Delete current recording.
|
||||||
@@ -123,11 +157,7 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initMediaRecording(stream) {
|
initMediaRecording(stream) {
|
||||||
this.recording = true;
|
|
||||||
|
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.recording = true;
|
|
||||||
|
|
||||||
this.mediaRecorder = new MediaRecorder(stream);
|
this.mediaRecorder = new MediaRecorder(stream);
|
||||||
|
|
||||||
// The following code is needed for visualization.
|
// The following code is needed for visualization.
|
||||||
@@ -137,10 +167,9 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
this.analyser = this.audioContext.createAnalyser();
|
this.analyser = this.audioContext.createAnalyser();
|
||||||
this.analyser.fftSize = BUFFER_SIZE;
|
this.analyser.fftSize = BUFFER_SIZE;
|
||||||
this.audioInput.connect(this.analyser);
|
this.audioInput.connect(this.analyser);
|
||||||
this.visualize();
|
|
||||||
|
|
||||||
this.mediaRecorder.onstop = _ => {
|
this.mediaRecorder.onstop = _ => {
|
||||||
console.log("Data available after MediaRecorder.stop() called.", this.min, this.max);
|
console.log("Data available after MediaRecorder.stop() called.");
|
||||||
const blob = new Blob(this.audioChunks, { 'type' : 'audio/mp3; codecs=mp3' });
|
const blob = new Blob(this.audioChunks, { 'type' : 'audio/mp3; codecs=mp3' });
|
||||||
this.audioChunks = [];
|
this.audioChunks = [];
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
@@ -149,19 +178,30 @@ export default class AudioRecorder extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.mediaRecorder.ondataavailable = (e) => {
|
this.mediaRecorder.ondataavailable = (e) => {
|
||||||
|
if (e.data.size > 0) {
|
||||||
this.audioChunks.push(e.data);
|
this.audioChunks.push(e.data);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startedOn = Date.now();
|
||||||
this.mediaRecorder.start();
|
this.mediaRecorder.start();
|
||||||
|
this.setState({recording: true}, this.visualize);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (this.state.enabled ?
|
return (this.state.enabled ?
|
||||||
<>
|
<div className="audio">
|
||||||
<canvas className="audio-visualiser" width="200" height="60" ref={this.canvasRef} />
|
<canvas className="visualiser" width="200" height="60" ref={this.canvasRef} />
|
||||||
<button className="record" onClick={this.handleStart}>Record</button>
|
<div className="duration">{this.state.duration}</div>
|
||||||
<button className="stop" onClick={this.handleStop}>Stop</button>
|
{this.state.recording ?
|
||||||
</> :
|
<a href="#" onClick={this.handleStop} title="Pause">
|
||||||
|
<i className="material-icons">pause_circle_outline</i>
|
||||||
|
</a> :
|
||||||
|
<a href="#" onClick={this.handleStart} title="Resume">
|
||||||
|
<i className="material-icons">mic</i>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div> :
|
||||||
<div>Audio not available</div>);
|
<div>Audio not available</div>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+94
-49
@@ -3059,9 +3059,14 @@ function shortDateFormat(then, locale) {
|
|||||||
day: 'numeric'
|
day: 'numeric'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function secondsToTime(seconds) {
|
function secondsToTime(seconds, fixedMin) {
|
||||||
const min = Math.floor(seconds / 60);
|
let min = Math.floor(seconds / 60);
|
||||||
let sec = seconds % 60;
|
|
||||||
|
if (fixedMin) {
|
||||||
|
min = min < 10 ? "0".concat(min) : min;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sec = seconds % 60 | 0;
|
||||||
sec = sec < 10 ? "0".concat(sec) : sec;
|
sec = sec < 10 ? "0".concat(sec) : sec;
|
||||||
return "".concat(min, ":").concat(sec);
|
return "".concat(min, ":").concat(sec);
|
||||||
}
|
}
|
||||||
@@ -9584,8 +9589,15 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
/* harmony export */ });
|
/* harmony export */ });
|
||||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
||||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
||||||
|
/* harmony import */ var _lib_strformat__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../lib/strformat */ "./src/lib/strformat.js");
|
||||||
|
|
||||||
|
|
||||||
const BUFFER_SIZE = 256;
|
const BUFFER_SIZE = 256;
|
||||||
|
const LINE_WIDTH = 5;
|
||||||
|
const SPACING = 2;
|
||||||
|
const MILLIS_PER_BAR = 100;
|
||||||
|
const BAR_COLOR = '#8fbed6';
|
||||||
|
const BKG_COLOR = '#eeeeee';
|
||||||
class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -9593,16 +9605,17 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
this.state = {
|
this.state = {
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
audioRecord: null,
|
audioRecord: null,
|
||||||
length: 0
|
recording: false,
|
||||||
|
paused: false,
|
||||||
|
duration: '0:00'
|
||||||
};
|
};
|
||||||
this.recording = false;
|
|
||||||
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.handleStart = this.handleStart.bind(this);
|
this.handleStart = this.handleStart.bind(this);
|
||||||
this.handleStop = this.handleStop.bind(this);
|
this.handleStop = this.handleStop.bind(this);
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
this.handleDelete = this.handleDelete.bind(this);
|
||||||
this.min = 1000000;
|
this.durationMillis = 0;
|
||||||
this.max = 0;
|
this.startedOn = null;
|
||||||
this.canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
|
this.canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9612,6 +9625,8 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.canvasContext = this.canvasRef.current.getContext('2d');
|
this.canvasContext = this.canvasRef.current.getContext('2d');
|
||||||
|
this.canvasContext.lineCap = 'round';
|
||||||
|
this.canvasContext.translate(0.5, 0.5);
|
||||||
this.stream = null;
|
this.stream = null;
|
||||||
this.mediaRecorder = null;
|
this.mediaRecorder = null;
|
||||||
this.audioContext = null;
|
this.audioContext = null;
|
||||||
@@ -9622,65 +9637,81 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
visualize() {
|
visualize() {
|
||||||
const record = [];
|
const pcmData = new Uint8Array(this.analyser.frequencyBinCount);
|
||||||
const pcmData = new Uint8Array(BUFFER_SIZE);
|
|
||||||
const width = this.canvasRef.current.width;
|
const width = this.canvasRef.current.width;
|
||||||
const height = this.canvasRef.current.height;
|
const height = this.canvasRef.current.height;
|
||||||
|
const viewBuffer = [];
|
||||||
|
const viewLength = width / (LINE_WIDTH + SPACING) | 0;
|
||||||
|
const viewDuration = MILLIS_PER_BAR * viewLength;
|
||||||
|
const samples = 0.1 * this.sampleRate | 0;
|
||||||
|
this.canvasContext.fillStyle = BKG_COLOR;
|
||||||
|
this.canvasContext.lineWidth = LINE_WIDTH;
|
||||||
|
this.canvasContext.strokeStyle = BAR_COLOR;
|
||||||
|
let prevBarCount = 0;
|
||||||
|
|
||||||
const drawFrame = () => {
|
const drawFrame = () => {
|
||||||
if (this.recording) {
|
const duration = this.durationMillis + Date.now() - this.startedOn;
|
||||||
|
this.setState({
|
||||||
|
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_1__.secondsToTime)(duration / 1000)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.state.recording) {
|
||||||
window.requestAnimationFrame(drawFrame);
|
window.requestAnimationFrame(drawFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.analyser.getByteTimeDomainData(pcmData);
|
this.analyser.getByteTimeDomainData(pcmData);
|
||||||
this.canvasContext.fillStyle = 'rgb(200, 200, 200)';
|
|
||||||
this.canvasContext.fillRect(0, 0, width, height);
|
|
||||||
this.canvasContext.lineWidth = 2;
|
|
||||||
this.canvasContext.strokeStyle = 'rgb(0, 0, 0)';
|
|
||||||
let volume = 0.0;
|
let volume = 0.0;
|
||||||
|
|
||||||
for (const amplitude of pcmData) {
|
for (const amplitude of pcmData) {
|
||||||
volume += (amplitude - 127) ** 2;
|
volume += (amplitude - 127) ** 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Amp sum, sqrt(sum), ave, sqrt(ave):", volume, Math.sqrt(volume) | 0, volume / pcmData.length | 0, Math.sqrt(volume / pcmData.length) | 0);
|
volume = Math.sqrt(volume / pcmData.length);
|
||||||
this.min = Math.min(this.min, Math.sqrt(volume / pcmData.length));
|
let barCount = duration / MILLIS_PER_BAR | 0;
|
||||||
this.max = Math.max(this.max, Math.sqrt(volume / pcmData.length));
|
const dx = viewDuration > duration ? 0 : (duration - MILLIS_PER_BAR * barCount) / MILLIS_PER_BAR * (LINE_WIDTH + SPACING);
|
||||||
record.push(volume);
|
|
||||||
|
|
||||||
if (record.length > BUFFER_SIZE) {
|
if (prevBarCount != barCount) {
|
||||||
record.shift();
|
prevBarCount = barCount;
|
||||||
|
viewBuffer.push(volume);
|
||||||
|
|
||||||
|
if (viewBuffer.length > viewLength) {
|
||||||
|
viewBuffer.shift();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.canvasContext.fillRect(0, 0, width, height);
|
||||||
this.canvasContext.beginPath();
|
this.canvasContext.beginPath();
|
||||||
const sliceWidth = width / BUFFER_SIZE;
|
|
||||||
let x = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < record.length; i++) {
|
for (let i = 0; i < viewBuffer.length; i++) {
|
||||||
let v = record[i] / 128.0;
|
let x = i * (LINE_WIDTH + SPACING) - dx;
|
||||||
let y = v * height * 0.5;
|
let y = viewBuffer[i] / 64 * height;
|
||||||
|
this.canvasContext.moveTo(x, (height - y) * 0.5);
|
||||||
if (i == 0) {
|
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
|
||||||
this.canvasContext.moveTo(x, y);
|
|
||||||
} else {
|
|
||||||
this.canvasContext.lineTo(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
x += sliceWidth;
|
this.canvasContext.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
drawFrame();
|
drawFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStop() {
|
handleStop() {
|
||||||
this.recording = false;
|
this.mediaRecorder.pause();
|
||||||
this.mediaRecorder.stop();
|
this.durationMillis += Date.now() - this.startedOn;
|
||||||
console.log(this.mediaRecorder.state);
|
this.setState({
|
||||||
console.log("recorder stopped");
|
recording: false,
|
||||||
|
duration: (0,_lib_strformat__WEBPACK_IMPORTED_MODULE_1__.secondsToTime)(this.durationMillis / 1000)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStart() {
|
handleStart() {
|
||||||
|
if (this.mediaRecorder) {
|
||||||
|
this.startedOn = Date.now();
|
||||||
|
this.mediaRecorder.resume();
|
||||||
|
this.setState({
|
||||||
|
recording: true
|
||||||
|
}, this.visualize);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
navigator.mediaDevices.getUserMedia({
|
navigator.mediaDevices.getUserMedia({
|
||||||
audio: true,
|
audio: true,
|
||||||
@@ -9690,15 +9721,14 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
this.props.onError(err);
|
this.props.onError(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleDelete() {
|
handleDelete() {
|
||||||
console.log("Delete current cached recording");
|
console.log("Delete current cached recording");
|
||||||
}
|
}
|
||||||
|
|
||||||
initMediaRecording(stream) {
|
initMediaRecording(stream) {
|
||||||
this.recording = true;
|
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.recording = true;
|
|
||||||
this.mediaRecorder = new MediaRecorder(stream);
|
this.mediaRecorder = new MediaRecorder(stream);
|
||||||
this.audioContext = new AudioContext();
|
this.audioContext = new AudioContext();
|
||||||
this.sampleRate = this.audioContext.sampleRate;
|
this.sampleRate = this.audioContext.sampleRate;
|
||||||
@@ -9706,10 +9736,9 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
this.analyser = this.audioContext.createAnalyser();
|
this.analyser = this.audioContext.createAnalyser();
|
||||||
this.analyser.fftSize = BUFFER_SIZE;
|
this.analyser.fftSize = BUFFER_SIZE;
|
||||||
this.audioInput.connect(this.analyser);
|
this.audioInput.connect(this.analyser);
|
||||||
this.visualize();
|
|
||||||
|
|
||||||
this.mediaRecorder.onstop = _ => {
|
this.mediaRecorder.onstop = _ => {
|
||||||
console.log("Data available after MediaRecorder.stop() called.", this.min, this.max);
|
console.log("Data available after MediaRecorder.stop() called.");
|
||||||
const blob = new Blob(this.audioChunks, {
|
const blob = new Blob(this.audioChunks, {
|
||||||
'type': 'audio/mp3; codecs=mp3'
|
'type': 'audio/mp3; codecs=mp3'
|
||||||
});
|
});
|
||||||
@@ -9722,25 +9751,41 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.mediaRecorder.ondataavailable = e => {
|
this.mediaRecorder.ondataavailable = e => {
|
||||||
|
if (e.data.size > 0) {
|
||||||
this.audioChunks.push(e.data);
|
this.audioChunks.push(e.data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.startedOn = Date.now();
|
||||||
this.mediaRecorder.start();
|
this.mediaRecorder.start();
|
||||||
|
this.setState({
|
||||||
|
recording: true
|
||||||
|
}, this.visualize);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.state.enabled ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {
|
return this.state.enabled ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "audio-visualiser",
|
className: "audio"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("canvas", {
|
||||||
|
className: "visualiser",
|
||||||
width: "200",
|
width: "200",
|
||||||
height: "60",
|
height: "60",
|
||||||
ref: this.canvasRef
|
ref: this.canvasRef
|
||||||
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
|
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "record",
|
className: "duration"
|
||||||
onClick: this.handleStart
|
}, this.state.duration), this.state.recording ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
}, "Record"), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
|
href: "#",
|
||||||
className: "stop",
|
onClick: this.handleStop,
|
||||||
onClick: this.handleStop
|
title: "Pause"
|
||||||
}, "Stop")) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, "Audio not available");
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: "material-icons"
|
||||||
|
}, "pause_circle_outline")) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
|
href: "#",
|
||||||
|
onClick: this.handleStart,
|
||||||
|
title: "Resume"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: "material-icons"
|
||||||
|
}, "mic"))) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, "Audio not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user