diff options
author | bobzel <zzzman@gmail.com> | 2021-09-24 22:31:22 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2021-09-24 22:31:22 -0400 |
commit | 6bcf4ae5f3953ba10ba1fba6c7d2246514a90eed (patch) | |
tree | 859bb94c62c70a5c731376afe4aae5573036c8f7 | |
parent | cbf22b4ccaab14a7c8cb62137ea09b58a001e13a (diff) |
refactored trim stuff out of audiobox into collectionstackedtimeline so that videobox can reuse trimming
-rw-r--r-- | src/client/views/AudioWaveform.tsx | 5 | ||||
-rw-r--r-- | src/client/views/ContextMenuItem.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 193 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 350 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 59 |
5 files changed, 248 insertions, 363 deletions
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 0a441552e..0e9c00656 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -16,7 +16,6 @@ export interface AudioWaveformProps { rawDuration: number; // length of underlying media data mediaPath: string; layoutDoc: Doc; - trimming: boolean; clipStart: number; clipEnd: number; PanelHeight: () => number; @@ -42,8 +41,8 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> { ({ 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. - const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration)], listSpec("number"), []); - this.props.layoutDoc[fieldKey] = 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)], 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)); } }, { fireImmediately: true }); diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index c3921d846..25d00f701 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -39,7 +39,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => { if ("event" in this.props) { - this.props.closeMenu && this.props.closeMenu(); + this.props.closeMenu?.(); let batch: UndoManager.Batch | undefined; if (this.props.undoable !== false) { batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`); @@ -90,7 +90,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select </span> ) : null} <div className="contextMenu-description"> - {this.props.description.replace(":","")} + {this.props.description.replace(":", "")} </div> </div> ); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 43f78cf78..48014921a 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -61,24 +61,21 @@ export type CollectionStackedTimelineProps = { mediaPath: string; dictationKey: string; rawDuration: number; - trimming: boolean; - clipStart: number; - clipEnd: number; - clipDuration: number; - trimStart: () => number; - trimEnd: () => number; - trimDuration: () => number; - setStartTrim: (newStart: number) => void; - setEndTrim: (newEnd: number) => void; + fieldKey: string; }; +export enum TrimScope { + All = 2, + Clip = 1, + None = 0, +} + @observer export class CollectionStackedTimeline extends CollectionSubView< PanZoomDocument, CollectionStackedTimelineProps >(PanZoomDocument) { - @observable static SelectingRegion: CollectionStackedTimeline | undefined = - undefined; + @observable static SelectingRegion: CollectionStackedTimeline | undefined; static RangeScript: ScriptField; static LabelScript: ScriptField; static RangePlayScript: ScriptField; @@ -87,37 +84,43 @@ export class CollectionStackedTimeline extends CollectionSubView< private _timeline: HTMLDivElement | null = null; private _markerStart: number = 0; @observable _markerEnd: number = 0; + @observable _trimming: number = TrimScope.None; + @observable _trimStart: number = 0; + @observable _trimEnd: number = 0; - get minLength() { - const rect = this._timeline?.getBoundingClientRect(); - if (rect) { - return 0.05 * this.clipDuration; - } - return 0; - } + get minTrimLength() { return this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0; } + @computed get trimStart() { return this.IsTrimming !== TrimScope.None ? this._trimStart : this.clipStart; } + @computed get trimDuration() { return this.trimEnd - this.trimStart; } + @computed get trimEnd() { return this.IsTrimming !== TrimScope.None ? this._trimEnd : this.clipEnd; } - get trimStart() { - return this.props.trimStart(); - } + @computed get clipStart() { return this.IsTrimming === TrimScope.All ? 0 : NumCast(this.layoutDoc.clipStart); } + @computed get clipDuration() { return this.clipEnd - this.clipStart; } + @computed get clipEnd() { return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration); } - get trimEnd() { - return this.props.trimEnd(); - } + @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); } - get clipDuration() { - return this.props.clipDuration; - } + public get IsTrimming() { return this._trimming; } - @computed get currentTime() { - return NumCast(this.layoutDoc._currentTimecode); + @action + public StartTrimming(scope: TrimScope) { + this._trimStart = this.clipStart; + this._trimEnd = this.clipEnd; + this._trimming = scope; + } + @action + public StopTrimming() { + this.layoutDoc.clipStart = this.trimStart; + this.layoutDoc.clipEnd = this.trimEnd; + this._trimming = TrimScope.None; } + @computed get selectionContainer() { return CollectionStackedTimeline.SelectingRegion !== this ? null : ( <div className="collectionStackedTimeline-selector" style={{ - left: `${((Math.min(this._markerStart, this._markerEnd) - this.trimStart) / this.props.trimDuration()) * 100}%`, - width: `${(Math.abs(this._markerStart - this._markerEnd) / this.props.trimDuration()) * 100}%`, + left: `${((Math.min(this._markerStart, this._markerEnd) - this.trimStart) / this.trimDuration) * 100}%`, + width: `${(Math.abs(this._markerStart - this._markerEnd) / this.trimDuration) * 100}%`, }} /> ); @@ -145,28 +148,21 @@ export class CollectionStackedTimeline extends CollectionSubView< componentDidMount() { document.addEventListener("keydown", this.keyEvents, true); } + + @action componentWillUnmount() { document.removeEventListener("keydown", this.keyEvents, true); if (CollectionStackedTimeline.SelectingRegion === this) { - runInAction( - () => (CollectionStackedTimeline.SelectingRegion = undefined) - ); + CollectionStackedTimeline.SelectingRegion = undefined; } } - anchorStart = (anchor: Doc) => - NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])) - anchorEnd = (anchor: Doc, val: any = null) => { - const endVal = NumCast(anchor[this.props.endTag], val); - return NumCast( - anchor._timecodeToHide, - endVal === undefined ? null : endVal - ); - } + anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])) + anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val)); toTimeline = (screen_delta: number, width: number) => { return Math.max( - this.props.clipStart, - Math.min(this.props.clipEnd, (screen_delta / width) * this.props.clipDuration + this.props.clipStart)); + this.clipStart, + Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart)); } rangeClickScript = () => CollectionStackedTimeline.RangeScript; @@ -233,10 +229,7 @@ export class CollectionStackedTimeline extends CollectionSubView< !wasSelecting && CollectionStackedTimeline.SelectingRegion !== this ) { - this._markerStart = this._markerEnd = this.toTimeline( - clientX - rect.x, - rect.width - ); + this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); CollectionStackedTimeline.SelectingRegion = this; wasSelecting = true; } @@ -254,7 +247,7 @@ export class CollectionStackedTimeline extends CollectionSubView< !isClick && CollectionStackedTimeline.SelectingRegion === this && Math.abs(movement[0]) > 15 && - !this.props.trimming + !this.IsTrimming ) { const anchor = CollectionStackedTimeline.createAnchor( this.rootDoc, @@ -287,7 +280,7 @@ export class CollectionStackedTimeline extends CollectionSubView< this.currentTime ); } else { - !wasPlaying && this.props.setTime(((clientX - rect.x) / rect.width) * this.clipDuration + this.props.clipStart); + !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } } ); @@ -304,21 +297,19 @@ export class CollectionStackedTimeline extends CollectionSubView< e, action((e, [], []) => { if (rect && this.props.isContentActive()) { - this.props.setStartTrim(Math.min( + this._trimStart = Math.min( Math.max( this.trimStart + (e.movementX / rect.width) * this.clipDuration, 0 ), - this.trimEnd - this.minLength - )); + this.trimEnd - this.minTrimLength + ); } return false; }), emptyFunction, action((e, doubleTap) => { - if (doubleTap) { - this.props.setStartTrim(this.props.clipStart); - } + doubleTap && (this._trimStart = this.clipStart); }) ); } @@ -332,21 +323,19 @@ export class CollectionStackedTimeline extends CollectionSubView< e, action((e, [], []) => { if (rect && this.props.isContentActive()) { - this.props.setEndTrim(Math.max( + this._trimEnd = Math.max( Math.min( this.trimEnd + (e.movementX / rect.width) * this.clipDuration, - this.props.clipStart + this.clipDuration + this.clipStart + this.clipDuration ), - this.trimStart + this.minLength - )); + this.trimStart + this.minTrimLength + ); } return false; }), emptyFunction, action((e, doubleTap) => { - if (doubleTap) { - this.props.setEndTrim(this.props.clipEnd); - } + doubleTap && (this._trimEnd = this.clipEnd); }) ); } @@ -356,17 +345,16 @@ export class CollectionStackedTimeline extends CollectionSubView< if (!de.embedKey && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false; if (!super.onInternalDrop(e, de)) return false; - // determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); const x = localPt[0] - docDragData.offset[0]; const timelinePt = this.toTimeline(x, this.props.PanelWidth()); for (let i = 0; i < docDragData.droppedDocuments.length; i++) { const d = Doc.GetProto(docDragData.droppedDocuments[i]); - if (d._timecodeToHide !== undefined) { - d._timecodeToHide = timelinePt + NumCast(d._timecodeToHide) - NumCast(d._timecodeToShow); + if (this.anchorEnd(d) !== undefined) { + d[d._timecodeToHide === undefined ? this.props.endTag : "_timecodeToHide"] = timelinePt + this.anchorEnd(d) - this.anchorStart(d); } - d._timecodeToShow = timelinePt; + d[d._timecodToShow === undefined ? this.props.startTag : "_timecodToShow"] = timelinePt; } return true; @@ -403,7 +391,7 @@ export class CollectionStackedTimeline extends CollectionSubView< }); Doc.GetProto(anchor)[startTag] = anchorStartTime; Doc.GetProto(anchor)[endTag] = anchorEndTime; - if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) { + if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) { Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor); } else { dataDoc[fieldKey] = new List<Doc>([anchor]); @@ -546,10 +534,9 @@ export class CollectionStackedTimeline extends CollectionSubView< duration={this.clipDuration} mediaPath={this.props.mediaPath} layoutDoc={this.layoutDoc} - clipStart={this.props.clipStart} - clipEnd={this.props.clipEnd} + clipStart={this.clipStart} + clipEnd={this.clipEnd} PanelHeight={this.timelineContentHeight} - trimming={this.props.trimming} /> </div> ); @@ -582,12 +569,12 @@ export class CollectionStackedTimeline extends CollectionSubView< d.anchor, start + (10 / timelineContentWidth) * this.clipDuration ); - if (end < this.props.clipStart || start > this.props.clipEnd) return (null); - const left = Math.max((start - this.props.clipStart) / this.clipDuration * timelineContentWidth, 0); + if (end < this.clipStart || start > this.clipEnd) return (null); + const left = Math.max((start - this.clipStart) / this.clipDuration * timelineContentWidth, 0); const top = (d.level / maxLevel) * this.timelineContentHeight(); const timespan = end - start; const width = (timespan / this.clipDuration) * timelineContentWidth; - const height = (this.timelineContentHeight()) / maxLevel; + const height = this.timelineContentHeight() / maxLevel; return this.props.Document.hideAnchors ? null : ( <div className={"collectionStackedTimeline-marker-timeline"} @@ -624,28 +611,28 @@ export class CollectionStackedTimeline extends CollectionSubView< </div> ); })} - {!this.props.trimming && this.selectionContainer} + {!this.IsTrimming && this.selectionContainer} {this.renderAudioWaveform} {this.renderDictation} <div className="collectionStackedTimeline-current" style={{ - left: `${((this.currentTime - this.props.clipStart) / this.clipDuration) * 100}%`, + left: `${((this.currentTime - this.clipStart) / this.clipDuration) * 100}%`, }} /> - {this.props.trimming && ( + {this.IsTrimming && ( <> <div className="collectionStackedTimeline-trim-shade" - style={{ width: `${((this.trimStart - this.props.clipStart) / this.clipDuration) * 100}%` }} + style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }} ></div> <div className="collectionStackedTimeline-trim-controls" style={{ - left: `${((this.trimStart - this.props.clipStart) / this.clipDuration) * 100}%`, + left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`, width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`, }} > @@ -662,8 +649,8 @@ export class CollectionStackedTimeline extends CollectionSubView< <div className="collectionStackedTimeline-trim-shade" style={{ - left: `${((this.trimEnd - this.props.clipStart) / this.clipDuration) * 100}%`, - width: `${((this.props.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`, + left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`, + width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`, }} ></div> </> @@ -754,8 +741,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> return this.props.toTimeline(e.clientX - rect.x, rect.width); }; const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => { - const timelineOnly = - Cast(anchor[this.props.startTag], "number", null) !== undefined; + const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; if (timelineOnly) { if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined; Doc.SetInPlace( @@ -767,9 +753,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> if (!left) Doc.SetInPlace(anchor, "borderRounding", time !== undefined ? undefined : "100%", true); } else { - left - ? (anchor._timecodeToShow = time) - : (anchor._timecodeToHide = time); + anchor[left ? "_timecodeToShow" : "_timecodeToHide"] = time; } return false; }; @@ -803,10 +787,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), - x: number, - y: number, - width: number, - height: number + screenXf: () => Transform, + width: () => number, + height: () => number ) { const anchor = observable({ view: undefined as any }); const focusFunc = ( @@ -825,24 +808,20 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} - ref={action((r: DocumentView | null) => (anchor.view = r))} + ref={action((r: DocumentView | null) => anchor.view = r)} Document={mark} DataDoc={undefined} renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())} isDocumentActive={this.props.isDocumentActive} - PanelWidth={() => width} - PanelHeight={() => height} - ScreenToLocalTransform={() => - this.props.ScreenToLocalTransform().translate(-x, -y) - } + PanelWidth={width} + PanelHeight={height} + ScreenToLocalTransform={screenXf} focus={focusFunc} rootSelected={returnFalse} onClick={script} - onDoubleClick={ - this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript - } + onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript} ignoreAutoHeight={false} hideResizeHandles={true} bringToFront={emptyFunction} @@ -852,15 +831,17 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> }; }); + anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top); + width = () => this.props.width; + height = () => this.props.height; render() { const inner = this.renderInner( this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, - this.props.left, - this.props.top, - this.props.width, - this.props.height + this.anchorScreenToLocalXf, + this.width, + this.height ); return ( <> @@ -876,9 +857,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> <div key="right" className="collectionStackedTimeline-resizer" - onPointerDown={(e) => - this.onAnchorDown(e, this.props.mark, false) - } + onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, false)} /> </> )} diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index bfc15cea8..81367ed19 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -14,15 +14,15 @@ import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, NumCast } from "../../../fields/Types"; +import { Cast, NumCast, DateCast } from "../../../fields/Types"; import { AudioField, nullAudio } from "../../../fields/URLField"; -import { emptyFunction, formatTime, OmitKeys, setupMoveUpEvents, returnFalse } from "../../../Utils"; +import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { DragManager } from "../../util/DragManager"; import { SnappingManager } from "../../util/SnappingManager"; -import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; +import { CollectionStackedTimeline, TrimScope } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { @@ -43,20 +43,21 @@ declare class MediaRecorder { type AudioDocument = makeInterface<[typeof documentSchema]>; const AudioDocument = makeInterface(documentSchema); +enum media_state { + PendingRecording = "pendingRecording", + Recording = "recording", + Paused = "paused", + Playing = "playing" +}; @observer -export class AudioBox extends ViewBoxAnnotatableComponent< - ViewBoxAnnotatableProps & FieldViewProps, - AudioDocument ->(AudioDocument) { +export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, AudioDocument>(AudioDocument) { 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; + _dropDisposer?: DragManager.DragDropDisposer; _disposers: { [name: string]: IReactionDisposer } = {}; _ele: HTMLAudioElement | null = null; _stackedTimeline = React.createRef<CollectionStackedTimeline>(); @@ -68,81 +69,39 @@ export class AudioBox extends ViewBoxAnnotatableComponent< _stream: MediaStream | undefined; _start: number = 0; _play: any = null; - _ended: boolean = false; @observable static _scrubTime = 0; @observable _markerEnd: number = 0; @observable _position: number = 0; @observable _waveHeight: Opt<number> = this.layoutDoc._height; @observable _paused: 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 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 !== AudioBox.ScopeNone && this._trimEnd !== undefined ? this._trimEnd : NumCast(this.layoutDoc.clipEnd, this.clipDuration); - } + @computed get recordingStart() { return DateCast(this.dataDoc[this.fieldKey + "-recordingStart"])?.date.getTime(); } + @computed get rawDuration() { return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); } + @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey]); } + @computed get links() { return DocListCast(this.dataDoc.links); } + @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct time + @computed get heightPercent() { return AudioBox.heightPercent; } + @computed get mediaState() { return this.layoutDoc.mediaState as media_state; } + set mediaState(value) { this.layoutDoc.mediaState = value; } - @computed get mediaState(): - | undefined - | "pendingRecording" - | "recording" - | "paused" - | "playing" { - return this.layoutDoc.mediaState as - | undefined - | "pendingRecording" - | "recording" - | "paused" - | "playing"; - } - set mediaState(value) { - this.layoutDoc.mediaState = value; - } - public static SetScrubTime = action((timeInMillisFrom1970: number) => { - AudioBox._scrubTime = 0; - AudioBox._scrubTime = timeInMillisFrom1970; - }); - @computed get recordingStart() { - return Cast( - this.dataDoc[this.props.fieldKey + "-recordingStart"], - DateField - )?.date.getTime(); - } - @computed get rawDuration() { - return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); - } - @computed get anchorDocs() { - return DocListCast(this.dataDoc[this.annotationKey]); - } - @computed get links() { - return DocListCast(this.dataDoc.links); - } - @computed get pauseTime() { - return this._pauseEnd - this._pauseStart; - } // total time paused to update the correct time - @computed get heightPercent() { - return AudioBox.heightPercent; - } + get timeline() { return this._stackedTimeline.current; } constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) { super(props); AudioBox.Instance = this; } + public static SetScrubTime = action((timeInMillisFrom1970: number) => { + AudioBox._scrubTime = 0; + AudioBox._scrubTime = timeInMillisFrom1970; + }); + getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; let la2 = l.anchor2 as Doc; const linkTime = - this._stackedTimeline.current?.anchorStart(la2) || - this._stackedTimeline.current?.anchorStart(la1) || + this.timeline?.anchorStart(la2) || + this.timeline?.anchorStart(la1) || 0; if (Doc.AreProtosEqual(la1, this.dataDoc)) { la1 = l.anchor2 as Doc; @@ -152,47 +111,42 @@ export class AudioBox extends ViewBoxAnnotatableComponent< } getAnchor = () => { - return ( - CollectionStackedTimeline.createAnchor( - this.rootDoc, - this.dataDoc, - this.annotationKey, - "_timecodeToShow" /* audioStart */, - "_timecodeToHide" /* audioEnd */, - this._ele?.currentTime || - Cast(this.props.Document._currentTimecode, "number", null) || - (this.mediaState === "recording" - ? (Date.now() - (this.recordingStart || 0)) / 1000 - : undefined) - ) || this.rootDoc - ); + return CollectionStackedTimeline.createAnchor( + this.rootDoc, + this.dataDoc, + this.annotationKey, + "_timecodeToShow" /* audioStart */, + "_timecodeToHide" /* audioEnd */, + this._ele?.currentTime || + Cast(this.props.Document._currentTimecode, "number", null) || + (this.mediaState === media_state.Recording + ? (Date.now() - (this.recordingStart || 0)) / 1000 + : undefined) + ) || this.rootDoc; } componentWillUnmount() { - this.dropDisposer?.(); + this._dropDisposer?.(); Object.values(this._disposers).forEach((disposer) => disposer?.()); const ind = DocUtils.ActiveRecordings.indexOf(this); ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); } - private dropDisposer?: DragManager.DragDropDisposer; @action componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. - this.mediaState = this.path ? "paused" : undefined; + this.mediaState = this.path ? media_state.Paused : undefined as any as media_state; this.path && this.setAnchorTime(NumCast(this.layoutDoc.clipStart)); this.path && this.timecodeChanged(); this._disposers.triggerAudio = reaction( - () => - !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 - ? NumCast(this.Document._triggerAudio, null) - : undefined, + () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 + ? NumCast(this.Document._triggerAudio, null) + : undefined, (start) => - start !== undefined && - setTimeout(() => { + start !== undefined && setTimeout(() => { this.playFrom(start); setTimeout(() => { this.Document._currentTimecode = start; @@ -203,13 +157,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent< ); this._disposers.audioStop = reaction( - () => - this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo - ? Cast(this.Document._audioStop, "number", null) - : undefined, + () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo + ? Cast(this.Document._audioStop, "number", null) + : undefined, (audioStop) => - audioStop !== undefined && - setTimeout(() => { + audioStop !== undefined && setTimeout(() => { this.Pause(); setTimeout(() => (this.Document._audioStop = undefined), 10); }), // wait for mainCont and try again to play @@ -220,27 +172,25 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // for updating the timecode @action timecodeChanged = () => { - const htmlEle = this._ele; - if (this.mediaState !== "recording" && htmlEle) { + if (this.mediaState !== media_state.Recording && this._ele) { this.links .map((l) => this.getLinkData(l)) .forEach(({ la1, la2, linkTime }) => { if ( linkTime > NumCast(this.layoutDoc._currentTimecode) && - linkTime < htmlEle.currentTime + linkTime < this._ele!.currentTime ) { Doc.linkFollowHighlight(la1); } }); - this.layoutDoc._currentTimecode = htmlEle.currentTime; - + this.layoutDoc._currentTimecode = this._ele.currentTime; } } // pause play back Pause = action(() => { this._ele!.pause(); - this.mediaState = "paused"; + this.mediaState = media_state.Paused; }); // play audio for documents created during recording @@ -251,32 +201,30 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // play back the audio from time @action - playFrom = (seekTimeInSeconds: number, endTime: number = this.trimEnd, fullPlay: boolean = false) => { + playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false): any => { clearTimeout(this._play); if (Number.isNaN(this._ele?.duration)) { setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); - } else if (this._ele && AudioBox.Enabled) { - if (seekTimeInSeconds < 0) { - if (seekTimeInSeconds > -1) { - setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); - } else { - this.Pause(); - } - } else if (this.trimStart <= endTime && seekTimeInSeconds <= this.trimEnd) { - const start = Math.max(this.trimStart, seekTimeInSeconds); - const end = Math.min(this.trimEnd, endTime); + } + else if (this.timeline && this._ele && AudioBox.Enabled) { + const end = Math.min(this.timeline.trimEnd, endTime ?? this.timeline.trimEnd); + const start = Math.max(this.timeline.trimStart, seekTimeInSeconds); + if (seekTimeInSeconds >= 0 && this.timeline.trimStart <= end && seekTimeInSeconds <= this.timeline.trimEnd) { this._ele.currentTime = start; this._ele.play(); - runInAction(() => (this.mediaState = "playing")); - if (endTime !== this.clipDuration) { - this._play = setTimeout( + runInAction(() => this.mediaState = media_state.Playing); + if (end !== this.timeline.clipDuration) { + return this._play = setTimeout( () => { - this._ended = fullPlay ? true : this._ended; + if (fullPlay) this.setAnchorTime(this.timeline!.trimStart); this.Pause(); }, (end - start) * 1000 ); // use setTimeout to play a specific duration } + } + if (seekTimeInSeconds < 0 && seekTimeInSeconds > -1) { + setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); } else { this.Pause(); } @@ -285,7 +233,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // update the recording time updateRecordTime = () => { - if (this.mediaState === "recording") { + if (this.mediaState === media_state.Recording) { setTimeout(this.updateRecordTime, 30); if (this._paused) { this._pausedTime += (new Date().getTime() - this._recordStart) / 1000; @@ -300,22 +248,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent< recordAudioAnnotation = async () => { this._stream = await navigator.mediaDevices.getUserMedia({ audio: true }); this._recorder = new MediaRecorder(this._stream); - this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField( - new Date() - ); + this.dataDoc[this.fieldKey + "-recordingStart"] = new DateField(); DocUtils.ActiveRecordings.push(this); this._recorder.ondataavailable = async (e: any) => { 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.clipDuration; + this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client); } }; this._recordStart = new Date().getTime(); - runInAction(() => (this.mediaState = "recording")); + runInAction(() => this.mediaState = media_state.Recording); setTimeout(this.updateRecordTime, 0); this._recorder.start(); - setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour + setTimeout(() => this.stopRecording(), 60 * 60 * 1000); // stop after an hour } // context menu @@ -353,17 +298,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // stops the recording stopRecording = action(() => { - this._recorder.stop(); - this._recorder = undefined; - this.dataDoc[this.fieldKey + "-duration"] = - (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; - this.mediaState = "paused"; - this._trimEnd = this.clipDuration; - this.layoutDoc.clipStart = 0; - this.layoutDoc.clipEnd = this.clipDuration; - this._stream?.getAudioTracks()[0].stop(); - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + if (this._recorder) { + this._recorder.stop(); + this._recorder = undefined; + this.dataDoc[this.fieldKey + "-duration"] = + (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; + this.mediaState = media_state.Paused; + this._stream?.getAudioTracks()[0].stop(); + const ind = DocUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + } }); // button for starting and stopping the recording @@ -376,16 +320,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // for play button Play = (e?: any) => { - let start; - if (this._ended || this._ele!.currentTime === this.clipDuration) { - start = NumCast(this.layoutDoc.clipStart); - this._ended = false; - } - else { - start = this._ele!.currentTime; - } - - this.playFrom(start, this.trimEnd, true); + const eleTime = this._ele!.currentTime; + const start = eleTime === this.timeline?.trimDuration ? NumCast(this.layoutDoc.trimStart) : eleTime; + this.playFrom(start, undefined, true); e?.stopPropagation?.(); } @@ -402,7 +339,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< ); Doc.GetProto(newDoc).recordingSource = this.dataDoc; Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction( - `self.recordingSource["${this.props.fieldKey}-recordingStart"]` + `self.recordingSource["${this.fieldKey}-recordingStart"]` ); Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction( "self.recordingSource.mediaState" @@ -420,7 +357,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< // returns the path of the audio file @computed get path() { - const field = Cast(this.props.Document[this.props.fieldKey], AudioField); + const field = Cast(this.props.Document[this.fieldKey], AudioField); const path = field instanceof AudioField ? field.url.href : ""; return path === nullAudio ? "" : path; } @@ -460,68 +397,33 @@ export class AudioBox extends ViewBoxAnnotatableComponent< e.stopPropagation(); } - playing = () => this.mediaState === "playing"; + playing = () => this.mediaState === media_state.Playing; playLink = (link: Doc) => { - const stack = this._stackedTimeline.current; if (link.annotationOn === this.rootDoc) { if (!this.layoutDoc.dontAutoPlayFollowedLinks) { - this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link)); + this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link)); } else { this._ele!.currentTime = this.layoutDoc._currentTimecode = - stack?.anchorStart(link) || 0; + this.timeline?.anchorStart(link) || 0; } } else { this.links .filter((l) => l.anchor1 === link || l.anchor2 === link) .forEach((l) => { const { la1, la2 } = this.getLinkData(l); - const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2); - const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2); + const startTime = this.timeline?.anchorStart(la1) || this.timeline?.anchorStart(la2); + const endTime = this.timeline?.anchorEnd(la1) || this.timeline?.anchorEnd(la2); if (startTime !== undefined) { if (!this.layoutDoc.dontAutoPlayFollowedLinks) { - endTime - ? this.playFrom(startTime, endTime) - : this.playFrom(startTime); + this.playFrom(startTime, endTime); } else { - this._ele!.currentTime = this.layoutDoc._currentTimecode = - startTime; + this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime; } } }); } } - // shows trim controls - @action - startTrim = (scope: number) => { - if (this.mediaState === "playing") { - this.Pause(); - } - this._trimming = scope; - } - - // hides trim controls and displays new clip - @undoBatch - finishTrim = action(() => { - if (this.mediaState === "playing") { - this.Pause(); - } - 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 = AudioBox.ScopeNone; - }); - - @action - setStartTrim = (newStart: number) => { - this._trimStart = newStart; - } - - @action - setEndTrim = (newEnd: number) => { - this._trimEnd = newEnd; - } - isActiveChild = () => this._isAnyChildContentActive; timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged( @@ -543,9 +445,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent< this.heightPercent) / 100 // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; - trimEndFunc = () => this.trimEnd; - trimStartFunc = () => this.trimStart; - trimDurationFunc = () => this.trimDuration; @computed get renderTimeline() { return ( <CollectionStackedTimeline @@ -576,31 +475,28 @@ export class AudioBox extends ViewBoxAnnotatableComponent< PanelWidth={this.timelineWidth} PanelHeight={this.timelineHeight} rawDuration={this.rawDuration} - - // this edits the entire waveform when trimming is activated - 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 !== AudioBox.ScopeNone} - trimStart={this.trimStartFunc} - trimEnd={this.trimEndFunc} - trimDuration={this.trimDurationFunc} - setStartTrim={this.setStartTrim} - setEndTrim={this.setEndTrim} /> ); } + // hides trim controls and displays new clip + @undoBatch + finishTrim = action(() => { + this.Pause(); + this.setAnchorTime(Math.max(Math.min(this.timeline?.trimEnd || 0, this._ele!.currentTime), this.timeline?.trimStart || 0)); + this.timeline?.StopTrimming(); + }); + startTrim = (scope: TrimScope) => { + this.Pause(); + this.timeline?.StartTrimming(scope); + } + onClipPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => { + this.timeline && 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); + this.startTrim(TrimScope.All); + } else if (this.timeline) { + this.Pause(); + this.timeline.IsTrimming !== TrimScope.None ? this.finishTrim() : this.startTrim(TrimScope.Clip); } })); } @@ -613,12 +509,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent< return ( <div ref={r => { - if (r && this._stackedTimeline.current) { - this.dropDisposer?.(); - this.dropDisposer = DragManager.MakeDropTarget(r, + if (r && this.timeline) { + this._dropDisposer?.(); + this._dropDisposer = DragManager.MakeDropTarget(r, (e, de) => { const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); - de.complete.docDragData && this._stackedTimeline.current!.internalDocDrop(e, de, de.complete.docDragData, xp); + de.complete.docDragData && this.timeline!.internalDocDrop(e, de, de.complete.docDragData, xp); } , this.layoutDoc, undefined); } @@ -644,7 +540,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent< size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> </div> - {this.mediaState === "recording" || this.mediaState === "paused" ? ( + {this.mediaState === media_state.Recording || this.mediaState === media_state.Playing ? ( <div className="recording" onClick={(e) => e.stopPropagation()}> <div className="recording-buttons" onClick={this.recordClick}> <FontAwesomeIcon @@ -694,22 +590,22 @@ export class AudioBox extends ViewBoxAnnotatableComponent< > <div className="audiobox-buttons" - title={this.mediaState === "paused" ? "play" : "pause"} - onClick={this.mediaState === "paused" ? this.Play : this.Pause} + title={this.mediaState === media_state.Paused ? "play" : "pause"} + onClick={this.mediaState === media_state.Paused ? this.Play : this.Pause} > {" "} <FontAwesomeIcon - icon={this.mediaState === "paused" ? "play" : "pause"} + icon={this.mediaState === media_state.Paused ? "play" : "pause"} size={"1x"} /> </div> <div className="audiobox-buttons" - title={this._trimming !== AudioBox.ScopeNone ? "finish" : "trim"} + title={this.timeline?.IsTrimming !== TrimScope.None ? "finish" : "trim"} onPointerDown={this.onClipPointerDown} > <FontAwesomeIcon - icon={this._trimming !== AudioBox.ScopeNone ? "check" : "cut"} + icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"} size={"1x"} /> </div> @@ -727,10 +623,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent< </div> {this.audio} <div className="audioBox-current-time"> - {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.clipStart)))} + {this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))} </div> <div className="audioBox-total-time"> - {formatTime(Math.round(NumCast(this.clipDuration)))} + {this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))} </div> </div> </div> @@ -738,4 +634,4 @@ export class AudioBox extends ViewBoxAnnotatableComponent< </div> ); } -} +}
\ No newline at end of file diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 8b33842ff..af65cce9f 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -16,7 +16,7 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; +import { CollectionStackedTimeline, TrimScope } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; @@ -31,6 +31,7 @@ import { DocumentManager } from "../../util/DocumentManager"; import { DocumentType } from "../../documents/DocumentTypes"; import { Tooltip } from "@material-ui/core"; import { AnchorMenu } from "../pdf/AnchorMenu"; +import { undoBatch } from "../../util/UndoManager"; const path = require('path'); type VideoDocument = makeInterface<[typeof documentSchema]>; @@ -101,7 +102,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this._playing = true; try { this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime); - update && this.player?.play(); + update && this.player && this.playFrom(this.player.currentTime); update && this._audioPlayer?.play(); update && this._youtubePlayer?.playVideo(); this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); @@ -301,6 +302,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors, icon: "expand-arrows-alt" }); subitems.push({ description: "Toggle Native Controls", event: action(() => VideoBox._nativeControls = !VideoBox._nativeControls), icon: "expand-arrows-alt" }); subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" }); + subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" }); + subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" }); + subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" }); + subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" }); } } @@ -483,34 +488,47 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp return this.addDocument(doc); } + get timeline() { return this._stackedTimeline.current; } // play back the video from time @action - playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => { + playFrom = (seekTimeInSeconds: number, endTime?: number) => { clearTimeout(this._playRegionTimer); - this._playRegionDuration = endTime - seekTimeInSeconds; if (Number.isNaN(this.player?.duration)) { setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); - } else if (this.player) { - if (seekTimeInSeconds < 0) { - if (seekTimeInSeconds > -1) { - setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); - } else { - this.Pause(); - } - } else if (seekTimeInSeconds <= this.player.duration) { - this.player.currentTime = seekTimeInSeconds; + } + else if (this.player) { + const end = Math.min(this.timeline?.trimEnd ?? this.duration, endTime ?? this.timeline?.trimEnd ?? this.duration); + const start = Math.max(this.timeline?.trimStart ?? 0, seekTimeInSeconds); + this._playRegionDuration = end - seekTimeInSeconds; + if (seekTimeInSeconds >= 0 && (this.timeline?.trimStart || 0) <= end && seekTimeInSeconds <= (this.timeline?.trimEnd || this.duration)) { + this.player.currentTime = start; this._audioPlayer && (this._audioPlayer.currentTime = seekTimeInSeconds); this.player.play(); this._audioPlayer?.play(); runInAction(() => this._playing = true); if (endTime !== this.duration) { - this._playRegionTimer = setTimeout(() => this.Pause(), (this._playRegionDuration) * 1000); // use setTimeout to play a specific duration + return this._playRegionTimer = + setTimeout(() => this.Pause(), (this._playRegionDuration) * 1000); // use setTimeout to play a specific duration } + } + if (seekTimeInSeconds < 0 && seekTimeInSeconds > -1) { + setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); } else { this.Pause(); } } } + // hides trim controls and displays new clip + @undoBatch + finishTrim = action(() => { + this.Pause(); + this._stackedTimeline.current?.StopTrimming(); + }); + startTrim = (scope: TrimScope) => { + this.Pause(); + this._stackedTimeline.current?.StartTrimming(scope); + } + playLink = (doc: Doc) => { const startTime = Math.max(0, (this._stackedTimeline.current?.anchorStart(doc) || 0)); @@ -524,7 +542,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp playing = () => this._playing; timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); timelineScreenToLocal = () => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight()); - setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time; + setAnchorTime = (time: number) => { + this.player!.currentTime = this.layoutDoc._currentTimecode = time; + } timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100; trimEndFunc = () => this.duration; @computed get renderTimeline() { @@ -550,15 +570,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp playLink={this.playLink} PanelHeight={this.timelineHeight} rawDuration={this.duration} - clipDuration={this.duration} - clipStart={0} - clipEnd={this.duration} - trimming={false} - trimStart={returnZero} - trimEnd={this.trimEndFunc} - trimDuration={this.trimEndFunc} - setStartTrim={emptyFunction} - setEndTrim={emptyFunction} /> </div>; } |