Queue message sound while audio is priming
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
let audio: HTMLAudioElement | null = null;
|
||||
let priming = false;
|
||||
let pendingMessagePlay = false;
|
||||
|
||||
async function primeSound(): Promise<void> {
|
||||
if (priming) return;
|
||||
@@ -14,9 +15,14 @@ async function primeSound(): Promise<void> {
|
||||
window.removeEventListener('pointerdown', primeSound);
|
||||
window.removeEventListener('keydown', primeSound);
|
||||
} catch {
|
||||
try { if (audio) audio.muted = false; } catch { /* Audio may be unavailable. */ }
|
||||
// Keep gesture listeners so the next gesture can retry.
|
||||
} finally {
|
||||
try { if (audio) audio.muted = false; } catch { /* Audio may be unavailable. */ }
|
||||
priming = false;
|
||||
if (pendingMessagePlay) {
|
||||
pendingMessagePlay = false;
|
||||
playMessageNotificationSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +30,10 @@ window.addEventListener('pointerdown', primeSound);
|
||||
window.addEventListener('keydown', primeSound);
|
||||
|
||||
export function playMessageNotificationSound(): void {
|
||||
if (priming) {
|
||||
pendingMessagePlay = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
audio ??= new Audio('/message-notification.mp3');
|
||||
audio.currentTime = 0;
|
||||
|
||||
@@ -130,6 +130,52 @@ test('failed gesture prime unmutes and retries on the next gesture', async () =>
|
||||
assert.equal(attempts, 3);
|
||||
});
|
||||
|
||||
async function delayedPrimeFixture() {
|
||||
const handlers = new Map();
|
||||
window.addEventListener = (type, handler) => handlers.set(type, handler);
|
||||
window.removeEventListener = (type) => handlers.delete(type);
|
||||
const plays = [];
|
||||
let resolvePrime;
|
||||
let rejectPrime;
|
||||
const primePlayback = new Promise((resolve, reject) => { resolvePrime = resolve; rejectPrime = reject; });
|
||||
let instance;
|
||||
globalThis.Audio = class {
|
||||
constructor() { instance = this; }
|
||||
currentTime = 7;
|
||||
muted = false;
|
||||
play() { plays.push(this.muted); return plays.length === 1 ? primePlayback : Promise.resolve(); }
|
||||
pause() {}
|
||||
};
|
||||
const sound = await freshSound();
|
||||
return { handlers, plays, sound, get instance() { return instance; }, resolvePrime, rejectPrime };
|
||||
}
|
||||
|
||||
test('messages during a successful delayed prime play once unmuted after priming', async () => {
|
||||
const fixture = await delayedPrimeFixture();
|
||||
const gesture = fixture.handlers.get('pointerdown')();
|
||||
fixture.sound.playMessageNotificationSound();
|
||||
fixture.sound.playMessageNotificationSound();
|
||||
assert.deepEqual(fixture.plays, [true]);
|
||||
fixture.resolvePrime();
|
||||
await gesture;
|
||||
assert.deepEqual(fixture.plays, [true, false]);
|
||||
assert.equal(fixture.instance.currentTime, 0);
|
||||
assert.equal(fixture.instance.muted, false);
|
||||
assert.equal(fixture.handlers.size, 0);
|
||||
});
|
||||
|
||||
test('messages during a failed delayed prime play once unmuted after priming', async () => {
|
||||
const fixture = await delayedPrimeFixture();
|
||||
const gesture = fixture.handlers.get('pointerdown')();
|
||||
fixture.sound.playMessageNotificationSound();
|
||||
assert.deepEqual(fixture.plays, [true]);
|
||||
fixture.rejectPrime(new Error('blocked'));
|
||||
await gesture;
|
||||
assert.deepEqual(fixture.plays, [true, false]);
|
||||
assert.equal(fixture.instance.muted, false);
|
||||
assert.equal(fixture.handlers.size, 2);
|
||||
});
|
||||
|
||||
test('incoming alerts gate own, visible and muted messages, but sound needs no notification permission', async () => {
|
||||
let plays = 0;
|
||||
globalThis.Audio = class { currentTime = 0; play() { plays++; return Promise.resolve(); } };
|
||||
|
||||
Reference in New Issue
Block a user