【前端屏幕共享录制】

MediaRecorder API 是一个强大的工具,用于从 MediaStream(如屏幕、摄像头、麦克风等)录制视频和音频。它提供了许多属性和方法来控制录制过程,并通过事件来处理录制数据。

下面,我将重点讲解 MediaRecorder 的时间属性与 API,尤其是如何使用 timesliceondataavailable 来控制录制过程中的数据采集时间。

1. MediaRecorder 基本属性

MediaRecorder 中,时间相关的属性主要影响录制的数据流的获取方式,具体包括:

  • timeslice(用于 start() 方法)
  • audioBitsPerSecond/videoBitsPerSecond 只读属性MediaRecorder返回正在使用的音视频编码比特率
  • ondataavailable(当录制的数据可用时触发)
  • onstart (开始录制是触发)
  • onstop(录制结束时触发)
  • onpause (录制暂停时触发)
  • onresume (恢复录制时触发)
  • onerror (方式错误触发)
  • state(以下三个状态之一)
    inactive:未进行录制 — 要么尚未开始,要么已开始然后停止。
    recording:正在捕获数据。
    paused:录制已开始,然后暂停,但尚未停止或恢复。

2. start() 方法与 timeslice

start() 方法是 MediaRecorder 开始录制的核心方法,它可以接受一个 timeslice 参数,用于设置 每次触发 ondataavailable 的时间间隔

语法:

mediaRecorder.start(timeslice);
  • timeslice(可选,单位:毫秒):指定每隔多少毫秒触发一次 ondataavailable 事件。在此期间录制的数据将被积累,直到触发该事件。

如何使用 timeslice

  • 如果你希望每隔一段时间就处理录制的数据,可以设置一个较短的 timeslice 值(例如,1000 毫秒,即每秒触发一次)。
  • 这个值通常用于 实时数据传输实时处理 场景,比如直播流媒体、实时分析等。

示例:

每隔 1 秒获取一次数据:

let mediaRecorder = new MediaRecorder(stream);

mediaRecorder.ondataavailable = (event) => {
  if (event.data.size > 0) {
    // 处理录制的数据
    console.log("获取到的数据:", event.data);
  }
};

mediaRecorder.onstop = () => {
  console.log("录制结束");
};

// 每 1 秒触发一次 ondataavailable 事件
mediaRecorder.start(1000);

在这个例子中:

  • 每隔 1 秒(1000 毫秒)触发一次 ondataavailable 事件,event.data 中存储的是这 1 秒内录制的媒体数据。
  • mediaRecorder.onstop 会在录制结束时触发,用于处理最终录制的数据。

3. MediaRecorder.isTypeSupported(mimeType) 方法

MediaRecorder.isTypeSupported()MediaRecorder API 中的一个静态方法,用来检查浏览器是否支持指定的 MIME 类型(格式)。它返回一个布尔值,指示浏览器是否能够录制指定格式的视频或音频。

语法:

MediaRecorder.isTypeSupported(mimeType);
  • mimeType:字符串类型,表示要检查的 MIME 类型,通常是视频或音频的格式,比如 "video/webm", "audio/webm", "video/mp4", "audio/mp3" 等。
  • 返回值:true 表示支持该 MIME 类型,false 表示不支持。

常见的 MIME 类型

  • video/webm:WebM 格式的视频。
  • audio/webm:WebM 格式的音频。
  • video/mp4:MP4 格式的视频(通常需要额外支持)。
  • audio/mp3:MP3 格式的音频。
  • video/ogg:OGG 格式的视频。
  • audio/ogg:OGG 格式的音频。

如何使用 MediaRecorder.isTypeSupported()

你可以使用 isTypeSupported() 来检查浏览器是否支持特定的 MIME 类型。根据返回的布尔值,你可以选择是否使用该格式进行录制。

示例:检查浏览器是否支持 WebM 格式
if (MediaRecorder.isTypeSupported("video/webm")) {
  console.log("浏览器支持 video/webm 格式");
} else {
  console.log("浏览器不支持 video/webm 格式");
}

检查所有常见格式

如果你想检查浏览器支持的所有格式,可以通过遍历一个 MIME 类型列表来检查:

const mimeTypes = [
  "video/webm",
  "video/mp4",
  "video/ogg",
  "audio/webm",
  "audio/mp3",
  "audio/ogg",
  "video/webm; codecs=vp8",
  "video/webm; codecs=vp9",
  "video/mp4; codecs=avc1.42E01E"
];

mimeTypes.forEach(type => {
  if (MediaRecorder.isTypeSupported(type)) {
    console.log(`${type} 被浏览器支持`);
  } else {
    console.log(`${type} 不被浏览器支持`);
  }
});

3. ondataavailable 事件

ondataavailable 事件是一个非常重要的回调函数,它会在录制过程中周期性地触发,尤其是在使用了 timeslice 参数后。

语法:

mediaRecorder.ondataavailable = function(event) {
  // 处理录制的数据
  console.log("录制数据:", event.data);
};
  • event.data 是一个 Blob 对象,包含了当前采集到的视频或音频数据。

