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

React TypeScript视频播放器字幕点击按钮不显示问题求助

字幕无法显示问题排查请求

将WebDevSimplified的原生JS视频播放器转为React TypeScript项目后,遇到字幕无法显示的问题:点击字幕按钮后,字幕未正常展示。已通过console.log确认字幕状态与videoRef.current.textTracks[0].mode同步(hidden对应false,showing对应true),但问题仍存在,怀疑问题出在Subtitles.vtt文件的路径或格式上。以下是相关代码片段、VTT文件内容及文件结构:

JSContextProvider.tsx

import { useReducer, useEffect, useRef, ReactNode } from 'react';
import { JSContext, ContextProps } from './JSContext';

export type JSReducerState = {
  captions: boolean;
};

const initialState: JSReducerState = {
  captions: false,
};

export type JSReducerAction =
  | { type: 'SET_CAPTIONS'; payload: boolean };

const reducer = (
  state: JSReducerState,
  action: JSReducerAction
): JSReducerState => {
  switch (action.type) {
    case 'SET_CAPTIONS':
      return { ...state, captions: action.payload };
    default:
      return state;
  }
};

export const JSProvider = ({ children }: { children: ReactNode }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const videoRef = useRef<HTMLVideoElement | null>(null);

  const captionsHandler = () => {
    if (videoRef.current) {
      const captions = videoRef.current.textTracks[0];
      
      if (!captions) {
        return;
      }
  
      if (captions.mode === 'hidden') {
        captions.mode = 'showing';
      } else {
        captions.mode = 'hidden';
      }
      dispatch({ type: 'SET_CAPTIONS', payload: captions.mode === 'showing' });
    }
  };

  const handleKeyPress = (e: KeyboardEvent) => {
    const tagName = document.activeElement?.tagName.toLowerCase();
    switch (e.key.toLowerCase()){
      case "c":
        captionsHandler();
        break;
      default:
        return;
    }
  };

  const videoKeyDownHandler = (e: React.KeyboardEvent<HTMLVideoElement>) => {
    handleKeyPress(e as unknown as KeyboardEvent);
  };

  useEffect(() => {
    window.addEventListener('keydown', handleKeyPress);
    return () => {
      window.removeEventListener('keydown', handleKeyPress);
    };
  }, [handleKeyPress]);

  const contextValue: ContextProps = {
    state,
    dispatch,
    videoRef,
    captionsHandler,
  };

  return (
    <JSContext.Provider value={contextValue}>
      {children}
    </JSContext.Provider>
  );
};

export default JSProvider;  

JSContext.ts

import { createContext, createRef } from 'react';
import { JSReducerAction, JSReducerState } from './JSContextProvider';

export type ContextProps = {
  state: JSReducerState;
  dispatch: React.Dispatch<JSReducerAction>;
  videoRef: React.RefObject<HTMLVideoElement | null>;
  captionsHandler: () => void;
};

export const JSContext = createContext<ContextProps>({
  state: {
    captions: false,
  },
  dispatch: () => {},
  videoRef: createRef(),
  captionsHandler: () => {},
});

VideoStyles.module.css

.captionsBtn{
    border-bottom: 3px solid rgba(255,255,255,1);
    filter: brightness(110%) drop-shadow(0 0 5px rgba(255, 255, 255, 0.5)) grayscale(10%);
}

VideoPlayer.tsx

import { useJSContext } from '../../../Context/UseJSContext';
import test5 from './Assets/test5.mp4';
import subtitles from './Assets/PreviewImgs/Subtitles.vtt';
import CaptionsBtn from './Btns/CaptionsBtn';
import classes from './VideoStyles.module.css';

const VideoPlayer = () => {
  const {
    videoRef,
    pauseHandler,
    playHandler,
    state,
    videoKeyDownHandler,
    playPauseHandler,
  } = useJSContext();

  return (
    <div className={`${classes.videoContainer}`}>
      <img className={classes.thumbnailImg} />
      <div className={classes.videoControlsContainer}>
        <div className={classes.controls}>
          <CaptionsBtn />
        </div>
      </div>

      <video
        className={`${state.isTheater === true ? `${classes.theater}` : ''}`}
        src={test5}
        ref={videoRef}
        onPause={pauseHandler}
        onPlay={playHandler}
        onEnded={pauseHandler}
        onKeyDown={videoKeyDownHandler}
        onClick={() => {
          playPauseHandler();
        }}
        autoPlay={state.display === 'video'}
      >
        <track 
          kind="captions" 
          srcLang="en" 
          src={subtitles}
          label="English"
          default
        />
      </video>
    </div>
  );
};

export default VideoPlayer;

Subtitles.vtt

WEBVTT

00:00:00.000 --> 00:00:03.000
This is test 5, test 5.

文件结构

文件结构


排查修正要点:

  1. VTT文件路径问题:React中不能直接使用相对路径引用静态资源,需像引入视频一样通过import导入VTT文件,或把VTT放到public目录下使用绝对路径。
  2. 代码语法错误:原JSContextProvider.tsx中存在变量名不一致(KSReducerState和JSReducerState混用)、函数定义位置错误(handleKeyPress等函数定义在组件函数外部)的问题,已修正。
  3. VTT格式优化:去掉了多余的序号和破折号,标准VTT格式中时间线后直接写字幕内容即可。
  4. track标签完善:添加label属性让浏览器识别字幕语言,增加default属性确保字幕轨道被正确加载。
  5. Context类型补全:JSContext.ts中补充了缺失的ContextProps类型定义,避免TypeScript类型报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 18:04:53