如何从Node.js向React.js传输视频列表并实现前端播放?
问题描述
Node.js后端从Google Cloud存储获取视频并返回的接口代码:
app.get("/getGalleryVideos", async (req, res) => { const [files] = await bucket.getFiles(bucketName, { prefix: req.query.user }); const fileNames = files.map((file) => file.name); const sumFiles = []; for (let i = 0; i < fileNames.length; i++) { await bucket .file(fileNames[i]) .download() .then((data) => { sumFiles.push(data); }); } res.json(sumFiles); });
React前端接收响应并尝试转换为video可用格式的测试代码:
function getGalleryVideos() { var user = "undefined"; if (typeof Cookies.get("loggedInUser") != "undefined") { user = Cookies.get("loggedInUser"); } axios({ method: "get", url: `http://localhost:3001/getGalleryVideos`, params: { user: user, }, timeout: 100000, }).then((res) => { console.log("res"); console.log(res); console.log("res.data"); console.log(res.data); console.log("res.data[1]"); console.log(res.data[1]); console.log("res.data[1][0]"); console.log(res.data[1][0]); console.log("res.data[1][0].data"); console.log(res.data[1][0].data); const vid = new Blob(res.data[1][0].data, { type: "video/mp4" }); console.log("vid"); console.log(vid); console.log("url"); console.log(URL.createObjectURL(vid)); setVideo(URL.createObjectURL(vid)); }); }
出现的问题:转换为Blob前缓冲区大小为773491,转换后变为1987122,数据体积异常膨胀,生成的URL无法正常播放视频。
修复方案及优化方案
一、修复现有代码问题
问题核心:后端用res.json()返回二进制数据时,JSON会自动将Buffer转为Base64字符串,前端接收后转Blob时会出现数据损坏和体积膨胀。
后端修改
改为流式传输单个视频,同时返回视频列表让前端按需请求:
// 接口1:返回用户的视频列表(文件名+请求地址) app.get("/getGalleryVideoList", async (req, res) => { const [files] = await bucket.getFiles(bucketName, { prefix: req.query.user }); const videoList = files.map(file => ({ name: file.name, requestUrl: `/getSingleVideo?file=${encodeURIComponent(file.name)}` })); res.json(videoList); }); // 接口2:单个视频的流式传输接口 app.get("/getSingleVideo", async (req, res) => { const fileName = decodeURIComponent(req.query.file); const file = bucket.file(fileName); // 设置正确的视频响应头 res.setHeader("Content-Type", "video/mp4"); // 直接用流传输,避免占用过多服务器内存 const readStream = file.createReadStream(); readStream.pipe(res); // 处理流错误 readStream.on("error", () => { res.status(500).send("视频加载失败"); }); });
前端修改
先获取视频列表,再请求单个视频并指定responseType: 'blob'直接接收Blob:
function getGalleryVideos() { const user = Cookies.get("loggedInUser") || "undefined"; // 先获取视频列表 axios.get(`http://localhost:3001/getGalleryVideoList`, { params: { user } }).then(listRes => { // 取第一个视频测试 const firstVideoUrl = listRes.data[0].requestUrl; // 请求单个视频,指定返回Blob类型 axios.get(firstVideoUrl, { responseType: 'blob', timeout: 100000 }).then(vidRes => { const videoUrl = URL.createObjectURL(vidRes.data); setVideo(videoUrl); }).catch(err => { console.error("视频加载失败:", err); }); }).catch(err => { console.error("视频列表获取失败:", err); }); }
二、更优的视频传输方案
1. 直接返回GCS签名URL
无需后端中转视频流,后端生成带有效期的Google Cloud存储签名URL,前端直接从GCS下载视频,大幅降低服务器压力:
app.get("/getSignedVideoUrls", async (req, res) => { const [files] = await bucket.getFiles(bucketName, { prefix: req.query.user }); // 批量生成签名URL const signedUrls = await Promise.all(files.map(async file => { const [url] = await file.getSignedUrl({ action: "read", expires: "03-01-2025" // 设置URL有效期,格式为MM-DD-YYYY }); return { name: file.name, playUrl: url }; })); res.json(signedUrls); });
前端直接使用签名URL作为video标签的src:
function getGalleryVideos() { const user = Cookies.get("loggedInUser") || "undefined"; axios.get(`http://localhost:3001/getSignedVideoUrls`, { params: { user } }).then(res => { // 取第一个视频测试 setVideo(res.data[0].playUrl); }).catch(err => { console.error("签名URL获取失败:", err); }); }
2. 流媒体传输(大视频场景)
如果视频体积较大,建议使用HLS/DASH流媒体协议。Google Cloud存储支持直接托管流媒体文件,后端可生成对应的播放列表URL,前端使用video.js等专用库进行播放,实现分段加载、清晰度切换等功能。
内容的提问来源于stack exchange,提问作者Ameen Izhac
相关产品推荐
相关产品推荐

