aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA i j a Z <[email protected]>2024-01-14 13:44:21 +0530
committerGitHub <[email protected]>2024-01-14 15:14:21 +0700
commit41f82c9a3fd7987eecc8fd70d113ea8949457310 (patch)
treee465c5049630ac32afd35355a768b543ede76a6d
parentredesign: Redesign schedules page (#112) (diff)
downloadmoopa-41f82c9a3fd7987eecc8fd70d113ea8949457310.tar.xz
moopa-41f82c9a3fd7987eecc8fd70d113ea8949457310.zip
Added seeking buttons and fixed vercel error (#113)
* readded seek buttons and vercel support * Redesign player layout on mobile and fix seek button * Update changelogs and player buttons --------- Co-authored-by: Factiven <[email protected]>
-rw-r--r--components/home/content.tsx2
-rw-r--r--components/shared/changelogs.tsx27
-rw-r--r--components/watch/new-player/components/buttons.tsx210
-rw-r--r--components/watch/new-player/components/layouts/video-layout.tsx82
-rw-r--r--components/watch/new-player/components/menus.tsx64
-rw-r--r--components/watch/new-player/components/sliders.tsx4
-rw-r--r--components/watch/new-player/player.module.css2
-rw-r--r--components/watch/new-player/player.tsx104
-rw-r--r--components/watch/primary/details.tsx8
-rw-r--r--package-lock.json14
-rw-r--r--package.json6
-rw-r--r--pages/en/anime/watch/[...info].js48
12 files changed, 369 insertions, 202 deletions
diff --git a/components/home/content.tsx b/components/home/content.tsx
index b193381..e1886b6 100644
--- a/components/home/content.tsx
+++ b/components/home/content.tsx
@@ -321,7 +321,7 @@ export default function Content({
? `/en/anime/watch/${
anime.id
}/gogoanime?id=${encodeURIComponent(
- anime?.slug
+ anime?.slug?.replace('/', '')
)}&num=${anime.currentEpisode}`
: `/en/${type}/${anime.id}`
: `/en/${type}/${anime.id}`
diff --git a/components/shared/changelogs.tsx b/components/shared/changelogs.tsx
index 2a8a843..b39eeac 100644
--- a/components/shared/changelogs.tsx
+++ b/components/shared/changelogs.tsx
@@ -3,22 +3,37 @@ import Link from "next/link";
import { Fragment, useEffect, useRef, useState } from "react";
const web = {
- version: "v4.4.0",
+ version: "v4.4.1"
};
const logs = [
{
- version: "v4.4.0",
+ version: "v4.4.1",
pre: false,
notes: null,
highlights: true,
changes: [
+ "New player layout for mobile devices",
+ "Added seek buttons in the player",
+ "Added previous and next episode buttons in the player",
"Added rate modal when user finished watching the whole series",
"Fix: only half of the episodes has episodes thumbnail",
"Fix: pressing back button in anime info page redirects user to the wrong page",
- "Progressively migrate codebase to typescript",
- ],
- },
+ "Progressively migrate codebase to typescript"
+ ]
+ }
+ // {
+ // version: "v4.4.0",
+ // pre: false,
+ // notes: null,
+ // highlights: false,
+ // changes: [
+ // "Added rate modal when user finished watching the whole series",
+ // "Fix: only half of the episodes has episodes thumbnail",
+ // "Fix: pressing back button in anime info page redirects user to the wrong page",
+ // "Progressively migrate codebase to typescript"
+ // ]
+ // }
// {
// version: "v4.3.1",
// pre: true,
@@ -221,7 +236,7 @@ export function ChangelogsVersions({
pre,
notes,
highlights,
- children,
+ children
}: ChangelogsVersionsProps) {
return (
<>
diff --git a/components/watch/new-player/components/buttons.tsx b/components/watch/new-player/components/buttons.tsx
index b8b5b57..eaa23aa 100644
--- a/components/watch/new-player/components/buttons.tsx
+++ b/components/watch/new-player/components/buttons.tsx
@@ -11,6 +11,7 @@ import {
type TooltipPlacement,
useMediaRemote,
useMediaStore,
+ SeekButton
} from "@vidstack/react";
import {
ClosedCaptionsIcon,
@@ -29,23 +30,26 @@ import {
VolumeHighIcon,
VolumeLowIcon,
PreviousIcon,
+ SeekForward10Icon,
+ SeekBackward10Icon
} from "@vidstack/react/icons";
import { useRouter } from "next/router";
import { Navigation } from "../player";
export interface MediaButtonProps {
tooltipPlacement: TooltipPlacement;
+ offset?: number | undefined;
navigation?: Navigation;
host?: boolean;
}
export const buttonClass =
- "group ring-media-focus relative inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-md outline-none ring-inset hover:bg-white/20 data-[focus]:ring-4";
+ "group ring-media-focus relative inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-md outline-none ring-inset sm:hover:bg-white/20 data-[focus]:ring-4 z-30";
export const tooltipClass =
"animate-out fade-out slide-out-to-bottom-2 data-[visible]:animate-in data-[visible]:fade-in data-[visible]:slide-in-from-bottom-4 z-10 rounded-sm bg-black/90 px-2 py-0.5 text-sm font-medium text-white parent-data-[open]:hidden";
-export function Play({ tooltipPlacement }: MediaButtonProps) {
+export function Play({ tooltipPlacement, offset }: MediaButtonProps) {
const isPaused = useMediaState("paused"),
ended = useMediaState("ended"),
tooltipText = isPaused ? "Play" : "Pause",
@@ -57,39 +61,95 @@ export function Play({ tooltipPlacement }: MediaButtonProps) {
<Icon className="w-8 h-8" />
</PlayButton>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
{tooltipText}
</Tooltip.Content>
</Tooltip.Root>
);
}
+export function SeekForwardButton({
+ tooltipPlacement,
+ offset
+}: MediaButtonProps) {
+ return (
+ <Tooltip.Root>
+ <Tooltip.Trigger asChild>
+ <SeekButton seconds={10} className={buttonClass}>
+ <SeekForward10Icon className="w-8 h-8" />
+ </SeekButton>
+ </Tooltip.Trigger>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
+ Forward 10 Seconds
+ </Tooltip.Content>
+ </Tooltip.Root>
+ );
+}
+
+export function SeekBackwardButton({
+ tooltipPlacement,
+ offset
+}: MediaButtonProps) {
+ return (
+ <Tooltip.Root>
+ <Tooltip.Trigger asChild>
+ <SeekButton seconds={-10} className={buttonClass}>
+ <SeekBackward10Icon className="w-8 h-8" />
+ </SeekButton>
+ </Tooltip.Trigger>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
+ Backward 10 Seconds
+ </Tooltip.Content>
+ </Tooltip.Root>
+ );
+}
+
export function NextEpisode({
tooltipPlacement,
- navigation,
+ offset,
+ navigation
}: MediaButtonProps) {
const router = useRouter();
const { dataMedia, track } = useWatchProvider();
+ function handleNext() {
+ router.push(
+ `/en/anime/watch/${dataMedia.id}/${track?.provider}?id=${
+ navigation?.next?.id
+ }&num=${navigation?.next?.number}${
+ track?.isDub ? `&dub=${track?.isDub}` : ""
+ }`
+ );
+ }
+
return (
navigation?.next && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
- <button
- onClick={() => {
- router.push(
- `/en/anime/watch/${dataMedia.id}/${track?.provider}?id=${
- navigation?.next?.id
- }&num=${navigation?.next?.number}${
- track?.isDub ? `&dub=${track?.isDub}` : ""
- }`
- );
- }}
+ <div
+ onClick={handleNext}
+ onTouchEnd={handleNext}
className={buttonClass}
>
- <NextIcon className="w-8 h-8" />
- </button>
+ <NextIcon className="w-7 h-7" />
+ </div>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
Next Episode
</Tooltip.Content>
</Tooltip.Root>
@@ -99,30 +159,38 @@ export function NextEpisode({
export function PreviousEpisode({
tooltipPlacement,
- navigation,
+ offset,
+ navigation
}: MediaButtonProps) {
const router = useRouter();
const { dataMedia, track } = useWatchProvider();
+ function handlePrev() {
+ router.push(
+ `/en/anime/watch/${dataMedia.id}/${track?.provider}?id=${
+ navigation?.prev?.id
+ }&num=${navigation?.prev?.number}${
+ track?.isDub ? `&dub=${track?.isDub}` : ""
+ }`
+ );
+ }
+
return (
navigation?.prev && (
<Tooltip.Root>
- <Tooltip.Trigger asChild>
- <button
- onClick={() => {
- router.push(
- `/en/anime/watch/${dataMedia.id}/${track?.provider}?id=${
- navigation?.prev?.id
- }&num=${navigation?.prev?.number}${
- track?.isDub ? `&dub=${track?.isDub}` : ""
- }`
- );
- }}
+ <Tooltip.Trigger>
+ <div
+ onClick={handlePrev}
+ onTouchEnd={handlePrev}
className={buttonClass}
>
- <PreviousIcon className="w-8 h-8" />
- </button>
+ <PreviousIcon className="w-7 h-7" />
+ </div>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
Previous Episode
</Tooltip.Content>
</Tooltip.Root>
@@ -135,27 +203,17 @@ export function MobilePlayButton({ tooltipPlacement, host }: MediaButtonProps) {
ended = useMediaState("ended"),
Icon = ended ? ReplayIcon : isPaused ? PlayIcon : PauseIcon;
return (
- <Tooltip.Root>
- <Tooltip.Trigger asChild>
- <PlayButton
- className={`${
- host ? "" : "pointer-events-none"
- } group ring-media-focus relative inline-flex h-16 w-16 media-paused:cursor-pointer cursor-default items-center justify-center rounded-full outline-none`}
- >
- <Icon className="w-10 h-10" />
- </PlayButton>
- </Tooltip.Trigger>
- {/* <Tooltip.Content
- className="animate-out fade-out slide-out-to-bottom-2 data-[visible]:animate-in data-[visible]:fade-in data-[visible]:slide-in-from-bottom-4 z-10 rounded-sm bg-black/90 px-2 py-0.5 text-sm font-medium text-white parent-data-[open]:hidden"
- placement={tooltipPlacement}
- >
- {tooltipText}
- </Tooltip.Content> */}
- </Tooltip.Root>
+ <PlayButton
+ className={`${
+ host ? "" : "pointer-events-none"
+ } group ring-media-focus relative inline-flex h-16 w-16 media-paused:cursor-pointer cursor-default items-center justify-center rounded-full outline-none`}
+ >
+ <Icon className="w-10 h-10" />
+ </PlayButton>
);
}
-export function Mute({ tooltipPlacement }: MediaButtonProps) {
+export function Mute({ tooltipPlacement, offset }: MediaButtonProps) {
const volume = useMediaState("volume"),
isMuted = useMediaState("muted");
return (
@@ -171,14 +229,18 @@ export function Mute({ tooltipPlacement }: MediaButtonProps) {
)}
</MuteButton>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
{isMuted ? "Unmute" : "Mute"}
</Tooltip.Content>
</Tooltip.Root>
);
}
-export function Caption({ tooltipPlacement }: MediaButtonProps) {
+export function Caption({ tooltipPlacement, offset }: MediaButtonProps) {
const track = useMediaState("textTrack"),
isOn = track && isTrackCaptionKind(track);
return (
@@ -192,14 +254,18 @@ export function Caption({ tooltipPlacement }: MediaButtonProps) {
)}
</CaptionButton>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
{isOn ? "Closed-Captions On" : "Closed-Captions Off"}
</Tooltip.Content>
</Tooltip.Root>
);
}
-export function TheaterButton({ tooltipPlacement }: MediaButtonProps) {
+export function TheaterButton({ tooltipPlacement, offset }: MediaButtonProps) {
const playerState = useMediaState("currentTime"),
isPlaying = useMediaState("playing");
@@ -215,7 +281,7 @@ export function TheaterButton({ tooltipPlacement }: MediaButtonProps) {
setPlayerState((prev: any) => ({
...prev,
currentTime: playerState,
- isPlaying: isPlaying,
+ isPlaying: isPlaying
}));
setTheaterMode((prev: any) => !prev);
}}
@@ -227,14 +293,18 @@ export function TheaterButton({ tooltipPlacement }: MediaButtonProps) {
)}
</button>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
Theatre Mode
</Tooltip.Content>
</Tooltip.Root>
);
}
-export function PIP({ tooltipPlacement }: MediaButtonProps) {
+export function PIP({ tooltipPlacement, offset }: MediaButtonProps) {
const isActive = useMediaState("pictureInPicture");
return (
<Tooltip.Root>
@@ -247,7 +317,11 @@ export function PIP({ tooltipPlacement }: MediaButtonProps) {
)}
</PIPButton>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
{isActive ? "Exit PIP" : "Enter PIP"}
</Tooltip.Content>
</Tooltip.Root>
@@ -256,7 +330,7 @@ export function PIP({ tooltipPlacement }: MediaButtonProps) {
export function PlayNextButton({
tooltipPlacement,
- navigation,
+ navigation
}: MediaButtonProps) {
// const remote = useMediaRemote();
const router = useRouter();
@@ -276,14 +350,14 @@ export function PlayNextButton({
);
}
}}
- className="next-button hidden"
+ className="next-button text-sm hidden"
>
Next Episode
</button>
);
}
-export function SkipOpButton({ tooltipPlacement }: MediaButtonProps) {
+export function SkipOpButton({ tooltipPlacement, offset }: MediaButtonProps) {
const remote = useMediaRemote();
const { track } = useWatchProvider();
const op = track?.skip?.find((item: any) => item.text === "Opening");
@@ -294,14 +368,14 @@ export function SkipOpButton({ tooltipPlacement }: MediaButtonProps) {
onClick={() => {
remote.seek(op?.endTime);
}}
- className="op-button hidden hover:bg-white/80 bg-white px-4 py-2 text-primary font-karla font-semibold rounded-md"
+ className="op-button hidden text-sm hover:bg-white/80 bg-white px-4 py-2 text-primary font-karla font-semibold rounded-md"
>
Skip Opening
</button>
);
}
-export function SkipEdButton({ tooltipPlacement }: MediaButtonProps) {
+export function SkipEdButton({ tooltipPlacement, offset }: MediaButtonProps) {
const remote = useMediaRemote();
const { duration } = useMediaStore();
const { track } = useWatchProvider();
@@ -317,14 +391,14 @@ export function SkipEdButton({ tooltipPlacement }: MediaButtonProps) {
title="ed-button"
type="button"
onClick={() => remote.seek(endTime)}
- className="ed-button hidden cursor-pointer hover:bg-white/80 bg-white px-4 py-2 text-primary font-karla font-semibold rounded-md"
+ className="ed-button hidden text-sm cursor-pointer hover:bg-white/80 bg-white px-4 py-2 text-primary font-karla font-semibold rounded-md"
>
Skip Ending
</button>
);
}
-export function Fullscreen({ tooltipPlacement }: MediaButtonProps) {
+export function Fullscreen({ tooltipPlacement, offset }: MediaButtonProps) {
const isActive = useMediaState("fullscreen");
return (
<Tooltip.Root>
@@ -337,7 +411,11 @@ export function Fullscreen({ tooltipPlacement }: MediaButtonProps) {
)}
</FullscreenButton>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
{isActive ? "Exit Fullscreen" : "Enter Fullscreen"}
</Tooltip.Content>
</Tooltip.Root>
diff --git a/components/watch/new-player/components/layouts/video-layout.tsx b/components/watch/new-player/components/layouts/video-layout.tsx
index 8d4ec01..d16dc9c 100644
--- a/components/watch/new-player/components/layouts/video-layout.tsx
+++ b/components/watch/new-player/components/layouts/video-layout.tsx
@@ -6,7 +6,8 @@ import {
Controls,
Gesture,
Spinner,
- useMediaState,
+ Time,
+ useMediaState
} from "@vidstack/react";
import * as Buttons from "../buttons";
@@ -41,7 +42,7 @@ export function VideoLayout({
thumbnails,
navigation,
host = true,
- session,
+ session
}: VideoLayoutProps) {
const [isMobile, setIsMobile] = useState(false);
@@ -56,7 +57,7 @@ export function VideoLayout({
setRatingModalState((prev: any) => {
return {
...prev,
- isFullscreen: isFullscreen,
+ isFullscreen: isFullscreen
};
});
}, [isFullscreen]);
@@ -65,7 +66,7 @@ export function VideoLayout({
<>
<Gestures host={host} />
<Captions
- className={`${captionStyles.captions} media-preview:opacity-0 media-controls:bottom-[85px] media-captions:opacity-100 absolute inset-0 bottom-2 z-10 select-none break-words opacity-0 transition-[opacity,bottom] duration-300`}
+ className={`${captionStyles.captions} media-preview:opacity-0 media-controls:bottom-[28px] sm:media-controls:bottom-[85px] media-captions:opacity-100 absolute inset-0 bottom-2 z-10 select-none break-words opacity-0 transition-[opacity,bottom] duration-300`}
/>
{ratingModalState.isFullscreen && (
<RateModal
@@ -75,24 +76,51 @@ export function VideoLayout({
session={session}
/>
)}
+
+ {/* TOP CONTROLS */}
<Controls.Root
className={`${styles.controls} media-paused:bg-black/10 duration-200 media-controls:opacity-100 absolute inset-0 z-10 flex h-full w-full flex-col bg-gradient-to-t from-black/30 via-transparent to-black/30 opacity-0 transition-opacity`}
>
<Controls.Group className="flex justify-between items-center w-full px-2 pt-2">
<Title navigation={navigation} />
+
<div className="flex-1" />
- {/* <Menus.Episodes placement="left start" /> */}
+ <div className="flex sm:hidden items-center">
+ {!!track?.subtitles?.length && (
+ <Buttons.Caption tooltipPlacement="top" />
+ )}
+ <Buttons.Mute offset={10} tooltipPlacement="bottom" />
+ <Buttons.PIP offset={10} tooltipPlacement="bottom" />
+ <Menus.Settings
+ offset={10}
+ placement="bottom end"
+ tooltipPlacement="bottom end"
+ />
+ </div>
</Controls.Group>
<div className="flex-1" />
- {/* {isPaused && ( */}
<Controls.Group
- className={`media-paused:opacity-100 media-paused:scale-100 backdrop-blur-sm scale-[160%] opacity-0 duration-200 ease-out flex shadow bg-white/10 rounded-full absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2`}
+ className={`hidden media-paused:opacity-100 media-paused:scale-100 backdrop-blur-sm scale-[160%] opacity-0 duration-200 ease-out sm:flex shadow bg-white/10 rounded-full absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2`}
>
<Buttons.MobilePlayButton tooltipPlacement="top center" host={host} />
</Controls.Group>
- {/* )} */}
+ {/* MOBILE CENTER */}
+ <Controls.Group
+ className={`duration-200 ease-out flex sm:hidden gap-5 items-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-20`}
+ >
+ <Buttons.SeekBackwardButton offset={10} tooltipPlacement="top" />
+ <div className="backdrop-blur-sm shadow bg-white/10 rounded-full">
+ <Buttons.MobilePlayButton
+ tooltipPlacement="top center"
+ host={host}
+ />
+ </div>
+ <Buttons.SeekForwardButton offset={10} tooltipPlacement="top" />
+ </Controls.Group>
+
+ {/* LOADING */}
<div className="pointer-events-none absolute inset-0 z-50 flex h-full w-full items-center justify-center">
<Spinner.Root
className="text-white opacity-0 transition-opacity duration-200 ease-linear media-buffering:animate-spin media-buffering:opacity-100"
@@ -102,7 +130,6 @@ export function VideoLayout({
<Spinner.TrackFill className="opacity-75" width={8} />
</Spinner.Root>
</div>
- {/* </Controls.Group> */}
<Controls.Group className="flex px-4">
<div className="flex-1" />
@@ -118,15 +145,16 @@ export function VideoLayout({
)}
</Controls.Group>
- <Controls.Group className="flex w-full items-center px-2">
+ {/* DESKTOP CONTROLS */}
+ <Controls.Group className="hidden sm:flex w-full items-center px-2">
<Sliders.Time thumbnails={thumbnails} host={host} />
</Controls.Group>
- <Controls.Group className="-mt-0.5 flex w-full items-center px-2 pb-2">
+ <Controls.Group className="hidden -mt-0.5 sm:flex w-full items-center px-2 pb-2">
<Buttons.PreviousEpisode
navigation={navigation}
- tooltipPlacement="top"
+ tooltipPlacement="top start"
/>
- <Buttons.Play tooltipPlacement="top start" />
+ <Buttons.Play tooltipPlacement="top" />
<Buttons.NextEpisode navigation={navigation} tooltipPlacement="top" />
<Buttons.Mute tooltipPlacement="top" />
<Sliders.Volume />
@@ -136,6 +164,8 @@ export function VideoLayout({
{!!track?.subtitles?.length && (
<Buttons.Caption tooltipPlacement="top" />
)}
+ <Buttons.SeekBackwardButton tooltipPlacement="top" />
+ <Buttons.SeekForwardButton tooltipPlacement="top" />
<Menus.Settings placement="top end" tooltipPlacement="top" />
{!isMobile && !isFullscreen && (
<Buttons.TheaterButton tooltipPlacement="top" />
@@ -143,6 +173,32 @@ export function VideoLayout({
<Buttons.PIP tooltipPlacement="top" />
<Buttons.Fullscreen tooltipPlacement="top end" />
</Controls.Group>
+
+ {/* MOBILE CONTROLS */}
+ <Controls.Group className="flex sm:hidden w-full items-center px-2 pb-1 z-40">
+ <div className="flex items-center mr-1 z-40">
+ <Buttons.PreviousEpisode
+ navigation={navigation}
+ offset={10}
+ tooltipPlacement="top start"
+ />
+ <Buttons.NextEpisode
+ offset={10}
+ navigation={navigation}
+ tooltipPlacement="top"
+ />
+ </div>
+ <div className="text-xs">
+ <Time className="time" type="current" />
+ </div>
+ <Sliders.Time thumbnails={thumbnails} host={host} />
+ <div className="text-xs">
+ <Time className="time" type="duration" />
+ </div>
+ <div className="flex ml-1">
+ <Buttons.Fullscreen offset={10} tooltipPlacement="top end" />
+ </div>
+ </Controls.Group>
</Controls.Root>
</>
);
diff --git a/components/watch/new-player/components/menus.tsx b/components/watch/new-player/components/menus.tsx
index de2b302..7a28eec 100644
--- a/components/watch/new-player/components/menus.tsx
+++ b/components/watch/new-player/components/menus.tsx
@@ -12,7 +12,7 @@ import {
type TooltipPlacement,
useVideoQualityOptions,
useMediaState,
- usePlaybackRateOptions,
+ usePlaybackRateOptions
} from "@vidstack/react";
import {
ChevronLeftIcon,
@@ -25,7 +25,7 @@ import {
// EpisodesIcon,
SettingsSwitchIcon,
// PlaybackSpeedCircleIcon,
- OdometerIcon,
+ OdometerIcon
} from "@vidstack/react/icons";
import { buttonClass, tooltipClass } from "./buttons";
@@ -34,6 +34,7 @@ import React from "react";
export interface SettingsProps {
placement: MenuPlacement;
+ offset?: number;
tooltipPlacement: TooltipPlacement;
}
@@ -44,10 +45,15 @@ export const submenuClass =
"hidden w-full flex-col items-start justify-center outline-none data-[keyboard]:mt-[3px] data-[open]:inline-block";
export const contentMenuClass =
- "flex cust-scroll h-[var(--menu-height)] max-h-[180px] lg:max-h-[400px] min-w-[260px] flex-col overflow-y-auto overscroll-y-contain rounded-md border border-white/10 bg-secondary p-2 font-sans text-[15px] font-medium outline-none backdrop-blur-sm transition-[height] duration-300 will-change-[height] data-[resizing]:overflow-hidden";
+ "flex cust-scroll h-[var(--menu-height)] max-h-[180px] lg:max-h-[400px] min-w-[260px] flex-col overflow-y-auto overscroll-y-contain rounded-md border border-white/10 bg-secondary p-1 font-sans text-[15px] font-medium outline-none backdrop-blur-sm transition-[height] duration-300 will-change-[height] data-[resizing]:overflow-hidden";
-export function Settings({ placement, tooltipPlacement }: SettingsProps) {
+export function Settings({
+ placement,
+ offset,
+ tooltipPlacement
+}: SettingsProps) {
const { track } = useWatchProvider();
+ const isFullscreen = useMediaState("fullscreen");
const isSubtitleAvailable = track?.epiData?.subtitles?.length > 0;
return (
@@ -58,21 +64,33 @@ export function Settings({ placement, tooltipPlacement }: SettingsProps) {
<SettingsIcon className="h-8 w-8 transform transition-transform duration-200 ease-out group-data-[open]:rotate-90" />
</Menu.Button>
</Tooltip.Trigger>
- <Tooltip.Content className={tooltipClass} placement={tooltipPlacement}>
+ <Tooltip.Content
+ offset={offset}
+ className={tooltipClass}
+ placement={tooltipPlacement}
+ >
Settings
</Tooltip.Content>
</Tooltip.Root>
- {/* <Menu.Content className={menuClass} placement={placement}>
- {isSubtitleAvailable && <CaptionSubmenu />}
- <QualitySubmenu />
- </Menu.Content> */}
- <Menu.Content className={contentMenuClass} placement={placement}>
- <AutoPlay />
- <AutoNext />
- <SpeedSubmenu />
- {isSubtitleAvailable && <CaptionSubmenu />}
- <QualitySubmenu />
- </Menu.Content>
+ {!isFullscreen ? (
+ <Menu.Portal>
+ <Menu.Content className={contentMenuClass} placement={placement}>
+ <AutoPlay />
+ <AutoNext />
+ <SpeedSubmenu />
+ {isSubtitleAvailable && <CaptionSubmenu />}
+ <QualitySubmenu />
+ </Menu.Content>
+ </Menu.Portal>
+ ) : (
+ <Menu.Content className={contentMenuClass} placement={placement}>
+ <AutoPlay />
+ <AutoNext />
+ <SpeedSubmenu />
+ {isSubtitleAvailable && <CaptionSubmenu />}
+ <QualitySubmenu />
+ </Menu.Content>
+ )}
</Menu.Root>
);
}
@@ -194,13 +212,13 @@ function AutoPlay() {
{
label: "On",
value: "on",
- selected: false,
+ selected: false
},
{
label: "Off",
value: "off",
- selected: true,
- },
+ selected: true
+ }
]);
const { autoplay, setAutoPlay } = useWatchProvider();
@@ -254,13 +272,13 @@ function AutoNext() {
{
label: "On",
value: "on",
- selected: false,
+ selected: false
},
{
label: "Off",
value: "off",
- selected: true,
- },
+ selected: true
+ }
]);
const { autoNext, setAutoNext } = useWatchProvider();
@@ -368,7 +386,7 @@ function SubmenuButton({
label,
hint,
icon: Icon,
- disabled,
+ disabled
}: SubmenuButtonProps) {
return (
<Menu.Button
diff --git a/components/watch/new-player/components/sliders.tsx b/components/watch/new-player/components/sliders.tsx
index f31e28a..3cecd06 100644
--- a/components/watch/new-player/components/sliders.tsx
+++ b/components/watch/new-player/components/sliders.tsx
@@ -28,7 +28,7 @@ export function Time({ thumbnails, host }: TimeSliderProps) {
<TimeSlider.Root
className={`${
host ? "" : "pointer-events-none"
- } time-slider group relative mx-[7.5px] inline-flex h-10 w-full cursor-pointer touch-none select-none items-center outline-none`}
+ } time-slider group relative mx-[7.5px] inline-flex h-7 w-full cursor-pointer touch-none select-none items-center outline-none`}
>
<TimeSlider.Chapters className="relative flex h-full w-full items-center rounded-[1px]">
{(cues, forwardRef) =>
@@ -39,7 +39,7 @@ export function Time({ thumbnails, host }: TimeSliderProps) {
key={cue.startTime}
ref={forwardRef}
>
- <TimeSlider.Track className="relative ring-media-focus z-0 h-[5px] group-hover/slider:h-[10px] transition-all duration-100 w-full rounded-sm bg-white/30 group-data-[focus]:ring-[3px]">
+ <TimeSlider.Track className="relative ring-media-focus z-0 h-[4px] group-hover/slider:h-[10px] transition-all duration-100 w-full rounded-sm bg-white/30 group-data-[focus]:ring-[3px]">
<TimeSlider.TrackFill className="bg-white absolute h-full w-[var(--chapter-fill)] rounded-sm will-change-[width]" />
<TimeSlider.Progress className="absolute z-10 h-full w-[var(--chapter-progress)] rounded-sm bg-white/50 will-change-[width]" />
</TimeSlider.Track>
diff --git a/components/watch/new-player/player.module.css b/components/watch/new-player/player.module.css
index f2f5b39..4b4743e 100644
--- a/components/watch/new-player/player.module.css
+++ b/components/watch/new-player/player.module.css
@@ -11,7 +11,7 @@
color: #f5f5f5;
contain: layout;
font-family: sans-serif;
- overflow: hidden;
+ /* overflow: hidden; */
}
.player[data-focus]:not([data-playing]) {
diff --git a/components/watch/new-player/player.tsx b/components/watch/new-player/player.tsx
index f2d11f7..5e25a61 100644
--- a/components/watch/new-player/player.tsx
+++ b/components/watch/new-player/player.tsx
@@ -11,7 +11,7 @@ import {
useMediaRemote,
type MediaPlayerInstance,
Track,
- MediaTimeUpdateEventDetail,
+ MediaTimeUpdateEventDetail
} from "@vidstack/react";
import { VideoLayout } from "./components/layouts/video-layout";
import { useWatchProvider } from "@/lib/context/watchPageProvider";
@@ -86,7 +86,7 @@ export default function VidStack({
id,
navigation,
userData,
- sessions,
+ sessions
}: VidStackProps) {
let player = useRef<MediaPlayerInstance>(null);
@@ -97,7 +97,7 @@ export default function VidStack({
playerState,
dataMedia,
autoNext,
- setRatingModalState,
+ setRatingModalState
} = useWatchProvider();
const { qualities, duration } = useMediaStore(player);
@@ -193,8 +193,8 @@ export default function VidStack({
provider: track?.provider,
nextId: navigation?.next?.id,
nextNumber: Number(navigation?.next?.number),
- dub: track?.isDub ? true : false,
- }),
+ dub: track?.isDub ? true : false
+ })
});
}
@@ -214,7 +214,7 @@ export default function VidStack({
nextId: navigation?.next?.id,
nextNumber: navigation?.next?.number,
dub: track?.isDub ? true : false,
- createdAt: new Date().toISOString(),
+ createdAt: new Date().toISOString()
});
// console.log("update");
}, 5000);
@@ -256,58 +256,49 @@ export default function VidStack({
}, [playerState?.currentTime, playerState?.isPlaying]);
useEffect(() => {
- const chapter = track?.skip,
- videoDuration = Math.round(duration);
+ const chapter = track?.skip;
+ const videoDuration = Math.round(duration);
+
+ if (!chapter || chapter.length === 0 || !player.current) {
+ // Handle cases where there's no chapter data or player is not ready
+ setChapters("");
+ return;
+ }
let vtt = "WEBVTT\n\n";
- let lastEndTime = 0;
-
- if (chapter && chapter?.length > 0) {
- chapter.forEach((item: SkipData) => {
- let startMinutes = Math.floor(item.startTime / 60);
- let startSeconds = item.startTime % 60;
- let endMinutes = Math.floor(item.endTime / 60);
- let endSeconds = item.endTime % 60;
-
- let start = `${startMinutes.toString().padStart(2, "0")}:${startSeconds
- .toString()
- .padStart(2, "0")}`;
- let end = `${endMinutes.toString().padStart(2, "0")}:${endSeconds
- .toString()
- .padStart(2, "0")}`;
-
- vtt += `${start} --> ${end}\n${item.text}\n\n`;
- if (item.endTime > lastEndTime) {
- lastEndTime = item.endTime;
- }
- });
-
- if (lastEndTime < videoDuration) {
- let startMinutes = Math.floor(lastEndTime / 60);
- let startSeconds = lastEndTime % 60;
- let endMinutes = Math.floor(videoDuration / 60);
- let endSeconds = videoDuration % 60;
-
- let start = `${startMinutes.toString().padStart(2, "0")}:${startSeconds
- .toString()
- .padStart(2, "0")}`;
- let end = `${endMinutes.toString().padStart(2, "0")}:${endSeconds
- .toString()
- .padStart(2, "0")}`;
-
- vtt += `${start} --> ${end}\n\n\n`;
+ chapter.forEach((item: { endTime: any; startTime: any; text: any }) => {
+ if (!item.endTime) {
+ // Handle missing endTime gracefully
+ console.warn("Skipping item with missing endTime:", item);
+ return;
}
- const vttBlob = new Blob([vtt], { type: "text/vtt" });
- const vttUrl = URL.createObjectURL(vttBlob);
+ const [startMinutes, startSeconds] = formatTime(item.startTime);
+ const [endMinutes, endSeconds] = formatTime(item.endTime);
+
+ vtt += `${startMinutes}:${startSeconds} --> ${endMinutes}:${endSeconds}\n${item.text}\n\n`;
+ });
- setChapters(vttUrl);
+ if (chapter[chapter.length - 1].endTime < videoDuration) {
+ // Add a final chapter if needed
+ const [startMinutes, startSeconds] = formatTime(
+ chapter[chapter.length - 1].endTime
+ );
+ const [endMinutes, endSeconds] = formatTime(videoDuration);
+ vtt += `${startMinutes}:${startSeconds} --> ${endMinutes}:${endSeconds}\n\n\n`;
}
+
+ const vttBlob = new Blob([vtt], { type: "text/vtt" });
+ const vttUrl = URL.createObjectURL(vttBlob);
+
+ setChapters(vttUrl);
+
return () => {
setChapters("");
+ URL.revokeObjectURL(vttUrl); // Clean up VTT URL
};
- }, [track?.skip, duration]);
+ }, [track?.skip, duration, player.current]);
useEffect(() => {
return () => {
@@ -315,7 +306,7 @@ export default function VidStack({
player.current.destroy();
}
};
- }, []);
+ }, [id]);
function onEnded() {
if (!navigation?.next?.id) return;
@@ -382,14 +373,14 @@ export default function VidStack({
// @ts-ignore Fix when convert useAnilist to typescript
markProgress({
mediaId: dataMedia.id,
- progress: navigation.playing.number,
+ progress: navigation.playing.number
});
if (dataMedia.episodes === +navigation.playing?.number) {
setRatingModalState((prev: any) => {
return {
...prev,
- isOpen: true,
+ isOpen: true
};
});
}
@@ -447,7 +438,7 @@ export default function VidStack({
crossorigin="anonymous"
src={{
src: defaultQuality?.url,
- type: "application/vnd.apple.mpegurl",
+ type: "application/vnd.apple.mpegurl"
}}
onTimeUpdate={onTimeUpdate}
playsinline
@@ -486,3 +477,12 @@ export function calculateAspectRatio(width: number, height: number) {
const aspectRatio = `${width / divisor}/${height / divisor}`;
return aspectRatio;
}
+
+function formatTime(timeInSeconds: number) {
+ const minutes = Math.floor(timeInSeconds / 60);
+ const seconds = Math.floor(timeInSeconds % 60);
+ return [
+ minutes.toString().padStart(2, "0"),
+ seconds.toString().padStart(2, "0")
+ ];
+}
diff --git a/components/watch/primary/details.tsx b/components/watch/primary/details.tsx
index dd739f2..4ff1be5 100644
--- a/components/watch/primary/details.tsx
+++ b/components/watch/primary/details.tsx
@@ -28,7 +28,7 @@ export default function Details({
onList,
setOnList,
handleOpen,
- disqus,
+ disqus
}: DetailsProps) {
const [showComments, setShowComments] = useState(false);
const { markPlanning } = useAniList(session);
@@ -65,7 +65,7 @@ export default function Details({
{info ? (
<Link
className="hover:scale-105 hover:shadow-lg duration-300 ease-out"
- href={`/en/anime/${id}`}
+ href={`/en/anime/${info.id}`}
>
<Image
src={info.coverImage.extraLarge}
@@ -170,7 +170,7 @@ export default function Details({
? description
: description?.length > 420
? truncatedDesc
- : description,
+ : description
}}
className={`p-5 text-sm font-light font-roboto text-[#e4e4e4] `}
/>
@@ -224,7 +224,7 @@ export default function Details({
title: info.title.romaji,
url: window.location.href,
episode: epiNumber,
- name: disqus,
+ name: disqus
}}
/>
</div>
diff --git a/package-lock.json b/package-lock.json
index 6c651cc..3447d17 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
"@heroicons/react": "^2.0.17",
"@prisma/client": "^5.3.1",
"@vercel/og": "^0.5.4",
- "@vidstack/react": "^1.8.3",
+ "@vidstack/react": "^1.9.8",
"axios": "^1.4.0",
"cookies": "^0.8.0",
"cron": "^2.4.0",
@@ -2682,9 +2682,9 @@
}
},
"node_modules/@vidstack/react": {
- "version": "1.8.3",
- "resolved": "https://registry.npmjs.org/@vidstack/react/-/react-1.8.3.tgz",
- "integrity": "sha512-QCyHy6e3LpzfajtjrhJPXzGYbBrBCUE5qYAatKXX+nxWqRvspa0fJPlnGeWb+tg6DlDsgwDLFjGNWj8qUeUVXQ==",
+ "version": "1.9.8",
+ "resolved": "https://registry.npmjs.org/@vidstack/react/-/react-1.9.8.tgz",
+ "integrity": "sha512-1gGlCXpmGriKZ+sgP1WLgm4tpkU2buXeAIPFoh8t7V4X3jV1l15oZS4whfPswCY6/9jAwVKq0jQBLryAuBYZww==",
"dependencies": {
"media-captions": "^1.0.1"
},
@@ -5178,9 +5178,9 @@
"dev": true
},
"node_modules/follow-redirects": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
- "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
diff --git a/package.json b/package.json
index 6f77d24..59e73cb 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
{
"name": "moopa",
- "version": "4.4.0",
+ "version": "4.4.1",
"private": true,
"founder": "Factiven",
"scripts": {
"dev": "next dev",
- "build": "next build",
+ "build": "npx prisma migrate deploy && npx prisma generate && next build",
"export": "next build && next export",
"start": "next start",
"type-check": "tsc",
@@ -16,7 +16,7 @@
"@heroicons/react": "^2.0.17",
"@prisma/client": "^5.3.1",
"@vercel/og": "^0.5.4",
- "@vidstack/react": "^1.8.3",
+ "@vidstack/react": "^1.9.8",
"axios": "^1.4.0",
"cookies": "^0.8.0",
"cron": "^2.4.0",
diff --git a/pages/en/anime/watch/[...info].js b/pages/en/anime/watch/[...info].js
index 0f8dff9..445d220 100644
--- a/pages/en/anime/watch/[...info].js
+++ b/pages/en/anime/watch/[...info].js
@@ -29,7 +29,7 @@ export async function getServerSideProps(context) {
const query = context?.query;
if (!query) {
return {
- notFound: true,
+ notFound: true
};
}
@@ -53,8 +53,8 @@ export async function getServerSideProps(context) {
return {
redirect: {
destination: "/en/removed",
- permanent: false,
- },
+ permanent: false
+ }
};
}
@@ -62,7 +62,7 @@ export async function getServerSideProps(context) {
method: "POST",
headers: {
"Content-Type": "application/json",
- ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` })
},
body: JSON.stringify({
query: `query ($id: Int) {
@@ -103,9 +103,9 @@ export async function getServerSideProps(context) {
}
`,
variables: {
- id: aniId,
- },
- }),
+ id: aniId
+ }
+ })
});
const data = await ress.json();
// const variables = { id: aniId };
@@ -139,8 +139,8 @@ export async function getServerSideProps(context) {
userData: userData?.[0] || null,
info: data?.data?.Media || null,
proxy,
- disqus,
- },
+ disqus
+ }
};
}
@@ -153,7 +153,7 @@ export default function Watch({
userData,
sessions,
provider,
- epiNumber,
+ epiNumber
}) {
const [artStorage, setArtStorage] = useState(null);
@@ -178,7 +178,7 @@ export default function Watch({
setMarked,
setTrack,
aspectRatio,
- setDataMedia,
+ setDataMedia
} = useWatchProvider();
useEffect(() => {
@@ -238,9 +238,9 @@ export default function Watch({
title: playingData?.title || info?.title?.romaji,
description: playingData?.description,
img: playingData?.img || playingData?.image,
- number: currentEpisode.number,
+ number: currentEpisode.number
},
- next: nextEpisode,
+ next: nextEpisode
};
setEpisodeNavigation(vidNav);
}
@@ -272,7 +272,7 @@ export default function Watch({
const anify = await fetch("/api/v2/source", {
method: "POST",
headers: {
- "Content-Type": "application/json",
+ "Content-Type": "application/json"
},
body: JSON.stringify({
source:
@@ -283,8 +283,8 @@ export default function Watch({
watchId: watchId,
episode: epiNumber,
id: info.id,
- sub: dub ? "dub" : "sub",
- }),
+ sub: dub ? "dub" : "sub"
+ })
}).then((res) => res.json());
if (!anify?.sources?.length > 0) {
@@ -317,14 +317,14 @@ export default function Watch({
anify?.intro?.start ?? Math.round(getOp?.interval.startTime),
endTime:
anify?.intro?.end ?? Math.round(getOp?.interval.endTime),
- text: "Opening",
+ text: "Opening"
}
: null,
ed = {
startTime:
anify?.outro?.start ?? Math.round(getEd?.interval.startTime),
endTime: anify?.outro?.end ?? Math.round(getEd?.interval.endTime),
- text: "Ending",
+ text: "Ending"
};
const skipData = [op, ed].filter((i) => i !== null);
@@ -338,7 +338,7 @@ export default function Watch({
src: proxy + "/" + i.url,
label: i.lang,
kind: i.lang === "Thumbnails" ? "thumbnails" : "subtitles",
- ...(i.lang === "English" && { default: true }),
+ ...(i.lang === "English" && { default: true })
};
});
@@ -358,12 +358,12 @@ export default function Watch({
url: `${proxy}/proxy/m3u8/${encodeURIComponent(
String(quality?.url)
)}/${encodeURIComponent(JSON.stringify(anify?.headers))}`,
- headers: anify?.headers,
+ headers: anify?.headers
},
subtitles: subtitles,
thumbnails: thumbnails?.src,
epiData: anify,
- skip: skipData,
+ skip: skipData
};
setTrack(episode);
@@ -374,7 +374,7 @@ export default function Watch({
return () => {
setPlayerState({
currentTime: 0,
- isPlaying: false,
+ isPlaying: false
});
setMarked(0);
setTrack(null);
@@ -402,7 +402,7 @@ export default function Watch({
? "- Episode " + epiNumber
: `- ${info?.title?.romaji || info?.title?.english}`
}`,
- artwork,
+ artwork
});
}, [episodeNavigation, info, epiNumber]);
@@ -412,7 +412,7 @@ export default function Watch({
await navigator.share({
title: `Watch Now - ${info?.title?.english || info.title.romaji}`,
// text: `Watch [${info?.title?.romaji}] and more on Moopa. Join us for endless anime entertainment"`,
- url: window.location.href,
+ url: window.location.href
});
} else {
// Web Share API is not supported, provide a fallback or show a message