Merge pull request '[MVP][READY] #223 Telefon-klient guard: ingen lydafspilning på secondary device' (#235) from feature/issue-223-player-audio-guard into main
All checks were successful
CI / test-and-quality (push) Successful in 2m47s
All checks were successful
CI / test-and-quality (push) Successful in 2m47s
This commit was merged in pull request #235.
This commit is contained in:
@@ -375,4 +375,34 @@ describe('PlayerShellComponent gameplay wiring', () => {
|
|||||||
|
|
||||||
await expect(mediaPrototype.play()).rejects.toThrow('original play');
|
await expect(mediaPrototype.play()).rejects.toThrow('original play');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps audio guard active until the last mounted player shell is destroyed', async () => {
|
||||||
|
const originalPlay = vi.fn().mockRejectedValue(new Error('original play'));
|
||||||
|
const mediaPrototype = { play: originalPlay };
|
||||||
|
|
||||||
|
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: mediaPrototype },
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
});
|
||||||
|
vi.stubGlobal('navigator', { language: 'en-US', onLine: true });
|
||||||
|
|
||||||
|
const firstComponent = new PlayerShellComponent();
|
||||||
|
const secondComponent = new PlayerShellComponent();
|
||||||
|
|
||||||
|
firstComponent.ngOnInit();
|
||||||
|
secondComponent.ngOnInit();
|
||||||
|
|
||||||
|
await expect(mediaPrototype.play()).resolves.toBeUndefined();
|
||||||
|
|
||||||
|
firstComponent.ngOnDestroy();
|
||||||
|
await expect(mediaPrototype.play()).resolves.toBeUndefined();
|
||||||
|
|
||||||
|
secondComponent.ngOnDestroy();
|
||||||
|
await expect(mediaPrototype.play()).rejects.toThrow('original play');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ interface SessionDetail {
|
|||||||
type ConnectionState = 'online' | 'reconnecting' | 'offline';
|
type ConnectionState = 'online' | 'reconnecting' | 'offline';
|
||||||
type LoadingTransition = 'refresh' | 'join' | 'submit-lie' | 'submit-guess' | null;
|
type LoadingTransition = 'refresh' | 'join' | 'submit-lie' | 'submit-guess' | null;
|
||||||
|
|
||||||
|
type MediaPrototypeWithGuardState = {
|
||||||
|
play?: () => Promise<void>;
|
||||||
|
__wppSecondaryDeviceAudioGuard__?: {
|
||||||
|
originalPlay: () => Promise<void>;
|
||||||
|
installs: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
function resolveLocalStorage(): Storage | undefined {
|
function resolveLocalStorage(): Storage | undefined {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -194,17 +202,36 @@ export class PlayerShellComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaPrototype = (window as Window & { HTMLMediaElement?: { prototype?: { play?: () => Promise<void> } } }).HTMLMediaElement
|
const mediaPrototype = (window as Window & { HTMLMediaElement?: { prototype?: MediaPrototypeWithGuardState } }).HTMLMediaElement
|
||||||
?.prototype;
|
?.prototype;
|
||||||
|
|
||||||
if (!mediaPrototype || typeof mediaPrototype.play !== 'function') {
|
if (!mediaPrototype || typeof mediaPrototype.play !== 'function') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalPlay = mediaPrototype.play;
|
const guardState = mediaPrototype.__wppSecondaryDeviceAudioGuard__;
|
||||||
mediaPrototype.play = () => Promise.resolve();
|
if (guardState) {
|
||||||
|
guardState.installs += 1;
|
||||||
|
} else {
|
||||||
|
const originalPlay = mediaPrototype.play;
|
||||||
|
mediaPrototype.play = () => Promise.resolve();
|
||||||
|
mediaPrototype.__wppSecondaryDeviceAudioGuard__ = {
|
||||||
|
originalPlay,
|
||||||
|
installs: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.restoreAudioGuard = () => {
|
this.restoreAudioGuard = () => {
|
||||||
mediaPrototype.play = originalPlay;
|
const currentState = mediaPrototype.__wppSecondaryDeviceAudioGuard__;
|
||||||
|
if (!currentState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentState.installs -= 1;
|
||||||
|
if (currentState.installs <= 0) {
|
||||||
|
mediaPrototype.play = currentState.originalPlay;
|
||||||
|
delete mediaPrototype.__wppSecondaryDeviceAudioGuard__;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user