aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormehekj <mehek.jethani@gmail.com>2021-11-04 16:09:13 -0400
committermehekj <mehek.jethani@gmail.com>2021-11-04 16:09:13 -0400
commitbcae1802bc5277811476ce968a337813a7841fb6 (patch)
tree783aca01ca1fb09dfc8f0804f8d663f1d1fdb5ab /src
parent3ba076f93e2aa182698a229b381a77765f11213e (diff)
smooth scroll in timeline while playing audio
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts20
-rw-r--r--src/client/views/AudioWaveform.tsx118
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss17
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx59
-rw-r--r--src/client/views/nodes/AudioBox.tsx4
5 files changed, 178 insertions, 40 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index bfb29fe8d..9faea8d60 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -514,6 +514,26 @@ export function smoothScroll(duration: number, element: HTMLElement | HTMLElemen
};
animateScroll();
}
+
+export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) {
+ const elements = (element instanceof HTMLElement ? [element] : element);
+ const starts = elements.map(element => element.scrollLeft);
+ const startDate = new Date().getTime();
+
+ const animateScroll = () => {
+ const currentDate = new Date().getTime();
+ const currentTime = currentDate - startDate;
+ elements.map((element, i) => element.scrollLeft = easeInOutQuad(currentTime, starts[i], to - starts[i], duration));
+
+ if (currentTime < duration) {
+ requestAnimationFrame(animateScroll);
+ } else {
+ elements.forEach(element => element.scrollLeft = to);
+ }
+ };
+ animateScroll();
+}
+
export function addStyleSheet(styleType: string = "text/css") {
const style = document.createElement("style");
style.type = styleType;
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 4d9c039c4..8a3c3c319 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -10,6 +10,7 @@ import { Cast, NumCast } from "../../fields/Types";
import { numberRange } from "../../Utils";
import "./AudioWaveform.scss";
import { Colors } from "./global/globalEnums";
+import Color = require("color");
export interface AudioWaveformProps {
duration: number; // length of media clip
@@ -19,15 +20,15 @@ export interface AudioWaveformProps {
clipStart: number;
clipEnd: number;
zoomFactor: number;
- PanelHeight: () => number;
- PanelWidth: () => number;
+ PanelHeight: number;
+ PanelWidth: number;
}
@observer
export class AudioWaveform extends React.Component<AudioWaveformProps> {
public static NUMBER_OF_BUCKETS = 100;
_disposer: IReactionDisposer | undefined;
- @computed get waveHeight() { return Math.max(50, this.props.PanelHeight()); }
+ @computed get waveHeight() { return Math.max(50, this.props.PanelHeight); }
@computed get clipStart() { return this.props.clipStart; }
@computed get clipEnd() { return this.props.clipEnd; }
@computed get zoomFactor() { return this.props.zoomFactor; }
@@ -88,10 +89,17 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
render() {
return (
<div className="audioWaveform">
+ {/* <Waveform
+ barWidth={2}
+ width={() => this.props.PanelWidth}
+ height={this.props.PanelHeight}
+ peaks={this.audioBuckets}
+ color={Colors.MEDIUM_BLUE}
+ /> */}
<Waveform
color={Colors.MEDIUM_BLUE_ALT}
height={this.waveHeight}
- barWidth={2}
+ barWidth={200 / this.audioBuckets.length}
pos={this.props.duration}
duration={this.props.duration}
peaks={this.audioBuckets}
@@ -103,14 +111,102 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
}
-// export interface WaveformProps {
-// barWidth: number;
-// width: () => number;
-// height: () => number;
-// peaks: number[];
-// color: string;
-// }
+export interface WaveformProps {
+ barWidth: number;
+ width: () => number;
+ height: () => number;
+ peaks: number[];
+ color: string;
+}
+// @observer
// export class Waveform extends React.Component<WaveformProps> {
+// private _canvas: HTMLCanvasElement | null = null;
+
+// get width() { return this.props.width(); }
+// get height() { return this.props.height(); }
+// get peaks() { return this.props.peaks; }
+
+// componentDidMount() {
+// this.drawBars();
+// }
+
+// drawBars() {
+// const waveCanvasCtx = this._canvas?.getContext("2d");
+
+// if (waveCanvasCtx) {
+// const pixelRatio = window.devicePixelRatio;
+// console.log(pixelRatio);
+
+// const displayWidth = Math.round(this.width);
+// const displayHeight = Math.round(this.height);
+// waveCanvasCtx.canvas.width = this.width;
+// waveCanvasCtx.canvas.height = this.height;
+// waveCanvasCtx.canvas.style.width = `${displayWidth}px`;
+// waveCanvasCtx.canvas.style.height = `${displayHeight}px`;
+
+// waveCanvasCtx.clearRect(0, 0, this.width, this.height);
+
+// const hasMinVals = [].some.call(this.peaks, (val) => val < 0);
+// let filteredPeaks = this.peaks;
+// if (hasMinVals) {
+// // If the first value is negative, add 1 to the filtered indices
+// let indexOffset = 0;
+// if (this.peaks[0] < 0) {
+// indexOffset = 1;
+// }
+// filteredPeaks = [].filter.call(
+// this.peaks,
+// (_, index) => (index + indexOffset) % 2 == 0
+// );
+// }
+
+// const $ = 0.5;
+// const height = this.height;
+// const offsetY = 0;
+// const halfH = this.height / 2;
+// const length = filteredPeaks.length;
+// const bar = this.props.barWidth;
+// const gap = 2;
+// const step = bar + gap;
+
+// let absmax = 1;
+// absmax = this.absMax(filteredPeaks);
+
+// const scale = length / this.width;
+
+// waveCanvasCtx.fillStyle = this.props.color;
+
+// for (let i = 0; i < this.width; i += step) {
+// let h = Math.round(filteredPeaks[Math.floor(i * scale)] / absmax * halfH)
+// if (h === 0) {
+// h = 1
+// }
+// waveCanvasCtx.fillRect(i + $, halfH - h + offsetY, bar + $, h * 2)
+// }
+// }
+// }
+
+// absMax = (values: number[]) => {
+// let max = -Infinity;
+// for (const i in values) {
+// const num = Math.abs(values[i]);
+// if (num > max) {
+// max = num;
+// }
+// }
+
+// return max;
+// }
+
+// render() {
+// return this.props.peaks ? (
+// <canvas
+// ref={(instance) => {
+// this._canvas = instance;
+// }}
+// />
+// ) : null
+// }
// } \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 19913350b..fce105a44 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -3,25 +3,13 @@
.timeline-container {
height: calc(100% - 50px);
overflow-x: auto;
+ overflow-y: hidden;
border: none;
background-color: $white;
border: 2px solid $dark-gray;
border-width: 0 2px 0 2px;
}
-::-webkit-scrollbar {
- position: relative;
- -webkit-appearance: none;
- height: 5px;
-}
-
-::-webkit-scrollbar-thumb {
- position: relative;
- -webkit-appearance: none;
- height: 5px;
- background-color: $medium-gray;
-}
-
.collectionStackedTimeline {
position: absolute;
background: $off-white;
@@ -33,6 +21,7 @@
height: 100%;
background-color: $dark-gray;
opacity: 0.3;
+ top: 0;
}
.collectionStackedTimeline-trim-controls {
@@ -43,6 +32,8 @@
display: flex;
justify-content: space-between;
max-width: 100%;
+ top: 0;
+ left: 0;
.collectionStackedTimeline-trim-handle {
background-color: $medium-blue;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index b63835b00..ced8a68e8 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -23,6 +23,8 @@ import {
setupMoveUpEvents,
StopEvent,
returnTrue,
+ smoothScroll,
+ smoothScrollHorizontal,
} from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { LinkManager } from "../../util/LinkManager";
@@ -81,6 +83,7 @@ export class CollectionStackedTimeline extends CollectionSubView<
static LabelPlayScript: ScriptField;
private _timeline: HTMLDivElement | null = null;
+ private _timelineWrapper: HTMLDivElement | null = null;
private _markerStart: number = 0;
@observable _markerEnd: number | undefined;
@observable _trimming: number = TrimScope.None;
@@ -345,8 +348,23 @@ export class CollectionStackedTimeline extends CollectionSubView<
}
@action
- setScroll = (e: React.MouseEvent) => {
- this._scroll = e.currentTarget.scrollLeft;
+ setScroll = (e: React.UIEvent) => {
+ e.stopPropagation();
+ this._scroll = this._timelineWrapper!.scrollLeft;
+ }
+
+ @action
+ scrollToTime = (time: number) => {
+ console.log("rightmost visible time: " + this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth));
+ if (this._timelineWrapper && time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) {
+ console.log("scrolled");
+ this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth());
+ smoothScrollHorizontal(100, this._timelineWrapper, this._scroll);
+ }
+ else if (this._timelineWrapper && time < this.toTimeline(this._scroll, this.timelineContentWidth)) {
+ this._scroll = time / this.timelineContentWidth * this.clipDuration;
+ smoothScrollHorizontal(100, this._timelineWrapper, this._scroll);
+ }
}
@action
@@ -357,7 +375,7 @@ export class CollectionStackedTimeline extends CollectionSubView<
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
const x = localPt[0] - docDragData.offset[0];
- const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth());
+ const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
docDragData.droppedDocuments.forEach(drop => {
const anchorEnd = this.anchorEnd(drop);
if (anchorEnd !== undefined) {
@@ -466,7 +484,7 @@ export class CollectionStackedTimeline extends CollectionSubView<
m: Doc,
placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]
) => {
- const timelineContentWidth = this.timelineContentWidth();
+ const timelineContentWidth = this.timelineContentWidth;
const x1 = this.anchorStart(m);
const x2 = this.anchorEnd(
m,
@@ -497,9 +515,9 @@ export class CollectionStackedTimeline extends CollectionSubView<
dictationHeightPercent = 50;
dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
- timelineContentHeight = () => (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
- timelineContentWidth = () => (this.props.PanelWidth() * this.zoomFactor - 4); // subtract size of container border
- dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight());
+ @computed get timelineContentHeight() { return this.props.PanelHeight() * this.dictationHeightPercent / 100; }
+ @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor - 4 }; // subtract size of container border
+ dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
currentTimecode = () => this.currentTime;
@@ -510,7 +528,7 @@ export class CollectionStackedTimeline extends CollectionSubView<
style={{
position: "absolute",
height: "100%",
- top: this.timelineContentHeight(),
+ top: this.timelineContentHeight,
background: Colors.LIGHT_BLUE,
}}
>
@@ -570,7 +588,6 @@ export class CollectionStackedTimeline extends CollectionSubView<
}
render() {
- const timelineContentWidth = this.timelineContentWidth();
const overlaps: {
anchorStartTime: number;
anchorEndTime: number;
@@ -584,25 +601,26 @@ export class CollectionStackedTimeline extends CollectionSubView<
return (<div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined }}>
<div className="timeline-container"
style={{ width: this.props.PanelWidth() }}
- onScroll={this.setScroll}>
+ onScroll={this.setScroll}
+ ref={(wrapper: HTMLDivElement | null) => (this._timelineWrapper = wrapper)}>
<div
className="collectionStackedTimeline"
ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
onClick={(e) => this.isContentActive() && StopEvent(e)}
onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)}
- style={{ width: timelineContentWidth }}>
+ style={{ width: this.timelineContentWidth }}>
{drawAnchors.map((d) => {
const start = this.anchorStart(d.anchor);
const end = this.anchorEnd(
d.anchor,
- start + (10 / timelineContentWidth) * this.clipDuration
+ start + (10 / this.timelineContentWidth) * this.clipDuration
);
if (end < this.clipStart || start > this.clipEnd) return (null);
- const left = Math.max((start - this.clipStart) / this.clipDuration * timelineContentWidth, 0);
+ const left = Math.max((start - this.clipStart) / this.clipDuration * this.timelineContentWidth, 0);
const top = (d.level / maxLevel) * this.props.PanelHeight();
const timespan = Math.max(0, end - this.clipStart) - Math.max(0, start - this.clipStart);
- const width = (timespan / this.clipDuration) * timelineContentWidth;
+ const width = (timespan / this.clipDuration) * this.timelineContentWidth;
const height = this.props.PanelHeight() / maxLevel;
return this.props.Document.hideAnchors ? null : (
<div
@@ -641,7 +659,18 @@ export class CollectionStackedTimeline extends CollectionSubView<
);
})}
{!this.IsTrimming && this.selectionContainer}
- {this.renderAudioWaveform}
+ {/* {this.renderAudioWaveform} */}
+ <AudioWaveform
+ rawDuration={this.props.rawDuration}
+ duration={this.clipDuration}
+ mediaPath={this.props.mediaPath}
+ layoutDoc={this.layoutDoc}
+ clipStart={this.clipStart}
+ clipEnd={this.clipEnd}
+ zoomFactor={this.zoomFactor}
+ PanelHeight={this.timelineContentHeight}
+ PanelWidth={this.timelineContentWidth}
+ />
{this.renderDictation}
<div
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index a896c7d90..b51908e20 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -21,7 +21,7 @@ import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
import "./AudioBox.scss";
import { FieldView, FieldViewProps } from "./FieldView";
-import { timeStamp } from "console";
+import { time, timeStamp } from "console";
declare class MediaRecorder {
constructor(e: any); // whatever MediaRecorder has
@@ -139,6 +139,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
});
this.layoutDoc._currentTimecode = this._ele.currentTime;
+ console.log("current time: " + this.layoutDoc._currentTimecode);
+ this.timeline?.scrollToTime(NumCast(this.layoutDoc._currentTimecode));
}
}