Advanced_DRM_Player/player_interaction.js

324 lines
12 KiB
JavaScript

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 = `<img src="${escapeHtml(logoSrc)}" class="taskbar-item-logo" alt="" onerror="this.style.display='none'; this.nextElementSibling.style.display='inline-flex';">
<span class="taskbar-item-logo-placeholder" style="display: none;">${escapeHtml(channel.name.charAt(0))}</span>`;
} else {
iconHtml = `<span class="taskbar-item-logo-placeholder">${escapeHtml(channel.name.charAt(0))}</span>`;
}
item.innerHTML = `
<div class="taskbar-item-icon-container">${iconHtml}</div>
<span class="taskbar-item-text">${escapeHtml(channel.name)}</span>
`;
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 = `<img src="${escapeHtml(logoSrc)}" class="player-channel-list-logo" alt="${escapeHtml(channel.name)}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<div class="player-channel-list-logo-placeholder" style="display: none;"></div>`;
} else {
logoHtml = `<div class="player-channel-list-logo-placeholder"></div>`;
}
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}
<div class="player-channel-list-info">
<span class="player-channel-list-name">${escapeHtml(channel.name)}</span>
<span class="player-channel-list-epg ${epgClass}">${escapeHtml(epgText)}</span>
</div>
`;
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' });
}
});
}
}