324 lines
12 KiB
JavaScript
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' });
|
|
}
|
|
});
|
|
}
|
|
} |