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:
@ -35,6 +35,7 @@ class WatchPage {
|
||||
this.timeTotal = document.getElementById('watch-time-total');
|
||||
this.scrollHint = document.getElementById('watch-scroll-hint');
|
||||
this.loadingSpinner = document.getElementById('watch-loading');
|
||||
this.castService = null;
|
||||
|
||||
// Next episode
|
||||
this.nextEpisodePanel = document.getElementById('watch-next-episode');
|
||||
@ -153,6 +154,7 @@ class WatchPage {
|
||||
// Picture-in-Picture
|
||||
const pipBtn = document.getElementById('watch-pip');
|
||||
pipBtn?.addEventListener('click', () => this.togglePictureInPicture());
|
||||
this.initCastControl();
|
||||
|
||||
// Overflow Menu
|
||||
const overflowBtn = document.getElementById('watch-overflow');
|
||||
@ -240,6 +242,39 @@ class WatchPage {
|
||||
});
|
||||
}
|
||||
|
||||
initCastControl() {
|
||||
const button = document.getElementById('watch-cast');
|
||||
const status = document.getElementById('watch-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('[Watch] Device playback picker failed:', error?.message || error);
|
||||
}
|
||||
}
|
||||
});
|
||||
void this.castService.watchAvailability();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point - play content
|
||||
* @param {Object} content - Movie or episode info
|
||||
|
||||
Reference in New Issue
Block a user