fix: dtime (#1121)
* fix dtime * change npm mirror * 发布时间离当前时间必须≥4小时且≤15天 * 使用 artplayer 替换 西瓜播放器 * 自动切换播放器 * 控件修改为 ScrollList * 移除 西瓜播放器
This commit is contained in:
1 parent
bad5849a01
commit
dfdd92ae60
9 files changed
+4216
-1842
No files matched your search
+9
-15
@@ -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<FileList[]>("/v1/videos", fetcher);
|
||||
|
||||
+90
-17
@@ -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<PlayerConfig> = ({ url, height = '100%', width = '100%' }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const playerRef = useRef<VideoPlayer>(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 <div id="mse"></div>;
|
||||
}, [url, height, width]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{ width, height }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Players;
|
||||
@@ -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<FormFCChild<StudioEntity & {isDtime: boolean}>> =
|
||||
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(
|
||||
<>
|
||||
<Section text={'基本信息'}>
|
||||
@@ -186,10 +220,41 @@ const TemplateFields: React.FC<FormFCChild<StudioEntity & {isDtime: boolean}>> =
|
||||
|
||||
<div style={{display: 'flex', alignItems: 'center', color: 'var(--semi-color-tertiary)'}}>
|
||||
<Switch field='isDtime' label={{ text: '定时发布' }} checkedText="|" uncheckedText="〇"/>
|
||||
<span style={{paddingLeft: 12, fontSize: 12}}>(当前+2小时 ≤ 可选时间 ≤ 当前+15天,转载稿件撞车判定以过审发布时间为准)</span>
|
||||
<span style={{paddingLeft: 12, fontSize: 12}}>(当前+2小时 ≤ 可选时间 ≤ 当前+15天,转载稿件撞车判定以过审发布时间为准。上传速度不佳的机器请谨慎开启,或设置更大的延迟时间。)</span>
|
||||
</div>
|
||||
{values.isDtime ? (
|
||||
<DatePicker field="dtime" label=' ' type='dateTime' fieldStyle={{ paddingTop: 0 }} />
|
||||
<ScrollList style={scrollStyle} header={'延迟时间'} footer={
|
||||
<Button size="small" type="primary" onClick={() => {
|
||||
const delaySeconds = (selectedHours * 3600) + (selectedMinutes * 60);
|
||||
formApi.setValue('dtime', delaySeconds);
|
||||
Notification.success({
|
||||
title: '保存成功',
|
||||
content: `延迟时间:${selectedHours}小时${selectedMinutes}分钟`,
|
||||
duration: 3,
|
||||
position: 'top'
|
||||
});
|
||||
console.log(delaySeconds);
|
||||
}}>
|
||||
确认
|
||||
</Button>
|
||||
}>
|
||||
<ScrollItem
|
||||
mode="wheel"
|
||||
cycled={true}
|
||||
list={allowedHoursList}
|
||||
type={1}
|
||||
selectedIndex={allowedHoursList.findIndex(item => item.value === selectedHours)}
|
||||
onSelect={(item) => setSelectedHours(item.value)}
|
||||
/>
|
||||
<ScrollItem
|
||||
mode="wheel"
|
||||
cycled={true}
|
||||
list={allowedMinutesList}
|
||||
type={2}
|
||||
selectedIndex={allowedMinutesList.findIndex(item => item.value === selectedMinutes)}
|
||||
onSelect={(item) => setSelectedMinutes(item.value)}
|
||||
/>
|
||||
</ScrollList>
|
||||
) : null}
|
||||
</Section>
|
||||
|
||||
|
||||
@@ -175,9 +175,10 @@ const Bilibili: React.FC<Props> = (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> = (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'
|
||||
|
||||
@@ -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 (<>
|
||||
<UserList visible={visible} onCancel={change}></UserList>
|
||||
<Modal
|
||||
@@ -92,17 +99,18 @@ export default function Union() {
|
||||
okText="上传"
|
||||
visible={visibleModal}
|
||||
onOk={handleOk}
|
||||
afterClose={handleAfterClose} //>=1.16.0
|
||||
afterClose={handleAfterClose}
|
||||
onCancel={handleCancel}
|
||||
closeOnEsc={true}
|
||||
>
|
||||
<Transfer
|
||||
style={{ width: 568, height: 416 }}
|
||||
dataSource={data}
|
||||
draggable
|
||||
onChange={(values, items) => setSelectFiles(values)}
|
||||
/>
|
||||
</Modal>
|
||||
<Transfer
|
||||
style={{ width: 568, height: 416 }}
|
||||
dataSource={data}
|
||||
draggable
|
||||
value={transferData}
|
||||
onChange={handleTransferChange}
|
||||
/>
|
||||
</Modal>
|
||||
<Header style={{ backgroundColor: 'var(--semi-color-bg-1)' }}>
|
||||
<nav
|
||||
style={{
|
||||
@@ -112,7 +120,7 @@ export default function Union() {
|
||||
alignItems:'center',
|
||||
justifyContent:'space-between',
|
||||
flexWrap:'wrap',
|
||||
boxShadow:'0 1px 2px 0 rgb(0 0 0 / 0.05)'
|
||||
boxShadow:'0 1px 2px 0 rgb(0 0 0 / 0.05)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
Vendored
+1
-1
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
+4
-3
@@ -1,9 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// reactStrictMode: true,
|
||||
output: 'export',
|
||||
images:{
|
||||
unoptimized:true
|
||||
}
|
||||
images: {
|
||||
unoptimized: true
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
Generated
+4013
-1780
File diff suppressed because it is too large.
Load diff
+10
-11
@@ -9,26 +9,25 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@douyinfe/semi-ui": "^2.59.1",
|
||||
"@douyinfe/semi-ui": "^2.72.0",
|
||||
"artplayer": "5.1.7",
|
||||
"mpegts.js": "^1.8.0",
|
||||
"ncp": "^2.0.0",
|
||||
"next": "^14.2.3",
|
||||
"next": "^14.2.21",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-json-tree": "^0.19.0",
|
||||
"sass": "^1.77.4",
|
||||
"swr": "^2.2.5",
|
||||
"xgplayer": "^3.0.14",
|
||||
"xgplayer-flv": "^3.0.18",
|
||||
"xgplayer-flv.js": "^3.0.18"
|
||||
"sass": "^1.83.0",
|
||||
"swr": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.13.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "^14.2.3",
|
||||
"typescript": "^5.4.5",
|
||||
"eslint-config-prettier": "^9.1.0"
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-next": "^15.1.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user