aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/VideoBox.tsx
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2024-01-25 11:35:26 -0500
committerSophie Zhang <sophie_zhang@brown.edu>2024-01-25 11:35:26 -0500
commitf3dab2a56db5e4a6a3dca58185d94e1ff7d1dc32 (patch)
treea7bc895266b53bb620dbd2dd71bad2e83b555446 /src/client/views/nodes/VideoBox.tsx
parentb5c5410b4af5d2c68d2107d3f064f6e3ec4ac3f2 (diff)
parent136f3d9f349d54e8bdd73b6380ea47c19e5edebf (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/nodes/VideoBox.tsx')
-rw-r--r--src/client/views/nodes/VideoBox.tsx349
1 files changed, 116 insertions, 233 deletions
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d7f7c9b73..40647feff 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,40 +1,36 @@
-import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { basename } from 'path';
-import { Doc, StrListCast } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import * as React from 'react';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
-import { Networking } from '../../Network';
import { DocumentManager } from '../../util/DocumentManager';
import { FollowLinkScript } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ReplayMovements } from '../../util/ReplayMovements';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
-const path = require('path');
/**
* VideoBox
@@ -42,48 +38,47 @@ const path = require('path');
* Supporting Components: CollectionStackedTimeline
*
* VideoBox is a node that supports the playback of video files in Dash.
- * When a video file or YouTube video is importeed into Dash, it is immediately rendered as a VideoBox document.
+ * When a video file is importeed into Dash, it is immediately rendered as a VideoBox document.
* CollectionStackedTimline handles AudioBox and VideoBox shared behavior, but VideoBox handles playing, pausing, etc because it contains <video> element
* User can trim video: nondestructive, just sets new bounds for playback and rendering timeline
* Like images, users can zoom and pan and it has an overlay layer allowing for annotations on top of the video at different times
*/
@observer
-export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(VideoBox, fieldKey);
}
-
- static _youtubeIframeCounter: number = 0;
static heightPercent = 80; // height of video relative to videoBox when timeline is open
static numThumbnails = 20;
private unmounting = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
- private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null; // <video> ref
private _contentRef: HTMLDivElement | null = null; // ref to div that wraps video and controls for full screen
- private _youtubeIframeId: number = -1;
- private _youtubeContentCreated = false;
private _audioPlayer: HTMLAudioElement | null = null;
+ private _marqueeref = React.createRef<MarqueeAnnotator>();
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); // outermost div
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _playRegionTimer: any = null; // timeout for playback
private _controlsFadeTimer: any = null; // timeout for controls fade
- @observable _stackedTimeline: any; // CollectionStackedTimeline ref
- @observable static _nativeControls: boolean; // default html controls
- @observable _marqueeing: number[] | undefined; // coords for marquee selection
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ this._props.setContentViewBox?.(this);
+ }
+
+ @observable _stackedTimeline: CollectionStackedTimeline | undefined = undefined; // CollectionStackedTimeline ref
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _screenCapture = false;
@observable _clicking = false; // used for transition between showing/hiding timeline
- @observable _forceCreateYouTubeIFrame = false;
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
@observable _playing = false;
@observable _finished: boolean = false; // has playback reached end of clip
@observable _volume: number = 1;
@observable _muted: boolean = false;
- @observable _controlsTransform?: { X: number; Y: number };
+ @observable _controlsTransform?: { X: number; Y: number } = undefined;
@observable _controlsVisible: boolean = true;
@observable _scrubbing: boolean = false;
@@ -96,14 +91,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// @computed get rawDuration() { return NumCast(this.dataDoc[this.fieldKey + "_duration"]); }
@observable rawDuration: number = 0;
- @computed get youtubeVideoId() {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
- return field && field.url.href.indexOf('youtube') !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split('/')) : '';
- }
-
// returns the path of the audio file
@computed get audiopath() {
- const field = Cast(this.props.Document[this.props.fieldKey + '_audio'], AudioField, null);
+ const field = Cast(this.Document[this._props.fieldKey + '_audio'], AudioField, null);
const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
return field?.url.href ?? vfield?.url.href ?? '';
}
@@ -126,18 +116,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
componentDidMount() {
this.unmounting = false;
- this.props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
- if (this.youtubeVideoId) {
- const youtubeaspect = 400 / 315;
- const nativeWidth = Doc.NativeWidth(this.layoutDoc);
- const nativeHeight = Doc.NativeHeight(this.layoutDoc);
- if (!nativeWidth || !nativeHeight) {
- if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 600);
- Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 600) / youtubeaspect);
- this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
- }
- }
- this.player && this.setPlayheadTime(this.timeline.clipStart || 0);
+ this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+ this.player && this.setPlayheadTime(this.timeline?.clipStart || 0);
document.addEventListener('keydown', this.keyEvents, true);
if (this.presentation) {
@@ -163,7 +143,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (
// need to include range inputs because after dragging time slider it becomes target element
!(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
- this.props.isSelected(true)
+ this._props.isSelected()
) {
switch (e.key) {
case 'ArrowLeft':
@@ -198,8 +178,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime);
update && this.player && this.playFrom(start, undefined, true);
update && this._audioPlayer?.play();
- update && this._youtubePlayer?.playVideo();
- this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
} catch (e) {
console.log('Video Play Exception:', e);
}
@@ -209,11 +187,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// goes to time
@action public Seek(time: number) {
- try {
- this._youtubePlayer?.seekTo(Math.round(time), true);
- } catch (e) {
- console.log('Video Seek Exception:', e);
- }
this.player && (this.player.currentTime = time);
this._audioPlayer && (this._audioPlayer.currentTime = time);
// TODO: revisit this and clean it
@@ -239,13 +212,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
try {
update && this.player?.pause();
update && this._audioPlayer?.pause();
- update && this._youtubePlayer?.pauseVideo();
- this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
- this._youtubePlayer?.seekTo(this._youtubePlayer?.getCurrentTime(), true);
} catch (e) {
console.log('Video Pause Exception:', e);
}
- this._youtubePlayer && SelectionManager.DeselectAll(); // if we don't deselect the player, then we get an annoying YouTube spinner I guess telling us we're paused.
this._playTimer = undefined;
this.updateTimecode();
if (!this._finished) {
@@ -267,11 +236,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._fullScreen = true;
this.player && this._contentRef && this._contentRef.requestFullscreen();
}
- try {
- this._youtubePlayer && this.props.addDocTab(this.rootDoc, OpenWhere.add);
- } catch (e) {
- console.log('Video FullScreen Exception:', e);
- }
};
// fades out controls in fullscreen after mouse stops moving
@@ -329,24 +293,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
title: (this.layoutDoc._layout_currentTimecode || 0).toString(),
onClick: FollowLinkScript(),
});
- this.props.addDocument?.(b);
- DocUtils.MakeLink(b, this.rootDoc, { link_relationship: 'video snapshot' });
- Networking.PostToServer('/youtubeScreenshot', {
- id: this.youtubeVideoId,
- timecode: this.layoutDoc._layout_currentTimecode,
- }).then(response => {
- const resolved = response?.accessPaths?.agnostic?.client;
- if (resolved) {
- this.props.removeDocument?.(b);
- this.createSnapshotLink(resolved);
- }
- });
+ this._props.addDocument?.(b);
+ DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
} else {
//convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, '');
- const encodedFilename = encodeURIComponent('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString().replace(/\./, '_'));
+ const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
+ const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_'));
const filename = basename(encodedFilename);
Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
}
@@ -355,8 +309,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
updateIcon = () => {
const makeIcon = (returnedfilename: string) => {
this.dataDoc.icon = new ImageField(returnedfilename);
- this.dataDoc.icon_nativeWidth = this.layoutDoc[Width]();
- this.dataDoc.icon_nativeHeight = this.layoutDoc[Height]();
+ this.dataDoc.icon_nativeWidth = NumCast(this.layoutDoc._width);
+ this.dataDoc.icon_nativeHeight = NumCast(this.layoutDoc._height);
};
this.Snapshot(undefined, undefined, makeIcon);
};
@@ -376,11 +330,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
_height: (height / width) * 150,
title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
});
- Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
- Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
- this.props.addDocument?.(imageSnapshot);
+ Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
+ Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
+ this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
- link && (Doc.GetProto(link.link_anchor_2 as Doc).timecodeToHide = NumCast((link.link_anchor_2 as Doc).timecodeToShow) + 3);
+ link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
};
@@ -389,9 +343,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
const anchor = addAsAnnotation
- ? CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.rootDoc
- : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc);
+ ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.Document
+ : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.Document);
return anchor;
};
@@ -413,16 +367,18 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
updateTimecode = () => {
!this.unmounting && this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime);
- try {
- this._youtubePlayer && (this.layoutDoc._layout_currentTimecode = this._youtubePlayer.getCurrentTime?.());
- } catch (e) {
- console.log('Video Timecode Exception:', e);
- }
};
- // getView = async (doc: Doc) => {
- // return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
- // };
+ getView = (doc: Doc, options: FocusViewOptions) => {
+ if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
+ if (this.heightPercent === 100) {
+ this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+ options.didMove = true;
+ }
+ return this._stackedTimeline.getView(doc, options);
+ }
+ return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+ };
// extracts video thumbnails and saves them as field of doc
getVideoThumbnails = () => {
@@ -438,7 +394,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
canvas.height = 100;
canvas.width = 100;
canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
- const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, '');
+ const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
thumbnailPromises?.push(Utils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
@@ -493,13 +449,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// context menu
specificContextMenu = (e: React.MouseEvent): void => {
- const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
if (field) {
const url = field.url.href;
const subitems: ContextMenuProps[] = [];
subitems.push({ description: 'Full Screen', event: this.FullScreen, icon: 'expand' });
subitems.push({ description: 'Take Snapshot', event: this.Snapshot, icon: 'expand-arrows-alt' });
- this.rootDoc.type === DocumentType.SCREENSHOT &&
+ this.Document.type === DocumentType.SCREENSHOT &&
subitems.push({
description: 'Screen Capture',
event: async () => {
@@ -515,7 +471,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
icon: 'expand-arrows-alt',
});
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: "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" });
@@ -533,7 +488,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
event: () => {
this.dataDoc.layout = RecordingBox.LayoutString(this.fieldKey);
// delete assoicated video data
- this.dataDoc[this.props.fieldKey] = '';
+ this.dataDoc[this._props.fieldKey] = '';
this.dataDoc[this.fieldKey + '_duration'] = '';
// delete assoicated presentation data
this.dataDoc[this.fieldKey + '_presentation'] = '';
@@ -551,7 +506,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// renders the video and audio
@computed get content() {
const field = Cast(this.dataDoc[this.fieldKey], VideoField);
- const interactive = Doc.ActiveTool !== InkTool.None || !this.props.isSelected() ? '' : '-interactive';
+ const interactive = Doc.ActiveTool !== InkTool.None || !this._props.isSelected() ? '' : '-interactive';
const classname = 'videoBox-content' + (this._fullScreen ? '-fullScreen' : '') + interactive;
const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0;
return !field ? (
@@ -576,9 +531,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
key="video"
autoPlay={this._screenCapture}
ref={this.setVideoRef}
- style={this._fullScreen ? this.fullScreenSize() : this.isCropped ? { width: 'max-content', height: 'max-content', transform: `scale(${1 / NumCast(this.rootDoc._freeform_scale)})`, transformOrigin: 'top left' } : {}}
+ style={this._fullScreen ? this.fullScreenSize() : this.isCropped ? { width: 'max-content', height: 'max-content', transform: `scale(${1 / NumCast(this.layoutDoc._freeform_scale)})`, transformOrigin: 'top left' } : {}}
onCanPlay={this.videoLoad}
- controls={VideoBox._nativeControls}
+ controls={false}
onPlay={() => this.Play()}
onSeeked={this.updateTimecode}
onPause={() => this.Pause()}
@@ -587,7 +542,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
Not supported.
</video>
{!this.audiopath || this.audiopath === field.url.href ? null : (
- <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}>
+ <audio ref={this.setAudioRef} className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}>
<source src={this.audiopath} type="audio/mpeg" />
Not supported.
</audio>
@@ -597,54 +552,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
);
}
- @action youtubeIframeLoaded = (e: any) => {
- if (!this._youtubeContentCreated) {
- this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame;
- return;
- } else this._youtubeContentCreated = false;
-
- this.loadYouTube(e.target);
- };
-
- loadYouTube = (iframe: any) => {
- let started = true;
- const onYoutubePlayerStateChange = (event: any) =>
- runInAction(() => {
- if (started && event.data === YT.PlayerState.PLAYING) {
- started = false;
- this._youtubePlayer?.unMute();
- //this.Pause();
- return;
- }
- if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
- if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
- });
- const onYoutubePlayerReady = (event: any) => {
- this._disposers.reactionDisposer?.();
- this._disposers.youtubeReactionDisposer?.();
- this._disposers.reactionDisposer = reaction(
- () => this.layoutDoc._layout_currentTimecode,
- () => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode))
- );
- this._disposers.youtubeReactionDisposer = reaction(
- () => Doc.ActiveTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
- interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
- { fireImmediately: true }
- );
- };
- if (typeof YT === undefined) setTimeout(() => this.loadYouTube(iframe), 100);
- else {
- (YT as any)?.ready(() => {
- this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
- events: {
- onReady: this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
- onStateChange: this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
- },
- });
- });
- }
- };
-
// for play button
onPlayDown = () => (this._playing ? this.Pause() : this.Play());
@@ -678,9 +585,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
e,
action(encodeURIComponent => {
this._clicking = false;
- if (this.props.isContentActive()) {
- // const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
- // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
+ if (this._props.isContentActive()) {
+ // const local = this.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
+ // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this._props.PanelHeight() * 100));
this.layoutDoc._layout_timelineHeightPercent = 80;
}
@@ -694,15 +601,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
500
);
},
- this.props.isContentActive(),
- this.props.isContentActive()
+ this._props.isContentActive(),
+ this._props.isContentActive()
);
};
// removes from currently playing display
@action
removeCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this.DocumentView?.();
if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -711,7 +618,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
- const docView = this.props.DocumentView?.();
+ const docView = this.DocumentView?.();
if (!CollectionStackedTimeline.CurrentlyPlaying) {
CollectionStackedTimeline.CurrentlyPlaying = [];
}
@@ -720,25 +627,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
};
- @computed get youtubeContent() {
- this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
- this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
- const classname = 'videoBox-content-YouTube' + (this._fullScreen ? '-fullScreen' : '');
- const start = untracked(() => Math.round(NumCast(this.layoutDoc._layout_currentTimecode)));
- return (
- <iframe
- key={this._youtubeIframeId}
- id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
- onPointerLeave={this.updateTimecode}
- onLoad={this.youtubeIframeLoaded}
- className={classname}
- width={Doc.NativeWidth(this.layoutDoc) || 640}
- height={Doc.NativeHeight(this.layoutDoc) || 390}
- src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._nativeControls ? 1 : 0}`}
- />
- );
- }
-
// for annotating, adds doc with time info
@action.bound
addDocWithTimecode(doc: Doc | Doc[]): boolean {
@@ -851,7 +739,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
zoom = (zoom: number) => this.timeline?.setZoom(zoom);
// plays link
- playLink = (doc: Doc, options: DocFocusOptions) => {
+ playLink = (doc: Doc, options: FocusViewOptions) => {
const startTime = Math.max(0, NumCast(doc.config_clipStart, this._stackedTimeline?.anchorStart(doc) || 0));
const endTime = this.timeline?.anchorEnd(doc);
if (startTime !== undefined) {
@@ -862,13 +750,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// starts marquee selection
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
action(e => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
- this._marqueeing = [e.clientX, e.clientY];
+ this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
return true;
}),
returnFalse,
@@ -882,48 +770,47 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// ends marquee selection
@action
finishMarquee = () => {
- this._marqueeing = undefined;
- this.props.select(true);
+ this._marqueeref.current?.onTerminateSelection();
+ this._props.select(true);
};
- timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+ timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
timelineScreenToLocal = () =>
- this.props
+ this._props
.ScreenToLocalTransform()
.scale(this.scaling())
- .translate(0, (-this.heightPercent / 100) * this.props.PanelHeight());
+ .translate(0, (-this.heightPercent / 100) * this._props.PanelHeight());
setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time);
- timelineHeight = () => (this.props.PanelHeight() * (100 - this.heightPercent)) / 100;
+ timelineHeight = () => (this._props.PanelHeight() * (100 - this.heightPercent)) / 100;
playing = () => this._playing;
- scaling = () => this.props.NativeDimScaling?.() || 1;
+ scaling = () => this._props.NativeDimScaling?.() || 1;
- panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
- panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : (this.props.PanelHeight() * this.heightPercent) / 100);
+ panelWidth = () => (this._props.PanelWidth() * this.heightPercent) / 100;
+ panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.dataDoc) || 1) : (this._props.PanelHeight() * this.heightPercent) / 100);
screenToLocalTransform = () => {
- const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
- return this.props
+ const offset = (this._props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
+ return this._props
.ScreenToLocalTransform()
.translate(-offset, 0)
.scale(100 / this.heightPercent);
};
- marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100;
marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`];
// renders video controls
componentUI = (boundsLeft: number, boundsTop: number) => {
- const xf = this.props.ScreenToLocalTransform().inverse();
- const height = this.props.PanelHeight();
+ const xf = this.ScreenToLocalBoxXf().inverse();
+ const height = this._props.PanelHeight();
const vidHeight = (height * this.heightPercent) / 100 / this.scaling();
- const vidWidth = this.props.PanelWidth() / this.scaling();
+ const vidWidth = this._props.PanelWidth() / this.scaling();
const uiHeight = 25;
const uiMargin = 10;
const yBot = xf.transformPoint(0, vidHeight)[1];
@@ -938,8 +825,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div
className="videoBox-ui"
style={{
- transformOrigin: 'top left',
- transform: `rotate(${NumCast(this.rootDoc.rotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+ transform: `rotate(${this.ScreenToLocalBoxXf().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
left: xPos,
top: yMid,
height: uiHeight,
@@ -953,22 +839,22 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
);
};
+ thumbnails = () => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']);
// renders CollectionStackedTimeline
@computed get renderTimeline() {
return (
<div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%`, display: this.heightPercent === 100 ? 'none' : '' }}>
<CollectionStackedTimeline
ref={action((r: any) => (this._stackedTimeline = r))}
- {...this.props}
+ {...this._props}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + '_dictation'}
mediaPath={this.audiopath}
- thumbnails={() => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])}
- renderDepth={this.props.renderDepth + 1}
+ thumbnails={this.thumbnails}
+ renderDepth={this._props.renderDepth + 1}
startTag={'_timecodeToShow' /* videoStart */}
endTag={'_timecodeToHide' /* videoEnd */}
- bringToFront={emptyFunction}
playFrom={this.playFrom}
setTime={this.setPlayheadTime}
playing={this.playing}
@@ -999,32 +885,33 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
crop = (region: Doc | undefined, addCrop?: boolean) => {
if (!region) return;
const cropping = Doc.MakeCopy(region, true);
- Doc.GetProto(region).backgroundColor = 'transparent';
- Doc.GetProto(region).lockedPosition = true;
- Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
- Doc.GetProto(region).followLinkToggle = true;
+ const regionData = region[DocData];
+ regionData.backgroundColor = 'transparent';
+ regionData.lockedPosition = true;
+ regionData.title = 'region:' + this.Document.title;
+ regionData.followLinkToggle = true;
region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
this.addDocument(region);
const anchx = NumCast(cropping.x);
const anchy = NumCast(cropping.y);
const anchw = NumCast(cropping._width);
const anchh = NumCast(cropping._height);
- const viewScale = NumCast(this.rootDoc[this.fieldKey + '_nativeWidth']) / anchw;
- cropping.title = 'crop: ' + this.rootDoc.title;
- cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
- cropping.y = NumCast(this.rootDoc.y);
- cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
- cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+ const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) / anchw;
+ cropping.title = 'crop: ' + this.Document.title;
+ cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
+ cropping.y = NumCast(this.Document.y);
+ cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
+ cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
cropping.timecodeToHide = undefined;
cropping.timecodeToShow = undefined;
cropping.onClick = undefined;
- const croppingProto = Doc.GetProto(cropping);
+ const croppingProto = cropping[DocData];
croppingProto.annotationOn = undefined;
croppingProto.isDataDoc = true;
- croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+ croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
croppingProto.type = DocumentType.VID;
croppingProto.layout = VideoBox.LayoutString('data');
- croppingProto.data = ObjectField.MakeCopy(this.rootDoc[this.fieldKey] as ObjectField);
+ croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
croppingProto['data_nativeWidth'] = anchw;
croppingProto['data_nativeHeight'] = anchh;
croppingProto.videoCrop = true;
@@ -1040,12 +927,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
- this.props.bringToFront(cropping);
+ this._props.bringToFront?.(cropping);
return cropping;
};
savedAnnotations = () => this._savedAnnotations;
render() {
- const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+ const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / this.scaling()}px` : borderRad;
return (
<div
@@ -1055,11 +942,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
style={{
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
borderRadius,
- overflow: this.props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
- }}
- onWheel={e => {
- e.stopPropagation();
- e.preventDefault();
+ overflow: this.DocumentView?.().layout_fitWidth ? 'auto' : undefined,
}}>
<div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
<div
@@ -1069,19 +952,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
width: this.panelWidth(),
height: this.panelHeight(),
top: 0,
- left: (this.props.PanelWidth() - this.panelWidth()) / 2,
+ left: (this._props.PanelWidth() - this.panelWidth()) / 2,
}}>
<CollectionFreeFormView
- {...this.props}
- setContentView={emptyFunction}
+ {...this._props}
+ setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
- renderDepth={this.props.renderDepth + 1}
+ renderDepth={this._props.renderDepth + 1}
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
annotationLayerHostsContent={true}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
+ PanelWidth={this._props.PanelWidth}
+ PanelHeight={this._props.PanelHeight}
isAnyChildContentActive={returnFalse}
ScreenToLocalTransform={this.screenToLocalTransform}
childFilters={this.timelineDocFilter}
@@ -1092,24 +975,26 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocWithTimecode}>
- {this.youtubeVideoId ? this.youtubeContent : this.content}
+ {this.content}
</CollectionFreeFormView>
</div>
{this.annotationLayer}
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+ {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
- rootDoc={this.rootDoc}
+ ref={this._marqueeref}
+ Document={this.Document}
scrollTop={0}
- down={this._marqueeing}
- scaling={this.marqueeFitScaling}
- docView={this.props.docViewPath().slice(-1)[0]}
+ annotationLayerScrollTop={0}
+ scaling={returnOne}
+ annotationLayerScaling={this._props.NativeDimScaling}
+ docView={this.DocumentView}
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
selectionText={returnEmptyString}
annotationLayer={this._annotationLayer.current}
- mainCont={this._mainCont.current}
+ marqueeContainer={this._mainCont.current}
anchorMenuCrop={this.crop}
/>
)}
@@ -1120,7 +1005,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@computed get UIButtons() {
- const bounds = this.props.docViewPath().lastElement().getBounds();
+ const bounds = this.DocumentView?.().getBounds;
const width = (bounds?.right || 0) - (bounds?.left || 0);
const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
return (
@@ -1225,5 +1110,3 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
);
}
}
-
-VideoBox._nativeControls = false;