React视频画廊播放报错:Cannot read properties of undefined (reading 'play')
视频画廊单次播放功能问题解决
问题概述
开发通过map渲染的视频画廊时,无法实现单次播放一个视频的功能,触发错误:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'play')
尝试将播放函数改为async/await后问题仍存在,该写法在单个视频场景下正常,但多视频场景失效。
核心错误原因
- 单个Ref无法绑定多个元素:原代码中使用单个
useRef()绑定所有视频元素,map循环结束后,vidRef.current只会指向最后一个渲染的视频。点击其他视频的播放按钮时,无法获取对应视频的引用,调用play()时就会出现undefined错误。 - 全局状态导致控件失控:
showControls是全局状态,一旦设置为true,所有视频都会显示控件,不符合“单次播放一个视频”的需求。 - 属性名错误:原代码中视频和播放按钮上写的是
refs,正确的React ref属性名是ref,这也会导致ref无法正确绑定。
解决方案
1. 使用Ref数组存储每个视频引用
用useRef([])创建一个ref数组,在map循环中为每个视频元素分配对应的数组索引,确保能精准获取每个视频的DOM元素。
2. 维护独立的播放状态
用状态记录当前正在播放的视频索引,代替全局的showControls,实现单个视频的控件显示和播放状态控制。
3. 优化播放逻辑
播放指定视频时,先暂停其他正在播放的视频,再播放当前视频,确保同一时间只有一个视频在播放。
修改后的完整代码
export default function VideoGallery() { const [videoData, setVideoData] = useState([]); // 记录当前正在播放的视频索引,初始为null表示无视频播放 const [playingIndex, setPlayingIndex] = useState(null); // 用ref数组存储每个视频元素的引用 const vidRefs = useRef([]); // 播放指定索引的视频 const playVideo = async (index) => { // 暂停之前正在播放的视频 if (playingIndex !== null && vidRefs.current[playingIndex]) { await vidRefs.current[playingIndex].pause(); } // 获取当前点击的视频元素 const currentVideo = vidRefs.current[index]; if (currentVideo) { await currentVideo.play(); setPlayingIndex(index); } }; // 视频播放结束后重置状态 const handleVideoEnd = (index) => { if (playingIndex === index) { setPlayingIndex(null); } }; return ( <div className="relative w-full p-1 group"> <div className="flex flex-wrap -m-1 md:-m-2"> {videoData.map((data, index) => { const videoURL = data.videoURL.replace( "https://firebasestorage.googleapis.com", "" ); return ( <div key={data.videoURL} className="relative flex w-full md:w-1/3 sm:w-1/2 flex-wrap group" > <div className="relative w-full p-1 group"> <video src={videoURL} preload="metadata" volume={1} // 仅当前播放的视频显示控件 controls={playingIndex === index} controlsList="nodownload" // 将视频元素存入ref数组对应位置 ref={(el) => (vidRefs.current[index] = el)} onEnded={() => handleVideoEnd(index)} /> {/* 仅未播放的视频显示播放按钮 */} {playingIndex !== index && ( <div className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2"> <BsPlayCircle className="text-[#fa9204] text-6xl cursor-pointer" onClick={() => playVideo(index)} /> </div> )} </div> </div> ); })} </div> </div> ); }
关键修改点说明
- 将单个
vidRef改为vidRefs数组,每个视频对应数组中的一个索引,确保能准确获取目标视频元素。 - 用
playingIndex状态替代全局的showControls,精准控制单个视频的控件显示和播放状态。 - 播放函数接收索引参数,先暂停之前的视频再播放当前视频,实现“单次播放一个视频”的需求。
- 修正了原代码中错误的
refs属性,改为正确的ref属性,确保ref绑定生效。 - 添加
onEnded事件监听,视频播放结束后自动重置播放状态,恢复播放按钮显示。
内容的提问来源于stack exchange,提问作者Coolkid
相关产品推荐
相关产品推荐

