diff --git a/desktop/src/scripts/anime/player.js b/desktop/src/scripts/anime/player.js index a97545e..42ba644 100644 --- a/desktop/src/scripts/anime/player.js +++ b/desktop/src/scripts/anime/player.js @@ -8,7 +8,10 @@ let plyrInstance; let hlsInstance; let totalEpisodes = 0; let animeTitle = ""; +let aniSkipData = null; +let isAnilist = false; +let malId = null; const params = new URLSearchParams(window.location.search); const firstKey = params.keys().next().value; @@ -21,6 +24,17 @@ const href = extName document.getElementById('back-link').href = href; document.getElementById('episode-label').innerText = `Episode ${currentEpisode}`; +async function loadAniSkip(malId, episode, duration) { + try { + const res = await fetch(`https://api.aniskip.com/v2/skip-times/${malId}/${episode}?types[]=op&types[]=ed&episodeLength=${duration}`); + if (!res.ok) return null; + const data = await res.json(); + return data.results || []; + } catch (error) { + console.error('Error loading AniSkip data:', error); + return null; + } +} async function loadMetadata() { try { @@ -61,6 +75,14 @@ async function loadMetadata() { seasonYear = data.year || ''; } + if (isAnilistFormat && data.idMal) { + isAnilist = true; + malId = data.idMal; + } else { + isAnilist = false; + malId = null; + } + document.getElementById('anime-title-details').innerText = title; document.getElementById('anime-title-details2').innerText = title; animeTitle = title; @@ -106,6 +128,102 @@ async function loadMetadata() { } } +async function applyAniSkip(video) { + if (!isAnilist || !malId) { + console.log('AniSkip disabled: isAnilist=' + isAnilist + ', malId=' + malId); + return; + } + + console.log('Loading AniSkip for MAL ID:', malId, 'Episode:', currentEpisode); + + aniSkipData = await loadAniSkip( + malId, + currentEpisode, + Math.floor(video.duration) + ); + + console.log('AniSkip data received:', aniSkipData); + + if (!aniSkipData || aniSkipData.length === 0) { + console.log('No AniSkip data available'); + return; + } + + let op, ed; + const markers = []; + + aniSkipData.forEach(item => { + const { startTime, endTime } = item.interval; + + if (item.skipType === 'op') { + op = { start: startTime, end: endTime }; + markers.push({ + start: startTime, + end: endTime, + label: 'Opening' + }); + + console.log('Opening found:', startTime, '-', endTime); + } + + if (item.skipType === 'ed') { + ed = { start: startTime, end: endTime }; + markers.push({ + start: startTime, + end: endTime, + label: 'Ending' + }); + + console.log('Ending found:', startTime, '-', endTime); + } + }); + + // Crear markers visuales en el DOM + if (plyrInstance && markers.length > 0) { + console.log('Creating visual markers:', markers); + + // Esperar a que el player esté completamente cargado + setTimeout(() => { + const progressContainer = document.querySelector('.plyr__progress'); + if (!progressContainer) { + console.error('Progress container not found'); + return; + } + + // Eliminar markers anteriores si existen + const oldMarkers = progressContainer.querySelector('.plyr__markers'); + if (oldMarkers) oldMarkers.remove(); + + // Crear contenedor de markers + const markersContainer = document.createElement('div'); + markersContainer.className = 'plyr__markers'; + + markers.forEach(marker => { + const markerElement = document.createElement('div'); + markerElement.className = 'plyr__marker'; + markerElement.dataset.label = marker.label; + + const startPercent = (marker.start / video.duration) * 100; + const widthPercent = ((marker.end - marker.start) / video.duration) * 100; + + markerElement.style.left = `${startPercent}%`; + markerElement.style.width = `${widthPercent}%`; + + markerElement.addEventListener('click', (e) => { + e.stopPropagation(); + video.currentTime = marker.start; + }); + + markersContainer.appendChild(markerElement); + }); + + + progressContainer.appendChild(markersContainer); + console.log('Visual markers created successfully'); + }, 500); + } +} + async function loadExtensionEpisodes() { try { const extQuery = extName ? `?source=${extName}` : "?source=anilist"; @@ -337,7 +455,15 @@ function playVideo(url, subtitles = []) { plyrInstance = new Plyr(video, { captions: { active: true, update: true, language: 'en' }, controls: ['play-large', 'play', 'progress', 'current-time', 'duration', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen'], - settings: ['captions', 'quality', 'speed'] + settings: ['captions', 'quality', 'speed'], + markers: { + enabled: true, + points: [] + } + }); + + video.addEventListener('loadedmetadata', () => { + applyAniSkip(video); }); let rpcActive = false; diff --git a/desktop/views/css/anime/watch.css b/desktop/views/css/anime/watch.css index 27bddfd..ecd6081 100644 --- a/desktop/views/css/anime/watch.css +++ b/desktop/views/css/anime/watch.css @@ -4,7 +4,11 @@ left: 0; right: 0; padding: var(--spacing-lg) var(--spacing-xl); - background: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, transparent 100%); + background: linear-gradient( + 180deg, + rgba(0, 0, 0, 0.8) 0%, + transparent 100% + ); z-index: 1000; pointer-events: none; } @@ -64,7 +68,11 @@ box-shadow: var(--shadow-sm); } -.control-group { display: flex; align-items: center; gap: var(--spacing-md); } +.control-group { + display: flex; + align-items: center; + gap: var(--spacing-md); +} .sd-toggle { display: flex; @@ -87,11 +95,15 @@ letter-spacing: 0.05em; } -.sd-option.active { color: var(--color-text-primary); } +.sd-option.active { + color: var(--color-text-primary); +} .sd-bg { position: absolute; - top: 4px; left: 4px; bottom: 4px; + top: 4px; + left: 4px; + bottom: 4px; width: calc(50% - 4px); background: var(--color-primary); border-radius: var(--radius-full); @@ -100,7 +112,9 @@ z-index: 1; } -.sd-toggle[data-state="dub"] .sd-bg { transform: translateX(100%); } +.sd-toggle[data-state="dub"] .sd-bg { + transform: translateX(100%); +} .source-select { appearance: none; @@ -119,8 +133,15 @@ transition: all var(--transition-base); } -.source-select:hover { border-color: var(--color-primary); background-color: var(--color-bg-card); } -.source-select:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-primary-glow); } +.source-select:hover { + border-color: var(--color-primary); + background-color: var(--color-bg-card); +} +.source-select:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px var(--color-primary-glow); +} .video-container { aspect-ratio: 16/9; @@ -128,14 +149,25 @@ background: var(--color-bg-base); border-radius: var(--radius-xl); overflow: hidden; - box-shadow: var(--shadow-lg), 0 0 0 1px var(--glass-border); + box-shadow: + var(--shadow-lg), + 0 0 0 1px var(--glass-border); position: relative; transition: box-shadow var(--transition-smooth); } -.video-container:hover { box-shadow: var(--shadow-lg), 0 0 0 1px var(--color-primary), var(--shadow-glow); } +.video-container:hover { + box-shadow: + var(--shadow-lg), + 0 0 0 1px var(--color-primary), + var(--shadow-glow); +} -#player { width: 100%; height: 100%; object-fit: contain; } +#player { + width: 100%; + height: 100%; + object-fit: contain; +} .loading-overlay { position: absolute; @@ -150,16 +182,25 @@ } .spinner { - width: 48px; height: 48px; - border: 3px solid rgba(255,255,255,0.1); + width: 48px; + height: 48px; + border: 3px solid rgba(255, 255, 255, 0.1); border-top-color: var(--color-primary); border-radius: 50%; animation: spin 0.8s linear infinite; } -@keyframes spin { to { transform: rotate(360deg); } } +@keyframes spin { + to { + transform: rotate(360deg); + } +} -.loading-overlay p { color: var(--color-text-secondary); font-size: 0.95rem; font-weight: 500; } +.loading-overlay p { + color: var(--color-text-secondary); + font-size: 0.95rem; + font-weight: 500; +} .episode-controls { display: flex; @@ -174,21 +215,49 @@ box-shadow: var(--shadow-sm); } -.episode-info h1 { font-size: 1.75rem; font-weight: 800; margin: 0 0 var(--spacing-xs); } -.episode-info p { color: var(--color-primary); font-weight: 600; font-size: 1rem; text-transform: uppercase; letter-spacing: 0.05em; } - -.navigation-buttons { display: flex; gap: var(--spacing-md); } - -.nav-btn { - display: flex; align-items: center; gap: var(--spacing-sm); - background: var(--color-bg-elevated); border: var(--border-subtle); - color: var(--color-text-primary); padding: 0.75rem 1.5rem; - border-radius: var(--radius-full); font-weight: 600; font-size: 0.9rem; - cursor: pointer; transition: all var(--transition-base); +.episode-info h1 { + font-size: 1.75rem; + font-weight: 800; + margin: 0 0 var(--spacing-xs); +} +.episode-info p { + color: var(--color-primary); + font-weight: 600; + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 0.05em; } -.nav-btn:hover:not(:disabled) { background: var(--color-primary); border-color: var(--color-primary); transform: translateY(-2px); box-shadow: var(--shadow-glow); } -.nav-btn:disabled { opacity: 0.3; cursor: not-allowed; } +.navigation-buttons { + display: flex; + gap: var(--spacing-md); +} + +.nav-btn { + display: flex; + align-items: center; + gap: var(--spacing-sm); + background: var(--color-bg-elevated); + border: var(--border-subtle); + color: var(--color-text-primary); + padding: 0.75rem 1.5rem; + border-radius: var(--radius-full); + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-base); +} + +.nav-btn:hover:not(:disabled) { + background: var(--color-primary); + border-color: var(--color-primary); + transform: translateY(-2px); + box-shadow: var(--shadow-glow); +} +.nav-btn:disabled { + opacity: 0.3; + cursor: not-allowed; +} .episode-carousel-compact { width: 100%; @@ -258,10 +327,18 @@ scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none; - mask-image: linear-gradient(to right, transparent, black var(--spacing-md), black calc(100% - var(--spacing-md)), transparent); + mask-image: linear-gradient( + to right, + transparent, + black var(--spacing-md), + black calc(100% - var(--spacing-md)), + transparent + ); } -.episode-carousel-compact-list::-webkit-scrollbar { display: none; } +.episode-carousel-compact-list::-webkit-scrollbar { + display: none; +} .carousel-item { flex: 0 0 200px; @@ -290,12 +367,14 @@ .carousel-item.active-ep-carousel { border-color: var(--color-primary); background: rgba(139, 92, 246, 0.15); - box-shadow: 0 0 0 2px var(--color-primary), var(--shadow-md); + box-shadow: + 0 0 0 2px var(--color-primary), + var(--shadow-md); transform: scale(1.02); } .carousel-item.active-ep-carousel::after { - content: 'WATCHING'; + content: "WATCHING"; position: absolute; top: 0; right: 0; @@ -391,7 +470,8 @@ color: var(--color-primary); } -.anime-details, .anime-extra-content { +.anime-details, +.anime-extra-content { max-width: 1600px; margin: var(--spacing-2xl) auto; } @@ -425,9 +505,17 @@ text-align: left; } -.cover-image { width: 220px; border-radius: var(--radius-md); box-shadow: var(--shadow-lg); } +.cover-image { + width: 220px; + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); +} -.details-content h1 { font-size: 1.5rem; font-weight: 800; margin-bottom: var(--spacing-md); } +.details-content h1 { + font-size: 1.5rem; + font-weight: 800; + margin-bottom: var(--spacing-md); +} .meta-badge { background: rgba(139, 92, 246, 0.12); @@ -439,8 +527,15 @@ border: 1px solid rgba(139, 92, 246, 0.2); } -.meta-badge.meta-score { background: var(--color-primary); color: white; } -.details-description { font-size: 1rem; line-height: 1.7; color: var(--color-text-secondary); } +.meta-badge.meta-score { + background: var(--color-primary); + color: white; +} +.details-description { + font-size: 1rem; + line-height: 1.7; + color: var(--color-text-secondary); +} .characters-header { display: flex; @@ -471,9 +566,15 @@ border-radius: var(--radius-sm); } -.expand-btn:hover { background: rgba(139, 92, 246, 0.1); } -.expand-btn svg { transition: transform var(--transition-smooth); } -.expand-btn[data-expanded="true"] svg { transform: rotate(180deg); } +.expand-btn:hover { + background: rgba(139, 92, 246, 0.1); +} +.expand-btn svg { + transition: transform var(--transition-smooth); +} +.expand-btn[data-expanded="true"] svg { + transform: rotate(180deg); +} .characters-carousel { display: flex; @@ -495,7 +596,6 @@ } .characters-carousel.expanded { - height: auto; max-height: 3200px; overflow-y: auto; @@ -519,15 +619,38 @@ background: transparent; } -.plyr--video { border-radius: var(--radius-xl); } -.plyr__controls { background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.5) 50%, transparent 100%) !important; padding: 1rem 1.5rem 1.5rem !important; } -.plyr--full-ui input[type=range] { color: var(--color-primary); } -.plyr__control:hover { background: rgba(255,255,255,0.12) !important; } -.plyr__menu__container { background: var(--glass-bg) !important; backdrop-filter: blur(16px); border: 1px solid var(--glass-border); box-shadow: var(--shadow-lg) !important; } +.plyr--video { + border-radius: var(--radius-xl); +} +.plyr__controls { + background: linear-gradient( + to top, + rgba(0, 0, 0, 0.9) 0%, + rgba(0, 0, 0, 0.5) 50%, + transparent 100% + ) !important; + padding: 1rem 1.5rem 1.5rem !important; +} +.plyr--full-ui input[type="range"] { + color: var(--color-primary); +} +.plyr__control:hover { + background: rgba(255, 255, 255, 0.12) !important; +} +.plyr__menu__container { + background: var(--glass-bg) !important; + backdrop-filter: blur(16px); + border: 1px solid var(--glass-border); + box-shadow: var(--shadow-lg) !important; +} @media (min-width: 1024px) { - .carousel-nav { display: flex; } - .watch-container { padding-top: 5rem; } + .carousel-nav { + display: flex; + } + .watch-container { + padding-top: 5rem; + } .details-cover { align-items: center; @@ -540,64 +663,168 @@ } @media (max-width: 768px) { - .watch-container { padding: 4.5rem 1rem; } - - .episode-carousel-compact-list { - padding: var(--spacing-sm) var(--spacing-md); - } - .carousel-header { - padding: 0 var(--spacing-md); - } - .carousel-item { - flex: 0 0 180px; - height: 100px; - } - .carousel-item-img-container { height: 60px; } - .carousel-item-info p { font-size: 0.95rem; } - .carousel-item.no-thumbnail { - flex: 0 0 140px; - height: 80px; + .watch-container { + padding: 5rem 1rem 2rem 1rem; + margin: 0; + width: 100%; + overflow-x: hidden; } - .details-container { flex-direction: column; text-align: center; } + .player-toolbar { + flex-direction: column; + align-items: stretch; + gap: 1rem; + padding: 1rem; + } - .details-cover { - align-items: center; + .control-group { + justify-content: space-between; width: 100%; } - .details-cover h1 { - font-size: 2rem; - text-align: center; - } - .cover-image { width: 180px; margin: 0 auto; } - .episode-controls { flex-direction: column; gap: var(--spacing-md); } - .navigation-buttons { width: 100%; justify-content: center; } - .nav-btn { flex: 1; justify-content: center; } -} - -@media (max-width: 480px) { - .episode-info h1, .details-content h1 { font-size: 1.5rem; } - - .carousel-item { - flex: 0 0 150px; - height: 90px; - } - .carousel-item-img-container { height: 50px; } - .carousel-item-info p { font-size: 0.9rem; } - .carousel-item.no-thumbnail { - flex: 0 0 120px; - height: 70px; + .source-select { + width: 100%; + background-position: right 1.5rem center; } - .details-cover h1 { - font-size: 1.5rem; + .episode-controls { + flex-direction: column; + align-items: flex-start; + gap: 1.5rem; } - .nav-btn span { display: none; } + .episode-info { + width: 100%; + text-align: left; + } + + .episode-info h1 { + font-size: 1.4rem; + line-height: 1.3; + } + + .navigation-buttons { + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.8rem; + } + + .nav-btn { + justify-content: center; + padding: 0.8rem; + width: 100%; + } + + .details-container { + flex-direction: column; + padding: 1.5rem; + gap: 2rem; + } + + .details-cover { + flex-direction: row; + align-items: flex-start; + width: 100%; + gap: 1.5rem; + } + + @media (max-width: 480px) { + .details-cover { + flex-direction: column; + align-items: center; + text-align: center; + } + + .details-cover h1 { + text-align: center; + } + } + + .cover-image { + width: 140px; + flex-shrink: 0; + margin: 0 auto; + } + + .details-content h1 { + font-size: 1.3rem; + } + + .characters-carousel { + justify-content: center; + padding-bottom: 1rem; + } .character-card { - - flex: 1 1 100%; + width: calc(50% - 0.75rem); + flex: 0 0 calc(50% - 0.75rem); } -} \ No newline at end of file +} + +.plyr__progress { + position: relative; +} + +.plyr__markers { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 100%; + pointer-events: none; + z-index: 2; +} + +.plyr__marker { + position: absolute; + bottom: 0; + width: 3px; + height: 100%; + background: rgba(255, 215, 0, 0.8); /* Color dorado para Opening */ + pointer-events: all; + cursor: pointer; + transition: all 0.2s ease; +} + +.plyr__marker[data-label*="Ending"] { + background: rgba(255, 100, 100, 0.8); /* Color rojo para Ending */ +} + +.plyr__marker:hover { + height: 120%; + width: 4px; + background: rgba(255, 215, 0, 1); +} + +.plyr__marker[data-label*="Ending"]:hover { + background: rgba(255, 100, 100, 1); +} + +/* Tooltip para mostrar el label */ +.plyr__marker::before { + content: attr(data-label); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%) translateY(-8px); + background: rgba(0, 0, 0, 0.9); + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; +} + +.plyr__marker:hover::before { + opacity: 1; +} +.plyr__marker { + position: absolute; + height: 100%; + background: rgba(255, 255, 255, 0.35); + cursor: pointer; +} diff --git a/docker/src/scripts/anime/player.js b/docker/src/scripts/anime/player.js index fdd7e50..65a3b13 100644 --- a/docker/src/scripts/anime/player.js +++ b/docker/src/scripts/anime/player.js @@ -7,6 +7,10 @@ let currentExtension = ''; let plyrInstance; let hlsInstance; let totalEpisodes = 0; +let aniSkipData = null; + +let isAnilist = false; +let malId = null; const params = new URLSearchParams(window.location.search); const firstKey = params.keys().next().value; @@ -20,6 +24,18 @@ const href = extName document.getElementById('back-link').href = href; document.getElementById('episode-label').innerText = `Episode ${currentEpisode}`; +async function loadAniSkip(malId, episode, duration) { + try { + const res = await fetch(`https://api.aniskip.com/v2/skip-times/${malId}/${episode}?types[]=op&types[]=ed&episodeLength=${duration}`); + if (!res.ok) return null; + const data = await res.json(); + return data.results || []; + } catch (error) { + console.error('Error loading AniSkip data:', error); + return null; + } +} + async function loadMetadata() { try { const extQuery = extName ? `?source=${extName}` : "?source=anilist"; @@ -40,11 +56,8 @@ async function loadMetadata() { let format = ''; let seasonYear = ''; let season = ''; - let episodesCount = 0; - let characters = []; if (isAnilistFormat) { - title = data.title.romaji || data.title.english || data.title.native || 'Anime Title'; description = data.description || 'No description available.'; coverImage = data.coverImage?.large || data.coverImage?.medium || ''; @@ -62,6 +75,14 @@ async function loadMetadata() { seasonYear = data.year || ''; } + if (isAnilistFormat && data.idMal) { + isAnilist = true; + malId = data.idMal; + } else { + isAnilist = false; + malId = null; + } + document.getElementById('anime-title-details').innerText = title; document.getElementById('anime-title-details2').innerText = title; document.title = `Watching ${title} - Ep ${currentEpisode}`; @@ -106,6 +127,102 @@ async function loadMetadata() { } } +async function applyAniSkip(video) { + if (!isAnilist || !malId) { + console.log('AniSkip disabled: isAnilist=' + isAnilist + ', malId=' + malId); + return; + } + + console.log('Loading AniSkip for MAL ID:', malId, 'Episode:', currentEpisode); + + aniSkipData = await loadAniSkip( + malId, + currentEpisode, + Math.floor(video.duration) + ); + + console.log('AniSkip data received:', aniSkipData); + + if (!aniSkipData || aniSkipData.length === 0) { + console.log('No AniSkip data available'); + return; + } + + let op, ed; + const markers = []; + + aniSkipData.forEach(item => { + const { startTime, endTime } = item.interval; + + if (item.skipType === 'op') { + op = { start: startTime, end: endTime }; + markers.push({ + start: startTime, + end: endTime, + label: 'Opening' + }); + + console.log('Opening found:', startTime, '-', endTime); + } + + if (item.skipType === 'ed') { + ed = { start: startTime, end: endTime }; + markers.push({ + start: startTime, + end: endTime, + label: 'Ending' + }); + + console.log('Ending found:', startTime, '-', endTime); + } + }); + + // Crear markers visuales en el DOM + if (plyrInstance && markers.length > 0) { + console.log('Creating visual markers:', markers); + + // Esperar a que el player esté completamente cargado + setTimeout(() => { + const progressContainer = document.querySelector('.plyr__progress'); + if (!progressContainer) { + console.error('Progress container not found'); + return; + } + + // Eliminar markers anteriores si existen + const oldMarkers = progressContainer.querySelector('.plyr__markers'); + if (oldMarkers) oldMarkers.remove(); + + // Crear contenedor de markers + const markersContainer = document.createElement('div'); + markersContainer.className = 'plyr__markers'; + + markers.forEach(marker => { + const markerElement = document.createElement('div'); + markerElement.className = 'plyr__marker'; + markerElement.dataset.label = marker.label; + + const startPercent = (marker.start / video.duration) * 100; + const widthPercent = ((marker.end - marker.start) / video.duration) * 100; + + markerElement.style.left = `${startPercent}%`; + markerElement.style.width = `${widthPercent}%`; + + markerElement.addEventListener('click', (e) => { + e.stopPropagation(); + video.currentTime = marker.start; + }); + + markersContainer.appendChild(markerElement); + }); + + + progressContainer.appendChild(markersContainer); + console.log('Visual markers created successfully'); + }, 500); + } +} + async function loadExtensionEpisodes() { try { const extQuery = extName ? `?source=${extName}` : "?source=anilist"; @@ -117,7 +234,6 @@ async function loadExtensionEpisodes() { if (Array.isArray(data) && data.length > 0) { populateEpisodeCarousel(data); } else { - const fallback = []; for (let i = 1; i <= totalEpisodes; i++) { fallback.push({ number: i, title: null, thumbnail: null }); @@ -132,6 +248,8 @@ async function loadExtensionEpisodes() { function populateEpisodeCarousel(episodesData) { const carousel = document.getElementById('episode-carousel'); + if (!carousel) return; + carousel.innerHTML = ''; episodesData.forEach((ep, index) => { @@ -319,9 +437,8 @@ function playVideo(url, subtitles = []) { if (plyrInstance) plyrInstance.destroy(); - while (video.textTracks.length > 0) { - video.removeChild(video.textTracks[0]); - } + const existingTracks = video.querySelectorAll('track'); + existingTracks.forEach(track => track.remove()); subtitles.forEach(sub => { if (!sub.url) return; @@ -337,7 +454,15 @@ function playVideo(url, subtitles = []) { plyrInstance = new Plyr(video, { captions: { active: true, update: true, language: 'en' }, controls: ['play-large', 'play', 'progress', 'current-time', 'duration', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen'], - settings: ['captions', 'quality', 'speed'] + settings: ['captions', 'quality', 'speed'], + markers: { + enabled: true, + points: [] + } + }); + + video.addEventListener('loadedmetadata', () => { + applyAniSkip(video); }); let alreadyTriggered = false; @@ -353,7 +478,6 @@ function playVideo(url, subtitles = []) { } }); - video.play().catch(() => console.log("Autoplay blocked")); } @@ -382,7 +506,6 @@ if (currentEpisode <= 1) { document.getElementById('prev-btn').disabled = true; } - async function sendProgress() { const token = localStorage.getItem('token'); if (!token) return; @@ -415,7 +538,5 @@ async function sendProgress() { } } - loadMetadata(); -loadExtensions(); - +loadExtensions(); \ No newline at end of file diff --git a/docker/views/css/anime/watch.css b/docker/views/css/anime/watch.css index d450672..c9be73a 100644 --- a/docker/views/css/anime/watch.css +++ b/docker/views/css/anime/watch.css @@ -761,3 +761,70 @@ flex: 0 0 calc(50% - 0.75rem); } } + + .plyr__progress { + position: relative; + } + +.plyr__markers { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 100%; + pointer-events: none; + z-index: 2; +} + +.plyr__marker { + position: absolute; + bottom: 0; + width: 3px; + height: 100%; + background: rgba(255, 215, 0, 0.8); /* Color dorado para Opening */ + pointer-events: all; + cursor: pointer; + transition: all 0.2s ease; +} + +.plyr__marker[data-label*="Ending"] { + background: rgba(255, 100, 100, 0.8); /* Color rojo para Ending */ +} + +.plyr__marker:hover { + height: 120%; + width: 4px; + background: rgba(255, 215, 0, 1); +} + +.plyr__marker[data-label*="Ending"]:hover { + background: rgba(255, 100, 100, 1); +} + +/* Tooltip para mostrar el label */ +.plyr__marker::before { + content: attr(data-label); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%) translateY(-8px); + background: rgba(0, 0, 0, 0.9); + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; +} + +.plyr__marker:hover::before { + opacity: 1; +} +.plyr__marker { + position: absolute; + height: 100%; + background: rgba(255, 255, 255, 0.35); + cursor: pointer; +}