aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.scss84
-rw-r--r--src/client/views/nodes/VideoBox.tsx86
3 files changed, 109 insertions, 63 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9c9b19122..f0011d65f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -190,7 +190,7 @@ export class MainView extends React.Component {
fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll,
fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines,
- fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeDown, fa.faSquareRootAlt]);
+ fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeDown, fa.faSquareRootAlt, fa.faVolumeMute]);
this.initAuthenticationRouters();
}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index d4cddd65e..c7108d290 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -80,55 +80,51 @@
// pointer-events: all;
// }
+.videoBox-ui-wrapper {
+ width: 0;
+ height: 0;
+}
+
.videoBox-ui {
position: absolute;
flex-direction: row;
align-items: center;
justify-content: center;
display: flex;
- width: 100%;
- visibility: none;
- opacity: 0;
background-color: $dark-gray;
color: white;
border-radius: 100px;
- transform-origin: bottom left;
- left: 0;
- bottom: 0;
-
- transition: top 0.5s, width 0.5s, opacity 0.2s, visibility 0s;
- height: 24px;
- padding: 0 20px;
+ z-index: 2001;
+ min-width: 300px;
+
+ transition: top 0.5s, width 0.5s, opacity 0.2s, visibility 0.2s;
+ height: 50px;
+ padding: 0 10px 0 7px;
.timecode-controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
- margin: 0 5px;
+ margin: 0 2px;
flex-grow: 2;
- font-size: 12px;
-
- .timecode {
- margin: 0 5px;
- }
+ font-size: 14px;
.timeline-slider {
- margin: 0 10px 0 10px;
+ margin: 5px;
flex-grow: 2;
}
}
- .toolbar-slider.volume,
- .toolbar-slider.zoom {
- width: 100px;
+ .toolbar-slider.volume, .toolbar-slider.zoom {
+ width: 50px;
}
.videobox-button {
- margin: 5px;
+ margin: 2px;
cursor: pointer;
- width: 24px;
- height: 24px;
+ width: 30px;
+ height: 30px;
border-radius: 50%;
background: $dark-gray;
display: flex;
@@ -140,8 +136,8 @@
}
svg {
- width: 18px;
- height: 18px;
+ width: 17px;
+ height: 17px;
}
}
}
@@ -163,28 +159,17 @@
}
}
-.videoBox:hover {
- .videoBox-ui {
- visibility: visible;
- opacity: 1;
- z-index: 10000;
- }
-}
-
-.videoBox-content-fullScreen,
-.videoBox-content-fullScreen-interactive {
+.videoBox-content-fullScreen, .videoBox-content-fullScreen-interactive {
display: flex;
justify-content: center;
- align-items: center;
-
- &:hover {
- .videoBox-ui {
- opacity: 0;
- }
- }
+ align-items: flex-end;
- .videoBox-ui:hover {
- opacity: 1;
+ .videoBox-ui {
+ left: 50%;
+ top: 90%;
+ transform: translate(-50%, -50%);
+ width: 80%;
+ transition: top 0s, width 0s, opacity 0.3s, visibility 0.3s;
}
}
@@ -195,7 +180,6 @@ video::-webkit-media-controls {
input[type="range"] {
-webkit-appearance: none;
background: none;
- margin: 10px;
}
input[type="range"]:focus {
@@ -204,19 +188,19 @@ input[type="range"]:focus {
input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
- height: 18px;
+ height: 10px;
cursor: pointer;
box-shadow: 0;
background: $light-gray;
- border-radius: 18px;
+ border-radius: 10px;
}
input[type="range"]::-webkit-slider-thumb {
box-shadow: 0;
border: 0;
- height: 20px;
- width: 20px;
- border-radius: 20px;
+ height: 12px;
+ width: 12px;
+ border-radius: 10px;
background: $medium-blue;
cursor: pointer;
-webkit-appearance: none;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0017e5f5e..5fe1ad5ed 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
import { observer } from "mobx-react";
import { basename } from "path";
import * as rp from 'request-promise';
-import { Doc, DocListCast, HeightSym, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { InkTool } from "../../../fields/InkField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { AudioField, ImageField, VideoField } from "../../../fields/URLField";
@@ -84,6 +84,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
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
+
+ public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
+
@observable _stackedTimeline: any; // CollectionStackedTimeline ref
@observable static _nativeControls: boolean; // default html controls
@observable _marqueeing: number[] | undefined; // coords for marquee selection
@@ -97,6 +101,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@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 _controlsVisible: boolean = true;
@computed get links() { return DocListCast(this.dataDoc.links); }
@computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); } // current percent of video relative to VideoBox height
@@ -214,7 +220,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
else {
this._fullScreen = true;
this.player && this._contentRef && this._contentRef.requestFullscreen();
-
}
try {
this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add");
@@ -223,6 +228,35 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
}
+ // fades out controls in fullscreen after mouse stops moving
+ @action controlsFade = (e: PointerEvent) => {
+ e.stopPropagation();
+ this._controlsVisible = true;
+ clearTimeout(this._controlsFadeTimer);
+ this._controlsFadeTimer = setTimeout(action(() => this._controlsVisible = false), 3000);
+ }
+
+
+ // drag controls around window in fulls screen
+ @action controlsDrag = (e: React.PointerEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const eleStyle = getComputedStyle(e.target as Element);
+ this._controlsTransform = { X: parseInt(eleStyle.left), Y: parseInt(eleStyle.top) };
+
+ setupMoveUpEvents(e.target,
+ e,
+ action((e, down, delta) => {
+ if (this._controlsTransform) {
+ this._controlsTransform.X = Math.max(0, Math.min(delta[0] + this._controlsTransform.X, window.innerWidth));
+ this._controlsTransform.Y = Math.max(0, Math.min(delta[1] + this._controlsTransform.Y, window.innerHeight));
+ }
+ return false;
+ }),
+ emptyFunction,
+ emptyFunction)
+ }
+
// creates and links snapshot photo of current video frame
@action public Snapshot = (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => {
@@ -344,7 +378,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
setContentRef = (cref: HTMLDivElement | null) => {
this._contentRef = cref;
if (cref) {
- cref.onfullscreenchange = action((e) => this._fullScreen = (document.fullscreenElement === cref));
+ cref.onfullscreenchange = action((e) => {
+ this._fullScreen = (document.fullscreenElement === cref);
+ if (this._fullScreen) {
+ document.addEventListener('pointermove', this.controlsFade);
+ this._controlsVisible = true;
+ this._controlsFadeTimer = setTimeout(action(() => this._controlsVisible = false), 3000)
+ console.log("added");
+ }
+ else {
+ document.removeEventListener('pointermove', this.controlsFade);
+ console.log("removed");
+ }
+ });
}
}
@@ -385,16 +431,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
const classname = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div key="loading">Loading</div> :
- <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: "multiply" }}>
+ <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: "multiply", cursor: this._fullScreen && !this._controlsVisible ? 'none' : 'pointer' }}>
<div className={classname} ref={this.setContentRef} onPointerDown={(e) => this._fullScreen && e.stopPropagation()}>
- {this.uIButtons}
+ {this._fullScreen && <div className="videoBox-ui" onPointerDown={this.controlsDrag}
+ style={{ left: this._controlsTransform && this._controlsTransform.X, top: this._controlsTransform && this._controlsTransform.Y, visibility: this._controlsVisible ? 'visible' : 'hidden', opacity: this._controlsVisible ? 1 : 0 }}>
+ {this.UIButtons}
+ </div>}
<video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} style={this._fullScreen ? this.fullScreenSize() : {}}
onCanPlay={this.videoLoad}
controls={VideoBox._nativeControls}
onPlay={() => this.Play()}
onSeeked={this.updateTimecode}
onPause={() => this.Pause()}
- onClick={e => e.preventDefault()}>
+ onClick={this._fullScreen ? () => this.playing() ? this.Pause() : this.Play() : e => e.preventDefault()}>
<source src={field.url.href} type="video/mp4" />
Not supported.
</video>
@@ -697,12 +746,25 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// renders video controls
- @computed get uIButtons() {
+ componentUI = (boundsLeft: number, boundsTop: number) => {
+ const bounds = this.props.docViewPath().lastElement().getBounds();
+ const left = bounds?.left || 0;
+ const right = bounds?.right || 0;
+ const top = bounds?.top || 0;
+ const height = (bounds?.bottom || 0) - top;
+ const yPos = height * this.heightPercent / 100 - 60 + top;
+ const width = Math.max(right - left, 300);
+ const overflow = (width - right + left) / 2;
+ return this._fullScreen ? null : <div className="videoBox-ui-wrapper" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
+ <div className="videoBox-ui" style={{ left: left - overflow, top: yPos, width: width }}>
+ {this.UIButtons}
+ </div>
+ </div>
+ }
+
+ @computed get UIButtons() {
const curTime = NumCast(this.layoutDoc._currentTimecode) - (this.timeline?.clipStart || 0);
- const scaling = (this.props.scaling?.() || 1);
- return <div className="videoBox-ui" style={{
- transform: `scale(${1 / scaling})`, width: `${100 * scaling}%`, bottom: 20 / scaling
- }}>
+ return <>
<div className="videobox-button"
title={this._playing ? "play" : "pause"}
onPointerDown={this.onPlayDown}>
@@ -770,7 +832,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.zoom(Number(e.target.value)); }}
/>
</>}
- </div>;
+ </>
}
// renders CollectionStackedTimeline