diff options
author | bobzel <zzzman@gmail.com> | 2021-09-24 00:40:30 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2021-09-24 00:40:30 -0400 |
commit | a4b3114f3792c80e20a3f40053ec4724729c1100 (patch) | |
tree | 534212ddce5dea1919aa18cb45410c63ea16d774 /src | |
parent | 9675e948be8a7ea2d86c8ca68a89c09452ece0e7 (diff) |
added doubleclick on clip button to switch to editing entire timeline vs. single click to edit just the clip. fixed some audio timeline bugs with printing out current time and time span when trimming. cleaned up audioWaveform bucket fields.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/AudioWaveform.tsx | 24 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 13 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 78 |
3 files changed, 56 insertions, 59 deletions
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 7d83ea3dc..f7b117130 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -32,27 +32,25 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> { @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); } + audioBucketField = (start: number, end: number) => { return "audioBuckets/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_"); } @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<number>(numberRange(AudioWaveform.NUMBER_OF_BUCKETS)); - setTimeout(this.createWaveformBuckets); - } + this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd) }), + ({ clipStart, clipEnd, fieldKey }) => { + 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. + this.props.layoutDoc[fieldKey] = new List<number>(numberRange(AudioWaveform.NUMBER_OF_BUCKETS)); + setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd)); } }, { fireImmediately: true }); } // decodes the audio file into peaks for generating the waveform - createWaveformBuckets = async () => { + createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number) => { axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then( (response) => { const context = new window.AudioContext(); @@ -60,8 +58,8 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> { response.data, action((buffer) => { const rawDecodedAudioData = buffer.getChannelData(0); - const startInd = this.clipStart / this.props.rawDuration; - const endInd = this.clipEnd / this.props.rawDuration; + 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 bucketDataSize = Math.floor( @@ -78,7 +76,7 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> { 0 ) / 2 ); - this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)] = new List<number>(bucketList); + this.props.layoutDoc[fieldKey] = new List<number>(bucketList); }) ); } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index e00e66295..7859d3c3f 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -50,7 +50,6 @@ import { DragManager } from "../../util/DragManager"; type PanZoomDocument = makeInterface<[]>; const PanZoomDocument = makeInterface(); export type CollectionStackedTimelineProps = { - clipDuration: number; Play: () => void; Pause: () => void; playLink: (linkDoc: Doc) => void; @@ -65,6 +64,7 @@ export type CollectionStackedTimelineProps = { trimming: boolean; clipStart: number; clipEnd: number; + clipDuration: number; trimStart: () => number; trimEnd: () => number; trimDuration: () => number; @@ -165,8 +165,8 @@ export class CollectionStackedTimeline extends CollectionSubView< } toTimeline = (screen_delta: number, width: number) => { return Math.max( - this.trimStart, - Math.min(this.trimEnd, (screen_delta / width) * this.props.trimDuration() + this.trimStart)); + this.props.clipStart, + Math.min(this.props.clipEnd, (screen_delta / width) * this.props.clipDuration + this.props.clipStart)); } rangeClickScript = () => CollectionStackedTimeline.RangeScript; @@ -282,12 +282,7 @@ export class CollectionStackedTimeline extends CollectionSubView< this.props.isSelected(true) || this.props.isContentActive(), undefined, () => { - !wasPlaying && - (this.props.trimming && this.clipDuration ? - this.props.setTime(((clientX - rect.x) / rect.width) * this.clipDuration) - : - this.props.setTime(((clientX - rect.x) / rect.width) * this.props.trimDuration() + this.trimStart) - ); + !wasPlaying && this.props.setTime(((clientX - rect.x) / rect.width) * this.clipDuration + this.props.clipStart); } ); } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 6a25ffaeb..bfc15cea8 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -16,7 +16,7 @@ import { makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; import { AudioField, nullAudio } from "../../../fields/URLField"; -import { emptyFunction, formatTime, OmitKeys } from "../../../Utils"; +import { emptyFunction, formatTime, OmitKeys, setupMoveUpEvents, returnFalse } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -48,13 +48,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent< ViewBoxAnnotatableProps & FieldViewProps, AudioDocument >(AudioDocument) { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(AudioBox, fieldKey); - } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } public static Enabled = false; static playheadWidth = 40; // width of playhead static heightPercent = 75; // height of timeline in percent of height of audioBox. static Instance: AudioBox; + static ScopeAll = 2; + static ScopeClip = 1; + static ScopeNone = 0; _disposers: { [name: string]: IReactionDisposer } = {}; _ele: HTMLAudioElement | null = null; @@ -74,14 +75,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent< @observable _position: number = 0; @observable _waveHeight: Opt<number> = this.layoutDoc._height; @observable _paused: boolean = false; - @observable _trimming: boolean = false; + @observable _trimming: number = AudioBox.ScopeNone; @observable _trimStart: number = NumCast(this.layoutDoc.clipStart); @observable _trimEnd: number | undefined = Cast(this.layoutDoc.clipEnd, "number"); - @computed get clipStart() { return NumCast(this.layoutDoc.clipStart); } - @computed get clipEnd() { return NumCast(this.layoutDoc.clipEnd, this.duration); } - @computed get trimStart() { return this._trimming ? this._trimStart : NumCast(this.layoutDoc.clipStart); } + @computed get clipStart() { return this._trimming === AudioBox.ScopeAll ? 0 : NumCast(this.layoutDoc.clipStart); } + @computed get clipDuration() { + return this._trimming === AudioBox.ScopeAll ? NumCast(this.dataDoc[`${this.fieldKey}-duration`]) : + NumCast(this.layoutDoc.clipEnd, this.clipStart + NumCast(this.dataDoc[`${this.fieldKey}-duration`])) - this.clipStart; + } + @computed get clipEnd() { return this.clipStart + this.clipDuration; } + @computed get trimStart() { return this._trimming !== AudioBox.ScopeNone ? this._trimStart : NumCast(this.layoutDoc.clipStart); } + @computed get trimDuration() { return this.trimEnd - this.trimStart; } @computed get trimEnd() { - return this._trimming && this._trimEnd !== undefined ? this._trimEnd : NumCast(this.layoutDoc.clipEnd, this.duration); + return this._trimming !== AudioBox.ScopeNone && this._trimEnd !== undefined ? this._trimEnd : NumCast(this.layoutDoc.clipEnd, this.clipDuration); } @computed get mediaState(): @@ -113,13 +119,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent< @computed get rawDuration() { return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); } - @computed get duration() { - return NumCast(this.layoutDoc.clipEnd, NumCast(this.layoutDoc.clipStart) + NumCast(this.dataDoc[`${this.fieldKey}-duration`])) - NumCast(this.layoutDoc.clipStart); - // NumCast(this.dataDoc[`${this.fieldKey}-duration`]); - } - @computed get trimDuration() { - return this.trimEnd - this.trimStart; - } @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey]); } @@ -269,7 +268,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< this._ele.currentTime = start; this._ele.play(); runInAction(() => (this.mediaState = "playing")); - if (endTime !== this.duration) { + if (endTime !== this.clipDuration) { this._play = setTimeout( () => { this._ended = fullPlay ? true : this._ended; @@ -309,7 +308,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< const [{ result }] = await Networking.UploadFilesToServer(e.data); if (!(result instanceof Error)) { this.props.Document[this.props.fieldKey] = new AudioField(result.accessPaths.agnostic.client); - if (this._trimEnd === undefined) this._trimEnd = this.duration; + if (this._trimEnd === undefined) this._trimEnd = this.clipDuration; } }; this._recordStart = new Date().getTime(); @@ -359,9 +358,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent< this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; this.mediaState = "paused"; - this._trimEnd = this.duration; + this._trimEnd = this.clipDuration; this.layoutDoc.clipStart = 0; - this.layoutDoc.clipEnd = this.duration; + this.layoutDoc.clipEnd = this.clipDuration; this._stream?.getAudioTracks()[0].stop(); const ind = DocUtils.ActiveRecordings.indexOf(this); ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); @@ -378,7 +377,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // for play button Play = (e?: any) => { let start; - if (this._ended || this._ele!.currentTime === this.duration) { + if (this._ended || this._ele!.currentTime === this.clipDuration) { start = NumCast(this.layoutDoc.clipStart); this._ended = false; } @@ -494,11 +493,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // shows trim controls @action - startTrim = () => { + startTrim = (scope: number) => { if (this.mediaState === "playing") { this.Pause(); } - this._trimming = true; + this._trimming = scope; } // hides trim controls and displays new clip @@ -510,7 +509,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< this.layoutDoc.clipStart = this.trimStart; this.layoutDoc.clipEnd = this.trimEnd; this.setAnchorTime(Math.max(Math.min(this.trimEnd, this._ele!.currentTime), this.trimStart)); - this._trimming = false; + this._trimming = AudioBox.ScopeNone; }); @action @@ -579,15 +578,15 @@ export class AudioBox extends ViewBoxAnnotatableComponent< rawDuration={this.rawDuration} // this edits the entire waveform when trimming is activated - clipStart={this._trimming ? 0 : this.clipStart} - clipEnd={this._trimming ? this.rawDuration : this.clipEnd} - clipDuration={this._trimming ? this.rawDuration : this.duration} + clipStart={this._trimming === AudioBox.ScopeAll ? 0 : this.clipStart} + clipEnd={this._trimming === AudioBox.ScopeAll ? this.rawDuration : this.clipEnd} + clipDuration={this._trimming === AudioBox.ScopeAll ? this.rawDuration : this.clipDuration} // this edits just the current waveform clip when trimming is activated // clipStart={this.clipStart} // clipEnd={this.clipEnd} // clipDuration={this.duration} - trimming={this._trimming} + trimming={this._trimming !== AudioBox.ScopeNone} trimStart={this.trimStartFunc} trimEnd={this.trimEndFunc} trimDuration={this.trimDurationFunc} @@ -596,6 +595,15 @@ export class AudioBox extends ViewBoxAnnotatableComponent< /> ); } + onClipPointerDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => { + if (doubleTap) { + this.startTrim(AudioBox.ScopeAll); + } else { + this._trimming !== AudioBox.ScopeNone ? this.finishTrim() : this.startTrim(AudioBox.ScopeClip); + } + })); + } render() { const interactive = @@ -697,11 +705,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent< </div> <div className="audiobox-buttons" - title={this._trimming ? "finish" : "trim"} - onClick={this._trimming ? this.finishTrim : this.startTrim} + title={this._trimming !== AudioBox.ScopeNone ? "finish" : "trim"} + onPointerDown={this.onClipPointerDown} > <FontAwesomeIcon - icon={this._trimming ? "check" : "cut"} + icon={this._trimming !== AudioBox.ScopeNone ? "check" : "cut"} size={"1x"} /> </div> @@ -719,14 +727,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent< </div> {this.audio} <div className="audioBox-current-time"> - {this._trimming ? - formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode))) - : formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.trimStart)))} + {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.clipStart)))} </div> <div className="audioBox-total-time"> - {this._trimming || !this._trimEnd ? - formatTime(Math.round(NumCast(this.duration))) - : formatTime(Math.round(NumCast(this.trimDuration)))} + {formatTime(Math.round(NumCast(this.clipDuration)))} </div> </div> </div> |