init
This commit is contained in:
611
animex/view.html
Normal file
611
animex/view.html
Normal file
@@ -0,0 +1,611 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>Episode Player</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"/>
|
||||
<style>
|
||||
:root {
|
||||
--accent-color: #FF9500;
|
||||
--background-color: #0A0A0A;
|
||||
--surface-color: #141414;
|
||||
--text-primary: #FFFFFF;
|
||||
--text-secondary: #A0A0A0;
|
||||
--border-color: rgba(255, 255, 255, 0.1);
|
||||
--indicator-left: 4px;
|
||||
--indicator-width: 0px;
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--background-color);
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--text-primary);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* --- Loading Screen --- */
|
||||
#loading-container {
|
||||
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
||||
z-index: 200; background-color: #000; background-size: cover;
|
||||
background-position: center; display: flex; align-items: flex-end;
|
||||
justify-content: center; transition: opacity 0.3s ease;
|
||||
}
|
||||
#loading-container::before {
|
||||
content: ''; position: absolute; top: 0; left: 0; width: 100%;
|
||||
height: 100%; background: linear-gradient(to top, rgba(0,0,0,1) 20%, rgba(0,0,0,0.5) 50%, rgba(0,0,0,0) 100%);
|
||||
}
|
||||
#loading-info {
|
||||
position: relative; text-align: left; padding: 1.5rem 1.5rem 3rem;
|
||||
max-width: 90%; max-height: 50%; overflow-y: auto; box-sizing: border-box;
|
||||
}
|
||||
#loading-info h2 { margin: 0 0 0.5rem 0; font-size: 1.5rem; }
|
||||
#loading-info p { margin: 0; font-size: 0.9rem; color: var(--text-secondary); line-height: 1.5; }
|
||||
.loader-spinner {
|
||||
border: 4px solid rgba(255, 255, 255, 0.2); border-radius: 50%;
|
||||
border-top: 4px solid var(--accent-color); width: 40px; height: 40px;
|
||||
animation: spin 1s linear infinite; position: absolute; top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
@keyframes spin { 100% { transform: translate(-50%, -50%) rotate(360deg); } }
|
||||
|
||||
/* --- Player & Content --- */
|
||||
#player-container {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
background: #000;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
#player-container iframe {
|
||||
width: 100%; height: 100%; border: none; background: #111;
|
||||
}
|
||||
#error-message {
|
||||
color: #ff6b6b; text-align: center; padding: 2rem 1rem; font-size: 1.1rem;
|
||||
display: flex; align-items: center; justify-content: center; height: 100%;
|
||||
}
|
||||
|
||||
#content-container {
|
||||
flex-grow: 1;
|
||||
padding: 1rem 1.2rem;
|
||||
background: var(--background-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* --- Header Info --- */
|
||||
#header-info {
|
||||
text-align: left;
|
||||
}
|
||||
#series-title { font-size: 1.3rem; font-weight: 700; line-height: 1.3; }
|
||||
#meta-line { font-size: 0.9rem; color: var(--text-secondary); margin-top: 0.25rem; }
|
||||
|
||||
/* --- Controls --- */
|
||||
#controls-main {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
gap: 0.8rem;
|
||||
align-items: center;
|
||||
}
|
||||
.control-btn {
|
||||
background: var(--surface-color);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
height: 44px; /* Match height of other controls */
|
||||
width: 44px;
|
||||
}
|
||||
.control-btn:hover {
|
||||
background: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
#source-controls {
|
||||
display: flex;
|
||||
background-color: var(--surface-color);
|
||||
border-radius: 12px;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* SUB/DUB Toggle */
|
||||
.subdub-toggle {
|
||||
position: relative; display: flex;
|
||||
list-style: none; margin: 0; padding: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.subdub-toggle::before {
|
||||
content: ''; position: absolute; top: 0; left: var(--indicator-left);
|
||||
width: var(--indicator-width); height: 100%;
|
||||
background: var(--accent-color);
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
.subdub-btn {
|
||||
position: relative; /* Ensure z-index stacking context */
|
||||
background: none; border: none; color: var(--text-secondary);
|
||||
font-size: 0.9em; font-weight: 600;
|
||||
padding: 8px 16px; /* Add horizontal padding for spacing */
|
||||
border-radius: 8px; cursor: pointer;
|
||||
outline: none; z-index: 2; transition: color 0.3s ease;
|
||||
flex-grow: 1; text-align: center;
|
||||
}
|
||||
.subdub-btn.active { color: var(--text-primary); }
|
||||
|
||||
/* Source Selector */
|
||||
.source-selector-wrapper {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
}
|
||||
#source-changer-select {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
color: var(--text-primary);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 28px 8px 12px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
font-family: 'Inter', sans-serif;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
text-align-last: center; /* For Firefox */
|
||||
}
|
||||
.source-selector-wrapper::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid var(--text-secondary);
|
||||
pointer-events: none;
|
||||
}
|
||||
#source-changer-select:focus { outline: none; }
|
||||
|
||||
/* Next Episode Button */
|
||||
#next-episode-btn {
|
||||
display: block; background: var(--accent-color); color: #fff;
|
||||
font-weight: 600; font-size: 0.95em; border: none; border-radius: 12px;
|
||||
padding: 12px 20px; cursor: pointer; transition: background 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#next-episode-btn:hover { background: #e6850a; }
|
||||
#series-over-msg {
|
||||
text-align: center; font-weight: 600; font-size: 0.95em;
|
||||
color: var(--accent-color); opacity: 0.8; padding: 12px 0;
|
||||
}
|
||||
|
||||
/* Synopsis */
|
||||
#synopsis-container {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
#synopsis-container h4 {
|
||||
margin: 0 0 0.5rem 0; font-weight: 600; opacity: 0.9;
|
||||
}
|
||||
#synopsis-container p {
|
||||
font-size: 0.9em; line-height: 1.6; color: var(--text-secondary); margin: 0;
|
||||
}
|
||||
|
||||
/* --- Landscape Mode --- */
|
||||
@media (orientation: landscape) and (max-height: 600px) {
|
||||
body { flex-direction: row; }
|
||||
#player-container {
|
||||
width: 100vw; height: 100vh;
|
||||
position: fixed; top: 0; left: 0; z-index: 100;
|
||||
}
|
||||
#content-container {
|
||||
display: none; /* Hide by default in landscape */
|
||||
}
|
||||
/* Add specific landscape controls if needed, or rely on player's native controls */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="loading-container">
|
||||
<div class="loader-spinner"></div>
|
||||
<div id="loading-info">
|
||||
<h2 id="loading-title">—</h2>
|
||||
<p id="loading-synopsis">—</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="player-container"></div>
|
||||
|
||||
<div id="content-container">
|
||||
<div id="header-info">
|
||||
<div id="series-title">—</div>
|
||||
<div id="meta-line">
|
||||
<span>Type: <span id="series-type">—</span></span> |
|
||||
<span>Year: <span id="series-year">—</span></span> |
|
||||
<span>Episode: <span id="episode-number">—</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="controls-main">
|
||||
<div id="source-controls">
|
||||
<ul class="subdub-toggle" id="subdub-toggle">
|
||||
<li><button class="subdub-btn active" id="sub-option-btn" type="button">SUB</button></li>
|
||||
<li><button class="subdub-btn" id="dub-option-btn" type="button">DUB</button></li>
|
||||
</ul>
|
||||
<div class="source-selector-wrapper">
|
||||
<select id="source-changer-select" name="source-changer"></select>
|
||||
</div>
|
||||
</div>
|
||||
<button id="reload-iframe-btn" class="control-btn" title="Reload Player">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
<div id="next-episode-container"></div>
|
||||
</div>
|
||||
|
||||
<div id="synopsis-container">
|
||||
<h4>Synopsis</h4>
|
||||
<p id="synopsis-text">—</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const jikanId = params.get('id');
|
||||
const episode = params.get('ep');
|
||||
|
||||
// DOM Elements
|
||||
const playerContainer = document.getElementById('player-container');
|
||||
const loadingContainer = document.getElementById('loading-container');
|
||||
const subOptionBtn = document.getElementById('sub-option-btn');
|
||||
const dubOptionBtn = document.getElementById('dub-option-btn');
|
||||
const sourceChangerSelect = document.getElementById('source-changer-select');
|
||||
const nextEpisodeContainer = document.getElementById('next-episode-container');
|
||||
const synopsisText = document.getElementById('synopsis-text');
|
||||
|
||||
// App State
|
||||
const serverIp = localStorage.getItem('extension_server_ip');
|
||||
const profileId = localStorage.getItem("currentProfileId");
|
||||
let currentProfile = null;
|
||||
let userPref = localStorage.getItem('animeDubPref') || 'sub';
|
||||
let currentModule = 'default';
|
||||
let fetchedAnimeData = null; // Declare here to make it accessible
|
||||
|
||||
// --- Main Initialization ---
|
||||
try {
|
||||
// Initial Checks
|
||||
if (!jikanId || !episode) {
|
||||
throw new Error('Missing "id" or "ep" parameter.');
|
||||
}
|
||||
if (!serverIp) {
|
||||
throw new Error('Extension server IP not set. Please configure it.');
|
||||
}
|
||||
|
||||
loadingContainer.style.display = 'flex';
|
||||
|
||||
await fetchProfile();
|
||||
fetchedAnimeData = await fetchAnimeData(); // Assign to the new variable
|
||||
updateUI(fetchedAnimeData);
|
||||
await logWatchHistory(jikanId, episode, fetchedAnimeData.title);
|
||||
await populateSourceChanger();
|
||||
|
||||
if (userPref === 'dub') {
|
||||
dubOptionBtn.classList.add('active');
|
||||
subOptionBtn.classList.remove('active');
|
||||
} else {
|
||||
subOptionBtn.classList.add('active');
|
||||
dubOptionBtn.classList.remove('active');
|
||||
}
|
||||
|
||||
await loadIframe(userPref === 'dub', currentModule, fetchedAnimeData); // Pass fetchedAnimeData
|
||||
setupEventListeners();
|
||||
setTimeout(moveActiveIndicator, 150);
|
||||
|
||||
} catch (error) {
|
||||
console.error("A critical error occurred during initialization:", error);
|
||||
loadingContainer.style.display = 'none';
|
||||
playerContainer.innerHTML = `<div id="error-message">Failed to load player: ${error.message}</div>`;
|
||||
}
|
||||
|
||||
|
||||
// --- Functions ---
|
||||
|
||||
async function fetchProfile() {
|
||||
if (!profileId) return;
|
||||
try {
|
||||
const response = await fetch(`/profiles/${profileId}`);
|
||||
if (response.ok) currentProfile = await response.json();
|
||||
else console.warn("Could not load active profile.");
|
||||
} catch (e) {
|
||||
console.error("Error fetching profile:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function logWatchHistory(malId, episodeNumber, seriesTitle) {
|
||||
if (!currentProfile || !seriesTitle || seriesTitle === 'Unknown') return;
|
||||
const query = new URLSearchParams({
|
||||
mal_id: malId,
|
||||
episode_number: episodeNumber,
|
||||
series_title: seriesTitle,
|
||||
season_number: 1,
|
||||
});
|
||||
try {
|
||||
await fetch(`/profiles/${currentProfile.id}/watch-history?${query.toString()}`, { method: 'POST' });
|
||||
console.log(`Logged watch history for Ep. ${episodeNumber}.`);
|
||||
} catch (error) {
|
||||
console.error("Failed to log watch history:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function moveActiveIndicator() {
|
||||
const activeButton = document.querySelector('.subdub-btn.active');
|
||||
if (activeButton) {
|
||||
const indicator = document.querySelector('.subdub-toggle');
|
||||
const listRect = indicator.getBoundingClientRect();
|
||||
const buttonRect = activeButton.getBoundingClientRect();
|
||||
const leftPosition = buttonRect.left - listRect.left;
|
||||
indicator.style.setProperty('--indicator-left', `${leftPosition}px`);
|
||||
indicator.style.setProperty('--indicator-width', `${activeButton.offsetWidth}px`);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAnimeData() {
|
||||
let title = 'Unknown', type = 'N/A', year = 'N/A', totalEpisodes = null, synopsis = 'No synopsis available.';
|
||||
let episodeSynopsis = 'No episode synopsis available.', episodeThumbnail = null;
|
||||
|
||||
try {
|
||||
const [jikanInfoResp, jikanEpisodesResp, kitsuMapResp] = await Promise.all([
|
||||
fetch(`https://api.jikan.moe/v4/anime/${jikanId}`),
|
||||
fetch(`https://api.jikan.moe/v4/anime/${jikanId}/episodes`),
|
||||
fetch(`http://${serverIp}:7275/map/mal/${jikanId}`)
|
||||
]);
|
||||
|
||||
if (jikanInfoResp.ok) {
|
||||
const data = (await jikanInfoResp.json()).data || {};
|
||||
title = data.title_english || data.title || 'Unknown';
|
||||
type = data.type || 'N/A';
|
||||
year = data.year || data.aired?.prop?.from?.year || 'N/A';
|
||||
}
|
||||
|
||||
if (jikanEpisodesResp.ok) {
|
||||
const data = await jikanEpisodesResp.json();
|
||||
totalEpisodes = data.pagination?.last_visible_page > 1 ? null : data.data?.length;
|
||||
}
|
||||
|
||||
if (kitsuMapResp.ok) {
|
||||
const mapData = await kitsuMapResp.json();
|
||||
const kitsuId = mapData.kitsu_id;
|
||||
const [kitsuAnimeResp, kitsuEpisodeResp] = await Promise.all([
|
||||
fetch(`https://kitsu.io/api/edge/anime/${kitsuId}`),
|
||||
fetch(`https://kitsu.io/api/edge/anime/${kitsuId}/episodes?filter[number]=${episode}`)
|
||||
]);
|
||||
if (kitsuAnimeResp.ok) {
|
||||
const kitsuData = await kitsuAnimeResp.json();
|
||||
if (kitsuData && kitsuData.data && kitsuData.data.attributes) {
|
||||
const attributes = kitsuData.data.attributes;
|
||||
synopsis = attributes.synopsis || synopsis;
|
||||
if (attributes.posterImage?.original) {
|
||||
loadingContainer.style.backgroundImage = `url(${attributes.posterImage.original})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kitsuEpisodeResp.ok) {
|
||||
const episodeData = await kitsuEpisodeResp.json();
|
||||
if (episodeData.data?.length > 0 && episodeData.data[0].attributes) {
|
||||
const episodeAttributes = episodeData.data[0].attributes;
|
||||
episodeSynopsis = episodeAttributes.synopsis || episodeAttributes.description || episodeSynopsis;
|
||||
episodeThumbnail = episodeAttributes.thumbnail?.original;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch some anime data:', err);
|
||||
// Return default data, but the main try/catch will handle the UI
|
||||
}
|
||||
|
||||
return { title, type, year, totalEpisodes, synopsis, episodeSynopsis, episodeThumbnail };
|
||||
}
|
||||
|
||||
async function populateSourceChanger() {
|
||||
try {
|
||||
const response = await fetch(`http://${serverIp}:7275/modules/streaming`);
|
||||
if (!response.ok) throw new Error('Failed to fetch streaming modules');
|
||||
const modules = await response.json();
|
||||
|
||||
sourceChangerSelect.innerHTML = '';
|
||||
const defaultOption = document.createElement('option');
|
||||
defaultOption.textContent = 'Default Source';
|
||||
defaultOption.value = 'default';
|
||||
sourceChangerSelect.appendChild(defaultOption);
|
||||
|
||||
if (modules.length > 0) {
|
||||
modules.forEach(module => {
|
||||
const option = document.createElement('option');
|
||||
option.value = module.id;
|
||||
option.textContent = module.name;
|
||||
sourceChangerSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Could not populate source changer:', error);
|
||||
const errorOption = document.createElement('option');
|
||||
errorOption.textContent = 'Error loading';
|
||||
errorOption.value = 'default';
|
||||
errorOption.disabled = true;
|
||||
sourceChangerSelect.innerHTML = '';
|
||||
sourceChangerSelect.appendChild(errorOption);
|
||||
}
|
||||
}
|
||||
|
||||
function updateUI(data) {
|
||||
const safeData = data || {};
|
||||
document.getElementById('series-title').textContent = safeData.title || '—';
|
||||
document.getElementById('series-type').textContent = safeData.type || 'N/A';
|
||||
document.getElementById('series-year').textContent = safeData.year || 'N/A';
|
||||
document.getElementById('episode-number').textContent = episode || 'N/A';
|
||||
synopsisText.textContent = safeData.episodeSynopsis || 'No synopsis available.';
|
||||
document.getElementById('loading-title').textContent = safeData.title || 'Loading...';
|
||||
document.getElementById('loading-synopsis').textContent = safeData.episodeSynopsis || '';
|
||||
|
||||
if (safeData.episodeThumbnail) {
|
||||
loadingContainer.style.backgroundImage = `url(${safeData.episodeThumbnail})`;
|
||||
}
|
||||
|
||||
const currentEpNum = parseInt(episode, 10);
|
||||
nextEpisodeContainer.innerHTML = ''; // Clear previous button/message
|
||||
if (safeData.totalEpisodes && currentEpNum < safeData.totalEpisodes) {
|
||||
const nextEpBtn = document.createElement('button');
|
||||
nextEpBtn.id = 'next-episode-btn';
|
||||
nextEpBtn.textContent = `Next →`;
|
||||
nextEpBtn.addEventListener('click', () => {
|
||||
params.set('ep', currentEpNum + 1);
|
||||
window.location.search = params.toString();
|
||||
});
|
||||
nextEpisodeContainer.appendChild(nextEpBtn);
|
||||
} else if (safeData.totalEpisodes && currentEpNum >= safeData.totalEpisodes) {
|
||||
const seriesOverMsg = document.createElement('div');
|
||||
seriesOverMsg.id = 'series-over-msg';
|
||||
seriesOverMsg.textContent = 'Series Complete';
|
||||
nextEpisodeContainer.appendChild(seriesOverMsg);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadIframe(dub, preferModule = 'default', animeData) {
|
||||
playerContainer.innerHTML = '';
|
||||
// Don't show loading screen again if it's already visible
|
||||
if (loadingContainer.style.display !== 'flex') {
|
||||
loadingContainer.style.display = 'flex';
|
||||
}
|
||||
|
||||
let iframeSrcURL = `http://${serverIp}:7275/iframe-src?mal_id=${jikanId}&episode=${episode}&dub=${dub}`;
|
||||
if (preferModule && preferModule !== 'default') {
|
||||
iframeSrcURL += `&prefer_module=${encodeURIComponent(preferModule)}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(iframeSrcURL);
|
||||
if (!res.ok) {
|
||||
if (res.status === 404 && preferModule !== 'default') {
|
||||
console.warn(`Module '${preferModule}' failed with 404. Retrying with 'default' module.`);
|
||||
// Clear previous error message if any
|
||||
playerContainer.innerHTML = '';
|
||||
// Retry with default module
|
||||
await loadIframe(dub, 'default', animeData);
|
||||
return; // Stop execution here as the retry is handled
|
||||
} else {
|
||||
throw new Error(`Proxy error ${res.status}`);
|
||||
}
|
||||
}
|
||||
const json = await res.json();
|
||||
if (!json.src) throw new Error('No embed source found from any module');
|
||||
|
||||
// Append to json.src
|
||||
const playerUrl = new URL(json.src, window.location.origin);
|
||||
playerUrl.searchParams.set('series_title', animeData.title);
|
||||
playerUrl.searchParams.set('episode_num', episode); // Ensure episode is always passed explicitly
|
||||
if (animeData.episodeThumbnail) {
|
||||
playerUrl.searchParams.set('thumbnail_url', animeData.episodeThumbnail);
|
||||
}
|
||||
json.src = playerUrl.toString();
|
||||
|
||||
if (json.source_module) {
|
||||
sourceChangerSelect.value = json.source_module;
|
||||
currentModule = json.source_module;
|
||||
}
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-fullscreen');
|
||||
iframe.setAttribute('src', json.src);
|
||||
iframe.setAttribute('allowfullscreen', 'true');
|
||||
iframe.setAttribute('scrolling', 'no');
|
||||
playerContainer.appendChild(iframe);
|
||||
|
||||
// Hide the loading screen as soon as the iframe is added to the DOM.
|
||||
// The iframe itself will show its own loading progress.
|
||||
loadingContainer.style.display = 'none';
|
||||
} catch (err) {
|
||||
console.error('Error loading player:', err);
|
||||
loadingContainer.style.display = 'none';
|
||||
playerContainer.innerHTML = `<div id="error-message">Could not load player: ${err.message}. Please try another source or type.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
const reloadIframeBtn = document.getElementById('reload-iframe-btn');
|
||||
|
||||
subOptionBtn.addEventListener('click', async () => {
|
||||
if (userPref !== 'sub') {
|
||||
userPref = 'sub';
|
||||
localStorage.setItem('animeDubPref', 'sub');
|
||||
subOptionBtn.classList.add('active');
|
||||
dubOptionBtn.classList.remove('active');
|
||||
moveActiveIndicator();
|
||||
// FIX: Pass fetchedAnimeData as the 3rd argument
|
||||
await loadIframe(false, currentModule, fetchedAnimeData);
|
||||
}
|
||||
});
|
||||
|
||||
dubOptionBtn.addEventListener('click', async () => {
|
||||
if (userPref !== 'dub') {
|
||||
userPref = 'dub';
|
||||
localStorage.setItem('animeDubPref', 'dub');
|
||||
dubOptionBtn.classList.add('active');
|
||||
subOptionBtn.classList.remove('active');
|
||||
moveActiveIndicator();
|
||||
// FIX: Pass fetchedAnimeData as the 3rd argument
|
||||
await loadIframe(true, currentModule, fetchedAnimeData);
|
||||
}
|
||||
});
|
||||
|
||||
sourceChangerSelect.addEventListener('change', async () => {
|
||||
const selectedModule = sourceChangerSelect.value;
|
||||
if (selectedModule !== currentModule) {
|
||||
currentModule = selectedModule;
|
||||
// FIX: Pass fetchedAnimeData as the 3rd argument
|
||||
await loadIframe(userPref === 'dub', currentModule, fetchedAnimeData);
|
||||
}
|
||||
});
|
||||
|
||||
reloadIframeBtn.addEventListener('click', () => {
|
||||
const iframe = playerContainer.querySelector('iframe');
|
||||
if (iframe) {
|
||||
iframe.src = iframe.src;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => setTimeout(moveActiveIndicator, 100));
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (!event.data || event.data.type !== 'animex-next-episode') return;
|
||||
const currentEp = parseInt(episode, 10);
|
||||
if (isNaN(currentEp)) return;
|
||||
params.set('ep', currentEp + 1);
|
||||
window.location.search = params.toString();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user