feat(player): add PWA-compatible stream casting (#33)

Add a browser-native casting service with Live and Watch player controls for Remote Playback and AirPlay-compatible devices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-06 22:07:49 +02:00
parent d1934163ca
commit fd314f0a59
5 changed files with 275 additions and 2 deletions

View File

@ -36,6 +36,7 @@ class VideoPlayer {
this.originalStreamUrl = null;
this.sourceStreamInfo = null;
this.currentLogicalChannel = null;
this.castService = null;
// Settings - start with defaults, load from server async
this.settings = this.getDefaultSettings();
@ -363,6 +364,7 @@ class VideoPlayer {
e.stopPropagation();
this.togglePictureInPicture();
});
this.initCastControl();
// Overflow Menu
const btnOverflow = document.getElementById('btn-overflow');
@ -446,6 +448,39 @@ class VideoPlayer {
updateVolumeUI();
}
initCastControl() {
const button = document.getElementById('player-cast');
const status = document.getElementById('player-cast-status');
if (!button || !this.video || typeof CastService === 'undefined') {
if (button) button.hidden = true;
if (status) status.textContent = 'Not supported on this browser';
return;
}
this.castService = new CastService(this.video);
this.castService.subscribe(state => {
const unsupported = state.type === 'unsupported';
button.hidden = unsupported;
button.disabled = !state.available || state.status === 'connecting';
button.setAttribute('aria-pressed', String(state.status === 'connected'));
if (status) status.textContent = unsupported ? 'Not supported on this browser'
: state.status === 'connected' ? 'Playing on a device'
: state.status === 'connecting' ? 'Opening device picker…'
: state.available ? 'Playback device available' : 'No playback devices available';
});
button.addEventListener('click', async event => {
event.stopPropagation();
try {
await this.castService.prompt();
} catch (error) {
if (status) status.textContent = 'Could not connect to a playback device.';
if (error?.name !== 'NotAllowedError' && error?.name !== 'AbortError') {
console.warn('[Player] Device playback picker failed:', error?.message || error);
}
}
});
void this.castService.watchAvailability();
}
/**
* Add the live quality control to the existing player overflow menu.
*/