aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-01-26 01:38:55 -0500
committerbobzel <zzzman@gmail.com>2021-01-26 01:38:55 -0500
commit20add09510fb02b144d421910a56d3f3896b1f90 (patch)
tree6cea1b9339afe4f8d6efab6499bc8037fbb0e2f4
parent0a48a55afd868b0cc3f298407a4b4882c4ee9bd2 (diff)
preparing to unify VideoBox timeline with AudioBox timeline. changed names from videoStart/audioStart,End to anchorStart,EndTime and _displayTimecode to _timecodeToShow etc
-rw-r--r--src/client/documents/Documents.ts14
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/views/DocComponent.tsx3
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/StyleProvider.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx4
-rw-r--r--src/client/views/collections/TabDocView.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx3
-rw-r--r--src/client/views/nodes/AudioBox.scss3
-rw-r--r--src/client/views/nodes/AudioBox.tsx217
-rw-r--r--src/client/views/nodes/PresBox.tsx23
-rw-r--r--src/client/views/nodes/VideoBox.tsx207
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx2
-rw-r--r--src/fields/documentSchemas.ts6
15 files changed, 270 insertions, 235 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 286b7afa9..080270321 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -151,8 +151,10 @@ export interface DocumentOptions {
_curPage?: number;
_currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds
_currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
- displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
- undisplayTimecode?: number; // the time that a document should be hidden
+ _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays)
+ _timecodeToHide?: number; // the time that a document should be hidden
+ anchorStartTime?: number; // the time when an annotation starts on a timeline (e.g., when an audio anchor starts on an audio/video timeline)
+ anchorEndTime?: number; // the time when an annotaiton ends on a timeline
lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
activeFrame?: number; // the active frame of a document in a frame base collection
appearFrame?: number; // the frame in which the document appears
@@ -227,8 +229,6 @@ export interface DocumentOptions {
linearViewIsExpanded?: boolean; // is linear view expanded
isLabel?: boolean; // whether the document is a label or not (video / audio)
useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox
- audioStart?: number; // the time frame where the audio should begin playing
- audioEnd?: number; // the time frame where the audio should stop playing
border?: string; //for searchbox
hovercolor?: string;
}
@@ -722,8 +722,8 @@ export namespace Docs {
linkDocProto.treeViewOpen = true;// setting this in the instance creator would set it on the view document.
linkDocProto.anchor1 = source.doc;
linkDocProto.anchor2 = target.doc;
- linkDocProto.anchor1_timecode = source.doc._currentTimecode || source.doc.displayTimecode;
- linkDocProto.anchor2_timecode = target.doc._currentTimecode || target.doc.displayTimecode;
+ linkDocProto.anchor1_timecode = source.doc._currentTimecode || source.doc._timecodeToShow;
+ linkDocProto.anchor2_timecode = target.doc._currentTimecode || target.doc._timecodeToShow;
if (linkDocProto.linkBoxExcludedKeys === undefined) {
Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]);
@@ -1252,7 +1252,7 @@ export namespace DocUtils {
docList.forEach((d, i) => {
d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
- d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
});
});
if (x !== undefined && y !== undefined) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 1f2dd350b..41c7a1409 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -191,8 +191,8 @@ export class DocumentManager {
targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
// now find the target document within the context
- if (targetDoc.displayTimecode) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
- targetDocContext._currentTimecode = targetDoc.displayTimecode;
+ if (targetDoc._timecodeToShow) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
+ targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow;
finished?.();
} else { // no timecode means we need to find the context view and focus on our target
const findView = (delay: number) => {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index db0b626a1..8d545c61b 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -81,6 +81,8 @@ export interface ViewBoxAnnotatableProps {
}
export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
class Component extends Touchable<P> {
+ _annotationKey: string = "annotations";
+
@observable _isChildActive = false;
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document(): T { return schemaCtor(this.props.Document); }
@@ -120,7 +122,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- _annotationKey: string = "annotations";
public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; }
@action.bound
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e1b9fe8aa..fb85684fb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -613,7 +613,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
}}>
{closeIcon}
{bounds.r - bounds.x < 100 ? null : titleArea}
- {(seldoc.rootDoc.annotationOn as Doc)?.type === DocumentType.AUDIO ? (null) :
+ {seldoc.rootDoc.anchorStartTime !== undefined ? (null) :
<>
{SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
<Tooltip key="i" title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top">
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 95a28b33e..8628be014 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -108,7 +108,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
case DocumentType.FILTER: docColor = docColor || (darkScheme() ? "#2d2d2d" : "rgba(105, 105, 105, 0.432)"); break;
case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break;
case DocumentType.SLIDER: break;
- case DocumentType.LABEL: docColor = docColor || (doc?.audioStart !== undefined || doc?.displayTimecode !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
+ case DocumentType.LABEL: docColor = docColor || (doc.audioStart !== undefined || doc?.anchorStartTime !== undefined || doc?._timecodeToShow !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
case DocumentType.LINK: return "transparent";
case DocumentType.VID: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
@@ -141,7 +141,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
`${darkScheme() ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`);
case DocumentType.LABEL:
- if (doc?.audioStart !== undefined) return "black 2px 2px 1px";
+ if (doc?.anchorStartTime !== undefined) return "black 2px 2px 1px";
default:
return doc.z ? `#9c9396 ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow
props?.ContainingCollectionDoc?._useClusters && doc.type !== DocumentType.INK ? (`${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.ContentScaling?.() || 1)}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 83c639871..6b9b1a3c0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -158,14 +158,14 @@ export class CollectionView extends Touchable<CollectionViewProps> {
const pushpin = Docs.Create.FontIconDocument({
title: "pushpin", label: "",
icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
- _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
+ _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null)
});
pushpin.isPushpin = true;
Doc.GetProto(pushpin).annotationOn = doc.annotationOn;
Doc.SetInPlace(doc, "annotationOn", undefined, true);
Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
- doc.displayTimecode = undefined;
+ doc._timecodeToShow = undefined;
}
doc._stayInCollection = undefined;
doc.context = this.props.Document;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e5f05c407..c66734556 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -182,12 +182,13 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const presArray: Doc[] = PresBox.Instance?.sortArray();
const size: number = PresBox.Instance?._selectedArray.size;
const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
+ const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
Doc.AddDocToList(curPres, "data", pinDoc, presSelected);
- if (!pinProps?.audioRange && (pinDoc.type === DocumentType.AUDIO || pinDoc.type === DocumentType.VID)) {
+ if (!pinProps?.audioRange && duration !== undefined) {
pinDoc.mediaStart = "manual";
pinDoc.mediaStop = "manual";
pinDoc.presStartTime = 0;
- pinDoc.presEndTime = pinDoc.type === DocumentType.AUDIO ? doc.duration : NumCast(doc["data-duration"]);
+ pinDoc.presEndTime = duration;
}
//save position
if (pinProps?.setPosition || pinDoc.isInkMask) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 013472a04..6619205af 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -54,7 +54,7 @@ export const panZoomSchema = createSchema({
_panX: "number",
_panY: "number",
_currentTimecode: "number",
- displayTimecode: "number",
+ _timecodeToShow: "number",
_currentFrame: "number",
arrangeInit: ScriptField,
_useClusters: "boolean",
@@ -197,10 +197,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
}
public isCurrent(doc: Doc) {
- const dispTime = NumCast(doc.displayTimecode, -1);
- const endTime = NumCast(doc.undisplayTimecode, dispTime + 1.5);
+ const dispTime = NumCast(doc._timecodeToShow, -1);
+ const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
const curTime = NumCast(this.Document._currentTimecode, -1);
- return dispTime === -1 || ((curTime - dispTime) >= -0.1 && curTime <= endTime);
+ return dispTime === -1 || ((curTime - dispTime) >= -1e-4 && curTime <= endTime);
}
public getActiveDocuments = () => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 9ef37ecc2..0edbfe7a5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -426,7 +426,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
delete d.x;
delete d.y;
delete d.activeFrame;
- delete d.displayTimecode; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
d.x = dx - this.Bounds.left - this.Bounds.width / 2;
d.y = dy - this.Bounds.top - this.Bounds.height / 2;
return d;
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 93cdbbb09..4a1cee721 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -159,9 +159,8 @@
}
.audiobox-timeline {
- position: relative;
+ position: absolute;
width: 100%;
- background: white;
border: gray solid 1px;
border-radius: 3px;
z-index: 1000;
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index a481317c3..0faac44a7 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -13,11 +13,10 @@ import { createSchema, listSpec, makeInterface } from "../../../fields/Schema";
import { ComputedField, ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
-import { emptyFunction, formatTime, numberRange, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, formatTime, numberRange, returnFalse, setupMoveUpEvents, Utils, OmitKeys } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DocumentManager } from "../../util/DocumentManager";
import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
import { SnappingManager } from "../../util/SnappingManager";
@@ -30,6 +29,7 @@ import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment
import { LinkDocPreview } from "./LinkDocPreview";
import "./AudioBox.scss";
import { Id } from "../../../fields/FieldSymbols";
+import { LabelBox } from "./LabelBox";
declare class MediaRecorder {
// whatever MediaRecorder has
@@ -69,7 +69,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
_audioRef = React.createRef<HTMLDivElement>();
_timeline: Opt<HTMLDivElement>;
_markerStart: number = 0;
- _currMarker: any;
+ _currAnchor: Opt<Doc>;
@observable static SelectingRegion: AudioBox | undefined = undefined;
@observable static _scrubTime = 0;
@@ -81,8 +81,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
set audioState(value) { this.dataDoc.audioState = 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 audioDuration() { return NumCast(this.dataDoc.duration); }
- @computed get markerDocs() { return DocListCast(this.dataDoc[this.annotationKey]); }
+ @computed get duration() { 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
@@ -90,17 +90,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
super(props);
AudioBox.Instance = this;
+ if (this.duration === undefined) {
+ runInAction(() => {
+ this.Document[this.fieldKey + "-duration"] = this.Document.duration;
+ })
+ }
+
// onClick play scripts
- AudioBox.RangeScript = AudioBox.RangeScript || ScriptField.MakeFunction(`scriptContext.clickMarker(self, this.audioStart, this.audioEnd)`, { self: Doc.name, scriptContext: "any" })!;
- AudioBox.LabelScript = AudioBox.LabelScript || ScriptField.MakeFunction(`scriptContext.clickMarker(self, this.audioStart)`, { self: Doc.name, scriptContext: "any" })!;
- AudioBox.RangePlayScript = AudioBox.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(self, this.audioStart, this.audioEnd)`, { self: Doc.name, scriptContext: "any" })!;
- AudioBox.LabelPlayScript = AudioBox.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(self, this.audioStart)`, { self: Doc.name, scriptContext: "any" })!;
+ AudioBox.RangeScript = AudioBox.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this)`, { self: Doc.name, scriptContext: "any" })!;
+ AudioBox.LabelScript = AudioBox.LabelScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this)`, { self: Doc.name, scriptContext: "any" })!;
+ AudioBox.RangePlayScript = AudioBox.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this)`, { self: Doc.name, scriptContext: "any" })!;
+ AudioBox.LabelPlayScript = AudioBox.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this)`, { self: Doc.name, scriptContext: "any" })!;
}
+ anchorStart = (anchor: Doc) => NumCast(anchor.anchorStartTime, NumCast(anchor.audioStart))
+ anchorEnd = (anchor: Doc, defaultVal: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor.audioEnd, defaultVal))
+
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- const linkTime = NumCast(la2.audioStart, NumCast(la1.audioStart));
+ const linkTime = NumCast(la2.anchorStartTime, NumCast(la1.anchorStartTime));
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
@@ -109,7 +118,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
}
getAnchor = () => {
- return this.createMarker(this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined));
+ return this.createAnchor(this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined));
}
componentWillUnmount() {
@@ -126,13 +135,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
- this._disposers.audioStart = reaction(
- () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? Cast(this.Document._audioStart, "number", null) : undefined,
- audioStart => audioStart !== undefined && setTimeout(() => {
- this._audioRef.current && this.playFrom(audioStart);
+ this._disposers.triggerAudio = reaction(
+ () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
+ start => start !== undefined && setTimeout(() => {
+ this._audioRef.current && this.playFrom(start);
setTimeout(() => {
- this.Document._currentTimecode = audioStart;
- this.Document._audioStart = undefined;
+ this.Document._currentTimecode = start;
+ this.Document._triggerAudio = undefined;
}, 10);
}, this._audioRef.current ? 0 : 250), // wait for mainCont and try again to play
{ fireImmediately: true }
@@ -148,23 +157,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
);
}
- playLink = (doc: Doc) => {
- this.links.filter(l => l.anchor1 === doc || l.anchor2 === doc).forEach(l => {
- const { la1, la2 } = this.getLinkData(l);
- const startTime = NumCast(la1.audioStart, NumCast(la2.audioStart, null));
- const endTime = NumCast(la1.audioEnd, NumCast(la2.audioEnd, null));
- if (startTime !== undefined) {
- this.layoutDoc.playOnSelect && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime));
- }
- });
- doc.annotationOn === this.rootDoc && this.playFrom(NumCast(doc.audioStart), Cast(doc.audioEnd, "number", null));
- }
-
// for updating the timecode
timecodeChanged = () => {
const htmlEle = this._ele;
if (this.audioState !== "recording" && htmlEle) {
- htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration);
+ htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration);
this.links.map(l => {
const { la1, linkTime } = this.getLinkData(l);
if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < htmlEle.currentTime) {
@@ -188,21 +185,21 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
// play back the audio from time
@action
- playOnClick = (anchorDoc: Doc, seekTimeInSeconds: number, endTime: number = this.audioDuration) => {
- this.playFrom(seekTimeInSeconds, endTime);
+ playOnClick = (anchorDoc: Doc) => {
+ this.playFrom(this.anchorStart(anchorDoc), this.anchorEnd(anchorDoc, this.duration));
return true;
}
// play back the audio from time
@action
- clickMarker = (anchorDoc: Doc, seekTimeInSeconds: number, endTime: number = this.audioDuration) => {
- if (this.layoutDoc.autoPlay) return this.playOnClick(anchorDoc, seekTimeInSeconds, endTime);
- this._ele && (this._ele.currentTime = this.layoutDoc._currentTimecode = seekTimeInSeconds);
+ clickAnchor = (anchorDoc: Doc) => {
+ if (this.layoutDoc.autoPlay) return this.playOnClick(anchorDoc);
+ this._ele && (this._ele.currentTime = this.layoutDoc._currentTimecode = this.anchorStart(anchorDoc));
return true;
}
// play back the audio from time
@action
- playFrom = (seekTimeInSeconds: number, endTime: number = this.audioDuration) => {
+ playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => {
clearTimeout(this._play);
if (Number.isNaN(this._ele?.duration)) {
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
@@ -217,7 +214,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._ele.currentTime = seekTimeInSeconds;
this._ele.play();
runInAction(() => this.audioState = "playing");
- if (endTime !== this.audioDuration) {
+ if (endTime !== this.duration) {
this._play = setTimeout(() => this.pause(), (endTime - seekTimeInSeconds) * 1000); // use setTimeout to play a specific duration
}
} else {
@@ -260,9 +257,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
// context menu
specificContextMenu = (e: React.MouseEvent): void => {
const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors", event: () => this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors, icon: "expand-arrows-alt" });
funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when link is selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
- funcs.push({ description: (this.layoutDoc.hideMarkers ? "Don't hide" : "Hide") + " range markers", event: () => this.layoutDoc.hideMarkers = !this.layoutDoc.hideMarkers, icon: "expand-arrows-alt" });
- funcs.push({ description: (this.layoutDoc.autoPlay ? "Don't auto play" : "Auto play") + " markers onClick", event: () => this.layoutDoc.autoPlay = !this.layoutDoc.autoPlay, icon: "expand-arrows-alt" });
+ funcs.push({ description: (this.layoutDoc.autoPlay ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlay = !this.layoutDoc.autoPlay, icon: "expand-arrows-alt" });
ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
@@ -270,7 +267,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
stopRecording = action(() => {
this._recorder.stop();
this._recorder = undefined;
- this.dataDoc.duration = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+ this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
this.audioState = "paused";
this._stream?.getAudioTracks()[0].stop();
const ind = DocUtils.ActiveRecordings.indexOf(this);
@@ -348,16 +345,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
e.stopPropagation();
}
- // starting the drag event for marker resizing
+ // starting the drag event for anchor resizing
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();// (e.target as any).getBoundingClientRect();
if (rect && e.target !== this._audioRef.current && this.active()) {
const wasPaused = this.audioState === "paused";
- this._ele!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration;
+ this._ele!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.duration;
wasPaused && this.pause();
- const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.audioDuration;
+ const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.duration;
this._markerStart = this._markerEnd = toTimeline(e.clientX - rect.x);
AudioBox.SelectingRegion = this;
setupMoveUpEvents(this, e,
@@ -372,46 +369,46 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._markerStart = this._markerEnd;
this._markerEnd = tmp;
}
- AudioBox.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createMarker(this._markerStart, this._markerEnd);
+ AudioBox.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd);
AudioBox.SelectingRegion = undefined;
}),
e => {
this.props.select(false);
- e.shiftKey && this.createMarker(this._ele!.currentTime);
+ e.shiftKey && this.createAnchor(this._ele!.currentTime);
}
, this.props.isSelected(true) || this._isChildActive);
}
}
@action
- createMarker(audioStart?: number, audioEnd?: number) {
- if (audioStart === undefined) return this.rootDoc;
- const marker = Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(`"#" + formatToTime(self.audioStart) + "-" + formatToTime(self.audioEnd)`) as any,
+ createAnchor(anchorStartTime?: number, anchorEndTime?: number) {
+ if (anchorStartTime === undefined) return this.rootDoc;
+ const anchor = Docs.Create.LabelDocument({
+ title: ComputedField.MakeFunction(`"#" + formatToTime(self.anchorStartTime) + "-" + formatToTime(self.anchorEndTime)`) as any,
useLinkSmallAnchor: true,
hideLinkButton: true,
- audioStart,
- audioEnd,
+ anchorStartTime,
+ anchorEndTime,
annotationOn: this.props.Document
});
if (this.dataDoc[this.annotationKey]) {
- this.dataDoc[this.annotationKey].push(marker);
+ this.dataDoc[this.annotationKey].push(anchor);
} else {
- this.dataDoc[this.annotationKey] = new List<Doc>([marker]);
+ this.dataDoc[this.annotationKey] = new List<Doc>([anchor]);
}
- return marker;
+ return anchor;
}
- // starting the drag event for marker resizing
- onPointerDown = (e: React.PointerEvent, m: any, left: boolean): void => {
- this._currMarker = m;
+ // starting the drag event for anchor resizing
+ onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => {
+ this._currAnchor = m;
this._left = left;
this._timeline?.setPointerCapture(e.pointerId);
- const toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.audioDuration, screen_delta / width * this.audioDuration));
+ const toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration));
setupMoveUpEvents(this, e,
(e) => {
const rect = (e.target as any).getBoundingClientRect();
- this.changeMarker(this._currMarker, toTimeline(e.clientX - rect.x, rect.width));
+ this.changeAnchor(this._currAnchor, toTimeline(e.clientX - rect.x, rect.width));
return false;
},
(e) => {
@@ -422,27 +419,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
emptyFunction);
}
- // updates the marker with the new time
+ // updates the anchor with the new time
@action
- changeMarker = (m: any, time: any) => {
- this.markerDocs.filter(marker => this.isSame(marker, m)).forEach(marker =>
- this._left ? marker.audioStart = time : marker.audioEnd = time);
+ changeAnchor = (anchor: Opt<Doc>, time: number) => {
+ anchor && (this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time);
}
- // checks if the two markers are the same with start and end time
+ // checks if the two anchors are the same with start and end time
isSame = (m1: any, m2: any) => {
- return m1.audioStart === m2.audioStart && m1.audioEnd === m2.audioEnd;
+ return this.anchorStart(m1) === this.anchorStart(m2) && this.anchorEnd(m1) === this.anchorEnd(m2);
}
- // makes sure no markers overlaps each other by setting the correct position and width
- getLevel = (m: any, placed: { audioStart: number, audioEnd: number, level: number }[]) => {
+ // makes sure no anchors overlaps each other by setting the correct position and width
+ getLevel = (m: Doc, placed: { anchorStartTime: number, anchorEndTime: number, level: number }[]) => {
const timelineContentWidth = this.props.PanelWidth() - AudioBox.playheadWidth;
- const x1 = m.audioStart;
- const x2 = m.audioEnd === undefined ? m.audioStart + 10 / timelineContentWidth * this.audioDuration : m.audioEnd;
+ const x1 = this.anchorStart(m);
+ const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration);
let max = 0;
const overlappedLevels = new Set(placed.map(p => {
- const y1 = p.audioStart;
- const y2 = p.audioEnd;
+ const y1 = p.anchorStartTime;
+ const y2 = p.anchorEndTime;
if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) ||
(y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) {
max = Math.max(max, p.level);
@@ -452,14 +448,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
let level = max + 1;
for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
- placed.push({ audioStart: x1, audioEnd: x2, level });
+ placed.push({ anchorStartTime: x1, anchorEndTime: x2, level });
return level;
}
@computed get selectionContainer() {
return AudioBox.SelectingRegion !== this ? (null) : <div className="audiobox-container" style={{
- left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.audioDuration * 100}%`,
- width: `${Math.abs(this._markerStart - this._markerEnd) / this.audioDuration * 100}%`, height: "100%", top: "0%"
+ left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.duration * 100}%`,
+ width: `${Math.abs(this._markerStart - this._markerEnd) / this.duration * 100}%`, height: "100%", top: "0%"
}} />;
}
@@ -471,8 +467,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
color={"darkblue"}
height={this._waveHeight}
barWidth={0.1}
- pos={this.audioDuration}
- duration={this.audioDuration}
+ pos={this.duration}
+ duration={this.duration}
peaks={audioBuckets.length === AudioBox.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
progressColor={"blue"} />;
}
@@ -496,17 +492,36 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
labelClickScript = () => AudioBox.LabelScript;
rangePlayScript = () => AudioBox.RangePlayScript;
labelPlayScript = () => AudioBox.LabelPlayScript;
+
+ playLink = (link: Doc) => {
+ if (link.annotationOn === this.rootDoc) {
+ if (this.layoutDoc.playOnSelect) this.playFrom(this.anchorStart(link), this.anchorEnd(link));
+ else this._ele!.currentTime = this.layoutDoc._currentTimecode = this.anchorStart(link);
+ }
+ else this.links.filter(l => l.anchor1 === link || l.anchor2 === link).forEach(l => {
+ const { la1, la2 } = this.getLinkData(l);
+ const startTime = NumCast(la1.anchorStartTime, NumCast(la2.anchorStartTime, null));
+ const endTime = NumCast(la1.anchorEndTime, NumCast(la2.anchorEndTime, null));
+ if (startTime !== undefined) {
+ if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
+ else this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime;
+ }
+ });
+ }
+
renderInner = computedFn(function (this: AudioBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
- const marker = observable({ view: undefined as any });
+ const anchor = observable({ view: undefined as any });
return {
- marker, view: <DocumentView key="view" {...this.props} ref={action((r: DocumentView | null) => marker.view = r)}
+ anchor, view: <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} ref={action((r: DocumentView | null) => anchor.view = r)}
Document={mark}
+ DataDoc={undefined}
PanelWidth={() => width}
PanelHeight={() => height}
renderDepth={this.props.renderDepth + 1}
focus={() => this.playLink(mark)}
rootSelected={returnFalse}
LayoutTemplate={undefined}
+ LayoutTemplateString={LabelBox.LayoutString("data")}
ContainingCollectionDoc={this.props.Document}
removeDocument={this.removeDocument}
ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x - 4, -y - 3)}
@@ -519,11 +534,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
scriptContext={this} />
};
});
- renderMarker = computedFn(function (this: AudioBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
+ renderAnchor = computedFn(function (this: AudioBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
const inner = this.renderInner(mark, script, doublescript, x, y, width, height);
return <>
{inner.view}
- {!inner.marker.view || !SelectionManager.IsSelected(inner.marker.view) ? (null) :
+ {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) :
<>
<div key="left" className="left-resizer" onPointerDown={e => this.onPointerDown(e, mark, true)} />
<div key="right" className="resizer" onPointerDown={e => this.onPointerDown(e, mark, false)} />
@@ -535,8 +550,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : "";
const timelineContentWidth = this.props.PanelWidth() - AudioBox.playheadWidth;
const timelineContentHeight = (this.props.PanelHeight() * AudioBox.heightPercent / 100) * AudioBox.heightPercent / 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline)
- const overlaps: { audioStart: number, audioEnd: number, level: number }[] = [];
- const drawMarkers = this.markerDocs.map((m, i) => ({ level: this.getLevel(m, overlaps), marker: m }));
+ const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = [];
+ const drawAnchors = this.anchorDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
return <div className="audiobox-container"
onContextMenu={this.specificContextMenu}
@@ -566,37 +581,43 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
<div className="audiobox-dictation" />
<div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
<div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.audioState === "paused" ? "play" : "pause"} onClick={this.onPlay}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.audioState === "paused" ? "play" : "pause"} size={"1x"} /></div>
- <div className="audiobox-timeline" style={{ height: `${AudioBox.heightPercent}%` }} ref={this.timelineRef}
- onClick={e => { e.stopPropagation(); e.preventDefault(); }}
- onPointerDown={e => e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e)}>
- <div className="waveform">
+ <div className="audiobox-timeline" style={{ height: `${AudioBox.heightPercent}%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)`, background: "white" }}>
+ <div className="waveform" >
{this.waveform}
</div>
- {drawMarkers.map(d => {
- const m = d.marker;
- const left = NumCast(m.audioStart) / this.audioDuration * timelineContentWidth;
+ </div>
+ <div className="audiobox-timeline" style={{ height: `${AudioBox.heightPercent}%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)` }} ref={this.timelineRef}
+ onClick={e => { e.stopPropagation(); e.preventDefault(); }}
+ onPointerDown={e => e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e)}>
+ {drawAnchors.map(d => {
+ const m = d.anchor;
+ const start = this.anchorStart(m);
+ const end = this.anchorEnd(m, start + 10 / timelineContentWidth * this.duration);
+ const left = start / this.duration * timelineContentWidth;
const top = d.level / maxLevel * timelineContentHeight;
- const timespan = m.audioEnd === undefined ? 10 / timelineContentWidth * this.audioDuration : NumCast(m.audioEnd) - NumCast(m.audioStart);
- return this.layoutDoc.hideMarkers ? (null) :
+ const timespan = end - start;
+ return this.layoutDoc.hideAnchors ? (null) :
<div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={m[Id]}
- style={{ left, top, width: `${timespan / this.audioDuration * 100}%`, height: `${1 / maxLevel * 100}%` }}
- onClick={e => { this.playFrom(NumCast(m.audioStart), Cast(m.audioEnd, "number", null)); e.stopPropagation(); }} >
- {this.renderMarker(m, this.rangeClickScript, this.rangePlayScript,
+ style={{ left, top, width: `${timespan / this.duration * 100}%`, height: `${1 / maxLevel * 100}%` }}
+ onClick={e => { this.playFrom(start, this.anchorEnd(m)); e.stopPropagation(); }} >
+ {this.renderAnchor(m, this.rangeClickScript, this.rangePlayScript,
left + AudioBox.playheadWidth,
(1 - AudioBox.heightPercent / 100) / 2 * this.props.PanelHeight() + top,
- timelineContentWidth * timespan / this.audioDuration,
+ timelineContentWidth * timespan / this.duration,
timelineContentHeight / maxLevel)}
</div>;
})}
{this.selectionContainer}
- <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} />
- {this.audio}
+ <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }}
+ style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.duration * 100}%`, pointerEvents: "none" }}
+ />
</div>
+ {this.audio}
<div className="current-time">
{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}
</div>
<div className="total-time">
- {formatTime(Math.round(this.audioDuration))}
+ {formatTime(Math.round(this.duration))}
</div>
</div>
</div>
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index aef776563..62e497e18 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -232,14 +232,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const duration: number = NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime);
if (targetDoc.type === DocumentType.AUDIO) {
if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
- targetDoc._audioStart = NumCast(activeItem.presStartTime);
+ targetDoc._triggerAudio = NumCast(activeItem.presStartTime);
this._mediaTimer = [setTimeout(() => targetDoc._audioStop = true, duration * 1000), targetDoc];
} else if (targetDoc.type === DocumentType.VID) {
if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
- targetDoc._videoStop = true;
+ targetDoc._triggerVideoStop = true;
setTimeout(() => targetDoc._currentTimecode = NumCast(activeItem.presStartTime), 10);
- setTimeout(() => targetDoc._videoStart = true, 20);
- this._mediaTimer = [setTimeout(() => targetDoc._videoStop = true, (duration * 1000) + 20), targetDoc];
+ setTimeout(() => targetDoc._triggerVideo = true, 20);
+ this._mediaTimer = [setTimeout(() => targetDoc._triggerVideoStop = true, (duration * 1000) + 20), targetDoc];
}
}
@@ -249,7 +249,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
targetDoc._audioStop = true;
} else if (targetDoc.type === DocumentType.VID) {
if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
- targetDoc._videoStop = true;
+ targetDoc._triggerVideoStop = true;
}
}
@@ -714,9 +714,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
if (audio) {
audio.mediaStart = "manual";
audio.mediaStop = "manual";
- audio.presStartTime = NumCast(doc.audioStart);
- audio.presEndTime = NumCast(doc.audioEnd);
- audio.presDuration = NumCast(doc.audioEnd) - NumCast(doc.audioStart);
+ audio.presStartTime = NumCast(doc.anchorStartTime);
+ audio.presEndTime = NumCast(doc.anchorEndTime);
+ audio.presDuration = NumCast(doc.anchorEndTime) - NumCast(doc.anchorStartTime);
TabDocView.PinDoc(audio, { audioRange: true });
setTimeout(() => this.removeDocument(doc), 0);
return false;
@@ -1477,6 +1477,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@computed get mediaOptionsDropdown() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
+ const duration = Math.round(NumCast(activeItem[`${Doc.LayoutFieldKey(activeItem)}-duration`]) * 10);
const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc);
const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + ". " + this.childDocs[mediaStopDocInd - 1].title : "";
if (activeItem && targetDoc) {
@@ -1521,7 +1522,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
</div>
<div className="multiThumb-slider">
- <input type="range" step="0.1" min="0" max={activeItem.type === DocumentType.AUDIO ? Math.round(NumCast(activeItem.duration) * 10) / 10 : Math.round(NumCast(activeItem["data-duration"]) * 10) / 10} value={NumCast(activeItem.presEndTime)}
+ <input type="range" step="0.1" min="0" max={duration / 10} value={NumCast(activeItem.presEndTime)}
style={{ gridColumn: 1, gridRow: 1 }}
className={`toolbar-slider ${"end"}`}
id="toolbar-slider"
@@ -1545,7 +1546,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
e.stopPropagation();
activeItem.presEndTime = Number(e.target.value);
}} />
- <input type="range" step="0.1" min="0" max={activeItem.type === DocumentType.AUDIO ? Math.round(NumCast(activeItem.duration) * 10) / 10 : Math.round(NumCast(activeItem["data-duration"]) * 10) / 10} value={NumCast(activeItem.presStartTime)}
+ <input type="range" step="0.1" min="0" max={duration / 10} value={NumCast(activeItem.presStartTime)}
style={{ gridColumn: 1, gridRow: 1 }}
className={`toolbar-slider ${"start"}`}
id="toolbar-slider"
@@ -1573,7 +1574,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}>
<div className="slider-text">0 s</div>
<div className="slider-text"></div>
- <div className="slider-text">{activeItem.type === DocumentType.AUDIO ? Math.round(NumCast(activeItem.duration) * 10) / 10 : Math.round(NumCast(activeItem["data-duration"]) * 10) / 10} s</div>
+ <div className="slider-text">{duration / 10} s</div>
</div>
</div>
<div className="ribbon-final-box">
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 12822b64a..988dd47d8 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -42,6 +42,7 @@ const VideoDocument = makeInterface(documentSchema, timeSchema);
@observer
export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); }
static _youtubeIframeCounter: number = 0;
static Instance: VideoBox;
static RangeScript: ScriptField;
@@ -58,20 +59,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
private _isResetClick = 0;
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- _play: any = null;
- _timeline: Opt<HTMLDivElement>;
- _audioRef = React.createRef<HTMLDivElement>();
- _markerStart: number = 0;
- _left: boolean = false;
- _count: Array<any> = [];
- _duration = 0;
- _start: boolean = true;
- _currMarker: any;
+ private _play: any = null;
+ private _timeline: Opt<HTMLDivElement>;
+ private _audioRef = React.createRef<HTMLDivElement>();
+ private _markerStart: number = 0;
+ private _left: boolean = false;
+ private _duration = 0;
+ private _start: boolean = true;
+ private _currAnchor: Doc | undefined;
+ @observable static _showControls: boolean;
+ @observable static SelectingRegion: VideoBox | undefined = undefined;
@observable _marqueeing: number[] | undefined;
@observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
@observable _screenCapture = false;
- @observable static _showControls: boolean;
- @observable static SelectingRegion: VideoBox | undefined = undefined;
@observable _visible: boolean = false;
@observable _markerEnd: number = 0;
@observable _forceCreateYouTubeIFrame = false;
@@ -79,11 +79,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
@observable _fullScreen = false;
@observable _playing = false;
@computed get links() { return DocListCast(this.dataDoc.links); }
- @computed get heightPercent() { return this.layoutDoc._showTimeline ? NumCast(this.layoutDoc._videoTimelineHeightPercent, VideoBox.heightPercent) : 100; }
- @computed get videoDuration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
- @computed get markerDocs() { return DocListCast(this.dataDoc[this.annotationKey + "-timeline"]).concat(DocListCast(this.dataDoc[this.annotationKey])); }
-
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); }
+ @computed get heightPercent() { return this.layoutDoc._timelineShow ? NumCast(this.layoutDoc._videoTimelineHeightPercent, VideoBox.heightPercent) : 100; }
+ @computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+ @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey + "-timeline"]).concat(DocListCast(this.dataDoc[this.annotationKey])); }
public get player(): HTMLVideoElement | null { return this._videoRef; }
@@ -92,14 +90,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
VideoBox.Instance = this;
// onClick play scripts
- VideoBox.RangeScript = VideoBox.RangeScript || ScriptField.MakeFunction(`scriptContext.clickMarker(self, clientX, this.displayTimecode, this.undisplayTimecode)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!;
- VideoBox.LabelScript = VideoBox.LabelScript || ScriptField.MakeFunction(`scriptContext.clickMarker(self, clientX, this.displayTimecode)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!;
- VideoBox.RangePlayScript = VideoBox.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(self, clientX, this.displayTimecode, this.undisplayTimecode)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!;
- VideoBox.LabelPlayScript = VideoBox.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(self, clientX, this.displayTimecode)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!;
+ VideoBox.RangeScript = VideoBox.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { this: Doc.name, clientX: "number", scriptContext: "any" })!;
+ VideoBox.LabelScript = VideoBox.LabelScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { this: Doc.name, clientX: "number", scriptContext: "any" })!;
+ VideoBox.RangePlayScript = VideoBox.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!;
+ VideoBox.LabelPlayScript = VideoBox.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!;
}
+ anchorStart = (anchor: Doc) => NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart)))
+ anchorEnd = (anchor: Doc, defaultVal: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, defaultVal)))
+
getAnchor = () => {
- return this.createMarker(Cast(this.layoutDoc._currentTimecode, "number", null));
+ return this.createAnchor(Cast(this.layoutDoc._currentTimecode, "number", null));
}
choosePath(url: string) {
@@ -241,19 +242,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
},
{ fireImmediately: true });
- this._disposers.videoStart = reaction(
- () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? Cast(this.Document._videoStart, "number", null) : undefined,
- videoStart => videoStart !== undefined && setTimeout(() => {
+ this._disposers.triggerVideo = reaction(
+ () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
+ time => time !== undefined && setTimeout(() => {
this.player && this.Play();
- setTimeout(() => this.Document._videoStart = undefined, 10);
+ setTimeout(() => this.Document._triggerVideo = undefined, 10);
}, this.player ? 0 : 250), // wait for mainCont and try again to play
{ fireImmediately: true }
);
- this._disposers.videoStop = reaction(
- () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._videoStop, "number", null) : undefined,
- videoStop => videoStop !== undefined && setTimeout(() => {
+ this._disposers.triggerStop = reaction(
+ () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? NumCast(this.Document._triggerVideoStop, null) : undefined,
+ stop => stop !== undefined && setTimeout(() => {
this.player && this.Pause();
- setTimeout(() => this.Document._videoStop = undefined, 10);
+ setTimeout(() => this.Document._triggerVideoStop = undefined, 10);
}, this.player ? 0 : 250), // wait for mainCont and try again to play
{ fireImmediately: true }
);
@@ -321,7 +322,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
}), icon: "expand-arrows-alt"
});
- subitems.push({ description: (this.layoutDoc.autoPlay ? "Don't auto play" : "Auto play") + " markers onClick", event: () => this.layoutDoc.autoPlay = !this.layoutDoc.autoPlay, icon: "expand-arrows-alt" });
+ subitems.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when link is selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
+ subitems.push({ description: (this.layoutDoc.autoPlay ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlay = !this.layoutDoc.autoPlay, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
}
}
@@ -408,7 +410,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
right: this.scaling() * 10 - 10,
bottom: this.scaling() * 10 - 10
}}>
- <FontAwesomeIcon icon={this.layoutDoc._showTimeline ? "eye-slash" : "eye"} style={{ width: "100%" }} />
+ <FontAwesomeIcon icon={this.layoutDoc._timelineShow ? "eye-slash" : "eye"} style={{ width: "100%" }} />
</div>,
VideoBox._showControls ? (null) : [
// <div className="control-background">
@@ -470,16 +472,16 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
@action.bound
- addDocumentWithTimestamp(doc: Doc | Doc[]): boolean {
+ addDocWithTimecode(doc: Doc | Doc[]): boolean {
const docs = doc instanceof Doc ? [doc] : doc;
const curTime = NumCast(this.layoutDoc._currentTimecode);
- docs.forEach(doc => doc.displayTimecode = curTime);
+ docs.forEach(doc => doc._timecodeToShow = curTime);
return this.addDocument(doc);
}
// play back the video from time
@action
- playFrom = (seekTimeInSeconds: number, endTime: number = this.videoDuration) => {
+ playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => {
clearTimeout(this._play);
this._duration = endTime - seekTimeInSeconds;
if (Number.isNaN(this.player?.duration)) {
@@ -495,7 +497,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this.player.currentTime = seekTimeInSeconds;
this.player.play();
runInAction(() => this._playing = true);
- if (endTime !== this.videoDuration) {
+ if (endTime !== this.duration) {
this._play = setTimeout(() => this.Pause(), (this._duration) * 1000); // use setTimeout to play a specific duration
}
} else {
@@ -505,12 +507,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
@action
- toggleTimeline = (e: React.PointerEvent) => this.layoutDoc._showTimeline = !this.layoutDoc._showTimeline
+ toggleTimeline = (e: React.PointerEvent) => this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow
// ref for timeline
- timelineRef = (timeline: HTMLDivElement) => { this._timeline = timeline; };
+ timelineRef = (timeline: HTMLDivElement) => this._timeline = timeline
- // starting the drag event creating a range marker
+ // starting the drag event creating a range mark
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
@@ -520,7 +522,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
else if (!this._doubleTime) {
this._doubleTime = setTimeout(() => {
this._doubleTime = undefined;
- this.player!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.videoDuration;
+ this.player!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.duration;
}, 300);
}
@@ -537,12 +539,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._markerStart = this._markerEnd;
this._markerEnd = tmp;
}
- VideoBox.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createMarker(this._markerStart, this._markerEnd);
+ VideoBox.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd);
VideoBox.SelectingRegion = undefined;
}),
(e, doubleTap) => {
this.props.select(false);
- e.shiftKey && this.createMarker(this.player!.currentTime);
+ e.shiftKey && this.createAnchor(this.player!.currentTime);
!wasPlaying && doubleTap && this.Play();
}
, this.props.isSelected(true) || this._isChildActive);
@@ -550,25 +552,27 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
@action
- createMarker(displayTimecode: number, undisplayTimecode?: number) {
- const marker = Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(`"#" + formatToTime(self.displayTimecode) + "-" + formatToTime(self.undisplayTimecode)`) as any,
+ createAnchor(anchorStartTime: number, anchorEndTime?: number) {
+ const anchor = Docs.Create.LabelDocument({
+ title: ComputedField.MakeFunction(`"#" + formatToTime(self.anchorStartTime) + "-" + formatToTime(self.anchorEndTime)`) as any,
useLinkSmallAnchor: true, // bcz: note this also flags that the annotation is not on the video itself, just the timeline
hideLinkButton: true,
- displayTimecode,
- undisplayTimecode,
+ anchorStartTime,
+ anchorEndTime,
annotationOn: this.props.Document
});
if (this.dataDoc[this.annotationKey + "-timeline"]) {
- this.dataDoc[this.annotationKey + "-timeline"].push(marker);
+ this.dataDoc[this.annotationKey + "-timeline"].push(anchor);
} else {
- this.dataDoc[this.annotationKey + "-timeline"] = new List<Doc>([marker]);
+ this.dataDoc[this.annotationKey + "-timeline"] = new List<Doc>([anchor]);
}
- return marker;
+ return anchor;
}
@action
- playOnClick = (anchorDoc: Doc, clientX: number, seekTimeInSeconds: number, endTime: number = this.videoDuration) => {
+ playOnClick = (anchorDoc: Doc, clientX: number) => {
+ const seekTimeInSeconds = this.anchorStart(anchorDoc);
+ const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlay) {
if (this._playing) this.Pause();
else this.playFrom(seekTimeInSeconds, endTime);
@@ -587,7 +591,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
@action
- clickMarker = (anchorDoc: Doc, clientX: number, seekTimeInSeconds: number, endTime: number = this.videoDuration) => {
+ clickAnchor = (anchorDoc: Doc, clientX: number) => {
+ const seekTimeInSeconds = this.anchorStart(anchorDoc);
+ const endTime = this.anchorEnd(anchorDoc);
if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) {
if (this._playing) this.Pause();
else if (this.layoutDoc.autoPlay) this.Play();
@@ -602,16 +608,16 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
return { select: true };
}
- toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.videoDuration, screen_delta / width * this.videoDuration));
- // starting the drag event for marker resizing
- onPointerDown = (e: React.PointerEvent, m: any, left: boolean): void => {
- this._currMarker = m;
+ toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration));
+ // starting the drag event for anchor resizing
+ onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => {
+ this._currAnchor = m;
this._left = left;
this._timeline?.setPointerCapture(e.pointerId);
setupMoveUpEvents(this, e,
(e: PointerEvent) => {
const rect = (e.target as any).getBoundingClientRect();
- this.changeMarker(this._currMarker, this.toTimeline(e.clientX - rect.x, rect.width));
+ this.changeAnchor(this._currAnchor, this.toTimeline(e.clientX - rect.x, rect.width));
return false;
},
(e: PointerEvent) => {
@@ -622,14 +628,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
emptyFunction);
}
- // makes sure no markers overlaps each other by setting the correct position and width
- getLevel = (m: any, placed: { videoStart: number, videoEnd: number, level: number }[]) => {
+ // makes sure no anchors overlaps each other by setting the correct position and width
+ getLevel = (m: any, placed: { anchorStartTime: number, videoEnd: number, level: number }[]) => {
const timelineContentWidth = this.props.PanelWidth();
- const x1 = m.displayTimecode;
- const x2 = m.undisplayTimecode === undefined ? m.displayTimecode + 10 / timelineContentWidth * this.videoDuration : m.undisplayTimecode;
+ const x1 = this.anchorStart(m);
+ const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration);
let max = 0;
const overlappedLevels = new Set(placed.map(p => {
- const y1 = p.videoStart;
+ const y1 = p.anchorStartTime;
const y2 = p.videoEnd;
if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) ||
(y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) {
@@ -640,28 +646,29 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
let level = max + 1;
for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
- placed.push({ videoStart: x1, videoEnd: x2, level });
+ placed.push({ anchorStartTime: x1, videoEnd: x2, level });
return level;
}
playLink = (doc: Doc) => {
- const startTime = NumCast(doc.displayTimecode);
- const endTime = NumCast(doc.undisplayTimecode, null);
+ const startTime = NumCast(doc.anchorStartTime, NumCast(doc._timecodeToShow));
+ const endTime = NumCast(doc.anchorEndTime, NumCast(doc._timecodeToHide, null));
if (startTime !== undefined) {
- this.layoutDoc.playOnSelect && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime));
+ if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
+ else this.Seek(startTime);
}
}
- // renders the markers as a document
+ // renders the anchors as a document
renderInner = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number, annotationKey: string) {
- const marker = observable({ view: undefined as any });
+ const anchor = observable({ view: undefined as any });
return {
- marker, view: <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} ref={action((r: DocumentView | null) => marker.view = r)}
+ anchor, view: <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} ref={action((r: DocumentView | null) => anchor.view = r)}
Document={mark}
DataDoc={undefined}
- focus={() => this.playLink(mark)}
PanelWidth={() => width}
PanelHeight={() => height}
renderDepth={this.props.renderDepth + 1}
+ focus={() => this.playLink(mark)}
rootSelected={returnFalse}
LayoutTemplate={undefined}
LayoutTemplateString={LabelBox.LayoutString("data")}
@@ -678,11 +685,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
};
});
- renderMarker = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number, annotationKey: string) {
+ renderAnchor = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number, annotationKey: string) {
const inner = this.renderInner(mark, script, doublescript, x, y, width, height, annotationKey);
return <>
{inner.view}
- {!inner.marker.view || !SelectionManager.IsSelected(inner.marker.view) ? (null) :
+ {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) :
<>
<div key="left" className="left-resizer" onPointerDown={e => this.onPointerDown(e, mark, true)} />
<div key="right" className="resizer" onPointerDown={e => this.onPointerDown(e, mark, false)} />
@@ -694,10 +701,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
@computed get renderTimeline() {
const timelineContentWidth = this.props.PanelWidth();
const timelineContentHeight = this.props.PanelHeight() * (100 - this.heightPercent) / 100;
- const overlaps: { videoStart: number, videoEnd: number, level: number }[] = [];
- const drawMarkers: { level: number, marker: Doc }[] = this.markerDocs.map((m, i) => ({ level: this.getLevel(m, overlaps), marker: m }));
+ const overlaps: { anchorStartTime: number, videoEnd: number, level: number }[] = [];
+ const drawAnchors: { level: number, anchor: Doc }[] = this.anchorDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
- return !this.layoutDoc._showTimeline ? (null) :
+ return !this.layoutDoc._timelineShow ? (null) :
<div className="audiobox-timeline" ref={this.timelineRef} style={{ height: `${100 - this.heightPercent}%` }}
onClick={e => {
if (this._isChildActive || this.props.isSelected()) {
@@ -709,47 +716,51 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e);
}
}}>
- {drawMarkers.map(d => {
- const m = d.marker;
- const start = NumCast(m.displayTimecode, NumCast(m.displayTimecode, null));
- const left = start / this.videoDuration * timelineContentWidth;
+ {drawAnchors.map(d => {
+ const m = d.anchor;
+ const start = this.anchorStart(m);
+ const end = this.anchorEnd(m, start + 10 / timelineContentWidth * this.duration);
+ const left = start / this.duration * timelineContentWidth;
const top = d.level / maxLevel * timelineContentHeight;
- const timespan = m.undisplayTimecode === undefined ? 10 / timelineContentWidth * this.videoDuration : NumCast(m.undisplayTimecode) - NumCast(m.displayTimecode);
- return this.layoutDoc.hideMarkers ? (null) :
+ const timespan = end - start;
+ return this.layoutDoc.hideAnchors ? (null) :
<div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={m[Id]}
- style={{ left, top, width: `${timespan / this.videoDuration * 100}%`, height: `${1 / maxLevel * 100}%` }}
- onClick={e => { this.playFrom(start, Cast(m.undisplayTimecode, "number", null)); e.stopPropagation(); }} >
- {this.renderMarker(m, this.rangeClickScript, this.rangePlayScript,
+ style={{ left, top, width: `${timespan / this.duration * 100}%`, height: `${1 / maxLevel * 100}%` }}
+ onClick={e => { this.playFrom(start, this.anchorEnd(m)); e.stopPropagation(); }} >
+ {this.renderAnchor(m, this.rangeClickScript, this.rangePlayScript,
left,
top + (this.props.PanelHeight() - timelineContentHeight),
- timelineContentWidth * timespan / this.videoDuration,
- timelineContentHeight / maxLevel, this.annotationKey + (m.useLinkSmallAnchor ? "-timeline" : ""))}
+ timelineContentWidth * timespan / this.duration,
+ timelineContentHeight / maxLevel, this.annotationKey + (m.anchorStartTime !== undefined ? "-timeline" : ""))}
</div>;
})}
{this.selectionContainer}
<div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }}
- style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.videoDuration * 100}%`, pointerEvents: "none" }}
+ style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.duration * 100}%`, pointerEvents: "none" }}
/>
</div>;
}
- // updates the marker with the new time
+ // updates the anchor with the new time
@action
- changeMarker = (m: any, time: any) => {
- this.markerDocs.filter(marker => this.isSame(marker, m)).forEach(marker =>
- this._left ? marker.displayTimecode = time : marker.undisplayTimecode = time);
+ changeAnchor = (anchor: Opt<Doc>, time: number) => {
+ if (anchor) {
+ const timelineOnly = Cast(anchor.anchorStartTime, "number", null) !== undefined;
+ if (timelineOnly) this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time;
+ else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
+ }
}
- // checks if the two markers are the same with start and end time
+ // checks if the two anchors are the same with start and end time
isSame = (m1: any, m2: any) => {
- return m1.displayTimecode === m2.displayTimecode && m1.undisplayTimecode === m2.undisplayTimecode;
+ return m1.anchorStartTime === m2.anchorStartTime && m1.anchorEndTime === m2.anchorEndTime && m1._timecodeToShow === m2._timecodeToShow && m1._timecodeToHide === m2._timecodeToHide;
}
// returns the blue container when dragging
@computed get selectionContainer() {
return VideoBox.SelectingRegion !== this ? (null) : <div className="audiobox-container" style={{
- left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.videoDuration * 100}%`,
- width: `${Math.abs(this._markerStart - this._markerEnd) / this.videoDuration * 100}%`, height: "100%", top: "0%"
+ left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.duration * 100}%`,
+ width: `${Math.abs(this._markerStart - this._markerEnd) / this.duration * 100}%`, height: "100%", top: "0%"
}} />;
}
@@ -757,7 +768,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
VideoBox.Instance.keyEvents(e);
}
- // for creating key markers with key events
+ // for creating key anchors with key events
@action
keyEvents = (e: KeyboardEvent) => {
if (e.target instanceof HTMLInputElement) return;
@@ -770,7 +781,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._start = false;
this._visible = true;
} else {
- this.createMarker(this._markerStart, currTime);
+ this.createAnchor(this._markerStart, currTime);
this._start = true;
this._visible = false;
}
@@ -828,7 +839,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
whenActiveChanged={this.whenActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
- addDocument={this.addDocumentWithTimestamp}
+ addDocument={this.addDocWithTimecode}
CollectionView={undefined}
renderDepth={this.props.renderDepth + 1}>
{this.contentFunc}
@@ -838,7 +849,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
{this.annotationLayer}
{this.renderTimeline}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocumentWithTimestamp} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocWithTimecode} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div>
</div >);
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 7348ebdd2..e06a324d2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -361,7 +361,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DocListCast(this.dataDoc.links).map((l, i) => {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- this._linkTime = NumCast(la1.audioStart, NumCast(la2.audioStart));
+ this._linkTime = NumCast(la1.anchorStartTime, NumCast(la2.anchorStartTime));
audioState = la2.audioState;
if (Doc.AreProtosEqual(la2, this.dataDoc)) {
la1 = l.anchor2 as Doc;
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 67df56015..d52f3a928 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -19,10 +19,10 @@ export const documentSchema = createSchema({
lastFrame: "number", // last frame of a frame based collection (e.g., a progressive slide)
activeFrame: "number", // the active frame of a frame based animated document
_currentTimecode: "number", // current play back time of a temporal document (video / audio)
- displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+ _timecodeToShow: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
isLabel: "boolean", // whether the document is a label or not (video / audio)
- audioStart: "number", // the time frame where the audio should begin playing
- audioEnd: "number", // the time frame where the audio should stop playing
+ anchorStartTime: "number", // the time code where a document activates (eg in Audio or video timelines)
+ anchorEndTime: "number", // the time code where a document deactivates
markers: listSpec(Doc), // list of markers for audio / video
x: "number", // x coordinate when in a freeform view
y: "number", // y coordinate when in a freeform view