diff options
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/collectionLinear/CollectionLinearView.tsx | 14 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 29 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.scss | 65 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 120 |
5 files changed, 134 insertions, 96 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index dca5089f4..793e01822 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -72,6 +72,8 @@ export class CollectionStackedTimeline extends CollectionSubView< CollectionStackedTimelineProps >(PanZoomDocument) { @observable static SelectingRegion: CollectionStackedTimeline | undefined; + @observable public static CurrentlyPlaying: Doc[]; + static RangeScript: ScriptField; static LabelScript: ScriptField; static RangePlayScript: ScriptField; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 77eed8bfc..70c8c9436 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -19,6 +19,7 @@ import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { DocumentView } from '../../nodes/DocumentView'; import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; import { StyleProp } from '../../StyleProvider'; +import { CollectionStackedTimeline } from '../CollectionStackedTimeline'; import { CollectionSubView } from '../CollectionSubView'; import { CollectionViewType } from '../CollectionView'; import "./CollectionLinearView.scss"; @@ -231,23 +232,16 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { </Tooltip> </span> : null} - {AudioBox.CurrentlyPlaying && AudioBox.CurrentlyPlaying.length != 0 && StrCast(this.layoutDoc.title) === "docked buttons" ? <span className="bottomPopup-background"> + {CollectionStackedTimeline.CurrentlyPlaying && CollectionStackedTimeline.CurrentlyPlaying.length != 0 && StrCast(this.layoutDoc.title) === "docked buttons" ? <span className="bottomPopup-background"> <span className="bottomPopup-text"> - Currently listening to: {AudioBox.CurrentlyPlaying.map((clip, i) => + Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => <span className="audio-title" onPointerDown={() => { DocumentManager.Instance.jumpToDocument(clip, true); - }}>{clip.title + (i == AudioBox.CurrentlyPlaying.length - 1 ? "" : ",")} </span> + }}>{clip.title + (i == CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")} </span> )} </span> </span> : null} - {/* THIS RENDERS AUDIOBOX FOR EACH CLIP */} - {/* {AudioBox.CurrentlyPlaying && AudioBox.CurrentlyPlaying.length != 0 && StrCast(this.layoutDoc.title) === "docked buttons" ? <div> - <div className="currently-playing"> - {AudioBox.CurrentlyPlaying.map((clip) => this.getDisplayDoc(clip, true))} - </div> - </div> : null} */} - </div> </div>; } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index b9046b61e..eb7b9a773 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -37,7 +37,6 @@ enum media_state { } @observer export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, AudioDocument>(AudioDocument) { - @observable public static CurrentlyPlaying: Doc[]; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } public static SetScrubTime = action((timeInMillisFrom1970: number) => { @@ -47,7 +46,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp public static Enabled = false; static topControlsHeight = 30; // width of playhead static bottomControlsHeight = 20; // height of timeline in percent of height of audioBox. - static zoomInterval = 0.1; @observable static _scrubTime = 0; _dropDisposer?: DragManager.DragDropDisposer; @@ -182,19 +180,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp // removes from currently playing display @action removeCurrentlyPlaying = () => { - if (AudioBox.CurrentlyPlaying) { - const index = AudioBox.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc); - index !== -1 && AudioBox.CurrentlyPlaying.splice(index, 1); + if (CollectionStackedTimeline.CurrentlyPlaying) { + const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc); + index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1); } } @action addCurrentlyPlaying = () => { - if (!AudioBox.CurrentlyPlaying) { - AudioBox.CurrentlyPlaying = []; + if (!CollectionStackedTimeline.CurrentlyPlaying) { + CollectionStackedTimeline.CurrentlyPlaying = []; } - if (AudioBox.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc) == -1) { - AudioBox.CurrentlyPlaying.push(this.layoutDoc.doc as Doc); + if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc) == -1) { + CollectionStackedTimeline.CurrentlyPlaying.push(this.layoutDoc.doc as Doc); } } @@ -421,17 +419,16 @@ 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; + if (this._muted) { + this.toggleMute(); + } } } @action - toggleMute = (e: React.PointerEvent) => { - e.stopPropagation(); + toggleMute = () => { if (this._ele) { this._muted = !this._muted; this._ele.muted = this._muted; @@ -505,8 +502,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp <div className="controls-right"> <div className="audiobox-button" title={this._muted ? "unmute" : "mute"} - onPointerDown={this.toggleMute}> - <FontAwesomeIcon icon={this._muted || this._volume == 0 ? "volume-mute" : "volume-up"} /> + onPointerDown={(e) => { e.stopPropagation(); this.toggleMute(); }}> + <FontAwesomeIcon icon={this._muted ? "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" diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 35f5a7e65..3cf10a033 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -86,27 +86,42 @@ background-color: $dark-gray; color: white; border-radius: 100px; + top: calc(100% - 20px); left: 50%; - transform: translate(-50%, -150%); - transition: top 0.5s, opacity 0.2s, visibility 0s; - height: 5%; - width: 80%; + transform: translate(-50%, -100%); + + transition: top 0.5s, width 0.5s, opacity 0.2s, visibility 0s; + height: 120px; + padding: 0 20px; .timecode-controls { display: flex; flex-direction: row; + align-items: center; + justify-content: center; margin: 0 5px; + flex-grow: 2; + font-size: 32px; .timecode { margin: 0 5px; } + + .timeline-slider { + margin: 0 20px 0 20px; + flex-grow: 2; + } + } + + .toolbar-slider.volume, .toolbar-slider.zoom { + width: 100px; } .videobox-button { margin: 5px; cursor: pointer; - width: 30px; - height: 30px; + width: 70px; + height: 70px; border-radius: 50%; background: $dark-gray; display: flex; @@ -118,10 +133,12 @@ } svg { - width: 20px; + width: 40px; + height: 40px; } } } + .videoBox-time, .videoBox-snapshot, .videoBox-timelineButton, @@ -146,6 +163,22 @@ } } +.videoBox-content-fullScreen, .videoBox-content-fullScreen-interactive { + display: flex; + justify-content: center; + align-items: center; + + &:hover { + .videoBox-ui { + opacity: 0; + } + } + + .videoBox-ui:hover { + opacity: 1; + } +} + video::-webkit-media-controls { display: none !important; } @@ -153,7 +186,7 @@ video::-webkit-media-controls { input[type="range"] { -webkit-appearance: none; background: none; - margin: 5px; + margin: 10px; } input[type="range"]:focus { @@ -162,25 +195,21 @@ input[type="range"]:focus { input[type="range"]::-webkit-slider-runnable-track { width: 100%; - height: 6px; + height: 20px; cursor: pointer; box-shadow: 0; background: $light-gray; - border-radius: 3px; + border-radius: 20px; } input[type="range"]::-webkit-slider-thumb { box-shadow: 0; border: 0; - height: 10px; - width: 10px; - border-radius: 10px; + height: 26px; + width: 26px; + border-radius: 20px; background: $medium-blue; cursor: pointer; -webkit-appearance: none; - margin-top: -2px; -} - -.toolbar-slider.volume { - width: 50px; + margin-top: -3px; }
\ No newline at end of file diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index bd9423d74..c8e0cdb66 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -117,6 +117,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } componentWillUnmount() { + this.removeCurrentlyPlaying(); this.Pause(); Object.keys(this._disposers).forEach(d => this._disposers[d]?.()); } @@ -155,6 +156,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @action public Pause = (update: boolean = true) => { this._playing = false; + this.removeCurrentlyPlaying(); try { update && this.player?.pause(); update && this._audioPlayer?.pause(); @@ -178,6 +180,7 @@ 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"); @@ -326,9 +329,9 @@ 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} ref={this.setContentRef}> + <div className={classname} ref={this.setContentRef} onPointerDown={(e) => this._fullScreen && e.stopPropagation()}> {this.uIButtons} - <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} + <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} style={this._fullScreen ? this.fullScreenSize() : {}} onCanPlay={this.videoLoad} controls={VideoBox._nativeControls} onPlay={() => this.Play()} @@ -436,6 +439,24 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp (e: PointerEvent) => this.layoutDoc._currentTimecode = 0); } + @action + removeCurrentlyPlaying = () => { + if (CollectionStackedTimeline.CurrentlyPlaying) { + const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc); + index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1); + } + } + + @action + addCurrentlyPlaying = () => { + if (!CollectionStackedTimeline.CurrentlyPlaying) { + CollectionStackedTimeline.CurrentlyPlaying = []; + } + if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc) == -1) { + CollectionStackedTimeline.CurrentlyPlaying.push(this.layoutDoc.doc as Doc); + } + } + @computed get youtubeContent() { this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; @@ -472,9 +493,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this.player.play(); this._audioPlayer?.play(); this._playing = true; + this.addCurrentlyPlaying(); this._playRegionTimer = setTimeout( () => { if (fullPlay) this._finished = true; + else this.removeCurrentlyPlaying(); this.Pause(); }, this._playRegionDuration * 1000); } else { @@ -512,20 +535,33 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp if (this.player) { this._volume = volume; this.player.volume = volume; + if (this._muted) { + this.toggleMute(); + } } } @action - toggleMute = (e: React.PointerEvent) => { - e.stopPropagation(); - console.log("click"); + toggleMute = () => { if (this.player) { - console.log("audio exists"); this._muted = !this._muted; this.player.muted = this._muted; } } + fullScreenSize() { + if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) { + return { height: "100%" } + } + else { + return { width: "100%" } + } + } + + zoom = (zoom: number) => { + this.timeline?.setZoom(zoom); + } + playLink = (doc: Doc) => { const startTime = Math.max(0, (this._stackedTimeline.current?.anchorStart(doc) || 0)); const endTime = this.timeline?.anchorEnd(doc); @@ -571,42 +607,7 @@ 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" /> - // </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)` }}> + return <div className="videoBox-ui" style={this._fullScreen || this.heightPercent == 100 ? { fontSize: "40px", minWidth: "80%" } : {}}> <div className="videobox-button" title={this._playing ? "play" : "pause"} onPointerDown={this.onPlayDown}> @@ -618,13 +619,16 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp {formatTime(curTime)} </div> - <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> + {this._fullScreen || this.heightPercent == 100 ? + <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> + : + <div>/</div>} <div className="timecode-end"> {formatTime(this.timeline.clipDuration)} @@ -649,16 +653,28 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"} /> </div>} - <div className="videobox-button" + <div className="videobox-button show-slider" title={this._muted ? "unmute" : "mute"} - onPointerDown={this.toggleMute}> - <FontAwesomeIcon icon={this._muted || this._volume == 0 ? "volume-mute" : "volume-up"} /> + onPointerDown={(e) => { e.stopPropagation(); this.toggleMute(); }}> + <FontAwesomeIcon icon={this._muted ? "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)) }} /> + + {!this._fullScreen && this.heightPercent != 100 && + <> + <div className="videobox-button" title="zoom"> + <FontAwesomeIcon icon="search-plus" /> + </div> + <input type="range" step="0.1" min="1" max="5" value={this.timeline?._zoomFactor} + className="toolbar-slider zoom" + onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.zoom(Number(e.target.value)); }} + /> + </>} </div> } @computed get renderTimeline() { |