程序笔记   发布时间:2022-07-05  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

副标题:iREC 一款基于浏览器JavaScript的屏幕录制工具

背景

周末࿰c;公司里的测试小妹给我发消息说࿰c;她昨晚又加班到很晚࿰c;原因是研发要求提复杂bug时需要附上具体的操作流程以便详细了解操作过程和复现。最好能提供一个录制视频࿰c;这不是难为我们测试小妹嘛?随后她问我有没有好用࿰c;免费的录制屏幕的软件。我答应帮她找找。 看到这里你可能以为这是一篇软件推荐文章࿰c;但其实这是一篇造轮子的文章࿰c;经过一番搜索࿰c;我发现大多数的录屏软件࿰c;不是比较笨重࿰c;就是有些需要付费࿰c;或者无法跨平台使用。于是我想能不能自己开发一个录屏工具࿰c;这个想法一旦产生就无法停止࿰c;在造轮子之前我需要简单整理一下需求范围࿰c;便挑选合适的工具来实现。

需求如下

实现一个录屏工具或软件࿰c;能够录制整个屏幕࿰c;最低要求是能够录制浏览器的操作。该软件有一个开始录制的按钮࿰c;点击后开始录制࿰c;按钮变成停止按钮࿰c;再次点击按钮࿰c;录制完成࿰c;并将录制的文件下载下来。 这是一个最小的需求࿰c;如果要扩张的话࿰c;需要增减暂停录制࿰c;继续录制࿰c;输入自定义的文件名࿰c;定制视频格式࿰c;清晰度࿰c;是否录制声音。这些要求都是核心需求之外的。可以后续虑。 ​

需求了解清楚了࿰c;接下来就是寻找合适的工具或编程语言来实现。

技术调研

首先我想到的是JavaScript,因为JavaScript 是世界上最好的编程语言 😂。马克斯的火箭操作面板就是使用JavaScript写的࿰c; Lens–Kubernets IDE 也是使用JavaScript写的。于是我决定先在JavaScript方向上尝试实现这个工具。 在经过几番的搜索与请教一些做专业人士后࿰c;最终我在JavaScript 的BOM编程中找到了这个对象Navigator.mediaDevices。 mediaDevices 是 Navigator 只读属性࿰c;返回一个 MediaDevices 对象࿰c;该对象可提供对相机和麦克风等媒体输入设备的连接访问࿰c;也包括屏幕共享。 经过一番的尝试与搜索我得出:在浏览器上使用JavaScript做录屏功能使用的主要API是navigator.mediaDevices.getDisplaymedia() 与 @H_598_22@mediaRecorder 对象。下面进行一一拆分讲解。 navigator 下的@H_598_22@mediaDevices有以下几个主要接口:

