aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/AudioWaveform.tsx26
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss167
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx46
-rw-r--r--src/client/views/nodes/AudioBox.scss178
-rw-r--r--src/client/views/nodes/AudioBox.tsx14
5 files changed, 256 insertions, 175 deletions
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
index 270b3869c..7b9b1aa81 100644
--- a/src/client/views/AudioWaveform.tsx
+++ b/src/client/views/AudioWaveform.tsx
@@ -18,6 +18,7 @@ export interface AudioWaveformProps {
layoutDoc: Doc;
clipStart: number;
clipEnd: number;
+ zoomFactor: number;
PanelHeight: () => number;
}
@@ -28,28 +29,30 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
@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 audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)], listSpec("number"), []); }
+ @computed get zoomFactor() { return this.props.zoomFactor; }
+ @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec("number"), []); }
- audioBucketField = (start: number, end: number) => "audioBuckets/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_");
+ audioBucketField = (start: number, end: number, zoomFactor: number) => "audioBuckets/" + "/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_") + "/" + (zoomFactor * 10);
componentWillUnmount() {
this._disposer?.();
}
componentDidMount() {
- this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd) }),
- ({ clipStart, clipEnd, fieldKey }) => {
+ console.log("new waveform");
+ this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
+ ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
if (!this.props.layoutDoc[fieldKey]) {
// setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
- const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration)], listSpec("number"));
+ const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec("number"));
this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice(clipStart / this.props.rawDuration * waveform.length, clipEnd / this.props.rawDuration * waveform.length));
- setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd));
+ setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
}
}, { fireImmediately: true });
}
// decodes the audio file into peaks for generating the waveform
- createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number) => {
+ createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
(response) => {
const context = new window.AudioContext();
@@ -60,12 +63,15 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
const startInd = clipStart / this.props.rawDuration;
const endInd = clipEnd / this.props.rawDuration;
const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
+ const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
+
+ console.log(numBuckets);
const bucketDataSize = Math.floor(
- decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS
+ decodedAudioData.length / numBuckets
);
const brange = Array.from(Array(bucketDataSize));
- const bucketList = numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map(
+ const bucketList = numberRange(numBuckets).map(
(i: number) =>
brange.reduce(
(p, x, j) =>
@@ -87,7 +93,7 @@ export class AudioWaveform extends React.Component<AudioWaveformProps> {
<Waveform
color={Colors.MEDIUM_BLUE}
height={this.waveHeight}
- barWidth={0.1}
+ barWidth={10.0 / this.audioBuckets.length}
pos={this.props.duration}
duration={this.props.duration}
peaks={this.audioBuckets}
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 7a957ae5c..0ec5f9aef 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,94 +1,109 @@
@import "../global/globalCssVariables.scss";
.collectionStackedTimeline {
- position: absolute;
- width: 100%;
- height: 100%;
- z-index: 1000;
- top: 0px;
-
- .collectionStackedTimeline-trim-shade {
position: absolute;
+ width: 100%;
height: 100%;
- background-color: $dark-gray;
- opacity: 0.3;
- }
+ z-index: 1000;
+ top: 0px;
+ // overflow-x: scroll;
- .collectionStackedTimeline-trim-controls {
- height: 100%;
- position: absolute;
- box-sizing: border-box;
- border: 2px solid $medium-blue;
- display: flex;
- justify-content: space-between;
- max-width: 100%;
+ ::-webkit-scrollbar {
+ position: relative;
+ -webkit-appearance: none;
+ height: 5px;
+ background-color: white;
+ }
- .collectionStackedTimeline-trim-handle {
- background-color: $medium-blue;
- height: 100%;
- width: 5px;
- cursor: ew-resize;
+ ::-webkit-scrollbar-thumb {
+ position: relative;
+ -webkit-appearance: none;
+ height: 5px;
+ background-color: $medium-gray;
}
- }
- .collectionStackedTimeline-selector {
- position: absolute;
- width: 10px;
- top: 2.5%;
- height: 95%;
- background: $light-blue;
- border-radius: 3px;
- opacity: 0.3;
- z-index: 500;
- border-style: solid;
- border-color: $medium-blue;
- border-width: 1px;
- }
+ .collectionStackedTimeline-trim-shade {
+ position: absolute;
+ height: 100%;
+ background-color: $dark-gray;
+ opacity: 0.3;
+ }
- .collectionStackedTimeline-current {
- width: 1px;
- height: 100%;
- background-color: $pink;
- position: absolute;
- top: 0px;
- pointer-events: none;
- }
+ .collectionStackedTimeline-trim-controls {
+ height: 100%;
+ position: absolute;
+ box-sizing: border-box;
+ border: 2px solid $medium-blue;
+ display: flex;
+ justify-content: space-between;
+ max-width: 100%;
- .collectionStackedTimeline-marker-timeline {
- position: absolute;
- top: 2.5%;
- height: 95%;
- border-radius: 4px;
- background: $light-gray;
- &:hover {
- opacity: 1;
+ .collectionStackedTimeline-trim-handle {
+ background-color: $medium-blue;
+ height: 100%;
+ width: 5px;
+ cursor: ew-resize;
+ }
}
- .collectionStackedTimeline-left-resizer,
- .collectionStackedTimeline-resizer {
- background: $medium-gray;
- position: absolute;
- top: 0;
- height: 100%;
- width: 10px;
- pointer-events: all;
- cursor: ew-resize;
- z-index: 100;
+ .collectionStackedTimeline-selector {
+ position: absolute;
+ width: 10px;
+ top: 2.5%;
+ height: 95%;
+ background: $light-blue;
+ border-radius: 3px;
+ opacity: 0.3;
+ z-index: 500;
+ border-style: solid;
+ border-color: $medium-blue;
+ border-width: 1px;
}
- .collectionStackedTimeline-resizer {
- right: 0;
+
+ .collectionStackedTimeline-current {
+ width: 1px;
+ height: 100%;
+ background-color: $pink;
+ position: absolute;
+ top: 0px;
+ pointer-events: none;
}
- .collectionStackedTimeline-left-resizer {
- left: 0;
+
+ .collectionStackedTimeline-marker-timeline {
+ position: absolute;
+ top: 2.5%;
+ height: 95%;
+ border-radius: 4px;
+ background: $light-gray;
+ &:hover {
+ opacity: 1;
+ }
+
+ .collectionStackedTimeline-left-resizer,
+ .collectionStackedTimeline-resizer {
+ background: $medium-gray;
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: 10px;
+ pointer-events: all;
+ cursor: ew-resize;
+ z-index: 100;
+ }
+ .collectionStackedTimeline-resizer {
+ right: 0;
+ }
+ .collectionStackedTimeline-left-resizer {
+ left: 0;
+ }
}
- }
- .collectionStackedTimeline-waveform {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
- pointer-events: none;
- }
+ .collectionStackedTimeline-waveform {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ }
}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index b5c266526..5c02611bb 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -100,6 +100,8 @@ export class CollectionStackedTimeline extends CollectionSubView<
@computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
+ @computed get zoomFactor() { return this._zoomFactor }
+
constructor(props: any) {
super(props);
// onClick play scripts
@@ -120,6 +122,8 @@ export class CollectionStackedTimeline extends CollectionSubView<
}
componentDidMount() {
+ this.layoutDoc.clipStart = 0;
+ this.layoutDoc.clipEnd = this.props.rawDuration;
document.addEventListener("keydown", this.keyEvents, true);
}
@@ -135,7 +139,6 @@ export class CollectionStackedTimeline extends CollectionSubView<
@action
public StartTrimming(scope: TrimScope) {
- console.log(this.minTrimLength);
this._trimStart = this.clipStart;
this._trimEnd = this.clipEnd;
this._trimming = scope;
@@ -148,8 +151,9 @@ export class CollectionStackedTimeline extends CollectionSubView<
}
@action
- public setZoom(change: number) {
- this._zoomFactor = Math.max(1, this._zoomFactor + change);
+ public setZoom(zoom: number) {
+ this._zoomFactor = zoom;
+ // console.log(this._timeline?.scrollWidth);
}
anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
@@ -216,7 +220,9 @@ export class CollectionStackedTimeline extends CollectionSubView<
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
+ const scrollLeft = this._timeline?.scrollLeft;
const clientX = e.clientX;
+ const diff = rect ? clientX - rect?.x : null;
const shiftKey = e.shiftKey;
if (rect && this.props.isContentActive()) {
const wasPlaying = this.props.playing();
@@ -529,7 +535,7 @@ export class CollectionStackedTimeline extends CollectionSubView<
}
@computed get renderAudioWaveform() {
return !this.props.mediaPath ? null : (
- <div className="collectionStackedTimeline-waveform">
+ <div className="collectionStackedTimeline-waveform" style={{ width: `${this.zoomFactor * 100}%`, overflowX: "scroll" }}>
<AudioWaveform
rawDuration={this.props.rawDuration}
duration={this.clipDuration}
@@ -538,6 +544,7 @@ export class CollectionStackedTimeline extends CollectionSubView<
clipStart={this.clipStart}
clipEnd={this.clipEnd}
PanelHeight={this.timelineContentHeight}
+ zoomFactor={this.zoomFactor}
/>
</div>
);
@@ -556,7 +563,7 @@ export class CollectionStackedTimeline extends CollectionSubView<
}
render() {
- const timelineContentWidth = this.props.PanelWidth();
+ const timelineContentWidth = this.props.PanelWidth() * this.zoomFactor;
const overlaps: {
anchorStartTime: number;
anchorEndTime: number;
@@ -573,8 +580,9 @@ export class CollectionStackedTimeline extends CollectionSubView<
ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
onClick={(e) => this.isContentActive() && StopEvent(e)}
onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)}
- style={{ width: `${this._zoomFactor * 100}%` }}
+ onPointerEnter={(e) => { console.log("scroll"); e.preventDefault(); e.stopPropagation(); }}
>
+
{drawAnchors.map((d) => {
const start = this.anchorStart(d.anchor);
const end = this.anchorEnd(
@@ -868,19 +876,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
{inner.view}
{!inner.anchor.view ||
!SelectionManager.IsSelected(inner.anchor.view) ? null : (
- <>
- <div
- key="left"
- className="collectionStackedTimeline-left-resizer"
- onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
- />
- <div
- key="right"
- className="collectionStackedTimeline-resizer"
- onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, false)}
- />
- </>
- )}
+ <>
+ <div
+ key="left"
+ className="collectionStackedTimeline-left-resizer"
+ onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
+ />
+ <div
+ key="right"
+ className="collectionStackedTimeline-resizer"
+ onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, false)}
+ />
+ </>
+ )}
</>
);
}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index b33c7f506..a2fdd38e5 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,51 +1,50 @@
@import "../global/globalCssVariables.scss";
-
.audiobox-container,
.audiobox-container-interactive {
- width: 100%;
- height: 100%;
- position: inherit;
- display: flex;
- position: relative;
- cursor: default;
-
- .audiobox-buttons {
- display: flex;
width: 100%;
- align-items: center;
+ height: 100%;
+ position: inherit;
+ display: flex;
+ position: relative;
+ cursor: default;
- .audiobox-dictation {
- position: relative;
- width: 30px;
- height: 100%;
- align-items: center;
- display: inherit;
- background: $medium-gray;
- left: 0px;
- color: $dark-gray;
- &:hover {
- color: $black;
- cursor: pointer;
- }
+ .audiobox-buttons {
+ display: flex;
+ width: 100%;
+ align-items: center;
+
+ .audiobox-dictation {
+ position: relative;
+ width: 30px;
+ height: 100%;
+ align-items: center;
+ display: inherit;
+ background: $medium-gray;
+ left: 0px;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ cursor: pointer;
+ }
+ }
}
- }
- .audiobox-control,
- .audiobox-control-interactive {
- top: 0;
- max-height: 32px;
- width: 100%;
- display: inline-block;
- pointer-events: none;
- }
+ .audiobox-control,
+ .audiobox-control-interactive {
+ top: 0;
+ max-height: 32px;
+ width: 100%;
+ display: inline-block;
+ pointer-events: none;
+ }
- .audiobox-control-interactive {
- pointer-events: all;
- }
+ .audiobox-control-interactive {
+ pointer-events: all;
+ }
- .audiobox-record-interactive,
- .audiobox-record {
+ .audiobox-record-interactive,
+ .audiobox-record {
pointer-events: all;
cursor: pointer;
width: 100%;
@@ -59,45 +58,46 @@
color: white;
font-weight: bold;
background-color: $dark-gray;
- }
+ }
- .audiobox-record {
+ .audiobox-record {
pointer-events: none;
- }
+ }
- .recording {
- margin-top: auto;
- margin-bottom: auto;
- width: 100%;
- height: 100%;
- position: relative;
- padding-right: 5px;
- display: flex;
- background-color: $medium-blue;
+ .recording {
+ margin-top: auto;
+ margin-bottom: auto;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ padding-right: 5px;
+ display: flex;
+ background-color: $medium-blue;
- .time {
- position: relative;
- width: 100%;
- font-size: $large-header;
- text-align: center;
- }
+ .time {
+ position: relative;
+ width: 100%;
+ font-size: $large-header;
+ text-align: center;
+ }
- .recording-buttons {
- position: relative;
- margin-top: auto;
- margin-bottom: auto;
- color: $dark-gray;
- &:hover {
- color: $black;
- }
- }
+ .recording-buttons {
+ position: relative;
+ margin-top: auto;
+ margin-bottom: auto;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ }
+ }
- .time, .recording-buttons {
- display: flex;
- align-items: center;
- padding: 5px;
+ .time,
+ .recording-buttons {
+ display: flex;
+ align-items: center;
+ padding: 5px;
+ }
}
- }
.audiobox-buttons {
display: flex;
width: 100%;
@@ -267,6 +267,44 @@
right: 2px;
}
+ .toolbar-slider {
+ position: absolute;
+ top: 75px;
+ left: 70px;
+ }
+
+ input[type="range"] {
+ width: calc(100% - 100px);
+ height: 16px;
+ -webkit-appearance: none;
+ background: none;
+ }
+
+ input[type="range"]:focus {
+ outline: none;
+ }
+
+ input[type="range"]::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 5px;
+ cursor: pointer;
+ box-shadow: 0;
+ background: #dfdfdf;
+ border-radius: 3px;
+ }
+
+ input[type="range"]::-webkit-slider-thumb {
+ box-shadow: 0;
+ border: 0;
+ height: 7px;
+ width: 7px;
+ border-radius: 10px;
+ background: #4476f7;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin: -1px;
+ }
+
.audiobox-zoom {
bottom: 0;
left: 30px;
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 2eb34d27a..f2001adcd 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -45,6 +45,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
public static Enabled = false;
static playheadWidth = 40; // width of playhead
static heightPercent = 75; // height of timeline in percent of height of audioBox.
+ static zoomInterval = 0.1;
@observable static _scrubTime = 0;
_dropDisposer?: DragManager.DragDropDisposer;
@@ -374,6 +375,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}));
}
+ zoom = (zoom: number) => {
+ this.timeline?.setZoom(zoom);
+ }
+
setupTimelineDrop = (r: HTMLDivElement | null) => {
if (r && this.timeline) {
this._dropDisposer?.();
@@ -437,6 +442,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
icon={this.mediaState === media_state.Paused ? "play" : "pause"}
size={"1x"} />
</div>
+
<div className="audiobox-buttons"
title={this.timeline?.IsTrimming !== TrimScope.None ? "finish" : "trim"}
onPointerDown={this.onClipPointerDown}
@@ -445,6 +451,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"}
size={"1x"} />
</div>
+
<div className="audiobox-timeline"
style={{
left: AudioBox.playheadWidth,
@@ -457,6 +464,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<div className="audioBox-current-time">
{this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))}
</div>
+
+ {/* <input type="range" step="0.1" min="1" max="5" value={this.timeline?._zoomFactor}
+ className="toolbar-slider" id="zoom-slider"
+ onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.zoom(e.target.value); }}
+ /> */}
+
<div className="audioBox-total-time">
{this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))}
</div>