You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中通过MUI滑块设置HTML5 video的currentTime总是重置为0

解决MUI Slider控制HTML5 Video进度重置为0的问题

你遇到的问题核心来自两个关键错误,直接导致滑块拖动时视频进度被重置为0:


1. 错误的currentTime赋值类型

HTML5 Video的currentTime属性要求接收数字类型的值,但你在handleChange里把newValue转成了字符串:

videoRef.current.currentTime = `${newValue}`; // 错误:字符串赋值

当传入字符串时,浏览器解析失败会自动将currentTime设为0,这是进度重置的直接原因。

2. 重复绑定事件导致状态冲突

你的video元素同时绑定了onProgress和onTimeUpdate到同一个handleTimeUpdate函数:

<video
    onProgress={handleTimeUpdate} <!-- 多余绑定 -->
    onTimeUpdate={handleTimeUpdate}
    ...
/>

onProgress是在视频加载缓冲进度变化时触发的,此时视频的currentTime可能还处于初始状态,会错误地将videoTime重置为0,覆盖滑块设置的进度。


修复方案

修正handleChange函数

直接传入数字类型的newValue:

const handleChange = (e, newValue) => {
    videoRef.current.currentTime = newValue; // 移除字符串转换
    setVideoTime(newValue);
};

移除多余的onProgress绑定

只保留onTimeUpdate监听播放进度变化:

<video
    id="viewer"
    style={{ aspectRatio: "16/9", borderRadius: '4px' }}
    width="100%"
    height="100%"
    ref={videoRef}
    src={src}
    onLoadedMetadata={handleVideoLoaded}
    onTimeUpdate={handleTimeUpdate}
/>

额外优化:视频结束逻辑

原代码中视频结束判断可能因精度问题提前触发,增加误差阈值避免误判:

const handleTimeUpdate = (e) => {
    const current = e.target.currentTime;
    setVideoTime(current);
    // 增加0.1秒误差阈值,确保视频确实播放完毕
    if (current >= duration - 0.1) {
        handlePause();
        setIsPlaying(false);
        setVideoTime(0);
    }
};

完整修正后的组件代码

import React, { useState, useRef, useEffect, memo } from 'react';
import { Stack, IconButton, Typography } from '@mui/material';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import PauseIcon from '@mui/icons-material/Pause';
import ContentCutIcon from '@mui/icons-material/ContentCut';
import Slider from '@mui/material/Slider';
import { useSelector } from 'react-redux';
import { VideoPlayerGrayColor } from 'theme/colors';
import { calculateTimeWithSeconds } from 'utils/helperFunction';
import { STAGING_URL } from 'utils/constants';

function VideoViewer({ handleTrim }) {
    const activeMedia = useSelector(state => state?.mediaLibrary?.activeMedia)
    const videoRef = useRef(null);
    const [videoTime, setVideoTime] = useState(0);
    const [isPlaying, setIsPlaying] = useState(false);
    const [duration, setDuration] = useState(0);
    const [src, setSrc] = useState(`${STAGING_URL}${activeMedia?.uploaded_file}`)

    const handleChange = (e, newValue) => {
        videoRef.current.currentTime = newValue;
        setVideoTime(newValue);
    };

    const handleTimeUpdate = (e) => {
        const current = e.target.currentTime;
        setVideoTime(current);
        if (current >= duration - 0.1) {
            handlePause();
            setIsPlaying(false);
            setVideoTime(0);
        }
    };

    const handlePause = () => {
        setIsPlaying(false);
        videoRef.current.pause();
    };

    const togglePlayPause = () => {
        if (videoRef.current.paused) {
            if (videoRef.current.currentTime >= duration - 0.1) {
                videoRef.current.currentTime = 0;
            }
            videoRef.current.play();
        } else {
            videoRef.current.pause();
        }
        setIsPlaying(p => !p);
    };

    const handleVideoLoaded = () => {
        const duration = videoRef.current?.duration || 0;
        setDuration(duration);
    };

    useEffect(() => {
        setSrc(`${STAGING_URL}${activeMedia?.uploaded_file}`);
    }, [activeMedia])

    return (
        <Stack width="100%" height="100%" gap={1}>
            <IconButton onClick={handleTrim} sx={{ borderRadius: '50%', width: 30, padding: '8px 20px', border: "1px solid #E1E1E6" }}>
                <ContentCutIcon />
            </IconButton>
            <video
                id="viewer"
                style={{ aspectRatio: "16/9", borderRadius: '4px' }}
                width="100%"
                height="100%"
                ref={videoRef}
                src={src}
                onLoadedMetadata={handleVideoLoaded}
                onTimeUpdate={handleTimeUpdate}
            />
            <Stack direction="row" justifyContent="flex-end" alignItems="center">
                <Stack direction="row" minWidth="55%" justifyContent="space-between" alignItems="center">
                    <IconButton onClick={togglePlayPause} disableRipple>
                        {isPlaying ? <PauseIcon sx={{ color: VideoPlayerGrayColor, fontSize: "2rem" }} /> : <PlayArrowIcon sx={{ color: VideoPlayerGrayColor, fontSize: "2rem" }} />}
                    </IconButton>
                    <Stack minWidth="25%">
                        <Slider
                            value={videoTime}
                            onChange={handleChange}
                            scale={calculateTimeWithSeconds}
                            valueLabelDisplay="auto"
                            max={duration}
                        />
                        <Typography textAlign="end" color={() => VideoPlayerGrayColor} fontSize={12} fontWeight={500}>{calculateTimeWithSeconds(videoTime)} / {calculateTimeWithSeconds(duration)}</Typography>
                    </Stack>
                </Stack>
            </Stack>
        </Stack>
    );
}

export default memo(VideoViewer);

内容的提问来源于stack exchange,提问作者Meer Humza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:21:03