aboutsummaryrefslogtreecommitdiff
path: root/components/watch/player
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-09-25 00:44:40 +0700
committerGitHub <[email protected]>2023-09-25 00:44:40 +0700
commit1a85c2571690ba592ac5183d5eadaf9846fe532b (patch)
tree3f3552c00cd49c0eeab5275275cf5cf5666e5027 /components/watch/player
parentDelete .github/workflows/deploy.yml (diff)
downloadmoopa-4.1.0.tar.xz
moopa-4.1.0.zip
Update v4.1.0 (#79)v4.1.0
* Update v4.1.0 * Update pages/_app.js
Diffstat (limited to 'components/watch/player')
-rw-r--r--components/watch/player/artplayer.js325
-rw-r--r--components/watch/player/component/controls/quality.js15
-rw-r--r--components/watch/player/component/controls/subtitle.js3
-rw-r--r--components/watch/player/component/overlay.js57
-rw-r--r--components/watch/player/playerComponent.js478
-rw-r--r--components/watch/player/utils/getZoroSource.js0
6 files changed, 878 insertions, 0 deletions
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: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M4.05 16.975q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Zm10 0q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Z"></path></svg>',
+ 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: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M4.05 16.975q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Zm10 0q-.5.35-1.025.05t-.525-.9v-8.25q0-.6.525-.888t1.025.038l6.2 4.15q.45.3.45.825t-.45.825l-6.2 4.15Z"></path></svg>',
+ // icon: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M5.59 7.41L7 6l6 6l-6 6l-1.41-1.41L10.17 12L5.59 7.41m6 0L13 6l6 6l-6 6l-1.41-1.41L16.17 12l-4.58-4.59Z"></path></svg>',
+ 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: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 512 512"><path fill="currentColor" d="M381.25 112a48 48 0 0 0-90.5 0H48v32h242.75a48 48 0 0 0 90.5 0H464v-32ZM176 208a48.09 48.09 0 0 0-45.25 32H48v32h82.75a48 48 0 0 0 90.5 0H464v-32H221.25A48.09 48.09 0 0 0 176 208Zm160 128a48.09 48.09 0 0 0-45.25 32H48v32h242.75a48 48 0 0 0 90.5 0H464v-32h-82.75A48.09 48.09 0 0 0 336 336Z"></path></svg>',
+ 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: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 32 32"><path fill="currentColor" d="m24.6 24.4l2.6 2.6l-2.6 2.6L26 31l4-4l-4-4zm-2.2 0L19.8 27l2.6 2.6L21 31l-4-4l4-4z"></path><circle cx="11" cy="8" r="1" fill="currentColor"></circle><circle cx="11" cy="16" r="1" fill="currentColor"></circle><circle cx="11" cy="24" r="1" fill="currentColor"></circle><path fill="currentColor" d="M24 3H8c-1.1 0-2 .9-2 2v22c0 1.1.9 2 2 2h7v-2H8v-6h18V5c0-1.1-.9-2-2-2zm0 16H8v-6h16v6zm0-8H8V5h16v6z"></path></svg>',
+ 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: '<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24"><path fill="currentColor" d="M4 20q-.825 0-1.413-.588T2 18V6q0-.825.588-1.413T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.588 1.413T20 20H4Zm2-4h8v-2H6v2Zm10 0h2v-2h-2v2ZM6 12h2v-2H6v2Zm4 0h8v-2h-8v2Z"></path></svg>',
+ width: 300,
+ tooltip: "Settings",
+ selector: [
+ {
+ html: "Display",
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="35" height="26" viewBox="0 -960 960 960"><path d="M480.169-341.796q65.754 0 111.894-46.31 46.141-46.309 46.141-112.063t-46.31-111.894q-46.309-46.141-112.063-46.141t-111.894 46.31q-46.141 46.309-46.141 112.063t46.31 111.894q46.309 46.141 112.063 46.141zm-.371-48.307q-45.875 0-77.785-32.112-31.91-32.112-31.91-77.987 0-45.875 32.112-77.785 32.112-31.91 77.987-31.91 45.875 0 77.785 32.112 31.91 32.112 31.91 77.987 0 45.875-32.112 77.785-32.112 31.91-77.987 31.91zm.226 170.102q-130.921 0-239.6-69.821-108.679-69.82-167.556-186.476-2.687-4.574-3.892-10.811Q67.77-493.347 67.77-500t1.205-12.891q1.205-6.237 3.892-10.811Q131.745-640.358 240.4-710.178q108.655-69.821 239.576-69.821t239.6 69.821q108.679 69.82 167.556 186.476 2.687 4.574 3.892 10.811 1.205 6.238 1.205 12.891t-1.205 12.891q-1.205 6.237-3.892 10.811Q828.255-359.642 719.6-289.822q-108.655 69.821-239.576 69.821zM480-500zm-.112 229.744q117.163 0 215.048-62.347Q792.821-394.949 844.308-500q-51.487-105.051-149.26-167.397-97.772-62.347-214.936-62.347-117.163 0-215.048 62.347Q167.179-605.051 115.282-500q51.897 105.051 149.67 167.397 97.772 62.347 214.936 62.347z"></path></svg>',
+ 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: '<svg xmlns="http://www.w3.org/2000/svg" width="35" height="26" viewBox="0 -960 960 960"><path d="M619.861-177.694q-15.655 0-26.475-10.918-10.821-10.918-10.821-26.516v-492.309H415.128q-15.598 0-26.516-10.959-10.918-10.959-10.918-26.615 0-15.655 10.918-26.475 10.918-10.82 26.516-10.82h409.744q15.598 0 26.516 10.958 10.918 10.959 10.918 26.615 0 15.656-10.918 26.476-10.918 10.82-26.516 10.82H657.435v492.309q0 15.598-10.959 26.516-10.959 10.918-26.615 10.918zm-360 0q-15.655 0-26.475-10.918-10.821-10.918-10.821-26.516v-292.309h-87.437q-15.598 0-26.516-10.959-10.918-10.959-10.918-26.615 0-15.655 10.918-26.475 10.918-10.82 26.516-10.82h249.744q15.598 0 26.516 10.958 10.918 10.959 10.918 26.615 0 15.656-10.918 26.476-10.918 10.82-26.516 10.82h-87.437v292.309q0 15.598-10.959 26.516-10.959 10.918-26.615 10.918z"></path></svg>',
+ 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: '<svg xmlns="http://www.w3.org/2000/svg" width="35" height="26" viewBox="0 -960 960 960"><path d="M528.282-110.771q-21.744 0-31.308-14.013t-2.205-34.295l135.952-359.307q5.304-14.793 20.292-25.126 14.988-10.334 31.152-10.334 15.398 0 30.85 10.388 15.451 10.387 20.932 25.125l137.128 357.485q8.025 20.949-1.83 35.513-9.855 14.564-33.24 14.564-10.366 0-19.392-6.616-9.025-6.615-12.72-16.242l-30.997-91.808H594.769l-33.381 91.869q-3.645 9.181-13.148 15.989-9.504 6.808-19.958 6.808zm87.871-179.281h131.64l-64.615-180.717h-2.41l-64.615 180.717zM302.104-608.384q14.406 25.624 31.074 48.184 16.669 22.559 37.643 47.021 41.333-44.128 68.628-90.461t46.038-97.897H111.499q-15.674 0-26.278-10.615-10.603-10.616-10.603-26.308t10.615-26.307q10.616-10.616 26.308-10.616h221.537v-36.923q0-15.692 10.615-26.307 10.616-10.616 26.308-10.616t26.307 10.616q10.616 10.615 10.616 26.307v36.923h221.537q15.692 0 26.307 10.616 10.616 10.615 10.616 26.307 0 15.692-10.616 26.308-10.615 10.615-26.307 10.615h-69.088q-19.912 64.153-53.237 125.74-33.325 61.588-82.341 116.412l89.384 90.974-27.692 75.179-115.486-112.922-158.948 158.947q-10.615 10.616-25.667 10.616-15.051 0-25.666-11.026-11.026-10.615-11.026-25.666 0-15.052 11.026-26.077l161.614-161.358q-24.666-28.308-45.551-57.307-20.884-29-37.756-60.103-10.641-19.871-1.346-34.717t33.038-14.846q9.088 0 18.429 5.73 9.34 5.731 13.956 13.577z"></path></svg>',
+ 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: '<p class="theater"><svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M19 3H1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm-1 12H2V5h16v10z"></path></svg></p>',
+ 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 <div ref={artRef} {...rest}></div>;
+}
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:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 20 20"><path fill="currentColor" d="M10 8a3 3 0 1 0 0 6a3 3 0 0 0 0-6zm8-3h-2.4a.888.888 0 0 1-.789-.57l-.621-1.861A.89.89 0 0 0 13.4 2H6.6c-.33 0-.686.256-.789.568L5.189 4.43A.889.889 0 0 1 4.4 5H2C.9 5 0 5.9 0 7v9c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-8 11a5 5 0 0 1-5-5a5 5 0 1 1 10 0a5 5 0 0 1-5 5zm7.5-7.8a.7.7 0 1 1 0-1.4a.7.7 0 0 1 0 1.4z"></path></svg>',
+ play: '<svg xmlns="http://www.w3.org/2000/svg" width="25px" height="25px" viewBox="0 0 20 20"><path fill="currentColor" d="M15 10.001c0 .299-.305.514-.305.514l-8.561 5.303C5.51 16.227 5 15.924 5 15.149V4.852c0-.777.51-1.078 1.135-.67l8.561 5.305c-.001 0 .304.215.304.514z"></path></svg>',
+ pause:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="25px" height="25px" viewBox="0 0 20 20"><path fill="currentColor" d="M15 3h-2c-.553 0-1 .048-1 .6v12.8c0 .552.447.6 1 .6h2c.553 0 1-.048 1-.6V3.6c0-.552-.447-.6-1-.6zM7 3H5c-.553 0-1 .048-1 .6v12.8c0 .552.447.6 1 .6h2c.553 0 1-.048 1-.6V3.6c0-.552-.447-.6-1-.6z"></path></svg>',
+ volume:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M19 13.805c0 .657-.538 1.195-1.195 1.195H1.533c-.88 0-.982-.371-.229-.822l16.323-9.055C18.382 4.67 19 5.019 19 5.9v7.905z"></path></svg>',
+ fullscreenOff:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M3.28 2.22a.75.75 0 0 0-1.06 1.06L5.44 6.5H2.75a.75.75 0 0 0 0 1.5h4.5A.75.75 0 0 0 8 7.25v-4.5a.75.75 0 0 0-1.5 0v2.69L3.28 2.22Zm10.22.53a.75.75 0 0 0-1.5 0v4.5c0 .414.336.75.75.75h4.5a.75.75 0 0 0 0-1.5h-2.69l3.22-3.22a.75.75 0 0 0-1.06-1.06L13.5 5.44V2.75ZM3.28 17.78l3.22-3.22v2.69a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.69l-3.22 3.22a.75.75 0 1 0 1.06 1.06Zm10.22-3.22l3.22 3.22a.75.75 0 1 0 1.06-1.06l-3.22-3.22h2.69a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0-.75.75v4.5a.75.75 0 0 0 1.5 0v-2.69Z"></path></svg>',
+ fullscreenOn:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="m13.28 7.78l3.22-3.22v2.69a.75.75 0 0 0 1.5 0v-4.5a.75.75 0 0 0-.75-.75h-4.5a.75.75 0 0 0 0 1.5h2.69l-3.22 3.22a.75.75 0 0 0 1.06 1.06ZM2 17.25v-4.5a.75.75 0 0 1 1.5 0v2.69l3.22-3.22a.75.75 0 0 1 1.06 1.06L4.56 16.5h2.69a.75.75 0 0 1 0 1.5h-4.5a.747.747 0 0 1-.75-.75Zm10.22-3.97l3.22 3.22h-2.69a.75.75 0 0 0 0 1.5h4.5a.747.747 0 0 0 .75-.75v-4.5a.75.75 0 0 0-1.5 0v2.69l-3.22-3.22a.75.75 0 1 0-1.06 1.06ZM3.5 4.56l3.22 3.22a.75.75 0 0 0 1.06-1.06L4.56 3.5h2.69a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0-.75.75v4.5a.75.75 0 0 0 1.5 0V4.56Z"></path></svg>',
+};
+
+export const backButton = {
+ name: "back-button",
+ index: 10,
+ position: "top",
+ html: "<div class='parent-player-title'><div></div><div className='flex gap-2'><p className='pt-1'><ChevronLeftIcon className='w-7 h-7'/></p><div class='flex flex-col text-white'><p className='font-outfit font-bold text-2xl'>Komi-san wa, Komyushou desu.</p><p className=''>Episode 1</p></div></div></div>",
+ // 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: '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M17.959 4.571L10.756 9.52s-.279.201-.279.481s.279.479.279.479l7.203 4.951c.572.38 1.041.099 1.041-.626V5.196c0-.727-.469-1.008-1.041-.625zm-9.076 0L1.68 9.52s-.279.201-.279.481s.279.479.279.479l7.203 4.951c.572.381 1.041.1 1.041-.625v-9.61c0-.727-.469-1.008-1.041-.625z"></path></svg>',
+ tooltip: "Backward 5s",
+ click: function () {
+ art.backward = 5;
+ },
+};
+
+export const seekForward = {
+ index: 11,
+ name: "fast-forward",
+ position: "left",
+ html: '<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 20 20"><path fill="currentColor" d="M9.244 9.52L2.041 4.571C1.469 4.188 1 4.469 1 5.196v9.609c0 .725.469 1.006 1.041.625l7.203-4.951s.279-.199.279-.478c0-.28-.279-.481-.279-.481zm9.356.481c0 .279-.279.478-.279.478l-7.203 4.951c-.572.381-1.041.1-1.041-.625V5.196c0-.727.469-1.008 1.041-.625L18.32 9.52s.28.201.28.481z"></path></svg>',
+ 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: '<button class="skip-button">Skip Opening</button>',
+ 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: '<button class="skip-button">Skip Ending</button>',
+ 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: '<div class="vid-con"><button class="next-button progress">Play Next</button></div>',
+ 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 (
+ <div
+ id={id}
+ className={`${className} bg-black`}
+ style={{ aspectRatio: aspectRatio }}
+ >
+ <div className="w-full h-full">
+ {!loading && track && url && (
+ <NewPlayer
+ playerRef={playerRef}
+ res={resolution}
+ quality={source}
+ option={option}
+ provider={provider}
+ defSize={defSize}
+ defSub={defSub}
+ subSize={subSize}
+ subtitles={subtitle}
+ getInstance={getInstance}
+ style={{
+ width: "100%",
+ height: "100%",
+ }}
+ />
+ )}
+ </div>
+ </div>
+ );
+}
diff --git a/components/watch/player/utils/getZoroSource.js b/components/watch/player/utils/getZoroSource.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/components/watch/player/utils/getZoroSource.js