aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/AudioWaveform.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-05-14 12:06:31 -0400
committerbobzel <zzzman@gmail.com>2023-05-14 12:06:31 -0400
commitcfd353baf7356024dc88c61289755dd6699ae9fd (patch)
tree971b25f07ff19cde5b3f40dc440e6dfa02944e18 /src/client/views/AudioWaveform.tsx
parent24f9e3ddefb1853cce3f3c51dfbe6183d88bce78 (diff)
parent42afc0250de658fc3e924864bfae5afb4edec335 (diff)
Merge branch 'master' into UI_Update_Eric_Ma
Diffstat (limited to 'src/client/views/AudioWaveform.tsx')
-rw-r--r--src/client/views/AudioWaveform.tsx124
1 files changed, 57 insertions, 67 deletions
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 525c0ce5a..14c922526 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -1,27 +1,25 @@
-import React = require("react");
-import axios from "axios";
-import { action, computed, IReactionDisposer, reaction } from "mobx";
-import { observer } from "mobx-react";
-import Waveform from "react-audio-waveform";
-import { Doc } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { listSpec } from "../../fields/Schema";
-import { Cast } from "../../fields/Types";
-import { numberRange } from "../../Utils";
-import "./AudioWaveform.scss";
-import { Colors } from "./global/globalEnums";
-
+import React = require('react');
+import axios from 'axios';
+import { action, computed, IReactionDisposer, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import Waveform from 'react-audio-waveform';
+import { Doc } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { listSpec } from '../../fields/Schema';
+import { Cast } from '../../fields/Types';
+import { numberRange } from '../../Utils';
+import './AudioWaveform.scss';
+import { Colors } from './global/globalEnums';
/**
* AudioWaveform
- *
+ *
* Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
* Uses react-audio-waveform package.
* Bins the audio data into audioBuckets which are passed to package to render the lines.
* Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
*/
-
export interface AudioWaveformProps {
duration: number; // length of media clip
rawDuration: number; // length of underlying media data
@@ -32,6 +30,7 @@ export interface AudioWaveformProps {
zoomFactor: number;
PanelHeight: number;
PanelWidth: number;
+ fieldKey: string;
}
@observer
@@ -40,80 +39,71 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
_disposer: IReactionDisposer | undefined;
- @computed get waveHeight() { return Math.max(50, this.props.PanelHeight); }
-
- @computed get clipStart() { return this.props.clipStart; }
- @computed get clipEnd() { return this.props.clipEnd; }
- @computed get zoomFactor() { return this.props.zoomFactor; }
+ @computed get waveHeight() {
+ return Math.max(50, this.props.PanelHeight);
+ }
- @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec("number"), []); }
- audioBucketField = (start: number, end: number, zoomFactor: number) => "audioBuckets/" + "/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_") + "/" + (zoomFactor * 10);
+ @computed get clipStart() {
+ return this.props.clipStart;
+ }
+ @computed get clipEnd() {
+ return this.props.clipEnd;
+ }
+ @computed get zoomFactor() {
+ return this.props.zoomFactor;
+ }
+ @computed get audioBuckets() {
+ return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec('number'), []);
+ }
+ audioBucketField = (start: number, end: number, zoomFactor: number) => this.props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
componentWillUnmount() {
this._disposer?.();
}
componentDidMount() {
- this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
+ this._disposer = reaction(
+ () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
if (!this.props.layoutDoc[fieldKey]) {
// setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
- const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec("number"));
- this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice(clipStart / this.props.rawDuration * waveform.length, clipEnd / this.props.rawDuration * waveform.length));
+ const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec('number'));
+ this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this.props.rawDuration) * waveform.length, (clipEnd / this.props.rawDuration) * waveform.length));
setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
}
- }, { fireImmediately: true });
+ },
+ { fireImmediately: true }
+ );
}
// decodes the audio file into peaks for generating the waveform
createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
- axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
- (response) => {
- const context = new window.AudioContext();
- context.decodeAudioData(
- response.data,
- action((buffer) => {
- const rawDecodedAudioData = buffer.getChannelData(0);
- const startInd = clipStart / this.props.rawDuration;
- const endInd = clipEnd / this.props.rawDuration;
- const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
- const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
+ axios({ url: this.props.mediaPath, responseType: 'arraybuffer' }).then(response => {
+ const context = new window.AudioContext();
+ context.decodeAudioData(
+ response.data,
+ action(buffer => {
+ const rawDecodedAudioData = buffer.getChannelData(0);
+ const startInd = clipStart / this.props.rawDuration;
+ const endInd = clipEnd / this.props.rawDuration;
+ const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
+ const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
- const bucketDataSize = Math.floor(
- decodedAudioData.length / numBuckets
- );
- const brange = Array.from(Array(bucketDataSize));
- const bucketList = numberRange(numBuckets).map(
- (i: number) =>
- brange.reduce(
- (p, x, j) =>
- Math.abs(
- Math.max(p, decodedAudioData[i * bucketDataSize + j])
- ),
- 0
- ) / 2
- );
- this.props.layoutDoc[fieldKey] = new List<number>(bucketList);
- })
- );
- }
- );
- }
+ const bucketDataSize = Math.floor(decodedAudioData.length / numBuckets);
+ const brange = Array.from(Array(bucketDataSize));
+ const bucketList = numberRange(numBuckets).map((i: number) => brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2);
+ this.props.layoutDoc[fieldKey] = new List<number>(bucketList);
+ })
+ );
+ });
+ };
render() {
return (
<div className="audioWaveform">
- <Waveform
- color={Colors.MEDIUM_BLUE_ALT}
- height={this.waveHeight}
- barWidth={200 / this.audioBuckets.length}
- pos={this.props.duration}
- duration={this.props.duration}
- peaks={this.audioBuckets}
- progressColor={Colors.MEDIUM_BLUE_ALT}
- />
+ <Waveform color={Colors.MEDIUM_BLUE_ALT} height={this.waveHeight} barWidth={200 / this.audioBuckets.length} pos={this.props.duration} duration={this.props.duration} peaks={this.audioBuckets} progressColor={Colors.MEDIUM_BLUE_ALT} />
</div>
);
}
-} \ No newline at end of file
+}