本教程详细指导如何在JavaScript前端录制视频后,通过Fetch API将其上传至Django后端进行文件系统存储和数据库关联。文章涵盖了前端视频数据处理、csrf令牌管理以及django视图层接收文件并保存的完整流程,旨在帮助开发者实现视频录制与持久化存储的无缝集成。
在现代web应用中,用户生成内容的场景日益增多,其中视频录制与上传是常见需求。本教程将深入探讨如何将前端javascript录制的视频文件,通过异步请求(fetch api)安全有效地传输至django后端,实现文件的持久化存储于文件系统,并将其关联信息记录到数据库中。与传统表单提交方式不同,我们采用更灵活的api驱动方法,优化用户体验并提升开发效率。
1. 前端视频录制与数据封装
首先,我们依赖JavaScript的MediaDevices API(getUserMedia)进行视频流获取,并使用MediaRecorder API进行录制。录制完成后,视频数据以Blob(二进制大对象)的形式存储在recordedBlobs数组中。为了将此Blob发送到服务器,我们需要将其封装为FormData对象。
1.1 录制流程回顾
前端html结构包含video元素用于显示摄像头预览和录制回放,以及控制按钮(开始、录制、播放、下载)。JavaScript代码负责:
- 通过navigator.mediaDevices.getUserMedia获取摄像头和麦克风权限及流。
- 实例化MediaRecorder对象,并监听ondataavailable事件收集视频数据块。
- 在stopRecording时,recordedBlobs数组包含了完整的视频数据。
1.2 封装视频数据为FormData
当用户点击“下载”按钮时,除了传统的客户端下载逻辑外,我们还需要触发上传至后端的动作。核心在于将recordedBlobs合并成一个Blob,然后将其转换为File对象并添加到FormData中。
// ... (保留原有的JavaScript录制逻辑,例如mediaRecorder、recordedBlobs等) const errorMsgElement = document.querySelector('span#errorMsg'); const recordedVideo = document.querySelector('video#recorded'); const recordButton = document.querySelector('button#record'); const playButton = document.querySelector('button#play'); const downloadButton = document.querySelector('button#download'); // 获取CSRF Token的辅助函数 function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } // 上传函数:负责将FormData发送到Django后端 async function upload(formData) { try { // 这里的URL '/video-app' 需与Django的urls.py配置保持一致 const response = await fetch("/video-app", { method: "POST", headers: { "X-CSRFToken": getCookie('csrftoken'), // 附带CSRF Token,确保Django安全认证 }, body: formData, // 发送FormData对象,Fetch API会自动设置Content-Type为multipart/form-data }); const result = await response.JSon(); // 解析后端返回的json响应 console.log("上传响应:", result); // 根据后端返回的result处理,例如显示成功或失败消息 if (result.error_code === 0) { alert('视频上传成功!'); } else { alert('视频上传失败: ' + result.message); } } catch (error) { console.error("上传错误:", error); alert('视频上传过程中发生错误!'); } } // 修改下载按钮的点击事件,增加上传逻辑 downloadButton.addEventListener('click', () => { // 将录制的Blob数据合并为一个完整的MP4视频Blob const blob = new Blob(recordedBlobs, {type: 'video/mp4'}); // 1. 上传到服务器 const formData = new FormData(); // 将Blob转换为File对象,命名为'my-interview.mp4',并作为'video'字段添加到FormData formData.append("video", new File([blob], 'my-interview.mp4')); upload(formData); // 调用上传函数 // 2. 客户端下载(如果不需要客户端下载,可以移除以下代码) const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = 'w3-coder-recorder-test.mp4'; // 客户端下载的文件名 document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); window.URL.revokeObjectURL(url); }, 100); }); // ... (保留原有的start/stop Recording, play, handleSuccess, init等函数) // 确保MediaRecorder的ondataavailable和onstop事件处理逻辑正确 function handleDataAvailable(event) { console.log('handleDataAvailable', event); if (event.data && event.data.size > 0) { recordedBlobs.push(event.data); } } function startRecording() { recordedBlobs = []; let options = {mimeType: 'video/webm;codecs=vp9,opus'}; try { mediaRecorder = new MediaRecorder(window.stream, options); } catch (e) { console.error('Exception while creating MediaRecorder:', e); errorMsgElement.innerHTML = `Exception while creating MediaRecorder: ${JSON.stringify(e)}`; return; } recordButton.textContent = 'Stop Recording'; playButton.disabled = true; downloadButton.disabled = true; mediaRecorder.onstop = (event) => { console.log('Recorder stopped: ', event); console.log('Recorded Blobs: ', recordedBlobs); }; mediaRecorder.ondataavailable = handleDataAvailable; mediaRecorder.start(); } function stopRecording() { mediaRecorder.stop(); recordButton.textContent = 'Record'; // 停止后将按钮文本改回“Record” playButton.disabled = false; downloadButton.disabled = false; }
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END