diff --git a/packages/spf/src/playback/behaviors/dom/tests/track-current-time.test.ts b/packages/spf/src/playback/behaviors/dom/tests/track-current-time.test.ts index a833024b..2cbe7cb3 100644 --- a/packages/spf/src/playback/behaviors/dom/tests/track-current-time.test.ts +++ b/packages/spf/src/playback/behaviors/dom/tests/track-current-time.test.ts @@ -182,6 +182,31 @@ describe('trackCurrentTime', () => { cleanup(); }); + it('resets currentTime on emptied event (src change reuses engine)', async () => { + const mediaElement = document.createElement('video'); + let currentTime = 5.5; + Object.defineProperty(mediaElement, 'currentTime', { + get: () => currentTime, + set: (v: number) => { + currentTime = v; + }, + configurable: true, + }); + + const { state, cleanup } = setupTrackCurrentTime({}, { mediaElement }); + + await vi.waitFor(() => expect(state.currentTime.get()).toBe(5.5)); + + // Simulate src change on the same element: the media element load + // algorithm resets currentTime to 0 and dispatches `emptied`. + currentTime = 0; + mediaElement.dispatchEvent(new Event('emptied')); + + await vi.waitFor(() => expect(state.currentTime.get()).toBe(0)); + + cleanup(); + }); + it('updates currentTime on seeking events (seek while paused)', async () => { const mediaElement = document.createElement('video'); Object.defineProperty(mediaElement, 'currentTime', { value: 0, writable: true }); @@ -211,6 +236,7 @@ describe('trackCurrentTime', () => { (mediaElement as any).currentTime = 50.0; mediaElement.dispatchEvent(new Event('timeupdate')); mediaElement.dispatchEvent(new Event('seeking')); + mediaElement.dispatchEvent(new Event('emptied')); await new Promise((resolve) => setTimeout(resolve, 30)); expect(state.currentTime.get()).toBe(0); diff --git a/packages/spf/src/playback/behaviors/dom/track-current-time.ts b/packages/spf/src/playback/behaviors/dom/track-current-time.ts index 6dd51489..d8b2e240 100644 --- a/packages/spf/src/playback/behaviors/dom/track-current-time.ts +++ b/packages/spf/src/playback/behaviors/dom/track-current-time.ts @@ -5,6 +5,10 @@ * at the new position when this event dispatches, so buffer management can * react immediately rather than waiting for `timeupdate`, which does not * fire while paused. + * - `emptied` — fires when the resource selection algorithm tears down the + * current media (e.g. a new `src` is set on the same element). Re-syncs so + * downstream state doesn't retain the stale playback position from the + * previous source when the engine is reused across src changes. * * Also syncs immediately when a media element becomes available. * @@ -54,9 +58,11 @@ function trackCurrentTimeSetup({ const sync = () => state.currentTime.set(mediaElement.currentTime); sync(); + const removeEmptied = listen(mediaElement, 'emptied', sync); const removeTimeupdate = listen(mediaElement, 'timeupdate', sync); const removeSeeking = listen(mediaElement, 'seeking', sync); return () => { + removeEmptied(); removeTimeupdate(); removeSeeking(); };