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

react-native-video全屏弹窗时进度条异常问题求助

问题解决:React Native视频全屏Modal双实例播放冲突

问题根源

你当前代码里渲染了两个独立的<Video>组件实例,且都绑定了同一个videoPlayer ref、相同的播放状态(paused、currentTime等)和回调函数。Modal弹出时,背景的Video未停止,两个实例同时播放,各自触发onProgress更新同一个currentTime状态,导致进度条在两个实例的播放进度间来回跳动。

解决方案

核心思路是只保留一个Video实例,根据全屏状态切换它的渲染位置(原容器/Modal),而非渲染两个独立实例。具体修改步骤如下:

修改后的完整代码

import React, { useRef, useState } from 'react';
import { View, StyleSheet, TouchableOpacity, Modal, Alert, Text } from 'react-native';
import Video from 'react-native-video';
import MediaControls from 'react-native-media-controls';

const PLAYER_STATES = {
  PLAYING: 'playing',
  PAUSED: 'paused',
  ENDED: 'ended',
};

const ChatVideoPlayerComponent = () => {
  const videoPlayer = useRef<any>(null);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [isPlayerLoading, setIsPlayerLoading] = useState(true);
  const [paused, setPaused] = useState(false);
  const [playerState, setPlayerState] = useState(PLAYER_STATES.PLAYING);
  const [modalVisible, setModalVisible] = useState(false);

  const onSeek = (seek: number) => {
    videoPlayer.current?.seek(seek);
  };

  const onPaused = (state: string) => {
    setPaused(!paused);
    setPlayerState(state);
  };

  const onReplay = () => {
    setPlayerState(PLAYER_STATES.PLAYING);
    setPaused(false);
    videoPlayer.current?.seek(0);
  };

  const onProgress = (data: { currentTime: number }) => {
    if (!isPlayerLoading && playerState !== PLAYER_STATES.ENDED) {
      setCurrentTime(data.currentTime);
    }
  };

  const onLoad = (data: { duration: number }) => {
    setDuration(data.duration);
    setIsPlayerLoading(false);
  };

  const onLoadStart = () => setIsPlayerLoading(true);

  const onEnd = () => setPlayerState(PLAYER_STATES.ENDED);

  const onError = () => console.log('视频播放出错');

  const toggleFullScreen = () => {
    setModalVisible(!modalVisible);
  };

  const renderToolbar = () => (
    <View>
      <Text style={styles.toolbar}> toolbar </Text>
    </View>
  );

  const onSeeking = (time: number) => setCurrentTime(time);

  // 统一渲染视频与控制栏的复用组件
  const renderVideoWithControls = (containerStyle: any) => (
    <View style={{ position: 'relative', flex: 1, ...containerStyle }}>
      <Video
        ref={videoPlayer}
        source={{ uri: 'https://assets.mixkit.co/videos/download/mixkit-countryside-meadow-4075.mp4' }}
        style={styles.video}
        resizeMode={modalVisible ? 'cover' : 'cover'}
        paused={paused}
        onEnd={onEnd}
        onLoad={onLoad}
        onLoadStart={onLoadStart}
        onProgress={onProgress}
        onError={onError}
        repeat={true}
        volume={10}
      />
      <MediaControls
        duration={duration}
        isLoading={isPlayerLoading}
        mainColor="#333"
        onFullScreen={toggleFullScreen}
        onPaused={onPaused}
        onReplay={onReplay}
        onSeek={onSeek}
        onSeeking={onSeeking}
        playerState={playerState}
        progress={currentTime}
        toolbar={renderToolbar()}
      />
    </View>
  );

  return (
    <View style={styles.container}>
      {/* 非全屏状态下的视频 */}
      {!modalVisible && renderVideoWithControls({ width: 200, height: 200 })}

      {/* 全屏Modal */}
      <Modal
        animationType="slide"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => {
          Alert.alert('Modal已关闭');
          toggleFullScreen();
        }}
      >
        <View style={styles.modalContainer}>
          <View style={styles.modalContent}>
            <TouchableOpacity style={styles.closeBtn} onPress={toggleFullScreen}>
              <Text style={styles.closeTxt}>×</Text>
            </TouchableOpacity>
            {/* 全屏状态下的视频 */}
            {renderVideoWithControls({ width: '100%' })}
          </View>
        </View>
      </Modal>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    justifyContent: 'flex-start',
  },
  video: {
    width: '100%',
    height: '100%',
  },
  toolbar: {
    marginTop: 30,
    backgroundColor: 'red',
    padding: 10,
    borderRadius: 5,
  },
  modalContainer: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#000000A0',
  },
  modalContent: {
    marginHorizontal: 10,
    backgroundColor: 'white',
    borderRadius: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
    flex: 1,
  },
  closeBtn: {
    alignSelf: 'flex-end',
    padding: 15,
  },
  closeTxt: {
    fontSize: 24,
    color: '#333',
  },
});

export default ChatVideoPlayerComponent;

关键修改说明

  • 移除重复的<Video>和<MediaControls>实例,通过renderVideoWithControls函数统一渲染,彻底避免双实例冲突。
  • 用toggleFullScreen简化全屏切换逻辑,仅控制Modal显示状态,删除冗余的screenType等状态管理。
  • 两个状态下的视频共用同一个videoPlayer ref,确保播放进度、暂停状态完全同步。
  • 优化Modal关闭逻辑,保持播放状态的一致性。

内容的提问来源于stack exchange,提问作者pratteek shaurya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 16:25:35