(function (root, factory) { const exports = factory(); if (typeof module === 'object' && module.exports) module.exports = exports; if (root) root.ServerCastDevicePicker = exports.ServerCastDevicePicker; })(typeof globalThis !== 'undefined' ? globalThis : this, function () { class ServerCastDevicePicker { constructor(options = {}) { this.document = options.document || (typeof document !== 'undefined' ? document : null); this.api = options.api; this.nativeCastService = options.nativeCastService || null; this.mediaUrlProvider = options.mediaUrlProvider || (() => ''); this.onState = options.onState || (() => {}); this.devices = []; this.query = ''; this.ensureDialog(); this.attachListeners(); } ensureDialog() { if (!this.document) return; this.dialog = this.document.getElementById('cast-device-dialog'); if (!this.dialog) { this.dialog = this.document.createElement('dialog'); this.dialog.id = 'cast-device-dialog'; this.dialog.className = 'cast-device-dialog'; this.dialog.innerHTML = this.dialogContent(); this.document.body.appendChild(this.dialog); } this.list = this.document.getElementById('cast-device-list'); this.search = this.document.getElementById('cast-device-search'); this.message = this.document.getElementById('cast-device-message'); this.nativeButton = this.document.getElementById('cast-browser-picker'); } dialogContent() { return `
Choose a device connected to the same network as Nodecast.
No devices match your search.
'; this.list.querySelectorAll('[data-device-id]:not(:disabled)').forEach(button => { button.addEventListener('click', () => void this.connect(button.dataset.deviceId)); }); } async connect(deviceId) { const mediaPath = this.mediaUrlProvider(); if (!this.isAllowedMediaPath(mediaPath)) { this.setMessage('This stream is not ready for server casting yet. Continue playback here or use the browser device picker.', 'error'); return; } this.setMessage('Connecting…'); this.onState({ status: 'connecting', deviceId }); try { const session = await this.api.startSession({ deviceId, mediaPath }); this.setMessage('Playing on the selected device.'); this.onState({ status: 'connected', deviceId, session }); this.close(); } catch (error) { const message = error?.message || 'Unable to start device playback.'; this.setMessage(/base url|localhost|lan|nodecast/i.test(message) ? `Nodecast is not reachable by this device: ${message}` : message, 'error'); this.onState({ status: 'error', deviceId, error: message }); } } isAllowedMediaPath(value) { if (typeof value !== 'string' || value.includes('?')) return false; return /^\/api\/transcode\/[A-Za-z0-9_-]{1,128}\/stream\.m3u8$/.test(value) || /^\/api\/proxy\/xtream\/\d+\/stream\/[A-Za-z0-9._-]{1,128}\/(?:live|movie|series)$/.test(value); } setMessage(message, state = '') { if (this.message) { this.message.textContent = message; this.message.dataset.state = state; } } escape(value) { return String(value ?? '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } } return { ServerCastDevicePicker }; });