From 0680171f460f8d26552051fb865a83fd77f5a114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E7=99=BD-=E7=99=BD?= Date: Sun, 27 Apr 2025 09:41:17 +0800 Subject: [PATCH] feat(video player): support flv cloudreve/Cloudreve#2300 (#254) * feat(video player): support flv cloudreve/Cloudreve#2300 * fix(video player): ignore case in file extension * fix(video player): specify the video type Since the link may not include the file extension. --- package.json | 1 + src/component/Viewers/Video/Artplayer.tsx | 53 ++++++++++++++++----- src/component/Viewers/Video/VideoViewer.tsx | 2 +- yarn.lock | 17 +++++++ 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index b390e0f..90dad8f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "lodash": "^4.17.21", "material-ui-popup-state": "^5.0.10", "monaco-editor": "^0.49.0", + "mpegts.js": "^1.8.0", "mui-one-time-password-input": "^2.0.1", "notistack": "^3.0.1", "nuqs": "^2.3.1", diff --git a/src/component/Viewers/Video/Artplayer.tsx b/src/component/Viewers/Video/Artplayer.tsx index c2ef56d..c84226f 100644 --- a/src/component/Viewers/Video/Artplayer.tsx +++ b/src/component/Viewers/Video/Artplayer.tsx @@ -1,8 +1,10 @@ import { Box, BoxProps } from "@mui/material"; +import { fileExtension } from "../../../util"; import Artplayer from "artplayer"; import artplayerPluginChapter from "artplayer-plugin-chapter"; import artplayerPluginHlsControl from "artplayer-plugin-hls-control"; import Hls, { HlsConfig } from "hls.js"; +import mpegts from 'mpegts.js'; import i18next from "i18next"; import { useEffect, useRef } from "react"; import "./artplayer.css"; @@ -96,6 +98,26 @@ const playM3u8 = } }; +const playFlv = + (video: HTMLVideoElement, url: string, art: Artplayer) => { + if (mpegts.isSupported()) { + if (art.flv) art.flv.destroy(); + const flv = mpegts.createPlayer({ + type: 'flv', + url: url, + }, { + lazyLoadMaxDuration: 5 * 60, + accurateSeek: true, + }); + flv.attachMediaElement(video); + flv.load(); + art.flv = flv; + art.on('destroy', () => flv.destroy()); + } else { + art.notice.show = 'Unsupported playback format: flv'; + } + }; + export default function Player({ option, chapters, @@ -105,12 +127,27 @@ export default function Player({ ...rest }: PlayerProps) { const artRef = useRef(); + const ext = fileExtension(option.title); useEffect(() => { const opts = { ...option, - plugins: [ - ...option.plugins, + plugins: [...option.plugins], + container: artRef.current, + customType: { + ...option.customType, + m3u8: playM3u8(m3u8UrlTransform, getEntityUrl), + flv: playFlv, + }, + type: ext, + }; + + if (chapters) { + opts.plugins.push(artplayerPluginChapter({ chapters })); + } + + if (ext === "m3u8") { + opts.plugins.push( artplayerPluginHlsControl({ quality: { // Show qualitys in control @@ -135,17 +172,9 @@ export default function Player({ auto: i18next.t("application:fileManager.auto"), }, }), - ], - container: artRef.current, - customType: { - ...option.customType, - m3u8: playM3u8(m3u8UrlTransform, getEntityUrl), - }, - }; - - if (chapters) { - opts.plugins.push(artplayerPluginChapter({ chapters })); + ); } + const art = new Artplayer(opts); if (getInstance && typeof getInstance === "function") { diff --git a/src/component/Viewers/Video/VideoViewer.tsx b/src/component/Viewers/Video/VideoViewer.tsx index 8720cb4..66eb256 100644 --- a/src/component/Viewers/Video/VideoViewer.tsx +++ b/src/component/Viewers/Video/VideoViewer.tsx @@ -100,7 +100,7 @@ const VideoViewer = () => { } const firstLoad = !currentExpire.current; - const isM3u8 = viewerState.file.name.endsWith(".m3u8"); + const isM3u8 = fileExtension(viewerState.file.name) === "m3u8"; if (isM3u8) { // For m3u8, use masked url const crFileUrl = new CrUri(getFileLinkedUri(viewerState.file)); diff --git a/yarn.lock b/yarn.lock index b4cfb80..a59b4d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4690,6 +4690,11 @@ es6-iterator@^2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" +es6-promise@^4.2.5: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + es6-symbol@^3, es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.4" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c" @@ -6574,6 +6579,14 @@ monaco-editor@^0.49.0: resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.49.0.tgz#4e80e9859feb2c421def3cef194d12d822606472" integrity sha512-2I8/T3X/hLxB2oPHgqcNYUVdA/ZEFShT7IAujifIPMfKkNbLOqY8XCoyHCXrsdjb36dW9MwoTwBCFpXKMwNwaQ== +mpegts.js@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/mpegts.js/-/mpegts.js-1.8.0.tgz#b944751df0e811be880c0d1e32dcd721f5ff9550" + integrity sha512-ZtujqtmTjWgcDDkoOnLvrOKUTO/MKgLHM432zGDI8oPaJ0S+ebPxg1nEpDpLw6I7KmV/GZgUIrfbWi3qqEircg== + dependencies: + es6-promise "^4.2.5" + webworkify-webpack xqq/webworkify-webpack + ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" @@ -8229,6 +8242,10 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +"webworkify-webpack@github:xqq/webworkify-webpack": + version "2.1.5" + resolved "https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"