添加播放时间更新

This commit is contained in:
LittleBoy 2024-12-24 00:02:40 +08:00
parent 2e5893d3ab
commit 2b2fe09e71
4 changed files with 70 additions and 37 deletions

View File

@ -21,6 +21,7 @@ type Props = {
url?: string; cover?: string; showControls?: boolean; className?: string; url?: string; cover?: string; showControls?: boolean; className?: string;
poster?: string; poster?: string;
onChange?: (state: State) => void; onChange?: (state: State) => void;
onProgress?: (current:number,duration:number) => void;
muted?: boolean; muted?: boolean;
} }
export type PlayerInstance = { export type PlayerInstance = {
@ -84,6 +85,9 @@ export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
player.on('ended', () => { player.on('ended', () => {
setState({end: true, playing: false, error: false}) setState({end: true, playing: false, error: false})
}) })
player.on('timeupdate', () => {
props.onProgress?.(player.currentTime(), player.duration())
})
player.on('error', () => { player.on('error', () => {
setState({end: false, playing: false, error: true}) setState({end: false, playing: false, error: true})
}) })

View File

@ -32,7 +32,8 @@ export default function LiveIndex() {
muted: true, muted: true,
showToTop: false, showToTop: false,
checkedAll: false, checkedAll: false,
originSort:'' originSort: '',
playProgress: 0
}) })
const activeIndex = useRef(state.activeIndex) const activeIndex = useRef(state.activeIndex)
useEffect(() => { useEffect(() => {
@ -114,7 +115,7 @@ export default function LiveIndex() {
// console.log('origin list', res.list.map(s => s.id)) // console.log('origin list', res.list.map(s => s.id))
setVideoData(() => (res.list || [])) setVideoData(() => (res.list || []))
setState({ setState({
originSort: res.list?res.list.map(s => s.id).join(','):'' originSort: res.list ? res.list.map(s => s.id).join(',') : ''
}) })
setCheckedIdArray([]) setCheckedIdArray([])
}); });
@ -138,7 +139,7 @@ export default function LiveIndex() {
return; return;
} }
const newSort = videoData.map(s => s.id).join(',') const newSort = videoData.map(s => s.id).join(',')
if(newSort == state.originSort){ if (newSort == state.originSort) {
setEditable(false) setEditable(false)
return; return;
} }
@ -149,10 +150,10 @@ export default function LiveIndex() {
onOk: () => { onOk: () => {
//showToast('编辑成功!!!', 'info'); //showToast('编辑成功!!!', 'info');
modifyOrder(videoData.map(s => s.id)).then(() => { modifyOrder(videoData.map(s => s.id)).then(() => {
showToast('已完成直播队列的修改!','success') showToast('已完成直播队列的修改!', 'success')
setEditable(false) setEditable(false)
}).catch(() => { }).catch(() => {
showToast('调整视频顺序失败,请重试!','warning') showToast('调整视频顺序失败,请重试!', 'warning')
}) })
}, },
onCancel: () => { onCancel: () => {
@ -178,8 +179,11 @@ export default function LiveIndex() {
const currentTotalDuration = useMemo(() => { const currentTotalDuration = useMemo(() => {
if (state.activeIndex == -1 || !videoData || videoData.length == 0) return 0; if (state.activeIndex == -1 || !videoData || videoData.length == 0) return 0;
// 计算总时长 // 计算总时长
return videoData.filter((_,index)=>(index <= state.activeIndex)).reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0); return videoData
}, [videoData]) .filter((_, index) => (index < state.activeIndex))
.reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0) + state.playProgress
;
}, [videoData, state.playProgress])
const currentSelectedId = useMemo(() => { const currentSelectedId = useMemo(() => {
if (state.activeIndex < 0 || state.activeIndex >= videoData.length) return []; if (state.activeIndex < 0 || state.activeIndex >= videoData.length) return [];
@ -192,12 +196,18 @@ export default function LiveIndex() {
<div className="flex"> <div className="flex">
<div className="video-player-container mr-8 flex flex-col"> <div className="video-player-container mr-8 flex flex-col">
<div className="text-center text-base"> <div className="text-center text-base">
<span>{formatDuration(currentTotalDuration)} / {formatDuration(totalDuration)}</span> <span>: {formatDuration(currentTotalDuration)} / {formatDuration(totalDuration)}</span>
</div> </div>
<div className="video-player flex justify-center flex-1 mt-5"> <div className="video-player flex justify-center flex-1 mt-5">
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]" <div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]"
style={{backgroundColor: 'hsl(210, 100%, 48%)'}}> style={{backgroundColor: 'hsl(210, 100%, 48%)'}}>
<Player ref={player} className="w-[360px] h-[636px] bg-white" muted={true}/> <Player
ref={player} className="w-[360px] h-[636px] bg-white"
muted={true}
onProgress={(progress) => {
setState({playProgress: progress})
}}
/>
</div> </div>
</div> </div>
</div> </div>
@ -241,7 +251,8 @@ export default function LiveIndex() {
<InfiniteScroller <InfiniteScroller
ref={scrollerRef} ref={scrollerRef}
onScroll={top => setState({showToTop: top > 30})} onScroll={top => setState({showToTop: top > 30})}
onCallback={()=>{}} onCallback={() => {
}}
> >
<div className="sort-list-container flex-1"> <div className="sort-list-container flex-1">
<DndContext onDragEnd={(e) => { <DndContext onDragEnd={(e) => {

View File

@ -16,18 +16,23 @@ import ButtonToTop from "@/components/scoller/button-to-top.tsx";
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx"; import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import {IconDelete} from "@/components/icons"; import {IconDelete} from "@/components/icons";
import {useLocation} from "react-router-dom"; import {useLocation} from "react-router-dom";
import {playState} from "@/service/api/live.ts";
export default function VideoIndex() { export default function VideoIndex() {
const [editId, setEditId] = useState(-1) const [editId, setEditId] = useState(-1)
const loc = useLocation() const loc = useLocation()
const [videoData, setVideoData] = useState<VideoInfo[]>([]) const [videoData, setVideoData] = useState<VideoInfo[]>([])
const player = useRef<PlayerInstance | null>(null) const player = useRef<PlayerInstance | null>(null)
const scrollerRef = useRef<InfiniteScrollerRef|null>(null) const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
const [state, setState] = useSetState({ const [state, setState] = useSetState({
checkedAll: false, checkedAll: false,
playingIndex: -1, playingIndex: -1,
showToTop: false, showToTop: false,
showStatePos: false showStatePos: false,
playState: {
current: -1,
total: -1
}
}) })
const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([]) const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([])
@ -50,19 +55,19 @@ export default function VideoIndex() {
// 播放视频 // 播放视频
const playVideo = (video: VideoInfo, playingIndex: number) => { const playVideo = (video: VideoInfo, playingIndex: number) => {
if(state.playingIndex == playingIndex){ if (state.playingIndex == playingIndex) {
player.current?.pause(); player.current?.pause();
setState({playingIndex: -1}) setState({playingIndex: -1})
return; return;
} }
if(video.status == VideoStatus.Generating ) return; if (video.status == VideoStatus.Generating) return;
setState({playingIndex})
player.current?.play('https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-17/1186196465916190720.flv',30)
//
// if (video.oss_video_url && video.status !== 1) {
// setState({playingIndex}) // setState({playingIndex})
// player.current?.play(video.oss_video_url, 0) // player.current?.play('https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-17/1186196465916190720.flv', 30)
// } //
if (video.oss_video_url && video.status !== 1) {
setState({playingIndex})
player.current?.play(video.oss_video_url, 0)
}
} }
// 处理全选 // 处理全选
const handleAllCheckedChange = () => { const handleAllCheckedChange = () => {
@ -71,35 +76,35 @@ export default function VideoIndex() {
checkedAll: !state.checkedAll checkedAll: !state.checkedAll
}) })
} }
const handleModifySort = (items:VideoInfo[]) => { const handleModifySort = (items: VideoInfo[]) => {
modifyOrder(items.map(s => s.id)).then(() => { modifyOrder(items.map(s => s.id)).then(() => {
showToast('调整视频顺序成功!','success') showToast('调整视频顺序成功!', 'success')
}).catch(()=>{ }).catch(() => {
loadList(); loadList();
showToast('调整视频顺序失败,请重试!','warning') showToast('调整视频顺序失败,请重试!', 'warning')
}) })
} }
// //
useEffect(loadList, []) useEffect(loadList, [])
const totalDuration = useMemo(() => { const totalDuration = useMemo(() => {
if (!videoData || videoData.length == 0) return 0; if (!videoData || videoData.length == 0) return 0;
if(state.playingIndex == -1 || state.playingIndex >= videoData.length) return 0 if (state.playingIndex == -1 || state.playingIndex >= videoData.length) return 0
const v= videoData[state.playingIndex] as VideoInfo; const v = videoData[state.playingIndex] as VideoInfo;
return Math.ceil(v.duration / 1000) return Math.ceil(v.duration / 1000)
// 计算总时长 // 计算总时长
//return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0); //return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0);
}, [videoData,state.playingIndex]) }, [videoData, state.playingIndex])
useEffect(()=>{ useEffect(() => {
if(loc.state == 'push-success' && !state.showStatePos && videoData.length && scrollerRef.current){ if (loc.state == 'push-success' && !state.showStatePos && videoData.length && scrollerRef.current) {
const generatingItem = document.querySelector(`.list-item-state-${VideoStatus.Generating}`) const generatingItem = document.querySelector(`.list-item-state-${VideoStatus.Generating}`)
if(generatingItem){ if (generatingItem) {
generatingItem.scrollIntoView({behavior: 'smooth'}) generatingItem.scrollIntoView({behavior: 'smooth'})
setState({showStatePos: true}) setState({showStatePos: true})
} }
} }
},[videoData,scrollerRef]) }, [videoData, scrollerRef])
const processDeleteVideo = async (ids: Id[]) => { const processDeleteVideo = async (ids: Id[]) => {
deleteByIds(ids).then(() => { deleteByIds(ids).then(() => {
showToast('删除成功!', 'success') showToast('删除成功!', 'success')
@ -116,12 +121,21 @@ export default function VideoIndex() {
<Player <Player
ref={player} url={videoData[state.playingIndex]?.oss_video_url} ref={player} url={videoData[state.playingIndex]?.oss_video_url}
onChange={(state) => { onChange={(state) => {
console.log(state)
if (state.end || state.error) setState({playingIndex: -1}) if (state.end || state.error) setState({playingIndex: -1})
}} }}
onProgress={(current, duration) => {
setState({
playState: {
current: current,
total: duration
}
})
}}
className="w-[360px] h-[640px] bg-white"/> className="w-[360px] h-[640px] bg-white"/>
</div> </div>
</div> </div>
<div className="text-center text-sm mt-4 text-gray-400">: {formatDuration(totalDuration)}</div> <div className="text-center text-sm mt-4 text-gray-400">{formatDuration(state.playState.current)} / {formatDuration(state.playState.total)}</div>
</div> </div>
<div className="video-list-container rounded flex flex-col flex-1"> <div className="video-list-container rounded flex flex-col flex-1">
<div className="live-control flex justify-between"> <div className="live-control flex justify-between">
@ -132,7 +146,7 @@ export default function VideoIndex() {
<span className="text-sm mr-2"></span> <span className="text-sm mr-2"></span>
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/} {/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
</button> </button>
<Checkbox checked={state.checkedAll} onChange={()=>handleAllCheckedChange()} /> <Checkbox checked={state.checkedAll} onChange={() => handleAllCheckedChange()}/>
</div> </div>
</div> </div>
<div className={'video-list-sort-container flex-1'}> <div className={'video-list-sort-container flex-1'}>
@ -145,7 +159,7 @@ export default function VideoIndex() {
<div className="col operation"></div> <div className="col operation"></div>
</div> </div>
</div> </div>
<InfiniteScroller ref={scrollerRef} onScroll={top=> setState({showToTop: top > 30})}> <InfiniteScroller ref={scrollerRef} onScroll={top => setState({showToTop: top > 30})}>
{ {
videoData.length == 0 ? <div className="m-auto"><Empty/></div> : videoData.length == 0 ? <div className="m-auto"><Empty/></div> :
<div className="sort-list-container flex-1"> <div className="sort-list-container flex-1">
@ -191,7 +205,7 @@ export default function VideoIndex() {
return newArr; return newArr;
}) })
}} }}
onItemClick={ () => playVideo(v, index)} onItemClick={() => playVideo(v, index)}
onRemove={() => processDeleteVideo([v.id])} onRemove={() => processDeleteVideo([v.id])}
onEdit={v.status == VideoStatus.Generating ? undefined : () => { onEdit={v.status == VideoStatus.Generating ? undefined : () => {
setEditId(v.article_id) setEditId(v.article_id)
@ -208,7 +222,7 @@ export default function VideoIndex() {
</div> </div>
</div> </div>
<div className="page-action"> <div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)}/> <ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
{checkedIdArray.length > 0 && <ButtonBatch {checkedIdArray.length > 0 && <ButtonBatch
onProcess={deleteByIds} onProcess={deleteByIds}
selected={checkedIdArray} selected={checkedIdArray}
@ -222,7 +236,7 @@ export default function VideoIndex() {
}} }}
> >
<span className="text"></span> <span className="text"></span>
<IconDelete /> <IconDelete/>
</ButtonBatch>} </ButtonBatch>}
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/> <ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
</div> </div>

View File

@ -90,6 +90,10 @@ export function calcContentLengthLikeWord(str:string) {
// 将时长转换成 时:分:秒 // 将时长转换成 时:分:秒
export function formatDuration(duration: number) { export function formatDuration(duration: number) {
if(duration < 0 || isNaN(duration)){
return '00:00:00';
}
duration = Math.ceil(duration);
const hour = Math.floor(duration / 3600); const hour = Math.floor(duration / 3600);
const minute = Math.floor((duration - hour * 3600) / 60); const minute = Math.floor((duration - hour * 3600) / 60);
const second = duration - hour * 3600 - minute * 60; const second = duration - hour * 3600 - minute * 60;