@H_262_33@
  • navigator.mediaDevices.enumerateDevices() 该方法返回 一个promise࿰c;包含了系统中可用的媒体输入和输出设备的一系列信息。
  • navigator.mediaDevices.getDisplaymedia() 该方法返回一个promise࿰c;并提示用户选择显示器或显示器的一部分(例如窗口)以捕获为MediaStream 以便共享或记录。直接在浏览器控制台输入该方法即可调取授权窗口。
  • navigator.mediaDevices.getUserMedia()返回一个promise࿰c;在用户通过提示允许的情况下࿰c;打开系统上的相机或屏幕共享和/或麦克风࿰c;并提供 MediaStream 包含视频轨道和/或音频轨道的输入。
  • 本次使用的API就是 navigator.mediaDevices.getDisplaymedia()。该方法可以配置一个参数࿰c;可以省略。我们可以直接将这段代码复制到浏览器控制台中运行。运行效果如下图 @H_450_50@给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我

    图1:运行navigator.mediaDevices.getDisplaymedia() 效果图 选中对应的窗体或屏幕࿰c;点击分享就可以了。在这个页面࿰c;你鼠标的移动࿰c;页签的切换都会实时地反应在上面。由于是目前查看的是当前屏幕࿰c;所有会有一个无限嵌套的效果。 点击分享后࿰c;在屏幕的下方会有一个如下的标识 @H_450_50@给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我

    图2:屏幕分享tab信息 ​

    并且在启动分享的tab上有一个红色的标识 @H_450_50@给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我

    图3:屏幕分享tab标识 ​

    点击了分享之后࿰c;我们的系统就发起了一个分享࿰c;当这个分享活动创建后c;就会生成一个 MediaStream 翻译成中文就是媒体流。它是一个媒体内容的流.。一个流包含几个_轨道_࿰c;比如视频和音频轨道。这个MediaStream可以直接使用 html中的video 标签显示出具体的内容。 ​

    于是一个清晰的思路就出现了。首先调用API navigator.mediaDevices.getUserMedia() 回去一个媒体流࿰c;然后使用一个video来显示这个媒体流。 伪代码如下

    @H_598_22@mediaStream @H_489_78@= await navigator.@H_658_79@mediaDevices.getDisplaymedia()
    document.querySELEctor("video").srcObject @H_489_78@= mediaStream
    

    这里需要注意一个细节࿰c;要显示媒体流的内容我们必须将媒体流设置在videosrcObject 属性上。 videosrcsrcObject二个属性的区别在与࿰c; src是静态的地址。srcObject是一个实时数据࿰c;媒体流。 思路很清晰。接下来我们进行详细的编码。

    编码

    开始分享屏幕

    首先创建一个html࿰c;加入一个按钮࿰c;点击按钮进行分享屏幕。并在该页面上显示分享的内容。

    <!DOCTYPE html>
    <html>
    
    <head>
      <@H_658_79@meta charset="utf-8" />
      <title>测试web屏幕分享</title>
      <@H_658_79@meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.17/dist/css/bootstrap.min.css" rel="stylesheet">
      <style type="text/css">
        html,
        body {
          height: 100%;
          BACkground-color: #fff;
        }
    
        .container {
          height: 100%;
          border: 1px solid #ddd;
          padding: 20px;
        }
    
        .pre-start-view {
          height: 100%;
          display: flex;
          justify-content: center;
          align-items: center;
        }
    
        .living-view {
          display: none;
        }
    
        .video-container {
          display: flex;
          justify-content: center;
          align-content: center;
        }
    
        #j_video {
          width: 800px;
          height: 500px;
        }
    
        .video-btns {
          @H_212_273@margin-top: 16px;
          text-align: center;
        }
      </style>
    </head>
    
    <body>
      <div class="container">
        <div class="pre-start-view">
          <button type="button" class="btn btn-priMary" id="j_start">Start</button>
        </div>
        <div class="living-view">
          <div class="video-container">
            <video autoplay playsinline id="j_video"></video>
          </div>
        </div>
      </div>
    </body>
    <script>
    
      const videoplay @H_489_78@= document.querySELEctor("#j_video")
    
      async function getMediaStream(stream) {
        videoplay.srcObject @H_489_78@= stream
        window.$stream @H_489_78@= stream
      }
    
      function handleError() {
        console.error('getUserMedia error : ', err)
      }
    
      async function startCapture(displaymediaOptions) {
        let captureStream @H_489_78@= null;
        try {
          captureStream @H_489_78@= await navigator.@H_658_79@mediaDevices.getDisplaymedia(displaymediaOptions);
        } catch (err) {
          console.error("Error: " @H_489_78@+ err);
        }
        return captureStream;
      }
    
      async function start() {
        if (@H_489_78@!navigator.@H_658_79@mediaDevices @H_489_78@|| @H_489_78@!navigator.@H_658_79@mediaDevices.getDisplaymedia) {
          console.log('getUserMedia is not supported')
        } else {
          const displaymediaOptions @H_489_78@= {
            video@H_489_78@: true,
            audio@H_489_78@: false,
          }
    
          let captureStream @H_489_78@= await startCapture(displaymediaOptions)
          await getMediaStream(captureStream)
    
          document.querySELEctor(".pre-start-view").style.display @H_489_78@= "none"
          document.querySELEctor(".living-view").style.display @H_489_78@= "block"
        }
      }
    
      function init() {
        document.querySELEctor("#j_start").addEventListener('click', start)
        document.querySELEctor("#j_record").addEventListener("click", record)
      }
    
      init()
    </script>
    
    </html>
    

    分享当前屏幕并显示分享内容 效果如下 @H_450_50@给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我

    图4:分享内容预览 到这里我们就已经完成了创建分享࿰c;查看分享。但要实现一个完整的录制功能࿰c;还缺少关键性的两步࿰c;就是录制࿰c;下载。 ​

    录制下载

    于是我们在视频下面添加一个Record 按钮。点击开始录制࿰c;然后按钮变成Stopc;点击后࿰c;停止录制࿰c;然后下载一个以当前时间命名的视频文件。 ​

    这里的录制应该是开始截取媒体流中的一部分࿰c;最后做成视频文件下载。 查阅文档后得知࿰c;要截取媒体流需要使用@H_598_22@mediaRecorder 对象。 @H_663_6@mediaRecorder() 构造函数会创建一个对指定的 MediaStream 进行录制的 MediaRecorder 对象。 该构造函数接受二个参数࿰c;一是媒体流MediaStream࿰c;第二个参数是配置参数࿰c;指定将媒体流转化为什么格式的内容࿰c;如mp4࿰c;音频的比特率࿰c;视频的比特率。 创建的MediaRecorder 对象可以对录制过程࿰c;进行管理࿰c;开始࿰c;暂停࿰c;停止࿰c; 此外MediaRecorder 对象 还有一些事件处理方法。

    @H_262_33@
  • @H_598_22@mediaRecorder.ondataavailable 调用它用来处理 dataavailable 事件, 该事件可用于获取录制的媒体资源 (在事件的 data 属性中会提供一个可用的 Blob 对象)
  • @H_598_22@mediaRecorder.onstart 用来处理 start 事件, 该事件在媒体开始录制时触发
  • @H_598_22@mediaRecorder.onpause用来处理 pause (en-US) 事件, 该事件在媒体暂停录制时触发
  • @H_598_22@mediaRecorder.onstop 用来处理 stop 事件, 该事件会在媒体录制结束时、媒体流(MediaStream)结束时、或者调用MediaRecorder.stop()方法后触发.
  • 我们在创建MediaRecorder对象后࿰c;需要监听它的ondataavailable事件࿰c;并将事件中的Blob数据存储起来。最终将存储起来的数据转化为一个视频文件࿰c;然后下载。 ​

    最终完整的html代码

    <!DOCTYPE html>
    <html>
    
    <head>
      <@H_658_79@meta charset="utf-8" />
      <title>测试web屏幕分享</title>
      <@H_658_79@meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.17/dist/css/bootstrap.min.css" rel="stylesheet">
      <style type="text/css">
        html,
        body {
          height: 100%;
          BACkground-color: #fff;
        }
    
        .container {
          height: 100%;
          border: 1px solid #ddd;
          padding: 20px;
        }
    
        .pre-start-view {
          height: 100%;
          display: flex;
          justify-content: center;
          align-items: center;
        }
    
        .living-view {
          display: none;
        }
    
        .video-container {
          display: flex;
          justify-content: center;
          align-content: center;
        }
    
        #j_video {
          width: 800px;
          height: 500px;
        }
    
        .video-btns {
          @H_212_273@margin-top: 16px;
          text-align: center;
        }
      </style>
    </head>
    
    <body>
      <div class="container">
        <div class="pre-start-view">
          <button type="button" class="btn btn-priMary" id="j_start">Start</button>
        </div>
        <div class="living-view">
          <div class="video-container">
            <video autoplay playsinline id="j_video"></video>
          </div>
          <p class="video-btns">
            <button type="button" class="btn btn-priMary" id="j_record">Record</button>
          </p>
        </div>
      </div>
    </body>
    <script>
    
      const videoplay @H_489_78@= document.querySELEctor("#j_video")
      let videoBuffer @H_489_78@= []
      let mediaRecorder
      let recording @H_489_78@= false
    
      async function getMediaStream(stream) {
        videoplay.srcObject @H_489_78@= stream
        window.$stream @H_489_78@= stream
      }
    
      function handleError() {
        console.error('getUserMedia error : ', err)
      }
    
      async function startCapture(displaymediaOptions) {
        let captureStream @H_489_78@= null;
        try {
          captureStream @H_489_78@= await navigator.@H_658_79@mediaDevices.getDisplaymedia(displaymediaOptions);
        } catch (err) {
          console.error("Error: " @H_489_78@+ err);
        }
        return captureStream;
      }
    
      async function start() {
        if (@H_489_78@!navigator.@H_658_79@mediaDevices @H_489_78@|| @H_489_78@!navigator.@H_658_79@mediaDevices.getDisplaymedia) {
          console.log('getUserMedia is not supported')
        } else {
          const displaymediaOptions @H_489_78@= {
            video@H_489_78@: true,
            audio@H_489_78@: false,
          }
    
          let captureStream @H_489_78@= await startCapture(displaymediaOptions)
          await getMediaStream(captureStream)
          document.querySELEctor(".pre-start-view").style.display @H_489_78@= "none"
          document.querySELEctor(".living-view").style.display @H_489_78@= "block"
        }
    
      }
    
      function handleDataAvailable(e) {
        if (e @H_489_78@&& e.data @H_489_78@&& e.data.size @H_489_78@> 0) {
          videoBuffer.push(e.data)
        }
      }
    
      async function record(even) {
        let $target @H_489_78@= even.target
        if (recording) {
          stopRecord()
          $target.innerText @H_489_78@= 'Record'
        } else {
          startRecord()
          $target.innerText @H_489_78@= 'stop'
        }
      }
    
      function startRecord() {
        videoBuffer @H_489_78@= []
        const options @H_489_78@= {
          mimeType@H_489_78@: 'video/webm; codecs = vp8',
        }
    
        if (@H_489_78@!@H_658_79@mediaRecorder.isTypeSupported(options.@H_658_79@mimeType)) {
          console.error('${options.mimeTypE} is not supported!')
          return
        }
    
        try {
          mediaRecorder @H_489_78@= new @H_228_2111@mediaRecorder(window.$stream, options)
        } catch (e) {
          console.error('Failed to create MediaRecorder:', e)
          return
        }
        mediaRecorder.ondataavailable @H_489_78@= handleDataAvailable
        mediaRecorder.start(10)
        recording @H_489_78@= true
      }
    
      function stopRecord() {
        mediaRecorder.stop()
        recording @H_489_78@= false
        downRecord()
      }
    
      // 下载录制
      function downRecord() {
        const blob @H_489_78@= new Blob(videoBuffer, { type@H_489_78@: 'video/webm' })
        const url @H_489_78@= window.URL.createObjectURL(blob)
        const a @H_489_78@= document.createElement('a')
        const filename @H_489_78@= new Date().toLocaleString()
        a.href @H_489_78@= url
        a.style.display @H_489_78@= 'none'
        a.download @H_489_78@= `${filename}.webm`
        a.click()
      }
    
      function init() {
        document.querySELEctor("#j_start").addEventListener('click', start)
        document.querySELEctor("#j_record").addEventListener("click", record)
      }
    
      init()
    
    </script>
    
    </html>
    

    经过加工改造࿰c;编写了一个js脚本࿰c;使用tampermonkey管理。直接将录制按钮注入到页面上࿰c;不会使用tampermonkey的࿰c;可以直接在控制台执行脚本。最后我将该工具的名字命名为 iREC。 完整工具脚本私信我获取。

    后续

    周一我把做好的录制脚本发给了测试小妹。 在使用过一段时间后࿰c;有人在内部群里给我发了一条这样的消息。

    @H_450_50@给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我

    哈哈࿰c;本故事纯属虚构࿰c;如有雷同纯属巧合。希望大家都能把学到的技术转化为生产力。提升生活品质。 猿力与你同在。

    相关链接

    媒体流解释: https://developer.mozilla.org/zh-CN/docs/Web/API/MediaStream 媒体录制解释: https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder srcObject解释: https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLMediaElement/srcObject WebRTC(五) Web端实现屏幕录制 https://blog.csdn.net/SImple_a/article/details/102523658 JavaScript 屏幕录制 API 学习 https://segmentfault.com/a/1190000020267689 MediaRecorder 支持的mimeType https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported

    大佬总结

    以上是大佬教程为你收集整理的给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我全部内容,希望文章能够帮你解决给测试小妹做了一个js版屏幕录制工具iREC,她用后竟说喜欢我所遇到的程序开发问题。

    如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

    本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
    如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。