1458 lines
68 KiB
JavaScript
1458 lines
68 KiB
JavaScript
let channels = [];
|
|
let favorites = [];
|
|
let appHistory = [];
|
|
let currentFilter = 'all';
|
|
let currentPage = 1;
|
|
let currentM3UContent = null;
|
|
let currentM3UName = null;
|
|
let notificationTimeout = null;
|
|
let currentGroupOrder = [];
|
|
let selectedMovistarLongTokenIdForSettings = null;
|
|
let currentView = { type: 'main' };
|
|
let navigationHistory = [];
|
|
|
|
let playerInstances = {};
|
|
let activePlayerId = null;
|
|
let highestZIndex = 1950;
|
|
|
|
let hoverPlayTimeout = null;
|
|
const HOVER_PLAY_DELAY = 700;
|
|
let activeCardPreviewPlayer = null;
|
|
let activeCardPreviewElement = null;
|
|
|
|
let currentTranslations = {};
|
|
|
|
async function loadLanguage(lang) {
|
|
try {
|
|
const response = await fetch(chrome.runtime.getURL(`_locales/${lang}/messages.json`));
|
|
if (!response.ok) throw new Error(`Could not load ${lang}.json`);
|
|
const messages = await response.json();
|
|
currentTranslations = {};
|
|
for (const key in messages) {
|
|
if (Object.hasOwnProperty.call(messages, key)) {
|
|
currentTranslations[key] = messages[key].message;
|
|
}
|
|
}
|
|
document.documentElement.lang = lang;
|
|
} catch (error) {
|
|
console.error("Error loading language file:", error);
|
|
if (lang !== 'es') {
|
|
await loadLanguage('es');
|
|
}
|
|
}
|
|
}
|
|
|
|
function applyTranslations() {
|
|
document.querySelectorAll('[data-lang-key]').forEach(element => {
|
|
const key = element.getAttribute('data-lang-key');
|
|
const message = currentTranslations[key];
|
|
|
|
if (message) {
|
|
let finalMessage = message;
|
|
if (element.hasAttribute('data-lang-vars')) {
|
|
try {
|
|
const varsAttr = element.getAttribute('data-lang-vars');
|
|
const vars = JSON.parse(varsAttr);
|
|
for (const varKey in vars) {
|
|
const selector = vars[varKey];
|
|
const varElement = document.querySelector(selector);
|
|
if (varElement) {
|
|
finalMessage = finalMessage.replace(`{${varKey}}`, varElement.innerHTML);
|
|
}
|
|
}
|
|
} catch(e) { console.error(`Error parsing data-lang-vars for key ${key}:`, e)}
|
|
}
|
|
|
|
const attr = element.getAttribute('data-lang-attr');
|
|
if (attr) {
|
|
element.setAttribute(attr, finalMessage);
|
|
} else {
|
|
element.innerHTML = finalMessage;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function showLoadFromDBModal() {
|
|
if (typeof dbPromise === 'undefined' || !dbPromise) {
|
|
showLoading(true, currentTranslations['loading'] || 'Iniciando base de datos local...');
|
|
try { if (typeof openDB === 'function') await openDB(); } catch (error) { showNotification(`Error DB: ${error.message}`, 'error'); showLoading(false); return; }
|
|
finally { showLoading(false); }
|
|
}
|
|
showLoading(true, currentTranslations['loadingLists'] || 'Cargando listas guardadas...');
|
|
try {
|
|
const files = typeof getAllFilesFromDB === 'function' ? await getAllFilesFromDB() : [];
|
|
const $list = $('#dbFilesList').empty();
|
|
if (!files || files.length === 0) {
|
|
$list.append(`<li class="list-group-item text-secondary text-center">${currentTranslations['noFileLoaded'] || "No hay listas guardadas."}</li>`);
|
|
} else {
|
|
files.sort((a,b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
files.forEach(file => {
|
|
const date = file.timestamp ? new Date(file.timestamp).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }) : 'Fecha desconocida';
|
|
const time = file.timestamp ? new Date(file.timestamp).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit'}) : '';
|
|
const count = typeof file.channelCount === 'number' ? file.channelCount : (typeof countChannels === 'function' ? countChannels(file.content) : 0);
|
|
$list.append(`
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
<div style="flex-grow: 1; margin-right: 1rem; overflow: hidden;">
|
|
<strong title="${escapeHtml(file.name)}" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block;">${escapeHtml(file.name)}</strong>
|
|
<small class="text-secondary">${count} canales | ${date} ${time}</small>
|
|
</div>
|
|
<div>
|
|
<button class="btn-control btn-sm load-file-btn me-2" data-name="${escapeHtml(file.name)}">${currentTranslations['loadButton'] || "Cargar"}</button>
|
|
<button class="btn-control btn-sm delete-file-btn" data-name="${escapeHtml(file.name)}"></button>
|
|
</div>
|
|
</li>`);
|
|
});
|
|
$list.off('click', '.load-file-btn').on('click', '.load-file-btn', function () { loadFileToPlayer($(this).data('name')); $('#loadFromDBModal').modal('hide'); });
|
|
$list.off('click', '.delete-file-btn').on('click', '.delete-file-btn', function () { handleDeleteFromDB($(this).data('name')); });
|
|
}
|
|
$('#loadFromDBModal').modal('show');
|
|
} catch (error) {
|
|
showNotification(`Error cargando listas guardadas: ${error.message}`, 'error');
|
|
$('#dbFilesList').empty().append('<li class="list-group-item text-danger text-center">Error al cargar listas.</li>');
|
|
}
|
|
finally { showLoading(false); }
|
|
}
|
|
|
|
async function loadFileToPlayer(name) {
|
|
showLoading(true, `Cargando "${escapeHtml(name)}" desde BD...`);
|
|
currentGroupOrder = [];
|
|
try {
|
|
const file = typeof getFileFromDB === 'function' ? await getFileFromDB(name) : null;
|
|
if (!file || !file.content) throw new Error('Lista no encontrada en la base de datos.');
|
|
processM3UContent(file.content, file.name, true);
|
|
|
|
if (userSettings.autoSaveM3U) {
|
|
if (file.content.length < 4 * 1024 * 1024) {
|
|
await saveAppConfigValue('lastM3UFileContent', file.content);
|
|
await saveAppConfigValue('lastM3UFileName', file.name);
|
|
await deleteAppConfigValue('lastM3UUrl');
|
|
await deleteAppConfigValue('currentXtreamServerInfo');
|
|
} else {
|
|
await deleteAppConfigValue('lastM3UFileContent');
|
|
await deleteAppConfigValue('lastM3UFileName');
|
|
await deleteAppConfigValue('lastM3UUrl');
|
|
await deleteAppConfigValue('currentXtreamServerInfo');
|
|
showNotification('Lista cargada pero demasiado grande para guardado automático futuro.', 'info');
|
|
}
|
|
}
|
|
showNotification(`Lista "${escapeHtml(name)}" cargada (${channels.length} canales).`, 'success');
|
|
} catch (error) {
|
|
showNotification(`Error cargando "${escapeHtml(name)}": ${error.message}`, 'error');
|
|
channels = []; currentM3UContent = null; currentM3UName = null; currentGroupOrder = [];
|
|
filterAndRenderChannels();
|
|
} finally { showLoading(false); }
|
|
}
|
|
|
|
async function handleDeleteFromDB(name) {
|
|
const confirmed = await showConfirmationModal(`¿Estás seguro de eliminar la lista "${escapeHtml(name)}" de forma permanente?`, "Confirmar Eliminación", "Sí, Eliminar", "btn-danger");
|
|
if (!confirmed) return;
|
|
|
|
showLoading(true, `Eliminando "${escapeHtml(name)}"...`);
|
|
try {
|
|
if (typeof deleteFileFromDB === 'function') await deleteFileFromDB(name); else throw new Error("deleteFileFromDB no definido");
|
|
showNotification(`Lista "${escapeHtml(name)}" eliminada.`, 'success');
|
|
$(`#dbFilesList li button[data-name="${escapeHtml(name)}"]`).closest('li').fadeOut(300, function() {
|
|
$(this).remove();
|
|
if ($('#dbFilesList li').length === 0) {
|
|
$('#dbFilesList').append(`<li class="list-group-item text-secondary text-center">${currentTranslations['noFileLoaded'] || "No hay listas guardadas."}</li>`);
|
|
}
|
|
});
|
|
|
|
const lastM3UFileName = await getAppConfigValue('lastM3UFileName');
|
|
if (lastM3UFileName === name) {
|
|
channels = []; currentM3UContent = null; currentM3UName = null; currentGroupOrder = [];
|
|
filterAndRenderChannels();
|
|
await deleteAppConfigValue('lastM3UFileContent');
|
|
await deleteAppConfigValue('lastM3UFileName');
|
|
showNotification('La lista actualmente cargada fue eliminada.', 'info');
|
|
}
|
|
} catch (error) {
|
|
showNotification(`Error al eliminar "${escapeHtml(name)}": ${error.message}`, 'error');
|
|
}
|
|
finally { showLoading(false); }
|
|
}
|
|
|
|
$(document).ready(async function () {
|
|
shaka.polyfill.installAll();
|
|
if (typeof loadUserSettings === 'function') {
|
|
await loadUserSettings();
|
|
}
|
|
if (typeof applyUISettings === 'function') {
|
|
await applyUISettings();
|
|
}
|
|
|
|
makeWindowsDraggableAndResizable();
|
|
bindEvents();
|
|
if (typeof bindEpgEvents === 'function') {
|
|
bindEpgEvents();
|
|
}
|
|
if (typeof MovistarTokenHandler !== 'undefined' && typeof MovistarTokenHandler.setLogCallback === 'function') {
|
|
MovistarTokenHandler.setLogCallback(logToMovistarSettingsUI);
|
|
loadAndDisplayInitialMovistarStatus();
|
|
}
|
|
if (typeof initXCodecPanelManagement === 'function') {
|
|
initXCodecPanelManagement();
|
|
}
|
|
if (typeof phpGenerator !== 'undefined' && typeof phpGenerator.init === 'function') {
|
|
phpGenerator.init();
|
|
}
|
|
|
|
|
|
if (userSettings.persistFilters && userSettings.lastSelectedFilterTab) {
|
|
currentFilter = userSettings.lastSelectedFilterTab;
|
|
}
|
|
updateActiveFilterButton();
|
|
checkIfChannelsExist();
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const channelNameFromUrl = urlParams.get('name');
|
|
const channelStreamUrl = urlParams.get('url');
|
|
const autoPlayFromUrl = (channelNameFromUrl && channelStreamUrl);
|
|
|
|
if (autoPlayFromUrl) {
|
|
showNotification(`Cargando ${escapeHtml(channelNameFromUrl)} desde editor...`, 'info');
|
|
const channelDataForPlayer = {
|
|
name: channelNameFromUrl,
|
|
url: channelStreamUrl,
|
|
'tvg-logo': urlParams.get('logo') || '',
|
|
'tvg-id': urlParams.get('tvgid') || '',
|
|
'group-title': urlParams.get('group') || 'Externo',
|
|
attributes: {
|
|
'tvg-id': urlParams.get('tvgid') || '',
|
|
'tvg-logo': urlParams.get('logo') || '',
|
|
'group-title': urlParams.get('group') || 'Externo',
|
|
'ch-number': urlParams.get('chnumber') || '',
|
|
...(urlParams.has('player-buffer') && { 'player-buffer': urlParams.get('player-buffer') })
|
|
},
|
|
kodiProps: {}, vlcOptions: {}, extHttp: {}
|
|
};
|
|
|
|
const licenseType = urlParams.get('licenseType');
|
|
const licenseKey = urlParams.get('licenseKey');
|
|
const serverCertBase64 = urlParams.get('serverCert');
|
|
|
|
if (licenseType) channelDataForPlayer.kodiProps['inputstream.adaptive.license_type'] = licenseType;
|
|
if (licenseKey) channelDataForPlayer.kodiProps['inputstream.adaptive.license_key'] = licenseKey;
|
|
if (serverCertBase64) channelDataForPlayer.kodiProps['inputstream.adaptive.server_certificate'] = serverCertBase64;
|
|
|
|
const streamHeaders = urlParams.get('streamHeaders');
|
|
if (streamHeaders) channelDataForPlayer.kodiProps['inputstream.adaptive.stream_headers'] = streamHeaders;
|
|
|
|
const userAgent = urlParams.get('userAgent');
|
|
const referrer = urlParams.get('referrer');
|
|
const origin = urlParams.get('origin');
|
|
if (userAgent) channelDataForPlayer.vlcOptions['http-user-agent'] = userAgent;
|
|
if (referrer) channelDataForPlayer.vlcOptions['http-referrer'] = referrer;
|
|
if (origin) channelDataForPlayer.vlcOptions['http-origin'] = origin;
|
|
|
|
const extHttpJson = urlParams.get('extHttp');
|
|
if (extHttpJson) {
|
|
try { channelDataForPlayer.extHttp = JSON.parse(extHttpJson); } catch (e) { }
|
|
}
|
|
|
|
if (typeof createPlayerWindow === 'function') {
|
|
createPlayerWindow(channelDataForPlayer);
|
|
}
|
|
|
|
} else {
|
|
await loadLastM3U();
|
|
if (userSettings.useMovistarVodAsEpg && typeof updateEpgWithMovistarVodData === 'function') {
|
|
const today = new Date();
|
|
const yyyy = today.getFullYear();
|
|
const mm = String(today.getMonth() + 1).padStart(2, '0');
|
|
const dd = String(today.getDate()).padStart(2, '0');
|
|
await updateEpgWithMovistarVodData(`${yyyy}-${mm}-${dd}`);
|
|
}
|
|
if (typeof startDynamicEpgUpdaters === 'function') {
|
|
startDynamicEpgUpdaters();
|
|
}
|
|
setTimeout(() => {
|
|
$(window).one('scroll mousemove touchstart', initParticles);
|
|
setTimeout(initParticles, 5000);
|
|
}, 100);
|
|
}
|
|
});
|
|
|
|
function launchEditor() {
|
|
if (!channels || channels.length === 0) {
|
|
showNotification("No hay ninguna lista M3U cargada para editar.", "warning");
|
|
return;
|
|
}
|
|
if (typeof editorHandler === 'undefined' || typeof editorHandler.init !== 'function') {
|
|
showNotification("El módulo del editor no está disponible.", "error");
|
|
return;
|
|
}
|
|
|
|
editorHandler.init(channels, currentM3UName);
|
|
|
|
const editorModal = new bootstrap.Modal(document.getElementById('editorModal'));
|
|
editorModal.show();
|
|
}
|
|
|
|
async function handleChannelCardClick(event) {
|
|
if ($(event.target).closest('.favorite-btn').length && userSettings.cardShowFavButton) {
|
|
return;
|
|
}
|
|
|
|
clearTimeout(hoverPlayTimeout);
|
|
if (typeof destroyActiveCardPreviewPlayer === 'function' && typeof activeCardPreviewPlayer !== 'undefined' && activeCardPreviewPlayer) {
|
|
await destroyActiveCardPreviewPlayer();
|
|
}
|
|
|
|
const card = $(this);
|
|
let channelUrl, seriesChannel, seasonData, episodeData;
|
|
|
|
try {
|
|
const seasonDataAttr = card.data('season-data');
|
|
if (seasonDataAttr) {
|
|
seasonData = (typeof seasonDataAttr === 'string') ? JSON.parse(seasonDataAttr) : seasonDataAttr;
|
|
const episodes = await loadXtreamSeasonEpisodes(seasonData.series_id, seasonData.season_number);
|
|
if (episodes && episodes.length > 0) {
|
|
pushNavigationState();
|
|
currentView = { type: 'episode_list', data: episodes, title: `${seasonData['group-title']} - ${seasonData.name}` };
|
|
renderCurrentView();
|
|
} else {
|
|
showNotification('No se encontraron episodios para esta temporada.', 'info');
|
|
}
|
|
return;
|
|
}
|
|
|
|
const episodeDataAttr = card.data('episode-data');
|
|
if (episodeDataAttr) {
|
|
episodeData = (typeof episodeDataAttr === 'string') ? JSON.parse(episodeDataAttr) : episodeDataAttr;
|
|
createPlayerWindow(episodeData);
|
|
return;
|
|
}
|
|
|
|
channelUrl = card.data('url');
|
|
seriesChannel = channels.find(c => c.url === channelUrl);
|
|
if (seriesChannel && seriesChannel.attributes && seriesChannel.attributes['xtream-type'] === 'series') {
|
|
const seriesId = seriesChannel.attributes['xtream-series-id'];
|
|
const seasons = await loadXtreamSeasons(seriesId, seriesChannel.name);
|
|
if (seasons && seasons.length > 0) {
|
|
pushNavigationState();
|
|
currentView = { type: 'season_list', data: seasons, title: seriesChannel.name };
|
|
renderCurrentView();
|
|
} else {
|
|
showNotification('No se encontraron temporadas para esta serie.', 'info');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (seriesChannel) {
|
|
const isMovistarStream = seriesChannel.url && (seriesChannel.url.toLowerCase().includes('telefonica.com') || seriesChannel.url.toLowerCase().includes('movistarplus.es'));
|
|
const existingMovistarWindow = isMovistarStream
|
|
? Object.values(playerInstances).find(inst =>
|
|
inst.channel &&
|
|
(inst.channel.url.toLowerCase().includes('telefonica.com') || inst.channel.url.toLowerCase().includes('movistarplus.es'))
|
|
)
|
|
: null;
|
|
|
|
if (existingMovistarWindow) {
|
|
const existingId = Object.keys(playerInstances).find(key => playerInstances[key] === existingMovistarWindow);
|
|
if (existingId) {
|
|
showNotification("Reutilizando la ventana de Movistar+ para el nuevo canal.", "info");
|
|
playChannelInShaka(seriesChannel, existingId);
|
|
const instance = playerInstances[existingId];
|
|
instance.container.querySelector('.player-window-title').textContent = seriesChannel.name;
|
|
setActivePlayer(existingId);
|
|
}
|
|
} else {
|
|
createPlayerWindow(seriesChannel);
|
|
}
|
|
} else {
|
|
showNotification('Error: Canal no encontrado para reproducir.', 'error');
|
|
}
|
|
|
|
} catch (e) {
|
|
showNotification('Error al procesar la acción: ' + e.message, 'error');
|
|
}
|
|
}
|
|
|
|
function handleGlobalKeyPress(e) {
|
|
if (!activePlayerId || !playerInstances[activePlayerId]) return;
|
|
|
|
const instance = playerInstances[activePlayerId];
|
|
if (instance.container.style.display === 'none') return;
|
|
|
|
const player = instance.player;
|
|
const ui = instance.ui;
|
|
const video = instance.videoElement;
|
|
|
|
if (!video) return;
|
|
|
|
if ($(e.target).is('input, textarea, [contenteditable="true"], .shaka-text-input')) return;
|
|
|
|
let currentChannelIndex = -1;
|
|
const currentFilteredChannels = getFilteredChannels();
|
|
if (instance.channel && instance.channel.url) {
|
|
currentChannelIndex = currentFilteredChannels.findIndex(ch => ch.url === instance.channel.url);
|
|
}
|
|
|
|
|
|
switch (e.key.toLowerCase()) {
|
|
case ' ':
|
|
e.preventDefault();
|
|
video.paused ? video.play() : video.pause();
|
|
break;
|
|
case 'f':
|
|
e.preventDefault();
|
|
if (ui) ui.toggleFullScreen();
|
|
break;
|
|
case 'i':
|
|
e.preventDefault();
|
|
showPlayerInfobar(instance.channel, instance.container.querySelector('.player-infobar'));
|
|
break;
|
|
case 'm':
|
|
e.preventDefault();
|
|
video.muted = !video.muted;
|
|
break;
|
|
case 'arrowleft':
|
|
e.preventDefault();
|
|
if (video.duration && video.currentTime > 0) {
|
|
video.currentTime = Math.max(0, video.currentTime - (e.shiftKey ? 15 : 5));
|
|
}
|
|
break;
|
|
case 'arrowright':
|
|
e.preventDefault();
|
|
if (video.duration && video.currentTime < video.duration) {
|
|
video.currentTime = Math.min(video.duration, video.currentTime + (e.shiftKey ? 15 : 5));
|
|
}
|
|
break;
|
|
case 'arrowup':
|
|
e.preventDefault();
|
|
video.volume = Math.min(1, video.volume + 0.05);
|
|
break;
|
|
case 'arrowdown':
|
|
e.preventDefault();
|
|
video.volume = Math.max(0, video.volume - 0.05);
|
|
break;
|
|
case 'pageup':
|
|
e.preventDefault();
|
|
if (currentChannelIndex > 0 && currentFilteredChannels.length > 0) {
|
|
const prevChannel = currentFilteredChannels[currentChannelIndex - 1];
|
|
playChannelInShaka(prevChannel, activePlayerId);
|
|
}
|
|
break;
|
|
case 'pagedown':
|
|
e.preventDefault();
|
|
if (currentChannelIndex !== -1 && currentChannelIndex < currentFilteredChannels.length - 1) {
|
|
const nextChannel = currentFilteredChannels[currentChannelIndex + 1];
|
|
playChannelInShaka(nextChannel, activePlayerId);
|
|
}
|
|
break;
|
|
case 'escape':
|
|
if (ui && ui.isFullScreen()) {
|
|
e.preventDefault();
|
|
ui.toggleFullScreen();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function bindEvents() {
|
|
$('#sidebarToggleBtn').on('click', async () => {
|
|
const sidebar = $('#sidebar');
|
|
const appContainer = $('#app-container');
|
|
sidebar.toggleClass('collapsed expanded');
|
|
appContainer.toggleClass('sidebar-collapsed');
|
|
userSettings.sidebarCollapsed = sidebar.hasClass('collapsed');
|
|
if(userSettings.persistFilters) await saveAppConfigValue('userSettings', userSettings);
|
|
});
|
|
|
|
$('#loadUrl').on('click', () => {
|
|
const url = $('#urlInput').val().trim();
|
|
if (url) {
|
|
if (typeof isXtreamUrl === 'function' && isXtreamUrl(url)) {
|
|
if (typeof handleXtreamUrl === 'function') handleXtreamUrl(url);
|
|
} else {
|
|
loadUrl(url);
|
|
}
|
|
} else {
|
|
showNotification('Introduce una URL válida.', 'info');
|
|
}
|
|
});
|
|
$('#fileInput').on('change', loadFile);
|
|
|
|
$('#loadFromDBBtnHeader').on('click', showLoadFromDBModal);
|
|
$('#saveToDBBtnHeader').on('click', () => {
|
|
if (!currentM3UContent) {
|
|
showNotification('No hay lista cargada para guardar.', 'info');
|
|
return;
|
|
}
|
|
let defaultName = currentM3UName || 'mi_lista';
|
|
defaultName = defaultName.replace(/\.(m3u8?|txt|pls|m3uplus)$/i, '').replace(/^\/|\/$/g, '');
|
|
if(defaultName.includes('/')) { defaultName = defaultName.substring(defaultName.lastIndexOf('/') + 1); }
|
|
defaultName = defaultName.replace(/[^\w\s._-]/g, '_').replace(/\s+/g, '_');
|
|
if (!defaultName || defaultName === '_') defaultName = 'lista_guardada';
|
|
|
|
$('#saveM3UNameInput').val(defaultName);
|
|
const saveModal = new bootstrap.Modal(document.getElementById('saveM3UModal'));
|
|
saveModal.show();
|
|
});
|
|
$('#confirmSaveM3UBtn').on('click', handleSaveToDB);
|
|
|
|
$('#openEditorBtn').on('click', launchEditor);
|
|
$('#applyEditorChangesBtn').on('click', () => {
|
|
if (typeof editorHandler !== 'undefined' && typeof editorHandler.getFinalData === 'function') {
|
|
const editorResult = editorHandler.getFinalData();
|
|
channels = editorResult.channels;
|
|
currentGroupOrder = editorResult.groupOrder;
|
|
|
|
regenerateCurrentM3UContentFromString();
|
|
filterAndRenderChannels();
|
|
|
|
showNotification("Cambios del editor aplicados y guardados.", "success");
|
|
const editorModalInstance = bootstrap.Modal.getInstance(document.getElementById('editorModal'));
|
|
if (editorModalInstance) {
|
|
editorModalInstance.hide();
|
|
}
|
|
} else {
|
|
showNotification("Error: No se pudieron aplicar los cambios del editor.", "error");
|
|
}
|
|
});
|
|
|
|
$('#downloadM3UBtnHeader').on('click', downloadCurrentM3U);
|
|
$('#loadOrangeTvBtnHeader').on('click', async () => {
|
|
if (typeof generateM3uOrangeTv === 'function') {
|
|
const orangeTvSourceName = "OrangeTV";
|
|
if (typeof removeChannelsBySourceOrigin === 'function') {
|
|
removeChannelsBySourceOrigin(orangeTvSourceName);
|
|
}
|
|
|
|
const m3uString = await generateM3uOrangeTv();
|
|
if (m3uString && !m3uString.includes("Error general en el proceso") && !m3uString.includes("No se pudieron obtener canales")) {
|
|
if (typeof appendM3UContent === 'function') {
|
|
appendM3UContent(m3uString, orangeTvSourceName);
|
|
}
|
|
} else {
|
|
showNotification('No se generaron canales de OrangeTV o hubo un error durante el proceso.', 'warning');
|
|
if (channels.length === 0) {
|
|
if (typeof filterAndRenderChannels === 'function') filterAndRenderChannels();
|
|
}
|
|
}
|
|
} else {
|
|
showNotification("Función para cargar OrangeTV no encontrada.", "error");
|
|
}
|
|
});
|
|
$('#loadAtresplayerBtnHeader').on('click', async () => {
|
|
if (typeof generateM3UAtresplayer === 'function') {
|
|
await generateM3UAtresplayer();
|
|
} else {
|
|
if (typeof showNotification === 'function') showNotification("Funcionalidad Atresplayer no cargada.", "error");
|
|
}
|
|
});
|
|
$('#loadBarTvBtnHeader').on('click', async () => {
|
|
if (typeof generateM3uBarTv === 'function') {
|
|
await generateM3uBarTv();
|
|
} else {
|
|
if (typeof showNotification === 'function') showNotification("Funcionalidad BarTV no cargada.", "error");
|
|
}
|
|
});
|
|
|
|
|
|
$('#searchInput').on('input', debounce(filterAndRenderChannels, 300));
|
|
|
|
$('#groupFilterSidebar').on('change', async function() {
|
|
const selectedGroup = $(this).val();
|
|
currentPage = 1;
|
|
filterAndRenderChannels();
|
|
if (userSettings.persistFilters) {
|
|
userSettings.lastSelectedGroup = selectedGroup;
|
|
await saveAppConfigValue('userSettings', userSettings);
|
|
}
|
|
});
|
|
|
|
$('#sidebarGroupList').on('click', '.list-group-item', function () {
|
|
const groupName = $(this).data('group-name');
|
|
$('#groupFilterSidebar').val(groupName).trigger('change');
|
|
});
|
|
|
|
$('#showAllChannels').on('click', () => switchFilter('all'));
|
|
$('#showFavorites').on('click', () => switchFilter('favorites'));
|
|
$('#showHistory').on('click', () => switchFilter('history'));
|
|
|
|
$('#openEpgModalBtn').on('click', () => $('#epgModal').modal('show'));
|
|
$('#openMovistarVODModalBtn').on('click', openMovistarVODModal);
|
|
$('#xtreamBackButton').on('click', popNavigationState);
|
|
|
|
$('#updateDaznBtn').on('click', async () => {
|
|
if (typeof orchestrateDaznUpdate === 'function') {
|
|
if (!channels || channels.length === 0) {
|
|
showNotification('Carga una lista M3U que contenga canales de DAZN primero.', 'info');
|
|
return;
|
|
}
|
|
let daznM3uUserAgent = null;
|
|
const daznChannelInM3U = channels.find(ch =>
|
|
(ch.sourceOrigin && ch.sourceOrigin.toLowerCase() === 'dazn') ||
|
|
(ch.url && ch.url.toLowerCase().includes('dazn')) ||
|
|
(ch['tvg-id'] && ch['tvg-id'].toLowerCase().includes('dazn'))
|
|
);
|
|
if (daznChannelInM3U && daznChannelInM3U.vlcOptions && daznChannelInM3U.vlcOptions['http-user-agent']) {
|
|
daznM3uUserAgent = daznChannelInM3U.vlcOptions['http-user-agent'];
|
|
}
|
|
await orchestrateDaznUpdate(daznM3uUserAgent);
|
|
} else {
|
|
showNotification('Error: Funcionalidad DAZN no cargada.', 'error');
|
|
}
|
|
});
|
|
$('#openXtreamModalBtn').on('click', () => {
|
|
if (typeof showXtreamConnectionModal === 'function') showXtreamConnectionModal();
|
|
});
|
|
$('#openManageXCodecPanelsModalBtn').on('click', () => {
|
|
if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal !== 'undefined') {
|
|
const xcodecModalEl = document.getElementById('manageXCodecPanelsModal');
|
|
if (xcodecModalEl) {
|
|
const xcodecModalInstance = bootstrap.Modal.getOrCreateInstance(xcodecModalEl);
|
|
xcodecModalInstance.show();
|
|
if (typeof loadSavedXCodecPanels === 'function') loadSavedXCodecPanels();
|
|
} else {
|
|
showNotification("Error: Modal XCodec no encontrado.", "error");
|
|
}
|
|
} else {
|
|
showNotification("Error: Bootstrap no cargado, no se puede abrir modal XCodec.", "error");
|
|
}
|
|
});
|
|
|
|
$('#openSettingsModalBtn').on('click', () => {
|
|
$('#settingsModal').modal('show');
|
|
if (typeof updateMovistarVodCacheStatsUI === 'function') {
|
|
updateMovistarVodCacheStatsUI();
|
|
}
|
|
});
|
|
|
|
$(document).on('keydown', handleGlobalKeyPress);
|
|
|
|
$('#player-taskbar').on('click', '.taskbar-item', function() {
|
|
const windowId = $(this).data('windowId');
|
|
if (windowId) {
|
|
setActivePlayer(windowId);
|
|
}
|
|
});
|
|
|
|
$('#prevPage').on('click', () => changePage(currentPage - 1));
|
|
$('#nextPage').on('click', () => changePage(currentPage + 1));
|
|
$('#channelGrid').on('click', '.channel-card', handleChannelCardClick);
|
|
|
|
$('#channelGrid').on('mouseenter', '.channel-card', function() {
|
|
if (!userSettings.enableHoverPreview) return;
|
|
const card = $(this);
|
|
if (Object.keys(playerInstances).length > 0) return;
|
|
|
|
if (activeCardPreviewPlayer && activeCardPreviewElement && activeCardPreviewElement[0] !== card[0]) {
|
|
if (typeof destroyActiveCardPreviewPlayer === 'function') destroyActiveCardPreviewPlayer();
|
|
}
|
|
|
|
clearTimeout(hoverPlayTimeout);
|
|
|
|
hoverPlayTimeout = setTimeout(async () => {
|
|
const channelUrl = card.data('url');
|
|
if (!channelUrl) return;
|
|
|
|
const channel = channels.find(c => c.url === channelUrl);
|
|
if (channel) {
|
|
if (channel.attributes && channel.attributes['xtream-type'] === 'series') {
|
|
return;
|
|
}
|
|
if (typeof playChannelInCardPreview === 'function') {
|
|
activeCardPreviewElement = card;
|
|
card.addClass('is-playing-preview');
|
|
await playChannelInCardPreview(channel, card.find('.card-video-preview-container')[0]);
|
|
}
|
|
}
|
|
}, HOVER_PLAY_DELAY);
|
|
});
|
|
|
|
$('#channelGrid').on('mouseleave', '.channel-card', function() {
|
|
clearTimeout(hoverPlayTimeout);
|
|
if (activeCardPreviewElement && activeCardPreviewElement[0] === $(this)[0]) {
|
|
if (typeof destroyActiveCardPreviewPlayer === 'function') destroyActiveCardPreviewPlayer();
|
|
}
|
|
});
|
|
|
|
|
|
$('#channelGrid').on('click', '.favorite-btn', handleFavoriteButtonClick);
|
|
$('#channelGrid').on('error', '.channel-logo', function () {
|
|
this.classList.add('error');
|
|
this.style.display = 'none';
|
|
const placeholder = $(this).siblings('.epg-icon-placeholder');
|
|
if (placeholder.length) { placeholder.show(); }
|
|
else { $(this).parent().addClass('no-logo-fallback'); }
|
|
});
|
|
|
|
$('#saveSettingsBtn').on('click', () => { if(typeof saveUserSettings === 'function') saveUserSettings(); });
|
|
|
|
const rangeInputsSelector = '#epgNameMatchThreshold, #playerBufferInput, #channelCardSizeInput, #abrDefaultBandwidthEstimateInput, #manifestRetryMaxAttemptsInput, #manifestRetryTimeoutInput, #segmentRetryMaxAttemptsInput, #segmentRetryTimeoutInput, #epgDensityInput, #channelsPerPageInput, #particleOpacityInput, #shakaDefaultPresentationDelayInput, #shakaAudioVideoSyncThresholdInput, #playerWindowOpacityInput';
|
|
$(rangeInputsSelector).on('input', function () {
|
|
const id = this.id;
|
|
const value = $(this).val();
|
|
if(id === 'epgNameMatchThreshold') $('#epgNameMatchThresholdValue').text(value + '%');
|
|
if(id === 'playerBufferInput') $('#playerBufferValue').text(value + 's');
|
|
if(id === 'channelCardSizeInput') {
|
|
const size = value + 'px';
|
|
$('#channelCardSizeValue').text(size);
|
|
document.documentElement.style.setProperty('--m3u-grid-minmax-size', size);
|
|
}
|
|
if(id === 'channelsPerPageInput') $('#channelsPerPageValue').text(value);
|
|
if(id === 'abrDefaultBandwidthEstimateInput') $('#abrDefaultBandwidthEstimateValue').text(value + ' Kbps');
|
|
if(id === 'manifestRetryMaxAttemptsInput') $('#manifestRetryMaxAttemptsValue').text(value);
|
|
if(id === 'manifestRetryTimeoutInput') $('#manifestRetryTimeoutValue').text(value);
|
|
if(id === 'segmentRetryMaxAttemptsInput') $('#segmentRetryMaxAttemptsValue').text(value);
|
|
if(id === 'segmentRetryTimeoutInput') $('#segmentRetryTimeoutValue').text(value);
|
|
if(id === 'epgDensityInput') $('#epgDensityValue').text(value + 'px/h');
|
|
if(id === 'particleOpacityInput') $('#particleOpacityValue').text(value + '%');
|
|
if(id === 'shakaDefaultPresentationDelayInput') $('#shakaDefaultPresentationDelayValue').text(parseFloat(value).toFixed(parseFloat(value) % 1 === 0 ? 0 : 1) + 's');
|
|
if(id === 'shakaAudioVideoSyncThresholdInput') $('#shakaAudioVideoSyncThresholdValue').text(parseFloat(value).toFixed(parseFloat(value) % 1 === 0 ? 0 : 2) + 's');
|
|
if(id === 'playerWindowOpacityInput') {
|
|
$('#playerWindowOpacityValue').text(Math.round(value * 100) + '%');
|
|
Object.values(playerInstances).forEach(instance => {
|
|
if (instance.container) {
|
|
instance.container.style.setProperty('--player-window-opacity', value);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
$('#exportSettingsBtn').on('click', () => { if(typeof exportSettings === 'function') exportSettings(); });
|
|
$('#importSettingsInput').on('change', (event) => { if(typeof importSettings === 'function') importSettings(event); });
|
|
$('#clearCacheBtn').on('click', clearCacheAndReload);
|
|
|
|
$('#connectXtreamServerBtn').on('click', () => {
|
|
if (typeof handleConnectXtreamServer === 'function') handleConnectXtreamServer();
|
|
});
|
|
$('#xtreamConfirmGroupSelectionBtn').on('click', () => {
|
|
if (typeof handleXtreamGroupSelection === 'function') handleXtreamGroupSelection();
|
|
});
|
|
$('#saveXtreamServerBtn').on('click', () => {
|
|
if (typeof handleSaveXtreamServer === 'function') handleSaveXtreamServer();
|
|
});
|
|
$('#savedXtreamServersList').on('click', '.load-xtream-server-btn', function() {
|
|
const serverId = parseInt($(this).data('id'), 10);
|
|
if (typeof loadXtreamServerToForm === 'function') loadXtreamServerToForm(serverId);
|
|
});
|
|
$('#savedXtreamServersList').on('click', '.delete-xtream-server-btn', function() {
|
|
const serverId = parseInt($(this).data('id'), 10);
|
|
if (typeof handleDeleteXtreamServer === 'function') handleDeleteXtreamServer(serverId);
|
|
});
|
|
|
|
$('#sendM3UToServerBtn').on('click', () => {
|
|
const urlFromInput = $('#m3uUploadServerUrlInput').val()?.trim();
|
|
if (typeof sendM3UToServer === 'function') {
|
|
sendM3UToServer(urlFromInput);
|
|
} else {
|
|
showNotification("Error: Función para enviar M3U no encontrada.", "error");
|
|
}
|
|
});
|
|
|
|
$('#movistarLoginBtnSettings').on('click', handleMovistarLogin);
|
|
$('#movistarValidateAllBtnSettings').on('click', handleMovistarValidateAllTokens);
|
|
$('#movistarDeleteExpiredBtnSettings').on('click', handleMovistarDeleteExpiredTokens);
|
|
$('#movistarAddManualTokenBtnSettings').on('click', handleMovistarAddManualToken);
|
|
$('#movistarLongTokensTableBodySettings').on('click', '.delete-long-token-btn-settings', function() {
|
|
const tokenId = $(this).closest('tr').data('tokenid');
|
|
if (tokenId) handleMovistarDeleteSingleLongToken(tokenId);
|
|
});
|
|
$('#movistarLongTokensTableBodySettings').on('click', '.validate-long-token-btn-settings', function() {
|
|
const tokenId = $(this).closest('tr').data('tokenid');
|
|
if (tokenId) handleMovistarValidateSingleLongToken(tokenId);
|
|
});
|
|
$('#movistarLongTokensTableBodySettings').on('click', 'tr', function(event) {
|
|
const tokenId = $(this).data('tokenid');
|
|
if (tokenId && !$(event.target).closest('button').length) {
|
|
selectedMovistarLongTokenIdForSettings = tokenId;
|
|
$('#movistarLongTokensTableBodySettings tr').removeClass('table-active');
|
|
$(this).addClass('table-active');
|
|
$('#selectedLongTokenIdDisplaySettings').text(`...${tokenId.slice(-12)}`);
|
|
$('#movistarDeviceManagementSectionSettings').show();
|
|
$('#movistarLoadDevicesForSettingsBtn').prop('disabled', false);
|
|
$('#movistarDevicesListForSettings').html('<div class="list-group-item text-muted text-center">Carga los dispositivos para el token seleccionado arriba.</div>');
|
|
$('#movistarAssociateDeviceForSettingsBtn').prop('disabled', true);
|
|
$('#movistarRegisterNewDeviceForSettingsBtn').prop('disabled', false);
|
|
}
|
|
});
|
|
$('#movistarLoadDevicesForSettingsBtn').on('click', handleMovistarLoadDevicesForSettings);
|
|
$('#movistarAssociateDeviceForSettingsBtn').on('click', handleMovistarAssociateDeviceForSettings);
|
|
$('#movistarRegisterNewDeviceForSettingsBtn').on('click', handleMovistarRegisterNewDeviceForSettings);
|
|
$('#movistarRefreshCdnBtnSettings').on('click', handleMovistarRefreshCdnToken);
|
|
$('#movistarCopyCdnBtnSettings').on('click', handleMovistarCopyCdnToken);
|
|
$('#movistarApplyCdnToChannelsBtnSettings').on('click', handleMovistarApplyCdnToChannels);
|
|
$('#clearMovistarVodCacheBtnSettings').on('click', handleClearMovistarVodCache);
|
|
|
|
|
|
$('#loadMovistarVODBtn').on('click', loadMovistarVODData);
|
|
$('#movistarVODDateInput').on('change', function() {
|
|
movistarVodSelectedDate = new Date($(this).val() + 'T00:00:00');
|
|
loadMovistarVODData();
|
|
});
|
|
$('#movistarVODModal-channel-filter, #movistarVODModal-genre-filter').on('change', renderMovistarVODPrograms);
|
|
$('#movistarVODModal-search-input').on('input', debounce(renderMovistarVODPrograms, 300));
|
|
|
|
$('#movistarVODModal-programs').on('click', '.movistar-vod-card', function() {
|
|
const programArrayIndex = parseInt($(this).data('program-array-index'), 10);
|
|
if (!isNaN(programArrayIndex) && movistarVodFilteredPrograms[programArrayIndex]) {
|
|
const program = movistarVodFilteredPrograms[programArrayIndex];
|
|
if (program && typeof handleMovistarVODProgramClick === 'function') {
|
|
handleMovistarVODProgramClick(program);
|
|
}
|
|
} else {
|
|
showNotification("Error al seleccionar el programa VOD.", "error");
|
|
}
|
|
});
|
|
|
|
$('#movistarVODModal-prev-page').on('click', function() {
|
|
if (movistarVodCurrentPage > 1) {
|
|
movistarVodCurrentPage--;
|
|
displayCurrentMovistarVODPage();
|
|
updateMovistarVODPaginationControls();
|
|
}
|
|
});
|
|
$('#movistarVODModal-next-page').on('click', function() {
|
|
const totalPages = Math.ceil(movistarVodFilteredPrograms.length / MOVISTAR_VOD_ITEMS_PER_PAGE);
|
|
if (movistarVodCurrentPage < totalPages) {
|
|
movistarVodCurrentPage++;
|
|
displayCurrentMovistarVODPage();
|
|
updateMovistarVODPaginationControls();
|
|
}
|
|
});
|
|
}
|
|
|
|
async function applyUISettings() {
|
|
await loadLanguage(userSettings.language);
|
|
applyTranslations();
|
|
|
|
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
|
|
if (typeof applyThemeAndFont === 'function') applyThemeAndFont();
|
|
|
|
const sidebar = $('#sidebar');
|
|
const appContainer = $('#app-container');
|
|
if (userSettings.sidebarCollapsed && window.innerWidth >= 992) {
|
|
sidebar.removeClass('expanded').addClass('collapsed');
|
|
appContainer.addClass('sidebar-collapsed');
|
|
} else if(window.innerWidth >= 992) {
|
|
sidebar.removeClass('collapsed').addClass('expanded');
|
|
appContainer.removeClass('sidebar-collapsed');
|
|
} else {
|
|
sidebar.removeClass('expanded').addClass('collapsed');
|
|
appContainer.addClass('sidebar-collapsed');
|
|
}
|
|
|
|
document.documentElement.style.setProperty('--m3u-grid-minmax-size', userSettings.channelCardSize + 'px');
|
|
document.documentElement.style.setProperty('--card-logo-aspect-ratio', userSettings.cardLogoAspectRatio === 'auto' ? '16/9' : userSettings.cardLogoAspectRatio);
|
|
Object.values(playerInstances).forEach(instance => {
|
|
if (typeof updatePlayerConfigFromSettings === 'function') {
|
|
updatePlayerConfigFromSettings(instance.player);
|
|
}
|
|
if (instance.container) {
|
|
instance.container.style.setProperty('--player-window-opacity', userSettings.playerWindowOpacity);
|
|
}
|
|
});
|
|
if (typeof initParticles === 'function') initParticles();
|
|
if (userSettings.persistFilters) {
|
|
if(userSettings.lastSelectedFilterTab) currentFilter = userSettings.lastSelectedFilterTab;
|
|
}
|
|
|
|
if (channels.length > 0) filterAndRenderChannels();
|
|
}
|
|
|
|
function initParticles() {
|
|
if (typeof particlesJS === 'function' && document.getElementById('particles-js') && !document.getElementById('particles-js').dataset.initialized && userSettings.particlesEnabled) {
|
|
document.getElementById('particles-js').dataset.initialized = 'true';
|
|
const particleColor = getComputedStyle(document.documentElement).getPropertyValue('--accent-secondary').trim();
|
|
const particleLineColor = getComputedStyle(document.documentElement).getPropertyValue('--accent-primary').trim();
|
|
document.documentElement.style.setProperty('--particle-opacity', userSettings.particleOpacity);
|
|
|
|
|
|
particlesJS('particles-js', {
|
|
"particles": {
|
|
"number": { "value": 30, "density": { "enable": true, "value_area": 1200 } },
|
|
"color": { "value": particleColor },
|
|
"shape": { "type": "circle" },
|
|
"opacity": { "value": 1, "random": true, "anim": { "enable": true, "speed": 0.15, "opacity_min": 0.3, "sync": false } },
|
|
"size": { "value": 1.5, "random": true },
|
|
"line_linked": { "enable": true, "distance": 160, "color": particleLineColor, "opacity": 0.5, "width": 1 },
|
|
"move": { "enable": true, "speed": 0.8, "direction": "none", "random": true, "straight": false, "out_mode": "out" }
|
|
},
|
|
"interactivity": { "detect_on": "canvas", "events": { "onhover": { "enable": false }, "onclick": { "enable": false }, "resize": true } },
|
|
"retina_detect": true
|
|
});
|
|
const particlesCanvas = document.querySelector('#particles-js canvas');
|
|
if(particlesCanvas && particlesCanvas.style) {
|
|
particlesCanvas.style.setProperty('opacity', '1', 'important');
|
|
}
|
|
$('#particles-js').removeClass('disabled');
|
|
} else if (!userSettings.particlesEnabled && document.getElementById('particles-js')) {
|
|
$('#particles-js').addClass('disabled');
|
|
if(typeof pJSDom !== 'undefined' && pJSDom.length > 0 && pJSDom[0].pJS) {
|
|
pJSDom[0].pJS.fn.vendors.destroypJS();
|
|
pJSDom = [];
|
|
if (document.getElementById('particles-js')) document.getElementById('particles-js').dataset.initialized = 'false';
|
|
}
|
|
}
|
|
}
|
|
|
|
window.updateM3UWithDaznData = function(daznChannelDetailsList) {
|
|
if (!channels || channels.length === 0) {
|
|
if(typeof showNotification === 'function') showNotification('DAZN: No hay lista M3U cargada para actualizar.', 'info');
|
|
return;
|
|
}
|
|
if (!daznChannelDetailsList || daznChannelDetailsList.length === 0) {
|
|
if(typeof showNotification === 'function') showNotification('DAZN: No se recibieron datos de canales para la actualización.', 'info');
|
|
return;
|
|
}
|
|
|
|
let updatedCount = 0;
|
|
const daznDataMapByLinearId = new Map();
|
|
daznChannelDetailsList.forEach(daznChannel => {
|
|
if (daznChannel.daznLinearId) {
|
|
daznDataMapByLinearId.set(daznChannel.daznLinearId, daznChannel);
|
|
}
|
|
});
|
|
|
|
channels.forEach(m3uChannel => {
|
|
let currentChannelLinearId = null;
|
|
if (m3uChannel.url) {
|
|
const urlMatch = m3uChannel.url.match(/dazn-linear-(\d+)/);
|
|
if (urlMatch && urlMatch[1]) {
|
|
currentChannelLinearId = urlMatch[1];
|
|
}
|
|
}
|
|
|
|
if (!currentChannelLinearId && m3uChannel['tvg-id']) {
|
|
const tvgIdMatch = String(m3uChannel['tvg-id']).match(/dazn-linear-(\d+)/i);
|
|
if (tvgIdMatch && tvgIdMatch[1]) {
|
|
currentChannelLinearId = tvgIdMatch[1];
|
|
}
|
|
}
|
|
|
|
if (currentChannelLinearId && daznDataMapByLinearId.has(currentChannelLinearId)) {
|
|
const daznUpdate = daznDataMapByLinearId.get(currentChannelLinearId);
|
|
|
|
m3uChannel.url = daznUpdate.baseUrl;
|
|
|
|
if (!m3uChannel.kodiProps) {
|
|
m3uChannel.kodiProps = {};
|
|
}
|
|
if (!m3uChannel.vlcOptions) {
|
|
m3uChannel.vlcOptions = {};
|
|
}
|
|
|
|
if (daznUpdate.cdnTokenName && daznUpdate.cdnTokenValue) {
|
|
m3uChannel.kodiProps['inputstream.adaptive.stream_headers'] = `${daznUpdate.cdnTokenName}=${daznUpdate.cdnTokenValue}`;
|
|
} else {
|
|
delete m3uChannel.kodiProps['inputstream.adaptive.stream_headers'];
|
|
}
|
|
|
|
if (daznUpdate.streamUserAgent) {
|
|
m3uChannel.vlcOptions['http-user-agent'] = daznUpdate.streamUserAgent;
|
|
}
|
|
m3uChannel.sourceOrigin = "DAZN";
|
|
|
|
updatedCount++;
|
|
}
|
|
});
|
|
|
|
if (updatedCount > 0) {
|
|
if(typeof showNotification === 'function') showNotification(`DAZN: ${updatedCount} canales actualizados en tu lista M3U.`, 'success');
|
|
regenerateCurrentM3UContentFromString();
|
|
filterAndRenderChannels();
|
|
} else {
|
|
if(typeof showNotification === 'function') showNotification('DAZN: No se encontraron canales en tu M3U que coincidieran con los datos de DAZN para actualizar.', 'info');
|
|
}
|
|
};
|
|
|
|
function logToMovistarSettingsUI(message, type = 'info') {
|
|
const logArea = $('#movistarLogAreaSettings');
|
|
if (logArea.length) {
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
const existingLog = logArea.val();
|
|
const newLog = `[${timestamp}] ${message}\n`;
|
|
logArea.val(existingLog + newLog);
|
|
logArea.scrollTop(logArea[0].scrollHeight);
|
|
}
|
|
}
|
|
|
|
async function loadAndDisplayInitialMovistarStatus() {
|
|
if (!window.MovistarTokenHandler) return;
|
|
logToMovistarSettingsUI("Cargando estado inicial de Movistar+...", "info");
|
|
try {
|
|
const status = await window.MovistarTokenHandler.getShortTokenStatus();
|
|
updateMovistarCdnTokenUI(status.token, status.expiry);
|
|
await loadAndRenderLongTokensListSettings();
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error cargando estado inicial: ${error.message}`, "error");
|
|
}
|
|
}
|
|
|
|
function updateMovistarCdnTokenUI(token, expiryTimestamp) {
|
|
$('#movistarCdnTokenDisplaySettings').val(token || "");
|
|
const expiryDate = expiryTimestamp ? new Date(expiryTimestamp * 1000) : null;
|
|
if (expiryDate && expiryTimestamp > Math.floor(Date.now() / 1000)) {
|
|
$('#movistarCdnTokenExpirySettings').text(`${chrome.i18n.getMessage("movistarExpiresHeader") || "Expira"}: ${expiryDate.toLocaleString()}`);
|
|
$('#movistarCdnTokenExpirySettings').removeClass('text-danger').addClass('text-success');
|
|
} else if (expiryDate) {
|
|
$('#movistarCdnTokenExpirySettings').text(`Expirado: ${expiryDate.toLocaleString()}`);
|
|
$('#movistarCdnTokenExpirySettings').removeClass('text-success').addClass('text-danger');
|
|
} else {
|
|
$('#movistarCdnTokenExpirySettings').text(`${chrome.i18n.getMessage("movistarExpiresHeader") || "Expira"}: -`);
|
|
$('#movistarCdnTokenExpirySettings').removeClass('text-success text-danger');
|
|
}
|
|
$('#movistarCopyCdnBtnSettings').prop('disabled', !token);
|
|
$('#movistarApplyCdnToChannelsBtnSettings').prop('disabled', !token || (expiryTimestamp <= Math.floor(Date.now()/1000)));
|
|
}
|
|
|
|
async function loadAndRenderLongTokensListSettings() {
|
|
if (!window.MovistarTokenHandler) return;
|
|
const tbody = $('#movistarLongTokensTableBodySettings');
|
|
tbody.html(`<tr><td colspan="6" class="text-center p-3"><i class="fas fa-spinner fa-spin"></i> ${chrome.i18n.getMessage("movistarLoading") || "Cargando..."}</td></tr>`);
|
|
selectedMovistarLongTokenIdForSettings = null;
|
|
$('#movistarDeviceManagementSectionSettings').hide();
|
|
$('#movistarLoadDevicesForSettingsBtn').prop('disabled', true);
|
|
|
|
try {
|
|
const tokens = await window.MovistarTokenHandler.getAllLongTokens();
|
|
if (tokens.length === 0) {
|
|
tbody.html(`<tr><td colspan="6" class="text-center p-3 text-muted">${chrome.i18n.getMessage("xtreamNoSavedServers") || "No hay tokens largos guardados."}</td></tr>`);
|
|
return;
|
|
}
|
|
tokens.sort((a, b) => (b.expiry_tstamp || 0) - (a.expiry_tstamp || 0));
|
|
tbody.empty();
|
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
|
|
tokens.forEach(token => {
|
|
const isExpired = (token.expiry_tstamp || 0) < nowSeconds;
|
|
const expiryDate = token.expiry_tstamp ? new Date(token.expiry_tstamp * 1000) : null;
|
|
const expiryString = expiryDate ? expiryDate.toLocaleDateString() : 'N/D';
|
|
let statusText = isExpired ? 'Expirado' : (token.device_id ? 'Válido' : 'Sin DeviceID');
|
|
let statusClass = isExpired ? 'text-danger' : (token.device_id ? 'text-success' : 'text-warning');
|
|
|
|
const tr = $('<tr>').data('tokenid', token.id).css('cursor', 'pointer');
|
|
tr.append($('<td>').text(`...${token.id.slice(-12)}`).attr('title', token.id));
|
|
tr.append($('<td>').text(token.account_nbr || 'N/A'));
|
|
tr.append($('<td>').text(token.device_id ? `...${token.device_id.slice(-6)}` : 'NULO').attr('title', token.device_id || 'Sin Device ID'));
|
|
tr.append($('<td>').text(expiryString));
|
|
tr.append($('<td>').addClass(statusClass).text(statusText));
|
|
tr.append($('<td>')
|
|
.append($('<button>').addClass('btn btn-outline-danger btn-sm delete-long-token-btn-settings me-1').attr('title', 'Eliminar').html('<i class="fas fa-trash"></i>'))
|
|
.append($('<button>').addClass('btn btn-outline-info btn-sm validate-long-token-btn-settings').attr('title', 'Validar').html('<i class="fas fa-check"></i>'))
|
|
);
|
|
tbody.append(tr);
|
|
});
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error cargando lista de tokens largos: ${error.message}`, "error");
|
|
tbody.html(`<tr><td colspan="6" class="text-center p-3 text-danger">Error: ${escapeHtml(error.message)}</td></tr>`);
|
|
}
|
|
}
|
|
|
|
async function handleMovistarLogin() {
|
|
const username = $('#movistarUsernameSettingsInput').val();
|
|
const password = $('#movistarPasswordSettingsInput').val();
|
|
logToMovistarSettingsUI("Iniciando login...", "info");
|
|
showLoading(true, "Iniciando sesión Movistar+...");
|
|
|
|
const result = await window.MovistarTokenHandler.loginAndGetTokens(username, password);
|
|
|
|
showLoading(false);
|
|
logToMovistarSettingsUI(result.message, result.success ? "success" : "error");
|
|
showNotification(result.message, result.success ? "success" : "error");
|
|
|
|
if (result.success) {
|
|
updateMovistarCdnTokenUI(result.shortToken, result.shortTokenExpiry);
|
|
await loadAndRenderLongTokensListSettings();
|
|
$('#movistarUsernameSettingsInput').val('');
|
|
$('#movistarPasswordSettingsInput').val('');
|
|
}
|
|
}
|
|
|
|
async function handleMovistarValidateAllTokens() {
|
|
logToMovistarSettingsUI("Validando todos los tokens largos...", "info");
|
|
showLoading(true, "Validando tokens...");
|
|
const result = await window.MovistarTokenHandler.validateAllLongTokens();
|
|
showLoading(false);
|
|
let summary = `Validación completa: ${result.validated} validados, ${result.functional} funcionales, ${result.expired} expirados, ${result.noDeviceId} sin Device ID.`;
|
|
if(result.refreshed > 0 || result.refreshErrors > 0) {
|
|
summary += ` Refrescos: ${result.refreshed} éxitos, ${result.refreshErrors} fallos.`;
|
|
}
|
|
logToMovistarSettingsUI(summary, "info");
|
|
showNotification(summary, "info", 6000);
|
|
await loadAndRenderLongTokensListSettings();
|
|
}
|
|
|
|
async function handleMovistarDeleteExpiredTokens() {
|
|
logToMovistarSettingsUI("Eliminando tokens largos expirados...", "info");
|
|
const userConfirmed = await showConfirmationModal(
|
|
"¿Seguro que quieres eliminar todos los tokens largos expirados?",
|
|
"Confirmar Eliminación", "Sí, Eliminar Expirados", "btn-warning"
|
|
);
|
|
if (!userConfirmed) {
|
|
logToMovistarSettingsUI("Eliminación de expirados cancelada.", "info");
|
|
return;
|
|
}
|
|
showLoading(true, "Eliminando tokens expirados...");
|
|
const deletedCount = await window.MovistarTokenHandler.deleteExpiredLongTokens();
|
|
showLoading(false);
|
|
logToMovistarSettingsUI(`${deletedCount} tokens expirados eliminados.`, "info");
|
|
showNotification(`${deletedCount} tokens expirados eliminados.`, "success");
|
|
await loadAndRenderLongTokensListSettings();
|
|
}
|
|
|
|
async function handleMovistarAddManualToken() {
|
|
const jwt = $('#movistarAddManualTokenJwtInputSettings').val().trim();
|
|
const deviceId = $('#movistarAddManualTokenDeviceIdInputSettings').val().trim() || null;
|
|
if (!jwt) {
|
|
showNotification("Por favor, pega el token JWT largo.", "warning");
|
|
return;
|
|
}
|
|
logToMovistarSettingsUI(`Intentando añadir token manual: ${jwt.substring(0,20)}...`, "info");
|
|
showLoading(true, "Añadiendo token...");
|
|
try {
|
|
await window.MovistarTokenHandler.addLongTokenManually(jwt, deviceId);
|
|
logToMovistarSettingsUI("Token manual añadido con éxito.", "success");
|
|
showNotification("Token manual añadido con éxito.", "success");
|
|
$('#movistarAddManualTokenJwtInputSettings').val('');
|
|
$('#movistarAddManualTokenDeviceIdInputSettings').val('');
|
|
await loadAndRenderLongTokensListSettings();
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error añadiendo token manual: ${error.message}`, "error");
|
|
showNotification(`Error añadiendo token: ${error.message}`, "error");
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleMovistarDeleteSingleLongToken(tokenId) {
|
|
logToMovistarSettingsUI(`Intentando eliminar token ${tokenId.slice(-12)}...`, "info");
|
|
const userConfirmed = await showConfirmationModal(
|
|
`¿Seguro que quieres eliminar el token largo con ID ...${tokenId.slice(-12)}?`,
|
|
"Confirmar Eliminación", "Sí, Eliminar Token", "btn-danger"
|
|
);
|
|
if (!userConfirmed) {
|
|
logToMovistarSettingsUI("Eliminación cancelada.", "info");
|
|
return;
|
|
}
|
|
showLoading(true, "Eliminando token...");
|
|
try {
|
|
await window.MovistarTokenHandler.deleteLongToken(tokenId);
|
|
logToMovistarSettingsUI(`Token ${tokenId.slice(-12)} eliminado.`, "success");
|
|
showNotification(`Token ${tokenId.slice(-12)} eliminado.`, "success");
|
|
await loadAndRenderLongTokensListSettings();
|
|
if (selectedMovistarLongTokenIdForSettings === tokenId) {
|
|
selectedMovistarLongTokenIdForSettings = null;
|
|
$('#movistarDeviceManagementSectionSettings').hide();
|
|
$('#movistarLoadDevicesForSettingsBtn').prop('disabled', true);
|
|
}
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error eliminando token: ${error.message}`, "error");
|
|
showNotification(`Error eliminando token: ${error.message}`, "error");
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleMovistarValidateSingleLongToken(tokenId) {
|
|
logToMovistarSettingsUI(`Validando token ${tokenId.slice(-12)}...`, "info");
|
|
showLoading(true, `Validando token ...${tokenId.slice(-12)}`);
|
|
const tokens = await MovistarTokenHandler.getAllLongTokens();
|
|
const token = tokens.find(t => t.id === tokenId);
|
|
showLoading(false);
|
|
if (token) {
|
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
const isExpired = (token.expiry_tstamp || 0) < nowSeconds;
|
|
let msg = `Token ...${tokenId.slice(-12)}: `;
|
|
if (isExpired) msg += "Expirado.";
|
|
else if (!token.device_id) msg += "Válido pero SIN Device ID.";
|
|
else msg += "Válido y funcional.";
|
|
logToMovistarSettingsUI(msg, "info");
|
|
showNotification(msg, "info");
|
|
} else {
|
|
logToMovistarSettingsUI(`Token ...${tokenId.slice(-12)} no encontrado para validar.`, "warning");
|
|
showNotification(`Token ...${tokenId.slice(-12)} no encontrado.`, "warning");
|
|
}
|
|
await loadAndRenderLongTokensListSettings();
|
|
}
|
|
|
|
async function handleMovistarLoadDevicesForSettings() {
|
|
if (!selectedMovistarLongTokenIdForSettings) {
|
|
showNotification("Selecciona un token largo de la lista primero.", "warning");
|
|
return;
|
|
}
|
|
logToMovistarSettingsUI(`Cargando dispositivos para token ${selectedMovistarLongTokenIdForSettings.slice(-12)}...`, "info");
|
|
showLoading(true, "Cargando dispositivos...");
|
|
$('#movistarDevicesListForSettings').html('<div class="list-group-item text-muted text-center"><i class="fas fa-spinner fa-spin"></i> Cargando...</div>');
|
|
$('#movistarAssociateDeviceForSettingsBtn').prop('disabled', true);
|
|
|
|
try {
|
|
const devices = await window.MovistarTokenHandler.getDevicesForToken(selectedMovistarLongTokenIdForSettings);
|
|
$('#movistarDevicesListForSettings').empty();
|
|
if (devices.length === 0) {
|
|
$('#movistarDevicesListForSettings').html('<div class="list-group-item text-muted text-center">No se encontraron dispositivos para esta cuenta.</div>');
|
|
} else {
|
|
devices.forEach(dev => {
|
|
const item = $(`
|
|
<label class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<input class="form-check-input me-2" type="radio" name="movistarDeviceForSettings" value="${escapeHtml(dev.id)}" id="devRadioSettings_${dev.id.replace(/[^a-zA-Z0-9]/g, '')}">
|
|
<strong>${escapeHtml(dev.name)}</strong> <small class="text-muted">(...${escapeHtml(dev.id.slice(-6))}, ${escapeHtml(dev.type)})</small>
|
|
</div>
|
|
${dev.is_associated ? '<span class="badge bg-success rounded-pill">Asociado</span>' : ''}
|
|
</label>
|
|
`);
|
|
item.find('input').on('change', function() {
|
|
$('#movistarAssociateDeviceForSettingsBtn').prop('disabled', !this.checked);
|
|
});
|
|
$('#movistarDevicesListForSettings').append(item);
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error cargando dispositivos: ${error.message}`, "error");
|
|
showNotification(`Error cargando dispositivos: ${error.message}`, "error");
|
|
$('#movistarDevicesListForSettings').html(`<div class="list-group-item text-danger text-center">Error: ${escapeHtml(error.message)}</div>`);
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleMovistarAssociateDeviceForSettings() {
|
|
const selectedDeviceId = $('input[name="movistarDeviceForSettings"]:checked').val();
|
|
if (!selectedMovistarLongTokenIdForSettings || !selectedDeviceId) {
|
|
showNotification("Selecciona un token largo y un dispositivo para asociar.", "warning");
|
|
return;
|
|
}
|
|
logToMovistarSettingsUI(`Asociando dispositivo ${selectedDeviceId.slice(-6)} a token ${selectedMovistarLongTokenIdForSettings.slice(-12)}...`, "info");
|
|
showLoading(true, "Asociando dispositivo...");
|
|
try {
|
|
await window.MovistarTokenHandler.associateDeviceToToken(selectedMovistarLongTokenIdForSettings, selectedDeviceId);
|
|
logToMovistarSettingsUI("Dispositivo asociado con éxito.", "success");
|
|
showNotification("Dispositivo asociado con éxito.", "success");
|
|
await loadAndRenderLongTokensListSettings();
|
|
await handleMovistarLoadDevicesForSettings();
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error asociando dispositivo: ${error.message}`, "error");
|
|
showNotification(`Error asociando dispositivo: ${error.message}`, "error");
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleMovistarRegisterNewDeviceForSettings() {
|
|
if (!selectedMovistarLongTokenIdForSettings) {
|
|
showNotification("Selecciona un token largo de la lista primero.", "warning");
|
|
return;
|
|
}
|
|
logToMovistarSettingsUI(`Registrando nuevo dispositivo para token ${selectedMovistarLongTokenIdForSettings.slice(-12)}...`, "info");
|
|
const userConfirmed = await showConfirmationModal(
|
|
"Esto intentará registrar un NUEVO dispositivo en tu cuenta Movistar+ y asociarlo a este token largo. Puede fallar si has alcanzado el límite de dispositivos. ¿Continuar?",
|
|
"Confirmar Registro de Nuevo Dispositivo", "Sí, Registrar Nuevo", "btn-warning"
|
|
);
|
|
if (!userConfirmed) {
|
|
logToMovistarSettingsUI("Registro de nuevo dispositivo cancelado.", "info");
|
|
return;
|
|
}
|
|
showLoading(true, "Registrando nuevo dispositivo...");
|
|
try {
|
|
await window.MovistarTokenHandler.registerAndAssociateNewDeviceToToken(selectedMovistarLongTokenIdForSettings);
|
|
logToMovistarSettingsUI("Nuevo dispositivo registrado y asociado con éxito.", "success");
|
|
showNotification("Nuevo dispositivo registrado y asociado.", "success");
|
|
await loadAndRenderLongTokensListSettings();
|
|
await handleMovistarLoadDevicesForSettings();
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error registrando nuevo dispositivo: ${error.message}`, "error");
|
|
showNotification(`Error registrando dispositivo: ${error.message}`, "error");
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleMovistarRefreshCdnToken() {
|
|
logToMovistarSettingsUI("Refrescando token CDN...", "info");
|
|
showLoading(true, "Refrescando Token CDN...");
|
|
const result = await window.MovistarTokenHandler.refreshCdnToken(true);
|
|
showLoading(false);
|
|
logToMovistarSettingsUI(result.message, result.success ? "success" : "error");
|
|
showNotification(result.message, result.success ? "success" : "error");
|
|
if (result.success) {
|
|
updateMovistarCdnTokenUI(result.shortToken, result.shortTokenExpiry);
|
|
}
|
|
}
|
|
|
|
async function handleMovistarCopyCdnToken() {
|
|
const tokenToCopy = $('#movistarCdnTokenDisplaySettings').val();
|
|
if (!tokenToCopy) {
|
|
showNotification("No hay token CDN para copiar.", "warning");
|
|
return;
|
|
}
|
|
try {
|
|
await navigator.clipboard.writeText(tokenToCopy);
|
|
showNotification("Token CDN copiado al portapapeles.", "success");
|
|
logToMovistarSettingsUI("Token CDN copiado.", "info");
|
|
} catch (err) {
|
|
showNotification("Error al copiar. Revisa la consola.", "error");
|
|
logToMovistarSettingsUI(`Error al copiar token: ${err.message}`, "error");
|
|
}
|
|
}
|
|
|
|
async function handleMovistarApplyCdnToChannels() {
|
|
logToMovistarSettingsUI("Aplicando token CDN a canales Movistar+...", "info");
|
|
const status = await window.MovistarTokenHandler.getShortTokenStatus();
|
|
if (!status.token || status.expiry <= Math.floor(Date.now()/1000)) {
|
|
showNotification("El token CDN actual no es válido o ha expirado. Refréscalo primero.", "warning");
|
|
logToMovistarSettingsUI("Aplicación cancelada: token CDN no válido/expirado.", "warning");
|
|
return;
|
|
}
|
|
if (!channels || channels.length === 0) {
|
|
showNotification("No hay lista M3U cargada para aplicar el token.", "info");
|
|
logToMovistarSettingsUI("Aplicación cancelada: no hay canales cargados.", "info");
|
|
return;
|
|
}
|
|
|
|
showLoading(true, "Aplicando token a canales...");
|
|
let updatedCount = 0;
|
|
const tokenHeaderString = `X-TCDN-Token=${status.token}`;
|
|
|
|
channels.forEach(channel => {
|
|
if (channel && channel.url && (channel.url.toLowerCase().includes('telefonica.com') || channel.url.toLowerCase().includes('movistarplus.es')) ) {
|
|
channel.kodiProps = channel.kodiProps || {};
|
|
let headers = channel.kodiProps['inputstream.adaptive.stream_headers'] || '';
|
|
let headerParts = headers.split('&').filter(part => part && !part.toLowerCase().startsWith('x-tcdn-token='));
|
|
headerParts.push(tokenHeaderString);
|
|
channel.kodiProps['inputstream.adaptive.stream_headers'] = headerParts.join('&');
|
|
channel.sourceOrigin = "Movistar+";
|
|
updatedCount++;
|
|
}
|
|
});
|
|
|
|
if (updatedCount > 0) {
|
|
showNotification(`Token CDN aplicado a ${updatedCount} canales de Movistar+.`, "success");
|
|
logToMovistarSettingsUI(`Token aplicado a ${updatedCount} canales.`, "success");
|
|
regenerateCurrentM3UContentFromString();
|
|
filterAndRenderChannels();
|
|
} else {
|
|
showNotification("No se encontraron canales de Movistar+ para aplicar el token.", "info");
|
|
logToMovistarSettingsUI("No se encontraron canales Movistar+.", "info");
|
|
}
|
|
showLoading(false);
|
|
}
|
|
|
|
async function handleClearMovistarVodCache() {
|
|
logToMovistarSettingsUI("Limpiando caché VOD Movistar+...", "info");
|
|
const userConfirmed = await showConfirmationModal(
|
|
"¿Seguro que quieres eliminar TODOS los datos de caché de Movistar VOD guardados localmente?",
|
|
"Confirmar Limpieza de Caché VOD", "Sí, Limpiar Caché", "btn-danger"
|
|
);
|
|
if (!userConfirmed) {
|
|
logToMovistarSettingsUI("Limpieza de caché VOD cancelada.", "info");
|
|
return;
|
|
}
|
|
showLoading(true, "Limpiando caché VOD...");
|
|
try {
|
|
if (typeof clearMovistarVodCacheFromDB === 'function') {
|
|
await clearMovistarVodCacheFromDB();
|
|
logToMovistarSettingsUI("Caché VOD Movistar+ limpiada con éxito.", "success");
|
|
showNotification("Caché VOD Movistar+ limpiada.", "success");
|
|
if (typeof updateMovistarVodCacheStatsUI === 'function') {
|
|
updateMovistarVodCacheStatsUI();
|
|
}
|
|
} else {
|
|
throw new Error("Función de limpieza de caché no encontrada.");
|
|
}
|
|
} catch (error) {
|
|
logToMovistarSettingsUI(`Error limpiando caché VOD: ${error.message}`, "error");
|
|
showNotification(`Error limpiando caché VOD: ${error.message}`, "error");
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
function renderCurrentView() {
|
|
const mainContentEl = $('#main-content');
|
|
if (mainContentEl.length) mainContentEl.scrollTop(0);
|
|
|
|
$('#xtreamBackButton').toggle(navigationHistory.length > 0);
|
|
|
|
if (currentView.type === 'main') {
|
|
filterAndRenderChannels();
|
|
} else if (currentView.type === 'season_list' || currentView.type === 'episode_list') {
|
|
renderXtreamContent(currentView.data, currentView.title);
|
|
}
|
|
}
|
|
|
|
function pushNavigationState() {
|
|
navigationHistory.push(JSON.parse(JSON.stringify(currentView)));
|
|
}
|
|
|
|
function popNavigationState() {
|
|
if (navigationHistory.length > 0) {
|
|
currentView = navigationHistory.pop();
|
|
renderCurrentView();
|
|
}
|
|
}
|
|
|
|
function displayXtreamInfoBar(data) {
|
|
const infoBar = $('#xtream-info-bar');
|
|
if (!data || !data.user_info || !data.server_info) {
|
|
infoBar.hide();
|
|
return;
|
|
}
|
|
|
|
const userInfo = data.user_info;
|
|
const serverInfo = data.server_info;
|
|
|
|
let expDate = 'Permanente';
|
|
if (userInfo.exp_date && userInfo.exp_date !== 'null') {
|
|
expDate = new Date(parseInt(userInfo.exp_date, 10) * 1000).toLocaleDateString();
|
|
}
|
|
|
|
const html = `
|
|
<span title="Usuario"><i class="fas fa-user"></i> ${escapeHtml(userInfo.username)}</span>
|
|
<span title="Estado"><i class="fas fa-check-circle"></i> ${escapeHtml(userInfo.status)}</span>
|
|
<span title="Expira"><i class="fas fa-calendar-alt"></i> ${escapeHtml(expDate)}</span>
|
|
<span title="Conexiones Activas/Máximas"><i class="fas fa-network-wired"></i> ${escapeHtml(userInfo.active_cons)} / ${escapeHtml(userInfo.max_connections)}</span>
|
|
<span title="Servidor"><i class="fas fa-server"></i> ${escapeHtml(serverInfo.url)}:${escapeHtml(serverInfo.port)}</span>
|
|
`;
|
|
|
|
infoBar.html(html).show();
|
|
}
|
|
|
|
function hideXtreamInfoBar() {
|
|
$('#xtream-info-bar').hide().empty();
|
|
}
|
|
|
|
function setActivePlayer(id) {
|
|
if (activePlayerId === id) {
|
|
const instanceToToggle = playerInstances[id];
|
|
if (instanceToToggle && instanceToToggle.container.style.display === 'none') {
|
|
instanceToToggle.container.style.display = 'flex';
|
|
highestZIndex++;
|
|
instanceToToggle.container.style.zIndex = highestZIndex;
|
|
}
|
|
return;
|
|
}
|
|
|
|
activePlayerId = id;
|
|
|
|
Object.keys(playerInstances).forEach(instanceId => {
|
|
const instance = playerInstances[instanceId];
|
|
if (!instance) return;
|
|
|
|
const isNowActive = instanceId === activePlayerId;
|
|
const taskbarItem = document.getElementById(`taskbar-item-${instanceId}`);
|
|
|
|
if (isNowActive) {
|
|
highestZIndex++;
|
|
instance.container.style.zIndex = highestZIndex;
|
|
instance.container.classList.add('active');
|
|
if (instance.container.style.display === 'none') {
|
|
instance.container.style.display = 'flex';
|
|
}
|
|
} else {
|
|
instance.container.classList.remove('active');
|
|
}
|
|
|
|
if (instance.videoElement) {
|
|
instance.videoElement.muted = !isNowActive;
|
|
}
|
|
if (taskbarItem) {
|
|
taskbarItem.classList.toggle('active', isNowActive);
|
|
}
|
|
});
|
|
} |