如何解决React中video-react组件的Legacy Context API错误?
问题描述
基于React使用video-react库开发自定义视频播放器,目标实现点击「上一个」「下一个」按钮切换不同章节、小节视频的功能,但Player组件无法正常运行,弹出Legacy Context API相关错误。
错误截图

组件代码
import React, { useEffect, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useLocation, useNavigate, useParams } from "react-router-dom"; import { markLectureAsComplete } from "../../../services/operations/courseDetailsAPI"; import { updateCompletedLectures } from "../../../slices/viewCourseSlice"; import { Player } from "video-react"; import { BigPlayButton } from "video-react"; import "video-react/dist/video-react.css"; import IconBtn from "../../common/IconBtn"; import { AiFillPlayCircle } from "react-icons/ai"; const VideoDetails = () => { const { courseId, sectionId, subSectionId } = useParams(); const navigate = useNavigate(); const dispatch = useDispatch(); const location = useLocation(); const playerRef = useRef(); const { token } = useSelector((state) => state.auth); const { courseSectionData, courseEntireData, completedLectures } = useSelector((state) => state.viewCourse); const [previewSource, setPreviewSource] = useState(""); const [videoData, setVideoData] = useState([]); const [videoEnded, setVideoEnded] = useState(false); const [loading, setLoading] = useState(false); useEffect(() => { const setVideoSpecificDetails = async () => { if (!courseSectionData.length) return; if (!courseId && !sectionId && !subSectionId) { navigate("/dashboard/enrolled-courses"); } else { const filteredData = courseSectionData.filter( (course) => course._id === sectionId ); const filteredVideoData = filteredData?.[0].subSection.filter( (data) => data._id === subSectionId ); setVideoData(filteredVideoData[0]); setPreviewSource(courseEntireData.thumbnail); setVideoEnded(false); } }; setVideoSpecificDetails(); }, [courseSectionData, courseEntireData, location.pathname]); console.log("COURSE SECTION DATA", courseSectionData, courseEntireData); const isFirstVideo = () => { const currentSectionIndex = courseSectionData.findIndex( (data) => data._id === sectionId ); const currentSubSectionIndex = courseSectionData[ currentSectionIndex ].subSectionId.findIndex((data) => data._id === subSectionId); if (currentSectionIndex === 0 && currentSubSectionIndex === 0) { return true; } else { return false; } }; const isLastVideo = () => { const currentSectionIndex = courseSectionData.findIndex( (data) => data._id === sectionId ); const noOfSubSections = courseSectionData[currentSectionIndex].subSection.length; const currentSubSectionIndex = courseSectionData[ currentSectionIndex ].subSectionId.findIndex((data) => data._id === subSectionId); if ( currentSectionIndex === courseSectionData.length - 1 && currentSubSectionIndex === noOfSubSections - 1 ) { return true; } else { return false; } }; const goToNextVideo = () => { const currentSectionIndex = courseSectionData.findIndex( (data) => data._id === sectionId ); const noOfSubSections = courseSectionData[currentSectionIndex].subSection.length; const currentSubSectionIndex = courseSectionData[ currentSectionIndex ].subSectionId.findIndex((data) => data._id === subSectionId); if (currentSubSectionIndex !== noOfSubSections - 1) { const nextSubSectionId = courseSectionData[currentSectionIndex].subSection[ currentSectionIndex + 1 ]._id; navigate( `/view-course/${courseId}/section/${sectionId}/sub-section/${nextSubSectionId}` ); } else { const nextSectionId = courseSectionData[currentSectionIndex + 1]._id; const nextSubSectionId = courseSectionData[currentSectionIndex + 1].subSection[0]._id; navigate( `/view-course/${courseId}/section/${nextSectionId}/sub-section/${nextSubSectionId}` ); } }; const goToPrevVideo = () => { const currentSectionIndex = courseSectionData.findIndex( (data) => data._id === sectionId ); const noOfSubSections = courseSectionData[currentSectionIndex].subSection.length; const currentSubSectionIndex = courseSectionData[ currentSectionIndex ].subSectionId.findIndex((data) => data._id === subSectionId); if (currentSubSectionIndex !== 0) { const prevSubSectionId = courseSectionData[currentSectionIndex].subSection[ currentSubSectionIndex - 1 ]; navigate( `/view-course/${courseId}/section/${sectionId}/sub-section/${prevSubSectionId}` ); } else { const prevSectionId = courseSectionData[currentSectionIndex - 1]._id; const prevSubSectionLength = courseSectionData[currentSectionIndex - 1].subSection.length; const prevSubSectionId = courseSectionData[currentSectionIndex - 1].subSection[ prevSubSectionLength - 1 ]._id; navigate( `/view-course/${courseId}/section/${prevSectionId}/sub-section/${prevSubSectionId}` ); } }; const handleLectureCompletion = async () => { setLoading(true); const res = await markLectureAsComplete( { courseId: courseId, subSectionId: subSectionId }, token ); if (res) { dispatch(updateCompletedLectures(subSectionId)); } setLoading(false); }; return ( <div className="flex flex-col gap-5 text-white"> {!videoData ? ( <img src={previewSource} alt="Preview" className="h-full w-full rounded-md object-cover" /> ) : ( <Player ref={playerRef} aspectRatio="16:9" playsInline onEnded={() => setVideoEnded(true)} src={videoData?.videoUrl} > <AiFillPlayCircle fontSize={25} position="center" className="text-white" /> {videoEnded && ( <div style={{ backgroundImage: "linear-gradient(to top, rgb(0, 0, 0), rgba(0,0,0,0.7), rgba(0,0,0,0.5), rgba(0,0,0,0.1)", }} className="full absolute inset-0 z-[100] grid h-full place-content-center font-inter" > {!completedLectures.includes(subSectionId) && ( <IconBtn disabled={loading} onclick={() => handleLectureCompletion()} text={!loading ? "Mark As Completed" : "Loading..."} customClasses="text-xl max-w-max px-4 mx-auto" /> )} <IconBtn disabled={loading} onclick={() => { if (playerRef?.current) { playerRef?.current?.seek(0); setVideoEnded(false); } }} text="Rewatch" customClasses="text-xl max-w-max px-4 mx-auto mt-2" /> <div className="mt-10 flex min-w-[250px] justify-center gap-x-4 text-xl"> {!isFirstVideo() && ( <button disabled={loading} onClick={goToPrevVideo} className="blackButton" > Prev </button> )} {!isLastVideo() && ( <button disabled={loading} onClick={goToNextVideo} className="blackButton" > Next </button> )} </div> </div> )} </Player> )} <h1 className="mt-4 text-3xl font-semibold">{videoData?.title}</h1> <p className="pt-2 pb-6">{videoData?.description}</p> </div> ); }; export default VideoDetails;
解决方案
1. 解决Legacy Context API错误
video-react库已多年未维护,内部使用React旧版Context API,与React 18不兼容,有两种处理方式:
方式一:替换为维护中的视频库
推荐使用react-player(轻量)或video.js(功能丰富),两者均支持React 18:
- 安装react-player:
npm install react-player - 替换Player组件:
import ReactPlayer from 'react-player' // 原Player组件替换为: <ReactPlayer ref={playerRef} width="100%" height="100%" playsinline onEnded={() => setVideoEnded(true)} url={videoData?.videoUrl} controls />
方式二:降级React版本(不推荐)
将React和ReactDOM降级到17.x版本,同时修改入口文件渲染逻辑:
// index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
2. 修复代码逻辑错误
除库的问题外,代码中还有多处bug导致切换功能失效:
(1)subSection属性拼写错误
在isFirstVideo、isLastVideo等函数中,错误访问了不存在的subSectionId数组,实际数据结构为subSection,修正:
// 原代码 const currentSubSectionIndex = courseSectionData[currentSectionIndex].subSectionId.findIndex(...) // 修正后 const currentSubSectionIndex = courseSectionData[currentSectionIndex].subSection.findIndex( (data) => data._id === subSectionId );
(2)下一个视频索引错误
同一章节内切换下一个视频时,错误使用了章节索引作为小节索引,修正:
// 原代码 const nextSubSectionId = courseSectionData[currentSectionIndex].subSection[currentSectionIndex + 1]._id; // 修正后 const nextSubSectionId = courseSectionData[currentSectionIndex].subSection[currentSubSectionIndex + 1]._id;
(3)上一个视频ID遗漏
同一章节内切换上一个视频时,未取小节的_id属性,修正:
// 原代码 const prevSubSectionId = courseSectionData[currentSectionIndex].subSection[currentSubSectionIndex - 1]; // 修正后 const prevSubSectionId = courseSectionData[currentSectionIndex].subSection[currentSubSectionIndex - 1]._id;
(4)Player子组件不兼容
video-react的Player仅支持自身提供的子组件(如BigPlayButton),直接放入自定义图标会报错,修正:
<Player ...> <BigPlayButton position="center"> <AiFillPlayCircle fontSize={25} className="text-white" /> </BigPlayButton> {/* 其他内容 */} </Player>
(5)videoData初始值错误
videoData初始值设为空数组[],导致!videoData判断永远为false(空数组属于真值),修正:
const [videoData, setVideoData] = useState(null);
内容的提问来源于stack exchange,提问作者Kapilch232
相关产品推荐
相关产品推荐

