Queue message sound while audio is priming
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
let audio: HTMLAudioElement | null = null;
|
let audio: HTMLAudioElement | null = null;
|
||||||
let priming = false;
|
let priming = false;
|
||||||
|
let pendingMessagePlay = false;
|
||||||
|
|
||||||
async function primeSound(): Promise<void> {
|
async function primeSound(): Promise<void> {
|
||||||
if (priming) return;
|
if (priming) return;
|
||||||
@@ -14,9 +15,14 @@ async function primeSound(): Promise<void> {
|
|||||||
window.removeEventListener('pointerdown', primeSound);
|
window.removeEventListener('pointerdown', primeSound);
|
||||||
window.removeEventListener('keydown', primeSound);
|
window.removeEventListener('keydown', primeSound);
|
||||||
} catch {
|
} catch {
|
||||||
try { if (audio) audio.muted = false; } catch { /* Audio may be unavailable. */ }
|
// Keep gesture listeners so the next gesture can retry.
|
||||||
} finally {
|
} finally {
|
||||||
|
try { if (audio) audio.muted = false; } catch { /* Audio may be unavailable. */ }
|
||||||
priming = false;
|
priming = false;
|
||||||
|
if (pendingMessagePlay) {
|
||||||
|
pendingMessagePlay = false;
|
||||||
|
playMessageNotificationSound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +30,10 @@ window.addEventListener('pointerdown', primeSound);
|
|||||||
window.addEventListener('keydown', primeSound);
|
window.addEventListener('keydown', primeSound);
|
||||||
|
|
||||||
export function playMessageNotificationSound(): void {
|
export function playMessageNotificationSound(): void {
|
||||||
|
if (priming) {
|
||||||
|
pendingMessagePlay = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
audio ??= new Audio('/message-notification.mp3');
|
audio ??= new Audio('/message-notification.mp3');
|
||||||
audio.currentTime = 0;
|
audio.currentTime = 0;
|
||||||
|
|||||||
@@ -130,6 +130,52 @@ test('failed gesture prime unmutes and retries on the next gesture', async () =>
|
|||||||
assert.equal(attempts, 3);
|
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 () => {
|
test('incoming alerts gate own, visible and muted messages, but sound needs no notification permission', async () => {
|
||||||
let plays = 0;
|
let plays = 0;
|
||||||
globalThis.Audio = class { currentTime = 0; play() { plays++; return Promise.resolve(); } };
|
globalThis.Audio = class { currentTime = 0; play() { plays++; return Promise.resolve(); } };
|
||||||
|
|||||||
Reference in New Issue
Block a user