aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/VideoBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/VideoBox.tsx')
-rw-r--r--src/client/views/nodes/VideoBox.tsx140
1 files changed, 115 insertions, 25 deletions
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e83aa8bea..19968e6e1 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,15 +3,15 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface, createSchema } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types";
+import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema";
+import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
import { RouteStore } from "../../../server/RouteStore";
-import { Utils } from "../../../Utils";
+import { Utils, emptyFunction, returnOne } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
-import { DocComponent } from "../DocComponent";
+import { DocAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { documentSchema } from "./DocumentView";
@@ -22,6 +22,8 @@ import { faVideo } from "@fortawesome/free-solid-svg-icons";
import { Doc } from "../../../new_fields/Doc";
import { ScriptField } from "../../../new_fields/ScriptField";
import { positionSchema } from "./CollectionFreeFormDocumentView";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
var path = require('path');
export const timeSchema = createSchema({
@@ -33,20 +35,21 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
library.add(faVideo);
@observer
-export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+ static _youtubeIframeCounter: number = 0;
private _reactionDisposer?: IReactionDisposer;
private _youtubeReactionDisposer?: IReactionDisposer;
private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null;
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
- static _youtubeIframeCounter: number = 0;
+ private _isResetClick = 0;
@observable _forceCreateYouTubeIFrame = false;
- @observable static _showControls: boolean;
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
- @observable public Playing: boolean = false;
- public static LayoutString() { return FieldView.LayoutString(VideoBox); }
+ @observable _playing = false;
+ @observable static _showControls: boolean;
+ public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(VideoBox, "data", fieldExt); }
public get player(): HTMLVideoElement | null {
return this._videoRef;
@@ -65,7 +68,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
@action public Play = (update: boolean = true) => {
- this.Playing = true;
+ this._playing = true;
update && this.player && this.player.play();
update && this._youtubePlayer && this._youtubePlayer.playVideo();
this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
@@ -78,7 +81,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
@action public Pause = (update: boolean = true) => {
- this.Playing = false;
+ this._playing = false;
update && this.player && this.player.pause();
update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo();
this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
@@ -123,8 +126,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
//convert to desired file format
var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString()));
- VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => {
+ let filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_")));
+ VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
if (returnedFilename) {
let url = this.choosePath(Utils.prepend(returnedFilename));
let imageSummary = Docs.Create.ImageDocument(url, {
@@ -132,7 +135,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
});
imageSummary.isButton = true;
- this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
+ this.props.addDocument && this.props.addDocument(imageSummary);
DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot");
}
});
@@ -174,7 +177,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
this._reactionDisposer && this._reactionDisposer();
this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0,
- time => !this.Playing && (vref.currentTime = time), { fireImmediately: true });
+ time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
}
}
@@ -211,7 +214,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
- <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
+ <video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}>
<source src={field.url.href} type="video/mp4" />
Not supported.
@@ -239,13 +242,13 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
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);
+ if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
+ if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
});
let onYoutubePlayerReady = (event: any) => {
this._reactionDisposer && this._reactionDisposer();
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
- this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0));
+ this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0));
this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
iframe.style.pointerEvents = interactive ? "all" : "none";
@@ -259,8 +262,68 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
});
}
+ private get uIButtons() {
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+ let curTime = NumCast(this.props.Document.currentTimecode);
+ return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
+ <span>{"" + Math.round(curTime)}</span>
+ <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
+ </div>,
+ <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} style={{ transform: `scale(${scaling})` }}>
+ <FontAwesomeIcon icon="camera" size="lg" />
+ </div>,
+ VideoBox._showControls ? (null) : [
+ <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}>
+ <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
+ </div>,
+ <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
+ F
+ </div>
+ ]]);
+ }
+
+ @action
+ onPlayDown = () => this._playing ? this.Pause() : this.Play()
+
+ @action
+ onFullDown = (e: React.PointerEvent) => {
+ this.FullScreen();
+ e.stopPropagation();
+ e.preventDefault();
+ }
- @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+ @action
+ onSnapshot = (e: React.PointerEvent) => {
+ this.Snapshot();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ @action
+ onResetDown = (e: React.PointerEvent) => {
+ this.Pause();
+ e.stopPropagation();
+ this._isResetClick = 0;
+ document.addEventListener("pointermove", this.onResetMove, true);
+ document.addEventListener("pointerup", this.onResetUp, true);
+ InkingControl.Instance.switchTool(InkTool.Eraser);
+ }
+
+ @action
+ onResetMove = (e: PointerEvent) => {
+ this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
+ this.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
+ e.stopImmediatePropagation();
+ }
+ @action
+ onResetUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.onResetMove, true);
+ document.removeEventListener("pointerup", this.onResetUp, true);
+ InkingControl.Instance.switchTool(InkTool.None);
+ this._isResetClick < 10 && (this.props.Document.currentTimecode = 0);
+ }
+ @computed get fieldExtensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+ @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
@computed get youtubeContent() {
this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
@@ -269,15 +332,42 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
- src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
- ></iframe>;
+ src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
+ }
+
+ @action.bound
+ addDocumentWithTimestamp(doc: Doc): boolean {
+ Doc.GetProto(doc).annotationOn = this.props.Document;
+ var curTime = NumCast(this.props.Document.currentTimecode, -1);
+ curTime !== -1 && (doc.displayTimecode = curTime);
+ return Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
}
render() {
Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
- return <div style={{ pointerEvents: "all", width: "100%", height: "100%" }} onContextMenu={this.specificContextMenu}>
- {this.youtubeVideoId ? this.youtubeContent : this.content}
- </div>;
+ return (<div className={"videoBox-container"} onContextMenu={this.specificContextMenu}>
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ select={emptyFunction}
+ active={this.active}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocumentWithTimestamp}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ ruleProvider={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ chromeCollapsed={true}>
+ {() => [this.youtubeVideoId ? this.youtubeContent : this.content]}
+ </CollectionFreeFormView>
+ {this.uIButtons}
+ </div >);
}
}