47 lines
3.5 KiB
JavaScript
47 lines
3.5 KiB
JavaScript
const { test, expect } = require('@playwright/test');
|
|
const { openClassic, showPlayingFixture } = require('./helpers/classic-fixture');
|
|
// A small local PCM fixture exercises actual media elements without YouTube.
|
|
function wav() {
|
|
const rate = 8000, samples = rate * 12, bytes = Buffer.alloc(44 + samples * 2);
|
|
bytes.write('RIFF'); bytes.writeUInt32LE(bytes.length - 8, 4); bytes.write('WAVEfmt ', 8);
|
|
bytes.writeUInt32LE(16, 16); bytes.writeUInt16LE(1, 20); bytes.writeUInt16LE(1, 22);
|
|
bytes.writeUInt32LE(rate, 24); bytes.writeUInt32LE(rate * 2, 28); bytes.writeUInt16LE(2, 32); bytes.writeUInt16LE(16, 34);
|
|
bytes.write('data', 36); bytes.writeUInt32LE(samples * 2, 40);
|
|
for (let i = 0; i < samples; i++) bytes.writeInt16LE(Math.sin(i * 2 * Math.PI * 220 / rate) * 2000, 44 + i * 2);
|
|
return bytes;
|
|
}
|
|
for (const source of ['saved', 'streamed']) test(`${source}: iPhone keeps the same playing audio across repeated lock/unlock events`, async ({ page }) => {
|
|
await page.addInitScript(() => Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15' }));
|
|
await page.route('**/continuity.wav', route => route.fulfill({ contentType: 'audio/wav', body: wav() }));
|
|
await openClassic(page, { populated: true }); await showPlayingFixture(page);
|
|
await page.evaluate(source => {
|
|
current.localUrl = source === 'saved' ? '/continuity.wav' : '';
|
|
current.audioUrl = '/continuity.wav';
|
|
Player.attach(source === 'streamed' ? { url: '/continuity.wav', hasAudio: true } : null);
|
|
}, source);
|
|
await expect.poll(() => page.evaluate(() => els.audio.readyState)).toBeGreaterThanOrEqual(2);
|
|
await page.evaluate(() => Player.play());
|
|
await expect.poll(() => page.evaluate(() => !els.audio.paused && els.audio.currentTime > 0)).toBe(true);
|
|
const result = await page.evaluate(async () => {
|
|
const A = els.audio, V = els.video;
|
|
const initial = A.currentTime, src = A.src;
|
|
let playCalls = 0, audioSeeks = 0, loads = 0, pauses = 0;
|
|
const play = A.play.bind(A), load = A.load.bind(A), pause = A.pause.bind(A);
|
|
A.play = () => { playCalls++; return play(); }; A.load = () => { loads++; load(); }; A.pause = () => { pauses++; pause(); };
|
|
A.addEventListener('seeking', () => audioSeeks++);
|
|
Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'hidden' });
|
|
document.dispatchEvent(new Event('visibilitychange')); window.dispatchEvent(new Event('pagehide'));
|
|
window.dispatchEvent(new Event('focus')); // focus arriving while hidden must not restore
|
|
const promoted = Player.master === A && Player._bgHandoff === 'dual';
|
|
V.pause();
|
|
await new Promise(resolve => setTimeout(resolve, 350));
|
|
const advanced = A.currentTime > initial;
|
|
Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'visible' });
|
|
document.dispatchEvent(new Event('visibilitychange')); window.dispatchEvent(new Event('pageshow')); window.dispatchEvent(new Event('focus'));
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
const answer = { mode: Player.mode, promoted, advanced, sameSource: A.src === src, playing: !A.paused, playCalls, audioSeeks, loads, pauses, restored: Player.master === V && Player.secondary === A };
|
|
Player.pause(); Player.stopDrift(); return answer;
|
|
});
|
|
expect(result).toEqual({ mode: 'dual', promoted: true, advanced: true, sameSource: true, playing: true, playCalls: 0, audioSeeks: 0, loads: 0, pauses: 0, restored: true });
|
|
});
|