mirror of
https://github.com/JustAnimeCore/JustAnime.git
synced 2026-04-17 13:51:44 +00:00
resolved servers
This commit is contained in:
14
.env.example
14
.env.example
@@ -1,15 +1,3 @@
|
|||||||
#Refer https://github.com/itzzzme/anime-api to host your backend API
|
|
||||||
VITE_API_URL=<your_hosted_api>/api
|
VITE_API_URL=<your_hosted_api>/api
|
||||||
|
|
||||||
#Refer this gist to setup proxy server https://gist.github.com/itzzzme/180813be2c7b45eedc8ce8344c8dea3b
|
|
||||||
VITE_PROXY_URL=<proxy_server_name>/?url=
|
|
||||||
|
|
||||||
#Refer https://github.com/itzzzme/m3u8proxy to host you m3u8 proxy server though it's optional but if you don't set it up you may get CORS error for some servers if you set up from the given repo then only the url structure will look like this
|
|
||||||
VITE_M3U8_PROXY_URL=<m3u8_proxy_server_name>/m3u8-proxy?url=
|
VITE_M3U8_PROXY_URL=<m3u8_proxy_server_name>/m3u8-proxy?url=
|
||||||
|
VITE_HD_1_PROXY_URL=<hd_1_anime_paradise>/m3u8-proxy?url=
|
||||||
#totaly optional / if you don't want to setup worker just change the code of getQtip.utils.js following the pattern of any other utils file
|
|
||||||
VITE_WORKER_URL=https://worker1.workers.dev,https://worker2.workers.dev,https://worker3.workers.dev,...
|
|
||||||
|
|
||||||
VITE_BASE_IFRAME_URL=https://megaplay.buzz/stream/s-2
|
|
||||||
|
|
||||||
VITE_BASE_IFRAME_URL_2=https://vidwish.live/stream/s-2
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable no-empty */
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
@@ -28,20 +30,22 @@ import website_name from "@/src/config/website";
|
|||||||
import getChapterStyles from "./getChapterStyle";
|
import getChapterStyles from "./getChapterStyle";
|
||||||
import artplayerPluginHlsControl from "artplayer-plugin-hls-control";
|
import artplayerPluginHlsControl from "artplayer-plugin-hls-control";
|
||||||
import artplayerPluginUploadSubtitle from "./artplayerPluginUploadSubtitle";
|
import artplayerPluginUploadSubtitle from "./artplayerPluginUploadSubtitle";
|
||||||
|
import { getTitle } from "@/src/utils/title.utils";
|
||||||
|
|
||||||
Artplayer.LOG_VERSION = false;
|
Artplayer.LOG_VERSION = false;
|
||||||
Artplayer.CONTEXTMENU = false;
|
Artplayer.CONTEXTMENU = false;
|
||||||
|
|
||||||
const KEY_CODES = {
|
const KEY_CODES = {
|
||||||
M: "m",
|
M: "KeyM",
|
||||||
I: "i",
|
I: "KeyI",
|
||||||
F: "f",
|
F: "KeyF",
|
||||||
V: "v",
|
V: "KeyV",
|
||||||
SPACE: " ",
|
SPACE: "Space",
|
||||||
ARROW_UP: "arrowup",
|
SPACE_LEGACY: "Spacebar",
|
||||||
ARROW_DOWN: "arrowdown",
|
ARROW_UP: "ArrowUp",
|
||||||
ARROW_RIGHT: "arrowright",
|
ARROW_DOWN: "ArrowDown",
|
||||||
ARROW_LEFT: "arrowleft",
|
ARROW_RIGHT: "ArrowRight",
|
||||||
|
ARROW_LEFT: "ArrowLeft",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Player({
|
export default function Player({
|
||||||
@@ -59,17 +63,15 @@ export default function Player({
|
|||||||
animeInfo,
|
animeInfo,
|
||||||
episodeNum,
|
episodeNum,
|
||||||
streamInfo,
|
streamInfo,
|
||||||
serverName,
|
activeServerName,
|
||||||
}) {
|
}) {
|
||||||
const artRef = useRef(null);
|
const artRef = useRef(null);
|
||||||
const leftAtRef = useRef(0);
|
const leftAtRef = useRef(0);
|
||||||
|
const boundKeydownRef = useRef(null);
|
||||||
const proxy = import.meta.env.VITE_PROXY_URL;
|
const proxy = import.meta.env.VITE_PROXY_URL;
|
||||||
const m3u8proxy = import.meta.env.VITE_M3U8_PROXY_URL?.split(",") || [];
|
const m3u8proxy = import.meta.env.VITE_M3U8_PROXY_URL?.split(",") || [];
|
||||||
const m3u8proxyHD3 = import.meta.env.VITE_M3U8_PROXY_HD3;
|
|
||||||
const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
|
const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
|
||||||
episodes?.findIndex(
|
episodes?.findIndex((episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId)
|
||||||
(episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -80,11 +82,10 @@ export default function Player({
|
|||||||
setCurrentEpisodeIndex(newIndex);
|
setCurrentEpisodeIndex(newIndex);
|
||||||
}
|
}
|
||||||
}, [episodeId, episodes]);
|
}, [episodeId, episodes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const applyChapterStyles = () => {
|
const applyChapterStyles = () => {
|
||||||
const existingStyles = document.querySelectorAll(
|
const existingStyles = document.querySelectorAll("style[data-chapter-styles]");
|
||||||
"style[data-chapter-styles]"
|
|
||||||
);
|
|
||||||
existingStyles.forEach((style) => style.remove());
|
existingStyles.forEach((style) => style.remove());
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
styleElement.setAttribute("data-chapter-styles", "true");
|
styleElement.setAttribute("data-chapter-styles", "true");
|
||||||
@@ -112,18 +113,13 @@ export default function Player({
|
|||||||
|
|
||||||
art.on("destroy", () => hls.destroy());
|
art.on("destroy", () => hls.destroy());
|
||||||
|
|
||||||
// hls.on(Hls.Events.ERROR, (event, data) => {
|
|
||||||
// console.error("HLS.js error:", data);
|
|
||||||
// });
|
|
||||||
video.addEventListener("timeupdate", () => {
|
video.addEventListener("timeupdate", () => {
|
||||||
const currentTime = Math.round(video.currentTime);
|
const currentTime = Math.round(video.currentTime);
|
||||||
const duration = Math.round(video.duration);
|
const duration = Math.round(video.duration);
|
||||||
if (duration > 0 && currentTime >= duration) {
|
if (duration > 0 && currentTime >= duration) {
|
||||||
art.pause();
|
art.pause();
|
||||||
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
||||||
playNext(
|
playNext(episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]);
|
||||||
episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -135,9 +131,7 @@ export default function Player({
|
|||||||
if (duration > 0 && currentTime >= duration) {
|
if (duration > 0 && currentTime >= duration) {
|
||||||
art.pause();
|
art.pause();
|
||||||
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
|
||||||
playNext(
|
playNext(episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]);
|
||||||
episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -157,29 +151,76 @@ export default function Player({
|
|||||||
return chapters;
|
return chapters;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isEditableElement = (el) => {
|
||||||
|
if (!el) return false;
|
||||||
|
const tagName = el.tagName?.toLowerCase();
|
||||||
|
if (tagName === "input" || tagName === "textarea" || el.isContentEditable) return true;
|
||||||
|
if (el.closest) {
|
||||||
|
const editable = el.closest("input, textarea, [contenteditable='true']");
|
||||||
|
return !!editable;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const handleKeydown = (event, art) => {
|
const handleKeydown = (event, art) => {
|
||||||
const tagName = event.target.tagName.toLowerCase();
|
const container = artRef.current;
|
||||||
|
if (!container || !art) return;
|
||||||
|
|
||||||
if (tagName === "input" || tagName === "textarea") return;
|
const target = event.target;
|
||||||
|
if (isEditableElement(target)) return;
|
||||||
|
|
||||||
switch (event.key.toLowerCase()) {
|
const eventIsInsidePlayer =
|
||||||
|
container.contains(target) || container.contains(document.activeElement);
|
||||||
|
|
||||||
|
if (!eventIsInsidePlayer) return;
|
||||||
|
|
||||||
|
const code = event.code;
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
case KEY_CODES.M:
|
case KEY_CODES.M:
|
||||||
art.muted = !art.muted;
|
art.muted = !art.muted;
|
||||||
break;
|
break;
|
||||||
case KEY_CODES.I:
|
case KEY_CODES.I:
|
||||||
art.pip = !art.pip;
|
art.pip = !art.pip;
|
||||||
break;
|
break;
|
||||||
case KEY_CODES.F:
|
case KEY_CODES.F: {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
art.fullscreen = !art.fullscreen;
|
|
||||||
|
const container = artRef.current;
|
||||||
|
const doc = document;
|
||||||
|
const fsEl =
|
||||||
|
doc.fullscreenElement ||
|
||||||
|
doc.webkitFullscreenElement ||
|
||||||
|
doc.mozFullScreenElement ||
|
||||||
|
doc.msFullscreenElement;
|
||||||
|
|
||||||
|
if (fsEl && (fsEl === container || container.contains(fsEl))) {
|
||||||
|
if (doc.exitFullscreen) doc.exitFullscreen();
|
||||||
|
else if (doc.webkitExitFullscreen) doc.webkitExitFullscreen();
|
||||||
|
else if (doc.mozCancelFullScreen) doc.mozCancelFullScreen();
|
||||||
|
else if (doc.msExitFullscreen) doc.msExitFullscreen();
|
||||||
|
} else {
|
||||||
|
if (container.requestFullscreen) container.requestFullscreen();
|
||||||
|
else if (container.webkitRequestFullscreen) container.webkitRequestFullscreen();
|
||||||
|
else if (container.mozRequestFullScreen) container.mozRequestFullScreen();
|
||||||
|
else if (container.msRequestFullscreen) container.msRequestFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
art.fullscreen = !art.fullscreen;
|
||||||
|
} catch (e) {
|
||||||
|
// ignore if art not available
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case KEY_CODES.V:
|
case KEY_CODES.V:
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
art.subtitle.show = !art.subtitle.show;
|
art.subtitle.show = !art.subtitle.show;
|
||||||
break;
|
break;
|
||||||
case KEY_CODES.SPACE:
|
case KEY_CODES.SPACE:
|
||||||
|
case KEY_CODES.SPACE_LEGACY:
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
art.playing ? art.pause() : art.play();
|
art.playing ? art.pause() : art.play();
|
||||||
@@ -211,20 +252,34 @@ export default function Player({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!streamUrl || !artRef.current) return;
|
if (!streamUrl || !artRef.current) return;
|
||||||
|
|
||||||
const iframeUrl = streamInfo?.streamingLink?.iframe;
|
const iframeUrl = streamInfo?.streamingLink?.iframe;
|
||||||
const headers = {};
|
const headers = {
|
||||||
headers.referer = new URL(iframeUrl).origin + "/";
|
referer: iframeUrl ? new URL(iframeUrl).origin + "/" : window.location.origin + "/",
|
||||||
const finalProxy = (serverName === "hd-3" && m3u8proxyHD3)
|
};
|
||||||
? m3u8proxyHD3
|
|
||||||
|
const container = artRef.current;
|
||||||
|
let fullscreenRefocusTimeout = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!container.hasAttribute("tabindex")) container.setAttribute("tabindex", "0");
|
||||||
|
else {
|
||||||
|
const current = parseInt(container.getAttribute("tabindex"), 10);
|
||||||
|
if (isNaN(current) || current < 0) container.setAttribute("tabindex", "0");
|
||||||
|
}
|
||||||
|
container.style.outline = "none";
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
const hd1Proxy = import.meta.env.VITE_HD_1_PROXY_URL;
|
||||||
|
const currentProxy = (activeServerName === "HD-1" && hd1Proxy)
|
||||||
|
? hd1Proxy
|
||||||
: m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)];
|
: m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)];
|
||||||
console.log(finalProxy +
|
|
||||||
encodeURIComponent(streamUrl) +
|
|
||||||
"&headers=" +
|
|
||||||
encodeURIComponent(JSON.stringify(headers)));
|
|
||||||
|
|
||||||
const art = new Artplayer({
|
const art = new Artplayer({
|
||||||
url:
|
url:
|
||||||
finalProxy +
|
currentProxy +
|
||||||
encodeURIComponent(streamUrl) +
|
encodeURIComponent(streamUrl) +
|
||||||
"&headers=" +
|
"&headers=" +
|
||||||
encodeURIComponent(JSON.stringify(headers)),
|
encodeURIComponent(JSON.stringify(headers)),
|
||||||
@@ -245,8 +300,8 @@ export default function Player({
|
|||||||
fastForward: true,
|
fastForward: true,
|
||||||
aspectRatio: true,
|
aspectRatio: true,
|
||||||
moreVideoAttr: {
|
moreVideoAttr: {
|
||||||
crossOrigin: 'anonymous',
|
crossOrigin: "anonymous",
|
||||||
preload: 'none',
|
preload: "none",
|
||||||
playsInline: true,
|
playsInline: true,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -374,13 +429,55 @@ export default function Player({
|
|||||||
},
|
},
|
||||||
customType: { m3u8: playM3u8 },
|
customType: { m3u8: playM3u8 },
|
||||||
});
|
});
|
||||||
|
|
||||||
art.on("resize", () => {
|
art.on("resize", () => {
|
||||||
art.subtitle.style({
|
art.subtitle.style({
|
||||||
fontSize:
|
fontSize: (art.width > 500 ? art.width * 0.02 : art.width * 0.03) + "px",
|
||||||
(art.width > 500 ? art.width * 0.02 : art.width * 0.03) + "px",
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const refocusIfNeeded = (delay = 30) => {
|
||||||
|
try {
|
||||||
|
if (!container) return;
|
||||||
|
const active = document.activeElement;
|
||||||
|
if (!container.contains(active)) {
|
||||||
|
fullscreenRefocusTimeout = setTimeout(() => {
|
||||||
|
try {
|
||||||
|
container.focus();
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFullscreenChange = () => {
|
||||||
|
if (!document.fullscreenElement && !document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement) {
|
||||||
|
refocusIfNeeded(40);
|
||||||
|
} else {
|
||||||
|
refocusIfNeeded(20);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// vendor event wrappers
|
||||||
|
const fullscreenEvents = [
|
||||||
|
"fullscreenchange",
|
||||||
|
"webkitfullscreenchange",
|
||||||
|
"mozfullscreenchange",
|
||||||
|
"MSFullscreenChange",
|
||||||
|
];
|
||||||
|
fullscreenEvents.forEach((ev) => document.addEventListener(ev, onFullscreenChange));
|
||||||
|
|
||||||
art.on("ready", () => {
|
art.on("ready", () => {
|
||||||
|
try {
|
||||||
|
container.focus();
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
const continueWatchingList = JSON.parse(localStorage.getItem("continueWatching")) || [];
|
const continueWatchingList = JSON.parse(localStorage.getItem("continueWatching")) || [];
|
||||||
const currentEntry = continueWatchingList.find((item) => item.episodeId === episodeId);
|
const currentEntry = continueWatchingList.find((item) => item.episodeId === episodeId);
|
||||||
if (currentEntry?.leftAt) art.currentTime = currentEntry.leftAt;
|
if (currentEntry?.leftAt) art.currentTime = currentEntry.leftAt;
|
||||||
@@ -393,7 +490,15 @@ export default function Player({
|
|||||||
art.layers[website_name].style.opacity = 0;
|
art.layers[website_name].style.opacity = 0;
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
const defaultSubtitle = subtitles?.find((sub) => sub.label.toLowerCase() === "english");
|
const subs = (subtitles || []).map((s) => ({ ...s }));
|
||||||
|
|
||||||
|
for (const sub of subs) {
|
||||||
|
const encodedUrl = encodeURIComponent(sub.file);
|
||||||
|
const encodedHeaders = encodeURIComponent(JSON.stringify(headers));
|
||||||
|
sub.file = `${proxy}${encodedUrl}&headers=${encodedHeaders}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSubtitle = subs?.find((sub) => sub.label.toLowerCase() === "english");
|
||||||
if (defaultSubtitle) {
|
if (defaultSubtitle) {
|
||||||
art.subtitle.switch(defaultSubtitle.file, {
|
art.subtitle.switch(defaultSubtitle.file, {
|
||||||
name: defaultSubtitle.label,
|
name: defaultSubtitle.label,
|
||||||
@@ -402,12 +507,38 @@ export default function Player({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const skipRanges = [
|
const skipRanges = [
|
||||||
...(intro.start != null && intro.end != null ? [[intro.start + 1, intro.end - 1]] : []),
|
...(intro?.start != null && intro?.end != null ? [[intro.start + 1, intro.end - 1]] : []),
|
||||||
...(outro.start != null && outro.end != null ? [[outro.start + 1, outro.end]] : []),
|
...(outro?.start != null && outro?.end != null ? [[outro.start + 1, outro.end]] : []),
|
||||||
];
|
];
|
||||||
autoSkipIntro && art.plugins.add(autoSkip(skipRanges));
|
autoSkipIntro && art.plugins.add(autoSkip(skipRanges));
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => handleKeydown(event, art));
|
const boundKeydown = (event) => handleKeydown(event, art);
|
||||||
|
boundKeydownRef.current = boundKeydown;
|
||||||
|
document.addEventListener("keydown", boundKeydown);
|
||||||
|
|
||||||
|
const focusOnPointerDown = () => {
|
||||||
|
try {
|
||||||
|
container.focus();
|
||||||
|
} catch (err) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
container.addEventListener("pointerdown", focusOnPointerDown, { passive: true });
|
||||||
|
|
||||||
|
const onWindowFocus = () => refocusIfNeeded(30);
|
||||||
|
window.addEventListener("focus", onWindowFocus);
|
||||||
|
|
||||||
|
art.on("destroy", () => {
|
||||||
|
try {
|
||||||
|
document.removeEventListener("keydown", boundKeydown);
|
||||||
|
} catch (e) { }
|
||||||
|
try {
|
||||||
|
container.removeEventListener("pointerdown", focusOnPointerDown);
|
||||||
|
} catch (e) { }
|
||||||
|
try {
|
||||||
|
window.removeEventListener("focus", onWindowFocus);
|
||||||
|
} catch (e) { }
|
||||||
|
});
|
||||||
|
|
||||||
art.subtitle.style({
|
art.subtitle.style({
|
||||||
fontSize: (art.width > 500 ? art.width * 0.02 : art.width * 0.03) + "px",
|
fontSize: (art.width > 500 ? art.width * 0.02 : art.width * 0.03) + "px",
|
||||||
@@ -420,6 +551,7 @@ export default function Player({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $rewind = art.layers["rewind"];
|
const $rewind = art.layers["rewind"];
|
||||||
const $forward = art.layers["forward"];
|
const $forward = art.layers["forward"];
|
||||||
Artplayer.utils.isMobile &&
|
Artplayer.utils.isMobile &&
|
||||||
@@ -438,10 +570,11 @@ export default function Player({
|
|||||||
art.layers["forwardIcon"].style.opacity = 0;
|
art.layers["forwardIcon"].style.opacity = 0;
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
if (subtitles?.length > 0) {
|
|
||||||
|
if (subs?.length > 0) {
|
||||||
const defaultEnglishSub =
|
const defaultEnglishSub =
|
||||||
subtitles.find((sub) => sub.label.toLowerCase() === "english" && sub.default) ||
|
subs.find((sub) => sub.label.toLowerCase() === "english" && sub.default) ||
|
||||||
subtitles.find((sub) => sub.label.toLowerCase() === "english");
|
subs.find((sub) => sub.label.toLowerCase() === "english");
|
||||||
|
|
||||||
art.setting.add({
|
art.setting.add({
|
||||||
name: "captions",
|
name: "captions",
|
||||||
@@ -459,7 +592,7 @@ export default function Player({
|
|||||||
return !item.switch;
|
return !item.switch;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...subtitles.map((sub) => ({
|
...subs.map((sub) => ({
|
||||||
default: sub.label.toLowerCase() === "english" && sub === defaultEnglishSub,
|
default: sub.label.toLowerCase() === "english" && sub === defaultEnglishSub,
|
||||||
html: sub.label,
|
html: sub.label,
|
||||||
url: sub.file,
|
url: sub.file,
|
||||||
@@ -477,32 +610,42 @@ export default function Player({
|
|||||||
if (art && art.destroy) {
|
if (art && art.destroy) {
|
||||||
art.destroy(false);
|
art.destroy(false);
|
||||||
}
|
}
|
||||||
document.removeEventListener("keydown", handleKeydown);
|
|
||||||
const continueWatching = JSON.parse(localStorage.getItem("continueWatching")) || [];
|
|
||||||
const newEntry = {
|
|
||||||
id: animeInfo?.id,
|
|
||||||
data_id: animeInfo?.data_id,
|
|
||||||
episodeId,
|
|
||||||
episodeNum,
|
|
||||||
adultContent: animeInfo?.adultContent,
|
|
||||||
poster: animeInfo?.poster,
|
|
||||||
title: animeInfo?.title,
|
|
||||||
japanese_title: animeInfo?.japanese_title,
|
|
||||||
leftAt: leftAtRef.current,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!newEntry.data_id) return;
|
fullscreenEvents.forEach((ev) => document.removeEventListener(ev, onFullscreenChange));
|
||||||
|
if (boundKeydownRef.current) {
|
||||||
const existingIndex = continueWatching.findIndex((item) => item.data_id === newEntry.data_id);
|
try {
|
||||||
if (existingIndex !== -1) {
|
document.removeEventListener("keydown", boundKeydownRef.current);
|
||||||
continueWatching[existingIndex] = newEntry;
|
} catch (e) { }
|
||||||
} else {
|
boundKeydownRef.current = null;
|
||||||
continueWatching.push(newEntry);
|
}
|
||||||
|
if (fullscreenRefocusTimeout) clearTimeout(fullscreenRefocusTimeout);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const continueWatching = JSON.parse(localStorage.getItem("continueWatching")) || [];
|
||||||
|
const newEntry = {
|
||||||
|
id: animeInfo?.id,
|
||||||
|
data_id: animeInfo?.data_id,
|
||||||
|
episodeId,
|
||||||
|
episodeNum,
|
||||||
|
adultContent: animeInfo?.adultContent,
|
||||||
|
poster: animeInfo?.poster,
|
||||||
|
title: getTitle(animeInfo, "EN"),
|
||||||
|
japanese_title: getTitle(animeInfo, "JP"),
|
||||||
|
leftAt: leftAtRef.current,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!newEntry.data_id) return;
|
||||||
|
|
||||||
|
const filtered = continueWatching.filter((item) => item.data_id !== newEntry.data_id);
|
||||||
|
filtered.unshift(newEntry);
|
||||||
|
localStorage.setItem("continueWatching", JSON.stringify(filtered));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to save continueWatching:", err);
|
||||||
}
|
}
|
||||||
localStorage.setItem("continueWatching", JSON.stringify(continueWatching));
|
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [streamUrl, subtitles, intro, outro]);
|
}, [streamUrl, subtitles, intro, outro]);
|
||||||
|
|
||||||
return <div ref={artRef} className="w-full h-full"></div>;
|
return <div ref={artRef} className="w-full h-full" />;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import getAnimeInfo from "@/src/utils/getAnimeInfo.utils";
|
import getAnimeInfo from "@/src/utils/getAnimeInfo.utils";
|
||||||
@@ -113,58 +114,91 @@ export const useWatch = (animeId, initialEpisodeId) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!episodeId || !episodes || isServerFetchInProgress.current) return;
|
if (!episodeId || !episodes || isServerFetchInProgress.current) return;
|
||||||
|
|
||||||
|
let mounted = true;
|
||||||
|
const controller = new AbortController();
|
||||||
|
isServerFetchInProgress.current = true;
|
||||||
|
setServerLoading(true);
|
||||||
|
|
||||||
const fetchServers = async () => {
|
const fetchServers = async () => {
|
||||||
isServerFetchInProgress.current = true;
|
|
||||||
setServerLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const data = await getServers(animeId, episodeId);
|
const data = await getServers(animeId, episodeId, { signal: controller.signal });
|
||||||
console.log(data);
|
if (!mounted) return;
|
||||||
|
|
||||||
const filteredServers = data?.filter(
|
const filteredServers = data?.filter(
|
||||||
(server) =>
|
(server) =>
|
||||||
server.serverName === "HD-1" ||
|
server.serverName === "HD-1" ||
|
||||||
server.serverName === "HD-2" ||
|
server.serverName === "HD-2" ||
|
||||||
server.serverName === "HD-3"
|
// server.serverName === "HD-3" ||
|
||||||
);
|
server.serverName === "Vidstreaming" ||
|
||||||
if (filteredServers.some((s) => s.type === "sub")) {
|
server.serverName === "Vidcloud" ||
|
||||||
filteredServers.push({
|
server.serverName === "DouVideo"
|
||||||
type: "sub",
|
) || [];
|
||||||
data_id: "69696969",
|
|
||||||
server_id: "41",
|
let serversList = [...filteredServers];
|
||||||
serverName: "HD-4",
|
|
||||||
});
|
if (serversList.some((s) => s.type === "sub")) {
|
||||||
|
if (!serversList.some((s) => s.serverName === "HD-4" && s.type === "sub")) {
|
||||||
|
serversList.push({
|
||||||
|
type: "sub",
|
||||||
|
data_id: "69696968",
|
||||||
|
server_id: "41",
|
||||||
|
serverName: "HD-4",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (filteredServers.some((s) => s.type === "dub")) {
|
|
||||||
filteredServers.push({
|
if (serversList.some((s) => s.type === "dub")) {
|
||||||
type: "dub",
|
if (!serversList.some((s) => s.serverName === "HD-4" && s.type === "dub")) {
|
||||||
data_id: "96969696",
|
serversList.push({
|
||||||
server_id: "42",
|
type: "dub",
|
||||||
serverName: "HD-4",
|
data_id: "96969696",
|
||||||
});
|
server_id: "42",
|
||||||
|
serverName: "HD-4",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedServerName = localStorage.getItem("server_name");
|
const savedServerName = localStorage.getItem("server_name");
|
||||||
const savedServerType = localStorage.getItem("server_type");
|
const savedServerType = localStorage.getItem("server_type");
|
||||||
const initialServer =
|
|
||||||
filteredServers.find(s => s.serverName === savedServerName && s.type === savedServerType) ||
|
|
||||||
filteredServers.find(s => s.serverName === savedServerName) ||
|
|
||||||
filteredServers.find(s => s.type === savedServerType && ["HD-1", "HD-2", "HD-3", "HD-4"].includes(s.serverName)) ||
|
|
||||||
filteredServers.find(s => s.serverName === "HD-2") ||
|
|
||||||
filteredServers[0];
|
|
||||||
|
|
||||||
setServers(filteredServers);
|
const initialServer =
|
||||||
|
serversList.find(s => s.serverName === savedServerName && s.type === savedServerType) ||
|
||||||
|
serversList.find(s => s.serverName === savedServerName) ||
|
||||||
|
serversList.find(s => s.serverName === "HD-2") ||
|
||||||
|
serversList.find(
|
||||||
|
s =>
|
||||||
|
s.type === savedServerType &&
|
||||||
|
["HD-1", "HD-2", "HD-3", "HD-4", "Vidstreaming", "Vidcloud", "DouVideo"].includes(s.serverName)
|
||||||
|
) ||
|
||||||
|
serversList[0];
|
||||||
|
|
||||||
|
setServers(serversList);
|
||||||
setActiveServerType(initialServer?.type);
|
setActiveServerType(initialServer?.type);
|
||||||
setActiveServerName(initialServer?.serverName);
|
setActiveServerName(initialServer?.serverName);
|
||||||
setActiveServerId(initialServer?.data_id);
|
setActiveServerId(initialServer?.data_id);
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Error fetching servers:", error);
|
if (err?.name === "AbortError") return;
|
||||||
setError(error.message || "An error occurred.");
|
console.error("Error fetching servers:", err);
|
||||||
|
if (mounted) setError(err.message || "An error occurred.");
|
||||||
} finally {
|
} finally {
|
||||||
setServerLoading(false);
|
if (mounted) {
|
||||||
isServerFetchInProgress.current = false;
|
setServerLoading(false);
|
||||||
|
isServerFetchInProgress.current = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchServers();
|
fetchServers();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
try { controller.abort(); } catch (e) {
|
||||||
|
// console.log(e.message);
|
||||||
|
}
|
||||||
|
isServerFetchInProgress.current = false;
|
||||||
|
};
|
||||||
}, [episodeId, episodes]);
|
}, [episodeId, episodes]);
|
||||||
|
|
||||||
// Fetch stream info only when episodeId, activeServerId, and servers are ready
|
// Fetch stream info only when episodeId, activeServerId, and servers are ready
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -175,11 +209,10 @@ export const useWatch = (animeId, initialEpisodeId) => {
|
|||||||
isStreamFetchInProgress.current
|
isStreamFetchInProgress.current
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
if (
|
const iframeServers = [];
|
||||||
(activeServerName?.toLowerCase() === "hd-1" || activeServerName?.toLowerCase() === "hd-4")
|
// const iframeServers = ["hd-1", "hd-4", "vidstreaming", "vidcloud", "douvideo"];
|
||||||
&&
|
|
||||||
!serverLoading
|
if (iframeServers.includes(activeServerName?.toLowerCase()) && !serverLoading) {
|
||||||
) {
|
|
||||||
setBuffering(false);
|
setBuffering(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useLocation, useParams, Link, useNavigate } from "react-router-dom";
|
|||||||
import { useLanguage } from "@/src/context/LanguageContext";
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
import { useWatch } from "@/src/hooks/useWatch";
|
import { useWatch } from "@/src/hooks/useWatch";
|
||||||
import BouncingLoader from "@/src/components/ui/bouncingloader/Bouncingloader";
|
import BouncingLoader from "@/src/components/ui/bouncingloader/Bouncingloader";
|
||||||
import IframePlayer from "@/src/components/player/IframePlayer";
|
|
||||||
import Episodelist from "@/src/components/episodelist/Episodelist";
|
import Episodelist from "@/src/components/episodelist/Episodelist";
|
||||||
import website_name from "@/src/config/website";
|
import website_name from "@/src/config/website";
|
||||||
import Sidecard from "@/src/components/sidecard/Sidecard";
|
import Sidecard from "@/src/components/sidecard/Sidecard";
|
||||||
@@ -200,48 +199,45 @@ export default function Watch() {
|
|||||||
<div ref={playerRef} className="player w-full h-fit bg-black flex flex-col rounded-xl overflow-hidden shadow-2xl">
|
<div ref={playerRef} className="player w-full h-fit bg-black flex flex-col rounded-xl overflow-hidden shadow-2xl">
|
||||||
<div ref={videoContainerRef} className="w-full relative aspect-video bg-black">
|
<div ref={videoContainerRef} className="w-full relative aspect-video bg-black">
|
||||||
{!buffering ? (
|
{!buffering ? (
|
||||||
["hd-1", "hd-4"].includes(activeServerName.toLowerCase()) ? (
|
<Player
|
||||||
<IframePlayer
|
streamUrl={streamUrl}
|
||||||
episodeId={episodeId}
|
subtitles={subtitles}
|
||||||
servertype={activeServerType}
|
intro={intro}
|
||||||
serverName={activeServerName}
|
outro={outro}
|
||||||
animeInfo={animeInfo}
|
activeServerName={activeServerName}
|
||||||
episodeNum={activeEpisodeNum}
|
thumbnail={thumbnail}
|
||||||
episodes={episodes}
|
autoSkipIntro={autoSkipIntro}
|
||||||
playNext={setEpisodeId}
|
autoPlay={autoPlay}
|
||||||
autoNext={autoNext}
|
autoNext={autoNext}
|
||||||
/>
|
episodeId={episodeId}
|
||||||
) : (
|
episodes={episodes}
|
||||||
<Player
|
playNext={setEpisodeId}
|
||||||
streamUrl={streamUrl}
|
animeInfo={animeInfo}
|
||||||
subtitles={subtitles}
|
episodeNum={activeEpisodeNum}
|
||||||
intro={intro}
|
streamInfo={streamInfo}
|
||||||
outro={outro}
|
/>
|
||||||
serverName={activeServerName.toLowerCase()}
|
|
||||||
thumbnail={thumbnail}
|
|
||||||
autoSkipIntro={autoSkipIntro}
|
|
||||||
autoPlay={autoPlay}
|
|
||||||
autoNext={autoNext}
|
|
||||||
episodeId={episodeId}
|
|
||||||
episodes={episodes}
|
|
||||||
playNext={setEpisodeId}
|
|
||||||
animeInfo={animeInfo}
|
|
||||||
episodeNum={activeEpisodeNum}
|
|
||||||
streamInfo={streamInfo}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute inset-0 flex justify-center items-center bg-black">
|
<div className="absolute inset-0 flex justify-center items-center bg-black bg-opacity-50">
|
||||||
<BouncingLoader />
|
<BouncingLoader />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!buffering && !activeServerType && (
|
<p className="text-center underline font-medium text-[15px] absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 pointer-events-none">
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50 backdrop-blur-sm pointer-events-none">
|
{!buffering && !streamInfo ? (
|
||||||
<p className="text-center text-gray-300 font-medium p-4 max-w-sm">
|
servers ? (
|
||||||
Streaming server seems to be down. Please try another server or reload the page.
|
<>
|
||||||
</p>
|
Probably this server is down, try other servers
|
||||||
</div>
|
<br />
|
||||||
)}
|
Either reload or try again after sometime
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Probably streaming server is down
|
||||||
|
<br />
|
||||||
|
Either reload or try again after sometime
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-[#121212]">
|
<div className="bg-[#121212]">
|
||||||
|
|||||||
14
src/utils/title.utils.js
Normal file
14
src/utils/title.utils.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const getTitle = (item, language) => {
|
||||||
|
if (!item) return "N/A";
|
||||||
|
|
||||||
|
const title = item.title;
|
||||||
|
const japanese_title = item.japanese_title || item.japaneseTitle;
|
||||||
|
|
||||||
|
if (language === "EN") {
|
||||||
|
if (typeof title === "string") return title;
|
||||||
|
return title?.english || title?.romaji || "N/A";
|
||||||
|
} else {
|
||||||
|
// JP
|
||||||
|
return japanese_title || (typeof title === "string" ? title : title?.romaji || title?.english || "N/A");
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user