diff --git a/package.json b/package.json index 2007165..9449e76 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,9 @@ "license": "MIT", "dependencies": { "@reduxjs/toolkit": "^2.2.7", - "axios": "^1.7.4", - "framer-motion": "^11.3.28", + "artplayer": "^5.1.7", + "axios": "^1.7.3", + "framer-motion": "^11.3.23", "plyr": "^3.7.8", "prop-types": "^15.8.1", "react": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19a1c63..f0d4060 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,11 +11,14 @@ importers: '@reduxjs/toolkit': specifier: ^2.2.7 version: 2.2.7(react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1))(react@18.3.1) + artplayer: + specifier: ^5.1.7 + version: 5.1.7 axios: - specifier: ^1.7.4 + specifier: ^1.7.3 version: 1.7.4 framer-motion: - specifier: ^11.3.28 + specifier: ^11.3.23 version: 11.3.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) plyr: specifier: ^3.7.8 @@ -480,24 +483,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.6.1': resolution: {integrity: sha512-dr6YbLBg/SsNxs1hDqJhxdcrS8dGMlOXJwXIrUvACiA8jAd6S5BxYCaqsCefLYXtaOmu0bbx1FB/evfodqB70Q==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.6.1': resolution: {integrity: sha512-A0b/3V+yFy4LXh3O9umIE7LXPC7NBWdjl6AQYqymSMcMu0EOb1/iygA6s6uWhz9y3e172Hpb9b/CGsuD8Px/bg==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.6.1': resolution: {integrity: sha512-5dJjlzZXhC87nZZZWbpiDP8kBIO0ibis893F/rtPIQBI5poH+iJuA32EU3wN4/WFHeK4et8z6SGSVghPtWyk4g==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.6.1': resolution: {integrity: sha512-HBi1ZlwvfcUibLtT3g/lP57FaDPC799AD6InolB2KSgkqyBbZJ9wAXM8/CcH67GLIP0tZ7FqblrJTzGXxetTJQ==} @@ -1001,6 +1008,9 @@ packages: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} + artplayer@5.1.7: + resolution: {integrity: sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==} + asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -3100,6 +3110,9 @@ packages: resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} engines: {node: '>=18'} + option-validator@2.0.6: + resolution: {integrity: sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -5610,6 +5623,10 @@ snapshots: arrify@2.0.1: {} + artplayer@5.1.7: + dependencies: + option-validator: 2.0.6 + asap@2.0.6: {} async@3.2.5: {} @@ -8017,6 +8034,10 @@ snapshots: is-inside-container: 1.0.0 is-wsl: 3.1.0 + option-validator@2.0.6: + dependencies: + kind-of: 6.0.3 + optionator@0.9.4: dependencies: deep-is: 0.1.4 diff --git a/web_src/Components/artplayer.module.scss b/web_src/Components/artplayer.module.scss new file mode 100644 index 0000000..21021b5 --- /dev/null +++ b/web_src/Components/artplayer.module.scss @@ -0,0 +1,14 @@ +.artplayer { + padding: 18px; + > div:nth-child(1) { + width: 1120px; + height: 630px; + z-index: 0; + + @media (width <= 992px) { + width: auto; + height: auto; + aspect-ratio: 16/9; + } + } +} diff --git a/web_src/Components/artplayer.tsx b/web_src/Components/artplayer.tsx new file mode 100644 index 0000000..3c6cc11 --- /dev/null +++ b/web_src/Components/artplayer.tsx @@ -0,0 +1,76 @@ +import React, { forwardRef, useEffect, useRef } from "react"; +import ArtPlayer from "artplayer" +import { Title } from "./player-title"; +import * as styles from "./artplayer.module.scss" +import * as lib from "../lib" + +const ForwardArtPlayer = forwardRef(function ArtPlayer(props, ref: React.LegacyRef | undefined) { + return ( +
+ ) +}) + +const APlayer: React.FC<{ option: Omit }> = (props) => { + const { option } = props + const ref = useRef(null) + + useEffect(() => { + const art = new ArtPlayer({ + ...option, + container: ref.current! + }) + + art.on("video:loadedmetadata", async () => { + await new Promise(r => setTimeout(r, 2500)) + await art.play() + }) + + lib.redux.store.subscribe(() => { + const playingVideo = lib.redux.store.getState().redux.playingVideo + + if (encodeURI(playingVideo.playSrc) !== art.url) { + art.url = playingVideo.playSrc + art.poster = "/assets/poster/" + playingVideo?.poster + } + }) + + return () => { + if (art && art.destroy) { + art.destroy(false) + } + } + }, []) + + return ( +
+ + + </div> + ) +} + +export const Art: React.FC = (props) => { + const option: Omit<ArtPlayer["option"], "container"> = { + url: "", + screenshot: true, + autoplay: false, + setting: true, + hotkey: true, + pip: true, + mutex: true, + fullscreen: true, + fullscreenWeb: true, + playsInline: true, + autoOrientation: true, + theme: "#e85982", + autoPlayback: true, + id: "playbackid" + } + + return ( + <div> + <APlayer option={option} /> + </div> + ) +} + diff --git a/web_src/Components/file-element.module.scss b/web_src/Components/file-element.module.scss index 157dc39..7c3451c 100644 --- a/web_src/Components/file-element.module.scss +++ b/web_src/Components/file-element.module.scss @@ -4,9 +4,13 @@ @extend %element; > [class~="file-element"] { @include base.name(); - transition: color 0.2s ease-in-out; - text-overflow: ellipsis; - padding-right: 10px; + + & { + transition: color 0.2s ease-in-out; + text-overflow: ellipsis; + padding-right: 10px; + } + > [class~="name"]:hover { color: base.$main-color; } diff --git a/web_src/Components/folder-element.module.scss b/web_src/Components/folder-element.module.scss index bc4f50f..9ab772a 100644 --- a/web_src/Components/folder-element.module.scss +++ b/web_src/Components/folder-element.module.scss @@ -5,8 +5,10 @@ transition: background-color 0.2s cubic-bezier(0.215, 0.61, 0.355, 1); > [class~="folder-element"] { @include base.name(); - transition: color 0.2s ease-in-out; - cursor: pointer; + &{ + transition: color 0.2s ease-in-out; + cursor: pointer; + } > span { @include base.icon(); } diff --git a/web_src/Components/index.tsx b/web_src/Components/index.tsx index a1dc723..cd08ac6 100644 --- a/web_src/Components/index.tsx +++ b/web_src/Components/index.tsx @@ -10,4 +10,5 @@ export * from "./rename-element"; export * from "./types.d"; export * from "./player"; export * from "./video-boxes-sidebar"; -export * from "./websocket" \ No newline at end of file +export * from "./websocket" +export * from "./artplayer" \ No newline at end of file diff --git a/web_src/Components/player-title.module.scss b/web_src/Components/player-title.module.scss new file mode 100644 index 0000000..d053b38 --- /dev/null +++ b/web_src/Components/player-title.module.scss @@ -0,0 +1,38 @@ +.title { + border-left: 3px solid #e85982; + border-right: 3px solid #e85982; + padding: 0.25rem 1rem; + margin-top: 1rem; + border-radius: 5px; + + > h4 { + margin-block-end: 0em; + margin-block-start: 0em; + } + + > [class~="info"] { + display: flex; + justify-content: space-between; + + > a { + margin-block-start: 0.6em; + margin-block-end: 0em; + line-height: 22px; + font-size: 14px; + font-style: italic; + display: block; + + > span { + display: inline-block; + vertical-align: middle; + font-size: 20px; + margin-right: 3px; + } + + &:hover { + color: #e85982; + text-decoration: underline; + } + } + } +} diff --git a/web_src/Components/player-title.tsx b/web_src/Components/player-title.tsx new file mode 100644 index 0000000..2c83fa8 --- /dev/null +++ b/web_src/Components/player-title.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { FcLink } from "react-icons/fc"; +import { Link } from "react-router-dom"; +import * as lib from "../lib"; +import * as styles from "./player-title.module.scss" + +const Title: React.FC = () => { + const videoPlaying = lib.redux.useAppSelector(lib.redux.selectVideoPlaying); + const serialNumber = videoPlaying.name.match( + /([0-9]|[a-z]|[A-Z]){3,}-[0-9]{3,}/g, + ); + + return ( + <div className={styles["title"]}> + <h4> + {!!videoPlaying.name && + `${serialNumber ? serialNumber[0] : videoPlaying.name} ${videoPlaying.title}`} + {!videoPlaying.name && "没有正在播放"} + </h4> + <div className="info"> + {!!videoPlaying.actress && ( + <Link to={`/actress/${videoPlaying.actress}`}> + {videoPlaying.actress} + </Link> + )} + { + !!videoPlaying.sourceUrl && <a href={videoPlaying.sourceUrl} target="_blank" rel="noreferrer"> + <span><FcLink /></span>{"On JavDB"} + </a> + } + </div> + </div> + ); +}; + +export { Title } \ No newline at end of file diff --git a/web_src/Components/player.module.scss b/web_src/Components/player.module.scss index 14b4edf..51e3ee7 100644 --- a/web_src/Components/player.module.scss +++ b/web_src/Components/player.module.scss @@ -3,45 +3,6 @@ --plyr-color-main: #e85982; transition: all 0.2s ease-in-out; - [class~="title"] { - border-left: 3px solid #e85982; - border-right: 3px solid #e85982; - padding: 0.25rem 1rem; - margin-top: 1rem; - border-radius: 5px; - - h4 { - margin-block-end: 0em; - margin-block-start: 0em; - } - - > [class~="info"] { - display: flex; - justify-content: space-between; - - > a { - margin-block-start: 0.6em; - margin-block-end: 0em; - line-height: 22px; - font-size: 14px; - font-style: italic; - display: block; - - > span { - display: inline-block; - vertical-align: middle; - font-size: 20px; - margin-right: 3px; - } - - &:hover { - color: #e85982; - text-decoration: underline; - } - } - } - } - @media (width <= 992px) { margin: 12px; } diff --git a/web_src/Components/player.tsx b/web_src/Components/player.tsx index 0ff0111..5e15c8d 100644 --- a/web_src/Components/player.tsx +++ b/web_src/Components/player.tsx @@ -4,6 +4,7 @@ import plyrSvg from "plyr/dist/plyr.svg"; import React, { forwardRef, useEffect, useRef } from "react"; import { FcLink } from "react-icons/fc"; import { Link } from "react-router-dom"; +import { Title } from "./player-title"; import * as lib from "../lib"; @@ -11,35 +12,6 @@ import * as styles from "./player.module.scss"; const isProduction = process.env.NODE_ENV === "production"; -const Title: React.FC = () => { - const videoPlaying = lib.redux.useAppSelector(lib.redux.selectVideoPlaying); - const serialNumber = videoPlaying.name.match( - /([0-9]|[a-z]|[A-Z]){3,}-[0-9]{3,}/g, - ); - - return ( - <div className="title"> - <h4> - {!!videoPlaying.name && - `${serialNumber ? serialNumber[0] : videoPlaying.name} ${videoPlaying.title}`} - {!videoPlaying.name && "没有正在播放"} - </h4> - <div className="info"> - {!!videoPlaying.actress && ( - <Link to={`/actress/${videoPlaying.actress}`}> - {videoPlaying.actress} - </Link> - )} - { - !!videoPlaying.sourceUrl && <a href={videoPlaying.sourceUrl} target="_blank" rel="noreferrer"> - <span><FcLink /></span>{"On JavDB"} - </a> - } - </div> - </div> - ); -}; - const ForwordPlayer = forwardRef(function Player( props, ref: React.LegacyRef<HTMLVideoElement> | undefined, diff --git a/web_src/routes/error-page.tsx b/web_src/routes/error-page.tsx index 41f83be..6846b77 100644 --- a/web_src/routes/error-page.tsx +++ b/web_src/routes/error-page.tsx @@ -1,7 +1,7 @@ import { useRouteError } from "react-router"; import React from "react"; -import styles from "./error-page.module.scss"; +import * as styles from "./error-page.module.scss"; export const ErrorPage: React.FC = () => { const error = useRouteError(); diff --git a/web_src/routes/routes.tsx b/web_src/routes/routes.tsx index 578f8c2..68cf6c1 100644 --- a/web_src/routes/routes.tsx +++ b/web_src/routes/routes.tsx @@ -12,7 +12,7 @@ const router = createBrowserRouter([ path: "/", element: ( <> - <Components.Player /> + <Components.Art /> <Outlet /> </> ),