Advanced_DRM_Player/player_interaction.js

564 lines
22 KiB
JavaScript
Raw Normal View History

2025-06-19 04:02:44 +02:00
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);
}
}
2025-06-25 13:32:28 +02:00
class EQButton extends shaka.ui.Element {
constructor(parent, controls, windowId) {
super(parent, controls);
this.windowId = windowId;
this.button_ = document.createElement('button');
this.button_.classList.add('shaka-eq-button');
this.button_.classList.add('shaka-tooltip');
this.button_.setAttribute('aria-label', 'Ecualizador');
this.button_.setAttribute('data-tooltip-text', 'Ecualizador');
const icon = document.createElement('i');
icon.classList.add('material-icons-round');
icon.textContent = 'equalizer';
this.button_.appendChild(icon);
this.parent.appendChild(this.button_);
this.eventManager.listen(this.button_, 'click', () => {
toggleEQPanel(this.windowId);
});
}
destroy() {
this.eventManager.release();
super.destroy();
}
}
class EQButtonFactory {
constructor(windowId) {
this.windowId = windowId;
}
create(rootElement, controls) {
return new EQButton(rootElement, controls, this.windowId);
}
}
function setupEQPanel(windowId) {
const instance = playerInstances[windowId];
if (!instance || !instance.eqPanel || !instance.audioEnhancer) return;
const enhancer = instance.audioEnhancer;
const panel = instance.eqPanel;
const onOffSwitch = panel.querySelector('.eq-on-off');
const resetBtn = panel.querySelector('.eq-reset-btn');
const savePresetBtn = panel.querySelector('.eq-save-preset-btn');
const customPresetSelect = panel.querySelector('.eq-custom-preset-select');
const deletePresetBtn = panel.querySelector('.eq-delete-preset-btn');
const bandContainer = panel.querySelector('.eq-band-container');
bandContainer.innerHTML = '';
const updateUIFromSettings = (settings) => {
onOffSwitch.checked = settings.enabled;
const preampSlider = panel.querySelector('.eq-slider.preamp');
const preampValueLabel = panel.querySelector('.preamp-band .eq-value');
if (preampSlider && preampValueLabel) {
preampSlider.value = settings.preamp;
preampValueLabel.textContent = `${Math.round(settings.preamp)}dB`;
}
const bandSliders = panel.querySelectorAll('.eq-band:not(.preamp-band) .eq-slider');
const bandValueLabels = panel.querySelectorAll('.eq-band:not(.preamp-band) .eq-value');
bandSliders.forEach((slider, i) => {
if (settings.bands[i] !== undefined) {
slider.value = settings.bands[i];
if (bandValueLabels[i]) {
bandValueLabels[i].textContent = `${settings.bands[i]}dB`;
}
}
});
};
onOffSwitch.addEventListener('change', () => {
const isEnabled = onOffSwitch.checked;
enhancer.toggle(isEnabled);
userSettings.eqSettings.enabled = isEnabled;
saveAppConfigValue('userSettings', userSettings);
});
resetBtn.addEventListener('click', () => {
const flatSettings = { enabled: true, preamp: 0, bands: new Array(10).fill(0), compressor: {} };
userSettings.eqSettings = { ...userSettings.eqSettings, ...flatSettings };
enhancer.applySettings(flatSettings);
updateUIFromSettings(flatSettings);
customPresetSelect.value = '';
saveAppConfigValue('userSettings', userSettings);
});
const preampBand = document.createElement('div');
preampBand.className = 'eq-band preamp-band';
preampBand.innerHTML = `
<div class="eq-slider-wrapper">
<input type="range" class="eq-slider preamp" min="-15" max="15" step="1" value="0" orient="vertical">
</div>
<label>Pre</label>
<span class="eq-value">0dB</span>`;
bandContainer.appendChild(preampBand);
const preampSlider = preampBand.querySelector('.eq-slider');
const preampValueLabel = preampBand.querySelector('.eq-value');
preampSlider.addEventListener('input', () => {
const gain = parseFloat(preampSlider.value);
enhancer.changePreamp(gain);
preampValueLabel.textContent = `${Math.round(gain)}dB`;
userSettings.eqSettings.preamp = gain;
customPresetSelect.value = '';
});
preampSlider.addEventListener('change', () => saveAppConfigValue('userSettings', userSettings));
enhancer.bandFrequencies.forEach((freq, i) => {
const bandDiv = document.createElement('div');
bandDiv.className = 'eq-band';
const labelText = freq >= 1000 ? `${freq / 1000}k` : freq;
bandDiv.innerHTML = `
<div class="eq-slider-wrapper">
<input type="range" class="eq-slider" min="-15" max="15" step="1" value="0" orient="vertical">
</div>
<label>${labelText}</label>
<span class="eq-value">0dB</span>`;
bandContainer.appendChild(bandDiv);
const slider = bandDiv.querySelector('.eq-slider');
const valueLabel = bandDiv.querySelector('.eq-value');
slider.addEventListener('input', () => {
const gain = parseFloat(slider.value);
enhancer.changeGain(i, gain);
valueLabel.textContent = `${gain}dB`;
userSettings.eqSettings.bands[i] = gain;
customPresetSelect.value = '';
});
slider.addEventListener('change', () => saveAppConfigValue('userSettings', userSettings));
});
const staticPresets = {
'dialogue': { enabled: true, preamp: 0, bands: [-2, -1, 0, 2, 4, 4, 2, 0, -1, -2], compressor: {} },
'movie': { enabled: true, preamp: 2, bands: [3, 2, 1, 0, -1, 0, 1, 3, 4, 3], compressor: {} },
'night': { enabled: true, preamp: 0, bands: [4, 3, 2, 0, -2, -4, -5, -6, -7, -8], compressor: { threshold: -40, knee: 30, ratio: 12 } }
};
panel.querySelectorAll('.eq-static-presets button[data-preset]').forEach(button => {
button.addEventListener('click', () => {
const presetName = button.dataset.preset;
if (staticPresets[presetName]) {
const settings = staticPresets[presetName];
userSettings.eqSettings = { ...userSettings.eqSettings, ...settings };
enhancer.applySettings(settings);
updateUIFromSettings(settings);
customPresetSelect.value = '';
saveAppConfigValue('userSettings', userSettings);
}
});
});
const populateCustomPresets = () => {
customPresetSelect.innerHTML = '<option value="">-- Presets Guardados --</option>';
(userSettings.eqSettings.customPresets || []).forEach((preset, index) => {
customPresetSelect.innerHTML += `<option value="${index}">${escapeHtml(preset.name)}</option>`;
});
};
savePresetBtn.addEventListener('click', () => {
const presetName = prompt("Nombre para el preset:", "Mi Sonido");
if (presetName) {
const newPreset = {
name: presetName,
settings: {
enabled: userSettings.eqSettings.enabled,
preamp: userSettings.eqSettings.preamp,
bands: [...userSettings.eqSettings.bands]
}
};
if (!userSettings.eqSettings.customPresets) userSettings.eqSettings.customPresets = [];
userSettings.eqSettings.customPresets.push(newPreset);
populateCustomPresets();
customPresetSelect.value = userSettings.eqSettings.customPresets.length - 1;
saveAppConfigValue('userSettings', userSettings);
}
});
customPresetSelect.addEventListener('change', () => {
const index = customPresetSelect.value;
if (index !== '') {
const preset = userSettings.eqSettings.customPresets[index];
if (preset) {
const settings = { ...preset.settings, compressor: {} };
userSettings.eqSettings = { ...userSettings.eqSettings, ...settings };
enhancer.applySettings(settings);
updateUIFromSettings(settings);
saveAppConfigValue('userSettings', userSettings);
}
}
});
deletePresetBtn.addEventListener('click', () => {
const index = customPresetSelect.value;
if (index !== '' && userSettings.eqSettings.customPresets[index]) {
if (confirm(`¿Seguro que quieres eliminar el preset "${userSettings.eqSettings.customPresets[index].name}"?`)) {
userSettings.eqSettings.customPresets.splice(index, 1);
populateCustomPresets();
saveAppConfigValue('userSettings', userSettings);
}
}
});
updateUIFromSettings(userSettings.eqSettings);
populateCustomPresets();
}
2025-06-19 04:02:44 +02:00
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);
2025-06-25 13:32:28 +02:00
const channelListFactory = new ChannelListButtonFactory(uniqueId);
shaka.ui.Controls.registerElement('channel_list', channelListFactory);
2025-06-19 04:02:44 +02:00
2025-06-25 13:32:28 +02:00
const eqButtonFactory = new EQButtonFactory(uniqueId);
shaka.ui.Controls.registerElement('eq_button', eqButtonFactory);
2025-06-19 04:02:44 +02:00
uiInstance.configure({
2025-06-25 13:32:28 +02:00
controlPanelElements: ['play_pause', 'time_and_duration', 'volume', 'spacer', 'channel_list', 'eq_button', 'quality', 'language', 'captions', 'fullscreen'],
2025-06-19 04:02:44 +02:00
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
});
2025-06-25 13:32:28 +02:00
const eqPanelTemplate = document.getElementById('eqPanelTemplate');
const eqPanel = eqPanelTemplate.content.firstElementChild.cloneNode(true);
containerElement.appendChild(eqPanel);
const audioEnhancer = new AudioEnhancer(videoElement);
if (userSettings.eqSettings) {
audioEnhancer.applySettings(userSettings.eqSettings);
}
2025-06-19 04:02:44 +02:00
playerInstances[uniqueId] = {
player: playerInstance,
ui: uiInstance,
videoElement: videoElement,
container: newWindow,
channel: channel,
infobarInterval: null,
isChannelListVisible: false,
2025-06-25 13:32:28 +02:00
channelListPanelElement: channelListPanel,
audioEnhancer: audioEnhancer,
eqPanel: eqPanel
2025-06-19 04:02:44 +02:00
};
2025-06-25 13:32:28 +02:00
setupEQPanel(uniqueId);
2025-06-19 04:02:44 +02:00
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);
2025-06-25 13:32:28 +02:00
if (instance.audioEnhancer) {
instance.audioEnhancer.destroy();
}
2025-06-19 04:02:44 +02:00
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' });
}
});
}
2025-06-25 13:32:28 +02:00
}
function toggleEQPanel(windowId) {
const instance = playerInstances[windowId];
if (!instance || !instance.eqPanel) return;
instance.eqPanel.classList.toggle('open');
2025-06-19 04:02:44 +02:00
}