Let the video tab's Play start at the playhead and reset its button
Two faults in the video tab. A click into the ruler while a clip played stopped the playback, but the button kept showing "Stop". And Play always started at the start of the clip, wherever the playhead stood. - The tab now reports when the ruler stops the playback, and the button switches back to "Play". - Play starts at the playhead when it lies inside the clip. Without a selected clip the whole video counts. Outside or at the end, Play starts at the beginning. - So Stop and Play continue where the playback stopped. A clip that ran to its end without loop starts again at the beginning. Tests: gate on melektaus-atx green. e2e 66 ALL PASSED, 32 checks (2 new in 66e-i). With both fixes taken back, both checks are red with exactly the reported faults: the button shows "Stop", and Play runs from 0.71 s, i.e. from the clip start at 0.6 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f177ee90a4
commit
080df2d211
5 files changed
+60
-13
No files matched your search
@@ -28,6 +28,7 @@ export function videoHooks({ state, editor }: HookContext) {
|
||||
/** The middle of the clip strip, and of the track under it, at a second of the video. */
|
||||
stripAt: (seconds: number) => videoEditor.canvas.stripAt(seconds),
|
||||
trackAt: (seconds: number) => videoEditor.canvas.trackAt(seconds),
|
||||
rulerAt: (seconds: number) => videoEditor.canvas.rulerAt(seconds),
|
||||
/** The time's zoom, and whether a transition is on its way. */
|
||||
zoom: () => videoEditor.canvas.zoomInfo(),
|
||||
},
|
||||
|
||||
@@ -129,7 +129,7 @@ export class VideoCanvas extends Widget {
|
||||
/** The span the player is held to while it plays: the picked clip, else the whole video. */
|
||||
private playing: CutSpan | null = null;
|
||||
onTogglePlay: () => void = () => {};
|
||||
/** The player stopped by itself (the end of a clip that does not repeat). */
|
||||
/** The player stopped by itself (the end of a clip that does not repeat) or by a click in the ruler. */
|
||||
onStopped: () => void = () => {};
|
||||
|
||||
constructor(private readonly state: EditorState) {
|
||||
@@ -202,28 +202,43 @@ export class VideoCanvas extends Widget {
|
||||
return this.playing !== null;
|
||||
}
|
||||
|
||||
/** Plays the picked clip, else the whole video, from its start, with the video's volume and loop. */
|
||||
/**
|
||||
* Plays the picked clip, else the whole video, with the video's volume and loop: from the
|
||||
* playhead when it stands inside, else from the start.
|
||||
*/
|
||||
play(): void {
|
||||
const asset = this.asset();
|
||||
const span = this.shownSpan();
|
||||
const element = asset && this.player(asset);
|
||||
if (!asset || !span || !element) return;
|
||||
const preview = this.preview;
|
||||
if (!asset || !span || !element || !preview) return;
|
||||
this.playing = span;
|
||||
element.muted = false;
|
||||
element.volume = videoVolumeOf(asset.props);
|
||||
// A moment still wanted from scrubbing would send it back after the start.
|
||||
if (this.preview) this.preview.chase.wanted = null;
|
||||
element.currentTime = span.start;
|
||||
preview.chase.wanted = null;
|
||||
const head = preview.asked;
|
||||
element.currentTime = head >= span.start && head < span.end - LEAST_SPAN ? head : span.start;
|
||||
void element.play().catch(() => {});
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
const preview = this.preview;
|
||||
// The playhead stays where it stopped; the next Play goes on from there.
|
||||
if (this.playing && preview) preview.asked = preview.element.currentTime;
|
||||
this.playing = null;
|
||||
this.preview?.element.pause();
|
||||
preview?.element.pause();
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
/** Stops from inside the canvas, and tells the header so its button follows. */
|
||||
private halt(): void {
|
||||
if (!this.isPlaying) return;
|
||||
this.stop();
|
||||
this.onStopped();
|
||||
}
|
||||
|
||||
/** Where the tab's player stands, in seconds of the video; null while nothing is shown. */
|
||||
get playhead(): number | null {
|
||||
return this.preview && this.preview.file === this.asset()?.file ? this.preview.element.currentTime : null;
|
||||
@@ -239,10 +254,7 @@ export class VideoCanvas extends Widget {
|
||||
if (videoLoopsOf(asset.props)) {
|
||||
element.currentTime = span.start;
|
||||
if (element.paused) void element.play().catch(() => {});
|
||||
} else {
|
||||
this.stop();
|
||||
this.onStopped();
|
||||
}
|
||||
} else this.halt();
|
||||
}
|
||||
this.redraw();
|
||||
}
|
||||
@@ -277,6 +289,15 @@ export class VideoCanvas extends Widget {
|
||||
};
|
||||
}
|
||||
|
||||
/** The middle of the ruler at a second of the video, on screen (test hook). */
|
||||
rulerAt(seconds: number): Vec2 {
|
||||
const origin = this.getGlobalPosition();
|
||||
return {
|
||||
x: origin.x + secondsToX(this.view, seconds),
|
||||
y: origin.y + this.timelineTop + RULER_HEIGHT / 2,
|
||||
};
|
||||
}
|
||||
|
||||
/** The middle of the track at a second of the video, on screen (test hook). */
|
||||
trackAt(seconds: number): Vec2 {
|
||||
const origin = this.getGlobalPosition();
|
||||
@@ -418,7 +439,7 @@ export class VideoCanvas extends Widget {
|
||||
const from = Math.max(0, Math.min(asset.duration, xToSeconds(this.view, p.x)));
|
||||
if (y < RULER_HEIGHT) {
|
||||
this.gesture = { kind: 'seek' };
|
||||
this.stop();
|
||||
this.halt();
|
||||
this.seekTo(from);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class VideoEditor extends AssetTabEditor<VideoAsset, VideoCanvas> {
|
||||
this.playButton = new Button('Play', {
|
||||
icon: TOOLBAR_ICON_SVG.play,
|
||||
minWidth: 72,
|
||||
tooltip: 'Play the picked clip, else the whole video (Space)',
|
||||
tooltip: 'Play the picked clip, else the whole video, from the playhead (Space)',
|
||||
onClick: () => this.togglePlay(),
|
||||
});
|
||||
this.downloadButton = new Button('Download', {
|
||||
|
||||
Reference in new issue
Block a user