From ae27dd1689ae1716591aab094e6d41f3a0160fef Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 4 Jan 2024 13:11:22 -0500
Subject: fixed ffmpeg for uploading videos. fixed getView() to take focus
 options so that if a sidebar or timeline has to be opened, the didMove flag
 can be set properly.  added video snapshot frame to top menu.  fixed using
 '^' to stop and start a selection region on timelines.

---
 src/client/views/nodes/VideoBox.tsx | 158 +++++-------------------------------
 1 file changed, 20 insertions(+), 138 deletions(-)

(limited to 'src/client/views/nodes/VideoBox.tsx')

diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ce73d9f37..fb42286af 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,9 +1,9 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, 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 * as React from 'react';
-import { Doc, StrListCast } from '../../../fields/Doc';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -12,23 +12,20 @@ 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 } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
+import { DocFocusOptions, DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
@@ -40,7 +37,7 @@ import './VideoBox.scss';
  * 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
@@ -51,16 +48,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     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
@@ -74,12 +67,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._props.setContentView?.(this);
     }
 
-    @observable _stackedTimeline: any = undefined; // CollectionStackedTimeline ref
-    @observable static _nativeControls: boolean = false; // default html controls
+    @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;
@@ -99,11 +90,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // @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.Document[this._props.fieldKey + '_audio'], AudioField, null);
@@ -130,17 +116,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     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.player && this.setPlayheadTime(this.timeline?.clipStart || 0);
         document.addEventListener('keydown', this.keyEvents, true);
 
         if (this.presentation) {
@@ -201,8 +177,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 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);
             }
@@ -212,11 +186,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // 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
@@ -242,13 +211,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         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) {
@@ -270,11 +235,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             this._fullScreen = true;
             this.player && this._contentRef && this._contentRef.requestFullscreen();
         }
-        try {
-            this._youtubePlayer && this._props.addDocTab(this.Document, OpenWhere.add);
-        } catch (e) {
-            console.log('Video FullScreen Exception:', e);
-        }
     };
 
     // fades out controls in fullscreen after mouse stops moving
@@ -334,22 +294,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             });
             this._props.addDocument?.(b);
             DocUtils.MakeLink(b, this.Document, { 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);
-                }
-            });
         } 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.Document.title).replace(/[ -\.:]/g, '');
-            const encodedFilename = encodeURIComponent('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString().replace(/\./, '_'));
+            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));
         }
@@ -416,16 +366,18 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @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: DocFocusOptions) => {
+        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 = () => {
@@ -518,7 +470,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 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" });
@@ -581,7 +532,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ref={this.setVideoRef}
                         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()}
@@ -600,54 +551,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 
-    @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() && !SnappingManager.IsDragging && !SnappingManager.IsResizing,
-                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());
 
@@ -723,25 +626,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     };
 
-    @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 {
@@ -1090,7 +974,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             removeDocument={this.removeDocument}
                             moveDocument={this.moveDocument}
                             addDocument={this.addDocWithTimecode}>
-                            {this.youtubeVideoId ? this.youtubeContent : this.content}
+                            {this.content}
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
@@ -1225,5 +1109,3 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 }
-
-VideoBox._nativeControls = false;
-- 
cgit v1.2.3-70-g09d2

