import React = require("react"); import axios from "axios"; import { action, computed, reaction, IReactionDisposer } 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, NumCast } from "../../fields/Types"; import { numberRange } from "../../Utils"; import "./AudioWaveform.scss"; import { Colors } from "./global/globalEnums"; export interface AudioWaveformProps { duration: number; // length of media clip rawDuration: number; // length of underlying media data mediaPath: string; layoutDoc: Doc; trimming: boolean; clipStart: number; clipEnd: number; PanelHeight: () => number; } @observer export class AudioWaveform extends React.Component { public static NUMBER_OF_BUCKETS = 100; _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; } audioBucketField = (start: number, end: number) => { return "audioBuckets-" + start.toFixed(2) + "-" + end.toFixed(2); } @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)], listSpec("number"), []); } componentWillUnmount() { this._disposer?.(); } componentDidMount() { this._disposer = reaction(() => [this.clipStart, this.clipEnd, this.audioBuckets.length], (range) => { if (range[2] !== AudioWaveform.NUMBER_OF_BUCKETS) { if (!this.props.layoutDoc[this.audioBucketField(range[0], range[1])]) { // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time. this.props.layoutDoc[this.audioBucketField(range[0], range[1])] = new List(numberRange(AudioWaveform.NUMBER_OF_BUCKETS)); setTimeout(this.createWaveformBuckets); } } }, { fireImmediately: true }); } // decodes the audio file into peaks for generating the waveform createWaveformBuckets = async () => { 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 = this.clipStart / this.props.rawDuration; const endInd = this.clipEnd / this.props.rawDuration; const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length)); const bucketDataSize = Math.floor( decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS ); const brange = Array.from(Array(bucketDataSize)); const bucketList = numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map( (i: number) => brange.reduce( (p, x, j) => Math.abs( Math.max(p, decodedAudioData[i * bucketDataSize + j]) ), 0 ) / 2 ); this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)] = new List(bucketList); }) ); } ); } render() { return (
); } }