ondataavailable 使用场景:

  • 实时处理录制数据:比如在每次数据收集后做实时预处理、上传或保存。
  • 减少内存占用:如果每次都将数据存储到 Blob 中并立刻处理(如上传至服务器),则可以避免将大量数据缓存在内存中。

4. onstop 事件

onstop 事件会在调用 mediaRecorder.stop() 后触发。此时,所有的录制数据都已经完成,可以进行处理或保存。

语法:

mediaRecorder.onstop = function() {
  // 完成录制后的处理
  console.log("录制停止");
  const blob = new Blob(recordedChunks, { type: 'video/webm' });
  const url = URL.createObjectURL(blob);
  const downloadLink = document.getElementById('downloadLink');
  downloadLink.href = url;
  downloadLink.download = 'recorded-video.webm';
  downloadLink.style.display = 'block';
};
  • recordedChunks 是在 ondataavailable 中积累的数据,它会在 onstop 事件中合并成一个最终的视频文件。

5. timeslice 的用途

实时流处理

timeslice 参数非常适合 实时流处理 的场景。例如,你可以使用它来在录制过程中定时上传数据(如用于直播的场景),或者当你需要逐步处理数据时。

内存优化

如果不设置 timeslice,所有的数据会积累到内存中,直到调用 stop() 方法为止,这样会占用大量内存,尤其是长时间录制时。而通过 timeslice,每隔一段时间就获取一次数据,可以避免过多的数据占用内存。

6. MediaDevices 的用途


MediaDevices 接口提供了与媒体设备(如摄像头、麦克风和屏幕共享)交互的方法。除了常见的 getUserMedia() 方法外,它还包括其他几个有用的方法:

1. getDisplayMedia()

用于捕获屏幕、窗口或特定应用的内容流(常用于屏幕共享)。

语法:

navigator.mediaDevices.getDisplayMedia(constraints)
  .then(stream => {
    // 处理屏幕共享流
  })
  .catch(error => {
    console.error("获取屏幕共享失败:", error);
  });

参数:

  • constraints:指定要捕获的屏幕内容(视频约束)。

常见用途:

  • 实现屏幕共享功能(如视频会议、在线演示)。
  • 录制或直播屏幕内容。

2. enumerateDevices()

获取用户设备(摄像头、麦克风、扬声器等)列表。

语法:

navigator.mediaDevices.enumerateDevices()
  .then(devices => {
    devices.forEach(device => {
      console.log(`设备ID: ${device.deviceId}, 类型: ${device.kind}, 标签: ${device.label}`);
    });
  })
  .catch(error => {
    console.error("获取设备列表失败:", error);
  });

返回值:
返回 MediaDeviceInfo 对象数组,每个对象包含设备信息:

  • deviceId:设备的唯一标识符。
  • kind:设备类型(如 "audioinput""audiooutput""videoinput")。
  • label:设备名称(需用户授权后可见)。

常见用途:

  • 获取摄像头、麦克风、扬声器列表。
  • 允许用户选择特定的输入/输出设备。

3. getSupportedConstraints()

返回浏览器支持的 MediaStreamConstraints 约束(如 widthheightframeRate 等)。

语法:

const supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
console.log(supportedConstraints);

返回值:

  • 一个对象,包含所有支持的约束字段及其可用性(truefalse)。

常见用途:

  • 检查某些约束是否被浏览器支持,避免使用不兼容的配置。

4. ondevicechange 事件

当设备(如摄像头、麦克风)插入或移除时触发。

语法:

navigator.mediaDevices.addEventListener("devicechange", () => {
  console.log("媒体设备已更改");
});

常见用途:

  • 监听设备变化,并动态更新设备选择列表。

适用场景总结

方法作用适用场景
getUserMedia()获取摄像头/麦克风流语音/视频通话、录音、拍照
getDisplayMedia()获取屏幕共享流直播、远程协作、屏幕录制
enumerateDevices()获取设备列表设备管理、用户选择摄像头或麦克风
getSupportedConstraints()获取支持的媒体约束确保兼容性,避免使用不支持的参数
ondevicechange监听设备变更设备插拔监听,动态更新设备选项

屏幕录制

	<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>共享屏幕录屏</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 20px;
            background-color: #f4f4f4;
        }

        h2 {
            color: #333;
        }

        video {
            width: 80%;
            max-width: 800px;
            margin-top: 20px;
            border: 3px solid #333;
            border-radius: 8px;
            background: #000;
        }

        button {
            padding: 12px 20px;
            margin: 10px;
            font-size: 16px;
            cursor: pointer;
            border: none;
            border-radius: 5px;
            background-color: #007bff;
            color: white;
            transition: background 0.3s;
        }

        button:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }

        button:hover:not(:disabled) {
            background-color: #0056b3;
        }

        #timer {
            font-size: 18px;
            color: red;
            margin-top: 10px;
            font-weight: bold;
        }

        #filenameInput {
            padding: 8px;
            margin-top: 10px;
            font-size: 14px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }

        #downloadLink {
            display: block;
            margin-top: 15px;
            font-size: 16px;
            color: #28a745;
            text-decoration: none;
            font-weight: bold;
        }

        #downloadLink:hover {
            color: #1e7e34;
        }
    </style>
