aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx20
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx6
-rw-r--r--src/client/views/nodes/AudioBox.tsx7
-rw-r--r--src/client/views/nodes/VideoBox.scss90
-rw-r--r--src/client/views/nodes/VideoBox.tsx172
5 files changed, 231 insertions, 64 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 25f2e0b25..dca5089f4 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -4,7 +4,7 @@ import {
computed,
IReactionDisposer,
observable,
- reaction,
+ reaction
} from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
@@ -19,35 +19,29 @@ import {
formatTime,
OmitKeys,
returnFalse,
- returnOne,
- setupMoveUpEvents,
- StopEvent,
- returnTrue,
- smoothScroll,
- smoothScrollHorizontal,
+ returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent
} from "../../../Utils";
import { Docs } from "../../documents/Documents";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
+import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { AudioWaveform } from "../AudioWaveform";
import { CollectionSubView } from "../collections/CollectionSubView";
+import { Colors } from "../global/globalEnums";
import { LightboxView } from "../LightboxView";
import {
DocAfterFocusFunc,
DocFocusFunc,
DocumentView,
- DocumentViewProps,
+ DocumentViewProps
} from "../nodes/DocumentView";
import { LabelBox } from "../nodes/LabelBox";
import "./CollectionStackedTimeline.scss";
-import { Colors } from "../global/globalEnums";
-import { DocumentManager } from "../../util/DocumentManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { DragManager } from "../../util/DragManager";
-import { faBreadSlice } from "@fortawesome/free-solid-svg-icons";
type PanZoomDocument = makeInterface<[]>;
const PanZoomDocument = makeInterface();
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 4198a7979..77eed8bfc 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -3,13 +3,14 @@ import { Tooltip } from '@material-ui/core';
import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, HeightSym, Opt, WidthSym, DataSym } from '../../../../fields/Doc';
+import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { makeInterface } from '../../../../fields/Schema';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnTrue, returnFalse, OmitKeys, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
import { DocUtils } from '../../../documents/Documents';
+import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { Colors, Shadows } from '../../global/globalEnums';
@@ -20,7 +21,6 @@ import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
import { StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
import { CollectionViewType } from '../CollectionView';
-import { DocumentManager } from "../../../util/DocumentManager";
import "./CollectionLinearView.scss";
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c33a74325..b9046b61e 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -421,6 +421,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
setVolume = (volume: number) => {
if (this._ele) {
+ if (this._muted) {
+ this._muted = false;
+ }
this._volume = volume;
this._ele.volume = volume;
}
@@ -503,10 +506,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div className="audiobox-button"
title={this._muted ? "unmute" : "mute"}
onPointerDown={this.toggleMute}>
- <FontAwesomeIcon icon={this._muted ? "volume-mute" : "volume-up"} />
+ <FontAwesomeIcon icon={this._muted || this._volume == 0 ? "volume-mute" : "volume-up"} />
</div>
<input type="range" step="0.1" min="0" max="1" value={this._muted ? 0 : this._volume}
- className="toolbar-slider" id="volume-slider"
+ className="toolbar-slider volume"
onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.setVolume(Number(e.target.value)) }}
/>
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 8a3261c7d..35f5a7e65 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -78,10 +78,49 @@
.videoBox-ui {
position: absolute;
flex-direction: row;
- right: 5px;
- top: 5px;
- display: none;
- background-color: rgba($dark-gray, 0.6);
+ align-items: center;
+ justify-content: center;
+ display: flex;
+ visibility: none;
+ opacity: 0;
+ background-color: $dark-gray;
+ color: white;
+ border-radius: 100px;
+ left: 50%;
+ transform: translate(-50%, -150%);
+ transition: top 0.5s, opacity 0.2s, visibility 0s;
+ height: 5%;
+ width: 80%;
+
+ .timecode-controls {
+ display: flex;
+ flex-direction: row;
+ margin: 0 5px;
+
+ .timecode {
+ margin: 0 5px;
+ }
+ }
+
+ .videobox-button {
+ margin: 5px;
+ cursor: pointer;
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background: $dark-gray;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background: $black;
+ }
+
+ svg {
+ width: 20px;
+ }
+ }
}
.videoBox-time,
.videoBox-snapshot,
@@ -101,6 +140,47 @@
.videoBox:hover {
.videoBox-ui {
- display: flex;
+ visibility: visible;
+ opacity: 1;
+ z-index: 10000;
}
}
+
+video::-webkit-media-controls {
+ display: none !important;
+}
+
+input[type="range"] {
+ -webkit-appearance: none;
+ background: none;
+ margin: 5px;
+}
+
+input[type="range"]:focus {
+ outline: none;
+}
+
+input[type="range"]::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 6px;
+ cursor: pointer;
+ box-shadow: 0;
+ background: $light-gray;
+ border-radius: 3px;
+}
+
+input[type="range"]::-webkit-slider-thumb {
+ box-shadow: 0;
+ border: 0;
+ height: 10px;
+ width: 10px;
+ border-radius: 10px;
+ background: $medium-blue;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -2px;
+}
+
+.toolbar-slider.volume {
+ width: 50px;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d56363348..bd9423d74 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -59,6 +59,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
private _disposers: { [name: string]: IReactionDisposer } = {};
private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null;
+ private _contentRef: HTMLDivElement | null = null;
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
private _audioPlayer: HTMLAudioElement | null = null;
@@ -77,6 +78,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable _fullScreen = false;
@observable _playing = false;
@observable _finished: boolean = false;
+ @observable _volume: number = 1;
+ @observable _muted: boolean = false;
@computed get links() { return DocListCast(this.dataDoc.links); }
@computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); }
@@ -168,8 +171,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
@action public FullScreen = () => {
- this._fullScreen = true;
- this.player && this.player.requestFullscreen();
+ if (document.fullscreenElement == this._contentRef) {
+ this._fullScreen = false;
+ this.player && this._contentRef && document.exitFullscreen();
+ }
+ else {
+ this._fullScreen = true;
+ this.player && this._contentRef && this._contentRef.requestFullscreen();
+ }
try {
this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add");
} catch (e) {
@@ -269,13 +278,21 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (vref) {
this._videoRef!.ontimeupdate = this.updateTimecode;
// @ts-ignore
- vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
+ // vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
this._disposers.reactionDisposer?.();
this._disposers.reactionDisposer = reaction(() => (this.layoutDoc._currentTimecode || 0),
time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
}
}
+ @action
+ setContentRef = (cref: HTMLDivElement | null) => {
+ this._contentRef = cref;
+ if (cref) {
+ cref.onfullscreenchange = action((e) => this._fullScreen = (document.fullscreenElement == cref));
+ }
+ }
+
specificContextMenu = (e: React.MouseEvent): void => {
const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
if (field) {
@@ -292,10 +309,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
subitems.push({ description: (this.layoutDoc.dontAutoFollowLinks ? "" : "Don't") + " follow links when encountered", event: () => this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks, icon: "expand-arrows-alt" });
subitems.push({ description: (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") + " play when link is selected", event: () => this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks, 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" });
+ // 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" });
subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
}
@@ -309,7 +326,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
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={classname}>
+ <div className={classname} ref={this.setContentRef}>
+ {this.uIButtons}
<video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
onCanPlay={this.videoLoad}
controls={VideoBox._nativeControls}
@@ -489,6 +507,25 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}));
}
+ @action
+ setVolume = (volume: number) => {
+ if (this.player) {
+ this._volume = volume;
+ this.player.volume = volume;
+ }
+ }
+
+ @action
+ toggleMute = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ console.log("click");
+ if (this.player) {
+ console.log("audio exists");
+ this._muted = !this._muted;
+ this.player.muted = this._muted;
+ }
+ }
+
playLink = (doc: Doc) => {
const startTime = Math.max(0, (this._stackedTimeline.current?.anchorStart(doc) || 0));
const endTime = this.timeline?.anchorEnd(doc);
@@ -534,41 +571,95 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get uIButtons() {
const curTime = (this.layoutDoc._currentTimecode || 0) - (this.timeline?.clipStart || 0);
- const nonNativeControls = [
- <Tooltip title={<div className="dash-tooltip">{"playback"}</div>} key="play" placement="bottom">
- <div className="videoBox-play" onPointerDown={this.onPlayDown} >
- <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
- </div>
- </Tooltip>,
- <Tooltip title={<div className="dash-tooltip">{"timecode"}</div>} key="time" placement="bottom">
- <div className="videoBox-time" onPointerDown={this.onResetDown} >
- <span>{formatTime(curTime)}</span>
- <span style={{ fontSize: 8 }}>{" " + Math.floor((curTime - Math.trunc(curTime)) * 100).toString().padStart(2, "0")}</span>
- </div>
- </Tooltip>,
- <Tooltip title={<div className="dash-tooltip">{"view full screen"}</div>} key="full" placement="bottom">
- <div className="videoBox-full" onPointerDown={this.FullScreen}>
- <FontAwesomeIcon icon="expand" size="lg" />
- </div>
- </Tooltip>];
- return <div className="videoBox-ui">
- {[...(VideoBox._nativeControls ? [] : nonNativeControls),
- <Tooltip title={<div className="dash-tooltip">{"snapshot current frame"}</div>} key="snap" placement="bottom">
- <div className="videoBox-snapshot" onPointerDown={this.onSnapshotDown} >
- <FontAwesomeIcon icon="camera" size="lg" />
+ // const nonNativeControls = [
+ // <Tooltip title={<div className="dash-tooltip">{"playback"}</div>} key="play" placement="bottom">
+ // <div className="videoBox-play" onPointerDown={this.onPlayDown} >
+ // <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
+ // </div>
+ // </Tooltip>,
+ // <Tooltip title={<div className="dash-tooltip">{"timecode"}</div>} key="time" placement="bottom">
+ // <div className="videoBox-time" onPointerDown={this.onResetDown} >
+ // <span>{formatTime(curTime)}</span>
+ // <span style={{ fontSize: 8 }}>{" " + Math.floor((curTime - Math.trunc(curTime)) * 100).toString().padStart(2, "0")}</span>
+ // </div>
+ // </Tooltip>,
+ // <Tooltip title={<div className="dash-tooltip">{"view full screen"}</div>} key="full" placement="bottom">
+ // <div className="videoBox-full" onPointerDown={this.FullScreen}>
+ // <FontAwesomeIcon icon="expand" size="lg" />
+ // </div>
+ // </Tooltip>];
+ // return <div className="videoBox-ui">
+ // {[...(VideoBox._nativeControls ? [] : nonNativeControls),
+ // <Tooltip title={<div className="dash-tooltip">{"snapshot current frame"}</div>} key="snap" placement="bottom">
+ // <div className="videoBox-snapshot" onPointerDown={this.onSnapshotDown} >
+ // <FontAwesomeIcon icon="camera" size="lg" />
+ // </div>
+ // </Tooltip>,
+ // <Tooltip title={<div className="dash-tooltip">{"show annotation timeline"}</div>} key="timeline" placement="bottom">
+ // <div className="videoBox-timelineButton" onPointerDown={this.onTimelineHdlDown}>
+ // <FontAwesomeIcon icon="eye" size="lg" />
+ // </div>
+ // </Tooltip>,
+ // <Tooltip title={<div className="dash-tooltip">{this.timeline?.IsTrimming !== TrimScope.None ? "finish trimming" : "start trim"}</div>} key="trim" placement="bottom">
+ // <div className="videoBox-timelineButton" onPointerDown={this.onClipPointerDown}>
+ // <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"} size="lg" />
+ // </div>
+ // </Tooltip>,]}
+ // </div>;
+ return <div className="videoBox-ui" style={{ top: `calc(100% - 10px)` }}>
+ <div className="videobox-button"
+ title={this._playing ? "play" : "pause"}
+ onPointerDown={this.onPlayDown}>
+ <FontAwesomeIcon icon={this._playing ? "pause" : "play"} />
+ </div>
+
+ {this.timeline && <div className="timecode-controls">
+ <div className="timecode-current">
+ {formatTime(curTime)}
</div>
- </Tooltip>,
- <Tooltip title={<div className="dash-tooltip">{"show annotation timeline"}</div>} key="timeline" placement="bottom">
- <div className="videoBox-timelineButton" onPointerDown={this.onTimelineHdlDown}>
- <FontAwesomeIcon icon="eye" size="lg" />
+
+ <div className="timeline-slider">
+ <input type="range" step="0.1" min={this.timeline.clipStart} max={this.timeline.clipEnd} value={curTime}
+ className="toolbar-slider time-progress"
+ onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.setPlayheadTime(Number(e.target.value)) }}
+ />
</div>
- </Tooltip>,
- <Tooltip title={<div className="dash-tooltip">{this.timeline?.IsTrimming !== TrimScope.None ? "finish trimming" : "start trim"}</div>} key="trim" placement="bottom">
- <div className="videoBox-timelineButton" onPointerDown={this.onClipPointerDown}>
- <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"} size="lg" />
+
+ <div className="timecode-end">
+ {formatTime(this.timeline.clipDuration)}
</div>
- </Tooltip>,]}
- </div>;
+ </div>}
+
+ <div className="videobox-button"
+ title={"full screen"}
+ onPointerDown={this.onFullDown}>
+ <FontAwesomeIcon icon="expand" />
+ </div>
+
+ {!this._fullScreen && <div className="videobox-button"
+ title={"show timeline"}
+ onPointerDown={this.onTimelineHdlDown}>
+ <FontAwesomeIcon icon="eye" />
+ </div>}
+
+ {!this._fullScreen && <div className="videobox-button"
+ title={this.timeline?.IsTrimming !== TrimScope.None ? "finish trimming" : "start trim"}
+ onPointerDown={this.onClipPointerDown}>
+ <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"} />
+ </div>}
+
+ <div className="videobox-button"
+ title={this._muted ? "unmute" : "mute"}
+ onPointerDown={this.toggleMute}>
+ <FontAwesomeIcon icon={this._muted || this._volume == 0 ? "volume-mute" : "volume-up"} />
+ </div>
+ <input type="range" step="0.1" min="0" max="1" value={this._muted ? 0 : this._volume}
+ className="toolbar-slider volume"
+ onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.setVolume(Number(e.target.value)) }}
+ />
+ </div>
}
@computed get renderTimeline() {
return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
@@ -653,7 +744,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
mainCont={this._mainCont.current}
/>}
{this.renderTimeline}
- {this.uIButtons}
</div>
</div >);
}