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

举报
拿我格子衫来 发表于 2022/03/17 23:00:48 2022/03/17
【摘要】 副标题:iREC 一款基于浏览器JavaScript的屏幕录制工具 背景 周末,公司里的测试小妹给我发消息说,她昨晚又加班到很晚,原因是研发要求提复杂bug时需要附上具体的操作流程以便详细了解操作过程...

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

背景

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

需求如下

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

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

技术调研

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

  • navigator.mediaDevices.enumerateDevices() 该方法返回 一个promise,包含了系统中可用的媒体输入和输出设备的一系列信息。
  • navigator.mediaDevices.getDisplayMedia() 该方法返回一个promise,并提示用户选择显示器或显示器的一部分(例如窗口)以捕获为MediaStream 以便共享或记录。直接在浏览器控制台输入该方法即可调取授权窗口。
  • navigator.mediaDevices.getUserMedia()返回一个promise,在用户通过提示允许的情况下,打开系统上的相机或屏幕共享和/或麦克风,并提供 MediaStream 包含视频轨道和/或音频轨道的输入。

本次使用的API就是 navigator.mediaDevices.getDisplayMedia()。该方法可以配置一个参数,可以省略。我们可以直接将这段代码复制到浏览器控制台中运行。运行效果如下图
在这里插入图片描述

图1:运行navigator.mediaDevices.getDisplayMedia() 效果图
选中对应的窗体或屏幕,点击分享就可以了。在这个页面,你鼠标的移动,页签的切换都会实时地反应在上面。由于是目前查看的是当前屏幕,所有会有一个无限嵌套的效果。
点击分享后,在屏幕的下方会有一个如下的标识
在这里插入图片描述

图2:屏幕分享tab信息

并且在启动分享的tab上有一个红色的标识
在这里插入图片描述

图3:屏幕分享tab标识

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

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

mediaStream = await navigator.mediaDevices.getDisplayMedia()
document.querySelector("video").srcObject = mediaStream

  
 
  • 1
  • 2

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

编码

开始分享屏幕

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

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>测试web屏幕分享</title>
  <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 {
      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 = document.querySelector("#j_video")

  async function getMediaStream(stream) {
    videoplay.srcObject = stream
    window.$stream = stream
  }

  function handleError() {
    console.error('getUserMedia error : ', err)
  }

  async function startCapture(displayMediaOptions) {
    let captureStream = null;
    try {
      captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
    } catch (err) {
      console.error("Error: " + err);
    }
    return captureStream;
  }

  async function start() {
    if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
      console.log('getUserMedia is not supported')
    } else {
      const displayMediaOptions = {
        video: true,
        audio: false,
      }

      let captureStream = await startCapture(displayMediaOptions)
      await getMediaStream(captureStream)

      document.querySelector(".pre-start-view").style.display = "none"
      document.querySelector(".living-view").style.display = "block"
    }
  }

  function init() {
    document.querySelector("#j_start").addEventListener('click', start)
    document.querySelector("#j_record").addEventListener("click", record)
  }

  init()
</script>

</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

分享当前屏幕并显示分享内容 效果如下
在这里插入图片描述

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

录制下载

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

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

  • MediaRecorder.ondataavailable 调用它用来处理 dataavailable 事件, 该事件可用于获取录制的媒体资源 (在事件的 data 属性中会提供一个可用的 Blob 对象)
  • MediaRecorder.onstart 用来处理 start 事件, 该事件在媒体开始录制时触发
  • MediaRecorder.onpause用来处理 pause (en-US) 事件, 该事件在媒体暂停录制时触发
  • MediaRecorder.onstop 用来处理 stop 事件, 该事件会在媒体录制结束时、媒体流(MediaStream)结束时、或者调用MediaRecorder.stop()方法后触发.

我们在创建MediaRecorder对象后,需要监听它的ondataavailable事件,并将事件中的Blob数据存储起来。最终将存储起来的数据转化为一个视频文件,然后下载。

最终完整的html代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>测试web屏幕分享</title>
  <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 {
      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 = document.querySelector("#j_video")
  let videoBuffer = []
  let mediaRecorder
  let recording = false

  async function getMediaStream(stream) {
    videoplay.srcObject = stream
    window.$stream = stream
  }

  function handleError() {
    console.error('getUserMedia error : ', err)
  }

  async function startCapture(displayMediaOptions) {
    let captureStream = null;
    try {
      captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
    } catch (err) {
      console.error("Error: " + err);
    }
    return captureStream;
  }

  async function start() {
    if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
      console.log('getUserMedia is not supported')
    } else {
      const displayMediaOptions = {
        video: true,
        audio: false,
      }

      let captureStream = await startCapture(displayMediaOptions)
      await getMediaStream(captureStream)
      document.querySelector(".pre-start-view").style.display = "none"
      document.querySelector(".living-view").style.display = "block"
    }

  }

  function handleDataAvailable(e) {
    if (e && e.data && e.data.size > 0) {
      videoBuffer.push(e.data)
    }
  }

  async function record(even) {
    let $target = even.target
    if (recording) {
      stopRecord()
      $target.innerText = 'Record'
    } else {
      startRecord()
      $target.innerText = 'Stop'
    }
  }

  function startRecord() {
    videoBuffer = []
    const options = {
      mimeType: 'video/webm; codecs = vp8',
    }

    if (!MediaRecorder.isTypeSupported(options.mimeType)) {
      console.error('${options.mimeType} is not supported!')
      return
    }

    try {
      mediaRecorder = new MediaRecorder(window.$stream, options)
    } catch (e) {
      console.error('Failed to create MediaRecorder:', e)
      return
    }
    mediaRecorder.ondataavailable = handleDataAvailable
    mediaRecorder.start(10)
    recording = true
  }

  function stopRecord() {
    mediaRecorder.stop()
    recording = false
    downRecord()
  }

  // 下载录制
  function downRecord() {
    const blob = new Blob(videoBuffer, { type: 'video/webm' })
    const url = window.URL.createObjectURL(blob)
    const a = document.createElement('a')
    const fileName = new Date().toLocaleString()
    a.href = url
    a.style.display = 'none'
    a.download = `${fileName}.webm`
    a.click()
  }

  function init() {
    document.querySelector("#j_start").addEventListener('click', start)
    document.querySelector("#j_record").addEventListener("click", record)
  }

  init()

</script>

</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175

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

后续

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

在这里插入图片描述

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

相关链接

媒体流解释: 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

文章来源: fizzz.blog.csdn.net,作者:拿我格子衫来,版权归原作者所有,如需转载,请联系作者。

原文链接:fizzz.blog.csdn.net/article/details/120831011

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。