Keep a playing video when the window comes back to the front (#402)
Chrome draws no frames for a hidden page, so the scene stopped asking
for its videos. The sweeper of the VideoLibrary kept running and
dropped every player after 2 s. Back in the window, the video started
over, and Pixi warned that a texture source was destroyed while still
bound to a shader.
- The sweeper skips a hidden page. The time hidden does not count as
idle, the 2 s start once the page is shown again.
- A dropped player only unloads its video source instead of destroying
it. A shader that still holds the source no longer warns.
- CI serves videos through a new router for php -S. The built-in
server answers no Range requests, so Chrome could not seek. e2e 66
had 4 red checks in the suite job of d48f174 because of it. nginx on
stein2 hid this.
Tests:
- Unit video-library.test.ts (new): 3 tests
- e2e 66 (atx): ALL PASSED, 18 checks
- Router checked with curl: 206 with the right bytes
- Counter-check: the old sweep fails both hidden-page tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d48f174caf
commit
c815d2c35f
4 files changed
+154
-8
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* When the video library lets a player go (#402): after 2 s nobody asked for it, but never while
|
||||
* the page is hidden. Chrome draws no frames for a hidden page and so nothing asks, while the
|
||||
* sweeper's timer goes on; a sprite still wearing the video lost it and Pixi warned about the
|
||||
* destroyed source on the way back.
|
||||
*/
|
||||
const KEY = `video:a1b2c3d4e5f6/${'c7'.repeat(32)}.mp4#0~0~1~1`;
|
||||
|
||||
function fakeVideo() {
|
||||
return {
|
||||
muted: false,
|
||||
playsInline: false,
|
||||
preload: '',
|
||||
src: '',
|
||||
currentTime: 0,
|
||||
loop: false,
|
||||
paused: true,
|
||||
addEventListener: () => {},
|
||||
requestVideoFrameCallback: () => 0,
|
||||
play: () => Promise.resolve(),
|
||||
pause: () => {},
|
||||
removeAttribute: () => {},
|
||||
load: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
const page = { hidden: false, listeners: [] as (() => void)[] };
|
||||
|
||||
function setHidden(hidden: boolean): void {
|
||||
page.hidden = hidden;
|
||||
for (const listener of page.listeners) listener();
|
||||
}
|
||||
|
||||
async function freshLibrary() {
|
||||
const { VideoLibrary } = await import('../../src/viewport/VideoLibrary');
|
||||
return new VideoLibrary();
|
||||
}
|
||||
|
||||
describe('VideoLibrary sweeping', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers({ toFake: ['setInterval', 'clearInterval', 'performance'] });
|
||||
page.hidden = false;
|
||||
page.listeners = [];
|
||||
vi.stubGlobal('document', {
|
||||
get hidden() {
|
||||
return page.hidden;
|
||||
},
|
||||
addEventListener: (_type: string, listener: () => void) => page.listeners.push(listener),
|
||||
createElement: fakeVideo,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('lets a player go that nobody asked for in 2 s', async () => {
|
||||
const videos = await freshLibrary();
|
||||
videos.get(KEY);
|
||||
vi.advanceTimersByTime(4100);
|
||||
expect(videos.debugInfo()).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps it while the page is hidden, however long', async () => {
|
||||
const videos = await freshLibrary();
|
||||
videos.get(KEY);
|
||||
setHidden(true);
|
||||
vi.advanceTimersByTime(60_000);
|
||||
expect(videos.debugInfo()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not count the time hidden as idle once the page is back', async () => {
|
||||
const videos = await freshLibrary();
|
||||
videos.get(KEY);
|
||||
setHidden(true);
|
||||
vi.advanceTimersByTime(60_000);
|
||||
setHidden(false);
|
||||
vi.advanceTimersByTime(1900);
|
||||
expect(videos.debugInfo()).toHaveLength(1);
|
||||
vi.advanceTimersByTime(2200);
|
||||
expect(videos.debugInfo()).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user