mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-11-09 07:23:14 +00:00
Resume preview audio after source seeks
This commit is contained in:
1 parent
c5117bc572
commit
df0002f457
3 files changed
+50
-15
No files matched your search
@@ -119,6 +119,7 @@ describe("source preview playback ownership", () => {
|
||||
seeking,
|
||||
wasPlaying,
|
||||
}) => {
|
||||
const seekedListeners = new Set<() => void>();
|
||||
const audio = {
|
||||
src: "",
|
||||
dataset: {},
|
||||
@@ -136,6 +137,12 @@ describe("source preview playback ownership", () => {
|
||||
audio.paused = false;
|
||||
return Promise.resolve();
|
||||
}),
|
||||
addEventListener: vi.fn((event: string, listener: () => void) => {
|
||||
if (event === "seeked") seekedListeners.add(listener);
|
||||
}),
|
||||
removeEventListener: vi.fn((event: string, listener: () => void) => {
|
||||
if (event === "seeked") seekedListeners.delete(listener);
|
||||
}),
|
||||
};
|
||||
vi.stubGlobal("Audio", function () {
|
||||
return audio;
|
||||
@@ -172,15 +179,17 @@ describe("source preview playback ownership", () => {
|
||||
expect(harness.loaded).toHaveBeenCalledOnce();
|
||||
expect(audio.play).not.toHaveBeenCalled();
|
||||
if (wasPlaying) audio.paused = false;
|
||||
harness.effects.at(-1)?.();
|
||||
const cleanup = harness.effects.at(-1)?.();
|
||||
await Promise.resolve();
|
||||
expect(audio.play).toHaveBeenCalledTimes(plays ? 1 : 0);
|
||||
if (wasPlaying) expect(audio.pause).toHaveBeenCalled();
|
||||
if (seeking) {
|
||||
audio.seeking = false;
|
||||
harness.effects.at(-1)?.();
|
||||
for (const listener of seekedListeners) listener();
|
||||
await Promise.resolve();
|
||||
expect(audio.play).toHaveBeenCalledOnce();
|
||||
if (typeof cleanup === "function") cleanup();
|
||||
expect(seekedListeners.size).toBe(0);
|
||||
}
|
||||
if (plays) {
|
||||
expect(audio.currentTime).toBeCloseTo(time - delay / 1000);
|
||||
|
||||
@@ -377,6 +377,7 @@ export function useAudioPreviewSync({
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
const seekedListeners: Array<{ audio: HTMLAudioElement; resume: () => void }> = [];
|
||||
// A newly resolved source must pass the same playback checks as timeline updates.
|
||||
void sourceLoadVersion;
|
||||
|
||||
@@ -453,10 +454,15 @@ export function useAudioPreviewSync({
|
||||
|
||||
const atEnd = audioDuration !== null && targetTime >= audioDuration;
|
||||
if (isPlaying && !isCurrentClipMuted && !beforeAudioStart && !atEnd) {
|
||||
void ensureSourceAudioRunning().then(() => {
|
||||
if (!cancelled && audio.paused && !audio.seeking)
|
||||
audio.play().catch(() => undefined);
|
||||
});
|
||||
const resume = () => {
|
||||
void ensureSourceAudioRunning().then(() => {
|
||||
if (!cancelled && audio.paused && !audio.seeking)
|
||||
audio.play().catch(() => undefined);
|
||||
});
|
||||
};
|
||||
audio.addEventListener("seeked", resume);
|
||||
seekedListeners.push({ audio, resume });
|
||||
resume();
|
||||
} else if (!audio.paused) {
|
||||
audio.pause();
|
||||
}
|
||||
@@ -465,6 +471,9 @@ export function useAudioPreviewSync({
|
||||
lastSourceAudioSyncTimeRef.current = currentTime;
|
||||
return () => {
|
||||
cancelled = true;
|
||||
for (const { audio, resume } of seekedListeners) {
|
||||
audio.removeEventListener("seeked", resume);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
sourceLoadVersion,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { installDesktopBridge } from "./bridge";
|
||||
test.use({ channel: process.env.RECORDLY_TEST_BROWSER_CHANNEL === "chrome" ? "chrome" : undefined });
|
||||
|
||||
test("two cuts select the middle clip for deletion", async ({ page }) => {
|
||||
test.setTimeout(45_000);
|
||||
await installDesktopBridge(page, "filmstrip.mp4");
|
||||
await page.goto("/?windowType=editor");
|
||||
const clips = page.locator('[data-variant="clip"]');
|
||||
@@ -13,24 +14,40 @@ test("two cuts select the middle clip for deletion", async ({ page }) => {
|
||||
const canvas = await timeline.boundingBox();
|
||||
if (!firstClip || !canvas) throw new Error("Timeline is not visible");
|
||||
|
||||
await page.mouse.click(firstClip.x + firstClip.width * 0.25, canvas.y + 6);
|
||||
await page.mouse.click(firstClip.x + firstClip.width * 0.2, canvas.y + 6);
|
||||
await page.getByRole("button", { name: /Split Clip/ }).click();
|
||||
await expect(clips).toHaveCount(2);
|
||||
await page.mouse.click(firstClip.x + firstClip.width * 0.5, canvas.y + 6);
|
||||
await page.mouse.click(firstClip.x + firstClip.width * 0.65, canvas.y + 6);
|
||||
await page.getByRole("button", { name: /Split Clip/ }).click();
|
||||
await expect(clips).toHaveCount(3);
|
||||
const beforeDelete = await clips.evaluateAll((elements) =>
|
||||
elements.map((element) => ({
|
||||
start: Number(element.getAttribute("data-start-ms")),
|
||||
end: Number(element.getAttribute("data-end-ms")),
|
||||
})),
|
||||
);
|
||||
const [firstBefore, middleBefore, lastBefore] = beforeDelete;
|
||||
|
||||
await page.keyboard.press("Delete");
|
||||
await expect(clips).toHaveCount(2);
|
||||
const firstEnd = Number(await clips.nth(0).getAttribute("data-end-ms"));
|
||||
const lastStart = Number(await clips.nth(1).getAttribute("data-start-ms"));
|
||||
expect(lastStart).toBe(firstEnd);
|
||||
await expect(clips.first()).toHaveAttribute("data-start-ms", String(firstBefore.start));
|
||||
await expect(clips.first()).toHaveAttribute("data-end-ms", String(firstBefore.end));
|
||||
await expect(clips.last()).toHaveAttribute("data-start-ms", String(firstBefore.end));
|
||||
await expect(clips.last()).toHaveAttribute(
|
||||
"data-end-ms",
|
||||
String(firstBefore.end + lastBefore.end - lastBefore.start),
|
||||
);
|
||||
expect(middleBefore.end - middleBefore.start).toBeGreaterThan(0);
|
||||
const lastClip = await clips.nth(1).boundingBox();
|
||||
if (!lastClip) throw new Error("Retained clip is not visible");
|
||||
await page.mouse.click(lastClip.x + Math.min(8, lastClip.width / 4), canvas.y + 6);
|
||||
const video = page.locator('video[src*="filmstrip.mp4"]');
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.locator('video[src*="filmstrip.mp4"]').evaluate((video: HTMLVideoElement) => video.currentTime),
|
||||
)
|
||||
.toBeGreaterThan((firstEnd + 500) / 1_000);
|
||||
.poll(() => video.evaluate((element: HTMLVideoElement) => element.currentTime))
|
||||
.toBeGreaterThan(middleBefore.end / 1000);
|
||||
const timeBeforePlay = await video.evaluate((element: HTMLVideoElement) => element.currentTime);
|
||||
await page.getByRole("button", { name: "Play", exact: true }).click();
|
||||
await expect
|
||||
.poll(() => video.evaluate((element: HTMLVideoElement) => element.currentTime))
|
||||
.toBeGreaterThan(timeBeforePlay + 0.1);
|
||||
});
|
||||
Reference in new issue
Block a user