Compare commits

...

2 Commits

Author SHA1 Message Date
74b52562f4 feat: 添加自动播放 2024-12-16 21:48:48 +08:00
983b35f914 feat: 添加自动播放 2024-12-16 19:50:17 +08:00
4 changed files with 116 additions and 45 deletions

View File

@ -51,7 +51,7 @@ export const VideoListItem = (
className={`video-item-info flex gap-2 flex-1 bg-gray-100 h-[80px] overflow-hidden rounded-lg p-3 shadow-blue-500 ${active ? 'video-item-shadow' : ''}`}>
<div className={'video-title leading-7 flex-1'}>{video.title || video.video_title}</div>
<div className={'video-item-cover bg-white rounded-md overflow-hidden'}>
<img className="w-[100px] h-[56px] object-cover" src={video.cover_url || video.cover || ImageCover} alt={video.video_title}/>
<img className="w-[100px] h-[56px] object-cover" src={video.cover || ImageCover} alt={video.video_title}/>
</div>
</div>
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400">

View File

@ -1,25 +1,34 @@
import React, {useEffect, useState} from "react";
import {Button, message, Modal} from "antd";
import React, {useEffect, useMemo, useRef, useState} from "react";
import {Button, Modal} from "antd";
import {SortableContext, arrayMove} from '@dnd-kit/sortable';
import {DndContext} from "@dnd-kit/core";
import {VideoListItem} from "@/components/video/video-list-item.tsx";
import {deleteByIds, getList, modifyOrder, playState} from "@/service/api/live.ts";
import styles from './style.module.scss'
import {set} from "lodash";
import {showErrorToast, showToast} from "@/components/message.ts";
import ButtonBatch from "@/components/button-batch.tsx";
import FlvJs from "flv.js";
import {formatDuration} from "@/util/strings.ts";
import {useSetState} from "ahooks";
import {set} from "lodash";
const cache: { flvPlayer?: FlvJs.Player,timerPlayNext?:any,timerLoadState?:any } = {}
export default function LiveIndex() {
const videoRef = useRef<HTMLVideoElement | null>(null)
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
const [modal, contextHolder] = Modal.useModal()
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
const [editable, setEditable] = useState<boolean>(false)
const [state, setState] = useState({
const [state, setState] = useSetState({
activeIndex: -1,
muted: true,
})
const activeIndex = useRef(state.activeIndex)
useEffect(()=>{
activeIndex.current = state.activeIndex
},[state.activeIndex])
const showVideoItem = (index: number) => {
// 找到对应video item 并显示在视图可见区域
@ -41,41 +50,97 @@ export default function LiveIndex() {
}
const activeToNext = (index?: number) => {
const endToFirst = index != undefined && index > -1 ? false : state.activeIndex >= videoData.length - 1
const activeIndex = index != undefined && index > -1 ? index : (endToFirst ? 0 : state.activeIndex + 1)
setState(() => {
return {
activeIndex
}
})
const endToFirst = index != undefined && index > -1 ? false : activeIndex.current >= videoData.length - 1
const _activeIndex = index != undefined && index > -1 ? index : (endToFirst ? 0 : activeIndex.current + 1)
setState({activeIndex:_activeIndex})
if (endToFirst) {
showToast('即将播放第一条视频');
}
// 找到对应video item 并显示在视图可见区域
showVideoItem(activeIndex)
showVideoItem(_activeIndex)
return _activeIndex;
}
const initPlayingState = (videoList?: LiveVideoInfo[] | null) => {
const list = videoList || videoData || []
playState().then(ret => {
setState({
activeIndex: list.findIndex(v => v.id === ret.id)
})
const playVideo = (video: LiveVideoInfo, liveState: LiveState) => {
if (videoRef.current && video.video_oss_url) {
if(cache.timerPlayNext) clearTimeout(cache.timerPlayNext)
const duration = Math.ceil(video.video_duration / 1000)
const playedTime =( Date.now() / 1000 >> 0) - liveState.live_start_time
if (playedTime < 0 || playedTime > duration) { // 已播放时间大于总时长了
//initPlayingState() // 重新获取播放状态
return;
}
if (/mp4$/i.test(video.video_oss_url)) {
videoRef.current!.src = video.video_oss_url
if(liveState.live_start_time > 0 && playedTime > 0) videoRef.current!.currentTime = playedTime
videoRef.current!.play()
return;
}
if (FlvJs.isSupported()) {
// 已经有播放实例 则销毁
if (cache.flvPlayer) {
cache.flvPlayer.pause()
cache.flvPlayer.unload()
}
cache.flvPlayer = FlvJs.createPlayer({
type: 'flv',
url: video.video_oss_url
})
cache.flvPlayer.attachMediaElement(videoRef.current!)
cache.flvPlayer.load()
if(liveState.live_start_time > 0 && playedTime > 0) videoRef.current!.currentTime = playedTime
cache.flvPlayer.play()
cache.timerPlayNext = setTimeout(()=>{
const index = activeToNext(),nextVideo = videoData[index]
playVideo(nextVideo,{live_start_time:(Date.now() / 1000 >> 0),id:nextVideo.id})
},(duration - playedTime) * 1000)
}
}
}
const initPlayingState = () => {
if(cache.timerLoadState) clearTimeout(cache.timerLoadState)
if(videoData.length == 0) {
cache.timerLoadState = setTimeout(initPlayingState, 1000)
return;
}
playState().then(liveState => {
const video = videoData.find(v => v.id === liveState.id)
if (video) {
activeToNext(videoData.findIndex(v => v.id === liveState.id))
playVideo(video, liveState)
} else {
setState({activeIndex: -1})
cache.timerLoadState = setTimeout(initPlayingState, 5000)
}
});
}
const clearAllTimer = ()=>{
if(cache.timerPlayNext) clearTimeout(cache.timerPlayNext)
if(cache.timerLoadState) clearTimeout(cache.timerLoadState)
}
const loadList = () => {
clearAllTimer();
getList().then(res => {
console.log('origin list', res.list.map(s => s.id))
setVideoData(res.list || [])
// console.log('origin list', res.list.map(s => s.id))
setVideoData(()=>(res.list || []))
setCheckedIdArray([])
initPlayingState(res.list)
});
}
useEffect(loadList, [])
useEffect(()=>{
loadList()
initPlayingState();
return clearAllTimer;
}, [])
const processDeleteVideo = async (ids: number[]) => {
deleteByIds(ids).then(()=>{
showToast('删除成功!','success')
deleteByIds(ids).then(() => {
showToast('删除成功!', 'success')
loadList()
}).catch(showErrorToast)
}
@ -108,16 +173,32 @@ export default function LiveIndex() {
})
}
const totalDuration = useMemo(() => {
if (!videoData || videoData.length == 0) return 0;
// 计算总时长
return videoData.reduce((sum, v) => sum + v.video_duration, 0);
}, [videoData])
return (<div className="container py-10 page-live">
{contextHolder}
<div className="flex">
<div className="video-player-container mr-8 flex flex-col">
<div className="text-center text-base"></div>
<div className="video-player flex justify-center flex-1 mt-5">
<div className="live-player rounded overflow-hidden w-[360px] h-[700px]">
{/*<iframe src="https://fm.gachafun.com/" className="border-0 w-full h-full max-h-full"></iframe>*/}
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]" style={{backgroundColor:'hsl(210, 100%, 48%)'}}>
<video ref={videoRef} autoPlay muted={state.muted}
className="w-[360px] rounded overflow-hidden h-full object-contain"></video>
{state.muted && state.activeIndex != -1 && <div className="absolute inset-0 flex items-center justify-center">
<Button onClick={()=>{
setState({muted: false})
videoRef.current!.muted= false;
}}></Button>
</div>}
</div>
</div>
<div className="mt-4 text-center text-sm">
<span>: {formatDuration(totalDuration)}</span>
</div>
</div>
<div className="video-list-container flex-1">
<div className=" bg-white py-8 px-6 rounded">
@ -155,22 +236,11 @@ export default function LiveIndex() {
const {active, over} = e;
if (over && active.id !== over.id) {
let oldIndex = -1, newIndex = -1;
// const originArr = [...videoData]
setVideoData((items) => {
oldIndex = items.findIndex(s => s.id == active.id);
newIndex = items.findIndex(s => s.id == over.id);
return arrayMove(items, oldIndex, newIndex);
});
// modal.confirm({
// title: '提示',
// content: '是否要移动到指定位置',
// onCancel: () => {
// setVideoData(originArr);
// },
// onOk: () => {
// setVideoData([...videoData])
// }
// })
}
}}>
<SortableContext items={videoData}>

View File

@ -1,10 +1,7 @@
import {post} from "@/service/request.ts";
export function playState() {
return post<{
id: number;
start_time?: number;
}>({url: '/room/playing'})
return post<LiveState>({url: '/room/playing'})
}
export function getList() {

8
src/types/api.d.ts vendored
View File

@ -83,7 +83,7 @@ declare interface ListCrawlerNewsItem extends BasicArticleInfo {
declare interface VideoInfo {
id: number;
title: string;
cover?: string;
cover: string;
oss_video_url: string;
duration: number;
article_id: number;
@ -94,10 +94,14 @@ declare interface LiveVideoInfo {
id: number;
video_id: number;
video_title: string;
cover_url: string;
cover: string;
video_duration: number;
video_oss_url: string;
status: number;
order_no: string;
}
declare interface LiveState{
id: number;
live_start_time?: number;
}