class ChannelListButton extends shaka.ui.Element { constructor(parent, controls, windowId) { super(parent, controls); this.windowId = windowId; this.button_ = document.createElement('button'); this.button_.classList.add('shaka-channel-list-button'); this.button_.classList.add('shaka-tooltip'); this.button_.setAttribute('aria-label', 'Lista de Canales'); this.button_.setAttribute('data-tooltip-text', 'Lista de Canales'); const icon = document.createElement('i'); icon.classList.add('material-icons-round'); icon.textContent = 'video_library'; this.button_.appendChild(icon); this.parent.appendChild(this.button_); this.eventManager.listen(this.button_, 'click', () => { togglePlayerChannelList(this.windowId); }); } destroy() { this.eventManager.release(); super.destroy(); } } class ChannelListButtonFactory { constructor(windowId) { this.windowId = windowId; } create(rootElement, controls) { return new ChannelListButton(rootElement, controls, this.windowId); } } function createPlayerWindow(channel) { const template = document.getElementById('playerWindowTemplate'); if (!template) { showNotification("Error: No se encuentra la plantilla del reproductor.", "error"); return; } const newWindow = template.content.firstElementChild.cloneNode(true); const uniqueId = `player-window-${Date.now()}`; newWindow.id = uniqueId; const titleEl = newWindow.querySelector('.player-window-title'); titleEl.textContent = channel.name || 'Reproductor'; titleEl.title = channel.name || 'Reproductor'; const videoElement = newWindow.querySelector('.player-video'); const containerElement = newWindow.querySelector('.player-container'); const channelListPanel = newWindow.querySelector('.player-channel-list-panel'); containerElement.appendChild(channelListPanel); const numWindows = Object.keys(playerInstances).length; const baseTop = 50; const baseLeft = 50; const offset = (numWindows % 10) * 30; newWindow.style.top = `${baseTop + offset}px`; newWindow.style.left = `${baseLeft + offset}px`; document.getElementById('player-windows-container').appendChild(newWindow); const playerInstance = new shaka.Player(); const uiInstance = new shaka.ui.Overlay(playerInstance, containerElement, videoElement); const factory = new ChannelListButtonFactory(uniqueId); shaka.ui.Controls.registerElement('channel_list', factory); uiInstance.configure({ controlPanelElements: ['play_pause', 'time_and_duration', 'volume', 'live_display', 'spacer', 'channel_list', 'quality', 'language', 'captions', 'fullscreen'], overflowMenuButtons: ['cast', 'picture_in_picture', 'playback_rate'], addSeekBar: true, addBigPlayButton: true, enableTooltips: true, fadeDelay: userSettings.persistentControls ? Infinity : 0, seekBarColors: { base: 'rgba(255, 255, 255, 0.3)', played: 'var(--accent-primary)', buffered: 'rgba(200, 200, 200, 0.6)' }, volumeBarColors: { base: 'rgba(255, 255, 255, 0.3)', level: 'var(--accent-primary)' }, customContextMenu: true }); playerInstances[uniqueId] = { player: playerInstance, ui: uiInstance, videoElement: videoElement, container: newWindow, channel: channel, infobarInterval: null, isChannelListVisible: false, channelListPanelElement: channelListPanel }; setActivePlayer(uniqueId); playerInstance.attach(videoElement).then(() => { playChannelInShaka(channel, uniqueId); }).catch(e => { console.error("Error al adjuntar Shaka Player a la nueva ventana:", e); showNotification("Error al crear la ventana del reproductor.", "error"); destroyPlayerWindow(uniqueId); }); createTaskbarItem(uniqueId, channel); newWindow.querySelector('.player-window-close-btn').addEventListener('click', () => destroyPlayerWindow(uniqueId)); newWindow.querySelector('.player-window-minimize-btn').addEventListener('click', () => minimizePlayerWindow(uniqueId)); startPlayerInfobarUpdate(uniqueId); } function destroyPlayerWindow(id) { const instance = playerInstances[id]; if (instance) { if (instance.infobarInterval) clearInterval(instance.infobarInterval); if (instance.player) { instance.player.destroy().catch(e => {}); } if (instance.container) { instance.container.remove(); } delete playerInstances[id]; const taskbarItem = document.getElementById(`taskbar-item-${id}`); if (taskbarItem) taskbarItem.remove(); if (activePlayerId === id) { const remainingIds = Object.keys(playerInstances); setActivePlayer(remainingIds.length > 0 ? remainingIds[0] : null); } } if (Object.keys(playerInstances).length === 0) { applyHttpHeaders([]); } } function handleFavoriteButtonClick(event) { event.stopPropagation(); const url = $(this).data('url'); if (!url) { return; } toggleFavorite(url); } function showPlayerInfobar(channel, infobarElement) { if (!infobarElement || !channel) return; if (infobarElement.hideTimeout) clearTimeout(infobarElement.hideTimeout); updatePlayerInfobar(channel, infobarElement); $(infobarElement).addClass('show'); infobarElement.hideTimeout = setTimeout(() => { $(infobarElement).removeClass('show'); }, 7000); } function createTaskbarItem(windowId, channel) { const taskbar = document.getElementById('player-taskbar'); const item = document.createElement('button'); item.className = 'taskbar-item'; item.id = `taskbar-item-${windowId}`; item.title = channel.name; item.dataset.windowId = windowId; const logoSrc = channel['tvg-logo'] || ''; let iconHtml; if (logoSrc) { iconHtml = ` `; } else { iconHtml = `${escapeHtml(channel.name.charAt(0))}`; } item.innerHTML = `
${iconHtml}
${escapeHtml(channel.name)} `; taskbar.appendChild(item); } function minimizePlayerWindow(windowId) { const instance = playerInstances[windowId]; if (instance) { instance.container.style.display = 'none'; $(`#taskbar-item-${windowId}`).removeClass('active'); if (activePlayerId === windowId) { activePlayerId = null; } } } function togglePlayerChannelList(windowId) { const instance = playerInstances[windowId]; if (!instance || !instance.channelListPanelElement) return; instance.isChannelListVisible = !instance.isChannelListVisible; instance.channelListPanelElement.classList.toggle('open', instance.isChannelListVisible); if (instance.isChannelListVisible) { populatePlayerChannelList(windowId); } } function populatePlayerChannelList(windowId) { const instance = playerInstances[windowId]; if (!instance || !instance.channelListPanelElement || !instance.channel) return; const listContentElement = instance.channelListPanelElement.querySelector('.player-channel-list-content'); if (!listContentElement) return; listContentElement.innerHTML = ''; const currentPlayingChannel = instance.channel; const currentGroup = currentPlayingChannel['group-title'] || 'Sin Grupo'; const channelsInGroup = channels.filter(ch => (ch['group-title'] || 'Sin Grupo') === currentGroup); const fragment = document.createDocumentFragment(); if (channelsInGroup.length > 0) { const groupHeader = document.createElement('div'); groupHeader.className = 'player-channel-list-group-header'; groupHeader.textContent = escapeHtml(currentGroup); fragment.appendChild(groupHeader); channelsInGroup.forEach(channel => { fragment.appendChild(createPlayerChannelListItem(channel, windowId)); }); } else { const noChannelsMessage = document.createElement('p'); noChannelsMessage.className = 'p-2 text-secondary text-center'; noChannelsMessage.textContent = 'No hay canales en este grupo.'; fragment.appendChild(noChannelsMessage); } listContentElement.appendChild(fragment); highlightCurrentChannelInList(windowId); } function createPlayerChannelListItem(channel, windowId) { const itemElement = document.createElement('div'); itemElement.className = 'player-channel-list-item'; itemElement.dataset.channelUrl = channel.url; let logoSrc = channel['tvg-logo']; if (!logoSrc && typeof getEpgChannelIcon === 'function' && channel.effectiveEpgId) { logoSrc = getEpgChannelIcon(channel.effectiveEpgId); } let logoHtml; if (logoSrc) { logoHtml = ` `; } else { logoHtml = `
`; } let epgText = 'EPG no disponible'; let epgClass = 'no-epg'; if (channel.effectiveEpgId && typeof getEpgDataForChannel === 'function') { const programs = getEpgDataForChannel(channel.effectiveEpgId); const now = new Date(); const currentProgram = programs.find(p => now >= p.startDt && now < p.stopDt); if (currentProgram) { epgText = `Ahora: ${currentProgram.title}`; epgClass = ''; } } itemElement.innerHTML = ` ${logoHtml}
${escapeHtml(channel.name)} ${escapeHtml(epgText)}
`; itemElement.addEventListener('click', () => { const targetChannel = channels.find(ch => ch.url === channel.url); if (targetChannel) { const instance = playerInstances[windowId]; if (instance) { playChannelInShaka(targetChannel, windowId); const titleEl = instance.container.querySelector('.player-window-title'); if (titleEl) { titleEl.textContent = targetChannel.name; titleEl.title = targetChannel.name; } highlightCurrentChannelInList(windowId); } } }); return itemElement; } function highlightCurrentChannelInList(windowId) { const instance = playerInstances[windowId]; if (!instance || !instance.channelListPanelElement || !instance.channel) return; const listContentElement = instance.channelListPanelElement.querySelector('.player-channel-list-content'); if (!listContentElement) return; listContentElement.querySelectorAll('.player-channel-list-item.active').forEach(activeItem => { activeItem.classList.remove('active'); }); const currentChannelUrl = instance.channel.url; const currentItemInList = listContentElement.querySelector(`.player-channel-list-item[data-channel-url="${CSS.escape(currentChannelUrl)}"]`); if (currentItemInList) { currentItemInList.classList.add('active'); requestAnimationFrame(() => { if (currentItemInList.offsetParent) { currentItemInList.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }); } }