stream local files to player
This commit is contained in:
@@ -882,7 +882,6 @@ const AnimePlayer = (function() {
|
||||
if (els.video) els.video.playbackRate = parseFloat(value);
|
||||
}
|
||||
|
||||
// Volvemos al menú principal para confirmar visualmente (opcional, estilo YouTube)
|
||||
_settingsView = 'main';
|
||||
buildSettingsPanel();
|
||||
}
|
||||
@@ -904,31 +903,41 @@ const AnimePlayer = (function() {
|
||||
subtitleRenderer = null;
|
||||
}
|
||||
|
||||
// Find ASS subtitle
|
||||
const assSubtitle = _currentSubtitles.find(sub =>
|
||||
(sub.src && sub.src.endsWith('.ass')) ||
|
||||
(sub.label && sub.label.toLowerCase().includes('ass'))
|
||||
);
|
||||
const activeIdx = getActiveSubtitleIndex();
|
||||
if (activeIdx === -1) return;
|
||||
|
||||
if (!assSubtitle) {
|
||||
console.log('No ASS subtitles found in current list');
|
||||
return;
|
||||
}
|
||||
const currentSub = _currentSubtitles[activeIdx];
|
||||
if (!currentSub) return;
|
||||
|
||||
try {
|
||||
console.log('Initializing JASSUB for:', assSubtitle.label);
|
||||
const src = currentSub.src.toLowerCase();
|
||||
const label = (currentSub.label || '').toLowerCase();
|
||||
|
||||
// Check if JASSUB global is available
|
||||
if (window.SubtitleRenderer && typeof window.JASSUB !== 'undefined') {
|
||||
// --- CAMBIO AQUÍ: Pasamos els.subtitlesCanvas ---
|
||||
subtitleRenderer = new SubtitleRenderer(els.video, els.subtitlesCanvas);
|
||||
await subtitleRenderer.init(assSubtitle.src);
|
||||
} else {
|
||||
console.warn('JASSUB library not loaded.');
|
||||
// CASO 1: ASS (Usa JASSUB)
|
||||
if (src.endsWith('.ass') || label.includes('ass')) {
|
||||
try {
|
||||
console.log('Initializing JASSUB for:', currentSub.label);
|
||||
if (window.SubtitleRenderer && typeof window.JASSUB !== 'undefined') {
|
||||
subtitleRenderer = new SubtitleRenderer(els.video, els.subtitlesCanvas);
|
||||
await subtitleRenderer.init(currentSub.src);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('JASSUB setup error:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Subtitle renderer setup error:', e);
|
||||
subtitleRenderer = null;
|
||||
}
|
||||
// CASO 2: SRT (Usa SimpleSubtitleRenderer)
|
||||
else if (src.endsWith('.srt') || label.includes('srt')) {
|
||||
try {
|
||||
console.log('Initializing Simple Renderer for:', currentSub.label);
|
||||
if (window.SimpleSubtitleRenderer) {
|
||||
subtitleRenderer = new SimpleSubtitleRenderer(els.video, els.subtitlesCanvas);
|
||||
await subtitleRenderer.loadSubtitles(currentSub.src);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Simple Renderer setup error:', e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('Using native browser rendering for VTT');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,13 +1000,15 @@ const AnimePlayer = (function() {
|
||||
_rpcActive = false;
|
||||
setLoading("Checking availability...");
|
||||
|
||||
// Check local availability
|
||||
let shouldPlayLocal = false;
|
||||
try {
|
||||
const check = await fetch(`/api/library/${_animeId}/units`);
|
||||
const data = await check.json();
|
||||
const localUnit = data.units ? data.units.find(u => u.number === targetEp) : null;
|
||||
if (localUnit) shouldPlayLocal = true;
|
||||
|
||||
if (localUnit && els.extSelect.value === 'local') {
|
||||
shouldPlayLocal = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Availability check failed:", e);
|
||||
shouldPlayLocal = (els.extSelect.value === 'local');
|
||||
@@ -1216,49 +1227,64 @@ const AnimePlayer = (function() {
|
||||
|
||||
if (currentExt === 'local') {
|
||||
try {
|
||||
const localId = await getLocalEntryId();
|
||||
const check = await fetch(`/api/library/${_animeId}/units`);
|
||||
const data = await check.json();
|
||||
const targetUnit = data.units ? data.units.find(u => u.number === parseInt(_currentEpisode)) : null;
|
||||
setLoading("Fetching Local Unit Data...");
|
||||
|
||||
// 1. Obtener las unidades locales para encontrar el ID del episodio específico
|
||||
const unitsRes = await fetch(`/api/library/${_animeId}/units`);
|
||||
if (!unitsRes.ok) throw new Error("Could not fetch local units");
|
||||
|
||||
const unitsData = await unitsRes.json();
|
||||
const targetUnit = unitsData.units ? unitsData.units.find(u => u.number === parseInt(_currentEpisode)) : null;
|
||||
|
||||
if (!targetUnit) {
|
||||
console.log(`Episode ${_currentEpisode} not found locally.`);
|
||||
const localOption = els.extSelect.querySelector('option[value="local"]');
|
||||
if (localOption) localOption.remove();
|
||||
|
||||
const fallbackSource = (_entrySource === 'local') ? 'anilist' : _entrySource;
|
||||
if (els.extSelect.querySelector(`option[value="${fallbackSource}"]`)) {
|
||||
els.extSelect.value = fallbackSource;
|
||||
} else if (els.extSelect.options.length > 0) {
|
||||
els.extSelect.selectedIndex = 0;
|
||||
}
|
||||
handleExtensionChange(true);
|
||||
return;
|
||||
throw new Error(`Episode ${_currentEpisode} not found in local library`);
|
||||
}
|
||||
|
||||
const ext = targetUnit.format || targetUnit.name.split('.').pop().toLowerCase();
|
||||
setLoading("Initializing HLS Stream...");
|
||||
|
||||
if (![''].includes(ext)) {
|
||||
setLoading(`Local files are not supported on the web player yet. Use MPV.`);
|
||||
_rawVideoData = {
|
||||
url: targetUnit.path,
|
||||
headers: {}
|
||||
};
|
||||
if (els.mpvBtn) els.mpvBtn.style.display = 'flex';
|
||||
return;
|
||||
const manifestRes = await fetch(`/api/library/stream/anime/${unitsData.entry_id}/${targetUnit.number}/manifest`);
|
||||
if (!manifestRes.ok) throw new Error("Failed to generate stream manifest");
|
||||
|
||||
const manifestData = await manifestRes.json();
|
||||
|
||||
if (manifestData.chapters && manifestData.chapters.length > 0) {
|
||||
_skipIntervals = manifestData.chapters.map(c => ({
|
||||
startTime: c.start,
|
||||
endTime: c.end,
|
||||
type: c.title.toLowerCase().includes('op') ? 'op' :
|
||||
c.title.toLowerCase().includes('ed') ? 'ed' : 'chapter'
|
||||
}));
|
||||
renderSkipMarkers(_skipIntervals);
|
||||
monitorSkipButton(_skipIntervals);
|
||||
}
|
||||
|
||||
const localUrl = `/api/library/stream/${targetUnit.id}`;
|
||||
// 4. Mapear Subtítulos WebVTT
|
||||
const subs = (manifestData.subtitles || []).map(s => ({
|
||||
label: s.title || s.language || `Track ${s.index}`,
|
||||
srclang: s.language || 'unk',
|
||||
src: s.url // URL al endpoint de conversión VTT (.vtt)
|
||||
}));
|
||||
|
||||
// 5. Guardar referencia para MPV o descargas
|
||||
_rawVideoData = {
|
||||
url: localUrl,
|
||||
url: manifestData.masterPlaylist,
|
||||
headers: {}
|
||||
};
|
||||
_currentSubtitles = [];
|
||||
|
||||
initVideoPlayer(localUrl, 'mp4');
|
||||
// 6. Cargar en el reproductor (Hls.js gestionará los audios del master.m3u8)
|
||||
initVideoPlayer(manifestData.masterPlaylist, 'm3u8', subs);
|
||||
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
console.error("Local HLS Error:", e);
|
||||
setLoading("Local Error: " + e.message);
|
||||
|
||||
// Fallback: si falla, intentar cargar desde extensión online
|
||||
const localOption = els.extSelect.querySelector('option[value="local"]');
|
||||
if (localOption) localOption.remove();
|
||||
|
||||
const fallbackSource = (_entrySource === 'local') ? 'anilist' : _entrySource;
|
||||
els.extSelect.value = fallbackSource;
|
||||
handleExtensionChange(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1530,23 +1556,19 @@ const AnimePlayer = (function() {
|
||||
|
||||
function renderSkipMarkers(intervals) {
|
||||
if (!els.progressContainer || !els.video.duration) return;
|
||||
|
||||
els.progressContainer.querySelectorAll('.skip-range, .skip-cut').forEach(e => e.remove());
|
||||
|
||||
const duration = els.video.duration;
|
||||
|
||||
intervals.forEach(skip => {
|
||||
const startPct = (skip.startTime / duration) * 100;
|
||||
const endPct = (skip.endTime / duration) * 100;
|
||||
const startPct = (skip.startTime / els.video.duration) * 100;
|
||||
const endPct = (skip.endTime / els.video.duration) * 100;
|
||||
|
||||
const range = document.createElement('div');
|
||||
range.className = `skip-range ${skip.type}`; // 'op' o 'ed'
|
||||
range.className = `skip-range ${skip.type}`;
|
||||
range.style.left = `${startPct}%`;
|
||||
range.style.width = `${endPct - startPct}%`;
|
||||
els.progressContainer.appendChild(range);
|
||||
|
||||
createCut(startPct);
|
||||
|
||||
createCut(endPct);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user