diff options
| author | bobzel <zzzman@gmail.com> | 2024-01-04 13:11:22 -0500 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2024-01-04 13:11:22 -0500 |
| commit | ae27dd1689ae1716591aab094e6d41f3a0160fef (patch) | |
| tree | 09735c798abde4fd0b8f234c48375bd6cb1112a4 /src/client/views/nodes/VideoBox.tsx | |
| parent | ebf32ac65d35053f847fb2cf60f915eb29d6fdd5 (diff) | |
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.
Diffstat (limited to 'src/client/views/nodes/VideoBox.tsx')
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 158 |
1 files changed, 20 insertions, 138 deletions
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; |
