diff --git a/app/history/page.tsx b/app/history/page.tsx index 30d69ad..ccbba60 100644 --- a/app/history/page.tsx +++ b/app/history/page.tsx @@ -1,27 +1,21 @@ 'use client' -import {Button, ButtonGroup, Layout, List, Modal, Nav, Popconfirm, Typography} from "@douyinfe/semi-ui"; +import { Layout, Modal, Nav, Typography } from "@douyinfe/semi-ui"; import { - IconDeleteStroked, - IconEdit2Stroked, - IconHelpCircle, - IconPlusCircle, IconUserCardVideo, + IconUserCardVideo, IconVideoListStroked } from "@douyinfe/semi-icons"; -import TemplateModal from "../ui/TemplateModal"; -import { Table, Avatar } from '@douyinfe/semi-ui'; -import { IconMore } from '@douyinfe/semi-icons'; -import { ColumnProps } from "@douyinfe/semi-ui/lib/es/table/interface"; +import { Table } from '@douyinfe/semi-ui'; import { SortOrder } from "@douyinfe/semi-ui/lib/es/table"; import useSWR from "swr"; -import { fetcher, FileList, StudioEntity } from "@/app/lib/api-streamer"; +import { fetcher, FileList } from "@/app/lib/api-streamer"; import { useState } from "react"; -import Player from 'xgplayer'; -import 'xgplayer/dist/index.min.css'; -import FlvPlugin from "xgplayer-flv"; -import FlvJsPlugin from 'xgplayer-flv.js' -import Players from "@/app/ui/Player"; +import dynamic from 'next/dynamic'; import { humDate } from "@/app/lib/utils"; +const Players = dynamic(() => import("@/app/ui/Player"), { + ssr: false +}); + export default function Home() { const { Header, Footer, Sider, Content } = Layout; const { data: data, error, isLoading } = useSWR("/v1/videos", fetcher); diff --git a/app/ui/Player.tsx b/app/ui/Player.tsx index 9b2efb4..515755a 100644 --- a/app/ui/Player.tsx +++ b/app/ui/Player.tsx @@ -1,25 +1,98 @@ -import React, {useEffect} from "react"; -import Player from 'xgplayer'; -import 'xgplayer/dist/index.min.css'; -import FlvPlugin from "xgplayer-flv"; -import FlvJsPlugin from 'xgplayer-flv.js' +import React, { useEffect, useRef } from "react"; +import Artplayer from 'artplayer'; +import mpegts from 'mpegts.js'; -const Players: React.FC<{url: string}> = ({url}) => { - useEffect(() => { - let player: Player | null = new Player({ - id: 'mse', +type VideoPlayer = Artplayer | null; + +interface PlayerConfig { + url: string; + height?: string; + width?: string; +} + +function playFlv(video: HTMLVideoElement, url: string, art: Artplayer) { + if (mpegts.isSupported()) { + if (art.flv) { + art.flv.destroy(); + art.flv = null; + } + + const flv = mpegts.createPlayer({ + type: 'flv', url: url, - height: '100%', - plugins: [FlvPlugin], - // plugins: [FlvJsPlugin], - width: '100%', }); + + art.flv = flv; + art.on('destroy', () => { + if (art.flv) { + art.flv.destroy(); + art.flv = null; + } + }); + + flv.attachMediaElement(video); + flv.load(); + } else { + art.notice.show = 'Unsupported playback format: flv'; + } +} + +const Players: React.FC = ({ url, height = '100%', width = '100%' }) => { + const containerRef = useRef(null); + const playerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + + try { + if (playerRef.current) { + playerRef.current.destroy(); + playerRef.current = null; + } + + if (url.endsWith('.flv')) { + playerRef.current = new Artplayer({ + container: containerRef.current, + url, + type: 'flv', + customType: { + flv: playFlv, + }, + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + autoOrientation: true, + plugins: [], + }); + } else { + playerRef.current = new Artplayer({ + container: containerRef.current, + url, + autoSize: true, + fullscreen: true, + fullscreenWeb: true, + autoOrientation: true, + plugins: [], + }); + } + } catch (error) { + console.error('播放器初始化失败:', error); + } + return () => { - player?.destroy(); - player = null; + if (playerRef.current) { + playerRef.current.destroy(); + playerRef.current = null; + } }; - }, [url]) - return
; + }, [url, height, width]); + + return ( +
+ ); } export default Players; \ No newline at end of file diff --git a/app/ui/TemplateFields.tsx b/app/ui/TemplateFields.tsx index 6f91ced..2407fd5 100644 --- a/app/ui/TemplateFields.tsx +++ b/app/ui/TemplateFields.tsx @@ -1,7 +1,7 @@ -import React, {useState} from "react"; +import React, {useState, useMemo, useEffect} from "react"; import {FormFCChild} from "@douyinfe/semi-ui/lib/es/form"; import {IconChevronDown, IconChevronUp, IconMinusCircle, IconPlusCircle } from "@douyinfe/semi-icons"; -import {Avatar, Button, Collapsible, Form, InputGroup, Space, Typography, ArrayField, Notification} from "@douyinfe/semi-ui"; +import {Avatar, Button, Collapsible, Form, InputGroup, Space, Typography, ArrayField, Notification, ScrollList, ScrollItem} from "@douyinfe/semi-ui"; import useSWR from "swr"; import {BiliType, fetcher, StudioEntity} from "../lib/api-streamer"; import {useBiliUsers, useTypeTree} from "../lib/use-streamers"; @@ -92,6 +92,40 @@ const TemplateFields: React.FC> = setOpen(!isOpen); formApi.scrollToField('isDtime'); }; + const scrollStyle = { + border: 'unset', + boxShadow: 'unset', + width: 300, + height: 300, + }; + const allowedHoursList = new Array(24 * 15).fill(0).map((_, i) => { + return { + value: i, + text: `${i}小时`, + disabled: i < 4 || i >= 24 * 15, + }; + }); + const allowedMinutesList = new Array(60 / 5).fill(0).map((_, i) => { + return { + value: i * 5, + text: `${i * 5}分钟`, + }; + }); + const [selectedHours, setSelectedHours] = useState(4); + const [selectedMinutes, setSelectedMinutes] = useState(0); + + useEffect(() => { + const dtime = formApi.getValue('dtime'); + if (dtime) { + const hours = Math.floor(dtime / 3600); + const minutes = Math.floor((dtime % 3600) / 60); + if (hours >= 4 && hours < 24 * 15) { + setSelectedHours(hours); + setSelectedMinutes(Math.floor(minutes / 5) * 5); + } + } + }, [formApi]); + return( <>
@@ -186,10 +220,41 @@ const TemplateFields: React.FC> =
- (当前+2小时 ≤ 可选时间 ≤ 当前+15天,转载稿件撞车判定以过审发布时间为准) + (当前+2小时 ≤ 可选时间 ≤ 当前+15天,转载稿件撞车判定以过审发布时间为准。上传速度不佳的机器请谨慎开启,或设置更大的延迟时间。)
{values.isDtime ? ( - + { + const delaySeconds = (selectedHours * 3600) + (selectedMinutes * 60); + formApi.setValue('dtime', delaySeconds); + Notification.success({ + title: '保存成功', + content: `延迟时间:${selectedHours}小时${selectedMinutes}分钟`, + duration: 3, + position: 'top' + }); + console.log(delaySeconds); + }}> + 确认 + + }> + item.value === selectedHours)} + onSelect={(item) => setSelectedHours(item.value)} + /> + item.value === selectedMinutes)} + onSelect={(item) => setSelectedMinutes(item.value)} + /> + ) : null}
diff --git a/app/ui/plugins/bilibili.tsx b/app/ui/plugins/bilibili.tsx index 21af2e4..0cb1d20 100644 --- a/app/ui/plugins/bilibili.tsx +++ b/app/ui/plugins/bilibili.tsx @@ -175,9 +175,10 @@ const Bilibili: React.FC = (props) => { rules={[ { validator: (rule, value) => { + value = value ?? []; return Array.isArray(value) && value.every(item => /^(cn|ov)-gotcha\d+$/.test(item)); }, - message: '例: cn-gotcha204, ov-gotcha05' + message: '例: cn-gotcha204,ov-gotcha05' } ]} /> @@ -262,7 +263,7 @@ const Bilibili: React.FC = (props) => { rules={[ { validator: (rule, value) => { - value = value ?? (console.log(value), []); + value = value ?? []; return Array.isArray(value) && value.every(item => /^cn-[a-z]{2,6}-[a-z]{2,4}(-[0-9]{2}){2}$/.test(item)); }, message: '例: cn-hjlheb-cu-01-01,cn-tj-ct-01-01' diff --git a/app/upload-manager/page.tsx b/app/upload-manager/page.tsx index ea177c7..5993545 100644 --- a/app/upload-manager/page.tsx +++ b/app/upload-manager/page.tsx @@ -84,6 +84,13 @@ export default function Union() { key: v.key, }; }); + const [transferData, setTransferData] = useState<(string | number)[]>([]); + + const handleTransferChange = (values: (string | number)[], items: any[]) => { + setSelectFiles(values); + setTransferData(values); + }; + return (<> =1.16.0 + afterClose={handleAfterClose} onCancel={handleCancel} closeOnEsc={true} > - setSelectFiles(values)} - /> - + +