</head>

<body>

    <h2>共享屏幕录屏</h2>
    <button id="startBtn">开始录屏</button>
    <button id="stopBtn" disabled>停止录屏</button>
    <button id="pauseBtn" disabled>暂停录屏</button>
    <button id="resumeBtn" disabled>恢复录屏</button>
    <button id="screenshotBtn" disabled>截图</button>
    <br>
    <input type="text" id="filenameInput" placeholder="请输入文件名" />
    <select id="formatSelect">
        <option value="webm">WebM</option>
        <option value="mp4">MP4</option>
        <option value="mkv">MKV</option>
    </select>
    <a id="downloadLink" style="display: none;">下载视频</a>
    <p id="timer">录制时长:00:00</p>
    <video id="preview" autoplay playsinline muted></video>
    <canvas id="canvas" style="display: none;"></canvas>

    <script>
        let mediaRecorder, startTime, pauseTime = 0, timerInterval;
        let recordedChunks = [];

        document.getElementById("startBtn").addEventListener("click", async () => {
            try {
                const stream = await navigator.mediaDevices.getDisplayMedia({
                    video: { cursor: "always" },
                    audio: { echoCancellation: true, noiseSuppression: true }
                });

                const preview = document.getElementById("preview");
                preview.srcObject = stream;

                mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm" });
                recordedChunks = [];  // 清空已录制的片段

                mediaRecorder.ondataavailable = (event) => {
                    if (event.data.size > 0) {
                        recordedChunks.push(event.data);
                    }
                };

                mediaRecorder.onstop = () => {
                    const format = document.getElementById("formatSelect").value;
                    let mimeType = "video/webm";
                    let extension = "webm";

                    if (format === "mp4") {
                        mimeType = "video/mp4";
                        extension = "mp4";
                    } else if (format === "mkv") {
                        mimeType = "video/x-matroska";
                        extension = "mkv";
                    }

                    const blob = new Blob(recordedChunks, { type: mimeType });
                    const url = URL.createObjectURL(blob);
                    const downloadLink = document.getElementById("downloadLink");

                    let filename = document.getElementById("filenameInput").value.trim();
                    if (!filename) filename = "recorded-video";

                    downloadLink.href = url;
                    downloadLink.download = filename + "." + extension;
                    downloadLink.style.display = "block";
                    downloadLink.innerText = "下载录屏视频";

                    clearInterval(timerInterval);
                    document.getElementById("timer").innerText = "录制时长:00:00";
                };

                // 重新录制时重置时间
                startTime = Date.now();
                pauseTime = 0;
                startTimer(); // 重新启动计时器

                mediaRecorder.start();

                document.getElementById("startBtn").disabled = true;
                document.getElementById("stopBtn").disabled = false;
                document.getElementById("pauseBtn").disabled = false;
                document.getElementById("resumeBtn").disabled = true;
                document.getElementById("screenshotBtn").disabled = false;

            } catch (error) {
                console.error("获取屏幕共享失败:", error);
            }
        });

        document.getElementById("stopBtn").addEventListener("click", () => {
            mediaRecorder.stop();
            document.getElementById("preview").srcObject.getTracks().forEach(track => track.stop());

            document.getElementById("startBtn").disabled = false;
            document.getElementById("stopBtn").disabled = true;
            document.getElementById("pauseBtn").disabled = true;
            document.getElementById("resumeBtn").disabled = true;
            document.getElementById("screenshotBtn").disabled = true;
        });

        document.getElementById("pauseBtn").addEventListener("click", () => {
            if (mediaRecorder.state === "recording") {
                mediaRecorder.pause();
                pauseTime = Date.now(); // 记录暂停时间
                clearInterval(timerInterval);

                document.getElementById("pauseBtn").disabled = true;
                document.getElementById("resumeBtn").disabled = false;
            }
        });

        document.getElementById("resumeBtn").addEventListener("click", () => {
            if (mediaRecorder.state === "paused") {
                mediaRecorder.resume();
                const resumeTime = Date.now();
                startTime += resumeTime - pauseTime; // 计算暂停期间的时间差
                startTimer();

                document.getElementById("pauseBtn").disabled = false;
                document.getElementById("resumeBtn").disabled = true;
            }
        });

        document.getElementById("screenshotBtn").addEventListener("click", () => {
            const video = document.getElementById("preview");
            const canvas = document.getElementById("canvas");
            const ctx = canvas.getContext("2d");
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
            const link = document.createElement("a");
            link.href = canvas.toDataURL("image/png");
            link.download = "screenshot.png";
            link.click();
        });

        function startTimer() {
            startTime = startTime || Date.now();
            timerInterval = setInterval(() => {
                const elapsedTime = Math.floor((Date.now() - startTime) / 1000);
                const minutes = String(Math.floor(elapsedTime / 60)).padStart(2, "0");
                const seconds = String(elapsedTime % 60).padStart(2, "0");
                document.getElementById("timer").innerText = `录制时长:${minutes}:${seconds}`;
            }, 1000);
        }
    </script>

</body>

</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值