aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/VideoBox.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-09-13 10:11:49 -0400
committerbobzel <zzzman@gmail.com>2022-09-13 10:11:49 -0400
commit36b17b5b0878eeb2eb23fd4c5078e06fcc002aaf (patch)
tree33c1a3fb50381d09bf89bd43d869544a3c52c7b6 /src/client/views/nodes/VideoBox.tsx
parent7696d85b7b737a29cab189f4c65f395c5de132c7 (diff)
parentbb9f0d4dec849bdaf2d358d060707b2ed1ed677d (diff)
Merge branch 'sharing-jenny' of https://github.com/brown-dash/Dash-Web into sharing-jenny
Diffstat (limited to 'src/client/views/nodes/VideoBox.tsx')
-rw-r--r--src/client/views/nodes/VideoBox.tsx304
1 files changed, 156 insertions, 148 deletions
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b1f7d8023..b45ee7f6e 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -396,10 +396,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// sets video info on load
videoLoad = action(() => {
- const aspect = this.player!.videoWidth / this.player!.videoHeight;
- Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth);
- Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight);
- this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
+ const aspect = this.player!.videoWidth / (this.player!.videoHeight || 1);
+ if (aspect) {
+ Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth);
+ Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight);
+ this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
+ }
if (Number.isFinite(this.player!.duration)) {
this.rawDuration = this.player!.duration;
} else this.rawDuration = NumCast(this.dataDoc[this.fieldKey + '-duration']);
@@ -418,31 +420,26 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// extracts video thumbnails and saves them as field of doc
getVideoThumbnails = () => {
- const video = document.createElement('video');
+ if (this.dataDoc.thumbnails !== undefined) return;
+ this.dataDoc.thumbnails = new List<string>();
const thumbnailPromises: Promise<any>[] = [];
+ const video = document.createElement('video');
- video.onloadedmetadata = () => {
- video.currentTime = 0;
- };
+ video.onloadedmetadata = () => (video.currentTime = 0);
video.onseeked = () => {
const canvas = document.createElement('canvas');
- canvas.height = video.videoHeight;
- canvas.width = video.videoWidth;
- const ctx = canvas.getContext('2d');
- ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
- const imgUrl = canvas.toDataURL();
+ canvas.height = 100;
+ canvas.width = 100;
+ canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, '');
const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
- const filename = basename(encodedFilename);
- thumbnailPromises.push(VideoBox.convertDataUri(imgUrl, filename));
+ thumbnailPromises?.push(VideoBox.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
if (newTime < video.duration) {
video.currentTime = newTime;
} else {
- Promise.all(thumbnailPromises).then(thumbnails => {
- this.dataDoc.thumbnails = new List<string>(thumbnails);
- });
+ Promise.all(thumbnailPromises).then(thumbnails => (this.dataDoc.thumbnails = new List<string>(thumbnails)));
}
};
@@ -554,7 +551,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return !field ? (
<div key="loading">Loading</div>
) : (
- <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: 'multiply', cursor: this._fullScreen && !this._controlsVisible ? 'none' : 'pointer' }}>
+ <div className="videoBox-contentContainer" key="container" style={{ mixBlendMode: 'multiply', cursor: this._fullScreen && !this._controlsVisible ? 'none' : 'default' }}>
<div className={classname} ref={this.setContentRef} onPointerDown={e => this._fullScreen && e.stopPropagation()}>
{this._fullScreen && (
<div
@@ -836,16 +833,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// stretches vertically or horizontally depending on video orientation so video fits full screen
fullScreenSize() {
if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) {
- return { height: '100%' };
- } else {
- return { width: '100%' };
+ //prettier-ignore
+ return ({ height: '100%' });
}
+ //prettier-ignore
+ return ({ width: '100%' });
}
// for zoom slider, sets timeline waveform zoom
- zoom = (zoom: number) => {
- this.timeline?.setZoom(zoom);
- };
+ zoom = (zoom: number) => this.timeline?.setZoom(zoom);
// plays link
playLink = (doc: Doc) => {
@@ -859,7 +855,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// starts marquee selection
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._viewScale, 1) === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -898,7 +894,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
- scaling = () => this.props.scaling?.() || 1;
+ scaling = () => this.props.NativeDimScaling?.() || 1;
panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
panelHeight = () => (this.layoutDoc._fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : (this.props.PanelHeight() * this.heightPercent) / 100);
@@ -911,140 +907,46 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
.scale(100 / this.heightPercent);
};
- marqueeFitScaling = () => ((this.props.scaling?.() || 1) * this.heightPercent) / 100;
+ marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100;
marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`];
// renders video controls
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 width = Math.max(right - left, 100);
- const uiHeight = Math.max(25, Math.min(50, height / 10));
- const uiMargin = Math.min(10, height / 20);
- const vidHeight = (height * this.heightPercent) / 100;
- const yPos = top + vidHeight - uiHeight - uiMargin;
- const xPos = uiHeight / vidHeight > 0.4 ? right + 10 : left + 10;
+ const xf = this.props.ScreenToLocalTransform().inverse();
+ const height = this.props.PanelHeight();
+ const vidHeight = (height * this.heightPercent) / 100 / this.scaling();
+ const vidWidth = this.props.PanelWidth() / this.scaling();
+ const uiHeight = 25;
+ const uiMargin = 10;
+ const yBot = xf.transformPoint(0, vidHeight)[1];
+ // prettier-ignore
+ const yMid = (xf.transformPoint(0, 0)[1] +
+ xf.transformPoint(0, height / this.scaling())[1]) / 2;
+ const xPos = xf.transformPoint(vidWidth / 2, 0)[0];
+ const xRight = xf.transformPoint(vidWidth, 0)[0];
const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0;
- return this._fullScreen || right - left < 50 ? null : (
+ return this._fullScreen || (xRight - xPos) * 2 < 50 ? null : (
<div className="videoBox-ui-wrapper" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
- <div className="videoBox-ui" style={{ left: xPos, top: yPos, height: uiHeight, width: width - 20, transition: this._clicking ? 'top 0.5s' : '', opacity: opacity }}>
+ <div
+ className="videoBox-ui"
+ style={{
+ transformOrigin: 'top left',
+ transform: `rotate(${NumCast(this.rootDoc.jitterRotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+ left: xPos,
+ top: yMid,
+ height: uiHeight,
+ width: (xRight - xPos) * 2 - 20,
+ transition: this._clicking ? 'top 0.5s' : '',
+ opacity,
+ }}>
{this.UIButtons}
</div>
</div>
);
};
- @computed get UIButtons() {
- const bounds = this.props.docViewPath().lastElement().getBounds();
- const width = (bounds?.right || 0) - (bounds?.left || 0);
- const curTime = NumCast(this.layoutDoc._currentTimecode) - (this.timeline?.clipStart || 0);
- return (
- <>
- <div className="videobox-button" title={this._playing ? 'play' : 'pause'} onPointerDown={this.onPlayDown}>
- <FontAwesomeIcon icon={this._playing ? 'pause' : 'play'} />
- </div>
-
- {this.timeline && width > 150 && (
- <div className="timecode-controls">
- <div className="timecode-current">{formatTime(curTime)}</div>
-
- {this._fullScreen || (this.heightPercent === 100 && width > 200) ? (
- <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={action((e: React.PointerEvent) => {
- e.stopPropagation();
- this._scrubbing = true;
- })}
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setPlayheadTime(Number(e.target.value))}
- onPointerUp={action((e: React.PointerEvent) => {
- e.stopPropagation();
- this._scrubbing = false;
- })}
- />
- </div>
- ) : (
- <div>/</div>
- )}
-
- <div className="timecode-end">{formatTime(this.timeline.clipDuration)}</div>
- </div>
- )}
-
- <div className="videobox-button" title={'full screen'} onPointerDown={this.onFullDown}>
- <FontAwesomeIcon icon="expand" />
- </div>
-
- {!this._fullScreen && width > 300 && (
- <div className="videobox-button" title={'show timeline'} onPointerDown={this.onTimelineHdlDown}>
- <FontAwesomeIcon icon="eye" />
- </div>
- )}
-
- {!this._fullScreen && width > 300 && (
- <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={e => {
- e.stopPropagation();
- this.toggleMute();
- }}>
- <FontAwesomeIcon icon={this._muted ? 'volume-mute' : 'volume-up'} />
- </div>
- {width > 300 && (
- <input
- type="range"
- style={{ width: `min(25%, 50px)` }}
- 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 && width > 300 && (
- <>
- <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));
- }}
- />
- </>
- )}
- </>
- );
- }
-
// renders CollectionStackedTimeline
@computed get renderTimeline() {
return (
@@ -1124,7 +1026,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ScreenToLocalTransform={this.screenToLocalTransform}
docFilters={this.timelineDocFilter}
select={emptyFunction}
- scaling={returnOne}
+ NativeDimScaling={returnOne}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
@@ -1153,6 +1055,112 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
</div>
);
}
+
+ @computed get UIButtons() {
+ const bounds = this.props.docViewPath().lastElement().getBounds();
+ const width = (bounds?.right || 0) - (bounds?.left || 0);
+ const curTime = NumCast(this.layoutDoc._currentTimecode) - (this.timeline?.clipStart || 0);
+ return (
+ <>
+ <div className="videobox-button" title={this._playing ? 'play' : 'pause'} onPointerDown={this.onPlayDown}>
+ <FontAwesomeIcon icon={this._playing ? 'pause' : 'play'} />
+ </div>
+
+ {this.timeline && width > 150 && (
+ <div className="timecode-controls">
+ <div className="timecode-current">{formatTime(curTime)}</div>
+
+ {this._fullScreen || (this.heightPercent === 100 && width > 200) ? (
+ <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={action((e: React.PointerEvent) => {
+ e.stopPropagation();
+ this._scrubbing = true;
+ })}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setPlayheadTime(Number(e.target.value))}
+ onPointerUp={action((e: React.PointerEvent) => {
+ e.stopPropagation();
+ this._scrubbing = false;
+ })}
+ />
+ </div>
+ ) : (
+ <div>/</div>
+ )}
+
+ <div className="timecode-end">{formatTime(this.timeline.clipDuration)}</div>
+ </div>
+ )}
+
+ <div className="videobox-button" title={'full screen'} onPointerDown={this.onFullDown}>
+ <FontAwesomeIcon icon="expand" />
+ </div>
+
+ {!this._fullScreen && width > 300 && (
+ <div className="videobox-button" title={'show timeline'} onPointerDown={this.onTimelineHdlDown}>
+ <FontAwesomeIcon icon="eye" />
+ </div>
+ )}
+
+ {!this._fullScreen && width > 300 && (
+ <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={e => {
+ e.stopPropagation();
+ this.toggleMute();
+ }}>
+ <FontAwesomeIcon icon={this._muted ? 'volume-mute' : 'volume-up'} />
+ </div>
+ {width > 300 && (
+ <input
+ type="range"
+ style={{ width: `min(25%, 50px)` }}
+ 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 && width > 300 && (
+ <>
+ <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));
+ }}
+ />
+ </>
+ )}
+ </>
+ );
+ }
}
VideoBox._nativeControls = false;