From 1a85c2571690ba592ac5183d5eadaf9846fe532b Mon Sep 17 00:00:00 2001 From: Factiven Date: Mon, 25 Sep 2023 00:44:40 +0700 Subject: Update v4.1.0 (#79) * Update v4.1.0 * Update pages/_app.js --- components/watch/player/artplayer.js | 325 ++++++++++++++ .../watch/player/component/controls/quality.js | 15 + .../watch/player/component/controls/subtitle.js | 3 + components/watch/player/component/overlay.js | 57 +++ components/watch/player/playerComponent.js | 478 +++++++++++++++++++++ components/watch/player/utils/getZoroSource.js | 0 components/watch/primary/details.js | 189 ++++++++ components/watch/secondary/episodeLists.js | 143 ++++++ 8 files changed, 1210 insertions(+) create mode 100644 components/watch/player/artplayer.js create mode 100644 components/watch/player/component/controls/quality.js create mode 100644 components/watch/player/component/controls/subtitle.js create mode 100644 components/watch/player/component/overlay.js create mode 100644 components/watch/player/playerComponent.js create mode 100644 components/watch/player/utils/getZoroSource.js create mode 100644 components/watch/primary/details.js create mode 100644 components/watch/secondary/episodeLists.js (limited to 'components/watch') diff --git a/components/watch/player/artplayer.js b/components/watch/player/artplayer.js new file mode 100644 index 0000000..4eb766d --- /dev/null +++ b/components/watch/player/artplayer.js @@ -0,0 +1,325 @@ +import { useEffect, useRef } from "react"; +import Artplayer from "artplayer"; +import Hls from "hls.js"; +import { useWatchProvider } from "../../../lib/hooks/watchPageProvider"; +import { seekBackward, seekForward } from "./component/overlay"; +import artplayerPluginHlsQuality from "artplayer-plugin-hls-quality"; + +export default function NewPlayer({ + playerRef, + option, + getInstance, + provider, + defSub, + defSize, + subtitles, + subSize, + res, + quality, + ...rest +}) { + const artRef = useRef(null); + const { setTheaterMode, setPlayerState, setAutoPlay } = useWatchProvider(); + + function playM3u8(video, url, art) { + if (Hls.isSupported()) { + if (art.hls) art.hls.destroy(); + const hls = new Hls(); + hls.loadSource(url); + hls.attachMedia(video); + art.hls = hls; + art.on("destroy", () => hls.destroy()); + } else if (video.canPlayType("application/vnd.apple.mpegurl")) { + video.src = url; + } else { + art.notice.show = "Unsupported playback format: m3u8"; + } + } + + useEffect(() => { + const art = new Artplayer({ + ...option, + container: artRef.current, + type: "m3u8", + customType: { + m3u8: playM3u8, + }, + ...(provider === "zoro" && { + subtitle: { + url: `${defSub}`, + // type: "vtt", + encoding: "utf-8", + default: true, + name: "English", + escape: false, + style: { + color: "#FFFF", + fontSize: `${defSize?.size}`, + fontFamily: localStorage.getItem("font") + ? localStorage.getItem("font") + : "Arial", + textShadow: localStorage.getItem("subShadow") + ? JSON.parse(localStorage.getItem("subShadow")).value + : "0px 0px 10px #000000", + }, + }, + }), + + plugins: [ + artplayerPluginHlsQuality({ + // Show quality in setting + setting: true, + + // Get the resolution text from level + getResolution: (level) => level.height + "P", + + // I18n + title: "Quality", + auto: "Auto", + }), + ], + + settings: [ + // provider === "gogoanime" && + { + html: "Autoplay Next", + icon: '', + tooltip: "ON/OFF", + switch: localStorage.getItem("autoplay") === "true" ? true : false, + onSwitch: function (item) { + // setPlayNext(!item.switch); + localStorage.setItem("autoplay", !item.switch); + return !item.switch; + }, + }, + { + html: "Autoplay Video", + icon: '', + // icon: '', + tooltip: "ON/OFF", + switch: + localStorage.getItem("autoplay_video") === "true" ? true : false, + onSwitch: function (item) { + setAutoPlay(!item.switch); + localStorage.setItem("autoplay_video", !item.switch); + return !item.switch; + }, + }, + { + html: "Alternative Quality", + width: 250, + tooltip: `${res}`, + selector: quality?.alt, + icon: '', + onSelect: function (item) { + art.switchQuality(item.url, item.html); + localStorage.setItem("quality", item.html); + return item.html; + }, + }, + { + html: "Server", + width: 250, + tooltip: `${quality?.server[0].html}`, + icon: '', + selector: quality?.server, + onSelect: function (item) { + art.switchQuality(item.url, item.html); + localStorage.setItem("quality", item.html); + return item.html; + }, + }, + provider === "zoro" && { + html: "Subtitles", + icon: '', + width: 300, + tooltip: "Settings", + selector: [ + { + html: "Display", + icon: '', + tooltip: "Show", + switch: true, + onSwitch: function (item) { + item.tooltip = item.switch ? "Hide" : "Show"; + art.subtitle.show = !item.switch; + return !item.switch; + }, + }, + { + html: "Font Size", + icon: '', + selector: subSize, + onSelect: function (item) { + if (item.html === "Small") { + art.subtitle.style({ fontSize: "16px" }); + localStorage.setItem( + "subSize", + JSON.stringify({ + size: "16px", + html: "Small", + }) + ); + } else if (item.html === "Medium") { + art.subtitle.style({ fontSize: "36px" }); + localStorage.setItem( + "subSize", + JSON.stringify({ + size: "36px", + html: "Medium", + }) + ); + } else if (item.html === "Large") { + art.subtitle.style({ fontSize: "56px" }); + localStorage.setItem( + "subSize", + JSON.stringify({ + size: "56px", + html: "Large", + }) + ); + } + }, + }, + { + html: "Language", + icon: '', + tooltip: "English", + selector: [...subtitles], + onSelect: function (item) { + art.subtitle.switch(item.url, { + name: item.html, + }); + return item.html; + }, + }, + { + html: "Font Family", + tooltip: localStorage.getItem("font") + ? localStorage.getItem("font") + : "Arial", + selector: [ + { html: "Arial" }, + { html: "Comic Sans MS" }, + { html: "Verdana" }, + { html: "Tahoma" }, + { html: "Trebuchet MS" }, + { html: "Times New Roman" }, + { html: "Georgia" }, + { html: "Impact " }, + { html: "Andalé Mono" }, + { html: "Palatino" }, + { html: "Baskerville" }, + { html: "Garamond" }, + { html: "Courier New" }, + { html: "Brush Script MT" }, + ], + onSelect: function (item) { + art.subtitle.style({ fontFamily: item.html }); + localStorage.setItem("font", item.html); + return item.html; + }, + }, + { + html: "Font Shadow", + tooltip: localStorage.getItem("subShadow") + ? JSON.parse(localStorage.getItem("subShadow")).shadow + : "Default", + selector: [ + { html: "None", value: "none" }, + { + html: "Uniform", + value: + "2px 2px 0px #000, -2px -2px 0px #000, 2px -2px 0px #000, -2px 2px 0px #000", + }, + { html: "Raised", value: "-1px 2px 3px rgba(0, 0, 0, 1)" }, + { html: "Depressed", value: "-2px -3px 3px rgba(0, 0, 0, 1)" }, + { html: "Glow", value: "0 0 10px rgba(0, 0, 0, 0.8)" }, + { + html: "Block", + value: + "-3px 3px 4px rgba(0, 0, 0, 1),2px 2px 4px rgba(0, 0, 0, 1),1px -1px 3px rgba(0, 0, 0, 1),-3px -2px 4px rgba(0, 0, 0, 1)", + }, + ], + onSelect: function (item) { + art.subtitle.style({ textShadow: item.value }); + localStorage.setItem( + "subShadow", + JSON.stringify({ shadow: item.html, value: item.value }) + ); + return item.html; + }, + }, + ], + }, + ].filter(Boolean), + controls: [ + { + name: "theater-button", + index: 11, + position: "right", + tooltip: "Theater (t)", + html: '

', + click: function (...args) { + setPlayerState((prev) => ({ + ...prev, + currentTime: art.currentTime, + isPlaying: art.playing, + })); + setTheaterMode((prev) => !prev); + }, + }, + seekBackward, + seekForward, + ], + }); + + playerRef.current = art; + + art.events.proxy(document, "keydown", (event) => { + // Check if the focus is on an input field or textarea + const isInputFocused = + document.activeElement.tagName === "INPUT" || + document.activeElement.tagName === "TEXTAREA"; + + if (!isInputFocused) { + if (event.key === "f" || event.key === "F") { + art.fullscreen = !art.fullscreen; + } + + if (event.key === "t" || event.key === "T") { + setPlayerState((prev) => ({ + ...prev, + currentTime: art.currentTime, + isPlaying: art.playing, + })); + setTheaterMode((prev) => !prev); + } + } + }); + + art.events.proxy(document, "keypress", (event) => { + // Check if the focus is on an input field or textarea + const isInputFocused = + document.activeElement.tagName === "INPUT" || + document.activeElement.tagName === "TEXTAREA"; + + if (!isInputFocused && event.code === "Space") { + event.preventDefault(); + art.playing ? art.pause() : art.play(); + } + }); + + if (getInstance && typeof getInstance === "function") { + getInstance(art); + } + + return () => { + if (art && art.destroy) { + art.destroy(false); + } + }; + }, []); + + return
; +} diff --git a/components/watch/player/component/controls/quality.js b/components/watch/player/component/controls/quality.js new file mode 100644 index 0000000..08dbd0e --- /dev/null +++ b/components/watch/player/component/controls/quality.js @@ -0,0 +1,15 @@ +import artplayerPluginHlsQuality from "artplayer-plugin-hls-quality"; + +export const QualityPlugins = [ + artplayerPluginHlsQuality({ + // Show quality in setting + setting: true, + + // Get the resolution text from level + getResolution: (level) => level.height + "P", + + // I18n + title: "Quality", + auto: "Auto", + }), +]; diff --git a/components/watch/player/component/controls/subtitle.js b/components/watch/player/component/controls/subtitle.js new file mode 100644 index 0000000..02075f7 --- /dev/null +++ b/components/watch/player/component/controls/subtitle.js @@ -0,0 +1,3 @@ +import { useState } from "react"; + +export default function getSubtitles() {} diff --git a/components/watch/player/component/overlay.js b/components/watch/player/component/overlay.js new file mode 100644 index 0000000..1d5ac27 --- /dev/null +++ b/components/watch/player/component/overlay.js @@ -0,0 +1,57 @@ +/** + * @type {import("artplayer/types/icons".Icons)} + */ +export const icons = { + screenshot: + '', + play: '', + pause: + '', + volume: + '', + fullscreenOff: + '', + fullscreenOn: + '', +}; + +export const backButton = { + name: "back-button", + index: 10, + position: "top", + html: "

Komi-san wa, Komyushou desu.

Episode 1

", + // tooltip: "Your Button", + click: function (...args) { + console.info("click", args); + }, + mounted: function (...args) { + console.info("mounted", args); + }, +}; + +export const seekBackward = { + index: 10, + name: "fast-rewind", + position: "left", + html: '', + tooltip: "Backward 5s", + click: function () { + art.backward = 5; + }, +}; + +export const seekForward = { + index: 11, + name: "fast-forward", + position: "left", + html: '', + tooltip: "Forward 5s", + click: function () { + art.forward = 5; + }, +}; + +// /** +// * @type {import("artplayer/types/component").ComponentOption} +// */ +// export const diff --git a/components/watch/player/playerComponent.js b/components/watch/player/playerComponent.js new file mode 100644 index 0000000..d498384 --- /dev/null +++ b/components/watch/player/playerComponent.js @@ -0,0 +1,478 @@ +import React, { useEffect, useState } from "react"; +import NewPlayer from "./artplayer"; +import { icons } from "./component/overlay"; +import { useWatchProvider } from "../../../lib/hooks/watchPageProvider"; +import { useRouter } from "next/router"; +import { useAniList } from "../../../lib/anilist/useAnilist"; + +export function calculateAspectRatio(width, height) { + const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b)); + const divisor = gcd(width, height); + const aspectRatio = `${width / divisor}/${height / divisor}`; + return aspectRatio; +} + +const fontSize = [ + { + html: "Small", + size: "16px", + }, + { + html: "Medium", + size: "36px", + }, + { + html: "Large", + size: "56px", + }, +]; + +export default function PlayerComponent({ + playerRef, + session, + id, + info, + watchId, + proxy, + dub, + timeWatched, + skip, + track, + data, + provider, + className, +}) { + const { + aspectRatio, + setAspectRatio, + playerState, + setPlayerState, + autoplay, + marked, + setMarked, + } = useWatchProvider(); + + const router = useRouter(); + + const { markProgress } = useAniList(session); + + const [url, setUrl] = useState(""); + const [resolution, setResolution] = useState("auto"); + const [source, setSource] = useState([]); + const [subSize, setSubSize] = useState({ size: "16px", html: "Small" }); + const [defSize, setDefSize] = useState(); + const [subtitle, setSubtitle] = useState(); + const [defSub, setDefSub] = useState(); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + const resol = localStorage.getItem("quality"); + const sub = JSON.parse(localStorage.getItem("subSize")); + if (resol) { + setResolution(resol); + } + + if (provider === "zoro") { + const size = fontSize.map((i) => { + const isDefault = !sub ? i.html === "Small" : i.html === sub?.html; + return { + ...(isDefault && { default: true }), + html: i.html, + size: i.size, + }; + }); + + const defSize = size?.find((i) => i?.default === true); + setDefSize(defSize); + setSubSize(size); + } + + async function compiler() { + try { + const referer = JSON.stringify(data?.headers); + const source = data?.sources?.map((items) => { + const isDefault = + provider !== "gogoanime" + ? items.quality === "default" || items.quality === "auto" + : resolution === "auto" + ? items.quality === "default" || items.quality === "auto" + : items.quality === resolution; + return { + ...(isDefault && { default: true }), + html: items.quality === "default" ? "main" : items.quality, + url: `${proxy}/proxy/m3u8/${encodeURIComponent( + String(items.url) + )}/${encodeURIComponent(String(referer))}`, + }; + }); + + const defSource = source?.find((i) => i?.default === true); + + if (defSource) { + setUrl(defSource.url); + } + + if (provider === "zoro") { + const subtitle = data?.subtitles + .filter((subtitle) => subtitle.lang !== "Thumbnails") + .map((subtitle) => { + const isEnglish = subtitle.lang === "English"; + return { + ...(isEnglish && { default: true }), + url: subtitle.url, + html: `${subtitle.lang}`, + }; + }); + + const defSub = data?.subtitles.find((i) => i.lang === "English"); + + setDefSub(defSub?.url); + + setSubtitle(subtitle); + } + + const alt = source?.filter( + (i) => + i?.html !== "main" && + i?.html !== "auto" && + i?.html !== "default" && + i?.html !== "backup" + ); + const server = source?.filter( + (i) => + i?.html === "main" || + i?.html === "auto" || + i?.html === "default" || + i?.html === "backup" + ); + + setSource({ alt, server }); + setLoading(false); + } catch (error) { + console.error(error); + } + } + compiler(); + + return () => { + setUrl(""); + setSource([]); + setSubtitle([]); + setLoading(true); + }; + }, [provider, data]); + + /** + * @param {import("artplayer")} art + */ + function getInstance(art) { + art.on("ready", () => { + const autoplay = localStorage.getItem("autoplay_video") || false; + + if (autoplay === "true" || autoplay === true) { + if (playerState.currentTime === 0) { + art.play(); + } else { + if (playerState.isPlaying) { + art.play(); + } else { + art.pause(); + } + } + } else { + if (playerState.isPlaying) { + art.play(); + } else { + art.pause(); + } + } + art.seek = playerState.currentTime; + }); + + art.on("ready", () => { + if (playerState.currentTime !== 0) return; + const seek = art.storage.get(id); + const seekTime = seek?.timeWatched || 0; + const duration = art.duration; + const percentage = seekTime / duration; + const percentagedb = timeWatched / duration; + + if (subSize) { + art.subtitle.style.fontSize = subSize?.size; + } + + if (percentage >= 0.9 || percentagedb >= 0.9) { + art.currentTime = 0; + console.log("Video started from the beginning"); + } else if (timeWatched) { + art.currentTime = timeWatched; + } else { + art.currentTime = seekTime; + } + }); + + art.on("play", () => { + art.notice.show = ""; + setPlayerState({ ...playerState, isPlaying: true }); + }); + art.on("pause", () => { + art.notice.show = ""; + setPlayerState({ ...playerState, isPlaying: false }); + }); + + art.on("resize", () => { + art.subtitle.style({ + fontSize: art.height * 0.05 + "px", + }); + }); + + let mark = 0; + + art.on("video:timeupdate", async () => { + if (!session) return; + + var currentTime = art.currentTime; + const duration = art.duration; + const percentage = currentTime / duration; + + if (percentage >= 0.9) { + // use >= instead of > + if (mark < 1 && marked < 1) { + mark = 1; + setMarked(1); + markProgress(info.id, track.playing.number); + } + } + }); + + art.on("video:playing", () => { + if (!session) return; + const intervalId = setInterval(async () => { + await fetch("/api/user/update/episode", { + method: "PUT", + body: JSON.stringify({ + name: session?.user?.name, + id: String(info?.id), + watchId: watchId, + title: + track.playing?.title || info.title?.romaji || info.title?.english, + aniTitle: info.title?.romaji || info.title?.english, + image: track.playing?.img || info?.coverImage?.extraLarge, + number: Number(track.playing?.number), + duration: art.duration, + timeWatched: art.currentTime, + provider: provider, + nextId: track.next?.id, + nextNumber: Number(track.next?.number), + dub: dub ? true : false, + }), + }); + // console.log("updating db", { track }); + }, 5000); + + art.on("video:pause", () => { + clearInterval(intervalId); + }); + + art.on("video:ended", () => { + clearInterval(intervalId); + }); + + art.on("destroy", () => { + clearInterval(intervalId); + // console.log("clearing interval"); + }); + }); + + art.on("video:playing", () => { + const interval = setInterval(async () => { + art.storage.set(watchId, { + aniId: String(info.id), + watchId: watchId, + title: + track.playing?.title || info.title?.romaji || info.title?.english, + aniTitle: info.title?.romaji || info.title?.english, + image: track?.playing?.img || info?.coverImage?.extraLarge, + episode: Number(track.playing?.number), + duration: art.duration, + timeWatched: art.currentTime, + provider: provider, + nextId: track?.next?.id, + nextNumber: track?.next?.number, + dub: dub ? true : false, + createdAt: new Date().toISOString(), + }); + }, 5000); + + art.on("video:pause", () => { + clearInterval(interval); + }); + + art.on("video:ended", () => { + clearInterval(interval); + }); + + art.on("destroy", () => { + clearInterval(interval); + }); + }); + + art.on("video:loadedmetadata", () => { + // get raw video width and height + // console.log(art.video.videoWidth, art.video.videoHeight); + const aspect = calculateAspectRatio( + art.video.videoWidth, + art.video.videoHeight + ); + + setAspectRatio(aspect); + }); + + art.on("video:timeupdate", () => { + var currentTime = art.currentTime; + // console.log(art.currentTime); + + if ( + skip?.op && + currentTime >= skip.op.interval.startTime && + currentTime <= skip.op.interval.endTime + ) { + // Add the layer if it's not already added + if (!art.controls["op"]) { + // Remove the other control if it's already added + if (art.controls["ed"]) { + art.controls.remove("ed"); + } + + // Add the control + art.controls.add({ + name: "op", + position: "top", + html: '', + click: function (...args) { + art.seek = skip.op.interval.endTime; + }, + }); + } + } else if ( + skip?.ed && + currentTime >= skip.ed.interval.startTime && + currentTime <= skip.ed.interval.endTime + ) { + // Add the layer if it's not already added + if (!art.controls["ed"]) { + // Remove the other control if it's already added + if (art.controls["op"]) { + art.controls.remove("op"); + } + + // Add the control + art.controls.add({ + name: "ed", + position: "top", + html: '', + click: function (...args) { + art.seek = skip.ed.interval.endTime; + }, + }); + } + } else { + // Remove the controls if they're added + if (art.controls["op"]) { + art.controls.remove("op"); + } + if (art.controls["ed"]) { + art.controls.remove("ed"); + } + } + }); + + art.on("video:ended", () => { + if (!track?.next) return; + if (localStorage.getItem("autoplay") === "true") { + art.controls.add({ + name: "next-button", + position: "top", + html: '
', + click: function (...args) { + if (track?.next) { + router.push( + `/en/anime/watch/${ + info?.id + }/${provider}?id=${encodeURIComponent(track?.next?.id)}&num=${ + track?.next?.number + }${dub ? `&dub=${dub}` : ""}` + ); + } + }, + }); + + const button = document.querySelector(".next-button"); + + function stopTimeout() { + clearTimeout(timeoutId); + button.classList.remove("progress"); + } + + let timeoutId = setTimeout(() => { + art.controls.remove("next-button"); + if (track?.next) { + router.push( + `/en/anime/watch/${info?.id}/${provider}?id=${encodeURIComponent( + track?.next?.id + )}&num=${track?.next?.number}${dub ? `&dub=${dub}` : ""}` + ); + } + }, 7000); + + button.addEventListener("mouseover", stopTimeout); + } + }); + } + + /** + * @type {import("artplayer/types/option").Option} + */ + const option = { + url: url, + title: "title", + autoplay: autoplay ? true : false, + autoSize: false, + fullscreen: true, + autoOrientation: true, + icons: icons, + setting: true, + screenshot: true, + hotkey: true, + }; + + return ( +
+
+ {!loading && track && url && ( + + )} +
+
+ ); +} diff --git a/components/watch/player/utils/getZoroSource.js b/components/watch/player/utils/getZoroSource.js new file mode 100644 index 0000000..e69de29 diff --git a/components/watch/primary/details.js b/components/watch/primary/details.js new file mode 100644 index 0000000..32e1391 --- /dev/null +++ b/components/watch/primary/details.js @@ -0,0 +1,189 @@ +import { useEffect, useState } from "react"; +import { useAniList } from "../../../lib/anilist/useAnilist"; +import Skeleton from "react-loading-skeleton"; +import DisqusComments from "../../disqus"; +import Image from "next/image"; + +export default function Details({ + info, + session, + epiNumber, + description, + id, + onList, + setOnList, + handleOpen, + disqus, +}) { + const [showComments, setShowComments] = useState(false); + const { markPlanning } = useAniList(session); + + function handlePlan() { + if (onList === false) { + markPlanning(info.id); + setOnList(true); + } + } + + useEffect(() => { + setShowComments(false); + }, [id]); + + return ( +
+ {/*
*/} +
+
+ {info ? ( + Anime Cover + ) : ( + + )} +
+
+
+

+ Studios +

+
+ {info ? ( + info.studios?.edges[0].node.name + ) : ( + + )} +
+
+
+ { + session ? handlePlan() : handleOpen(); + }} + className={`w-8 h-8 hover:fill-white text-white hover:cursor-pointer ${ + onList ? "fill-white" : "" + }`} + > + + +
+
+
+
+

+ Status +

+
{info ? info.status : }
+
+
+

+ Titles +

+
+ {info ? ( + <> +
+ {info.title?.romaji || ""} +
+
+ {info.title?.english || ""} +
+
+ {info.title?.native || ""} +
+ + ) : ( + + )} +
+
+
+
+ {/*
*/} +
+ {info && + info.genres?.map((item, index) => ( +
+ {item} +
+ ))} +
+ {/*
*/} +
+ {info && ( +

+ )} +

+ {/* {
} */} + {!showComments && ( +
+ +
+ )} + {showComments && ( +
+ {info && ( +
+ +
+ )} +
+ )} +
+ ); +} diff --git a/components/watch/secondary/episodeLists.js b/components/watch/secondary/episodeLists.js new file mode 100644 index 0000000..8a057ce --- /dev/null +++ b/components/watch/secondary/episodeLists.js @@ -0,0 +1,143 @@ +import Skeleton from "react-loading-skeleton"; +import Image from "next/image"; +import Link from "next/link"; + +export default function EpisodeLists({ + info, + map, + providerId, + watchId, + episode, + artStorage, + dub, +}) { + const progress = info.mediaListEntry?.progress; + return ( +
+

Up Next

+
+ {episode && episode.length > 0 ? ( + map?.some( + (item) => + (item?.img || item?.image) && + !item?.img?.includes("https://s4.anilist.co/") + ) > 0 ? ( + episode.map((item) => { + const time = artStorage?.[item.id]?.timeWatched; + const duration = artStorage?.[item.id]?.duration; + let prog = (time / duration) * 100; + if (prog > 90) prog = 100; + + const mapData = map?.find((i) => i.number === item.number); + return ( + +
+
+ {/*
*/} + Anime Cover + {/* )} */} + + + Episode {item?.number} + + {item.id == watchId && ( +
+ + + +
+ )} +
+
+
+

+ {mapData?.title || info?.title?.romaji} +

+

+ {mapData?.description || `Episode ${item.number}`} +

+
+ + ); + }) + ) : ( + episode.map((item) => { + return ( + + Episode {item.number} + + ); + }) + ) + ) : ( + <> + {[1].map((item) => ( + + ))} + + )} +
+
+ ); +} -- cgit v1.2.3