首先定义获取媒体设备的方法
var MediaUtils = {/*** 获取用户媒体设备(处理兼容的问题)* @param videoEnable {boolean} - 是否启用摄像头* @param audioEnable {boolean} - 是否启用麦克风* @param callback {Function} - 处理回调*/getUserMedia: function (videoEnable, audioEnable, callback) {navigator.getUserMedia =navigator.getUserMedia ||navigator.webkitGetUserMedia ||navigator.mozGetUserMedia ||navigator.msGetUserMedia ||window.getUserMedia;var constraints = { video: videoEnable, audio: audioEnable };if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {callback(false, stream);})["catch"](function (err) {callback(err);});} else if (navigator.getUserMedia) {navigator.getUserMedia(constraints,function (stream) {callback(false, stream);},function (err) {callback(err);});} else {callback(new Error("Not support userMedia"));}},/*** 关闭媒体流* @param stream {MediaStream} - 需要关闭的流*/closeStream: function (stream) {if (typeof stream.stop === "function") {stream.stop();} else {let trackList = [stream.getAudioTracks(), stream.getVideoTracks()];for (let i = 0; i < trackList.length; i++) {let tracks = trackList[i];if (tracks && tracks.length > 0) {for (let j = 0; j < tracks.length; j++) {let track = tracks[j];if (typeof track.stop === "function") {track.stop();}}}}}},
};
var mediaRecorder, mediaStream, stopRecordCallback, recorderFile;
点击开启摄像头按钮或进入页面时,打开摄像头获取实时画面,注意:此处并非已经开始录像
// 调用摄像头callCamera() {let _this = this;MediaUtils.getUserMedia(true, true, function (err, stream) {if (err) {throw err;} else {// 通过 MediaRecorder 记录获取到的媒体流mediaRecorder = new MediaRecorder(stream, {mimeType: "video/webm;codecs=vp9",});mediaStream = stream;var chunks = [],startTime = 0;var video = _this.$refs.videos;video["srcObject"] = stream;video.play();// 播放实时画面mediaRecorder.ondataavailable = function (e) {mediaRecorder.blobs.push(e.data);chunks.push(e.data);};mediaRecorder.blobs = [];mediaRecorder.onstop = function (e) {recorderFile = new Blob(chunks, {type: mediaRecorder.mimeType,});console.log(recorderFile);var url = URL.createObjectURL(recorderFile);var videosreplay = _this.$refs.videosreplay;videosreplay.setAttribute("src", url);chunks = [];if (null != stopRecordCallback) {stopRecordCallback();}};}});},
点击开始录像时开启recorder,或结束录像
record() {if (this.recordtype == "ING") {this.stopRecord(() => {console.log("结束录制");});}if (this.recordtype == "BEGIN") {this.startAudio();mediaRecorder.start();this.recordtype = "ING";}},// 对录像时长进行记录
startAudio() {this.timer = setInterval(() => {this.recordtime += 1000;if (this.recordtime == 60000) {this.stopRecord();}this.second++;if (this.second >= 60) {this.second = 0;this.minute = this.minute + 1;}if (this.minute >= 60) {this.minute = 0;this.hour = this.hour + 1;}}, 1000);},// 停止录像时终止录制器,关闭媒体流并清除时长记录定时器
stopRecord(callback) {this.recordtype = "END";this.showReplay = true;stopRecordCallback = callback;clearInterval(this.timer);// 终止录制器mediaRecorder.stop();// 关闭媒体流MediaUtils.closeStream(mediaStream);var videosreplay = this.$refs.videosreplay;videosreplay.onended = () => {this.playtime = 0;this.replayVideo = false;clearInterval(this.playtimer);};videosreplay.onclick = () => {this.showReplay = !this.showReplay;};},
录制完成后,在页面中显示回放画面,可以直接播放本地录制的流文件,也可以上传服务器后播放服务器返回的文件地址。此处要注意的是,播放回放的容器与录制画面的容器不能是同一个,否则会导致录制失败,而两个容易也不能用v-if控制,否则在录制完成后会提示找不到播放容器
<videostyle="position:absolute;":style="recordtype=='END'?'z-index:3':'z-index:1'"id="videosreplay"class="local-video"src=""ref="videosreplay"></video><videoid="video"class="local-video"autoplayref="videos"></video><div@click="toggleReplayVideo"v-if="recordtype=='END'&&showReplay"style="position:absolute;z-index:3;"><imgv-if="!replayVideo"src="~assets/image/audiorecord/video-replay.png"style="width:224rem;height:224rem;"alt=""><imgv-if="replayVideo"src="~assets/image/audiorecord/video-repause.png"style="width:224rem;height:224rem;"alt=""></div>
点击容器中的回放按钮,开始播放录制好的画面,多次点击切换播放及暂停效果。前面录制时有给播放容器设置播放结束事件,当播放完成后清除播放时长及按钮显示。
// 回放toggleReplayVideo() {this.replayVideo = !this.replayVideo;this.showReplay = false;var videosreplay = this.$refs.videosreplay;if (this.replayVideo) {videosreplay.play();this.playtimer = setInterval(() => {this.playtime += 1000;}, 1000);} else {videosreplay.pause();clearInterval(this.playtimer);}},
将录制好的视频上传到服务器
submit() {var file = new File([recorderFile],"msr-" + new Date().toISOString().replace(/:|\./g, "-") + ".mp4",{type: "video/mp4",});var formdata = new FormData();formdata.append("file", file);this.axios.post("/api/public/common/fileUpload", formdata).then((res) => {if (!res.error) {} else {this.tool.showNotify("err", res.msg);}});},
至此就完成了视频录制相关功能。
音频录制在结构中用到的是audio标签而不是video,并且在录制中不需要,只是在回放的时候需要,因此只需要一个容器。其余调用计算机媒体录制相关配置与调用摄像头相同,在调用getUserMedia时设置videoEnable为true,audioEnable为false
<audioid="mp3Btn"ref="audio"src=""></audio>
let audio;
点击开始录音时,调用媒体录制开始方法并计时
toggleAudio() {if (this.type == "ING") {// 结束录音this.stopRecord(() => {console.log("结束录制");});}if (this.type == "NEW") {// 开始录音this.startAudio();mediaRecorder.start();this.type = "ING";}},startAudio() {this.timer = setInterval(() => {this.recordtime += 1000;if (this.recordtime == 60000) {this.stopRecord();}this.second++;if (this.second >= 60) {this.second = 0;this.minute = this.minute + 1;}if (this.minute >= 60) {this.minute = 0;this.hour = this.hour + 1;}}, 1000);},
在录制过程中可以实现暂停录制的功能
pauseAudio() {this.audioPause = !this.audioPause;if (!this.audioPause) {this.startAudio();// 继续开始计时mediaRecorder.resume(); // 恢复录制} else {mediaRecorder.pause(); // 暂定录制clearInterval(this.timer);}},
录音结束后还需要对生成的流文件进行不同格式的转化,此处也与视频录制不同,请注意与上问视频录制中的结束函数对比
mediaRecorder.onstop = function (e) {recorderFile = new Blob(chunks, { type: "audio/mp3" });console.log(recorderFile);var url = URL.createObjectURL(recorderFile);var audio = _this.$refs.audio;audio.setAttribute("src", url);chunks = [];if (null != stopRecordCallback) {stopRecordCallback();}};
录制结束主要调用
mediaRecorder.stop();
// 关闭媒体流
MediaUtils.closeStream(mediaStream);
其他需要完成的也可以一并,比如清除录制时长定时器,配置音频文件播放完成成需要做的事等,可以参考视频录制结束。
回放功能也与视频回放一致,上传服务器时需要将生成的类型为mp3的流文件处理一下
submit() {var file = new File([recorderFile],"msr-" + new Date().toISOString().replace(/:|\./g, "-") + ".mp3",{type: "audio/mp3",});var formdata = new FormData();formdata.append("file", file);this.axios.post("/api/public/common/fileUpload", formdata).then((res) => {if (!res.error) {} else {this.tool.showNotify("err", res.msg);}});},
至此完成所有功能。
在视频录制及播放中可能会遇到视频录制窗口的大小与想象中配置不一样,在查阅资料中,有方法提示配置mediaRecorder.setVideoSize(717,461);两个参数代表宽高,但是在vue中好像并不生效,因此给父元素设定好对应想要的宽高后,给video元素设置object-fit: fill的样式属性即可显示满