fix(player): harden secondary-device audio playback guard
This commit is contained in:
@@ -400,7 +400,14 @@ describe('PlayerShellComponent gameplay wiring', () => {
|
||||
const component = new PlayerShellComponent();
|
||||
component.ngOnInit();
|
||||
|
||||
await expect(mediaPrototype.play()).resolves.toBeUndefined();
|
||||
const pause = vi.fn();
|
||||
const audioElement = { muted: false, defaultMuted: false, volume: 1, pause };
|
||||
|
||||
await expect(mediaPrototype.play.call(audioElement)).resolves.toBeUndefined();
|
||||
expect(audioElement.muted).toBe(true);
|
||||
expect(audioElement.defaultMuted).toBe(true);
|
||||
expect(audioElement.volume).toBe(0);
|
||||
expect(pause).toHaveBeenCalledTimes(1);
|
||||
|
||||
component.ngOnDestroy();
|
||||
|
||||
|
||||
@@ -16,18 +16,21 @@ interface SessionDetail {
|
||||
type ConnectionState = 'online' | 'reconnecting' | 'offline';
|
||||
type LoadingTransition = 'refresh' | 'join' | 'submit-lie' | 'submit-guess' | null;
|
||||
|
||||
type GuardableMediaElement = {
|
||||
muted?: boolean;
|
||||
defaultMuted?: boolean;
|
||||
volume?: number;
|
||||
pause?: () => void;
|
||||
};
|
||||
|
||||
type MediaPrototypeWithGuardState = {
|
||||
play?: () => Promise<void>;
|
||||
play?: (this: GuardableMediaElement) => Promise<void>;
|
||||
__wppSecondaryDeviceAudioGuard__?: {
|
||||
originalPlay: () => Promise<void>;
|
||||
originalPlay: (this: GuardableMediaElement) => Promise<void>;
|
||||
installs: number;
|
||||
};
|
||||
};
|
||||
|
||||
type GuardableMediaElement = {
|
||||
muted?: boolean;
|
||||
pause?: () => void;
|
||||
};
|
||||
|
||||
function resolveLocalStorage(): Storage | undefined {
|
||||
if (typeof window === 'undefined') {
|
||||
@@ -221,7 +224,15 @@ export class PlayerShellComponent implements OnInit, OnDestroy {
|
||||
guardState.installs += 1;
|
||||
} else {
|
||||
const originalPlay = mediaPrototype.play;
|
||||
mediaPrototype.play = () => Promise.resolve();
|
||||
mediaPrototype.play = function mediaGuardedPlay(this: GuardableMediaElement): Promise<void> {
|
||||
this.muted = true;
|
||||
this.defaultMuted = true;
|
||||
if (typeof this.volume === 'number') {
|
||||
this.volume = 0;
|
||||
}
|
||||
this.pause?.();
|
||||
return Promise.resolve();
|
||||
};
|
||||
mediaPrototype.__wppSecondaryDeviceAudioGuard__ = {
|
||||
originalPlay,
|
||||
installs: 1,
|
||||
@@ -242,6 +253,7 @@ export class PlayerShellComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private silenceExistingMediaElements(): void {
|
||||
if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') {
|
||||
return;
|
||||
@@ -258,6 +270,10 @@ export class PlayerShellComponent implements OnInit, OnDestroy {
|
||||
|
||||
activeElements.forEach((element) => {
|
||||
element.muted = true;
|
||||
element.defaultMuted = true;
|
||||
if (typeof element.volume === 'number') {
|
||||
element.volume = 0;
|
||||
}
|
||||
element.pause?.();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user