// player.js — Native HLS player management using HLS.js directly var _hlsInstance = null; var _videoElement = null; /** * Fetch player config for a stream from the backend. * Returns {type: "hls"|"daddylive"|"proxy", hls_url, auth_token, ...} */ async function getPlayerConfig(streamId) { try { const resp = await fetch('/api/streams/' + streamId + '/player-config'); if (!resp.ok) return { type: 'proxy' }; return await resp.json(); } catch (e) { console.error('Failed to fetch player config:', e); return { type: 'proxy' }; } } /** * Decode a /hls/{b64} URL back to the original upstream URL. */ function decodeHLSURL(proxyURL) { if (!proxyURL || typeof proxyURL !== 'string') return proxyURL; var m = proxyURL.match(/\/hls\/([A-Za-z0-9_-]+)/); if (!m) return proxyURL; try { // base64url decode var b64 = m[1].replace(/-/g, '+').replace(/_/g, '/'); // pad while (b64.length % 4 !== 0) b64 += '='; return atob(b64); } catch (e) { return proxyURL; } } /** * Create an HLS.js player for a plain HLS stream. */ function createHLSPlayer(containerSelector, hlsURL) { destroyNativePlayer(); _buildPlayer(containerSelector, hlsURL, {}); } /** * Create an HLS.js player for DaddyLive streams with auth module integration. */ function createDaddyLivePlayer(containerSelector, config) { destroyNativePlayer(); if (config.auth_mod_url) { _loadAuthModAndPlay(containerSelector, config); } else { _buildPlayer(containerSelector, config.hls_url, {}); } } function _loadAuthModAndPlay(containerSelector, config) { var script = document.createElement('script'); script.src = config.auth_mod_url; script.onload = function () { _createDaddyLivePlayerWithAuth(containerSelector, config); }; script.onerror = function () { console.warn('Failed to load auth module, falling back to direct HLS'); _buildPlayer(containerSelector, config.hls_url, {}); }; document.head.appendChild(script); } function _createDaddyLivePlayerWithAuth(containerSelector, config) { var hlsConfig = {}; // If EPlayerAuth is available, set up xhr wrapping if (typeof EPlayerAuth !== 'undefined' && typeof EPlayerAuth.init === 'function') { try { EPlayerAuth.init({ authToken: config.auth_token, channelKey: config.channel_key, channelSalt: config.channel_salt, timestamp: config.timestamp, serverKey: config.server_key }); if (typeof EPlayerAuth.getXhrSetup === 'function') { var origSetup = EPlayerAuth.getXhrSetup(); hlsConfig.xhrSetup = function (xhr, url) { // Decode the real upstream URL from our /hls/{b64} proxy path var realURL = decodeHLSURL(url); // Create interceptor to capture headers the auth module sets var captured = {}; var fakeXHR = { setRequestHeader: function (k, v) { captured[k] = v; } }; try { origSetup(fakeXHR, realURL); } catch (e) { console.warn('Auth xhrSetup error:', e); } // Re-set captured headers with forwarding prefix for (var k in captured) { if (captured.hasOwnProperty(k)) { xhr.setRequestHeader('X-Hls-Forward-' + k, captured[k]); } } }; } } catch (e) { console.warn('EPlayerAuth init failed:', e); } } _buildPlayer(containerSelector, config.hls_url, hlsConfig); } /** * Build an HLS.js player with a