Bootstrap

vue3.0+TS+RecordRtc+javaSpringBoot实现前端录屏并保存本地\下载

vue+TS部分

首先我们下载RecordRtc相关包

npm install recordrtc

接着我们开始进入需要录制的页面

导入相关包

import RecordRTC from "recordrtc";

声明相关变量

<template>
// 其他代码
// 提示弹窗
<QuitDialog ref="quitRef"></QuitDialog>
</template>
​
let recorder: RecordRTC | null = null;
let recordingStream: MediaStream | null = null;
// 用于提示当离开界面时弹出提示,点击确定:停止录制并跳转,点击取消:取消跳转
const quitRef = ref<InstanceType<typeof QuitDialog> | null>(null);
// 进入页面开始录制,用户需要选择录制区域(无法避免,浏览器隐私限制)
onMounted(() => {
  autoStartRecording();
});
// 打开弹窗并传递参数
const openQuitDialog = (url: string) => {
  console.log("准备进入弹窗");
  const params = {
    recorder: recorder,
    recordedVideo: undefined,
    recordingStream: recordingStream,
    url: url
  };
  quitRef.value?.acceptParams(params);
};
​
//开始录屏
const autoStartRecording = () => {
  console.log("开始录屏");
  navigator.mediaDevices
    .getDisplayMedia({ video: true })
    .then(stream => {
      recordingStream = stream; // 保存录制的流
      console.log(recordingStream);
      recorder = new RecordRTC(stream, {
        type: "video"
      });
      recorder.startRecording();
    })
    .catch(error => {
      console.error("Error accessing screen:", error);
    });
};
​

接着我们开始编写弹窗代码

<template>
  <!-- 视频录制弹窗组件 -->
  <el-dialog v-model="dialogVisible" title="提示" width="500px" draggable>
    <!-- 提示内容 -->
    <span>确认退出?点击确认后将自动保存本次操作内容</span>
​
    <!-- 底部按钮区域 -->
    <template #footer>
      <span class="dialog-footer">
        <!-- 取消按钮 -->
        <el-button @click="closeDialog">取消</el-button>
        <!-- 确认按钮 -->
        <el-button type="primary" @click="confirm">确认</el-button>
      </span>
    </template>
  </el-dialog>
</template>
​
<script setup lang="ts">
import { ref } from "vue";
import router from "@/routers";
import RecordRTC from "recordrtc";
import { uploadVideo } from "@/api/modules/upload";
​
// 弹窗可见状态
const dialogVisible = ref(false);
​
// 录制相关参数
let recordedVideo: Blob | undefined;
​
interface DrawerProps {
  recorder: RecordRTC | null;
  recordingStream: MediaStream | null;
  url: string;
}
​
// 弹窗属性
const drawerProps = ref<DrawerProps>({
  recorder: null,
  recordingStream: null,
  url: ""
});
​
// 接收父组件传递的参数并显示弹窗
const acceptParams = (params: DrawerProps) => {
  console.log("已经进入弹窗");
  drawerProps.value = params;
  dialogVisible.value = true;
  console.log("drawerProps: " + JSON.stringify(drawerProps.value));
};
​
// 关闭弹窗
const closeDialog = () => {
  dialogVisible.value = false;
};
​
// 返回首页并关闭弹窗
const goHome = (url: string) => {
  dialogVisible.value = false;
  router.push(url);
};
​
// 处理确认按钮点击事件
const confirm = async () => {
  console.log("drawerProps.value?: " + drawerProps.value);
  autoStopRecording();
  goHome(drawerProps.value?.url);
};
​
// 停止录制操作
const autoStopRecording = async () => {
  console.log("停止录屏mi");
  if (drawerProps.value?.recorder) {
    console.log("recorder不为null");
    await drawerProps.value?.recorder.stopRecording(() => {
      recordedVideo = drawerProps.value?.recorder?.getBlob();
      stopScreenSharing(); // 在停止录制时停止屏幕共享
    });
  }
};
​
// 下载录制的视频内容
const downloadVideo = async () => {
  console.log("开始下载录屏内容");
  if (recordedVideo) {
    console.log("recordedVideo不为null");
    try {
      const url = URL.createObjectURL(recordedVideo);
      const response = await fetch(url);
      const data = await response.blob();
      // 创建FormData对象,用于将文件上传到后端
      const formData = new FormData();
      formData.append("video", data, "recorded-video.webm");
​
      // 上传视频到后端
      const result = await uploadVideo(formData);
      console.log("result: " + result);
​
      // 创建并点击一个隐藏的<a>标签以触发下载
      const a = document.createElement("a");
      document.body.appendChild(a);
      a.href = url;
      a.download = "recorded-video.webm";
      a.click();
      window.URL.revokeObjectURL(url);
    } catch (error) {
      console.error("Error uploading video:", error);
    }
  } else {
    console.log("recordedVideo为null");
  }
};
​
// 停止屏幕共享
const stopScreenSharing = () => {
  if (drawerProps.value?.recordingStream) {
    const tracks = drawerProps.value?.recordingStream.getTracks();
    tracks.forEach((track: { stop: () => void }) => track.stop());
  }
  // 下载录制的视频
  downloadVideo();
};
​
// 暴露acceptParams方法供父组件调用
defineExpose({ acceptParams });
</script>

此时前端的录制,停止录制,客户端本地下载都已完成

接下来是关于JavaSpringBoot后端的代码

@RestController
@RequestMapping("/api")
public class VideoController {
​
    /**
     * 上传视频文件接口
     *
     * @param file 视频文件,通过RequestParam注解获取
     * @return ResponseEntity<String> 包含上传结果的响应实体
     */
    @PostMapping("/video")
    public ResponseEntity<String> uploadVideo(@RequestParam("video") MultipartFile file) {
        System.out.println("执行请求");
​
        // 判断文件是否为空
        if (file.isEmpty()) {
            return ResponseWrapper.responseEntityFail("程序文件为空");
        }
​
        // 指定存储目录
        String uploadDir = "D:/programSoftware/java/AIDetectCloudPlatform/recordVideo/";
​
        // 获取当前时间作为文件名的一部分
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
        String currentDate = dateFormat.format(new Date());
​
        try {
            // 获取原始文件名
            String originalFileName = file.getOriginalFilename();
​
            // 构建新的文件名,添加时间戳防止文件名冲突
            String newFileName = currentDate + originalFileName.substring(originalFileName.lastIndexOf("."));
​
            // 构建文件路径
            String filePath = uploadDir + newFileName;
​
            // 创建目标目录(如果不存在)
            File directory = new File(uploadDir);
            if (!directory.exists()) {
                directory.mkdirs();
            }
​
            // 保存文件
            file.transferTo(new File(filePath));
​
            System.out.println("保存成功");
​
            // 返回成功的响应,包含上传视频的访问URL
            return ResponseWrapper.responseEntityAccept("http://localhost:8080/video/" + newFileName);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseWrapper.responseEntityFail("文件保存失败");
        }
    }
}

通过以上代码即可接收前端发送来的文件并本地保存

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;