2025-06-19 04:02:44 +02:00
<!DOCTYPE html>
< html lang = "es" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title data-lang-key = "pageTitle" > DRM player | Player Avanzado< / title >
< link href = "libs/bootstrap.min.css" rel = "stylesheet" >
< link rel = "stylesheet" href = "libs/controls.css" >
< link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" >
< link rel = "stylesheet" href = "css/base.css" >
< link rel = "stylesheet" href = "css/layout.css" >
< link rel = "stylesheet" href = "css/sidebar.css" >
< link rel = "stylesheet" href = "css/header.css" >
< link rel = "stylesheet" href = "css/channel_grid.css" >
< link rel = "stylesheet" href = "css/channel_card.css" >
< link rel = "stylesheet" href = "css/modals_general.css" >
< link rel = "stylesheet" href = "css/player_modal.css" >
< link rel = "stylesheet" href = "css/epg_modal.css" >
< link rel = "stylesheet" href = "css/movistar_vod_modal.css" >
< link rel = "stylesheet" href = "css/settings_modal.css" >
< link rel = "stylesheet" href = "css/xtream_modal.css" >
< link rel = "stylesheet" href = "css/generic_modals.css" >
< link rel = "stylesheet" href = "css/components.css" >
< link rel = "stylesheet" href = "css/responsive.css" >
< link rel = "stylesheet" href = "css/editor.css" >
2025-06-25 13:32:28 +02:00
< link rel = "stylesheet" href = "css/eq_panel.css" >
2025-06-19 04:02:44 +02:00
< / head >
< body id = "appBody" >
< div id = "particles-js" > < / div >
< div id = "app-container" >
< aside id = "sidebar" >
< div class = "sidebar-header" > < a class = "sidebar-logo" href = "#" data-lang-key = "appName" > DRM Player< / a > < / div >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "filterGroupsLabel" > Filtrar Grupos< / h6 >
< select class = "form-select form-select-sm group-filter mb-3" id = "groupFilterSidebar" >
< option value = "" data-lang-key = "allGroupsOption" > 📂 Todos los grupos< / option >
< / select >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "groupsLabel" > Grupos< / h6 >
< ul class = "list-group group-list-sidebar" id = "sidebarGroupList" >
< li class = "list-group-item active" data-group-name = "" data-lang-key = "allGroupsListItem" > Todos los Grupos< / li >
< / ul >
< / aside >
< div id = "main-content-wrapper" >
< header id = "top-header" >
< button class = "sidebar-toggle-btn" id = "sidebarToggleBtn" title = "Toggle Sidebar" > < / button >
< div class = "header-search-bar" >
< input type = "text" class = "header-search-input" id = "searchInput" placeholder = "Buscar canales..." data-lang-key = "searchPlaceholder" data-lang-attr = "placeholder" >
< span class = "header-search-icon" > < / span >
< / div >
< div class = "header-actions" >
< button class = "btn-header-action" id = "openEditorBtn" title = "Editor Avanzado" >
< span class = "icon-placeholder" style = "font-family: FontAwesome;" > < / span > < span class = "btn-text" data-lang-key = "advancedEditorButton" > Editor< / span >
< / button >
< div class = "dropdown" >
< button class = "btn-header-action dropdown-toggle" type = "button" id = "providersDropdown" data-bs-toggle = "dropdown" aria-expanded = "false" title = "Proveedores de Contenido" >
< span class = "icon-placeholder" style = "font-family: sans-serif;" > 📡< / span >
< span class = "btn-text" data-lang-key = "providersButton" > Proveedores< / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-dark dropdown-menu-end" aria-labelledby = "providersDropdown" >
< li > < button class = "dropdown-item" id = "loadAtresplayerBtnHeader" type = "button" > < span class = "icon-placeholder me-2" style = "font-family: initial; font-weight:bold;" > A< / span > Atresplayer< / button > < / li >
< li > < button class = "dropdown-item" id = "openMovistarVODModalBtn" type = "button" > < span class = "icon-placeholder me-2" style = "font-style: normal;" > Ⓜ️< / span > Movistar VOD< / button > < / li >
< li > < button class = "dropdown-item" id = "loadOrangeTvBtnHeader" type = "button" > < span class = "icon-placeholder me-2" style = "font-style: normal;" > 🍊< / span > OrangeTV< / button > < / li >
< li > < button class = "dropdown-item" id = "updateDaznBtn" type = "button" > < span class = "icon-placeholder me-2" style = "font-style: normal;" > 📺< / span > DAZN< / button > < / li >
< li > < button class = "dropdown-item" id = "loadBarTvBtnHeader" type = "button" > < span class = "icon-placeholder me-2" style = "font-style: normal;" > 🍺< / span > BarTV< / button > < / li >
< / ul >
< / div >
< button class = "btn-header-action" id = "openXtreamModalBtn" title = "Conectar a Xtream" >
< span class = "icon-placeholder" > < / span > < span class = "btn-text" > Xtream< / span >
< / button >
< button class = "btn-header-action" id = "openManageXCodecPanelsModalBtn" title = "Abrir Panel XCodec" >
< span class = "icon-placeholder" style = "font-style:normal;" > ⚙️< / span > < span class = "btn-text" > XCodec< / span >
< / button >
< div class = "dropdown" >
< button class = "btn-header-action dropdown-toggle" type = "button" id = "listManagementDropdown" data-bs-toggle = "dropdown" aria-expanded = "false" title = "Gestión de Listas" >
< span class = "icon-placeholder" style = "font-family: sans-serif;" > 📂< / span > < span class = "btn-text" data-lang-key = "listManagementButton" > Listas< / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-dark dropdown-menu-end" aria-labelledby = "listManagementDropdown" >
< li > < button class = "dropdown-item" id = "loadFromDBBtnHeader" type = "button" > < span class = "icon-placeholder me-2" > 💾< / span > < span data-lang-key = "loadListsButton" > Cargar Listas< / span > < / button > < / li >
< li > < button class = "dropdown-item" id = "saveToDBBtnHeader" type = "button" > < span class = "icon-placeholder me-2" > 🗳️< / span > < span data-lang-key = "saveListsButton" > Guardar Listas< / span > < / button > < / li >
< li > < hr class = "dropdown-divider" > < / li >
< li > < button class = "dropdown-item" id = "downloadM3UBtnHeader" type = "button" > < span class = "icon-placeholder me-2" > 📥< / span > < span data-lang-key = "downloadM3UButton" > Descargar M3U< / span > < / button > < / li >
< / ul >
< / div >
< button class = "btn-header-action" id = "openEpgModalBtn" title = "Abrir EPG" >
< span class = "icon-placeholder" > < / span > < span class = "btn-text" data-lang-key = "epgButton" > EPG< / span >
< / button >
< button class = "btn-header-action" id = "openSettingsModalBtn" title = "Ajustes" >
< span class = "icon-placeholder" > < / span > < span class = "btn-text" data-lang-key = "settingsButton" > Ajustes< / span >
< / button >
< / div >
< / header >
< div id = "xtream-info-bar" class = "xtream-info-bar" style = "display: none;" > < / div >
< main id = "main-content" >
< div class = "m3u-load-area" >
< div class = "row g-3 align-items-center" >
< div class = "col-lg-6 col-md-12" >
< label for = "urlInput" class = "form-label visually-hidden" > URL de lista M3U< / label >
< input type = "text" class = "form-control" id = "urlInput" placeholder = "📡 URL de lista M3U o Xtream API" >
< / div >
< div class = "col-lg-2 col-md-6 d-grid" > < button class = "btn-control primary" id = "loadUrl" data-lang-key = "loadUrlButton" > Cargar URL< / button > < / div >
< div class = "col-lg-4 col-md-6" >
< label for = "fileInput" class = "form-label visually-hidden" > Seleccionar archivo M3U local< / label >
< input type = "file" class = "form-control" id = "fileInput" accept = ".m3u,.m3u8" title = "Seleccionar archivo M3U local" data-lang-key = "loadFileInputTitle" data-lang-attr = "title" >
< / div >
< / div >
< / div >
< div class = "filter-tabs-container" >
< button class = "filter-tab-btn active" id = "showAllChannels" title = "Mostrar Todos" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "allChannelsTab" > Todos< / span > < / button >
< button class = "filter-tab-btn" id = "showFavorites" title = "Mostrar Favoritos" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "favoritesTab" > Favoritos< / span > < / button >
< button class = "filter-tab-btn" id = "showHistory" title = "Mostrar Historial" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "historyTab" > Historial< / span > < / button >
< / div >
< div class = "d-flex align-items-center mb-3" >
< button id = "xtreamBackButton" class = "btn-control btn-sm me-3" style = "display: none;" > < i class = "fas fa-arrow-left" > < / i > < span data-lang-key = "backButton" > Volver< / span > < / button >
< h2 class = "section-title-main flex-grow-1" id = "channelGridTitle" style = "margin-bottom:0 !important;" data-lang-key = "availableChannelsTitle" > Canales Disponibles< / h2 >
< / div >
< div id = "channelGrid" class = "m3u-grid" > < / div >
< p class = "text-center text-secondary w-100" id = "noChannelsMessage" style = "display: none;" > < / p >
< div class = "pagination-controls" id = "paginationControls" style = "display: none;" >
< button id = "prevPage" class = "btn-control" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "paginationPrev" > Ant.< / span > < / button >
< span id = "pageInfo" > Página 1 de 1< / span >
< button id = "nextPage" class = "btn-control" > < span data-lang-key = "paginationNext" > Sig.< / span > < span class = "icon-placeholder" > < / span > < / button >
< / div >
< / main >
< / div >
< / div >
< div id = "player-windows-container" > < / div >
< div id = "player-taskbar" > < / div >
< template id = "playerWindowTemplate" >
< div class = "player-window" >
< div class = "modal-header modal-header-draggable" >
< h5 class = "modal-title player-window-title" data-lang-key = "playerTitle" > Reproductor< / h5 >
< div class = "player-window-controls" >
< button type = "button" class = "btn-window-control player-window-minimize-btn" aria-label = "Minimize" title = "Minimizar" data-lang-key = "minimizeButton" data-lang-attr = "title" > < / button >
< button type = "button" class = "btn-window-control player-window-close-btn" aria-label = "Close" title = "Cerrar" data-lang-key = "closeButton" data-lang-attr = "title" > < / button >
< / div >
< / div >
< div class = "modal-body" >
< div class = "player-container" data-shaka-player-container >
< video class = "player-video" data-shaka-player playsinline x-webkit-airplay = "allow" crossorigin = "anonymous" poster = "" autoplay > < / video >
< div class = "player-infobar" >
< img class = "infobar-logo" src = "" alt = "Logo Canal" >
< div class = "infobar-details" >
< h3 class = "infobar-channel-name" > < / h3 >
< div class = "infobar-epg-current" > < / div >
< div class = "infobar-epg-next" > < / div >
< div class = "infobar-epg-progress-container" >
< div class = "infobar-epg-progress" > < / div >
< / div >
< / div >
< div class = "infobar-time" > < / div >
< / div >
< div class = "player-channel-list-panel" >
< div class = "player-channel-list-header" data-lang-key = "channelListTitle" > Lista de Canales< / div >
< div class = "player-channel-list-content" >
< / div >
< / div >
< / div >
< / div >
< div class = "resize-handle" > < / div >
< / div >
< / template >
2025-06-25 13:32:28 +02:00
< template id = "eqPanelTemplate" >
< div class = "eq-panel" >
< div class = "eq-header" >
< strong data-lang-key = "eqTitle" > Ecualizador de Audio< / strong >
< label class = "switch" >
< input type = "checkbox" class = "eq-on-off" >
< span class = "eq-slider-toggle" > < / span >
< / label >
< / div >
< div class = "eq-band-container" >
< / div >
< div class = "eq-controls-container" >
< div class = "eq-static-presets" >
< button class = "btn-control btn-sm eq-reset-btn" data-lang-key = "eqFlatPreset" > Plano< / button >
< button class = "btn-control btn-sm" data-preset = "dialogue" data-lang-key = "eqDialoguePreset" > Diálogo< / button >
< button class = "btn-control btn-sm" data-preset = "movie" data-lang-key = "eqMoviePreset" > Cine< / button >
< button class = "btn-control btn-sm" data-preset = "night" data-lang-key = "eqNightPreset" > Noche< / button >
< / div >
< div class = "eq-custom-presets" >
< select class = "form-select eq-custom-preset-select" >
< option value = "" data-lang-key = "eqCustomPresetPlaceholder" > -- Presets Guardados --< / option >
< / select >
< button class = "btn-control btn-sm eq-save-preset-btn" title = "Guardar preset actual" > < i class = "fas fa-save" > < / i > < / button >
< button class = "btn-control btn-sm btn-danger eq-delete-preset-btn" title = "Eliminar preset seleccionado" > < i class = "fas fa-trash" > < / i > < / button >
< / div >
< / div >
< / div >
< / template >
2025-06-19 04:02:44 +02:00
< div class = "modal fade" id = "editorModal" tabindex = "-1" aria-labelledby = "editorModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-fullscreen" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "editorModalLabel" data-lang-key = "advancedEditorTitle" > < i class = "fas fa-pencil-alt me-2" > < / i > Editor Avanzado M3U< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< div class = "content-wrapper" >
< div class = "list-panel" >
< div class = "list-toolbar" >
< span id = "file-name-display" data-lang-key = "noFileLoaded" > Ningún archivo cargado< / span >
< input type = "text" class = "form-control form-control-sm" id = "editor-search-input" placeholder = "Buscar en la lista..." style = "width: 200px; margin-left: 1rem;" data-lang-key = "searchInListPlaceholder" data-lang-attr = "placeholder" >
< select id = "editor-group-filter" class = "form-control-sm ms-auto" >
< option value = "" data-lang-key = "allGroups" > Todos los Grupos< / option >
< / select >
< button class = "btn btn-sm btn-outline-danger" id = "delete-selected-btn" disabled > < i class = "fas fa-trash" > < / i > < span data-lang-key = "deleteSelected" > Eliminar Sel.< / span > < / button >
< button class = "btn btn-sm" id = "clear-selection-btn" > < i class = "fas fa-eraser" > < / i > < span data-lang-key = "clearSelection" > Limpiar Sel.< / span > < / button >
< button class = "btn btn-sm btn-outline-primary" id = "multi-edit-btn" disabled > < i class = "fas fa-edit" > < / i > < span data-lang-key = "multiEdit" > Multi-Editar< / span > < / button >
< / div >
< div class = "table-container" id = "editor-table-container" >
< table id = "editor-channels-table" >
< thead >
< tr >
< th class = "checkbox-cell" > < input type = "checkbox" id = "editor-select-all" aria-label = "Seleccionar todo" > < / th >
< th class = "handle-cell" > < i class = "fas fa-grip-lines" > < / i > < / th >
< th class = "logo-cell" data-lang-key = "logoHeader" > Logo< / th >
< th class = "name-cell sortable" data-sort = "name" > < span data-lang-key = "nameHeader" > Nombre< / span > < i class = "fas fa-sort" > < / i > < / th >
< th class = "url-cell" data-lang-key = "urlHeader" > URL< / th >
< th class = "epg-cell sortable" data-sort = "tvg-id" > < span data-lang-key = "epgIdHeader" > EPG ID< / span > < i class = "fas fa-sort" > < / i > < / th >
< th class = "ch-num-cell sortable" data-sort = "ch-number" > < span data-lang-key = "channelNumHeader" > Num< / span > < i class = "fas fa-sort" > < / i > < / th >
< th class = "actions-cell" data-lang-key = "actionsHeader" > Acciones< / th >
< / tr >
< / thead >
< tbody id = "editor-table-body" >
< / tbody >
< / table >
< / div >
< / div >
< div class = "editor-panel" id = "editor-panel" >
< div id = "editor-placeholder" >
< i class = "fas fa-edit" > < / i >
< p data-lang-key = "editorPlaceholder" > Selecciona un canal para editar sus detalles.< / p >
< / div >
< div id = "editor-form-content" class = "hidden" >
< div class = "editor-panel-header" >
< h3 data-lang-key = "channelEditorTitle" > < i class = "fas fa-pencil-alt" > < / i > Editor de Canal< / h3 >
< button class = "btn-close-editor" id = "close-editor-btn" title = "Cerrar Editor" > × < / button >
< / div >
< div class = "editor-panel-content" >
< img id = "editor-logo-preview" src = "" alt = "Vista previa del logo" class = "editor-logo-preview" data-lang-key = "logoPreviewAlt" data-lang-attr = "alt" >
< div class = "form-group" > < label for = "editor-channel-name" data-lang-key = "channelNameLabel" > Nombre del Canal< / label > < input type = "text" id = "editor-channel-name" required > < / div >
< div class = "input-group" >
< div class = "form-group" > < label for = "editor-channel-tvg-id" data-lang-key = "epgIdLabel" > EPG ID (tvg-id)< / label > < input type = "text" id = "editor-channel-tvg-id" > < / div >
< div class = "form-group ch-num-wrapper" > < label for = "editor-channel-ch-num" data-lang-key = "channelNumLabel" > Núm. Canal (ch-number)< / label > < input type = "number" id = "editor-channel-ch-num" min = "-1" > < / div >
< / div >
< div class = "form-group" > < label for = "editor-channel-logo" data-lang-key = "logoLabel" > Logo (tvg-logo)< / label > < input type = "url" id = "editor-channel-logo" > < / div >
< div class = "form-group" > < label for = "editor-channel-url" data-lang-key = "streamUrlLabel" > URL del Stream< / label > < input type = "url" id = "editor-channel-url" required > < / div >
< div class = "form-group" > < label for = "editor-channel-group" data-lang-key = "groupLabel" > Grupo (group-title)< / label > < input type = "text" id = "editor-channel-group" list = "group-suggestions" > < / div >
< datalist id = "group-suggestions" > < / datalist >
< div class = "form-check-group" >
< div class = "form-check" > < input type = "checkbox" id = "editor-fav-channel" > < label for = "editor-fav-channel" data-lang-key = "favoriteLabel" > Favorito< / label > < / div >
< div class = "form-check" > < input type = "checkbox" id = "editor-hide-channel" > < label for = "editor-hide-channel" data-lang-key = "hideChannelLabel" > Ocultar canal< / label > < / div >
< / div >
< h6 data-lang-key = "advancedSettingsDRM" > Ajustes Avanzados / DRM< / h6 >
< div class = "form-group" > < label for = "editor-kodi-license-type" data-lang-key = "licenseTypeLabel" > Tipo Licencia DRM (license_type)< / label > < input type = "text" id = "editor-kodi-license-type" > < / div >
< div class = "form-group" > < label for = "editor-kodi-license-key" data-lang-key = "licenseKeyLabel" > Clave/URL Licencia DRM (license_key)< / label > < textarea id = "editor-kodi-license-key" rows = "2" > < / textarea > < / div >
< div class = "form-group" > < label for = "editor-kodi-stream-headers" data-lang-key = "streamHeadersLabel" > Cabeceras Stream DRM (stream_headers)< / label > < textarea id = "editor-kodi-stream-headers" rows = "2" placeholder = "Ej: User-Agent=XYZ&Referer=abc.com" > < / textarea > < / div >
< div class = "form-group" > < label for = "editor-vlc-user-agent" data-lang-key = "vlcUserAgentLabel" > VLC User-Agent (#EXTVLCOPT:http-user-agent)< / label > < textarea id = "editor-vlc-user-agent" rows = "2" > < / textarea > < / div >
< / div >
< div class = "editor-panel-footer" >
< button class = "btn btn-sm btn-info" id = "editor-play-btn" title = "Probar Canal" > < i class = "fas fa-play" > < / i > < span data-lang-key = "testButton" > Probar< / span > < / button >
< button class = "btn btn-sm btn-outline-danger" id = "editor-delete-btn" title = "Eliminar Canal" > < i class = "fas fa-trash" > < / i > < span data-lang-key = "deleteButton" > Eliminar< / span > < / button >
< button class = "btn btn-sm btn-success" id = "editor-save-btn" title = "Guardar Cambios del Canal" > < i class = "fas fa-save" > < / i > < span data-lang-key = "saveButton" > Guardar< / span > < / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "modal-footer justify-content-center" >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "closeEditorButton" > Cerrar Editor< / button >
< button type = "button" class = "btn-control primary" id = "applyEditorChangesBtn" > < i class = "fas fa-check-circle me-1" > < / i > < span data-lang-key = "applyChangesAndCloseButton" > Aplicar Cambios y Cerrar< / span > < / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "multiEditModal" tabindex = "-1" aria-labelledby = "multiEditModalLabel" aria-hidden = "true" data-bs-backdrop = "static" >
< div class = "modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "multiEditModalLabel" data-lang-key = "multiEditTitle" > < i class = "fas fa-edit" > < / i > Edición Múltiple de Canales< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
2025-06-25 13:32:28 +02:00
< p class = "text-secondary" > < span data-lang-key = "multiEditDescription" data-lang-vars = '{"count": "#multiEditChannelCount"}' > Aplica cambios a todos los ... canales seleccionados...< / span > < / p >
2025-06-19 04:02:44 +02:00
< div class = "multi-edit-field" >
< div class = "form-check form-switch" > < input class = "form-check-input" type = "checkbox" id = "multiEditEnableGroup" > < label for = "multiEditEnableGroup" data-lang-key = "changeGroupLabel" > Cambiar Grupo< / label > < / div >
< input type = "text" class = "form-control form-control-sm" id = "multiEditGroupInput" placeholder = "Nuevo nombre de grupo..." disabled list = "group-suggestions" data-lang-key = "newGroupNamePlaceholder" data-lang-attr = "placeholder" >
< / div >
< div class = "multi-edit-field" >
< div class = "form-check form-switch" > < input class = "form-check-input" type = "checkbox" id = "multiEditEnableFavorite" > < label for = "multiEditEnableFavorite" data-lang-key = "modifyFavoriteLabel" > Modificar Favorito< / label > < / div >
< select id = "multiEditFavoriteSelect" class = "form-select form-select-sm" disabled > < option value = "add" data-lang-key = "addToFavoritesOption" > Añadir a Favoritos< / option > < option value = "remove" data-lang-key = "removeFromFavoritesOption" > Quitar de Favoritos< / option > < / select >
< / div >
< div class = "multi-edit-field" >
< div class = "form-check form-switch" > < input class = "form-check-input" type = "checkbox" id = "multiEditEnableHidden" > < label for = "multiEditEnableHidden" data-lang-key = "modifyVisibilityLabel" > Modificar Visibilidad< / label > < / div >
< select id = "multiEditHiddenSelect" class = "form-select form-select-sm" disabled > < option value = "hide" data-lang-key = "hideChannelsOption" > Ocultar Canales< / option > < option value = "show" data-lang-key = "showChannelsOption" > Mostrar Canales< / option > < / select >
< / div >
< h6 class = "mt-4" data-lang-key = "headersAndDRM" > Cabeceras y DRM< / h6 >
< div class = "multi-edit-field" >
< div class = "form-check form-switch" > < input class = "form-check-input" type = "checkbox" id = "multiEditEnableUserAgent" > < label for = "multiEditEnableUserAgent" data-lang-key = "setUserAgentLabel" > Establecer User-Agent (VLC)< / label > < / div >
< textarea class = "form-control form-control-sm" id = "multiEditUserAgentInput" rows = "2" placeholder = "User-Agent para #EXTVLCOPT..." disabled data-lang-key = "userAgentPlaceholder" data-lang-attr = "placeholder" > < / textarea >
< / div >
< div class = "multi-edit-field" >
< div class = "form-check form-switch" > < input class = "form-check-input" type = "checkbox" id = "multiEditEnableStreamHeaders" > < label for = "multiEditEnableStreamHeaders" data-lang-key = "setStreamHeadersLabel" > Añadir/Sobrescribir Cabeceras de Stream (Kodi)< / label > < / div >
< textarea class = "form-control form-control-sm" id = "multiEditStreamHeadersInput" rows = "3" placeholder = "key1=value1|key2=value2..." disabled data-lang-key = "streamHeadersPlaceholder" data-lang-attr = "placeholder" > < / textarea >
< select id = "multiEditStreamHeadersMode" class = "form-select form-select-sm mt-1" disabled > < option value = "append" data-lang-key = "appendHeadersOption" > Añadir/Actualizar Cabeceras< / option > < option value = "replace" data-lang-key = "replaceHeadersOption" > Reemplazar Todas las Cabeceras< / option > < / select >
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control primary" id = "applyMultiEditBtn" > < i class = "fas fa-check-circle me-1" > < / i > < span data-lang-key = "applyChangesButton" > Aplicar Cambios< / span > < / button >
< / div >
< / div >
< / div >
< / div >
< div id = "loading-overlay" > < div class = "loader" > < / div > < / div >
< div id = "notification" > < / div >
< div class = "modal fade" id = "saveM3UModal" tabindex = "-1" aria-labelledby = "saveM3UModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "saveM3UModalLabel" data-lang-key = "saveM3UModalTitle" > Guardar Lista M3U Actual< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< p class = "text-secondary mb-3" data-lang-key = "saveM3UModalDescription" > Introduce un nombre para guardar la lista M3U cargada actualmente en la base de datos local de la extensión.< / p >
< div class = "mb-3" >
< label for = "saveM3UNameInput" class = "form-label" data-lang-key = "listNameLabel" > Nombre de la Lista:< / label >
< input type = "text" class = "form-control" id = "saveM3UNameInput" placeholder = "Ej: MiListaFavorita_TV" data-lang-key = "listNamePlaceholder" data-lang-attr = "placeholder" >
< / div >
< small class = "form-text text-secondary" > Si ya existe una lista con este nombre, se te preguntará si deseas reemplazarla.< / small >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control primary" id = "confirmSaveM3UBtn" data-lang-key = "saveListButton" > Guardar Lista< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "daznTokenModal" tabindex = "-1" aria-labelledby = "daznTokenModalLabel" aria-hidden = "true" data-bs-backdrop = "static" data-bs-keyboard = "false" >
< div class = "modal-dialog modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "daznTokenModalLabel" data-lang-key = "daznTokenModalTitle" > Token de Autenticación DAZN Requerido< / h5 >
< / div >
< div class = "modal-body" >
< p class = "text-secondary" data-lang-key = "daznTokenModalDescription" > Para actualizar los canales de DAZN, por favor, introduce tu Bearer Token completo de DAZN.< / p >
< p class = "text-secondary small" data-lang-key = "daznTokenModalHint" > Este token se puede obtener de las herramientas de desarrollador de tu navegador al inspeccionar las solicitudes de red mientras DAZN está activo y logueado.< / p >
< div class = "mb-3" >
< label for = "daznTokenModalInput" class = "form-label" data-lang-key = "daznTokenLabel" > Token de DAZN (Bearer):< / label >
< textarea class = "form-control" id = "daznTokenModalInput" rows = "4" placeholder = "Pega aquí tu Bearer token completo..." data-lang-key = "daznTokenPlaceholder" data-lang-attr = "placeholder" > < / textarea >
< / div >
< div class = "form-check" >
< input class = "form-check-input" type = "checkbox" id = "daznRememberTokenCheck" checked >
< label class = "form-check-label" for = "daznRememberTokenCheck" data-lang-key = "rememberTokenLabel" >
Recordar este token (se guardará localmente en los ajustes)
< / label >
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control" id = "cancelDaznTokenBtn" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control primary" id = "submitDaznTokenBtn" data-lang-key = "submitTokenButton" > Enviar Token< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "loadFromDBModal" tabindex = "-1" aria-labelledby = "loadFromDBModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "loadFromDBModalLabel" data-lang-key = "loadFromDBModalTitle" > Listas Guardadas< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< ul class = "list-group list-group-flush" id = "dbFilesList" >
< li class = "list-group-item text-secondary text-center" id = "dbListPlaceholder" data-lang-key = "loadingLists" > Cargando listas... < / li >
< / ul >
< / div >
< div class = "modal-footer" > < button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "closeButton" > Cerrar< / button > < / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "epgModal" tabindex = "-1" aria-labelledby = "epgModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-fullscreen" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "epgModalLabel" data-lang-key = "epgModalTitle" > Guía de Programación (EPG)< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< div class = "row g-2 mb-3 align-items-center" >
< div class = "col-md-8" > < input type = "text" class = "form-control" id = "epgUrlInputModal" placeholder = "📅 URL del archivo XMLTV EPG" data-lang-key = "epgUrlPlaceholder" data-lang-attr = "placeholder" > < / div >
< div class = "col-md-4 d-grid" > < button class = "btn-control primary" id = "loadEpgBtnModal" data-lang-key = "loadEpgButton" > Cargar/Actualizar EPG< / button > < / div >
< / div >
< div class = "epg-timeline flex-grow-1" >
< div class = "epg-timeline-header" > < div class = "epg-timebar" id = "epgTimeBar" > < / div > < / div >
< div class = "epg-timeline-body" >
< div class = "epg-channels" id = "epgChannelsList" > < / div >
< div class = "epg-programs-container" id = "epgProgramsContainer" >
< div class = "epg-programs" id = "epgPrograms" > < / div >
< div id = "epgCurrentTimeLine" class = "epg-current-time-line" style = "display:none;" > < / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "movistarVODModal" tabindex = "-1" aria-labelledby = "movistarVODModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-fullscreen" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "movistarVODModalLabel" data-lang-key = "movistarVODModalTitle" > Movistar+ VOD/Catchup< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< div class = "row g-2 mb-3 align-items-center" >
< div class = "col-md-3" >
< label for = "movistarVODDateInput" class = "form-label" data-lang-key = "selectDateLabel" > Seleccionar Fecha:< / label >
< input type = "date" class = "form-control form-control-sm" id = "movistarVODDateInput" >
< / div >
< div class = "col-md-3 d-flex align-items-end" >
< button class = "btn-control primary btn-sm w-100" id = "loadMovistarVODBtn" data-lang-key = "loadEpgDayButton" > Cargar EPG Día< / button >
< / div >
< div class = "col-md-6" id = "movistarVODModal-filters-container" style = "padding-top:1.8rem;" >
< input type = "text" class = "form-control form-control-sm" id = "movistarVODModal-search-input" placeholder = "Buscar programa..." data-lang-key = "searchProgramPlaceholder" data-lang-attr = "placeholder" >
< select class = "form-select form-select-sm" id = "movistarVODModal-channel-filter" >
< option value = "" data-lang-key = "allChannelsOption" > Todos los canales< / option >
< / select >
< select class = "form-select form-select-sm" id = "movistarVODModal-genre-filter" >
< option value = "" data-lang-key = "allGenresOption" > Todos los géneros< / option >
< / select >
< / div >
< / div >
< div id = "movistarVODModal-programs-container" class = "flex-grow-1" style = "overflow-y: auto;" >
< div id = "movistarVODModal-programs" class = "m3u-grid" >
< / div >
< p id = "movistarVODModal-no-results" class = "d-none text-center p-3" data-lang-key = "noProgramsFound" > No se encontraron programas para la fecha/filtros seleccionados.< / p >
< / div >
< div id = "movistarVODModal-pagination-controls" class = "text-center mt-3 py-2" style = "display: none; border-top: 1px solid var(--border-color);" >
< button class = "btn btn-sm btn-outline-secondary me-2" id = "movistarVODModal-prev-page" >
< i class = "fas fa-chevron-left" > < / i > < span data-lang-key = "previousButton" > Anterior< / span >
< / button >
< span id = "movistarVODModal-page-info" class = "align-middle" style = "color: var(--text-secondary); font-size: 0.9rem;" > < / span >
< button class = "btn btn-sm btn-outline-secondary ms-2" id = "movistarVODModal-next-page" >
< span data-lang-key = "nextButton" > Siguiente< / span > < i class = "fas fa-chevron-right" > < / i >
< / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade epg-program-modal" id = "epgProgramModal" tabindex = "-1" aria-labelledby = "epgProgramModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "epgProgramModalLabel" data-lang-key = "programDetailsTitle" > Detalles del Programa< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" id = "epgProgramDetails" > < / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control primary" id = "playEpgProgramBtn" style = "display: none;" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "playProgramButton" > Reproducir< / span > < / button >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "closeButton" > Cerrar< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "movistarVODProgramDetailsModal" tabindex = "-1" aria-labelledby = "movistarVODProgramDetailsModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "movistarVODProgramDetailsModalLabel" > Detalles del Programa VOD< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" id = "movistarVODProgramDetailsBody" >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control" id = "playMovistarVODProgramFromDetailsBtn" > < span class = "icon-placeholder" > ▶< / span > < span data-lang-key = "playProgramButton" > Reproducir< / span > < / button >
< button type = "button" class = "btn-control primary" id = "addMovistarVODToM3UFromDetailsBtn" > < span class = "icon-placeholder" > ➕ < / span > < span data-lang-key = "addToListButton" > Añadir a Lista M3U< / span > < / button >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "closeButton" > Cerrar< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "xtreamConnectionModal" tabindex = "-1" aria-labelledby = "xtreamConnectionModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "xtreamConnectionModalLabel" data-lang-key = "xtreamModalTitle" > Conexión a Servidor Xtream Codes< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< p class = "text-secondary mb-3" data-lang-key = "xtreamModalDescription" > Introduce los detalles de tu servidor Xtream. La URL M3U se generará automáticamente.< / p >
< div class = "mb-3" >
< label for = "xtreamServerNameInput" class = "form-label" data-lang-key = "xtreamServerNameLabel" > Nombre para Guardar (Opcional):< / label >
< input type = "text" class = "form-control form-control-sm" id = "xtreamServerNameInput" placeholder = "Ej: Mi Servidor Principal" >
< / div >
< div class = "mb-3" >
< label for = "xtreamHostInput" class = "form-label" data-lang-key = "xtreamHostLabel" > Host del Servidor (ej: http://dominio.com:puerto):< / label >
< input type = "url" class = "form-control form-control-sm" id = "xtreamHostInput" placeholder = "http://ejemplo.com:8080" required >
< / div >
< div class = "mb-3" >
< label for = "xtreamUsernameInput" class = "form-label" data-lang-key = "xtreamUserLabel" > Usuario:< / label >
< input type = "text" class = "form-control form-control-sm" id = "xtreamUsernameInput" required >
< / div >
< div class = "mb-3" >
< label for = "xtreamPasswordInput" class = "form-label" data-lang-key = "xtreamPasswordLabel" > Contraseña:< / label >
< input type = "password" class = "form-control form-control-sm" id = "xtreamPasswordInput" required >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label for = "xtreamOutputTypeSelect" class = "form-label" data-lang-key = "xtreamOutputTypeLabel" > Tipo de Salida Preferido:< / label >
< select class = "form-select form-select-sm" id = "xtreamOutputTypeSelect" >
< option value = "m3u_plus" selected data-lang-key = "xtreamM3uPlusOption" > M3U Plus (Recomendado)< / option >
< option value = "ts" data-lang-key = "xtreamTsOption" > TS< / option > < option value = "hls" data-lang-key = "xtreamHlsOption" > HLS (m3u8)< / option >
< / select >
< small class = "form-text text-secondary d-block mt-1" data-lang-key = "xtreamOutputHint" > Afecta al formato de las URLs de los streams.< / small >
< / div >
< div class = "col-md-6 mb-3" >
< label class = "form-label" data-lang-key = "xtreamContentToLoadLabel" > Contenido a Cargar:< / label >
< div class = "form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "xtreamLoadLive" checked >
< label class = "form-check-label" for = "xtreamLoadLive" data-lang-key = "xtreamLiveChannels" > Canales en Vivo< / label >
< / div >
< div class = "form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "xtreamLoadVod" checked >
< label class = "form-check-label" for = "xtreamLoadVod" data-lang-key = "xtreamVod" > VOD (Películas)< / label >
< / div >
< div class = "form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "xtreamLoadSeries" checked >
< label class = "form-check-label" for = "xtreamLoadSeries" data-lang-key = "xtreamSeries" > Series< / label >
< / div >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< div class = "form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "xtreamFetchEpgCheck" checked >
< label class = "form-check-label" for = "xtreamFetchEpgCheck" data-lang-key = "xtreamFetchEpgLabel" > Intentar obtener EPG del servidor< / label >
< / div >
< / div >
< div class = "col-md-6 mb-3" >
< div class = "form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "xtreamForceGroupSelectionCheck" >
< label class = "form-check-label" for = "xtreamForceGroupSelectionCheck" data-lang-key = "xtreamForceGroupSelectionLabel" > Forzar selección de grupos< / label >
< small class = "form-text text-secondary d-block" data-lang-key = "xtreamForceGroupSelectionHint" > Marca esto si quieres cambiar tu selección de grupos para este servidor.< / small >
< / div >
< / div >
< / div >
< hr class = "my-4" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xtreamSavedServersLabel" > Servidores Guardados< / h6 >
< div id = "savedXtreamServersListContainer" style = "max-height: 200px; overflow-y: auto;" >
< ul class = "list-group list-group-flush" id = "savedXtreamServersList" >
< li class = "list-group-item text-secondary text-center" data-lang-key = "xtreamNoSavedServers" > No hay servidores guardados.< / li >
< / ul >
< / div >
< / div >
< div class = "modal-footer justify-content-between" >
< div > < button type = "button" class = "btn-control btn-sm" id = "saveXtreamServerBtn" data-lang-key = "xtreamSaveConnectionButton" > Guardar Conexión Actual< / button > < / div >
< div >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control primary" id = "connectXtreamServerBtn" data-lang-key = "xtreamConnectButton" > Conectar y Cargar< / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "xtreamGroupSelectionModal" tabindex = "-1" aria-labelledby = "xtreamGroupSelectionModalLabel" aria-hidden = "true" data-bs-backdrop = "static" data-bs-keyboard = "false" >
< div class = "modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "xtreamGroupSelectionModalLabel" data-lang-key = "xtreamGroupSelectionTitle" > Seleccionar Grupos de Xtream< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< p class = "text-secondary mb-3" data-lang-key = "xtreamGroupSelectionDescription" > Selecciona los grupos de cada categoría que deseas cargar en la lista.< / p >
< div class = "row" >
< div class = "col-md-4" id = "xtreamLiveGroupsCol" style = "display: none;" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xtreamLiveGroupsLabel" > Grupos en Vivo< / h6 >
< div class = "btn-group btn-group-sm w-100 mb-2" role = "group" >
< button type = "button" class = "btn btn-outline-secondary" id = "xtreamSelectAllLive" data-lang-key = "selectAll" > Todos< / button >
< button type = "button" class = "btn btn-outline-secondary" id = "xtreamDeselectAllLive" data-lang-key = "deselectAll" > Ninguno< / button >
< / div >
< div id = "xtreamLiveGroupList" class = "xtream-group-list-container" >
< div class = "list-group-item text-secondary" data-lang-key = "loading" > Cargando...< / div >
< / div >
< / div >
< div class = "col-md-4" id = "xtreamVodGroupsCol" style = "display: none;" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xtreamVodGroupsLabel" > Grupos VOD< / h6 >
< div class = "btn-group btn-group-sm w-100 mb-2" role = "group" >
< button type = "button" class = "btn btn-outline-secondary" id = "xtreamSelectAllVod" data-lang-key = "selectAll" > Todos< / button >
< button type = "button" class = "btn btn-outline-secondary" id = "xtreamDeselectAllVod" data-lang-key = "deselectAll" > Ninguno< / button >
< / div >
< div id = "xtreamVodGroupList" class = "xtream-group-list-container" >
< div class = "list-group-item text-secondary" data-lang-key = "loading" > Cargando...< / div >
< / div >
< / div >
< div class = "col-md-4" id = "xtreamSeriesGroupsCol" style = "display: none;" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xtreamSeriesGroupsLabel" > Grupos Series< / h6 >
< div class = "btn-group btn-group-sm w-100 mb-2" role = "group" >
< button type = "button" class = "btn btn-outline-secondary" id = "xtreamSelectAllSeries" data-lang-key = "selectAll" > Todos< / button >
< button type = "button" class = "btn btn-outline-secondary" id = "xtreamDeselectAllSeries" data-lang-key = "deselectAll" > Ninguno< / button >
< / div >
< div id = "xtreamSeriesGroupList" class = "xtream-group-list-container" >
< div class = "list-group-item text-secondary" data-lang-key = "loading" > Cargando...< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control primary" id = "xtreamConfirmGroupSelectionBtn" data-lang-key = "loadSelectedButton" > Cargar Seleccionados< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "manageXCodecPanelsModal" tabindex = "-1" aria-labelledby = "manageXCodecPanelsModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "manageXCodecPanelsModalLabel" data-lang-key = "xcodecPanelsTitle" > < span class = "icon-placeholder" style = "font-style:normal;" > ⚙️< / span > Gestión de Paneles XCodec< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< input type = "hidden" id = "xcodecEditingPanelIdInput" >
< div class = "row" >
< div class = "col-md-7" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xcodecPanelFormLabel" > Formulario del Panel< / h6 >
< div class = "mb-2" >
< label for = "xcodecPanelNameInput" class = "form-label form-label-sm" data-lang-key = "xcodecPanelNameLabel" > Nombre del Panel (Opcional):< / label >
< input type = "text" class = "form-control form-control-sm" id = "xcodecPanelNameInput" placeholder = "Ej: Mi Panel XCodec" >
< / div >
< div class = "mb-2" >
< label for = "xcodecPanelServerUrlInput" class = "form-label form-label-sm" data-lang-key = "xcodecServerUrlLabel" > URL del Servidor X-UI/XC:< / label >
< input type = "url" class = "form-control form-control-sm" id = "xcodecPanelServerUrlInput" placeholder = "http://ejemplo.com:puerto" required >
< / div >
< div class = "mb-2" >
< label for = "xcodecPanelApiTokenInput" class = "form-label form-label-sm" data-lang-key = "xcodecApiTokenLabel" > Token API (si es requerido):< / label >
< input type = "text" class = "form-control form-control-sm" id = "xcodecPanelApiTokenInput" placeholder = "Token API del servidor" >
< / div >
< div class = "btn-group btn-group-sm w-100 mt-2" >
< button type = "button" class = "btn-control" id = "xcodecSavePanelBtn" > < i class = "fas fa-save me-1" > < / i > < span data-lang-key = "xcodecSavePanelButton" > Guardar Panel< / span > < / button >
< button type = "button" class = "btn-control" id = "xcodecClearFormBtn" > < i class = "fas fa-eraser me-1" > < / i > < span data-lang-key = "xcodecClearFormButton" > Limpiar< / span > < / button >
< / div >
< / div >
< div class = "col-md-5" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xcodecSavedPanelsLabel" > Paneles Guardados< / h6 >
< button class = "btn btn-sm btn-outline-info w-100 mb-2" id = "xcodecImportPresetPanelsBtn" > < i class = "fas fa-download me-1" > < / i > < span data-lang-key = "xcodecImportPresetButton" > Importar Paneles Predefinidos< / span > < / button >
< div id = "savedXCodecPanelsListContainer" style = "max-height: 200px; overflow-y: auto;" >
< ul class = "list-group list-group-flush" id = "savedXCodecPanelsList" >
< li class = "list-group-item text-secondary text-center" data-lang-key = "xcodecNoSavedPanels" > No hay paneles guardados.< / li >
< / ul >
< / div >
< / div >
< / div >
< hr class = "my-3" >
< div id = "xcodecProgressContainer" class = "mb-2" style = "display: none;" >
< div class = "progress" style = "height: 20px;" >
< div id = "xcodecProgressBar" class = "progress-bar progress-bar-striped progress-bar-animated" role = "progressbar" style = "width: 0%;" aria-valuenow = "0" aria-valuemin = "0" aria-valuemax = "100" > 0%< / div >
< / div >
< / div >
< div id = "xcodecStatus" class = "alert mt-2" role = "alert" style = "display:none; font-size:0.85rem;" > < / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control" id = "xcodecProcessAllPanelsBtn" title = "Procesar todos los paneles guardados y añadir sus streams directamente" > < i class = "fas fa-tasks me-1" > < / i > < span data-lang-key = "xcodecProcessAllButton" > Procesar Todos< / span > < / button >
< button type = "button" class = "btn-control primary" id = "xcodecProcessPanelBtn" title = "Procesar el panel cargado en el formulario (puede mostrar previsualización)" > < i class = "fas fa-cogs me-1" > < / i > < span data-lang-key = "xcodecProcessFormButton" > Procesar Panel (Formulario)< / span > < / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "xcodecPreviewModal" tabindex = "-1" aria-labelledby = "xcodecPreviewModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-xl modal-dialog-scrollable" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "xcodecPreviewModalLabel" data-lang-key = "xcodecPreviewTitle" > Previsualización Panel XCodec< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< div id = "xcodecPreviewStats" class = "alert alert-info alert-sm mb-3" data-lang-key = "xcodecPreviewStatsLoading" > Cargando estadísticas...< / div >
< div class = "row" >
< div class = "col-md-4" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xcodecPanelGroupsLabel" > Grupos del Panel< / h6 >
< div id = "xcodecPreviewGroupListContainer" style = "max-height: 60vh; overflow-y: auto;" >
< ul class = "list-group list-group-flush" id = "xcodecPreviewGroupList" >
< / ul >
< / div >
< div class = "mt-2" >
< button class = "btn btn-sm btn-outline-secondary w-100" id = "xcodecPreviewSelectAllGroupsBtn" data-lang-key = "xcodecSelectAllGroupsButton" > Seleccionar/Deseleccionar Todos los Grupos< / button >
< / div >
< / div >
< div class = "col-md-8" >
< h6 class = "text-secondary small text-uppercase mb-2" data-lang-key = "xcodecChannelsInGroupLabel" > Canales en Grupo Seleccionado< / h6 >
< div id = "xcodecPreviewChannelListContainer" style = "max-height: 60vh; overflow-y: auto;" >
< ul class = "list-group list-group-flush" id = "xcodecPreviewChannelList" >
< li class = "list-group-item text-secondary text-center" data-lang-key = "xcodecSelectGroupHint" > Selecciona un grupo para ver los canales.< / li >
< / ul >
< / div >
< div class = "mt-2" >
< button class = "btn btn-sm btn-outline-secondary w-100" id = "xcodecPreviewSelectAllChannelsInGroupBtn" disabled data-lang-key = "xcodecSelectAllInGroupButton" > Seleccionar/Deseleccionar Todos en Grupo< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control" id = "xcodecAddSelectedBtn" disabled > < i class = "fas fa-plus-circle me-1" > < / i > < span data-lang-key = "xcodecAddSelectedButton" > Añadir Seleccionados< / span > < / button >
< button type = "button" class = "btn-control primary" id = "xcodecAddAllValidBtn" disabled > < i class = "fas fa-check-double me-1" > < / i > < span data-lang-key = "xcodecAddAllValidButton" > Añadir Todos los Válidos< / span > < / button >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "settingsModal" tabindex = "-1" aria-labelledby = "settingsModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "settingsModalLabel" data-lang-key = "settingsTitle" > < span class = "icon-placeholder" > < / span > Ajustes del Reproductor< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "Close" > < / button >
< / div >
< div class = "modal-body" >
< div class = "row" >
< div class = "col-md-3" >
< div class = "nav flex-column nav-pills settings-tabs" id = "v-pills-tab" role = "tablist" aria-orientation = "vertical" >
< button class = "nav-link active" id = "generalUISettingsTab" data-bs-toggle = "pill" data-bs-target = "#generalUISettingsPane" type = "button" role = "tab" aria-controls = "generalUISettingsPane" aria-selected = "true" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsGeneralUITab" > General y UI< / span > < / button >
< button class = "nav-link" id = "shakaPlayerSettingsTab" data-bs-toggle = "pill" data-bs-target = "#shakaPlayerSettingsPane" type = "button" role = "tab" aria-controls = "shakaPlayerSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsPlayerTab" > Reproductor< / span > < / button >
< button class = "nav-link" id = "shakaNetworkSettingsTab" data-bs-toggle = "pill" data-bs-target = "#shakaNetworkSettingsPane" type = "button" role = "tab" aria-controls = "shakaNetworkSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsNetworkTab" > Red (Shaka)< / span > < / button >
< button class = "nav-link" id = "epgSettingsTab" data-bs-toggle = "pill" data-bs-target = "#epgSettingsPane" type = "button" role = "tab" aria-controls = "epgSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsEpgTab" > EPG< / span > < / button >
< button class = "nav-link" id = "xcodecSettingsTab" data-bs-toggle = "pill" data-bs-target = "#xcodecSettingsPane" type = "button" role = "tab" aria-controls = "xcodecSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsXCodecTab" > XCodec< / span > < / button >
< button class = "nav-link" id = "barTvSettingsTab" data-bs-toggle = "pill" data-bs-target = "#barTvSettingsPane" type = "button" role = "tab" aria-controls = "barTvSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsBarTvTab" > BarTV< / span > < / button >
< button class = "nav-link" id = "orangeTvSettingsTab" data-bs-toggle = "pill" data-bs-target = "#orangeTvSettingsPane" type = "button" role = "tab" aria-controls = "orangeTvSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsOrangeTvTab" > OrangeTV< / span > < / button >
< button class = "nav-link" id = "globalNetworkSettingsTab" data-bs-toggle = "pill" data-bs-target = "#globalNetworkSettingsPane" type = "button" role = "tab" aria-controls = "globalNetworkSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsGlobalNetworkTab" > Red Global< / span > < / button >
< button class = "nav-link" id = "daznSettingsTab" data-bs-toggle = "pill" data-bs-target = "#daznSettingsPane" type = "button" role = "tab" aria-controls = "daznSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsDaznTab" > DAZN< / span > < / button >
< button class = "nav-link" id = "movistarSettingsTab" data-bs-toggle = "pill" data-bs-target = "#movistarSettingsPane" type = "button" role = "tab" aria-controls = "movistarSettingsPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsMovistarTab" > Movistar+< / span > < / button >
< button class = "nav-link" id = "sendM3uToServerTab" data-bs-toggle = "pill" data-bs-target = "#sendM3uToServerPane" type = "button" role = "tab" aria-controls = "sendM3uToServerPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsSendM3uTab" > Enviar M3U< / span > < / button >
< button class = "nav-link" id = "appDataManagementTab" data-bs-toggle = "pill" data-bs-target = "#appDataManagementPane" type = "button" role = "tab" aria-controls = "appDataManagementPane" aria-selected = "false" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsDataManagementTab" > Gestión de Datos< / span > < / button >
< / div >
< / div >
< div class = "col-md-9" >
< div class = "tab-content" id = "v-pills-tabContent" >
< div class = "tab-pane fade show active" id = "generalUISettingsPane" role = "tabpanel" aria-labelledby = "generalUISettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "settingsUIAppearanceTitle" > Interfaz de Usuario y Apariencia< / h5 >
< div class = "row" >
< div class = "col-md-4 mb-3" >
< label for = "appLanguageSelect" class = "form-label" data-lang-key = "languageLabel" > Idioma (Language):< / label >
< select class = "form-select form-select-sm" id = "appLanguageSelect" >
< option value = "es" > Español< / option >
< option value = "en" > English< / option >
< / select >
< / div >
< div class = "col-md-4 mb-3" >
< label for = "appThemeSelect" class = "form-label" data-lang-key = "themeLabel" > Tema de Color:< / label >
< select class = "form-select form-select-sm" id = "appThemeSelect" >
< option value = "default-green" data-lang-key = "greenTheme" > Verde (Predeterminado)< / option > < option value = "blue" data-lang-key = "blueTheme" > Azul< / option >
< option value = "purple" data-lang-key = "purpleTheme" > Púrpura< / option > < option value = "orange" data-lang-key = "orangeTheme" > Naranja< / option >
< / select >
< / div >
< div class = "col-md-4 mb-3" >
< label for = "appFontSelect" class = "form-label" data-lang-key = "fontLabel" > Fuente Principal:< / label >
< select class = "form-select form-select-sm" id = "appFontSelect" >
< option value = "system" data-lang-key = "systemFont" > Sistema (Predeterminada)< / option > < option value = "sans-serif" data-lang-key = "sansSerifFont" > Sans-Serif Genérica< / option >
< option value = "serif" data-lang-key = "serifFont" > Serif Genérica< / option > < option value = "monospace" data-lang-key = "monospaceFont" > Monospace Genérica< / option >
< / select >
< / div >
< / div >
< div class = "mb-3" >
< label for = "channelCardSizeInput" class = "form-label" > < span data-lang-key = "cardSizeLabel" > Tamaño de Tarjetas de Canal:< / span > < span id = "channelCardSizeValue" class = "fw-bold" > 180px< / span > < / label >
< input type = "range" class = "form-range" min = "100" max = "300" step = "5" id = "channelCardSizeInput" value = "180" >
< / div >
< div class = "mb-3" >
< label for = "channelsPerPageInput" class = "form-label" > < span data-lang-key = "channelsPerPageLabel" > Canales por Página:< / span > < span id = "channelsPerPageValue" class = "fw-bold" > 48< / span > < / label >
< input type = "range" class = "form-range" min = "12" max = "120" step = "4" id = "channelsPerPageInput" value = "48" >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "autoSaveM3UCheck" checked >
< label class = "form-check-label" for = "autoSaveM3UCheck" data-lang-key = "storeLastM3ULabel" > Almacenar Última Lista M3U (< 4MB)< / label >
< / div >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "particlesEnabledCheck" checked >
< label class = "form-check-label" for = "particlesEnabledCheck" data-lang-key = "backgroundAnimationLabel" > Animación de Fondo (Partículas)< / label >
< / div >
< / div >
< div class = "mb-3" >
< label for = "particleOpacityInput" class = "form-label" > < span data-lang-key = "particleOpacityLabel" > Opacidad de Partículas:< / span > < span id = "particleOpacityValue" class = "fw-bold" > 2%< / span > < / label >
< input type = "range" class = "form-range" min = "0" max = "20" step = "1" id = "particleOpacityInput" value = "2" >
< / div >
< h5 class = "settings-group-title" id = "cardDisplaySettingsTitle" > < span class = "icon-placeholder" style = "font-family: FontAwesome;" > < / span > < span data-lang-key = "cardDisplaySettingsTitle" > Visualización en Tarjetas de Canal< / span > < / h5 >
< div class = "mb-3" >
< label for = "cardLogoAspectRatioSelect" class = "form-label" data-lang-key = "logoAspectRatioLabel" > Ratio de Aspecto del Logo:< / label >
< select class = "form-select form-select-sm" id = "cardLogoAspectRatioSelect" >
< option value = "16/9" data-lang-key = "aspectRatio169" > 16:9 (Panorámico)< / option > < option value = "4/3" data-lang-key = "aspectRatio43" > 4:3 (Estándar)< / option >
< option value = "1/1" data-lang-key = "aspectRatio11" > 1:1 (Cuadrado)< / option > < option value = "2/1" data-lang-key = "aspectRatio21" > 2:1 (Cinemático)< / option >
< option value = "auto" data-lang-key = "aspectRatioAuto" > Automático (Original del Contenedor)< / option >
< / select >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "cardShowChannelNumberCheck" >
< label class = "form-check-label" for = "cardShowChannelNumberCheck" data-lang-key = "showChannelNumberLabel" > Mostrar Número de Canal< / label >
< / div >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "cardShowGroupCheck" checked >
< label class = "form-check-label" for = "cardShowGroupCheck" data-lang-key = "showChannelGroupLabel" > Mostrar Grupo del Canal< / label >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "cardShowEpgCheck" checked >
< label class = "form-check-label" for = "cardShowEpgCheck" data-lang-key = "showEpgInfoLabel" > Mostrar Información EPG (Ahora/Siguiente)< / label >
< / div >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "cardShowFavButtonCheck" checked >
< label class = "form-check-label" for = "cardShowFavButtonCheck" data-lang-key = "showFavButtonLabel" > Mostrar Botón de Favoritos< / label >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "compactCardViewCheck" >
< label class = "form-check-label" for = "compactCardViewCheck" data-lang-key = "compactCardViewLabel" > Vista de tarjetas compacta< / label >
< / div >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "enableHoverPreviewCheck" checked >
< label class = "form-check-label" for = "enableHoverPreviewCheck" data-lang-key = "enableHoverPreviewLabel" > Habilitar previsualización al pasar el ratón< / label >
< / div >
< / div >
< / div >
< div class = "tab-pane fade" id = "shakaPlayerSettingsPane" role = "tabpanel" aria-labelledby = "shakaPlayerSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "shakaPlayerSettingsTitle" > Configuración del Reproductor Shaka< / h5 >
< div class = "row" >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "persistentControlsCheck" >
< label class = "form-check-label" for = "persistentControlsCheck" data-lang-key = "persistentControlsLabel" > Controles del Reproductor Siempre Visibles< / label >
< / div >
< div class = "col-md-6 mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "persistFiltersCheck" checked >
< label class = "form-check-label" for = "persistFiltersCheck" data-lang-key = "persistFiltersLabel" > Recordar Filtros entre sesiones< / label >
< / div >
< / div >
< div class = "mb-3" >
< label for = "playerWindowOpacityInput" class = "form-label" > < span data-lang-key = "playerWindowOpacityLabel" > Transparencia de la Ventana del Reproductor:< / span > < span id = "playerWindowOpacityValue" class = "fw-bold" > 100%< / span > < / label >
< input type = "range" class = "form-range" min = "0.2" max = "1" step = "0.05" id = "playerWindowOpacityInput" value = "1" >
< / div >
< div class = "mb-3" >
< label for = "playerBufferInput" class = "form-label" > < span data-lang-key = "playerBufferLabel" > Buffer del reproductor (segundos):< / span > < span id = "playerBufferValue" class = "fw-bold" > 30s< / span > < / label >
< input type = "range" class = "form-range" min = "5" max = "180" step = "1" id = "playerBufferInput" value = "30" >
< / div >
< div class = "mb-3" >
< label for = "maxVideoHeight" class = "form-label" data-lang-key = "maxVideoHeightLabel" > Altura Máxima de Video Preferida (ABR):< / label >
< select class = "form-select form-select-sm" id = "maxVideoHeight" >
< option value = "0" data-lang-key = "noRestrictionOption" > Automático (Sin restricción)< / option > < option value = "2160" > 4K (2160p)< / option >
< option value = "1440" > 2K (1440p)< / option > < option value = "1080" > Full HD (1080p)< / option >
< option value = "720" > HD (720p)< / option > < option value = "480" > SD (480p)< / option >
< option value = "360" > Baja (360p)< / option >
< / select >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label for = "preferredAudioLanguageInput" class = "form-label" data-lang-key = "preferredAudioLabel" > Audio Preferido:< / label >
< select class = "form-select form-select-sm" id = "preferredAudioLanguageInput" > < / select >
< / div >
< div class = "col-md-6 mb-3" >
< label for = "preferredTextLanguageInput" class = "form-label" data-lang-key = "preferredSubtitlesLabel" > Subtítulos Preferidos:< / label >
< select class = "form-select form-select-sm" id = "preferredTextLanguageInput" > < / select >
< / div >
< / div >
< div class = "mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "lowLatencyModeCheck" checked >
< label class = "form-check-label" for = "lowLatencyModeCheck" data-lang-key = "lowLatencyModeLabel" > Modo Baja Latencia (Streaming en Vivo)< / label >
< / div >
< div class = "mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "liveCatchUpModeCheck" >
< label class = "form-check-label" for = "liveCatchUpModeCheck" data-lang-key = "liveCatchUpModeLabel" > Sincronización Agresiva en Vivo (Live Catch-up)< / label >
< / div >
< div class = "mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "abrEnabledCheck" checked >
< label class = "form-check-label" for = "abrEnabledCheck" data-lang-key = "enableAbrLabel" > Habilitar ABR (Adaptación de Bitrate)< / label >
< / div >
< div class = "mb-3" >
< label for = "abrDefaultBandwidthEstimateInput" class = "form-label" > < span data-lang-key = "abrInitialBandwidthLabel" > Ancho de banda inicial ABR (Kbps):< / span > < span id = "abrDefaultBandwidthEstimateValue" class = "fw-bold" > 1000 Kbps< / span > < / label >
< input type = "range" class = "form-range" min = "250" max = "8000" step = "50" id = "abrDefaultBandwidthEstimateInput" value = "1000" >
< / div >
< div class = "mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "streamingJumpLargeGapsCheck" >
< label class = "form-check-label" for = "streamingJumpLargeGapsCheck" data-lang-key = "jumpLargeGapsLabel" > Saltar Huecos Grandes en Stream (Live)< / label >
< / div >
< div class = "mb-3" >
< label for = "shakaDefaultPresentationDelayInput" class = "form-label" > < span data-lang-key = "dashPresentationDelayLabel" > Retraso Presentación DASH (segundos):< / span > < span id = "shakaDefaultPresentationDelayValue" > 5s< / span > < / label >
< input type = "range" class = "form-range" min = "1" max = "20" step = "1" id = "shakaDefaultPresentationDelayInput" value = "5" >
< small class = "form-text text-secondary d-block" data-lang-key = "dashPresentationDelayHint" > Para streams DASH. Define cuánto detrás del borde "en vivo" comenzará la reproducción.< / small >
< / div >
< div class = "mb-3" >
< label for = "shakaAudioVideoSyncThresholdInput" class = "form-label" > < span data-lang-key = "avSyncThresholdLabel" > Umbral Sincronización A/V (segundos):< / span > < span id = "shakaAudioVideoSyncThresholdValue" > 0.25s< / span > < / label >
< input type = "range" class = "form-range" min = "0.05" max = "1" step = "0.05" id = "shakaAudioVideoSyncThresholdInput" value = "0.25" >
< small class = "form-text text-secondary d-block" data-lang-key = "avSyncThresholdHint" > Diferencia máxima permitida entre audio y video antes de intentar una corrección.< / small >
< / div >
< / div >
< div class = "tab-pane fade" id = "shakaNetworkSettingsPane" role = "tabpanel" aria-labelledby = "shakaNetworkSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "networkRetrySettingsTitle" > Configuración de Reintentos de Red (Shaka)< / h5 >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label for = "manifestRetryMaxAttemptsInput" class = "form-label" > < span data-lang-key = "manifestMaxRetriesLabel" > Máx. Reintentos Manifiesto:< / span > < span id = "manifestRetryMaxAttemptsValue" > 2< / span > < / label >
< input type = "range" class = "form-range" min = "0" max = "10" step = "1" id = "manifestRetryMaxAttemptsInput" value = "2" >
< / div >
< div class = "col-md-6 mb-3" >
< label for = "manifestRetryTimeoutInput" class = "form-label" > < span data-lang-key = "manifestTimeoutLabel" > Timeout Manifiesto (ms):< / span > < span id = "manifestRetryTimeoutValue" > 15000< / span > < / label >
< input type = "range" class = "form-range" min = "1000" max = "60000" step = "1000" id = "manifestRetryTimeoutInput" value = "15000" >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label for = "segmentRetryMaxAttemptsInput" class = "form-label" > < span data-lang-key = "segmentMaxRetriesLabel" > Máx. Reintentos Segmento:< / span > < span id = "segmentRetryMaxAttemptsValue" > 2< / span > < / label >
< input type = "range" class = "form-range" min = "0" max = "10" step = "1" id = "segmentRetryMaxAttemptsInput" value = "2" >
< / div >
< div class = "col-md-6 mb-3" >
< label for = "segmentRetryTimeoutInput" class = "form-label" > < span data-lang-key = "segmentTimeoutLabel" > Timeout Segmento (ms):< / span > < span id = "segmentRetryTimeoutValue" > 15000< / span > < / label >
< input type = "range" class = "form-range" min = "1000" max = "60000" step = "1000" id = "segmentRetryTimeoutInput" value = "15000" >
< / div >
< / div >
< / div >
< div class = "tab-pane fade" id = "epgSettingsPane" role = "tabpanel" aria-labelledby = "epgSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "epgSettingsTitle" > Guía de Programación (EPG)< / h5 >
< div class = "mb-3" >
< label for = "defaultEpgUrlInput" class = "form-label" data-lang-key = "defaultEpgUrlLabel" > URL EPG XMLTV por Defecto (Modal EPG):< / label >
< input type = "text" class = "form-control form-control-sm" id = "defaultEpgUrlInput" placeholder = "URL del archivo XMLTV EPG por defecto" >
< / div >
< div class = "mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "enableEpgNameMatchingCheck" >
< label class = "form-check-label" for = "enableEpgNameMatchingCheck" data-lang-key = "enableEpgNameMatchingLabel" > Habilitar Coincidencia EPG (XMLTV) por Nombre< / label >
< small class = "form-text text-secondary d-block" data-lang-key = "epgNameMatchingHint" > Si tvg-id falla, intenta por nombre (menos preciso).< / small >
< / div >
< div class = "mb-3" >
< label for = "epgNameMatchThreshold" class = "form-label" > < span data-lang-key = "epgNameMatchThresholdLabel" > Umbral Similitud Nombre EPG (XMLTV):< / span > < span id = "epgNameMatchThresholdValue" class = "fw-bold" > 80%< / span > < / label >
< input type = "range" class = "form-range" min = "50" max = "95" step = "1" id = "epgNameMatchThreshold" value = "80" >
< / div >
< div class = "mb-3" >
< label for = "epgDensityInput" class = "form-label" > < span data-lang-key = "epgDensityLabel" > Densidad Visual de la Guía EPG:< / span > < span id = "epgDensityValue" class = "fw-bold" > 200px/h< / span > < / label >
< input type = "range" class = "form-range" min = "100" max = "400" step = "10" id = "epgDensityInput" value = "200" >
< small class = "form-text text-secondary d-block" data-lang-key = "epgDensityHint" > Píxeles por hora en la línea de tiempo. Más alto = más ancho, más detalle. Más bajo = más compacto.< / small >
< / div >
< div class = "mb-3 form-check form-switch" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "useMovistarVodAsEpgCheck" >
< label class = "form-check-label" for = "useMovistarVodAsEpgCheck" data-lang-key = "useMovistarVodAsEpgLabel" > Usar datos VOD de Movistar+ como EPG (experimental)< / label >
< small class = "form-text text-secondary d-block" data-lang-key = "useMovistarVodAsEpgHint" > Integra la EPG del día actual de Movistar VOD para los canales de Movistar en tu lista.< / small >
< / div >
< div class = "mb-3 text-center" >
< button class = "btn-control btn-sm primary" id = "forceEpgRematchBtn" title = "Vuelve a procesar las coincidencias EPG con los ajustes actuales" >
< span class = "icon-placeholder" > < / span > < span data-lang-key = "rematchEpgNowButton" > Re-emparejar EPG Ahora< / span >
< / button >
< p class = "form-text text-secondary mt-1 mb-0" data-lang-key = "rematchEpgHint" > Necesita una lista M3U y un EPG cargados.< / p >
< / div >
< / div >
< div class = "tab-pane fade" id = "xcodecSettingsPane" role = "tabpanel" aria-labelledby = "xcodecSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "xcodecSettingsTitle" > Configuración de Paneles XCodec< / h5 >
< div class = "mb-3" >
< label for = "xcodecCorsProxyUrlInput" class = "form-label" data-lang-key = "corsProxyUrlLabel" > URL del Proxy CORS (Opcional):< / label >
< input type = "url" class = "form-control form-control-sm" id = "xcodecCorsProxyUrlInput" placeholder = "https://tu-proxy-cors.example.com/?url=" >
< small class = "form-text text-secondary" data-lang-key = "corsProxyUrlHint" > Introduce la URL de un proxy CORS si los paneles XCodec tienen problemas de CORS. La URL del panel se añadirá al final (ej: `proxy.com/?url=http://panel.com`). Déjalo vacío para llamadas directas.< / small >
< / div >
< div class = "mb-3" >
< label for = "xcodecIgnorePanelsOverStreamsInput" class = "form-label" data-lang-key = "ignorePanelsOverStreamsLabel" > Ignorar Paneles con más de X Streams (0 para deshabilitar):< / label >
< input type = "number" class = "form-control form-control-sm" id = "xcodecIgnorePanelsOverStreamsInput" min = "0" step = "100" value = "0" >
< small class = "form-text text-secondary" data-lang-key = "ignorePanelsOverStreamsHint" > Si un panel tiene más streams que este valor, no se procesará al añadir directamente (no afecta a la previsualización).< / small >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label for = "xcodecDefaultBatchSizeInput" class = "form-label" data-lang-key = "batchSizeLabel" > Tamaño de Lote (Batch) para Configs:< / label >
< input type = "number" class = "form-control form-control-sm" id = "xcodecDefaultBatchSizeInput" min = "1" max = "50" step = "1" value = "15" >
< small class = "form-text text-secondary" data-lang-key = "batchSizeHint" > Número de configuraciones de stream a pedir simultáneamente.< / small >
< / div >
< div class = "col-md-6 mb-3" >
< label for = "xcodecDefaultTimeoutInput" class = "form-label" data-lang-key = "apiTimeoutLabel" > Timeout por Petición API (ms):< / label >
< input type = "number" class = "form-control form-control-sm" id = "xcodecDefaultTimeoutInput" min = "1000" max = "30000" step = "1000" value = "8000" >
< small class = "form-text text-secondary" data-lang-key = "apiTimeoutHint" > Tiempo máximo de espera para cada llamada a la API del panel.< / small >
< / div >
< / div >
< / div >
< div class = "tab-pane fade" id = "barTvSettingsPane" role = "tabpanel" aria-labelledby = "barTvSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "barTvCredentialsTitle" > Credenciales de BarTV< / h5 >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label for = "barTvEmailInput" class = "form-label" data-lang-key = "emailLabel" > Email:< / label >
< input type = "email" class = "form-control form-control-sm" id = "barTvEmailInput" placeholder = "tu_email@ejemplo.com" >
< / div >
< div class = "col-md-6 mb-3" >
< label for = "barTvPasswordInput" class = "form-label" data-lang-key = "passwordLabel" > Contraseña:< / label >
< input type = "password" class = "form-control form-control-sm" id = "barTvPasswordInput" >
< / div >
< / div >
< p class = "form-text text-secondary" data-lang-key = "barTvCredentialsHint" > Introduce tus credenciales de BarTV para poder cargar los canales.< / p >
< / div >
< div class = "tab-pane fade" id = "orangeTvSettingsPane" role = "tabpanel" aria-labelledby = "orangeTvSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "orangeTvCredentialsTitle" > Credenciales de OrangeTV< / h5 >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label for = "orangeTvUsernameInput" class = "form-label" data-lang-key = "userLabel" > Usuario:< / label >
< input type = "text" class = "form-control form-control-sm" id = "orangeTvUsernameInput" >
< / div >
< div class = "col-md-6 mb-3" >
< label for = "orangeTvPasswordInput" class = "form-label" data-lang-key = "passwordLabel" > Contraseña:< / label >
< input type = "password" class = "form-control form-control-sm" id = "orangeTvPasswordInput" >
< / div >
< / div >
< h5 class = "settings-group-title" data-lang-key = "orangeTvGroupSelectionTitle" > Selección de Grupos de Canales OrangeTV< / h5 >
< div id = "orangeTvGroupSelectionContainer" class = "row row-cols-2 row-cols-sm-3 row-cols-md-3 g-2 mb-3" style = "font-size: 0.85rem;" >
< / div >
< p class = "form-text text-secondary" data-lang-key = "orangeTvGroupSelectionHint" > Si no se selecciona ningún grupo, se incluirán todos los grupos disponibles al cargar canales de OrangeTV.< / p >
< / div >
< div class = "tab-pane fade" id = "globalNetworkSettingsPane" role = "tabpanel" aria-labelledby = "globalNetworkSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "globalNetworkSettingsTitle" > Configuración Global de Red< / h5 >
< div class = "mb-3" >
< label for = "globalUserAgentInput" class = "form-label" data-lang-key = "globalUserAgentLabel" > User-Agent Global (Opcional):< / label >
< input type = "text" class = "form-control form-control-sm" id = "globalUserAgentInput" placeholder = "Ej: Mozilla/5.0 ..." >
< small class = "form-text text-secondary" data-lang-key = "globalUserAgentHint" > Aplicable si el canal no define uno propio vía KODIPROP, EXTVLCOPT o EXTHTTP.< / small >
< / div >
< div class = "mb-3" >
< label for = "globalReferrerInput" class = "form-label" data-lang-key = "globalReferrerLabel" > Referrer Global (Opcional):< / label >
< input type = "text" class = "form-control form-control-sm" id = "globalReferrerInput" placeholder = "Ej: https://sitio.com/" >
< small class = "form-text text-secondary" data-lang-key = "globalReferrerHint" > Aplicable si el canal no define uno propio.< / small >
< / div >
< div class = "mb-3" >
< label for = "additionalGlobalHeadersInput" class = "form-label" data-lang-key = "additionalGlobalHeadersLabel" > Cabeceras Adicionales Globales (JSON):< / label >
< textarea class = "form-control form-control-sm" id = "additionalGlobalHeadersInput" rows = "2" placeholder = 'Ej: { "X-Custom": "valor" }' > < / textarea >
< small class = "form-text text-secondary" data-lang-key = "additionalGlobalHeadersHint" > Se fusionarán con cabeceras del canal (canal tiene precedencia).< / small >
< / div >
< / div >
< div class = "tab-pane fade" id = "daznSettingsPane" role = "tabpanel" aria-labelledby = "daznSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "daznSettingsTitle" > Configuración de DAZN< / h5 >
< div class = "mb-3" >
< label for = "daznAuthTokenSettingsInput" class = "form-label" data-lang-key = "daznAuthTokenLabel" > Token de Autenticación DAZN:< / label >
< textarea class = "form-control form-control-sm" id = "daznAuthTokenSettingsInput" rows = "3" placeholder = "Introduce tu Bearer token completo de DAZN aquí..." > < / textarea >
< small class = "form-text text-secondary" data-lang-key = "daznAuthTokenHint" > Este token se usará para obtener y actualizar los canales de DAZN en tu lista M3U. Se guarda de forma segura.< / small >
< / div >
< / div >
< div class = "tab-pane fade" id = "movistarSettingsPane" role = "tabpanel" aria-labelledby = "movistarSettingsTab" >
< h5 class = "settings-group-title" data-lang-key = "movistarManagementTitle" > Gestión de Movistar+< / h5 >
< div class = "alert alert-info alert-sm" role = "alert" style = "font-size:0.85rem;" >
< i class = "fas fa-info-circle me-2" > < / i >
< span data-lang-key = "movistarManagementDescription" > Esta sección permite gestionar la autenticación y los tokens para Movistar+.< / span >
< / div >
< div class = "card mb-3" >
< div class = "card-body" >
< h6 class = "card-title" > < i class = "fas fa-user-circle me-1" > < / i > < span data-lang-key = "movistarLoginTitle" > Iniciar Sesión / Obtener Tokens< / span > < / h6 >
< div class = "mb-2" >
< label for = "movistarUsernameSettingsInput" class = "form-label form-label-sm" data-lang-key = "emailLabel" > Usuario (Email):< / label >
< input type = "email" class = "form-control form-control-sm" id = "movistarUsernameSettingsInput" placeholder = "tuemail@ejemplo.com" >
< / div >
< div class = "mb-3" >
< label for = "movistarPasswordSettingsInput" class = "form-label form-label-sm" data-lang-key = "passwordLabel" > Contraseña:< / label >
< input type = "password" class = "form-control form-control-sm" id = "movistarPasswordSettingsInput" >
< / div >
< button class = "btn btn-primary btn-sm w-100" id = "movistarLoginBtnSettings" >
< i class = "fas fa-key" > < / i > < span data-lang-key = "movistarLoginButton" > Iniciar Sesión y Obtener Tokens< / span >
< / button >
< / div >
< / div >
< div class = "card mb-3" >
< div class = "card-body" >
< h6 class = "card-title" > < i class = "fas fa-database me-1" > < / i > < span data-lang-key = "movistarSavedLongTokensTitle" > Tokens de Sesión Larga Guardados< / span > < / h6 >
< div id = "movistarLongTokensListContainerSettings" class = "table-responsive mb-2" style = "max-height: 200px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: var(--radius-md);" >
< table class = "table table-sm table-striped table-hover" style = "font-size: 0.8rem;" >
< thead > < tr >
< th data-lang-key = "movistarTokenIdHeader" > ID< / th >
< th data-lang-key = "movistarAccountHeader" > Cuenta< / th >
< th data-lang-key = "movistarDeviceIdHeader" > Device ID< / th >
< th data-lang-key = "movistarExpiresHeader" > Expira< / th >
< th data-lang-key = "movistarStatusHeader" > Estado< / th >
< th data-lang-key = "movistarActionHeader" > Acción< / th >
< / tr > < / thead >
< tbody id = "movistarLongTokensTableBodySettings" >
< tr > < td colspan = "6" class = "text-center p-3" data-lang-key = "movistarLoading" > Cargando...< / td > < / tr >
< / tbody >
< / table >
< / div >
< div class = "btn-group btn-group-sm mb-2 w-100" role = "group" >
< button type = "button" class = "btn btn-outline-info" id = "movistarValidateAllBtnSettings" title = "Validar todos los tokens y refrescar si es necesario" > < i class = "fas fa-check-double" > < / i > < span data-lang-key = "movistarValidateAllButton" > Validar Todos< / span > < / button >
< button type = "button" class = "btn btn-outline-warning" id = "movistarDeleteExpiredBtnSettings" title = "Eliminar tokens expirados" > < i class = "fas fa-calendar-times" > < / i > < span data-lang-key = "movistarDeleteExpiredButton" > Elim. Expirados< / span > < / button >
< / div >
< div class = "input-group input-group-sm mb-1" >
< span class = "input-group-text" style = "font-size:0.75rem;" data-lang-key = "movistarAddJwtLabel" > Añadir JWT:< / span >
< textarea class = "form-control" id = "movistarAddManualTokenJwtInputSettings" rows = "2" placeholder = "Pega aquí el token JWT largo (eyJ...)" > < / textarea >
< / div >
< div class = "input-group input-group-sm mb-2" >
< span class = "input-group-text" style = "font-size:0.75rem;" data-lang-key = "movistarDeviceIdLabel" > Device ID:< / span >
< input type = "text" class = "form-control" id = "movistarAddManualTokenDeviceIdInputSettings" placeholder = "Opcional: WP_OTT-xxxx..." >
< button class = "btn btn-outline-success" type = "button" id = "movistarAddManualTokenBtnSettings" title = "Añadir Token Manualmente" > < i class = "fas fa-plus-circle" > < / i > < / button >
< / div >
< / div >
< / div >
< div id = "movistarDeviceManagementSectionSettings" class = "card mb-3" style = "display: none;" >
< div class = "card-body" >
< h6 class = "card-title" > < i class = "fas fa-mobile-alt me-1" > < / i > < span data-lang-key = "movistarDeviceManagementTitle" > Gestión de Dispositivos para Token:< / span > < span id = "selectedLongTokenIdDisplaySettings" class = "fw-normal text-muted" style = "font-size:0.8em;" > < / span > < / h6 >
< div id = "movistarDevicesListForSettings" class = "list-group list-group-flush mb-2" style = "max-height: 150px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: var(--radius-md); font-size:0.8rem;" >
< div class = "list-group-item text-muted text-center" data-lang-key = "movistarLoadDevicesHint" > Carga los dispositivos para el token seleccionado arriba.< / div >
< / div >
< div class = "btn-group btn-group-sm w-100" role = "group" >
< button type = "button" class = "btn btn-outline-primary" id = "movistarLoadDevicesForSettingsBtn" disabled > < i class = "fas fa-list-ul" > < / i > < span data-lang-key = "movistarLoadDevicesButton" > Cargar Dispositivos< / span > < / button >
< button type = "button" class = "btn btn-outline-success" id = "movistarAssociateDeviceForSettingsBtn" disabled > < i class = "fas fa-link" > < / i > < span data-lang-key = "movistarAssociateDeviceButton" > Asociar Seleccionado< / span > < / button >
< button type = "button" class = "btn btn-outline-warning" id = "movistarRegisterNewDeviceForSettingsBtn" disabled > < i class = "fas fa-plus-circle" > < / i > < span data-lang-key = "movistarRegisterNewDeviceButton" > Registrar Nuevo< / span > < / button >
< / div >
< / div >
< / div >
< div class = "card mb-3" >
< div class = "card-body" >
< h6 class = "card-title" > < i class = "fas fa-ticket-alt me-1" > < / i > < span data-lang-key = "movistarCurrentCdnTokenTitle" > Token Corto (CDN) Actual< / span > < / h6 >
< div class = "mb-2" >
< label for = "movistarCdnTokenDisplaySettings" class = "form-label form-label-sm" data-lang-key = "movistarCdnTokenLabel" > Token CDN (X-TCDN-Token):< / label >
< textarea class = "form-control form-control-sm" id = "movistarCdnTokenDisplaySettings" rows = "3" readonly placeholder = "No disponible" > < / textarea >
< small id = "movistarCdnTokenExpirySettings" class = "form-text text-muted" data-lang-key = "movistarCdnExpiresLabel" > Expira: -< / small >
< / div >
< div class = "btn-group btn-group-sm w-100" role = "group" >
< button class = "btn btn-info" id = "movistarRefreshCdnBtnSettings" title = "Obtener/Refrescar Token CDN usando token largo válido" > < i class = "fas fa-sync-alt" > < / i > < span data-lang-key = "movistarRefreshCdnButton" > Refrescar Token CDN< / span > < / button >
< button class = "btn btn-secondary" id = "movistarCopyCdnBtnSettings" disabled title = "Copiar Token CDN al portapapeles" > < i class = "fas fa-copy" > < / i > < span data-lang-key = "movistarCopyCdnButton" > Copiar CDN< / span > < / button >
< button class = "btn btn-success" id = "movistarApplyCdnToChannelsBtnSettings" disabled title = "Aplicar Token CDN a URLs Movistar en la lista actual" > < i class = "fas fa-check" > < / i > < span data-lang-key = "movistarApplyToChannelsButton" > Aplicar a Canales< / span > < / button >
< / div >
< / div >
< / div >
< div class = "card mb-3" >
< div class = "card-body" >
< h6 class = "card-title" > < i class = "fas fa-hdd me-1" > < / i > < span data-lang-key = "movistarVodCacheManagementTitle" > Gestión de Caché VOD Movistar+< / span > < / h6 >
< p class = "card-text small text-secondary" >
< span data-lang-key = "movistarVodCacheSavedDaysLabel" > Días de datos VOD guardados:< / span > < strong id = "movistarVodCacheCurrentDaysSpan" class = "text-info" > -< / strong > < br >
< span data-lang-key = "movistarVodCacheEstimatedSizeLabel" > Tamaño estimado de la caché:< / span > < strong id = "movistarVodCacheSizeSpan" class = "text-info" > -< / strong >
< / p >
< div class = "mb-3" >
< label for = "movistarVodCacheDaysToKeepInput" class = "form-label form-label-sm" data-lang-key = "movistarVodCacheDaysToKeepLabel" > Días a mantener en caché (1-90):< / label >
< input type = "number" class = "form-control form-control-sm" id = "movistarVodCacheDaysToKeepInput" min = "1" max = "90" step = "1" value = "15" >
< / div >
< button class = "btn btn-danger btn-sm w-100" id = "clearMovistarVodCacheBtnSettings" >
< i class = "fas fa-broom" > < / i > < span data-lang-key = "movistarClearVodCacheButton" > Limpiar Caché VOD Movistar+ Ahora< / span >
< / button >
< / div >
< / div >
< div class = "mt-3" >
< label for = "movistarLogAreaSettings" class = "form-label form-label-sm" data-lang-key = "movistarLogLabel" > Registro de Acciones:< / label >
< textarea class = "form-control form-control-sm" id = "movistarLogAreaSettings" rows = "5" readonly style = "font-size: 0.75rem; background-color: var(--bg-element);" > < / textarea >
< / div >
< / div >
< div class = "tab-pane fade" id = "sendM3uToServerPane" role = "tabpanel" aria-labelledby = "sendM3uToServerTab" >
< div class = "mb-4" >
< h5 class = "settings-group-title" data-lang-key = "sendM3uToServerTitle" > Enviar Lista M3U a Servidor< / h5 >
< div class = "mb-3" >
< label for = "m3uUploadServerUrlInput" class = "form-label" data-lang-key = "phpServerUrlLabel" > URL del Servidor PHP:< / label >
< input type = "url" class = "form-control form-control-sm" id = "m3uUploadServerUrlInput" placeholder = "https://tuservidor.com/ruta/receive_m3u.php" >
< small class = "form-text text-secondary" data-lang-key = "phpServerUrlHint" > Introduce la URL completa del script PHP en tu servidor que recibirá el archivo M3U.< / small >
< / div >
< div class = "d-grid" >
< button class = "btn-control btn-sm primary" id = "sendM3UToServerBtn" >
< span class = "icon-placeholder" > < / span > < span data-lang-key = "sendM3uToServerButton" > Enviar Lista M3U Cargada Ahora< / span >
< / button >
< / div >
< small class = "d-block mt-1 form-text text-secondary" data-lang-key = "sendM3uToServerHint" > La lista M3U actualmente cargada en el reproductor se enviará al servidor especificado.< / small >
< / div >
< hr >
< div >
< h5 class = "settings-group-title" data-lang-key = "phpScriptGeneratorTitle" > Generador de Script PHP (receive_m3u.php)< / h5 >
< p class = "text-secondary small" data-lang-key = "phpScriptGeneratorHint" > Usa este generador para crear un script PHP personalizado para tu servidor. Configura las opciones y luego copia el código generado.< / p >
< div class = "row" >
< div class = "col-md-6" >
< h6 data-lang-key = "securityOptions" > Opciones de Seguridad< / h6 >
< div class = "form-check form-switch mb-2" >
< input class = "form-check-input" type = "checkbox" id = "phpSecretKeyCheck" >
< label class = "form-check-label" for = "phpSecretKeyCheck" data-lang-key = "requireSecretKeyLabel" > Requerir clave secreta< / label >
< / div >
< div class = "input-group input-group-sm mb-3" >
< span class = "input-group-text" data-lang-key = "keyLabel" > Clave< / span >
< input type = "text" class = "form-control" id = "phpSecretKey" placeholder = "TuClaveSuperSecreta123" >
< / div >
< div class = "form-check form-switch mb-3" >
< input class = "form-check-input" type = "checkbox" id = "phpRestrictToExtensionIdCheck" checked >
< label class = "form-check-label" for = "phpRestrictToExtensionIdCheck" data-lang-key = "restrictToExtensionIdLabel" > Restringir a esta ID de Extensión< / label >
< / div >
< h6 data-lang-key = "fileOptions" > Opciones de Archivo< / h6 >
< div class = "mb-3" >
< label for = "phpSavePath" class = "form-label" data-lang-key = "savePathLabel" > Ruta de guardado en servidor< / label >
< input type = "text" class = "form-control form-control-sm" id = "phpSavePath" placeholder = "Ej: /home/usuario/public_html/listas/" >
< small class = "text-secondary" data-lang-key = "savePathHint" > Ruta absoluta. Si se deja vacía, se guarda en el mismo directorio que el script.< / small >
< / div >
< div class = "mb-2" >
< label class = "form-label" data-lang-key = "filenameLabel" > Nombre del archivo:< / label >
< div class = "form-check" >
< input class = "form-check-input" type = "radio" name = "phpFilenameMode" id = "phpFilenameOriginal" value = "original" checked >
< label class = "form-check-label" for = "phpFilenameOriginal" data-lang-key = "keepOriginalFilenameLabel" > Mantener nombre original (sanitizado)< / label >
< / div >
< div class = "form-check" >
< input class = "form-check-input" type = "radio" name = "phpFilenameMode" id = "phpFilenameFixed" value = "fixed" >
< label class = "form-check-label" for = "phpFilenameFixed" data-lang-key = "useFixedFilenameLabel" > Usar nombre fijo:< / label >
< / div >
< input type = "text" class = "form-control form-control-sm mt-1" id = "phpFixedFilename" placeholder = "mi_lista_fija.m3u" >
< / div >
< div class = "form-check form-switch mb-2" >
< input class = "form-check-input" type = "checkbox" id = "phpAddTimestamp" >
< label class = "form-check-label" for = "phpAddTimestamp" data-lang-key = "addTimestampLabel" > Añadir fecha/hora al nombre del archivo< / label >
< / div >
< div class = "form-check form-switch mb-2" >
< input class = "form-check-input" type = "checkbox" id = "phpOverwrite" checked >
< label class = "form-check-label" for = "phpOverwrite" data-lang-key = "overwriteLabel" > Sobrescribir si el archivo ya existe< / label >
< / div >
< / div >
< div class = "col-md-6" >
< h6 data-lang-key = "generatedScriptLabel" > Script Generado< / h6 >
< textarea id = "generatedPhpCode" class = "form-control" rows = "20" readonly > Configura las opciones y pulsa "Generar Script"...< / textarea >
< div class = "d-flex gap-2 mt-2" >
< button class = "btn-control btn-sm flex-grow-1" id = "generatePhpScriptBtn" data-lang-key = "generateScriptButton" > Generar Script< / button >
< button class = "btn-control btn-sm flex-grow-1" id = "copyPhpScriptBtn" data-lang-key = "copyScriptButton" > Copiar Script< / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "tab-pane fade" id = "appDataManagementPane" role = "tabpanel" aria-labelledby = "appDataManagementTab" >
< h5 class = "settings-group-title" data-lang-key = "dataManagementTitle" > Gestión de Datos de la Aplicación< / h5 >
< div class = "d-flex gap-2 mb-2" >
< button class = "btn-control btn-sm flex-grow-1" id = "exportSettingsBtn" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "exportSettingsButton" > Exportar Ajustes< / span > < / button >
< input type = "file" id = "importSettingsInput" class = "d-none" accept = ".json" >
< label for = "importSettingsInput" class = "btn-control btn-sm flex-grow-1 mb-0" role = "button" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "importSettingsButton" > Importar Ajustes< / span > < / label >
< / div >
< div class = "d-grid" >
< button class = "btn-control btn-sm btn-danger" id = "clearCacheBtn" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "clearCacheButton" > Limpiar Caché y Datos Locales< / span > < / button >
< / div >
< small class = "d-block mt-1 form-text text-secondary" data-lang-key = "clearCacheHint" > Esto borra: historial, favoritos, listas guardadas, servidores Xtream, paneles XCodec, EPG, token DAZN y tokens Movistar. La página se recargará.< / small >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "modal-footer justify-content-center" >
< button type = "button" class = "btn-control" data-bs-dismiss = "modal" data-lang-key = "settingsCancel" > Cancelar< / button >
< button type = "button" class = "btn-control primary" id = "saveSettingsBtn" > < span class = "icon-placeholder" > < / span > < span data-lang-key = "settingsSaveAndApply" > Guardar y Aplicar Ajustes< / span > < / button >
< / div >
< / div >
< / div >
< / div >
2025-06-25 13:32:28 +02:00
2025-06-19 04:02:44 +02:00
< script src = "libs/jquery-3.7.0.min.js" > < / script >
< script src = "libs/Sortable.min.js" > < / script >
< script src = "libs/bootstrap.bundle.min.js" > < / script >
< script src = "libs/particles.min.js" > < / script >
< script src = "libs/shaka-player.compiled.js" > < / script >
< script src = "libs/shaka-player.ui.js" > < / script >
< script src = "db_manager.js" defer > < / script >
< script src = "settings_manager.js" defer > < / script >
< script src = "m3u_utils.js" defer > < / script >
< script src = "ui_actions.js" defer > < / script >
< script src = "m3u_operations.js" defer > < / script >
< script src = "channel_ui.js" defer > < / script >
< script src = "player_interaction.js" defer > < / script >
< script src = "user_session.js" defer > < / script >
< script src = "movistar_vod_ui.js" defer > < / script >
2025-06-25 13:32:28 +02:00
< script src = "audio_enhancer.js" defer > < / script >
2025-06-19 04:02:44 +02:00
< script src = "shaka_handler.js" defer > < / script >
< script src = "epg.js" defer > < / script >
< script src = "orange_tv_client.js" defer > < / script >
< script src = "xtream_handler.js" defer > < / script >
< script src = "xcodec_handler.js" defer > < / script >
< script src = "dazn_handler.js" defer > < / script >
< script src = "movistar_handler.js" defer > < / script >
< script src = "atresplayer_handler.js" defer > < / script >
< script src = "bartv_handler.js" defer > < / script >
< script src = "m3u_sender.js" defer > < / script >
< script src = "php_handler.js" defer > < / script >
< script src = "draggable_modals.js" defer > < / script >
< script src = "editor_handler.js" defer > < / script >
< script src = "player.js" defer > < / script >
< / body >
< / html >