diff --git a/desktop/src/scripts/anime/player.js b/desktop/src/scripts/anime/player.js index cfe325c..a55c79d 100644 --- a/desktop/src/scripts/anime/player.js +++ b/desktop/src/scripts/anime/player.js @@ -1,5 +1,4 @@ const AnimePlayer = (function() { - let _animeId = null; let _currentEpisode = 0; let _entrySource = 'anilist'; @@ -9,24 +8,23 @@ const AnimePlayer = (function() { let _skipBtn = null; let _skipIntervals = []; let _progressUpdated = false; - let _animeTitle = "Anime"; let _rpcActive = false; - let _rawVideoData = null; let _currentSubtitles = []; - let _localEntryId = null; let _totalEpisodes = 0; - - let plyrInstance = null; - let hlsInstance = null; - let _manualExtensionId = null; + let hlsInstance = null; + let subtitleRenderer = null; + let cursorTimeout = null; + let settingsPanelActive = false; + const els = { wrapper: null, playerWrapper: null, + playerContainer: null, video: null, loader: null, loaderText: null, @@ -45,6 +43,20 @@ const AnimePlayer = (function() { dlConfirmBtn: null, dlCancelBtn: null, manualMatchBtn: null, + + // Custom Controls + playPauseBtn: null, + volumeBtn: null, + volumeSlider: null, + timeDisplay: null, + settingsBtn: null, + settingsPanel: null, + fullscreenBtn: null, + progressContainer: null, + progressPlayed: null, + progressBuffer: null, + progressHandle: null, + subtitlesCanvas: null }; function init(animeId, initialSource, isLocal, animeData) { @@ -52,7 +64,6 @@ const AnimePlayer = (function() { _entrySource = initialSource || 'anilist'; _isLocal = isLocal; _malId = animeData.idMal || null; - _totalEpisodes = animeData.episodes || 1000; if (animeData.title) { @@ -62,15 +73,21 @@ const AnimePlayer = (function() { _skipIntervals = []; _localEntryId = null; + initElements(); + setupEventListeners(); + loadExtensionsList(); + } + + function initElements() { els.wrapper = document.getElementById('hero-wrapper'); els.playerWrapper = document.getElementById('player-wrapper'); + els.playerContainer = els.playerWrapper?.querySelector('.player-container'); els.video = document.getElementById('player'); els.loader = document.getElementById('player-loading'); els.loaderText = document.getElementById('player-loading-text'); + + // Header controls els.downloadBtn = document.getElementById('download-btn'); - if (els.downloadBtn) { - els.downloadBtn.addEventListener('click', downloadEpisode); - } els.downloadModal = document.getElementById('download-modal'); els.dlQualityList = document.getElementById('dl-quality-list'); els.dlAudioList = document.getElementById('dl-audio-list'); @@ -78,182 +95,621 @@ const AnimePlayer = (function() { els.dlConfirmBtn = document.getElementById('confirm-dl-btn'); els.dlCancelBtn = document.getElementById('cancel-dl-btn'); els.manualMatchBtn = document.getElementById('manual-match-btn'); - - const closeDlModalBtn = document.getElementById('close-download-modal'); - - if (els.dlConfirmBtn) els.dlConfirmBtn.onclick = executeDownload; - if (els.dlCancelBtn) els.dlCancelBtn.onclick = () => els.downloadModal.style.display = 'none'; - if (closeDlModalBtn) closeDlModalBtn.onclick = () => els.downloadModal.style.display = 'none'; - const closeModal = () => { - if (els.downloadModal) { - els.downloadModal.classList.remove('show'); - - setTimeout(() => { - - if(!els.downloadModal.classList.contains('show')) { - els.downloadModal.style.display = 'none'; - } - }, 300); - } - }; - if (els.dlCancelBtn) els.dlCancelBtn.onclick = closeModal; - - if (closeDlModalBtn) closeDlModalBtn.onclick = closeModal; els.mpvBtn = document.getElementById('mpv-btn'); - if (els.downloadModal) { - els.downloadModal.addEventListener('click', (e) => { - - if (e.target === els.downloadModal) { - closeModal(); - } - }); - } - if (els.mpvBtn) els.mpvBtn.addEventListener('click', openInMPV); - els.serverSelect = document.getElementById('server-select'); els.extSelect = document.getElementById('extension-select'); els.subDubToggle = document.getElementById('sd-toggle'); els.epTitle = document.getElementById('player-episode-title'); - els.prevBtn = document.getElementById('prev-ep-btn'); els.nextBtn = document.getElementById('next-ep-btn'); - const closeBtn = document.getElementById('close-player-btn'); - if(closeBtn) closeBtn.addEventListener('click', closePlayer); + // Custom controls + els.playPauseBtn = document.getElementById('play-pause-btn'); + els.volumeBtn = document.getElementById('volume-btn'); + els.volumeSlider = document.getElementById('volume-slider'); + els.timeDisplay = document.getElementById('time-display'); + els.settingsBtn = document.getElementById('settings-btn'); + els.settingsPanel = document.getElementById('settings-panel'); + els.fullscreenBtn = document.getElementById('fullscreen-btn'); + els.progressContainer = document.querySelector('.progress-container'); + els.progressPlayed = document.querySelector('.progress-played'); + els.progressBuffer = document.querySelector('.progress-buffer'); + els.progressHandle = document.querySelector('.progress-handle'); - if(els.prevBtn) els.prevBtn.onclick = () => playEpisode(_currentEpisode - 1); - if(els.nextBtn) els.nextBtn.onclick = () => playEpisode(_currentEpisode + 1); + // Subtitles canvas + els.subtitlesCanvas = document.getElementById('subtitles-canvas'); + // Create skip button if not exists if (!document.getElementById('skip-overlay-btn')) { const btn = document.createElement('button'); btn.id = 'skip-overlay-btn'; - const container = document.querySelector('.player-container'); - if(container) container.appendChild(btn); + if(els.playerContainer) els.playerContainer.appendChild(btn); _skipBtn = btn; } else { _skipBtn = document.getElementById('skip-overlay-btn'); } + } + + function setupEventListeners() { + // Close player + const closeBtn = document.getElementById('close-player-btn'); + if(closeBtn) closeBtn.addEventListener('click', closePlayer); + + // Episode navigation + if(els.prevBtn) els.prevBtn.onclick = () => playEpisode(_currentEpisode - 1); + if(els.nextBtn) els.nextBtn.onclick = () => playEpisode(_currentEpisode + 1); + + // Skip button if(_skipBtn) _skipBtn.onclick = () => handleOverlayClick(); + // Audio mode toggle if(els.subDubToggle) els.subDubToggle.addEventListener('click', toggleAudioMode); + + // Server/Extension changes if(els.serverSelect) els.serverSelect.addEventListener('change', () => loadStream()); if(els.extSelect) els.extSelect.addEventListener('change', () => handleExtensionChange(true)); + + // Manual match if (els.manualMatchBtn) { els.manualMatchBtn.addEventListener('click', openMatchModal); } - loadExtensionsList(); + // Download + if (els.downloadBtn) { + els.downloadBtn.addEventListener('click', downloadEpisode); + } + if (els.dlConfirmBtn) els.dlConfirmBtn.onclick = executeDownload; + if (els.dlCancelBtn) els.dlCancelBtn.onclick = closeDownloadModal; + const closeDlModalBtn = document.getElementById('close-download-modal'); + if (closeDlModalBtn) closeDlModalBtn.onclick = closeDownloadModal; + if (els.downloadModal) { + els.downloadModal.addEventListener('click', (e) => { + if (e.target === els.downloadModal) closeDownloadModal(); + }); + } + + // MPV + if(els.mpvBtn) els.mpvBtn.addEventListener('click', openInMPV); + + // Custom controls + setupCustomControls(); + + // Cursor management + setupCursorManagement(); + + // Keyboard shortcuts + setupKeyboardShortcuts(); } - function openMatchModal() { - const currentExt = els.extSelect.value; - if (!currentExt || currentExt === 'local') return; + function setupCustomControls() { + // Play/Pause + if(els.playPauseBtn) { + els.playPauseBtn.onclick = togglePlayPause; + } + if(els.video) { + // Remove old listeners to be safe (though usually new element) + els.video.onclick = togglePlayPause; + els.video.ondblclick = toggleFullscreen; + } - MatchModal.open({ - provider: currentExt, - initialQuery: _animeTitle, // Variable existente en player.js - onSearch: async (query, prov) => { - const res = await fetch(`/api/search/${prov}?q=${encodeURIComponent(query)}`); - const data = await res.json(); - return data.results || []; - }, - onSelect: (item) => { - console.log("Selected Anime ID:", item.id); - _manualExtensionId = item.id; - loadStream(); + // Volume + if(els.volumeBtn) { + els.volumeBtn.onclick = toggleMute; + } + if(els.volumeSlider) { + els.volumeSlider.oninput = (e) => { + setVolume(e.target.value / 100); + }; + } + + // Settings + if(els.settingsBtn) { + els.settingsBtn.onclick = () => { + settingsPanelActive = !settingsPanelActive; + els.settingsPanel?.classList.toggle('active', settingsPanelActive); + }; + } + + // Close settings when clicking outside + document.onclick = (e) => { + if (settingsPanelActive && els.settingsPanel && + !els.settingsPanel.contains(e.target) && + !els.settingsBtn.contains(e.target)) { + settingsPanelActive = false; + els.settingsPanel.classList.remove('active'); + } + }; + + // Fullscreen + if(els.fullscreenBtn) { + els.fullscreenBtn.onclick = toggleFullscreen; + } + + // Progress bar + if(els.progressContainer) { + els.progressContainer.onclick = seekToPosition; + } + + // Video events + if(els.video) { + // Remove previous listeners first if sticking to same element, but we replace element usually + els.video.onplay = onPlay; + els.video.onpause = onPause; + els.video.ontimeupdate = onTimeUpdate; + els.video.onprogress = onProgress; + els.video.onloadedmetadata = onLoadedMetadata; + els.video.onended = onEnded; + els.video.onvolumechange = onVolumeChange; + } + } + + function setupCursorManagement() { + if (!els.playerContainer) return; + + const showCursor = () => { + els.playerContainer.classList.add('show-cursor'); + clearTimeout(cursorTimeout); + if (!els.video?.paused) { + cursorTimeout = setTimeout(() => { + els.playerContainer.classList.remove('show-cursor'); + }, 3000); + } + }; + + els.playerContainer.addEventListener('mousemove', showCursor); + els.playerContainer.addEventListener('mouseenter', showCursor); + + els.video?.addEventListener('pause', () => { + clearTimeout(cursorTimeout); + els.playerContainer.classList.add('show-cursor'); + }); + + els.video?.addEventListener('play', () => { + showCursor(); + }); + } + + function setupKeyboardShortcuts() { + document.addEventListener('keydown', (e) => { + if (!els.playerWrapper || els.playerWrapper.style.display === 'none') return; + + // Ignore if typing in input + if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; + + switch(e.key.toLowerCase()) { + case ' ': + case 'k': + e.preventDefault(); + togglePlayPause(); + break; + case 'f': + e.preventDefault(); + toggleFullscreen(); + break; + case 'm': + e.preventDefault(); + toggleMute(); + break; + case 'arrowleft': + e.preventDefault(); + seekRelative(-10); + break; + case 'arrowright': + e.preventDefault(); + seekRelative(10); + break; + case 'j': + e.preventDefault(); + seekRelative(-10); + break; + case 'l': + e.preventDefault(); + seekRelative(10); + break; + case 'arrowup': + e.preventDefault(); + adjustVolume(0.1); + break; + case 'arrowdown': + e.preventDefault(); + adjustVolume(-0.1); + break; + case 'n': + e.preventDefault(); + if (_currentEpisode < _totalEpisodes) playEpisode(_currentEpisode + 1); + break; + case 'p': + e.preventDefault(); + if (_currentEpisode > 1) playEpisode(_currentEpisode - 1); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + e.preventDefault(); + const percent = parseInt(e.key) / 10; + seekToPercent(percent); + break; } }); } - async function openInMPV() { - if (!_rawVideoData) { - alert("No video loaded yet."); - return; + // Control functions + function togglePlayPause() { + if (!els.video) return; + if (els.video.paused) { + els.video.play().catch(() => {}); + } else { + els.video.pause(); } + } - const token = localStorage.getItem('token'); - if (!token) { - alert("You need to be logged in."); - return; + function toggleMute() { + if (!els.video) return; + els.video.muted = !els.video.muted; + } + + function setVolume(vol) { + if (!els.video) return; + els.video.volume = Math.max(0, Math.min(1, vol)); + els.video.muted = vol === 0; + } + + function adjustVolume(delta) { + if (!els.video) return; + setVolume(els.video.volume + delta); + if (els.volumeSlider) { + els.volumeSlider.value = els.video.volume * 100; } - const body = { - title: `${_animeTitle} - Episode ${_currentEpisode}`, - video: _rawVideoData, - subtitles: _currentSubtitles, - chapters: _skipIntervals, - animeId: _animeId, - episode: _currentEpisode, - entrySource: _entrySource, - token: localStorage.getItem('token') - }; + } - try { - const res = await fetch('/api/watch/mpv', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body) - }); - - if (res.ok) { - console.log("MPV Request Sent"); - closePlayer(); - } else { - console.error("MPV Request Failed"); + function toggleFullscreen() { + if (!document.fullscreenElement && !document.webkitFullscreenElement) { + const elem = els.playerContainer || els.playerWrapper; + if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.webkitRequestFullscreen) { + elem.webkitRequestFullscreen(); } - } catch (e) { - console.error("MPV Error:", e); - } finally { - if(els.mpvBtn) { - els.mpvBtn.innerHTML = originalContent; - els.mpvBtn.disabled = false; + } else { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); } } } - function sendRPC({ startTimestamp, endTimestamp, paused = false } = {}) { - fetch("/api/rpc", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - details: _animeTitle, - state: `Episode ${_currentEpisode}`, - mode: "watching", - startTimestamp, - endTimestamp, - paused - }) - }).catch(e => console.warn("RPC Error:", e)); + function seekToPosition(e) { + if (!els.video || !els.progressContainer) return; + const rect = els.progressContainer.getBoundingClientRect(); + const pos = (e.clientX - rect.left) / rect.width; + els.video.currentTime = pos * els.video.duration; } - function handleOverlayClick() { - if (!_skipBtn) return; - if (_skipBtn.classList.contains('is-next')) { + function updateProgressHandle(e) { + if (!els.progressHandle || !els.progressContainer) return; + const rect = els.progressContainer.getBoundingClientRect(); + const pos = (e.clientX - rect.left) / rect.width; + els.progressHandle.style.left = `${pos * 100}%`; + } + + function seekRelative(seconds) { + if (!els.video) return; + els.video.currentTime = Math.max(0, Math.min(els.video.duration, els.video.currentTime + seconds)); + } + + function seekToPercent(percent) { + if (!els.video) return; + els.video.currentTime = els.video.duration * percent; + } + + // Video event handlers + function onPlay() { + if (els.playPauseBtn) { + els.playPauseBtn.innerHTML = ` + + + + `; + } + + if (!els.video.duration) return; + const elapsed = Math.floor(els.video.currentTime); + const start = Math.floor(Date.now() / 1000) - elapsed; + const end = start + Math.floor(els.video.duration); + sendRPC({ startTimestamp: start, endTimestamp: end }); + _rpcActive = true; + } + + function onPause() { + if (els.playPauseBtn) { + els.playPauseBtn.innerHTML = ` + + + + `; + } + + if (_rpcActive) sendRPC({ paused: true }); + } + + function onTimeUpdate() { + if (!els.video) return; + + // Update progress bar + const percent = (els.video.currentTime / els.video.duration) * 100; + if (els.progressPlayed) { + els.progressPlayed.style.width = `${percent}%`; + } + if (els.progressHandle) { + els.progressHandle.style.left = `${percent}%`; + } + + // Update time display + if (els.timeDisplay) { + const current = formatTime(els.video.currentTime); + const total = formatTime(els.video.duration); + els.timeDisplay.textContent = `${current} / ${total}`; + } + + // Update progress for AniList + if (!_progressUpdated && els.video.duration) { + const percentage = els.video.currentTime / els.video.duration; + if (percentage >= 0.8) { + updateProgress(); + _progressUpdated = true; + } + } + + // Update subtitles - SAFE CHECK + // We only call setCurrentTime if renderer exists AND is not in disposed state + if (subtitleRenderer) { + try { + subtitleRenderer.setCurrentTime(els.video.currentTime); + } catch (e) { + // If the worker is dead or instance is invalid, silence the error + // and potentially nullify the renderer to stop further attempts + console.warn("Subtitle renderer error during timeupdate:", e); + subtitleRenderer = null; + } + } + } + + function onProgress() { + if (!els.video || !els.progressBuffer) return; + if (els.video.buffered.length > 0) { + const bufferedEnd = els.video.buffered.end(els.video.buffered.length - 1); + const percent = (bufferedEnd / els.video.duration) * 100; + els.progressBuffer.style.width = `${percent}%`; + } + } + + function onLoadedMetadata() { + if (els.video) { + applyAniSkip(_malId, _currentEpisode); + } + } + + function onEnded() { + if (_currentEpisode < _totalEpisodes) { playEpisode(_currentEpisode + 1); - } else if (_skipBtn.dataset.seekTo) { - els.video.currentTime = parseFloat(_skipBtn.dataset.seekTo); } - _skipBtn.classList.remove('visible'); } - async function getLocalEntryId() { - if (_localEntryId) return _localEntryId; + function onVolumeChange() { + if (!els.video || !els.volumeBtn || !els.volumeSlider) return; + + const volume = els.video.volume; + const muted = els.video.muted; + + els.volumeSlider.value = volume * 100; + + let icon; + if (muted || volume === 0) { + icon = ''; + } else if (volume < 0.5) { + icon = ''; + } else { + icon = ''; + } + + els.volumeBtn.innerHTML = icon; + } + + function formatTime(seconds) { + if (!isFinite(seconds) || isNaN(seconds)) return '0:00'; + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + if (h > 0) { + return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; + } + return `${m}:${s.toString().padStart(2, '0')}`; + } + + // Settings Panel + function buildSettingsPanel() { + if (!els.settingsPanel) return; + + let html = ''; + + // 1. Quality settings (for HLS) + if (hlsInstance && hlsInstance.levels && hlsInstance.levels.length > 1) { + html += '
'; + html += '
Calidad
'; + + html += `
+ Auto + + + +
`; + + hlsInstance.levels.forEach((level, i) => { + const active = hlsInstance.currentLevel === i; + html += `
+ ${level.height}p + + + +
`; + }); + html += '
'; + } + + // 2. Audio tracks + if (hlsInstance && hlsInstance.audioTracks && hlsInstance.audioTracks.length > 1) { + html += '
'; + html += '
Audio
'; + hlsInstance.audioTracks.forEach((track, i) => { + const active = hlsInstance.audioTrack === i; + const label = track.name || track.lang || `Audio ${i + 1}`; + html += `
+ ${label} + + + +
`; + }); + html += '
'; + } + + // 3. Subtitles (ESTO FALTABA) + if (_currentSubtitles && _currentSubtitles.length > 0) { + html += '
'; + html += '
Subtítulos
'; + + // Opción para desactivar + const isOff = els.video.textTracks && Array.from(els.video.textTracks).every(t => t.mode === 'hidden' || t.mode === 'disabled'); + + html += `
+ Off + + + +
`; + + // Lista de subtítulos + _currentSubtitles.forEach((sub, i) => { + // Verificamos si este track está activo en el elemento de video + let isActive = false; + if (els.video.textTracks && els.video.textTracks[i]) { + isActive = els.video.textTracks[i].mode === 'showing'; + } + + html += `
+ ${sub.label || sub.language || 'Desconocido'} + + + +
`; + }); + html += '
'; + } + + // 4. Playback speed + html += '
'; + html += '
Velocidad
'; + const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; + speeds.forEach(speed => { + const active = els.video && Math.abs(els.video.playbackRate - speed) < 0.01; + html += `
+ ${speed}x + + + +
`; + }); + html += '
'; + + els.settingsPanel.innerHTML = html; + + // Add click handlers + els.settingsPanel.querySelectorAll('.settings-option').forEach(opt => { + opt.addEventListener('click', () => { + const action = opt.dataset.action; + const value = opt.dataset.value; + + if (action === 'quality') { + if (hlsInstance) { + hlsInstance.currentLevel = parseInt(value); + buildSettingsPanel(); + } + } else if (action === 'audio') { + if (hlsInstance) { + hlsInstance.audioTrack = parseInt(value); + buildSettingsPanel(); + } + } else if (action === 'subtitle') { + // Lógica para cambiar subtítulos + const idx = parseInt(value); + if (els.video && els.video.textTracks) { + Array.from(els.video.textTracks).forEach((track, i) => { + // Activamos si el índice coincide, desactivamos si es -1 u otro + track.mode = (i === idx) ? 'showing' : 'hidden'; + }); + } + + // Si usas SubtitlesOctopus (Canvas) para ASS, aquí podrías necesitar lógica extra, + // pero para la mayoría de los casos web (VTT), cambiar el modo del track es suficiente. + buildSettingsPanel(); + + } else if (action === 'speed') { + if (els.video) { + els.video.playbackRate = parseFloat(value); + buildSettingsPanel(); + } + } + }); + }); + } + + // Subtitle renderer with libass + async function initSubtitleRenderer() { + if (!window.SubtitlesOctopus || !els.video || !els.subtitlesCanvas) return; + + // Ensure clean slate + if (subtitleRenderer) { + try { subtitleRenderer.dispose(); } catch(e) { console.warn(e); } + subtitleRenderer = null; + } + + // Find ASS subtitle + const assSubtitle = _currentSubtitles.find(sub => + sub.src && (sub.src.endsWith('.ass') || sub.label?.toLowerCase().includes('ass')) + ); + + if (!assSubtitle) return; + try { - const res = await fetch(`/api/library/anime/${_animeId}`); - if (!res.ok) return null; - const data = await res.json(); - _localEntryId = data.id; - return _localEntryId; + subtitleRenderer = new SubtitlesOctopus({ + video: els.video, + canvas: els.subtitlesCanvas, + subUrl: assSubtitle.src, + fonts: [], + workerUrl: '/libs/subtitles-octopus-worker.js', + legacyWorkerUrl: '/libs/subtitles-octopus-worker-legacy.js', + }); } catch (e) { - console.error("Error fetching local ID:", e); - return null; + console.error('Subtitle renderer error:', e); + subtitleRenderer = null; } } + // Player lifecycle async function playEpisode(episodeNumber) { const targetEp = parseInt(episodeNumber); if (targetEp < 1 || targetEp > _totalEpisodes) return; _currentEpisode = targetEp; + _progressUpdated = false; if(els.epTitle) els.epTitle.innerText = `Episode ${targetEp}`; if(els.prevBtn) els.prevBtn.disabled = (_currentEpisode <= 1); @@ -264,7 +720,6 @@ const AnimePlayer = (function() { _skipBtn.classList.remove('is-next'); } - // Actualizar URL y Botones const newUrl = new URL(window.location); newUrl.searchParams.set('episode', targetEp); window.history.pushState({}, '', newUrl); @@ -272,48 +727,29 @@ const AnimePlayer = (function() { if(els.playerWrapper) els.playerWrapper.style.display = 'block'; document.body.classList.add('stop-scrolling'); - // Pausar trailer si existe const trailer = document.querySelector('#trailer-player iframe'); if(trailer) trailer.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*'); _rpcActive = false; - - // Mostrar carga mientras verificamos disponibilidad setLoading("Checking availability..."); - // --- LÓGICA DE AUTO-DETECCIÓN LOCAL --- + // Check local availability let shouldPlayLocal = false; - try { - // Consultamos a la API si ESTE episodio específico existe localmente const check = await fetch(`/api/library/${_animeId}/units`); const data = await check.json(); - - // Buscamos el episodio en la respuesta const localUnit = data.units ? data.units.find(u => u.number === targetEp) : null; - - if (localUnit) { - shouldPlayLocal = true; - } + if (localUnit) shouldPlayLocal = true; } catch (e) { console.warn("Availability check failed:", e); - // Si falla el check (ej: error de red), mantenemos el modo actual por seguridad shouldPlayLocal = (els.extSelect.value === 'local'); } - if (shouldPlayLocal) { - els.manualMatchBtn.style.display = 'none'; - } - else{ - els.manualMatchBtn.style.display = 'flex'; - + if (els.manualMatchBtn) { + els.manualMatchBtn.style.display = shouldPlayLocal ? 'none' : 'flex'; } if (shouldPlayLocal) { - // CASO 1: El episodio EXISTE localmente - console.log(`Episode ${targetEp} found locally. Switching to Local.`); - - // 1. Asegurar que 'local' está en el dropdown y seleccionarlo let localOption = els.extSelect.querySelector('option[value="local"]'); if (!localOption) { localOption = document.createElement('option'); @@ -322,44 +758,23 @@ const AnimePlayer = (function() { els.extSelect.appendChild(localOption); } els.extSelect.value = 'local'; - - // 2. Ocultar controles que no son para local if(els.subDubToggle) els.subDubToggle.style.display = 'none'; if(els.serverSelect) els.serverSelect.style.display = 'none'; - - // 3. Cargar stream loadStream(); - } else { - // CASO 2: El episodio NO existe localmente (es Remoto) - - // Si estábamos en modo 'local', tenemos que cambiar a una extensión if (els.extSelect.value === 'local') { - console.log(`Episode ${targetEp} not local. Switching to Extension.`); - - // 1. Quitar la opción local para evitar errores (opcional) const localOption = els.extSelect.querySelector('option[value="local"]'); if (localOption) localOption.remove(); - // 2. Restaurar la fuente original (Anilist, Gogo, etc) - // Usamos _entrySource, pero si era 'local', forzamos 'anilist' para evitar bucle let fallbackSource = (_entrySource !== 'local') ? _entrySource : 'anilist'; - - // Verificar si esa fuente existe en el select, si no, usar la primera disponible if (!els.extSelect.querySelector(`option[value="${fallbackSource}"]`)) { if (els.extSelect.options.length > 0) { fallbackSource = els.extSelect.options[0].value; } } els.extSelect.value = fallbackSource; - - // 3. Como cambiamos de Local -> Extensión, necesitamos cargar los servidores de nuevo - // handleExtensionChange(true) se encarga de cargar settings, servers y luego hacer play. handleExtensionChange(true); - } else { - // Ya estábamos en modo remoto. - // Si por alguna razón no hay servidores cargados, recargamos la extensión. if (els.serverSelect.options.length === 0) { handleExtensionChange(true); } else { @@ -369,266 +784,26 @@ const AnimePlayer = (function() { } } - async function downloadEpisode() { - if (!_rawVideoData || !_rawVideoData.url) { - alert("Stream not loaded yet."); - return; - } - - const isInFullscreen = document.fullscreenElement || document.webkitFullscreenElement; - - if (isInFullscreen) { - try { - if (document.exitFullscreen) { - await document.exitFullscreen(); - } else if (document.webkitExitFullscreen) { - await document.webkitExitFullscreen(); - } - - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (err) { - console.warn("Error al salir de fullscreen:", err); - } - } - - const isM3U8 = hlsInstance && hlsInstance.levels && hlsInstance.levels.length > 0; - const hasMultipleAudio = hlsInstance && hlsInstance.audioTracks && hlsInstance.audioTracks.length > 1; - - const hasSubs = _currentSubtitles && _currentSubtitles.length > 0; - - if (isM3U8 || hasMultipleAudio || hasSubs) { - - await new Promise(resolve => requestAnimationFrame(resolve)); - openDownloadModal(); - } else { - - executeDownload(null, true); - } - } - - function openDownloadModal() { - if(!els.downloadModal) { - console.error("Modal element not found"); - return; - } - - els.dlQualityList.innerHTML = ''; - els.dlAudioList.innerHTML = ''; - els.dlSubsList.innerHTML = ''; - - let showQuality = false; - let showAudio = false; - let showSubs = false; - - if (hlsInstance && hlsInstance.levels && hlsInstance.levels.length > 0) { - showQuality = true; - - const levels = hlsInstance.levels.map((l, index) => ({...l, originalIndex: index})) - .sort((a, b) => b.height - a.height); - - levels.forEach((level, i) => { - const isSelected = i === 0; - - const div = document.createElement('div'); - div.className = 'dl-item'; - div.innerHTML = ` - - ${level.height}p - ${(level.bitrate / 1000000).toFixed(1)} Mbps - `; - div.onclick = (e) => { - if(e.target.tagName !== 'INPUT') div.querySelector('input').checked = true; - }; - els.dlQualityList.appendChild(div); - }); - } - document.getElementById('dl-quality-section').style.display = showQuality ? 'block' : 'none'; - - if (hlsInstance && hlsInstance.audioTracks && hlsInstance.audioTracks.length > 0) { - showAudio = true; - hlsInstance.audioTracks.forEach((track, index) => { - const div = document.createElement('div'); - div.className = 'dl-item'; - - const isCurrent = hlsInstance.audioTrack === index; - - div.innerHTML = ` - - ${track.name || track.lang || `Audio ${index+1}`} - ${track.lang || 'unk'} - `; - div.onclick = (e) => { - if(e.target.tagName !== 'INPUT') { - const cb = div.querySelector('input'); - cb.checked = !cb.checked; - } - }; - els.dlAudioList.appendChild(div); - }); - } - document.getElementById('dl-audio-section').style.display = showAudio ? 'block' : 'none'; - - if (_currentSubtitles && _currentSubtitles.length > 0) { - showSubs = true; - _currentSubtitles.forEach((sub, index) => { - const div = document.createElement('div'); - div.className = 'dl-item'; - div.innerHTML = ` - - ${sub.label || sub.language || 'Unknown'} - `; - div.onclick = (e) => { - if(e.target.tagName !== 'INPUT') { - const cb = div.querySelector('input'); - cb.checked = !cb.checked; - } - }; - els.dlSubsList.appendChild(div); - }); - } - document.getElementById('dl-subs-section').style.display = showSubs ? 'block' : 'none'; - - els.downloadModal.style.display = 'flex'; - - els.downloadModal.offsetHeight; - els.downloadModal.classList.add('show'); - } - - async function executeDownload(e, skipModal = false) { - if(els.downloadModal) { - els.downloadModal.classList.remove('show'); - setTimeout(() => els.downloadModal.style.display = 'none', 300); - } - const btn = els.downloadBtn; - const originalBtnContent = btn.innerHTML; - - btn.disabled = true; - btn.innerHTML = `
`; - - let body = { - anilist_id: parseInt(_animeId), - episode_number: parseInt(_currentEpisode), - stream_url: _rawVideoData.url, - headers: _rawVideoData.headers || {}, - chapters: _skipIntervals.map(i => ({ - title: i.type === 'op' ? 'Opening' : 'Ending', - start_time: i.startTime, - end_time: i.endTime - })), - subtitles: [] - }; - - if (skipModal) { - - if (_currentSubtitles) { - body.subtitles = _currentSubtitles.map(sub => ({ - language: sub.label || 'Unknown', - url: sub.src - })); - } - } else { - - const selectedSubs = Array.from(els.dlSubsList.querySelectorAll('input:checked')); - body.subtitles = selectedSubs.map(cb => { - const i = parseInt(cb.value); - return { - language: _currentSubtitles[i].label || 'Unknown', - url: _currentSubtitles[i].src - }; - }); - - const isQualityVisible = document.getElementById('dl-quality-section').style.display !== 'none'; - - if (isQualityVisible && hlsInstance && hlsInstance.levels) { - body.is_master = true; - - const qualityInput = document.querySelector('input[name="dl-quality"]:checked'); - const qualityIndex = qualityInput ? parseInt(qualityInput.value) : 0; - const level = hlsInstance.levels[qualityIndex]; - - if (level) { - body.variant = { - resolution: level.width ? `${level.width}x${level.height}` : '1920x1080', - bandwidth: level.bitrate, - codecs: level.attrs ? level.attrs.CODECS : '', - playlist_url: level.url - }; - } - - const audioInputs = document.querySelectorAll('input[name="dl-audio"]:checked'); - if (audioInputs.length > 0 && hlsInstance.audioTracks) { - body.audio = Array.from(audioInputs).map(input => { - const i = parseInt(input.value); - const track = hlsInstance.audioTracks[i]; - return { - group: track.groupId || 'audio', - language: track.lang || 'unk', - name: track.name || `Audio ${i}`, - playlist_url: track.url - }; - }); - } - } - } - - try { - const token = localStorage.getItem('token'); - const res = await fetch('/api/library/download/anime', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': token ? `Bearer ${token}` : '' - }, - body: JSON.stringify(body) - }); - - const data = await res.json(); - - if (res.status === 200) { - - btn.innerHTML = ``; - } else if (res.status === 409) { - - btn.innerHTML = ``; - } else { - - console.error("Download Error:", data); - btn.innerHTML = ``; - } - } catch (err) { - console.error("Request failed:", err); - btn.innerHTML = ``; - } finally { - - setTimeout(() => { - if (btn) { - btn.disabled = false; - resetDownloadButtonIcon(); - } - }, 3000); - } - } - - function resetDownloadButtonIcon() { - if (!els.downloadBtn) return; - els.downloadBtn.innerHTML = ` - - - - - `; - } - function closePlayer() { - if (plyrInstance) plyrInstance.destroy(); - if (hlsInstance) hlsInstance.destroy(); - plyrInstance = null; - hlsInstance = null; + if (hlsInstance) { + hlsInstance.destroy(); + hlsInstance = null; + } + + if (subtitleRenderer) { + try { subtitleRenderer.dispose(); } catch(e) {} + subtitleRenderer = null; + } + + // Stop video + if (els.video) { + els.video.pause(); + els.video.removeAttribute('src'); + els.video.load(); + } if(els.playerWrapper) els.playerWrapper.style.display = 'none'; - document.body.classList.remove('stop-scrolling'); - document.body.classList.remove('watch-mode'); _skipIntervals = []; _rpcActive = false; @@ -678,7 +853,6 @@ const AnimePlayer = (function() { async function handleExtensionChange(shouldPlay = true) { const selectedExt = els.extSelect.value; if (els.manualMatchBtn) { - // Si es local, lo ocultamos. Si es extensión, lo mostramos. els.manualMatchBtn.style.display = (selectedExt === 'local') ? 'none' : 'flex'; } @@ -690,8 +864,8 @@ const AnimePlayer = (function() { } _manualExtensionId = null; - setLoading("Loading Extension Settings..."); + try { const res = await fetch(`/api/extensions/${selectedExt}/settings`); const settings = await res.json(); @@ -742,6 +916,20 @@ const AnimePlayer = (function() { if(els.loader) els.loader.style.display = 'flex'; } + async function getLocalEntryId() { + if (_localEntryId) return _localEntryId; + try { + const res = await fetch(`/api/library/anime/${_animeId}`); + if (!res.ok) return null; + const data = await res.json(); + _localEntryId = data.id; + return _localEntryId; + } catch (e) { + console.error("Error fetching local ID:", e); + return null; + } + } + async function loadStream() { if (!_currentEpisode) return; _progressUpdated = false; @@ -750,7 +938,16 @@ const AnimePlayer = (function() { _rawVideoData = null; _currentSubtitles = []; - if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; } + // Cleanup before fetch to prevent ghost events + if (hlsInstance) { + hlsInstance.destroy(); + hlsInstance = null; + } + + if (subtitleRenderer) { + try { subtitleRenderer.dispose(); } catch(e) {} + subtitleRenderer = null; + } const currentExt = els.extSelect.value; @@ -762,43 +959,31 @@ const AnimePlayer = (function() { if (currentExt === 'local') { try { const localId = await getLocalEntryId(); - - // Paso 1: Obtener lista de archivos const check = await fetch(`/api/library/${_animeId}/units`); const data = await check.json(); - - // Paso 2: Buscar si el episodio actual existe const targetUnit = data.units ? data.units.find(u => u.number === parseInt(_currentEpisode)) : null; - // Paso 3: Si NO existe localmente if (!targetUnit) { - console.log(`Episode ${_currentEpisode} not found locally. Removing option.`); - + 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; } const ext = targetUnit.format || targetUnit.name.split('.').pop().toLowerCase(); - // Validación de formato para reproductor web if (!['mp4'].includes(ext)) { - setLoading( - `Format '${ext}' not supported in web player. Use MPV.` - ); - // Aseguramos que el botón de MPV tenga la data necesaria aunque falle el web player + setLoading(`Format '${ext}' not supported in web player. Use MPV.`); _rawVideoData = { - url: targetUnit.path, // O la URL de stream correspondiente + url: targetUnit.path, headers: {} }; if (els.mpvBtn) els.mpvBtn.style.display = 'flex'; @@ -806,15 +991,13 @@ const AnimePlayer = (function() { } const localUrl = `/api/library/stream/${targetUnit.id}`; - _rawVideoData = { - url: localUrl, // O window.location.origin + localUrl si es relativa + url: localUrl, headers: {} }; _currentSubtitles = []; initVideoPlayer(localUrl, 'mp4'); - } catch(e) { console.error(e); setLoading("Local Error: " + e.message); @@ -826,7 +1009,6 @@ const AnimePlayer = (function() { const extParam = `&ext=${currentExt}`; const realSource = _entrySource === 'local' ? 'anilist' : _entrySource; - // AQUÍ AGREGAMOS EL PARÁMETRO OPCIONAL extensionAnimeId let url = `/api/watch/stream?animeId=${_animeId}` + `&episode=${_currentEpisode}` + `&server=${encodeURIComponent(server)}` + @@ -834,7 +1016,6 @@ const AnimePlayer = (function() { `${extParam}` + `&source=${realSource}`; - // INYECCIÓN DEL ID MANUAL if (_manualExtensionId) { url += `&extensionAnimeId=${encodeURIComponent(_manualExtensionId)}`; } @@ -857,13 +1038,16 @@ const AnimePlayer = (function() { }; let proxyUrl = `/api/proxy?url=${encodeURIComponent(source.url)}`; - if (headers['Referer'] && headers['Referer'] !== "null") proxyUrl += `&referer=${encodeURIComponent(headers['Referer'])}`; - if (headers['User-Agent']) proxyUrl += `&userAgent=${encodeURIComponent(headers['User-Agent'])}`; - + if (headers['Referer'] && headers['Referer'] !== "null") { + proxyUrl += `&referer=${encodeURIComponent(headers['Referer'])}`; + } + if (headers['User-Agent']) { + proxyUrl += `&userAgent=${encodeURIComponent(headers['User-Agent'])}`; + } const subtitles = (source.subtitles || []).map(sub => ({ label: sub.language, - srclang: sub.language.toLowerCase().slice(0, 2), // en + srclang: sub.language.toLowerCase().slice(0, 2), src: `/api/proxy?url=${encodeURIComponent(sub.url)}` })); @@ -876,56 +1060,74 @@ const AnimePlayer = (function() { initVideoPlayer(proxyUrl, source.type, subtitles); } catch (err) { setLoading("Stream Error: " + err.message); + console.error(err); } } function initVideoPlayer(url, type, subtitles = []) { - if (plyrInstance) { - plyrInstance.destroy(); - plyrInstance = null; - } + // Double check cleanup if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; } + if (subtitleRenderer) { + try { subtitleRenderer.dispose(); } catch(e) {} + subtitleRenderer = null; + } const container = document.querySelector('.video-frame'); + if (!container) return; - container.innerHTML = ''; + // --- SAFE VIDEO ELEMENT REPLACEMENT --- + const oldVideo = container.querySelector('video'); + if (oldVideo) { + try { + // Remove listeners to stop events from firing during removal + oldVideo.ontimeupdate = null; + oldVideo.onplay = null; + oldVideo.onpause = null; + + // Stop playback + oldVideo.pause(); + oldVideo.removeAttribute('src'); + oldVideo.load(); // Forces media unload + + // Remove from DOM + if (oldVideo.parentNode) { + oldVideo.parentNode.removeChild(oldVideo); + } else { + oldVideo.remove(); + } + } catch (e) { + console.warn("Error cleaning up old video element:", e); + // Continue anyway, we need to create the new player + } + } const newVideo = document.createElement('video'); newVideo.id = 'player'; - newVideo.controls = true; newVideo.crossOrigin = 'anonymous'; newVideo.playsInline = true; - container.appendChild(newVideo); + // Insert new video carefully + if (container.firstChild) { + container.insertBefore(newVideo, container.firstChild); + } else { + container.appendChild(newVideo); + } + els.video = newVideo; - els.video.addEventListener("play", () => { - if (!els.video.duration) return; - const elapsed = Math.floor(els.video.currentTime); - const start = Math.floor(Date.now() / 1000) - elapsed; - const end = start + Math.floor(els.video.duration); - sendRPC({ startTimestamp: start, endTimestamp: end }); - _rpcActive = true; - }); + // Re-setup control listeners + setupCustomControls(); - els.video.addEventListener("pause", () => { - if (_rpcActive) sendRPC({ paused: true }); - }); + if (Hls.isSupported() && type === 'm3u8') { + hlsInstance = new Hls({ + enableWorker: true, + lowLatencyMode: false, + backBufferLength: 90 + }); - els.video.addEventListener("seeked", () => { - if (els.video.paused || !_rpcActive) return; - const elapsed = Math.floor(els.video.currentTime); - const start = Math.floor(Date.now() / 1000) - elapsed; - const end = start + Math.floor(els.video.duration); - sendRPC({ startTimestamp: start, endTimestamp: end }); - }); - - - if (Hls.isSupported() && (type === 'm3u8')) { - hlsInstance = new Hls(); hlsInstance.attachMedia(els.video); hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => { @@ -934,33 +1136,55 @@ const AnimePlayer = (function() { hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => { attachSubtitles(subtitles); - initPlyr(); - - els.video.addEventListener('loadedmetadata', () => { - applyAniSkip(_malId, _currentEpisode); - }, { once: true }); - - plyrInstance.on('ready', () => { - createAudioSelector(hlsInstance); - createQualitySelector(hlsInstance); - }); - + buildSettingsPanel(); + if (els.downloadBtn) els.downloadBtn.style.display = 'flex'; els.video.play().catch(() => {}); + if (els.loader) els.loader.style.display = 'none'; }); + hlsInstance.on(Hls.Events.LEVEL_SWITCHED, () => { + buildSettingsPanel(); + }); + + hlsInstance.on(Hls.Events.AUDIO_TRACK_SWITCHED, () => { + buildSettingsPanel(); + }); + + if (els.downloadBtn) { + els.downloadBtn.style.display = 'flex'; + } + + } else if (els.video.canPlayType('application/vnd.apple.mpegurl') && type === 'm3u8') { + // Native HLS support (Safari) + els.video.src = url; + attachSubtitles(subtitles); + buildSettingsPanel(); + els.video.play().catch(() => {}); + if(els.loader) els.loader.style.display = 'none'; + if (els.downloadBtn) { + els.downloadBtn.style.display = 'flex'; + } } else { els.video.src = url; attachSubtitles(subtitles); - initPlyr(); - els.video.play().catch(e => console.log("Autoplay blocked", e)); - els.video.addEventListener('loadedmetadata', () => { - applyAniSkip(_malId, _currentEpisode); - }, { once: true }); + buildSettingsPanel(); + els.video.play().catch(() => {}); if(els.loader) els.loader.style.display = 'none'; + if (els.downloadBtn) { + els.downloadBtn.style.display = 'none'; + } } + + // Try to init ASS subtitle renderer + initSubtitleRenderer(); } function attachSubtitles(subtitles) { + if (!els.video) return; + + // Remove existing tracks + Array.from(els.video.querySelectorAll('track')).forEach(t => t.remove()); + subtitles.forEach((sub, i) => { const track = document.createElement('track'); track.kind = 'subtitles'; @@ -970,212 +1194,21 @@ const AnimePlayer = (function() { track.default = i === 0; els.video.appendChild(track); }); - } - const ICONS = { - settings: ``, - audio: `` - }; - function createQualitySelector(hls) { - const levels = hls.levels; - if (!levels || !levels.length) return; - - const plyrEl = els.video.closest('.plyr'); - const controls = plyrEl.querySelector('.plyr__controls'); - if (!controls || controls.querySelector('#quality-control-wrapper')) return; - - const wrapper = document.createElement('div'); - wrapper.className = 'plyr__controls__item plyr__custom-select-wrapper'; - wrapper.id = 'quality-control-wrapper'; - - const btn = document.createElement('div'); - btn.className = 'plyr__custom-control-btn'; - - btn.innerHTML = `${ICONS.settings} Auto`; - - const select = document.createElement('select'); - select.className = 'plyr__sr-only-select'; - - const autoOpt = document.createElement('option'); - autoOpt.value = -1; - autoOpt.textContent = 'Auto'; - select.appendChild(autoOpt); - - levels.forEach((l, i) => { - const opt = document.createElement('option'); - opt.value = i; - opt.textContent = `${l.height}p`; - - select.appendChild(opt); - }); - - select.value = hls.currentLevel; - updateLabel(select.value); - - select.onchange = () => { - hls.currentLevel = Number(select.value); - updateLabel(select.value); - }; - - function updateLabel(val) { - const index = Number(val); - let text = 'Auto'; - if (index !== -1 && levels[index]) { - - text = `${levels[index].height}p`; + // Enable first track + setTimeout(() => { + if (els.video.textTracks && els.video.textTracks.length > 0) { + els.video.textTracks[0].mode = 'showing'; } - btn.innerHTML = `${text}`; - } - - wrapper.appendChild(select); - wrapper.appendChild(btn); - - const insertIndex = controls.children.length > 4 ? 4 : controls.children.length - 1; - controls.insertBefore(wrapper, controls.children[insertIndex]); - } - - function createAudioSelector(hls) { - if (!hls.audioTracks || hls.audioTracks.length < 2) return; - - const plyrEl = els.video.closest('.plyr'); - const controls = plyrEl.querySelector('.plyr__controls'); - if (!controls || controls.querySelector('#audio-control-wrapper')) return; - - const wrapper = document.createElement('div'); - wrapper.className = 'plyr__controls__item plyr__custom-select-wrapper'; - wrapper.id = 'audio-control-wrapper'; - - const btn = document.createElement('div'); - btn.className = 'plyr__custom-control-btn'; - btn.innerHTML = `Audio 1`; - - const select = document.createElement('select'); - - hls.audioTracks.forEach((t, i) => { - const opt = document.createElement('option'); - opt.value = i; - opt.textContent = t.name || t.lang || `Audio ${i + 1}`; - select.appendChild(opt); - }); - - select.value = hls.audioTrack; - updateLabel(select.value); - - select.onchange = () => { - hls.audioTrack = Number(select.value); - updateLabel(select.value); - }; - - function updateLabel(val) { - const index = Number(val); - const track = hls.audioTracks[index]; - - let rawText = track.lang || track.name || `A${index + 1}`; - - let shortText = rawText.substring(0, 2).toUpperCase(); - - btn.querySelector('.label-text').innerText = shortText; - } - - wrapper.appendChild(select); - wrapper.appendChild(btn); - - const qualityWrapper = controls.querySelector('#quality-control-wrapper'); - if(qualityWrapper) { - controls.insertBefore(wrapper, qualityWrapper); - } else { - const insertIndex = controls.children.length > 4 ? 4 : controls.children.length - 1; - controls.insertBefore(wrapper, controls.children[insertIndex]); - } - } - - function initPlyr(enableAudio = false) { - if (plyrInstance) return; - - const settings = ['captions', 'quality', 'speed']; - if (enableAudio) settings.unshift('audio'); - - plyrInstance = new Plyr(els.video, { - captions: { - active: true, - update: true, - language: els.video.querySelector('track')?.srclang || 'en' - }, - fullscreen: { - enabled: true, - fallback: true, - iosNative: true, - container: '.player-container' - }, - controls: [ - 'play-large', 'play', 'progress', 'current-time', - 'mute', 'volume', 'captions', 'settings', - 'fullscreen', 'airplay' - ], - settings - }); - - const container = document.querySelector('.player-container'); - plyrInstance.on('controlshidden', () => container.classList.add('ui-hidden')); - plyrInstance.on('controlsshown', () => container.classList.remove('ui-hidden')); - - const tracks = els.video.textTracks; - if (tracks && tracks.length) tracks[0].mode = 'showing'; - - plyrInstance.on('ready', () => { - if (hlsInstance) createAudioSelector(hlsInstance); - }); - - plyrInstance.on('timeupdate', (event) => { - const instance = event.detail.plyr; - if (!instance.duration || _progressUpdated) return; - const percentage = instance.currentTime / instance.duration; - if (percentage >= 0.8) { - updateProgress(); - _progressUpdated = true; - } - }); - } - - function toVtt(sec) { - const h = String(Math.floor(sec / 3600)).padStart(2, '0'); - const m = String(Math.floor(sec % 3600 / 60)).padStart(2, '0'); - const s = (sec % 60).toFixed(3).padStart(6, '0'); - return `${h}:${m}:${s}`; - } - - function injectAniSkipChapters(intervals) { - const vtt = ['WEBVTT', '']; - intervals.forEach(skip => { - const label = skip.type === 'op' ? 'Opening' : 'Ending'; - vtt.push(`${toVtt(skip.startTime)} --> ${toVtt(skip.endTime)}`, label, ''); - }); - const blob = new Blob([vtt.join('\n')], { type: 'text/vtt' }); - const url = URL.createObjectURL(blob); - const track = document.createElement('track'); - track.kind = 'chapters'; - track.label = 'Chapters'; - track.srclang = 'en'; - track.src = url; - els.video.appendChild(track); - } - - function waitForDuration(video) { - return new Promise(resolve => { - if (video.duration && video.duration > 0) return resolve(video.duration); - const check = () => { - if (video.duration && video.duration > 0) { - video.removeEventListener('timeupdate', check); - resolve(video.duration); - } - }; - video.addEventListener('timeupdate', check); - }); + }, 100); } + // AniSkip integration async function applyAniSkip(malId, episodeNumber) { - if (!malId) return; + if (!malId || !els.video) return; + const duration = await waitForDuration(els.video); + try { const url = `https://api.aniskip.com/v2/skip-times/${malId}/${episodeNumber}` + `?types[]=op&types[]=ed&episodeLength=${Math.floor(duration)}`; @@ -1189,16 +1222,32 @@ const AnimePlayer = (function() { endTime: item.interval.endTime, type: item.skipType })); - injectAniSkipChapters(_skipIntervals); - requestAnimationFrame(() => renderSkipMarkers(_skipIntervals)); - } catch (e) { console.error('AniSkip Error:', e); } + + renderSkipMarkers(_skipIntervals); + monitorSkipButton(_skipIntervals); + } catch (e) { + console.error('AniSkip Error:', e); + } + } + + function waitForDuration(video) { + return new Promise(resolve => { + if (video.duration && video.duration > 0) return resolve(video.duration); + const check = () => { + if (video.duration && video.duration > 0) { + video.removeEventListener('loadedmetadata', check); + resolve(video.duration); + } + }; + video.addEventListener('loadedmetadata', check); + }); } function renderSkipMarkers(intervals) { - const progressContainer = els.video.closest('.plyr')?.querySelector('.plyr__progress'); - if (!progressContainer || !els.video.duration) return; + if (!els.progressContainer || !els.video.duration) return; - progressContainer.querySelectorAll('.skip-marker').forEach(e => e.remove()); + // Remove existing markers + els.progressContainer.querySelectorAll('.skip-marker').forEach(e => e.remove()); intervals.forEach(skip => { const el = document.createElement('div'); @@ -1207,21 +1256,14 @@ const AnimePlayer = (function() { const endPct = (skip.endTime / els.video.duration) * 100; el.style.left = `${startPct}%`; el.style.width = `${endPct - startPct}%`; - progressContainer.appendChild(el); + els.progressContainer.appendChild(el); }); - monitorSkipButton(intervals); } function monitorSkipButton(intervals) { - if (!_skipBtn) return; - els.video.removeEventListener('timeupdate', checkTime); - els.video.addEventListener('timeupdate', checkTime); + if (!_skipBtn || !els.video) return; - els.video.addEventListener('ended', () => { - if (_currentEpisode < _totalEpisodes) playEpisode(_currentEpisode + 1); - }, { once: true }); - - function checkTime() { + const checkTime = () => { const ct = els.video.currentTime; const duration = els.video.duration; const activeInterval = intervals.find(i => ct >= i.startTime && ct <= i.endTime); @@ -1246,8 +1288,11 @@ const AnimePlayer = (function() { return; } } + _skipBtn.classList.remove('visible'); - } + }; + + els.video.addEventListener('timeupdate', checkTime); } function showSkipButton(text, seekTime, isNextAction) { @@ -1263,13 +1308,339 @@ const AnimePlayer = (function() { _skipBtn.classList.add('visible'); } + function handleOverlayClick() { + if (!_skipBtn) return; + if (_skipBtn.classList.contains('is-next')) { + playEpisode(_currentEpisode + 1); + } else if (_skipBtn.dataset.seekTo) { + els.video.currentTime = parseFloat(_skipBtn.dataset.seekTo); + } + _skipBtn.classList.remove('visible'); + } + + // Download functionality + async function downloadEpisode() { + if (!_rawVideoData || !_rawVideoData.url) { + alert("Stream not loaded yet."); + return; + } + + const isInFullscreen = document.fullscreenElement || document.webkitFullscreenElement; + if (isInFullscreen) { + try { + if (document.exitFullscreen) { + await document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + await document.webkitExitFullscreen(); + } + await new Promise(resolve => setTimeout(resolve, 100)); + } catch (err) { + console.warn("Error exiting fullscreen:", err); + } + } + + const isM3U8 = hlsInstance && hlsInstance.levels && hlsInstance.levels.length > 0; + const hasMultipleAudio = hlsInstance && hlsInstance.audioTracks && hlsInstance.audioTracks.length > 1; + const hasSubs = _currentSubtitles && _currentSubtitles.length > 0; + + if (isM3U8 || hasMultipleAudio || hasSubs) { + await new Promise(resolve => requestAnimationFrame(resolve)); + openDownloadModal(); + } else { + executeDownload(null, true); + } + } + + function openDownloadModal() { + if(!els.downloadModal) return; + + els.dlQualityList.innerHTML = ''; + els.dlAudioList.innerHTML = ''; + els.dlSubsList.innerHTML = ''; + + let showQuality = false; + let showAudio = false; + let showSubs = false; + + // Quality options + if (hlsInstance && hlsInstance.levels && hlsInstance.levels.length > 0) { + showQuality = true; + const levels = hlsInstance.levels.map((l, index) => ({...l, originalIndex: index})) + .sort((a, b) => b.height - a.height); + + levels.forEach((level, i) => { + const isSelected = i === 0; + const div = document.createElement('div'); + div.className = 'dl-item'; + div.innerHTML = ` + + ${level.height}p + ${(level.bitrate / 1000000).toFixed(1)} Mbps + `; + div.onclick = (e) => { + if(e.target.tagName !== 'INPUT') div.querySelector('input').checked = true; + }; + els.dlQualityList.appendChild(div); + }); + } + document.getElementById('dl-quality-section').style.display = showQuality ? 'block' : 'none'; + + // Audio tracks + if (hlsInstance && hlsInstance.audioTracks && hlsInstance.audioTracks.length > 0) { + showAudio = true; + hlsInstance.audioTracks.forEach((track, index) => { + const div = document.createElement('div'); + div.className = 'dl-item'; + div.innerHTML = ` + + ${track.name || track.lang || `Audio ${index+1}`} + ${track.lang || 'unk'} + `; + div.onclick = (e) => { + if(e.target.tagName !== 'INPUT') { + const cb = div.querySelector('input'); + cb.checked = !cb.checked; + } + }; + els.dlAudioList.appendChild(div); + }); + } + document.getElementById('dl-audio-section').style.display = showAudio ? 'block' : 'none'; + + // Subtitles + if (_currentSubtitles && _currentSubtitles.length > 0) { + showSubs = true; + _currentSubtitles.forEach((sub, index) => { + const div = document.createElement('div'); + div.className = 'dl-item'; + div.innerHTML = ` + + ${sub.label || sub.language || 'Unknown'} + `; + div.onclick = (e) => { + if(e.target.tagName !== 'INPUT') { + const cb = div.querySelector('input'); + cb.checked = !cb.checked; + } + }; + els.dlSubsList.appendChild(div); + }); + } + document.getElementById('dl-subs-section').style.display = showSubs ? 'block' : 'none'; + + els.downloadModal.style.display = 'flex'; + els.downloadModal.offsetHeight; + els.downloadModal.classList.add('show'); + } + + function closeDownloadModal() { + if (els.downloadModal) { + els.downloadModal.classList.remove('show'); + setTimeout(() => { + if(!els.downloadModal.classList.contains('show')) { + els.downloadModal.style.display = 'none'; + } + }, 300); + } + } + + async function executeDownload(e, skipModal = false) { + closeDownloadModal(); + + const btn = els.downloadBtn; + if (!btn) return; + + const originalBtnContent = btn.innerHTML; + btn.disabled = true; + btn.innerHTML = `
`; + + let body = { + anilist_id: parseInt(_animeId), + episode_number: parseInt(_currentEpisode), + stream_url: _rawVideoData.url, + headers: _rawVideoData.headers || {}, + chapters: _skipIntervals.map(i => ({ + title: i.type === 'op' ? 'Opening' : 'Ending', + start_time: i.startTime, + end_time: i.endTime + })), + subtitles: [] + }; + + if (skipModal) { + if (_currentSubtitles) { + body.subtitles = _currentSubtitles.map(sub => ({ + language: sub.label || 'Unknown', + url: sub.src + })); + } + } else { + const selectedSubs = Array.from(els.dlSubsList.querySelectorAll('input:checked')); + body.subtitles = selectedSubs.map(cb => { + const i = parseInt(cb.value); + return { + language: _currentSubtitles[i].label || 'Unknown', + url: _currentSubtitles[i].src + }; + }); + + const isQualityVisible = document.getElementById('dl-quality-section').style.display !== 'none'; + if (isQualityVisible && hlsInstance && hlsInstance.levels) { + body.is_master = true; + const qualityInput = document.querySelector('input[name="dl-quality"]:checked'); + const qualityIndex = qualityInput ? parseInt(qualityInput.value) : 0; + const level = hlsInstance.levels[qualityIndex]; + + if (level) { + body.variant = { + resolution: level.width ? `${level.width}x${level.height}` : '1920x1080', + bandwidth: level.bitrate, + codecs: level.attrs ? level.attrs.CODECS : '', + playlist_url: level.url + }; + } + + const audioInputs = document.querySelectorAll('input[name="dl-audio"]:checked'); + if (audioInputs.length > 0 && hlsInstance.audioTracks) { + body.audio = Array.from(audioInputs).map(input => { + const i = parseInt(input.value); + const track = hlsInstance.audioTracks[i]; + return { + group: track.groupId || 'audio', + language: track.lang || 'unk', + name: track.name || `Audio ${i}`, + playlist_url: track.url + }; + }); + } + } + } + + try { + const token = localStorage.getItem('token'); + const res = await fetch('/api/library/download/anime', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': token ? `Bearer ${token}` : '' + }, + body: JSON.stringify(body) + }); + + const data = await res.json(); + + if (res.status === 200) { + btn.innerHTML = ``; + } else if (res.status === 409) { + btn.innerHTML = ``; + } else { + console.error("Download Error:", data); + btn.innerHTML = ``; + } + } catch (err) { + console.error("Request failed:", err); + btn.innerHTML = ``; + } finally { + setTimeout(() => { + if (btn) { + btn.disabled = false; + btn.innerHTML = ``; + } + }, 3000); + } + } + + // MPV functionality + async function openInMPV() { + if (!_rawVideoData) { + alert("No video loaded yet."); + return; + } + + const token = localStorage.getItem('token'); + if (!token) { + alert("You need to be logged in."); + return; + } + + const body = { + title: `${_animeTitle} - Episode ${_currentEpisode}`, + video: _rawVideoData, + subtitles: _currentSubtitles, + chapters: _skipIntervals, + animeId: _animeId, + episode: _currentEpisode, + entrySource: _entrySource, + token: token + }; + + try { + const res = await fetch('/api/watch/mpv', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + + if (res.ok) { + console.log("MPV Request Sent"); + closePlayer(); + } else { + console.error("MPV Request Failed"); + } + } catch (e) { + console.error("MPV Error:", e); + } + } + + // Match Modal + function openMatchModal() { + const currentExt = els.extSelect.value; + if (!currentExt || currentExt === 'local') return; + + if (typeof MatchModal !== 'undefined') { + MatchModal.open({ + provider: currentExt, + initialQuery: _animeTitle, + onSearch: async (query, prov) => { + const res = await fetch(`/api/search/${prov}?q=${encodeURIComponent(query)}`); + const data = await res.json(); + return data.results || []; + }, + onSelect: (item) => { + _manualExtensionId = item.id; + loadStream(); + } + }); + } + } + + // RPC + function sendRPC({ startTimestamp, endTimestamp, paused = false } = {}) { + fetch("/api/rpc", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + details: _animeTitle, + state: `Episode ${_currentEpisode}`, + mode: "watching", + startTimestamp, + endTimestamp, + paused + }) + }).catch(e => console.warn("RPC Error:", e)); + } + + // Progress update async function updateProgress() { const token = localStorage.getItem('token'); if (!token) return; try { await fetch('/api/list/entry', { method: 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, body: JSON.stringify({ entry_id: _animeId, source: _entrySource, @@ -1278,7 +1649,9 @@ const AnimePlayer = (function() { progress: _currentEpisode }) }); - } catch (e) { console.error("Progress update failed", e); } + } catch (e) { + console.error("Progress update failed", e); + } } return { diff --git a/desktop/src/scripts/libass/default.woff2 b/desktop/src/scripts/libass/default.woff2 new file mode 100644 index 0000000..68a0517 Binary files /dev/null and b/desktop/src/scripts/libass/default.woff2 differ diff --git a/desktop/src/scripts/libass/subtitles-octopus-worker.js b/desktop/src/scripts/libass/subtitles-octopus-worker.js new file mode 100644 index 0000000..f5def9b --- /dev/null +++ b/desktop/src/scripts/libass/subtitles-octopus-worker.js @@ -0,0 +1 @@ +null;var Module=typeof Module!="undefined"?Module:{};var hasNativeConsole=typeof console!=="undefined";function makeCustomConsole(){var console=function(){function postConsoleMessage(prefix,args){postMessage({target:"console-"+prefix,content:JSON.stringify(Array.prototype.slice.call(args))})}return{log:function(){postConsoleMessage("log",arguments)},debug:function(){postConsoleMessage("debug",arguments)},info:function(){postConsoleMessage("info",arguments)},warn:function(){postConsoleMessage("warn",arguments)},error:function(){postConsoleMessage("error",arguments)}}}();return console}function isBrotliFile(url){var len=url.indexOf("?");if(len===-1){len=url.length}if(url.endsWith(".br",len)){console.warn("Support for manual brotli decompression is tentatively deprecated and "+"may be removed with the next release. Instead use HTTP's Content-Encoding.");return true}return false}Module=Module||{};Module["preRun"]=Module["preRun"]||[];Module["preRun"].push(function(){Module["FS_createPath"]("/","fonts",true,true);Module["FS_createPath"]("/","fontconfig",true,true);if(!self.subContent){if(isBrotliFile(self.subUrl)){self.subContent=Module["BrotliDecode"](readBinary(self.subUrl))}else{self.subContent=read_(self.subUrl)}}if(self.availableFonts&&self.availableFonts.length!==0){var sections=parseAss(self.subContent);for(var i=0;i0||self.libassGlyphLimit>0){self.octObj.setMemoryLimits(self.libassGlyphLimit,self.libassMemoryLimit)}};Module["print"]=function(text){if(arguments.length>1)text=Array.prototype.slice.call(arguments).join(" ");console.log(text)};Module["printErr"]=function(text){if(arguments.length>1)text=Array.prototype.slice.call(arguments).join(" ");console.error(text)};if(!hasNativeConsole){var console={log:function(x){if(typeof dump==="function")dump("log: "+x+"\n")},debug:function(x){if(typeof dump==="function")dump("debug: "+x+"\n")},info:function(x){if(typeof dump==="function")dump("info: "+x+"\n")},warn:function(x){if(typeof dump==="function")dump("warn: "+x+"\n")},error:function(x){if(typeof dump==="function")dump("error: "+x+"\n")}}}function BrotliDecodeClosure(){null;var DICTIONARY_DATA=new Int8Array(0);function InputStream(bytes){this.data=bytes;this.offset=0}var MAX_HUFFMAN_TABLE_SIZE=Int32Array.from([256,402,436,468,500,534,566,598,630,662,694,726,758,790,822,854,886,920,952,984,1016,1048,1080]);var CODE_LENGTH_CODE_ORDER=Int32Array.from([1,2,3,4,0,5,17,6,16,7,8,9,10,11,12,13,14,15]);var DISTANCE_SHORT_CODE_INDEX_OFFSET=Int32Array.from([0,3,2,1,0,0,0,0,0,0,3,3,3,3,3,3]);var DISTANCE_SHORT_CODE_VALUE_OFFSET=Int32Array.from([0,0,0,0,-1,1,-2,2,-3,3,-1,1,-2,2,-3,3]);var FIXED_TABLE=Int32Array.from([131072,131076,131075,196610,131072,131076,131075,262145,131072,131076,131075,196610,131072,131076,131075,262149]);var DICTIONARY_OFFSETS_BY_LENGTH=Int32Array.from([0,0,0,0,0,4096,9216,21504,35840,44032,53248,63488,74752,87040,93696,100864,104704,106752,108928,113536,115968,118528,119872,121280,122016]);var DICTIONARY_SIZE_BITS_BY_LENGTH=Int32Array.from([0,0,0,0,10,10,11,11,10,10,10,10,10,9,9,8,7,7,8,7,7,6,6,5,5]);var BLOCK_LENGTH_OFFSET=Int32Array.from([1,5,9,13,17,25,33,41,49,65,81,97,113,145,177,209,241,305,369,497,753,1265,2289,4337,8433,16625]);var BLOCK_LENGTH_N_BITS=Int32Array.from([2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,7,8,9,10,11,12,13,24]);var INSERT_LENGTH_N_BITS=Int16Array.from([0,0,0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,7,8,9,10,12,14,24]);var COPY_LENGTH_N_BITS=Int16Array.from([0,0,0,0,0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,7,8,9,10,24]);var CMD_LOOKUP=new Int16Array(2816);{unpackCommandLookupTable(CMD_LOOKUP)}function log2floor(i){var result=-1;var step=16;while(step>0){if(i>>>step!=0){result+=step;i=i>>>step}step=step>>1}return result+i}function calculateDistanceAlphabetSize(npostfix,ndirect,maxndistbits){return 16+ndirect+2*(maxndistbits<>npostfix)+4;var ndistbits=log2floor(offset)-1;var group=ndistbits-1<<1|offset>>ndistbits&1;return(group-1<>>6;var distanceContextOffset=-4;if(rangeIdx>=2){rangeIdx-=2;distanceContextOffset=0}var insertCode=(170064>>>rangeIdx*2&3)<<3|cmdCode>>>3&7;var copyCode=(156228>>>rangeIdx*2&3)<<3|cmdCode&7;var copyLengthOffset=copyLengthOffsets[copyCode];var distanceContext=distanceContextOffset+(copyLengthOffset>4?3:copyLengthOffset-2);var index=cmdCode*4;cmdLookup[index+0]=INSERT_LENGTH_N_BITS[insertCode]|COPY_LENGTH_N_BITS[copyCode]<<8;cmdLookup[index+1]=insertLengthOffsets[insertCode];cmdLookup[index+2]=copyLengthOffsets[copyCode];cmdLookup[index+3]=distanceContext}}function decodeWindowBits(s){var largeWindowEnabled=s.isLargeWindow;s.isLargeWindow=0;if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}if(readFewBits(s,1)==0){return 16}var n=readFewBits(s,3);if(n!=0){return 17+n}n=readFewBits(s,3);if(n!=0){if(n==1){if(largeWindowEnabled==0){return-1}s.isLargeWindow=1;if(readFewBits(s,1)==1){return-1}n=readFewBits(s,6);if(n<10||n>30){return-1}return n}else{return 8+n}}return 17}function initState(s,input){if(s.runningState!=0){throw"State MUST be uninitialized"}s.blockTrees=new Int32Array(3091);s.blockTrees[0]=7;s.distRbIdx=3;var maxDistanceAlphabetLimit=calculateDistanceAlphabetLimit(2147483644,3,15<<3);s.distExtraBits=new Int8Array(maxDistanceAlphabetLimit);s.distOffset=new Int32Array(maxDistanceAlphabetLimit);s.input=input;initBitReader(s);s.runningState=1}function close(s){if(s.runningState==0){throw"State MUST be initialized"}if(s.runningState==11){return}s.runningState=11;if(s.input!=null){closeInput(s.input);s.input=null}}function decodeVarLenUnsignedByte(s){if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}if(readFewBits(s,1)!=0){var n=readFewBits(s,3);if(n==0){return 1}else{return readFewBits(s,n)+(1<=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}s.inputEnd=readFewBits(s,1);s.metaBlockLength=0;s.isUncompressed=0;s.isMetadata=0;if(s.inputEnd!=0&&readFewBits(s,1)!=0){return}var sizeNibbles=readFewBits(s,2)+4;if(sizeNibbles==7){s.isMetadata=1;if(readFewBits(s,1)!=0){throw"Corrupted reserved bit"}var sizeBytes=readFewBits(s,2);if(sizeBytes==0){return}for(var i=0;i=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var bits=readFewBits(s,8);if(bits==0&&i+1==sizeBytes&&sizeBytes>1){throw"Exuberant nibble"}s.metaBlockLength|=bits<=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var bits=readFewBits(s,4);if(bits==0&&i+1==sizeNibbles&&sizeNibbles>4){throw"Exuberant nibble"}s.metaBlockLength|=bits<>>s.bitOffset;offset+=val&255;var bits=tableGroup[offset]>>16;var sym=tableGroup[offset]&65535;if(bits<=8){s.bitOffset+=bits;return sym}offset+=sym;var mask=(1<>>8;s.bitOffset+=(tableGroup[offset]>>16)+8;return tableGroup[offset]&65535}function readBlockLength(tableGroup,tableIdx,s){if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var code=readSymbol(tableGroup,tableIdx,s);var n=BLOCK_LENGTH_N_BITS[code];if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}return BLOCK_LENGTH_OFFSET[code]+(n<=16?readFewBits(s,n):readManyBits(s,n))}function moveToFront(v,index){var value=v[index];for(;index>0;index--){v[index]=v[index-1]}v[0]=value}function inverseMoveToFrontTransform(v,vLen){var mtf=new Int32Array(256);for(var i=0;i<256;i++){mtf[i]=i}for(var i=0;i0){if(s.halfOffset>2030){doReadMoreInput(s)}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var p=s.accumulator32>>>s.bitOffset&31;s.bitOffset+=table[p]>>16;var codeLen=table[p]&65535;if(codeLen<16){repeat=0;codeLengths[symbol++]=codeLen;if(codeLen!=0){prevCodeLen=codeLen;space-=32768>>codeLen}}else{var extraBits=codeLen-14;var newLen=0;if(codeLen==16){newLen=prevCodeLen}if(repeatCodeLen!=newLen){repeat=0;repeatCodeLen=newLen}var oldRepeat=repeat;if(repeat>0){repeat-=2;repeat<<=extraBits}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}repeat+=readFewBits(s,extraBits)+3;var repeatDelta=repeat-oldRepeat;if(symbol+repeatDelta>numSymbols){throw"symbol + repeatDelta > numSymbols"}for(var i=0;i=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var symbol=readFewBits(s,maxBits);if(symbol>=alphabetSizeLimit){throw"Can't readHuffmanCode"}symbols[i]=symbol}checkDupes(symbols,numSymbols);var histogramId=numSymbols;if(numSymbols==4){histogramId+=readFewBits(s,1)}switch(histogramId){case 1:codeLengths[symbols[0]]=1;break;case 2:codeLengths[symbols[0]]=1;codeLengths[symbols[1]]=1;break;case 3:codeLengths[symbols[0]]=1;codeLengths[symbols[1]]=2;codeLengths[symbols[2]]=2;break;case 4:codeLengths[symbols[0]]=2;codeLengths[symbols[1]]=2;codeLengths[symbols[2]]=2;codeLengths[symbols[3]]=2;break;case 5:codeLengths[symbols[0]]=1;codeLengths[symbols[1]]=2;codeLengths[symbols[2]]=3;codeLengths[symbols[3]]=3;break;default:break}return buildHuffmanTable(tableGroup,tableIdx,8,codeLengths,alphabetSizeLimit)}function readComplexHuffmanCode(alphabetSizeLimit,skip,tableGroup,tableIdx,s){var codeLengths=new Int32Array(alphabetSizeLimit);var codeLengthCodeLengths=new Int32Array(18);var space=32;var numCodes=0;for(var i=skip;i<18&&space>0;i++){var codeLenIdx=CODE_LENGTH_CODE_ORDER[i];if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var p=s.accumulator32>>>s.bitOffset&15;s.bitOffset+=FIXED_TABLE[p]>>16;var v=FIXED_TABLE[p]&65535;codeLengthCodeLengths[codeLenIdx]=v;if(v!=0){space-=32>>v;numCodes++}}if(space!=0&&numCodes!=1){throw"Corrupted Huffman code histogram"}readHuffmanCodeLengths(codeLengthCodeLengths,alphabetSizeLimit,codeLengths,s);return buildHuffmanTable(tableGroup,tableIdx,8,codeLengths,alphabetSizeLimit)}function readHuffmanCode(alphabetSizeMax,alphabetSizeLimit,tableGroup,tableIdx,s){if(s.halfOffset>2030){doReadMoreInput(s)}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var simpleCodeOrSkip=readFewBits(s,2);if(simpleCodeOrSkip==1){return readSimpleHuffmanCode(alphabetSizeMax,alphabetSizeLimit,tableGroup,tableIdx,s)}else{return readComplexHuffmanCode(alphabetSizeLimit,simpleCodeOrSkip,tableGroup,tableIdx,s)}}function decodeContextMap(contextMapSize,contextMap,s){if(s.halfOffset>2030){doReadMoreInput(s)}var numTrees=decodeVarLenUnsignedByte(s)+1;if(numTrees==1){contextMap.fill(0,0,contextMapSize);return numTrees}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var useRleForZeros=readFewBits(s,1);var maxRunLengthPrefix=0;if(useRleForZeros!=0){maxRunLengthPrefix=readFewBits(s,4)+1}var alphabetSize=numTrees+maxRunLengthPrefix;var tableSize=MAX_HUFFMAN_TABLE_SIZE[alphabetSize+31>>5];var table=new Int32Array(tableSize+1);var tableIdx=table.length-1;readHuffmanCode(alphabetSize,alphabetSize,table,tableIdx,s);for(var i=0;i2030){doReadMoreInput(s)}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var code=readSymbol(table,tableIdx,s);if(code==0){contextMap[i]=0;i++}else if(code<=maxRunLengthPrefix){if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var reps=(1<=contextMapSize){throw"Corrupted context map"}contextMap[i]=0;i++;reps--}}else{contextMap[i]=code-maxRunLengthPrefix;i++}}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}if(readFewBits(s,1)==1){inverseMoveToFrontTransform(contextMap,contextMapSize)}return numTrees}function decodeBlockTypeAndLength(s,treeType,numBlockTypes){var ringBuffers=s.rings;var offset=4+treeType*2;if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var blockType=readSymbol(s.blockTrees,2*treeType,s);var result=readBlockLength(s.blockTrees,2*treeType+1,s);if(blockType==1){blockType=ringBuffers[offset+1]+1}else if(blockType==0){blockType=ringBuffers[offset]}else{blockType-=2}if(blockType>=numBlockTypes){blockType-=numBlockTypes}ringBuffers[offset]=ringBuffers[offset+1];ringBuffers[offset+1]=blockType;return result}function decodeLiteralBlockSwitch(s){s.literalBlockLength=decodeBlockTypeAndLength(s,0,s.numLiteralBlockTypes);var literalBlockType=s.rings[5];s.contextMapSlice=literalBlockType<<6;s.literalTreeIdx=s.contextMap[s.contextMapSlice]&255;var contextMode=s.contextModes[literalBlockType];s.contextLookupOffset1=contextMode<<9;s.contextLookupOffset2=s.contextLookupOffset1+256}function decodeCommandBlockSwitch(s){s.commandBlockLength=decodeBlockTypeAndLength(s,1,s.numCommandBlockTypes);s.commandTreeIdx=s.rings[7]}function decodeDistanceBlockSwitch(s){s.distanceBlockLength=decodeBlockTypeAndLength(s,2,s.numDistanceBlockTypes);s.distContextMapSlice=s.rings[9]<<2}function maybeReallocateRingBuffer(s){var newSize=s.maxRingBufferSize;if(newSize>s.expectedTotalSize){var minimalNewSize=s.expectedTotalSize;while(newSize>>1>minimalNewSize){newSize>>=1}if(s.inputEnd==0&&newSize<16384&&s.maxRingBufferSize>=16384){newSize=16384}}if(newSize<=s.ringBufferSize){return}var ringBufferSizeWithSlack=newSize+37;var newBuffer=new Int8Array(ringBufferSizeWithSlack);if(s.ringBuffer.length!=0){newBuffer.set(s.ringBuffer.subarray(0,0+s.ringBufferSize),0)}s.ringBuffer=newBuffer;s.ringBufferSize=newSize}function readNextMetablockHeader(s){if(s.inputEnd!=0){s.nextRunningState=10;s.runningState=12;return}s.literalTreeGroup=new Int32Array(0);s.commandTreeGroup=new Int32Array(0);s.distanceTreeGroup=new Int32Array(0);if(s.halfOffset>2030){doReadMoreInput(s)}decodeMetaBlockLength(s);if(s.metaBlockLength==0&&s.isMetadata==0){return}if(s.isUncompressed!=0||s.isMetadata!=0){jumpToByteBoundary(s);s.runningState=s.isMetadata!=0?5:6}else{s.runningState=3}if(s.isMetadata!=0){return}s.expectedTotalSize+=s.metaBlockLength;if(s.expectedTotalSize>1<<30){s.expectedTotalSize=1<<30}if(s.ringBufferSize2030){doReadMoreInput(s)}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}s.distancePostfixBits=readFewBits(s,2);s.numDirectDistanceCodes=readFewBits(s,4)<=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}s.contextModes[i]=readFewBits(s,2)}if(s.halfOffset>2030){doReadMoreInput(s)}}s.contextMap=new Int8Array(s.numLiteralBlockTypes<<6);var numLiteralTrees=decodeContextMap(s.numLiteralBlockTypes<<6,s.contextMap,s);s.trivialLiteralContext=1;for(var j=0;j>6){s.trivialLiteralContext=0;break}}s.distContextMap=new Int8Array(s.numDistanceBlockTypes<<2);var numDistTrees=decodeContextMap(s.numDistanceBlockTypes<<2,s.distContextMap,s);s.literalTreeGroup=decodeHuffmanTreeGroup(256,256,numLiteralTrees,s);s.commandTreeGroup=decodeHuffmanTreeGroup(704,704,s.numCommandBlockTypes,s);var distanceAlphabetSizeMax=calculateDistanceAlphabetSize(s.distancePostfixBits,s.numDirectDistanceCodes,24);var distanceAlphabetSizeLimit=distanceAlphabetSizeMax;if(s.isLargeWindow==1){distanceAlphabetSizeMax=calculateDistanceAlphabetSize(s.distancePostfixBits,s.numDirectDistanceCodes,62);distanceAlphabetSizeLimit=calculateDistanceAlphabetLimit(2147483644,s.distancePostfixBits,s.numDirectDistanceCodes)}s.distanceTreeGroup=decodeHuffmanTreeGroup(distanceAlphabetSizeMax,distanceAlphabetSizeLimit,numDistTrees,s);calculateDistanceLut(s,distanceAlphabetSizeLimit);s.contextMapSlice=0;s.distContextMapSlice=0;s.contextLookupOffset1=s.contextModes[0]*512;s.contextLookupOffset2=s.contextLookupOffset1+256;s.literalTreeIdx=0;s.commandTreeIdx=0;s.rings[4]=1;s.rings[5]=0;s.rings[6]=1;s.rings[7]=0;s.rings[8]=1;s.rings[9]=0}function copyUncompressedData(s){var ringBuffer=s.ringBuffer;if(s.metaBlockLength<=0){reload(s);s.runningState=2;return}var chunkLength=min(s.ringBufferSize-s.pos,s.metaBlockLength);copyBytes(s,ringBuffer,s.pos,chunkLength);s.metaBlockLength-=chunkLength;s.pos+=chunkLength;if(s.pos==s.ringBufferSize){s.nextRunningState=6;s.runningState=12;return}reload(s);s.runningState=2}function writeRingBuffer(s){var toWrite=min(s.outputLength-s.outputUsed,s.ringBufferBytesReady-s.ringBufferBytesWritten);if(toWrite!=0){s.output.set(s.ringBuffer.subarray(s.ringBufferBytesWritten,s.ringBufferBytesWritten+toWrite),s.outputOffset+s.outputUsed);s.outputUsed+=toWrite;s.ringBufferBytesWritten+=toWrite}if(s.outputUsed>5];var group=new Int32Array(n+n*maxTableSize);var next=n;for(var i=0;i2030){doReadMoreInput(s)}if(s.commandBlockLength==0){decodeCommandBlockSwitch(s)}s.commandBlockLength--;if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var cmdCode=readSymbol(s.commandTreeGroup,s.commandTreeIdx,s)<<2;var insertAndCopyExtraBits=CMD_LOOKUP[cmdCode];var insertLengthOffset=CMD_LOOKUP[cmdCode+1];var copyLengthOffset=CMD_LOOKUP[cmdCode+2];s.distanceCode=CMD_LOOKUP[cmdCode+3];if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var extraBits=insertAndCopyExtraBits&255;s.insertLength=insertLengthOffset+(extraBits<=16?readFewBits(s,extraBits):readManyBits(s,extraBits));if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var extraBits=insertAndCopyExtraBits>>8;s.copyLength=copyLengthOffset+(extraBits<=16?readFewBits(s,extraBits):readManyBits(s,extraBits));s.j=0;s.runningState=7;case 7:if(s.trivialLiteralContext!=0){while(s.j2030){doReadMoreInput(s)}if(s.literalBlockLength==0){decodeLiteralBlockSwitch(s)}s.literalBlockLength--;if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}ringBuffer[s.pos]=readSymbol(s.literalTreeGroup,s.literalTreeIdx,s);s.pos++;s.j++;if(s.pos>=fence){s.nextRunningState=7;s.runningState=12;break}}}else{var prevByte1=ringBuffer[s.pos-1&ringBufferMask]&255;var prevByte2=ringBuffer[s.pos-2&ringBufferMask]&255;while(s.j2030){doReadMoreInput(s)}if(s.literalBlockLength==0){decodeLiteralBlockSwitch(s)}var literalContext=LOOKUP[s.contextLookupOffset1+prevByte1]|LOOKUP[s.contextLookupOffset2+prevByte2];var literalTreeIdx=s.contextMap[s.contextMapSlice+literalContext]&255;s.literalBlockLength--;prevByte2=prevByte1;if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}prevByte1=readSymbol(s.literalTreeGroup,literalTreeIdx,s);ringBuffer[s.pos]=prevByte1;s.pos++;s.j++;if(s.pos>=fence){s.nextRunningState=7;s.runningState=12;break}}}if(s.runningState!=7){continue}s.metaBlockLength-=s.insertLength;if(s.metaBlockLength<=0){s.runningState=4;continue}var distanceCode=s.distanceCode;if(distanceCode<0){s.distance=s.rings[s.distRbIdx]}else{if(s.halfOffset>2030){doReadMoreInput(s)}if(s.distanceBlockLength==0){decodeDistanceBlockSwitch(s)}s.distanceBlockLength--;if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}var distTreeIdx=s.distContextMap[s.distContextMapSlice+distanceCode]&255;distanceCode=readSymbol(s.distanceTreeGroup,distTreeIdx,s);if(distanceCode<16){var index=s.distRbIdx+DISTANCE_SHORT_CODE_INDEX_OFFSET[distanceCode]&3;s.distance=s.rings[index]+DISTANCE_SHORT_CODE_VALUE_OFFSET[distanceCode];if(s.distance<0){throw"Negative distance"}}else{var extraBits=s.distExtraBits[distanceCode];var bits;if(s.bitOffset+extraBits<=32){bits=readFewBits(s,extraBits)}else{if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}bits=extraBits<=16?readFewBits(s,extraBits):readManyBits(s,extraBits)}s.distance=s.distOffset[distanceCode]+(bits<s.maxDistance){s.runningState=9;continue}if(distanceCode>0){s.distRbIdx=s.distRbIdx+1&3;s.rings[s.distRbIdx]=s.distance}if(s.copyLength>s.metaBlockLength){throw"Invalid backward reference"}s.j=0;s.runningState=8;case 8:var src=s.pos-s.distance&ringBufferMask;var dst=s.pos;var copyLength=s.copyLength-s.j;var srcEnd=src+copyLength;var dstEnd=dst+copyLength;if(srcEnddst&&dstEnd>src){for(var k=0;k=fence){s.nextRunningState=8;s.runningState=12;break}}}if(s.runningState==8){s.runningState=4}continue;case 9:if(s.distance>2147483644){throw"Invalid backward reference"}if(s.copyLength>=4&&s.copyLength<=24){var offset=DICTIONARY_OFFSETS_BY_LENGTH[s.copyLength];var wordId=s.distance-s.maxDistance-1;var shift=DICTIONARY_SIZE_BITS_BY_LENGTH[s.copyLength];var mask=(1<>>shift;offset+=wordIdx*s.copyLength;if(transformIdx<121){var len=transformDictionaryWord(ringBuffer,s.pos,DICTIONARY_DATA,offset,s.copyLength,RFC_TRANSFORMS,transformIdx);s.pos+=len;s.metaBlockLength-=len;if(s.pos>=fence){s.nextRunningState=4;s.runningState=12;continue}}else{throw"Invalid backward reference"}}else{throw"Invalid backward reference"}s.runningState=4;continue;case 5:while(s.metaBlockLength>0){if(s.halfOffset>2030){doReadMoreInput(s)}if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}readFewBits(s,8);s.metaBlockLength--}s.runningState=2;continue;case 6:copyUncompressedData(s);continue;case 12:s.ringBufferBytesReady=min(s.pos,s.ringBufferSize);s.runningState=13;case 13:if(writeRingBuffer(s)==0){return}if(s.pos>=s.maxBackwardDistance){s.maxDistance=s.maxBackwardDistance}if(s.pos>=s.ringBufferSize){if(s.pos>s.ringBufferSize){ringBuffer.copyWithin(0,s.ringBufferSize,s.pos)}s.pos&=ringBufferMask;s.ringBufferBytesWritten=0}s.runningState=s.nextRunningState;continue;default:throw"Unexpected state "+s.runningState}}if(s.runningState==10){if(s.metaBlockLength<0){throw"Invalid metablock length"}jumpToByteBoundary(s);checkHealth(s,1)}}function Transforms(numTransforms,prefixSuffixLen,prefixSuffixCount){this.numTransforms=0;this.triplets=new Int32Array(0);this.prefixSuffixStorage=new Int8Array(0);this.prefixSuffixHeads=new Int32Array(0);this.params=new Int16Array(0);this.numTransforms=numTransforms;this.triplets=new Int32Array(numTransforms*3);this.params=new Int16Array(numTransforms);this.prefixSuffixStorage=new Int8Array(prefixSuffixLen);this.prefixSuffixHeads=new Int32Array(prefixSuffixCount+1)}var RFC_TRANSFORMS=new Transforms(121,167,50);function unpackTransforms(prefixSuffix,prefixSuffixHeads,transforms,prefixSuffixSrc,transformsSrc){var n=prefixSuffixSrc.length;var index=1;var j=0;for(var i=0;i#\n#]# for # a # that #. # with #\'# from # by #. The # on # as # is #ing #\n\t#:#ed #(# at #ly #="# of the #. This #,# not #er #al #=\'#ful #ive #less #est #ize #ous #'," !! ! , *! &! \" ! ) * * - ! # ! #!*! + ,$ ! - % . / # 0 1 . \" 2 3!* 4% ! # / 5 6 7 8 0 1 & $ 9 + : ; < ' != > ?! 4 @ 4 2 & A *# ( B C& ) % ) !*# *-% A +! *. D! %' & E *6 F G% ! *A *% H! D I!+! J!+ K +- *4! A L!*4 M N +6 O!*% +.! K *G P +%( ! G *D +D Q +# *K!*G!+D!+# +G +A +4!+% +K!+4!*D!+K!*K")}function transformDictionaryWord(dst,dstOffset,src,srcOffset,len,transforms,transformIndex){var offset=dstOffset;var triplets=transforms.triplets;var prefixSuffixStorage=transforms.prefixSuffixStorage;var prefixSuffixHeads=transforms.prefixSuffixHeads;var transformOffset=3*transformIndex;var prefixIdx=triplets[transformOffset];var transformType=triplets[transformOffset+1];var suffixIdx=triplets[transformOffset+2];var prefix=prefixSuffixHeads[prefixIdx];var prefixEnd=prefixSuffixHeads[prefixIdx+1];var suffix=prefixSuffixHeads[suffixIdx];var suffixEnd=prefixSuffixHeads[suffixIdx+1];var omitFirst=transformType-11;var omitLast=transformType-0;if(omitFirst<1||omitFirst>9){omitFirst=0}if(omitLast<1||omitLast>9){omitLast=0}while(prefix!=prefixEnd){dst[offset++]=prefixSuffixStorage[prefix++]}if(omitFirst>len){omitFirst=len}srcOffset+=omitFirst;len-=omitFirst;len-=omitLast;var i=len;while(i>0){dst[offset++]=src[srcOffset++];i--}if(transformType==10||transformType==11){var uppercaseOffset=offset-len;if(transformType==10){len=1}while(len>0){var c0=dst[uppercaseOffset]&255;if(c0<192){if(c0>=97&&c0<=122){dst[uppercaseOffset]^=32}uppercaseOffset+=1;len-=1}else if(c0<224){dst[uppercaseOffset+1]^=32;uppercaseOffset+=2;len-=2}else{dst[uppercaseOffset+2]^=5;uppercaseOffset+=3;len-=3}}}else if(transformType==21||transformType==22){var shiftOffset=offset-len;var param=transforms.params[transformIndex];var scalar=(param&32767)+(16777216-(param&32768));while(len>0){var step=1;var c0=dst[shiftOffset]&255;if(c0<128){scalar+=c0;dst[shiftOffset]=scalar&127}else if(c0<192){}else if(c0<224){if(len>=2){var c1=dst[shiftOffset+1];scalar+=c1&63|(c0&31)<<6;dst[shiftOffset]=192|scalar>>6&31;dst[shiftOffset+1]=c1&192|scalar&63;step=2}else{step=len}}else if(c0<240){if(len>=3){var c1=dst[shiftOffset+1];var c2=dst[shiftOffset+2];scalar+=c2&63|(c1&63)<<6|(c0&15)<<12;dst[shiftOffset]=224|scalar>>12&15;dst[shiftOffset+1]=c1&192|scalar>>6&63;dst[shiftOffset+2]=c2&192|scalar&63;step=3}else{step=len}}else if(c0<248){if(len>=4){var c1=dst[shiftOffset+1];var c2=dst[shiftOffset+2];var c3=dst[shiftOffset+3];scalar+=c3&63|(c2&63)<<6|(c1&63)<<12|(c0&7)<<18;dst[shiftOffset]=240|scalar>>18&7;dst[shiftOffset+1]=c1&192|scalar>>12&63;dst[shiftOffset+2]=c2&192|scalar>>6&63;dst[shiftOffset+3]=c3&192|scalar&63;step=4}else{step=len}}shiftOffset+=step;len-=step;if(transformType==21){len=0}}}while(suffix!=suffixEnd){dst[offset++]=prefixSuffixStorage[suffix++]}return offset-dstOffset}function getNextKey(key,len){var step=1<>=1}return(key&step-1)+step}function replicateValue(table,offset,step,end,item){do{end-=step;table[offset+end]=item}while(end>0)}function nextTableBitSize(count,len,rootBits){var left=1<0;count[len]--){replicateValue(tableGroup,tableOffset+key,step,tableSize,len<<16|sorted[symbol++]);key=getNextKey(key,len)}}var mask=totalSize-1;var low=-1;var currentOffset=tableOffset;for(var len=rootBits+1,step=2;len<=15;len++,step<<=1){for(;count[len]>0;count[len]--){if((key&mask)!=low){currentOffset+=tableSize;tableBits=nextTableBitSize(count,len,rootBits);tableSize=1<>rootBits),step,tableSize,len-rootBits<<16|sorted[symbol++]);key=getNextKey(key,len)}}return totalSize}function doReadMoreInput(s){if(s.endOfStreamReached!=0){if(halfAvailable(s)>=-2){return}throw"No more input"}var readOffset=s.halfOffset<<1;var bytesInBuffer=4096-readOffset;s.byteBuffer.copyWithin(0,readOffset,4096);s.halfOffset=0;while(bytesInBuffer<4096){var spaceLeft=4096-bytesInBuffer;var len=readInput(s.input,s.byteBuffer,bytesInBuffer,spaceLeft);if(len<=0){s.endOfStreamReached=1;s.tailBytes=bytesInBuffer;bytesInBuffer+=1;break}bytesInBuffer+=len}bytesToNibbles(s,bytesInBuffer)}function checkHealth(s,endOfStream){if(s.endOfStreamReached==0){return}var byteOffset=(s.halfOffset<<1)+(s.bitOffset+7>>3)-4;if(byteOffset>s.tailBytes){throw"Read after end"}if(endOfStream!=0&&byteOffset!=s.tailBytes){throw"Unused bytes after end"}}function readFewBits(s,n){var val=s.accumulator32>>>s.bitOffset&(1<>>16;s.bitOffset-=16;return low|readFewBits(s,n-16)<<16}function initBitReader(s){s.byteBuffer=new Int8Array(4160);s.accumulator32=0;s.shortBuffer=new Int16Array(2080);s.bitOffset=32;s.halfOffset=2048;s.endOfStreamReached=0;prepare(s)}function prepare(s){if(s.halfOffset>2030){doReadMoreInput(s)}checkHealth(s,0);s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16;s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}function reload(s){if(s.bitOffset==32){prepare(s)}}function jumpToByteBoundary(s){var padding=32-s.bitOffset&7;if(padding!=0){var paddingBits=readFewBits(s,padding);if(paddingBits!=0){throw"Corrupted padding bits"}}}function halfAvailable(s){var limit=2048;if(s.endOfStreamReached!=0){limit=s.tailBytes+1>>1}return limit-s.halfOffset}function copyBytes(s,data,offset,length){if((s.bitOffset&7)!=0){throw"Unaligned copyBytes"}while(s.bitOffset!=32&&length!=0){data[offset++]=s.accumulator32>>>s.bitOffset;s.bitOffset+=8;length--}if(length==0){return}var copyNibbles=min(halfAvailable(s),length>>1);if(copyNibbles>0){var readOffset=s.halfOffset<<1;var delta=copyNibbles<<1;data.set(s.byteBuffer.subarray(readOffset,readOffset+delta),offset);offset+=delta;length-=delta;s.halfOffset+=copyNibbles}if(length==0){return}if(halfAvailable(s)>0){if(s.bitOffset>=16){s.accumulator32=s.shortBuffer[s.halfOffset++]<<16|s.accumulator32>>>16;s.bitOffset-=16}while(length!=0){data[offset++]=s.accumulator32>>>s.bitOffset;s.bitOffset+=8;length--}checkHealth(s,0);return}while(length>0){var len=readInput(s.input,data,offset,length);if(len==-1){throw"Unexpected end of input"}offset+=len;length-=len}}function bytesToNibbles(s,byteLen){var byteBuffer=s.byteBuffer;var halfLen=byteLen>>1;var shortBuffer=s.shortBuffer;for(var i=0;i>2;lookup[1792+i]=2+(i>>6)}for(var i=0;i<128;++i){lookup[1024+i]=4*(map.charCodeAt(i)-32)}for(var i=0;i<64;++i){lookup[1152+i]=i&1;lookup[1216+i]=2+(i&1)}var offset=1280;for(var k=0;k<19;++k){var value=k&3;var rep=rle.charCodeAt(k)-32;for(var i=0;istopelseliestourpack.gifpastcss?graymean>rideshotlatesaidroadvar feeljohnrickportfast\'UA-deadpoorbilltypeU.S.woodmust2px;Inforankwidewantwalllead[0];paulwavesure$(\'#waitmassarmsgoesgainlangpaid!-- lockunitrootwalkfirmwifexml"songtest20pxkindrowstoolfontmailsafestarmapscorerainflowbabyspansays4px;6px;artsfootrealwikiheatsteptriporg/lakeweaktoldFormcastfansbankveryrunsjulytask1px;goalgrewslowedgeid="sets5px;.js?40pxif (soonseatnonetubezerosentreedfactintogiftharm18pxcamehillboldzoomvoideasyringfillpeakinitcost3px;jacktagsbitsrolleditknewnear\x3c!--growJSONdutyNamesaleyou lotspainjazzcoldeyesfishwww.risktabsprev10pxrise25pxBlueding300,ballfordearnwildbox.fairlackverspairjunetechif(!pickevil$("#warmlorddoespull,000ideadrawhugespotfundburnhrefcellkeystickhourlossfuel12pxsuitdealRSS"agedgreyGET"easeaimsgirlaids8px;navygridtips#999warsladycars); }php?helltallwhomzh:e*/\r\n 100hall.\n\nA7px;pushchat0px;crew*/ericmostguidbelldeschairmathatom/imgRluckcent000;tinygonehtmlselldrugFREEnodenick?id=losenullvastwindRSS wearrelybeensamedukenasacapewishgulfT23:hitsslotgatekickblurthey15px\'\'););">msiewinsbirdsortbetaseekT18:ordstreemall60pxfarmb\0sboys[0].\');"POSTbearkids);}}marytend(UK)quadzh:f-siz----prop\');\rliftT19:viceandydebt>RSSpoolneckblowT16:doorevalT17:letsfailoralpollnovacolsgene b\0softrometillross

pourfadepinkmini)|!(minezh:hbarshear00);milk --\x3eironfreddiskwentsoilputs/js/holyT22:ISBNT20:adamsees

json\', \'contT21: RSSloopasiamoon

soulLINEfortcartT14:

80px!--<9px;T04:mike:46ZniceinchYorkricezh:d\'));puremageparatonebond:37Z_of_\']);000,zh:gtankyardbowlbush:56ZJava30px\n|}\n%C3%:34ZjeffEXPIcashvisagolfsnowzh:iquer.csssickmeatmin.binddellhirepicsrent:36ZHTTP-201fotowolfEND xbox:54ZBODYdick;\n}\nexit:35Zvarsbeat\'});diet999;anne}}sonyguysfuckpipe|-\n!002)ndow[1];[];\nLog salt\r\n\t\tbangtrimbath){\r\n00px\n});ko:lfeesad>\rs:// [];tollplug(){\n{\r\n .js\'200pdualboat.JPG);\n}quot);\n\n\');\n\r\n}\r201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037201320122011201020092008200720062005200420032002200120001999199819971996199519941993199219911990198919881987198619851984198319821981198019791978197719761975197419731972197119701969196819671966196519641963196219611960195919581957195619551954195319521951195010001024139400009999comomC!sesteestaperotodohacecadaaC1obiendC-aasC-vidacasootroforosolootracualdijosidograntipotemadebealgoquC)estonadatrespococasabajotodasinoaguapuesunosantediceluisellamayozonaamorpisoobraclicellodioshoracasiP7P0P=P0P>PP>QP8P7P=P>P4P>QP>P6P5P>P=P8QPP0P5P5P1Q\vPP2Q\vP2P>PP>P>P1PP>P;P8P=P8P P$PP5PQ\vQQ\vPP=P8Pthing.org/multiheardPowerstandtokensolid(thisbringshipsstafftriedcallsfullyfactsagentThis //--\x3eadminegyptEvent15px;Emailtrue"crossspentblogsbox">notedleavechinasizesguest

robotheavytrue,sevengrandcrimesignsawaredancephase>\x3c!--en_US'200px_namelatinenjoyajax.ationsmithU.S. holdspeterindianav">chainscorecomesdoingpriorShare1990sromanlistsjapanfallstrialowneragreeabusealertopera"-//WcardshillsteamsPhototruthclean.php?saintmetallouismeantproofbriefrow">genretrucklooksValueFrame.net/--\x3e\n\n\x3c!--POST"ocean
floorspeakdepth sizebankscatchchart20px;aligndealswould50px;url="parksmouseMost ...drugs\x3c!-- aprilidealallenexactforthcodeslogicView seemsblankports (200saved_linkgoalsgrantgreekhomesringsrated30px;whoseparse();" Blocklinuxjonespixel\');">);if(-leftdavidhorseFocusraiseboxesTrackementbar">.src=toweralt="cablehenry24px;setupitalysharpminortastewantsthis.resetwheelgirls/css/100%;clubsstuffbiblevotes 1000korea});\r\nbandsqueue= {};80px;cking{\r\n\t\taheadclockirishlike ratiostatsForm"yahoo)[0];AboutfindsdebugtasksURL =cells})();12px;primetellsturns0x600.jpg"spainbeachtaxesmicroangel--\x3e\r\nname=diegopage swiss--\x3e\n\n#fff;">Log.com"treatsheet) && 14px;sleepntentfiledja:cid="cName"worseshots-box-delta\n<bears:48Z spendbakershops= "";php">ction13px;brianhellosize=o=%2F joinmaybe, fjsimg" ")[0]MTopBType"newlyDanskczechtrailknowsfaq">zh-cn10);\n-1");type=bluestrulydavis.js\';>\r\n\r\nform jesus100% menu.\r\n\t\r\nwalesrisksumentddingb-likteachgif" vegasdanskeestishqipsuomisobredesdeentretodospuedeaC1osestC!tienehastaotrospartedondenuevohacerformamismomejormundoaquC-dC-assC3loayudafechatodastantomenosdatosotrassitiomuchoahoralugarmayorestoshorastenerantesfotosestaspaC-snuevasaludforosmedioquienmesespoderchileserC!vecesdecirjosC)estarventagrupohechoellostengoamigocosasnivelgentemismaairesjuliotemashaciafavorjuniolibrepuntobuenoautorabrilbuenatextomarzosaberlistaluegocC3moenerojuegoperC:haberestoynuncamujervalorfueralibrogustaigualvotoscasosguC-apuedosomosavisousteddebennochebuscafaltaeurosseriedichocursoclavecasasleC3nplazolargoobrasvistaapoyojuntotratavistocrearcampohemoscincocargopisosordenhacenC!readiscopedrocercapuedapapelmenorC:tilclarojorgecalleponertardenadiemarcasigueellassiglocochemotosmadreclaserestoniC1oquedapasarbancohijosviajepabloC)stevienereinodejarfondocanalnorteletracausatomarmanoslunesautosvillavendopesartipostengamarcollevapadreunidovamoszonasambosbandamariaabusomuchasubirriojavivirgradochicaallC-jovendichaestantalessalirsuelopesosfinesllamabuscoC)stalleganegroplazahumorpagarjuntadobleislasbolsabaC1ohablaluchaCreadicenjugarnotasvalleallC!cargadolorabajoestC)gustomentemariofirmacostofichaplatahogarartesleyesaquelmuseobasespocosmitadcielochicomiedoganarsantoetapadebesplayaredessietecortecoreadudasdeseoviejodeseaaguas"domaincommonstatuseventsmastersystemactionbannerremovescrollupdateglobalmediumfilternumberchangeresultpublicscreenchoosenormaltravelissuessourcetargetspringmodulemobileswitchphotosborderregionitselfsocialactivecolumnrecordfollowtitle>eitherlengthfamilyfriendlayoutauthorcreatereviewsummerserverplayedplayerexpandpolicyformatdoublepointsseriespersonlivingdesignmonthsforcesuniqueweightpeopleenergynaturesearchfigurehavingcustomoffsetletterwindowsubmitrendergroupsuploadhealthmethodvideosschoolfutureshadowdebatevaluesObjectothersrightsleaguechromesimplenoticesharedendingseasonreportonlinesquarebuttonimagesenablemovinglatestwinterFranceperiodstrongrepeatLondondetailformeddemandsecurepassedtoggleplacesdevicestaticcitiesstreamyellowattackstreetflighthiddeninfo">openedusefulvalleycausesleadersecretseconddamagesportsexceptratingsignedthingseffectfieldsstatesofficevisualeditorvolumeReportmuseummoviesparentaccessmostlymother" id="marketgroundchancesurveybeforesymbolmomentspeechmotioninsidematterCenterobjectexistsmiddleEuropegrowthlegacymannerenoughcareeransweroriginportalclientselectrandomclosedtopicscomingfatheroptionsimplyraisedescapechosenchurchdefinereasoncorneroutputmemoryiframepolicemodelsNumberduringoffersstyleskilledlistedcalledsilvermargindeletebetterbrowselimitsGlobalsinglewidgetcenterbudgetnowrapcreditclaimsenginesafetychoicespirit-stylespreadmakingneededrussiapleaseextentScriptbrokenallowschargedividefactormember-basedtheoryconfigaroundworkedhelpedChurchimpactshouldalwayslogo" bottomlist">){var prefixorangeHeader.push(couplegardenbridgelaunchReviewtakingvisionlittledatingButtonbeautythemesforgotSearchanchoralmostloadedChangereturnstringreloadMobileincomesupplySourceordersviewed courseAbout islandPhilipawardshandleimportOfficeregardskillsnationSportsdegreeweekly (e.g.behinddoctorloggedunitedbeyond-scaleacceptservedmarineFootercamera\n_form"leavesstress" />\r\n.gif" onloadloaderOxfordsistersurvivlistenfemaleDesignsize="appealtext">levelsthankshigherforcedanimalanyoneAfricaagreedrecentPeople
wonderpricesturned|| {};main">inlinesundaywrap">failedcensusminutebeaconquotes150px|estateremoteemail"linkedright;signalformal1.htmlsignupprincefloat:.png" forum.AccesspaperssoundsextendHeightsliderUTF-8"& Before. WithstudioownersmanageprofitjQueryannualparamsboughtfamousgooglelongeri++) {israelsayingdecidehome">headerensurebranchpiecesblock;statedtop">boston.test(avatartested_countforumsschemaindex,filledsharesreaderalert(appearSubmitline">body">\n* TheThoughseeingjerseyNews\nSystem DavidcancertablesprovedApril reallydriveritem">more">boardscolorscampusfirst || [];media.guitarfinishwidth:showedOther .php" assumelayerswilsonstoresreliefswedenCustomeasily your String\n\nWhiltaylorclear:resortfrenchthough") + "buyingbrandsMembername">oppingsector5px;">vspacepostermajor coffeemartinmaturehappenkansaslink">Images=falsewhile hspace0& \n\nIn powerPolski-colorjordanBottomStart -count2.htmlnews">01.jpgOnline-rightmillerseniorISBN 00,000 guidesvalue)ectionrepair.xml" rights.html-blockregExp:hoverwithinvirginphones\rusing \n\tvar >\');\n\t\n\nbahasabrasilgalegomagyarpolskisrpskiX1X/Y\bd8-fg.\0d=g9i+d?!f/d8-e=f\bd;,d8\0d8*e,e8g.!gh.:ee/d;%f\re\n!f6i4d8*d::d:\'eh*e71d<d8f%g\ve7%d=hg3;f2!f\tg=g+f\t\0f\th/h.:d8-e?fg+ g(f\b7i&i!5d=h\0f\n\0f/i.i"g8e3d8\vh==fg4"d=?g(h=/d;6e(g:?d8;i"h5fh\'i"ee$\rf3(e\fg=g;f6hee.9f(h\re8e:f6\bf/g):i4ee8d;\0d9\be%=e\vgf4;e>g\tee1e&ff\t\vf:f0i;f\0f0f9e<e\fd:,fd>e3d:f4e$h?d8*g3;g;g%if88f\be9?e\ne6d;eh!(e.\te(g,,d8\0d<eh?h!\fg9e;g\t\bfg5e-d8g\fh.>h.!e\rh49fh2e\n e%f4;e\n(d;d;,eee\re."g0e(d8\nf57e&d=e72g;gh(\0h/&g;g$>e\f:g;e=f,g+i\0h&d;7f f%e=e.6e;:h.>f\ve\vih/;f3e>\vd=\rg=.g;f5i\0\tf\v)h?f 7e=e\t\re\bg1;fh!\fe d8:d:$ff\0ei3d9d8\rh=i\0h?h!\fd8g\'f\n\0e/h=h.>e$e\bd=e$\'e.6g$>d<g g)6d8d8e(i(i!9g.h?i\fh?f/e<\0e\'\vfe5g5hfd;6eg\t\fe8.e\n)fe\fh5f:e$\'e-&e-&d9 e0e\0f5h\'\bf\nh5e7%g(\vh&f1f\0d9\bf6e\0e\nh=d8;h&g.e\t\rh5h./ee8f9f3g5e=1f\vhe#0fd;;d=e%e:7f0f\r.g>e=f1=h=&d;\vg;\rd=f/d:$f5gd:\'f\t\0d;%g5h/f>g$:d8\0d:e\rd=\rd::ee\bfe0e>ff88e7%e7e-&gg3;e\bg=e\ve8e-e/g i"if\'e\b6e0e\f:e:f,e(e=g=d8\ni\rh&g,,d:\fef,"h?e%e\vfh?d:h\0h/eg0e9h.-d;%d8\nf?e:f\bd8:g/e"i&f8/e\ff6e(1d9ei\0d8\0e.e<\0ed=ef ef,"h?h\'#e3e0f9d8\0d8\vd;%e\nh4#d;;f\bh\0e."f\b7d;#h!(g\'/e\be%3d::f0g i\0e.e:g0g&;g:?e:g(e\bh!(d8\re\fg<h>g;h.!f%h/"d8\rh&f\te3f:fe>\be$f-f>g;g;f?g-g4f%h=e\nf%f:fig\ve\b0g-i(e3i.d8e\f:ie88h\v1h/-g>e:&e8\ffg>e%3f/h>g%h/h\'e.e;:h..i(i(fh\'g2>e=)f%f,fi+eh(\0f9i"e:ie$gfie=1g\ti6h!\fh?f\te\bd:+g\t)eg;h%f7;e\n d8e.6h?g\'\rh/i"h57f%d8e\n!e,e\nh.0e=g.\0d;\vh4(ig7d::e=1e\re<g(f\n%e\ni(e\be?+i\0e(h/"f6e0f3(fg3h/7e-&f !e:h/%ee2e*f/h?eh4-d90e\rg\'0d8:d:f\be\nh/4fd>e:e-)e-d8i"g(\ve:d8\0h\b,fe!e*f\te6e.d?f\n$h\0\fd8d;\ne$)g*e#e\n(f\0g\n6f\0g\t9e\b+h.$d8:e?i!;f4f0e0h/4f\be\0d=d8:e*d=e\ff\v,i#d9\bd8\0f 7e=ef/e&f 9f\r.g5h\'e-&i"e7f\th?g(\vg1d:d::f\t\re:f%d8\rh?f-#e(fffd:\ve3g3;f i"ee\n!h>e%d8\0g4e:g!\0fe-&d:h\'#e;:g-g;fe(gi\0g%h.!e\be/9d:h\t:f/g8e\feggge;:g+\vg-\tg:\'g1;e\vg;i*\fe.g0e\b6d=f%h*f g->d;%d8\vee\bf f3e6d8-e\0\vd::d8\0e\bf\fe\re3i-ie"g,,d8\te3f3(e f-$g\'g\tf71e3ed8e9?e7f%fi+g:\'f\0h?g;h!\fd8:d:$i\0h/d;7h\'\te>g2>e\re.6e:-e.\ff\bfh\'\te.\th#e>e\b0i.d;6e\b6e:&i#eh=g6h=,h==f\n%d;7h.0h\0f9f!\bh!\ff?d::f0g(ed8h%?fe:ie:g6ed;f,>g-g9d;%e\t\re.\fe(ee8h.>g=.i"e/g%h7e>e\b)g(g;\'g;-d= d;,h?d9\bf(!e<h/-h(\0h=e$ihf\rd=i#f d9&f\tf\bf5\vh/g\';e\n(f\t\rh=e3e.h!g%(d8\rf-i\0f1d8\re>e\nf3d9\vi4ig(h%i\0f\nh/\tg.f g\b1ffe=1f\td:h$h#=fe-&f:d<f0e-h#d?.h4-g\t)efe(i"g2>ee6e.d:\vff04e93fg$:d8\ne8h0"h0"f.i\0fe8\bd8\nd< g1;e\b+f-\ff2f\v%f\te\bf0i\rd;6e*h&f6d;#h3h(\nh>>e\b0d::gh."ih\0e8\be1g$:e?gh44e-g62g+d8;i!\fh*g6g:\'e\b+g.\0e\rf9i)i#d:f%h/4f\te<\0d;#g e\b i$h/e\b8h\ng.i\rg9f,!f8e$e0h\'e\bh5if\t>e\b0d;%ee$\'e(d8;i!5f\0d=3eg-e$)d8\vd?ig0d;#f#\0f%f\ng%(e0f6f2f\tf-#e88gh3d;#gg.e=e,e<\0e$\re\b6ih\re98g&g\t\bf,e="f\bee$h!\ffee\b0f\0f3f\0f 7e\rh..h.$h/f\0e%=d:\'gf\f\tg\'f\rh#e9?d8e\n(f<+ih4-f0f\t\vg;e>i"f?eh\0f?f2;e.9fe$)e0e\n*e\nd::d;,e\rg:\'i\0e:&d::g\t)h0f4f5h!\fi\0 f\bfe-i)e=h48fe<\0e1g8ih!(g0e=1h\'e&f-$g>e.9e$\'e0f\n%if!f,>e?fh.8e$f3h\'e.6e1d9&e:h?f%g+\ve\r3d8>f\n%f\n\0e7\'e%%h?g;e%d;%f%gh.:d:\vd;6h*g1d8-e\re\ne,e&\be&\bgf-#d8\rie(fe\be\fd;7e\0e7&e3h!d;=g-f!\be.ig5d?!g;gge=e.#d< d;;e\n!f-#e<g\t9h\t2d8\vf%e\rd<e*h=e=g6i\rf0e\'e.9f\fe/g(\ve\f;gg;h?h?e;d9\ve\t\rf6e%e94e:&fe?g>d8=f\0i+g;if*f%e\n e7%e\rh4#fg(\vg\t\beh:+d=i\re:e:e.f\bf,e="e<eh1e:e9d8f9i.g.1e\rd:,f1h\fee>h\fd=\rg8d?!i!5i"e\big=i!5g!.e.e>d>\vg=e\0g\'/fih//g.ge.h4f:e3i#i)f\bfgf/e. g\t)i$d:h)h+g>ge\nf6f1h4-g+g9e?g+%f/e$)d8-e$.h.$h/f/d8*e$)f4%e-d=e0g#g;4f\n$f,i!5d8*f\0\'e.f9e88h\'g8f:f\bg%e:e=e>\ve8\bf9d>?f !e-h!e8f\b?e1\vf g.ee7%e/ee<h57f9eg,,ed<h.!h**fig\'e.e.h\'h\ff6\bh49e1e\fe?h.0d=g3;e8&f%e\re-ge\n gee\b0d:\ff\t\ve$\'if\bd::f0ie1d:+e\f:ee%3e-)ee\bf\t\0e(g;fi\0d?!h6g:\'i\rg=.e=f6d<g\'\0f\0\'ff\b?d:\'i\nf\b2e:e#fd:$e01d8d?e%g(\ve:&ef0d:\vd8f4d8*e11d8ffg\t9f.\ne\bi!fe0\ve1d:i(f\b7h4"e\n!e#0i3e\ne6h4"g;ef\fe92i(f\bg+\ve\b)g\nh\0hf\bi=e\fh#g(f\b6f/h5fff\vee.\ff4gf/ge/g"i#g;?h\t2g(3e.g;\bd:gg\t)d>f1fg\ve\nid8%i\rf08h?egf\tig+d:\te/9h1!h49g(d8\re%=g;e/9e\re\bd?h?g9h/e=1i3d<e\n?d8\re0f,#h5e96d8f\tg9f9ee(f0d?!g(h.>f=e="h1!h5f eeg;d8\0e:g\t\bf\ti\0 g"ef&e5g(d:d?ge g4 d8-e\ve-e(h44e>f\0fi?fe#d;7gh4"e:e0e.\tff-&f1\ti\fi"e\be;:e$)g):i&e\be.\fei)1e\n(d8\vi"d8\re\rh/d?!fd9\ti3e\th\v1e=f<d:.ed:\vg)e.6g>$d<ef0e\r3e/e\rg(1e.6e7e\n(g;f3e\b0f3(fe0e-&f\0\'h=h\0g g!,d;6h\'g\vf8f%fg,i&i i;ii\0g(f1h\vge.d8;g.!i6f.5h(;e\ng?;h/fe\b)ee%=d<P:P0P:P8P;P8Q\rQP>P2QP5P5P3P>P?Q\0P8QP0P:P5Q\tP5QP6P5PP0P:P1P5P7P1Q\vP;P>P=P8PQP5P?P>P4P-QP>QP>PP=P0P3P4P5PP3P>P4P2P>QQP0PP2P0QP2P0PQQQP=P0P4P4P=QPP>QQQ\0P8P=P5P9PP0QP=P8PQQ\0QP1PP=P8PPP9P4P2P5P>P=P>QQP4`$`%`$9`%\b`$`%\0`$8`%`$`$>`$`%\v`$`$0`$*`$0`$(`%`$`$`$`$?`$-`%\0`$`$8`$`$0`$$`%\v`$9`%\v`$`$*`$9`%\0`$/`$9`$/`$>`$$`$`$%`$>jagran`$`$`$`%\v`$`$,`$&`%\v`$`$\b`$`$>`$`$`$9`$.`$`$(`$5`$9`$/`%`$%`%`$%`%\0`$`$0`$`$,`$&`%\0`$`$\b`$`%\0`$5`%`$(`$\b`$(`$`$9`$0`$\t`$8`$.`%`$`$.`$5`%\v`$2`%`$8`$,`$.`$\b`$&`%`$`$0`$`$.`$,`$8`$-`$0`$,`$(`$`$2`$.`$(`$`$`$8`%\0`$2`%\0X9YY\tX%YY\tYX0X\'X"X.X1X9X/X/X\'YY\tYX0YX5Y\bX1X:Y\nX1YX\'YY\bYX\'X(Y\nYX9X1X6X0YYYYX\'Y\nY\bYYX\'YX9YY\nX\'YX\'YYYX-X*Y\tYX(YY\bX-X)X\'X.X1YYX7X9X(X/X1YYX%X0X\'YYX\'X\'X-X/X%YX\'YY\nYX(X9X6YY\nYX(X-X+Y\bYYY\bYY\bX#YX\'X,X/X\'YYX\'X3YYX9YX/YY\nX3X9X(X1X5YY\tYYX0X(YX\'X#YYYX+YYYX*X\'YX\'X-Y\nX+YX5X1X4X1X-X-Y\bYY\bYY\nX\'X0X\'YYYYX1X)X\'YX*X\'YYX#X(Y\bX.X\'X5X#YX*X\'YYX\'YY\nX9X6Y\bY\bYX/X\'X(YX.Y\nX1X(YX*YYYX4X\'X!Y\bYY\nX\'X(Y\bYX5X5Y\bYX\'X1YYX#X-X/YX-YX9X/YX1X#Y\nX\'X-X)YX*X(X/Y\bYY\nX,X(YYYX*X-X*X,YX)X3YX)Y\nX*YYX1X)X:X2X)YYX3X(Y\nX*YYYYYX\'X*YYYYX(YYX\'X9YYX#Y\bYX4Y\nX!YY\bX1X#YX\'YY\nYX(YYX0X\'X*X1X*X(X(X#YYYX3X\'YYX(Y\nX9YYX/X-X3YYYYX4X9X1X#YYX4YX1YX7X1X7YX(profileservicedefaulthimselfdetailscontentsupportstartedmessagesuccessfashioncountryaccountcreatedstoriesresultsrunningprocesswritingobjectsvisiblewelcomearticleunknownnetworkcompanydynamicbrowserprivacyproblemServicerespectdisplayrequestreservewebsitehistoryfriendsoptionsworkingversionmillionchannelwindow.addressvisitedweathercorrectproductedirectforwardyou canremovedsubjectcontrolarchivecurrentreadinglibrarylimitedmanagerfurthersummarymachineminutesprivatecontextprogramsocietynumberswrittenenabledtriggersourcesloadingelementpartnerfinallyperfectmeaningsystemskeepingculture",journalprojectsurfaces"expiresreviewsbalanceEnglishContentthroughPlease opinioncontactaverageprimaryvillageSpanishgallerydeclinemeetingmissionpopularqualitymeasuregeneralspeciessessionsectionwriterscounterinitialreportsfiguresmembersholdingdisputeearlierexpressdigitalpictureAnothermarriedtrafficleadingchangedcentralvictoryimages/reasonsstudiesfeaturelistingmust beschoolsVersionusuallyepisodeplayinggrowingobviousoverlaypresentactions</ul>\r\nwrapperalreadycertainrealitystorageanotherdesktopofferedpatternunusualDigitalcapitalWebsitefailureconnectreducedAndroiddecadesregular & animalsreleaseAutomatgettingmethodsnothingPopularcaptionletterscapturesciencelicensechangesEngland=1&History = new CentralupdatedSpecialNetworkrequirecommentwarningCollegetoolbarremainsbecauseelectedDeutschfinanceworkersquicklybetweenexactlysettingdiseaseSocietyweaponsexhibit<!--Controlclassescoveredoutlineattacksdevices(windowpurposetitle="Mobile killingshowingItaliandroppedheavilyeffects-1\']);\nconfirmCurrentadvancesharingopeningdrawingbillionorderedGermanyrelated</form>includewhetherdefinedSciencecatalogArticlebuttonslargestuniformjourneysidebarChicagoholidayGeneralpassage,"animatefeelingarrivedpassingnaturalroughly.\n\nThe but notdensityBritainChineselack oftributeIreland" data-factorsreceivethat isLibraryhusbandin factaffairsCharlesradicalbroughtfindinglanding:lang="return leadersplannedpremiumpackageAmericaEdition]"Messageneed tovalue="complexlookingstationbelievesmaller-mobilerecordswant tokind ofFirefoxyou aresimilarstudiedmaximumheadingrapidlyclimatekingdomemergedamountsfoundedpioneerformuladynastyhow to SupportrevenueeconomyResultsbrothersoldierlargelycalling."AccountEdward segmentRobert effortsPacificlearnedup withheight:we haveAngelesnations_searchappliedacquiremassivegranted: falsetreatedbiggestbenefitdrivingStudiesminimumperhapsmorningsellingis usedreversevariant role="missingachievepromotestudentsomeoneextremerestorebottom:evolvedall thesitemapenglishway to AugustsymbolsCompanymattersmusicalagainstserving})();\r\npaymenttroubleconceptcompareparentsplayersregionsmonitor \'\'The winningexploreadaptedGalleryproduceabilityenhancecareers). The collectSearch ancientexistedfooter handlerprintedconsoleEasternexportswindowsChannelillegalneutralsuggest_headersigning.html">settledwesterncausing-webkitclaimedJusticechaptervictimsThomas mozillapromisepartieseditionoutside:false,hundredOlympic_buttonauthorsreachedchronicdemandssecondsprotectadoptedprepareneithergreatlygreateroverallimprovecommandspecialsearch.worshipfundingthoughthighestinsteadutilityquarterCulturetestingclearlyexposedBrowserliberal} catchProjectexamplehide();FloridaanswersallowedEmperordefenseseriousfreedomSeveral-buttonFurtherout of != nulltrainedDenmarkvoid(0)/all.jspreventRequestStephen\n\nWhen observe</h2>\r\nModern provide" alt="borders.\n\nFor \n\nMany artistspoweredperformfictiontype ofmedicalticketsopposedCouncilwitnessjusticeGeorge Belgium...</a>twitternotablywaitingwarfare Other rankingphrasesmentionsurvivescholar</p>\r\n Countryignoredloss ofjust asGeorgiastrange<head><stopped1\']);\r\nislandsnotableborder:list ofcarried100,000</h3>\n severalbecomesselect wedding00.htmlmonarchoff theteacherhighly biologylife ofor evenrise of»plusonehunting(thoughDouglasjoiningcirclesFor theAncientVietnamvehiclesuch ascrystalvalue =Windowsenjoyeda smallassumed<a id="foreign All rihow theDisplayretiredhoweverhidden;battlesseekingcabinetwas notlook atconductget theJanuaryhappensturninga:hoverOnline French lackingtypicalextractenemieseven ifgeneratdecidedare not/searchbeliefs-image:locatedstatic.login">convertviolententeredfirst">circuitFinlandchemistshe was10px;">as suchdivided</span>will beline ofa greatmystery/index.fallingdue to railwaycollegemonsterdescentit withnuclearJewish protestBritishflowerspredictreformsbutton who waslectureinstantsuicidegenericperiodsmarketsSocial fishingcombinegraphicwinners<br /><by the NaturalPrivacycookiesoutcomeresolveSwedishbrieflyPersianso muchCenturydepictscolumnshousingscriptsnext tobearingmappingrevisedjQuery(-width:title">tooltipSectiondesignsTurkishyounger.match(})();\n\nburningoperatedegreessource=Richardcloselyplasticentries</tr>\r\ncolor:#ul id="possessrollingphysicsfailingexecutecontestlink toDefault<br />\n: true,chartertourismclassicproceedexplain</h1>\r\nonline.?xml vehelpingdiamonduse theairlineend --\x3e).attr(readershosting#ffffffrealizeVincentsignals src="/ProductdespitediversetellingPublic held inJoseph theatreaffects<style>a largedoesn\'tlater, ElementfaviconcreatorHungaryAirportsee theso thatMichaelSystemsPrograms, and width=e"tradingleft">\npersonsGolden Affairsgrammarformingdestroyidea ofcase ofoldest this is.src = cartoonregistrCommonsMuslimsWhat isin manymarkingrevealsIndeed,equally/show_aoutdoorescape(Austriageneticsystem,In the sittingHe alsoIslandsAcademy\n\t\t\x3c!--Daniel bindingblock">imposedutilizeAbraham(except{width:putting).html(|| [];\nDATA[ *kitchenmountedactual dialectmainly _blank\'installexpertsif(typeIt also© ">Termsborn inOptionseasterntalkingconcerngained ongoingjustifycriticsfactoryits ownassaultinvitedlastinghis ownhref="/" rel="developconcertdiagramdollarsclusterphp?id=alcohol);})();using a><span>vesselsrevivalAddressamateurandroidallegedillnesswalkingcentersqualifymatchesunifiedextinctDefensedied in\n\t\x3c!-- customslinkingLittle Book ofeveningmin.js?are thekontakttoday\'s.html" target=wearingAll Rig;\n})();raising Also, crucialabout">declare--\x3e\n<scfirefoxas muchappliesindex, s, but type = \n\r\n\x3c!--towardsRecordsPrivateForeignPremierchoicesVirtualreturnsCommentPoweredinline;povertychamberLiving volumesAnthonylogin" RelatedEconomyreachescuttinggravitylife inChapter-shadowNotable</td>\r\n returnstadiumwidgetsvaryingtravelsheld bywho arework infacultyangularwho hadairporttown of\n\nSome \'click\'chargeskeywordit willcity of(this);Andrew unique checkedor more300px; return;rsion="pluginswithin herselfStationFederalventurepublishsent totensionactresscome tofingersDuke ofpeople,exploitwhat isharmonya major":"httpin his menu">\nmonthlyofficercouncilgainingeven inSummarydate ofloyaltyfitnessand wasemperorsupremeSecond hearingRussianlongestAlbertalateralset of small">.appenddo withfederalbank ofbeneathDespiteCapitalgrounds), and percentit fromclosingcontainInsteadfifteenas well.yahoo.respondfighterobscurereflectorganic= Math.editingonline paddinga wholeonerroryear ofend of barrierwhen itheader home ofresumedrenamedstrong>heatingretainscloudfrway of March 1knowingin partBetweenlessonsclosestvirtuallinks">crossedEND --\x3efamous awardedLicenseHealth fairly wealthyminimalAfricancompetelabel">singingfarmersBrasil)discussreplaceGregoryfont copursuedappearsmake uproundedboth ofblockedsaw theofficescoloursif(docuwhen heenforcepush(fuAugust UTF-8">Fantasyin mostinjuredUsuallyfarmingclosureobject defenceuse of Medical<body>\nevidentbe usedkeyCodesixteenIslamic#000000entire widely active (typeofone cancolor =speakerextendsPhysicsterrain<tbody>funeralviewingmiddle cricketprophetshifteddoctorsRussell targetcompactalgebrasocial-bulk ofman and</td>\n he left).val()false);logicalbankinghome tonaming Arizonacredits);\n});\nfounderin turnCollinsbefore But thechargedTitle">CaptainspelledgoddessTag --\x3eAdding:but wasRecent patientback in=false&Lincolnwe knowCounterJudaismscript altered\']);\n has theunclearEvent\',both innot all\n\n\x3c!-- placinghard to centersort ofclientsstreetsBernardassertstend tofantasydown inharbourFreedomjewelry/about..searchlegendsis mademodern only ononly toimage" linear painterand notrarely acronymdelivershorter00&as manywidth="/* <![Ctitle =of the lowest picked escapeduses ofpeoples PublicMatthewtacticsdamagedway forlaws ofeasy to windowstrong simple}catch(seventhinfoboxwent topaintedcitizenI don\'tretreat. Some ww.");\nbombingmailto:made in. Many carries||{};wiwork ofsynonymdefeatsfavoredopticalpageTraunless sendingleft"><comScorAll thejQuery.touristClassicfalse" Wilhelmsuburbsgenuinebishops.split(global followsbody ofnominalContactsecularleft tochiefly-hidden-banner</li>\n\n. When in bothdismissExplorealways via thespaC1olwelfareruling arrangecaptainhis sonrule ofhe tookitself,=0&(calledsamplesto makecom/pagMartin Kennedyacceptsfull ofhandledBesides//--\x3e</able totargetsessencehim to its by common.mineralto takeways tos.org/ladvisedpenaltysimple:if theyLettersa shortHerbertstrikes groups.lengthflightsoverlapslowly lesser social </p>\n\t\tit intoranked rate oful>\r\n attemptpair ofmake itKontaktAntoniohaving ratings activestreamstrapped").css(hostilelead tolittle groups,Picture--\x3e\r\n\r\n rows=" objectinverse<footerCustomV><\\/scrsolvingChamberslaverywoundedwhereas!= \'undfor allpartly -right:Arabianbacked centuryunit ofmobile-Europe,is homerisk ofdesiredClintoncost ofage of become none ofp"Middle ead\')[0Criticsstudios>©group">assemblmaking pressedwidget.ps:" ? rebuiltby someFormer editorsdelayedCanonichad thepushingclass="but arepartialBabylonbottom carrierCommandits useAs withcoursesa thirddenotesalso inHouston20px;">accuseddouble goal ofFamous ).bind(priests Onlinein Julyst + "gconsultdecimalhelpfulrevivedis veryr\'+\'iptlosing femalesis alsostringsdays ofarrivalfuture <objectforcingString(" />\n\t\there isencoded. The balloondone by/commonbgcolorlaw of Indianaavoidedbut the2px 3pxjquery.after apolicy.men andfooter-= true;for usescreen.Indian image =family,http://  driverseternalsame asnoticedviewers})();\n is moreseasonsformer the newis justconsent Searchwas thewhy theshippedbr><br>width: height=made ofcuisineis thata very Admiral fixed;normal MissionPress, ontariocharsettry to invaded="true"spacingis mosta more totallyfall of});\r\n immensetime inset outsatisfyto finddown tolot of Playersin Junequantumnot thetime todistantFinnishsrc = (single help ofGerman law andlabeledforestscookingspace">header-well asStanleybridges/globalCroatia About [0];\n it, andgroupedbeing a){throwhe madelighterethicalFFFFFF"bottom"like a employslive inas seenprintermost ofub-linkrejectsand useimage">succeedfeedingNuclearinformato helpWomen\'sNeitherMexicanprotein<table by manyhealthylawsuitdevised.push({sellerssimply Through.cookie Image(older">us.js"> Since universlarger open to!-- endlies in\']);\r\n marketwho is ("DOMComanagedone fortypeof Kingdomprofitsproposeto showcenter;made itdressedwere inmixtureprecisearisingsrc = \'make a securedBaptistvoting \n\t\tvar March 2grew upClimate.removeskilledway the</head>face ofacting right">to workreduceshas haderectedshow();action=book ofan area== "htt<header\n<html>conformfacing cookie.rely onhosted .customhe wentbut forspread Family a meansout theforums.footage">MobilClements" id="as highintense--\x3e\x3c!--female is seenimpliedset thea stateand hisfastestbesidesbutton_bounded"><img Infoboxevents,a youngand areNative cheaperTimeoutand hasengineswon the(mostlyright: find a -bottomPrince area ofmore ofsearch_nature,legallyperiod,land ofor withinducedprovingmissilelocallyAgainstthe wayk"px;">\r\npushed abandonnumeralCertainIn thismore inor somename isand, incrownedISBN 0-createsOctobermay notcenter late inDefenceenactedwish tobroadlycoolingonload=it. TherecoverMembersheight assumes<html>\npeople.in one =windowfooter_a good reklamaothers,to this_cookiepanel">London,definescrushedbaptismcoastalstatus title" move tolost inbetter impliesrivalryservers SystemPerhapses and contendflowinglasted rise inGenesisview ofrising seem tobut in backinghe willgiven agiving cities.flow of Later all butHighwayonly bysign ofhe doesdiffersbattery&lasinglesthreatsintegertake onrefusedcalled =US&See thenativesby thissystem.head of:hover,lesbiansurnameand allcommon/header__paramsHarvard/pixel.removalso longrole ofjointlyskyscraUnicodebr />\r\nAtlantanucleusCounty,purely count">easily build aonclicka givenpointerh"events else {\nditionsnow the, with man whoorg/Webone andcavalryHe diedseattle00,000 {windowhave toif(windand itssolely m"renewedDetroitamongsteither them inSenatorUs</a><King ofFrancis-produche usedart andhim andused byscoringat hometo haverelatesibilityfactionBuffalolink"><what hefree toCity ofcome insectorscountedone daynervoussquare };if(goin whatimg" alis onlysearch/tuesdaylooselySolomonsexual - <a hrmedium"DO NOT France,with a war andsecond take a >\r\n\r\n\r\nmarket.highwaydone inctivity"last">obligedrise to"undefimade to Early praisedin its for hisathleteJupiterYahoo! termed so manyreally s. The a woman?value=direct right" bicycleacing="day andstatingRather,higher Office are nowtimes, when a pay foron this-link">;borderaround annual the Newput the.com" takin toa brief(in thegroups.; widthenzymessimple in late{returntherapya pointbanninginks">\n();" rea place\\u003Caabout atr>\r\n\t\tccount gives a<SCRIPTRailwaythemes/toolboxById("xhumans,watchesin some if (wicoming formats Under but hashanded made bythan infear ofdenoted/iframeleft involtagein eacha"base ofIn manyundergoregimesaction </p>\r\n<ustomVa;></importsor thatmostly &re size="</a></ha classpassiveHost = WhetherfertileVarious=[];(fucameras/></td>acts asIn some>\r\n\r\n<!organis <br />BeijingcatalC deutscheuropeueuskaragaeilgesvenskaespaC1amensajeusuariotrabajomC)xicopC!ginasiempresistemaoctubreduranteaC1adirempresamomentonuestroprimeratravC)sgraciasnuestraprocesoestadoscalidadpersonanC:meroacuerdomC:sicamiembroofertasalgunospaC-sesejemploderechoademC!sprivadoagregarenlacesposiblehotelessevillaprimeroC:ltimoeventosarchivoculturamujeresentradaanuncioembargomercadograndesestudiomejoresfebrerodiseC1oturismocC3digoportadaespaciofamiliaantoniopermiteguardaralgunaspreciosalguiensentidovisitastC-tuloconocersegundoconsejofranciaminutossegundatenemosefectosmC!lagasesiC3nrevistagranadacompraringresogarcC-aacciC3necuadorquienesinclusodeberC!materiahombresmuestrapodrC-amaC1anaC:ltimaestamosoficialtambienningC:nsaludospodemosmejorarpositionbusinesshomepagesecuritylanguagestandardcampaignfeaturescategoryexternalchildrenreservedresearchexchangefavoritetemplatemilitaryindustryservicesmaterialproductsz-index:commentssoftwarecompletecalendarplatformarticlesrequiredmovementquestionbuildingpoliticspossiblereligionphysicalfeedbackregisterpicturesdisabledprotocolaudiencesettingsactivityelementslearninganythingabstractprogressoverviewmagazineeconomictrainingpressurevarious <strong>propertyshoppingtogetheradvancedbehaviordownloadfeaturedfootballselectedLanguagedistanceremembertrackingpasswordmodifiedstudentsdirectlyfightingnortherndatabasefestivalbreakinglocationinternetdropdownpracticeevidencefunctionmarriageresponseproblemsnegativeprogramsanalysisreleasedbanner">purchasepoliciesregionalcreativeargumentbookmarkreferrerchemicaldivisioncallbackseparateprojectsconflicthardwareinterestdeliverymountainobtained= false;for(var acceptedcapacitycomputeridentityaircraftemployedproposeddomesticincludesprovidedhospitalverticalcollapseapproachpartnerslogo"><adaughterauthor" culturalfamilies/images/assemblypowerfulteachingfinisheddistrictcriticalcgi-bin/purposesrequireselectionbecomingprovidesacademicexerciseactuallymedicineconstantaccidentMagazinedocumentstartingbottom">observed: "extendedpreviousSoftwarecustomerdecisionstrengthdetailedslightlyplanningtextareacurrencyeveryonestraighttransferpositiveproducedheritageshippingabsolutereceivedrelevantbutton" violenceanywherebenefitslaunchedrecentlyalliancefollowedmultiplebulletinincludedoccurredinternal$(this).republic><tr><tdcongressrecordedultimatesolution<ul id="discoverHome</a>websitesnetworksalthoughentirelymemorialmessagescontinueactive">somewhatvictoriaWestern title="LocationcontractvisitorsDownloadwithout right">\nmeasureswidth = variableinvolvedvirginianormallyhappenedaccountsstandingnationalRegisterpreparedcontrolsaccuratebirthdaystrategyofficialgraphicscriminalpossiblyconsumerPersonalspeakingvalidateachieved.jpg" />machines</h2>\n keywordsfriendlybrotherscombinedoriginalcomposedexpectedadequatepakistanfollow" valuable</label>relativebringingincreasegovernorplugins/List of Header">" name=" ("graduate</head>\ncommercemalaysiadirectormaintain;height:schedulechangingback to catholicpatternscolor: #greatestsuppliesreliable</ul>\n\t\t<select citizensclothingwatching<li id="specificcarryingsentence<center>contrastthinkingcatch(e)southernMichael merchantcarouselpadding:interior.split("lizationOctober ){returnimproved-->\n\ncoveragechairman.png" />subjectsRichard whateverprobablyrecoverybaseballjudgmentconnect..css" /> websitereporteddefault"/></a>\r\nelectricscotlandcreationquantity. ISBN 0did not instance-search-" lang="speakersComputercontainsarchivesministerreactiondiscountItalianocriteriastrongly: \'http:\'script\'coveringofferingappearedBritish identifyFacebooknumerousvehiclesconcernsAmericanhandlingdiv id="William provider_contentaccuracysection andersonflexibleCategorylawrence<script>layout="approved maximumheader"></table>Serviceshamiltoncurrent canadianchannels/themes//articleoptionalportugalvalue=""intervalwirelessentitledagenciesSearch" measuredthousandspending…new Date" size="pageNamemiddle" " /></a>hidden">sequencepersonaloverflowopinionsillinoislinks">\n\t<title>versionssaturdayterminalitempropengineersectionsdesignerproposal="false"EspaC1olreleasessubmit" er"additionsymptomsorientedresourceright"><pleasurestationshistory.leaving border=contentscenter">.\n\nSome directedsuitablebulgaria.show();designedGeneral conceptsExampleswilliamsOriginal"><span>search">operatorrequestsa "allowingDocumentrevision. \n\nThe yourselfContact michiganEnglish columbiapriorityprintingdrinkingfacilityreturnedContent officersRussian generate-8859-1"indicatefamiliar qualitymargin:0 contentviewportcontacts-title">portable.length eligibleinvolvesatlanticonload="default.suppliedpaymentsglossary\n\nAfter guidance</td><tdencodingmiddle">came to displaysscottishjonathanmajoritywidgets.clinicalthailandteachers<head>\n\taffectedsupportspointer;toString</small>oklahomawill be investor0" alt="holidaysResourcelicensed (which . After considervisitingexplorerprimary search" android"quickly meetingsestimate;return ;color:# height=approval, " checked.min.js"magnetic></a></hforecast. While thursdaydvertiseéhasClassevaluateorderingexistingpatients Online coloradoOptions"campbell\x3c!-- end</span><<br />\r\n_popups|sciences," quality Windows assignedheight: <b classle" value=" Companyexamples<iframe believespresentsmarshallpart of properly).\n\nThe taxonomymuch of </span>\n" data-srtuguC*sscrollTo project<head>\r\nattorneyemphasissponsorsfancyboxworld\'s wildlifechecked=sessionsprogrammpx;font- Projectjournalsbelievedvacationthompsonlightingand the special border=0checking</tbody><button Completeclearfix\n<head>\narticle <sectionfindingsrole in popular Octoberwebsite exposureused to changesoperatedclickingenteringcommandsinformed numbers </div>creatingonSubmitmarylandcollegesanalyticlistingscontact.loggedInadvisorysiblingscontent"s")s. This packagescheckboxsuggestspregnanttomorrowspacing=icon.pngjapanesecodebasebutton">gamblingsuch as , while </span> missourisportingtop:1px .</span>tensionswidth="2lazyloadnovemberused in height="cript">\n </<tr><td height:2/productcountry include footer" <!-- title"></jquery.</form>\n(g.\0d=)(g9i+)hrvatskiitalianoromC"nDtC<rkC\'eX\'X1X/Y\btambiC)nnoticiasmensajespersonasderechosnacionalserviciocontactousuariosprogramagobiernoempresasanunciosvalenciacolombiadespuC)sdeportesproyectoproductopC:bliconosotroshistoriapresentemillonesmediantepreguntaanteriorrecursosproblemasantiagonuestrosopiniC3nimprimirmientrasamC)ricavendedorsociedadrespectorealizarregistropalabrasinterC)sentoncesespecialmiembrosrealidadcC3rdobazaragozapC!ginassocialesbloqueargestiC3nalquilersistemascienciascompletoversiC3ncompletaestudiospC:blicaobjetivoalicantebuscadorcantidadentradasaccionesarchivossuperiormayorC-aalemaniafunciC3nC:ltimoshaciendoaquellosediciC3nfernandoambientefacebooknuestrasclientesprocesosbastantepresentareportarcongresopublicarcomerciocontratojC3venesdistritotC)cnicaconjuntoenergC-atrabajarasturiasrecienteutilizarboletC-nsalvadorcorrectatrabajosprimerosnegocioslibertaddetallespantallaprC3ximoalmerC-aanimalesquiC)nescorazC3nsecciC3nbuscandoopcionesexteriorconceptotodavC-agalerC-aescribirmedicinalicenciaconsultaaspectoscrC-ticadC3laresjusticiadeberC!nperC-odonecesitamantenerpequeC1orecibidatribunaltenerifecanciC3ncanariasdescargadiversosmallorcarequieretC)cnicodeberC-aviviendafinanzasadelantefuncionaconsejosdifC-cilciudadesantiguasavanzadatC)rminounidadessC!nchezcampaC1asoftonicrevistascontienesectoresmomentosfacultadcrC)ditodiversassupuestofactoressegundospequeC1aP3P>P4P0P5QP;P8P5QQQ\fP1Q\vP;P>P1Q\vQQ\fQ\rQP>P<PQP;P8QP>P3P>P<P5P=QP2QP5QQ\rQP>P9P4P0P6P5P1Q\vP;P8P3P>P4QP4P5P=Q\fQ\rQP>QP1Q\vP;P0QP5P1QP>P4P8P=QP5P1P5P=P0P4P>QP0P9QQP>QP>P=P5P3P>QP2P>P8QP2P>P9P8P3Q\0Q\vQP>P6P5P2QP5P<QP2P>QP;P8Q\bQ\fQ\rQP8QP?P>P:P0P4P=P5P9P4P>P<P0P<P8Q\0P0P;P8P1P>QP5P<QQP>QQP4P2QQQP5QP8P;QP4P8P4P5P;P>P<P8Q\0P5QP5P1QQP2P>P5P2P8P4P5QP5P3P>Q\rQP8P<QQP5QQP5P<Q\vQP5P=Q\vQQP0P;P2P5P4Q\fQP5P<P5P2P>P4Q\vQP5P1P5P2Q\vQ\bP5P=P0P<P8QP8P?P0QP>P<QP?Q\0P0P2P;P8QP0P>P4P=P0P3P>P4Q\vP7P=P0QP<P>P3QP4Q\0QP3P2QP5P9P8P4P5QP:P8P=P>P>P4P=P>P4P5P;P0P4P5P;P5QQ\0P>P:P8QP=QP2P5QQ\fPQQQ\fQ\0P0P7P0P=P0Q\bP8X\'YYYX\'YX*Y\nX,YY\nX9X.X\'X5X)X\'YX0Y\nX9YY\nYX,X/Y\nX/X\'YX"YX\'YX1X/X*X-YYX5YX-X)YX\'YX*X\'YYY\nY\nYY\bYX4X(YX)YY\nYX\'X(YX\'X*X-Y\bX\'X!X#YX+X1X.YX\'YX\'YX-X(X/YY\nYX/X1Y\bX3X\'X6X:X7X*YY\bYYYX\'YX3X\'X-X)YX\'X/Y\nX\'YX7X(X9YY\nYX4YX1X\'Y\nYYYYYYX\'X4X1YX)X1X&Y\nX3YX4Y\nX7YX\'X0X\'X\'YYYX4X(X\'X(X*X9X(X1X1X-YX)YX\'YX)Y\nYY\bYYX1YX2YYYX)X#X-YX/YYX(Y\nY\nX9YY\nX5Y\bX1X)X7X1Y\nYX4X\'X1YX,Y\bX\'YX#X.X1Y\tYX9YX\'X\'X(X-X+X9X1Y\bX6X(X4YYYX3X,YX(YX\'YX.X\'YX/YX*X\'X(YYY\nX)X(X/Y\bYX#Y\nX6X\'Y\nY\bX,X/YX1Y\nYYX*X(X*X#YX6YYX7X(X.X\'YX+X1X(X\'X1YX\'YX6YX\'X-YY\tYYX3YX#Y\nX\'YX1X/Y\bX/X#YYX\'X/Y\nYX\'X\'YX\'YYX9X1X6X*X9YYX/X\'X.YYYYY\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\b\t\n\v\f\r\r\f\v\n\t\b\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\b\0\b\0\b\0\b\0\0\0\0\0\0\0\0\0resourcescountriesquestionsequipmentcommunityavailablehighlightDTD/xhtmlmarketingknowledgesomethingcontainerdirectionsubscribeadvertisecharacter" value="</select>Australia" class="situationauthorityfollowingprimarilyoperationchallengedevelopedanonymousfunction functionscompaniesstructureagreement" title="potentialeducationargumentssecondarycopyrightlanguagesexclusivecondition</form>\r\nstatementattentionBiography} else {\nsolutionswhen the Analyticstemplatesdangeroussatellitedocumentspublisherimportantprototypeinfluence»</effectivegenerallytransformbeautifultransportorganizedpublishedprominentuntil thethumbnailNational .focus();over the migrationannouncedfooter">\nexceptionless thanexpensiveformationframeworkterritoryndicationcurrentlyclassNamecriticismtraditionelsewhereAlexanderappointedmaterialsbroadcastmentionedaffiliate</option>treatmentdifferent/default.Presidentonclick="biographyotherwisepermanentFranC\'aisHollywoodexpansionstandards</style>\nreductionDecember preferredCambridgeopponentsBusiness confusion>\n<title>presentedexplaineddoes not worldwideinterfacepositionsnewspaper</table>\nmountainslike the essentialfinancialselectionaction="/abandonedEducationparseInt(stabilityunable to\nrelationsNote thatefficientperformedtwo yearsSince thethereforewrapper">alternateincreasedBattle ofperceivedtrying tonecessaryportrayedelectionsElizabethdiscoveryinsurances.length;legendaryGeographycandidatecorporatesometimesservices.inheritedCommunityreligiouslocationsCommitteebuildingsthe worldno longerbeginningreferencecannot befrequencytypicallyinto the relative;recordingpresidentinitiallytechniquethe otherit can beexistenceunderlinethis timetelephoneitemscopepracticesadvantage);return For otherprovidingdemocracyboth the extensivesufferingsupportedcomputers functionpracticalsaid thatit may beEnglish\nsuspectedmargin: 0spiritual\n\nmicrosoftgraduallydiscussedhe becameexecutivejquery.jshouseholdconfirmedpurchasedliterallydestroyedup to thevariationremainingit is notcenturiesJapanese among thecompletedalgorithminterestsrebellionundefinedencourageresizableinvolvingsensitiveuniversalprovision(althoughfeaturingconducted), which continued-header">February numerous overflow:componentfragmentsexcellentcolspan="technicalnear the Advanced source ofexpressedHong Kong Facebookmultiple mechanismelevationoffensive\n\tsponsoreddocument.or "there arethose whomovementsprocessesdifficultsubmittedrecommendconvincedpromoting" width=".replace(classicalcoalitionhis firstdecisionsassistantindicatedevolution-wrapper"enough toalong thedelivered--\x3e\r\n\x3c!--American protectedNovember substanceautomaticaspect ofAmong theconnectedestimatesAir Forcesystem ofobjectiveimmediatemaking itpaintingsconqueredare stillproceduregrowth ofheaded byEuropean divisionsmoleculesfranchiseintentionattractedchildhoodalso useddedicatedsingaporedegree offather ofconflicts

\ncame fromwere usednote thatreceivingExecutiveeven moreaccess tocommanderPoliticalmusiciansdeliciousprisonersadvent ofUTF-8" />ContactSouthern bgcolor="series of. It was in Europepermittedvalidate.appearingofficialsseriously-languageinitiatedextendinglong-terminflationsuch thatgetCookiemarked byimplementbut it isincreasesdown the requiringdependent--\x3e\n\x3c!-- interviewWith the copies ofconsensuswas builtVenezuela(formerlythe statepersonnelstrategicfavour ofinventionWikipediacontinentvirtuallywhich wasprincipleComplete identicalshow thatprimitiveaway frommolecularpreciselydissolvedUnder theversion="> span id="sought tobelow thesurviving}his deathas in thecaused bypartiallyexisting using thewas givena list oflevels ofnotion ofOfficial dismissedscientistresemblesduplicateexplosiverecoveredall othergalleries{padding:people ofregion ofaddressesassociateimg alt="in modernshould bemethod ofreportingtimestampneeded tothe Greatregardingseemed toviewed asimpact onidea thatthe Worldheight ofexpandingThese arecurrent">carefullymaintainscharge ofClassicaladdressedpredictedownership\ndepend onsearch">\npieces ofcompetingReferencetennesseewhich has version= <gives thehistorianvalue="">padding:0view thattogether,the most was foundsubset ofattack onchildren,points ofpersonal position:allegedlyClevelandwas laterand afterare givenwas stillscrollingdesign ofmakes themuch lessAmericans.\n\nAfter , but theMuseum oflouisiana(from theminnesotaparticlesa processDominicanvolume ofreturningdefensive00px|righmade frommouseover" style="states of(which iscontinuesFranciscobuilding without awith somewho woulda form ofa part ofbefore itknown as Serviceslocation and oftenmeasuringand it ispaperbackvalues of\r\n= window.determineer" played byand early</center>from thisthe threepower andof "innerHTML<a href="y:inline;Church ofthe eventvery highofficial -height: content="/cgi-bin/to createafrikaansesperantofranC\'aislatvieE!ulietuviE3D\feE!tinaD\reE!tina`9`8`8"f%f,h*g.\0d=e-g9i+e-mj5-l4d8:d;\0d9\bh.!g.f:g,h.0f,h(h+e\r\0f\re\n!e(d:hg=f\b?e0d:\'d?1d9i(e:g\t\bg$>fh!\ff&i(h=f <h?d8\0f-%f/d;e.i*\fh/g e\'ed<f0f\r.e:f6\bh49h\0e\ne,e.$h.(h.:e\f:f71e3e8f-f>e(e\fd:,e8e$\'e-&gh6\nf%h6\ng.!ged?!f/g=serviciosartC-culoargentinabarcelonacualquierpublicadoproductospolC-ticarespuestawikipediasiguientebC:squedacomunidadseguridadprincipalpreguntascontenidorespondervenezuelaproblemasdiciembrerelaciC3nnoviembresimilaresproyectosprogramasinstitutoactividadencuentraeconomC-aimC!genescontactardescargarnecesarioatenciC3ntelC)fonocomisiC3ncancionescapacidadencontraranC!lisisfavoritostC)rminosprovinciaetiquetaselementosfuncionesresultadocarC!cterpropiedadprincipionecesidadmunicipalcreaciC3ndescargaspresenciacomercialopinionesejercicioeditorialsalamancagonzC!lezdocumentopelC-cularecientesgeneralestarragonaprC!cticanovedadespropuestapacientestC)cnicasobjetivoscontactos`$.`%`$`$2`$?`$`$9`%\b`$`$`$/`$>`$8`$>`$%`$`$5`$`$0`$9`%`$`%\v`$\b`$`%`$`$0`$9`$>`$,`$>`$&`$`$9`$>`$8`$-`%\0`$9`%`$`$0`$9`%\0`$.`%\b`$`$&`$?`$(`$,`$>`$$diplodocs`$8`$.`$/`$0`%`$*`$(`$>`$.`$*`$$`$>`$+`$?`$0`$`$8`$$`$$`$0`$9`$2`%\v`$`$9`%`$`$,`$>`$0`$&`%`$6`$9`%`$\b`$`%`$2`$/`$&`$?`$`$>`$.`$5`%`$,`$$`%\0`$(`$,`%\0`$`$.`%\f`$$`$8`$>`$2`$2`%`$`$`%\t`$,`$.`$&`$&`$$`$%`$>`$(`$9`%\0`$6`$9`$0`$`$2`$`$`$-`%\0`$(`$`$0`$*`$>`$8`$0`$>`$$`$`$?`$`$\t`$8`%`$`$/`%\0`$9`%`$`$`$`%`$`%\0`$.`$`%\v`$`$`$>`$0`$`$-`%\0`$`$/`%`$$`%`$.`$5`%\v`$`$&`%`$`$`$`$0`$`$8`%`$.`%`$2`$2`$`$>`$9`$>`$2`$\n`$*`$0`$`$>`$0`$`$8`$>`$&`%`$0`$`$?`$8`$&`$?`$2`$,`$`$&`$,`$(`$>`$9`%`$`$2`$>`$`$`%\0`$$`$,`$`$(`$.`$?`$2`$`$8`%`$`$(`%`$(`$/`$>`$`%`$2`$2`%\t`$`$-`$>`$`$0`%`$2`$`$`$9`$0`$>`$.`$2`$`%`$*`%`$`$9`$>`$%`$`$8`%\0`$8`$9`%\0`$`$2`$>`$ `%\0`$`$9`$>`$`$&`%`$0`$$`$9`$$`$8`$>`$$`$/`$>`$&`$`$/`$>`$*`$>`$`$`%\f`$(`$6`$>`$.`$&`%`$`$/`$9`%\0`$0`$>`$/`$`%`$&`$2`$`%\0categoriesexperience\r\nCopyright javascriptconditionseverything

\nmembershiplinguisticpx;paddingphilosophyassistanceuniversityfacilitiesrecognizedpreferenceif (typeofmaintainedvocabularyhypothesis.submit();&nbsp;annotationbehind theFoundationpublisher"assumptionintroducedcorruptionscientistsexplicitlyinstead ofdimensions onClick="considereddepartmentoccupationsoon afterinvestmentpronouncedidentifiedexperimentManagementgeographic" height="link rel=".replace(/depressionconferencepunishmenteliminatedresistanceadaptationoppositionwell knownsupplementdeterminedh1 class="0px;marginmechanicalstatisticscelebratedGovernment\n\nDuring tdevelopersartificialequivalentoriginatedCommissionattachment\r\nabsolute; supportingextremely mainstream popularityemployment\r\n colspan="\n conversionabout the

integrated" lang="enPortuguesesubstituteindividualimpossiblemultimediaalmost allpx solid #apart fromsubject toin Englishcriticizedexcept forguidelinesoriginallyremarkablethe secondh2 class="collection\r\nfunctionvisibilitythe use ofvolunteersattractionunder the threatened*\nevaluationemphasizedaccessiblesuccessionalong withMeanwhile,industries
has becomeaspects ofTelevisionsufficientbasketballboth sidescontinuingan articleadventureshis mothermanchesterprinciplesparticularcommentaryeffects ofdecided topublishersJournal ofdifficultyfacilitateacceptablestyle.css"\tfunction innovation>Copyrightsituationswould havebusinessesDictionarystatementsoften usedpersistentin Januarycomprising\n\tdiplomaticcontainingperformingextensionsmay not beconcept of onclick="It is alsofinancial making theLuxembourgadditionalare calledengaged in"script");but it waselectroniconsubmit="\n\x3c!-- End electricalofficiallysuggestiontop of theunlike theAustralianOriginallyreferences\n\r\nrecognisedinitializelimited toAlexandriaretirementAdventuresfour years\n\n<!-- increasingdecorationh3 class="origins ofobligationregulationclassified(function(advantagesbeing the historiansthe publicmany yearswhich wereover time,synonymouscontent">\npresumablyhis familyuserAgent.unexpectedincluding challengeda minorityundefined"belongs totaken fromin Octoberposition: said to bereligious Federation rowspan="only a fewmeant thatled to the--\x3e\r\n
Archbishop class="nobeing usedapproachesprivilegesnoscript>\nresults inmay be theEaster eggmechanismsreasonablePopulationCollectionselected">noscript>\r/index.phparrival of-jssdk\'));managed toincompletecasualtiescompletionChristiansSeptember arithmeticproceduresmight haveProductionit appearsPhilosophyfriendshipleading togiving thetoward theguaranteeddocumentedcolor:#000video gamecommissionreflectingchange theassociatedsans-serifonkeypress; padding:He was theunderlyingtypically , and the srcElementsuccessivesince the should be networkingaccountinguse of thelower thanshows that\n\t\tcomplaintscontinuousquantitiesastronomerhe did notdue to itsapplied toan averageefforts tothe futureattempt toTherefore,capabilityRepublicanwas formedElectronickilometerschallengespublishingthe formerindigenousdirectionssubsidiaryconspiracydetails ofand in theaffordablesubstancesreason forconventionitemtype="absolutelysupposedlyremained aattractivetravellingseparatelyfocuses onelementaryapplicablefound thatstylesheetmanuscriptstands for no-repeat(sometimesCommercialin Americaundertakenquarter ofan examplepersonallyindex.php?\npercentagebest-knowncreating a" dir="ltrLieutenant\n
is said tostructuralreferendummost oftena separate->\n
\r - - + -
@@ -54,18 +51,27 @@