[MVP][READY] #223 Telefon-klient guard: stop aktiv lyd på secondary device #236
@@ -351,6 +351,37 @@ describe('PlayerShellComponent gameplay wiring', () => {
|
|||||||
component.ngOnDestroy();
|
component.ngOnDestroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('silences active media elements when secondary-device audio guard is installed', () => {
|
||||||
|
const pauseAudio = vi.fn();
|
||||||
|
const pauseVideo = vi.fn();
|
||||||
|
const audioElement = { muted: false, pause: pauseAudio };
|
||||||
|
const videoElement = { muted: false, pause: pauseVideo };
|
||||||
|
const querySelectorAll = vi.fn().mockReturnValue([audioElement, videoElement]);
|
||||||
|
|
||||||
|
vi.stubGlobal('document', { querySelectorAll });
|
||||||
|
vi.stubGlobal('window', {
|
||||||
|
location: { hash: '', search: '' },
|
||||||
|
history: { state: null, replaceState: vi.fn() },
|
||||||
|
localStorage: { getItem: vi.fn().mockReturnValue('en'), setItem: vi.fn(), removeItem: vi.fn() },
|
||||||
|
sessionStorage: { getItem: vi.fn().mockReturnValue(null), setItem: vi.fn(), removeItem: vi.fn() },
|
||||||
|
HTMLMediaElement: { prototype: { play: vi.fn().mockResolvedValue(undefined) } },
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
});
|
||||||
|
vi.stubGlobal('navigator', { language: 'en-US', onLine: true });
|
||||||
|
|
||||||
|
const component = new PlayerShellComponent();
|
||||||
|
component.ngOnInit();
|
||||||
|
|
||||||
|
expect(querySelectorAll).toHaveBeenCalledWith('audio,video');
|
||||||
|
expect(audioElement.muted).toBe(true);
|
||||||
|
expect(videoElement.muted).toBe(true);
|
||||||
|
expect(pauseAudio).toHaveBeenCalledTimes(1);
|
||||||
|
expect(pauseVideo).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
component.ngOnDestroy();
|
||||||
|
});
|
||||||
|
|
||||||
it('installs secondary-device audio guard while player shell is mounted', async () => {
|
it('installs secondary-device audio guard while player shell is mounted', async () => {
|
||||||
const originalPlay = vi.fn().mockRejectedValue(new Error('original play'));
|
const originalPlay = vi.fn().mockRejectedValue(new Error('original play'));
|
||||||
const mediaPrototype = { play: originalPlay };
|
const mediaPrototype = { play: originalPlay };
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ type MediaPrototypeWithGuardState = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GuardableMediaElement = {
|
||||||
|
muted?: boolean;
|
||||||
|
pause?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
function resolveLocalStorage(): Storage | undefined {
|
function resolveLocalStorage(): Storage | undefined {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -202,6 +207,8 @@ export class PlayerShellComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.silenceExistingMediaElements();
|
||||||
|
|
||||||
const mediaPrototype = (window as Window & { HTMLMediaElement?: { prototype?: MediaPrototypeWithGuardState } }).HTMLMediaElement
|
const mediaPrototype = (window as Window & { HTMLMediaElement?: { prototype?: MediaPrototypeWithGuardState } }).HTMLMediaElement
|
||||||
?.prototype;
|
?.prototype;
|
||||||
|
|
||||||
@@ -235,6 +242,26 @@ export class PlayerShellComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private silenceExistingMediaElements(): void {
|
||||||
|
if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeElements = document.querySelectorAll('audio,video') as
|
||||||
|
| NodeListOf<GuardableMediaElement>
|
||||||
|
| GuardableMediaElement[]
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (!activeElements || typeof (activeElements as { forEach?: unknown }).forEach !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeElements.forEach((element) => {
|
||||||
|
element.muted = true;
|
||||||
|
element.pause?.();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private scheduleStateSync(): void {
|
private scheduleStateSync(): void {
|
||||||
this.clearStateSyncTimer();
|
this.clearStateSyncTimer();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user