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

如何在Express+AngularJS移动端网站实现音视频录制与保存

解决方案:AngularJS + Express + RecordRTC 实现移动端视频录制与保存

看起来你已经搭好了基础框架,我来帮你补全完整的实现流程——从前端模态框录制视频,到后端Express接收并保存到文件夹,一步到位解决问题。

1. 完善前端 AngularJS 代码

先把你的 video.partial.html 补全,加入模态框、录制控件和视频预览区:

<script src="./../../node_modules/recordrtc/RecordRTC.js"></script>
<div ng-controller="index">
  <button ng-click="openVideoModal()">Add Video</button>

  <!-- 视频录制模态框 -->
  <div class="modal fade" id="videoRecordModal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">录制视频</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">
          <!-- 视频预览区域(移动端要加playsinline避免全屏) -->
          <video id="previewVideo" autoplay playsinline controls style="width:100%;"></video>
          <div class="mt-3">
            <button ng-click="startRecording()" class="btn btn-primary" ng-disabled="isRecording">开始录制</button>
            <button ng-click="stopRecording()" class="btn btn-danger" ng-disabled="!isRecording">停止录制</button>
          </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
          <button type="button" class="btn btn-success" ng-click="saveVideo()" ng-disabled="!recordedBlob">保存视频</button>
        </div>
      </div>
    </div>
  </div>
</div>

然后补全 app.js 里的控制器逻辑,处理录制、预览和上传:

var app = angular.module('myApp', ['ngRoute', 'ngMaterial', 'ui.bootstrap']);

app.controller('index', ['$scope', '$http', function($scope, $http) {
  $scope.isRecording = false;
  $scope.recordedBlob = null;
  let recordRTCInstance = null;

  // 打开录制模态框并初始化摄像头
  $scope.openVideoModal = function() {
    $('#videoRecordModal').modal('show');
    
    // 请求摄像头和麦克风权限
    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
      .then(stream => {
        const previewVideo = document.getElementById('previewVideo');
        previewVideo.srcObject = stream;
        
        // 初始化 RecordRTC
        recordRTCInstance = RecordRTC(stream, {
          type: 'video',
          mimeType: 'video/webm' // 移动端兼容性较好的格式,也可改为video/mp4
        });
      })
      .catch(err => {
        alert('无法访问摄像头/麦克风:' + err.message);
        $('#videoRecordModal').modal('hide');
      });
  };

  // 开始录制
  $scope.startRecording = function() {
    $scope.isRecording = true;
    recordRTCInstance.startRecording();
  };

  // 停止录制并生成预览
  $scope.stopRecording = function() {
    $scope.isRecording = false;
    recordRTCInstance.stopRecording(() => {
      $scope.recordedBlob = recordRTCInstance.getBlob();
      // 切换预览为录制好的视频
      const previewVideo = document.getElementById('previewVideo');
      previewVideo.src = URL.createObjectURL($scope.recordedBlob);
      $scope.$apply(); // 手动触发Angular脏检查更新视图
    });
  };

  // 上传视频到后端
  $scope.saveVideo = function() {
    if (!$scope.recordedBlob) return;

    const formData = new FormData();
    formData.append('video', $scope.recordedBlob, `user_video_${Date.now()}.webm`);

    $http.post('/api/save-video', formData, {
      headers: { 'Content-Type': undefined }, // 让Angular自动处理FormData的Content-Type
      transformRequest: angular.identity
    })
    .then(res => {
      alert('视频保存成功!');
      $('#videoRecordModal').modal('hide');
      // 清理资源
      URL.revokeObjectURL($scope.recordedBlob);
      $scope.recordedBlob = null;
      recordRTCInstance.stream.getTracks().forEach(track => track.stop());
      recordRTCInstance = null;
    })
    .catch(err => {
      alert('视频保存失败:' + err.data?.message || '未知错误');
    });
  };

  // 模态框关闭时自动清理资源
  $('#videoRecordModal').on('hidden.bs.modal', () => {
    if (recordRTCInstance) {
      recordRTCInstance.stream.getTracks().forEach(track => track.stop());
      recordRTCInstance = null;
    }
    $scope.isRecording = false;
    $scope.recordedBlob = null;
    $scope.$apply();
  });
}]);

2. 后端 Express 实现视频保存

首先安装处理文件上传的 multer 中间件:

npm install multer --save

然后在你的 Express 主文件(比如 server.js)中添加上传路由:

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();

// 配置 multer 存储规则
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    const uploadDir = path.join(__dirname, 'uploads');
    // 如果uploads文件夹不存在则自动创建
    if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true });
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    // 自定义文件名,避免重复
    const ext = path.extname(file.originalname);
    cb(null, `video_${Date.now()}${ext}`);
  }
});

const upload = multer({
  storage: storage,
  limits: { fileSize: 100 * 1024 * 1024 } // 限制文件大小为100MB,可按需调整
});

// 处理视频上传的接口
app.post('/api/save-video', upload.single('video'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ message: '未上传视频文件' });
  }
  res.status(200).json({
    message: '视频保存成功',
    filePath: req.file.path
  });
});

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

3. 关键注意事项

  • HTTPS 要求:移动端浏览器在非HTTPS环境下会禁止访问摄像头,测试时请确保用HTTPS或者localhost(本地开发例外)。
  • 格式兼容性:video/webm 在大部分现代浏览器支持,如果需要MP4格式,可以把RecordRTC的mimeType改为video/mp4,但部分移动端可能需要额外编码支持。
  • 资源清理:录制完成或模态框关闭时一定要停止媒体流,避免占用设备摄像头/麦克风资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:51:17