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.tsx240
1 files changed, 175 insertions, 65 deletions
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 35ecf12f6..9806b10b5 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,19 +1,24 @@
import React = require("react");
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
-import { FieldView, FieldViewProps } from './FieldView';
-import * as rp from "request-promise";
-import "./VideoBox.scss";
-import { action, IReactionDisposer, reaction, observable } from "mobx";
-import { DocComponent } from "../DocComponent";
-import { positionSchema } from "./DocumentView";
+import * as rp from 'request-promise';
+import { InkTool } from "../../../new_fields/InkField";
import { makeInterface } from "../../../new_fields/Schema";
-import { pageSchema } from "./ImageBox";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
-import "./VideoBox.scss";
import { RouteStore } from "../../../server/RouteStore";
+import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { actionFieldDecorator } from "mobx/lib/internal";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { DocComponent } from "../DocComponent";
+import { DocumentDecorations } from "../DocumentDecorations";
+import { InkingControl } from "../InkingControl";
+import { positionSchema } from "./DocumentView";
+import { FieldView, FieldViewProps } from './FieldView';
+import { pageSchema } from "./ImageBox";
+import "./VideoBox.scss";
type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
const VideoDocument = makeInterface(positionSchema, pageSchema);
@@ -21,7 +26,14 @@ const VideoDocument = makeInterface(positionSchema, pageSchema);
@observer
export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
private _reactionDisposer?: IReactionDisposer;
+ private _youtubeReactionDisposer?: IReactionDisposer;
+ private _youtubePlayer: any = undefined;
private _videoRef: HTMLVideoElement | null = null;
+ private _youtubeIframeId: number = -1;
+ private _youtubeContentCreated = false;
+ static _youtubeIframeCounter: number = 0;
+ @observable _forceCreateYouTubeIFrame = false;
+ @observable static _showControls: boolean;
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
@observable public Playing: boolean = false;
@@ -42,37 +54,58 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
}
- @action public Play() {
+ @action public Play = (update: boolean = true) => {
this.Playing = true;
- if (this.player) this.player.play();
- if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 500);
+ update && this.player && this.player.play();
+ update && this._youtubePlayer && this._youtubePlayer.playVideo();
+ !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 500));
+ this.updateTimecode();
+ }
+
+ @action public Seek(time: number) {
+ this._youtubePlayer && this._youtubePlayer.seekTo(Math.round(time), true);
}
- @action public Pause() {
+ @action public Pause = (update: boolean = true) => {
this.Playing = false;
- if (this.player) this.player.pause();
- if (this._playTimer) {
- clearInterval(this._playTimer);
- this._playTimer = undefined;
- }
+ update && this.player && this.player.pause();
+ update && this._youtubePlayer && this._youtubePlayer.pauseVideo();
+ this._playTimer && clearInterval(this._playTimer);
+ this._playTimer = undefined;
+ this.updateTimecode();
}
@action public FullScreen() {
this._fullScreen = true;
this.player && this.player.requestFullscreen();
+ this._youtubePlayer && this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab");
}
@action
updateTimecode = () => {
this.player && (this.props.Document.curPage = this.player.currentTime);
+ this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime());
}
componentDidMount() {
if (this.props.setVideoBox) this.props.setVideoBox(this);
+
+ if (this.youtubeVideoId) {
+ let youtubeaspect = 400 / 315;
+ var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+ var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
+ if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
+ if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
+ this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect;
+ this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect;
+ }
+ }
}
+
componentWillUnmount() {
this.Pause();
- if (this._reactionDisposer) this._reactionDisposer();
+ this._reactionDisposer && this._reactionDisposer();
+ this._youtubeReactionDisposer && this._youtubeReactionDisposer();
}
@action
@@ -87,59 +120,136 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
}
- getMp4ForVideo(videoId: string = "JN5beCVArMs") {
- return new Promise(async (resolve, reject) => {
- const videoInfoRequestConfig = {
- headers: {
- connection: 'keep-alive',
- "user-agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/46.0',
+ public static async convertDataUri(imageUri: string, returnedFilename: string) {
+ try {
+ let posting = DocServer.prepend(RouteStore.dataUriToImage);
+ const returnedUri = await rp.post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename
},
- };
- try {
- let responseSchema: any = {};
- const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig);
- const dataHtml = videoInfoResponse;
- const start = dataHtml.indexOf('ytplayer.config = ') + 18;
- const end = dataHtml.indexOf(';ytplayer.load');
- const subString = dataHtml.substring(start, end);
- const subJson = JSON.parse(subString);
- const stringSub = subJson.args.player_response;
- const stringSubJson = JSON.parse(stringSub);
- const adaptiveFormats = stringSubJson.streamingData.adaptiveFormats;
- const videoDetails = stringSubJson.videoDetails;
- responseSchema.adaptiveFormats = adaptiveFormats;
- responseSchema.videoDetails = videoDetails;
- resolve(responseSchema);
- }
- catch (err) {
- console.log(`
- --- Youtube ---
- Function: getMp4ForVideo
- Error: `, err);
- reject(err);
- }
- });
- }
- onPointerDown = (e: React.PointerEvent) => {
- e.preventDefault();
- e.stopPropagation();
- }
+ json: true,
+ });
+ return returnedUri;
- render() {
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ specificContextMenu = (e: React.MouseEvent): void => {
let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ if (field) {
+ let url = field.url.href;
+ let subitems: ContextMenuProps[] = [];
+ subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
+ subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" });
+ let width = NumCast(this.props.Document.width);
+ let height = NumCast(this.props.Document.height);
+ subitems.push({
+ description: "Take Snapshot", event: async () => {
+ var canvas = document.createElement('canvas');
+ canvas.width = 640;
+ canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+ var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+ if (ctx) {
+ ctx.rect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = "blue";
+ ctx.fill();
+ this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
+ }
- // this.getMp4ForVideo().then((mp4) => {
- // console.log(mp4);
- // }).catch(e => {
- // console.log("")
- // });
- // //
+ //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 = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
+ VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+ if (returnedFilename) {
+ let url = DocServer.prepend(returnedFilename);
+ let imageSummary = Docs.Create.ImageDocument(url, {
+ x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
+ width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+ });
+ this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
+ DocUtils.MakeLink(imageSummary, this.props.Document);
+ }
+ });
+ },
+ icon: "expand-arrows-alt"
+ });
+ ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems });
+ }
+ }
- let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : "");
+ @computed get content() {
+ let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ 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} onPointerDown={this.onPointerDown}>
+ <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
+ onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()}>
<source src={field.url.href} type="video/mp4" />
Not supported.
</video>;
}
-} \ No newline at end of file
+
+ @computed get youtubeVideoId() {
+ let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : "";
+ }
+
+ @action youtubeIframeLoaded = (e: any) => {
+ if (!this._youtubeContentCreated) {
+ this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame;
+ return;
+ }
+ else this._youtubeContentCreated = false;
+
+ let iframe = e.target;
+ let started = true;
+ let 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);
+ });
+ let onYoutubePlayerReady = (event: any) => {
+ this._reactionDisposer && this._reactionDisposer();
+ this._youtubeReactionDisposer && this._youtubeReactionDisposer();
+ this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 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";
+ }, { fireImmediately: true });
+ };
+ this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
+ events: {
+ 'onReady': onYoutubePlayerReady,
+ 'onStateChange': onYoutubePlayerStateChange,
+ }
+ });
+
+ }
+
+ @computed get youtubeContent() {
+ this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
+ this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
+ let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
+ let start = untracked(() => Math.round(NumCast(this.props.Document.curPage)));
+ return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
+ onLoad={this.youtubeIframeLoaded} className={`${style}`} width="640" height="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>;
+ }
+
+ render() {
+ return <div style={{ pointerEvents: "all", width: "100%", height: "100%" }} onContextMenu={this.specificContextMenu}>
+ {this.youtubeVideoId ? this.youtubeContent : this.content}
+ </div>;
+ }
+}
+
+VideoBox._showControls = true; \ No newline at end of file