diff options
author | bobzel <zzzman@gmail.com> | 2023-05-14 12:06:31 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2023-05-14 12:06:31 -0400 |
commit | cfd353baf7356024dc88c61289755dd6699ae9fd (patch) | |
tree | 971b25f07ff19cde5b3f40dc440e6dfa02944e18 /src/client/views/AudioWaveform.tsx | |
parent | 24f9e3ddefb1853cce3f3c51dfbe6183d88bce78 (diff) | |
parent | 42afc0250de658fc3e924864bfae5afb4edec335 (diff) |
Merge branch 'master' into UI_Update_Eric_Ma
Diffstat (limited to 'src/client/views/AudioWaveform.tsx')
-rw-r--r-- | src/client/views/AudioWaveform.tsx | 124 |
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 +} |