目录

标签: Web

基于 Electron 的屏幕录制工具(带画中画)

本文主要实现了基于 Electron 技术的屏幕录制、麦克风声音录制、摄像头(画中画)录制。

整体效果如下所示:

代码中,比较重要的就是对多个媒体轨道进行合成、视频合成。

核心代码如下:

// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// No Node.js APIs are available in this process because
// `nodeIntegration` is turned off. Use `preload.js` to
// selectively enable features needed in the rendering
// process.

const jQuery = (window.jQuery = require('jquery'));
require('@popperjs/core');
require('bootstrap');
const Vue = require('vue/dist/vue');
const electron = require('electron');
const fs = require('fs');
const SCREEN_WIDTH = 3072;
const SCREEN_HEIGHT = 1920;
const PlayerCanvas = require('./PlayerCanvas');

new Vue({
  el: '#vueapp',
  data: {
    recording: false,
  },
  mounted() {
    this._playerCanvas = new PlayerCanvas(SCREEN_WIDTH, SCREEN_HEIGHT);
  },
  methods: {
    // 开始录制
    async btnStartRecordClicked(e) {
      this._stream = new MediaStream();
      await this.attachAudioStream();

      // 摄像头 stream
      this._cameraStream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: false,
      });
      this._playerCanvas.setCameraVideo(
        this.createVideoElementWithStream(this._cameraStream)
      );

      // 屏幕 stream
      this._screenStream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          mandatory: {
            chromeMediaSource: 'desktop',
            minWidth: SCREEN_WIDTH,
            maxWidth: SCREEN_WIDTH,
            minHeight: SCREEN_HEIGHT,
            maxHeight: SCREEN_HEIGHT,
          },
        },
      });
      this._playerCanvas.setScreenVideo(
        this.createVideoElementWithStream(this._screenStream)
      );
      //
      this._audioStream
        .getAudioTracks()
        .forEach((value) => this._stream.addTrack(value));
      let playerCanvasStream = this._playerCanvas.canvas.captureStream();
      playerCanvasStream.getTracks().forEach((t) => this._stream.addTrack(t));
      this.$refs.preview.srcObject = playerCanvasStream;
      this.startRecord();
    },
    // 附加音频流
    async attachAudioStream() {
      // 获取麦克风流
      this._audioStream = await navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true,
      });
      // 将麦克风的流,附加到主流上
      this._audioStream
        .getAudioTracks()
        .forEach((value) => this._stream.addTrack(value));
    },
    // 停止录制
    btnStopRecordClicked(e) {
      this.recording = false;
      this._recorder.stop();
    },
    // 创建一个 HTMLVideoElement
    createVideoElementWithStream(stream) {
      let video = document.createElement('video');
      video.autoplay = true;
      video.srcObject = stream;
      return video;
    },
    // 开始录制
    startRecord() {
      this._recorder = new MediaRecorder(this._stream, {
        mimeType: 'video/webm;codes=h264',
      });
      this._recorder.ondataavailable = async (e) => {
        let path = electron.remote.dialog.showSaveDialogSync(
          electron.remote.getCurrentWindow(),
          {
            title: '保存文件',
            defaultPath: 'ScreenData.webm',
          }
        );

        let dataArrayBuffer = await e.data.arrayBuffer();
        fs.writeFileSync(path, Buffer.from(dataArrayBuffer));
        // fs.writeFileSync(path, new Uint8Array(dataArrayBuffer));
      };
      this._recorder.start();
      this.recording = true;
    },
  },
});

完整源代码下载:

基于 Electron 的屏幕录制工具

主要基于 Chrome 内核提供的屏幕录制流(Stream)、麦克风流(Stream),进行画面与音频轨道合并。

最终会生成录制好的 webm 格式的文件,可以使用浏览器来打开它进行播放。或者使用 ffmpeg 类似的工具进行视频格式转换,使用其他工具进行播放。

实现效果如图所示:

核心代码如下:


const jQuery = (window.jQuery = require('jquery'));
require('@popperjs/core');
require('bootstrap');
const Vue = require('vue/dist/vue');
const electron = require('electron');
const fs = require('fs');

new Vue({
  el: '#vueapp',
  data: {
    recording: false,
  },
  methods: {
    async btnStartRecordClicked(e) {
      this._stream = await navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true,
      });
      let screenStream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          mandatory: {
            chromeMediaSource: 'desktop',
            minWidth: 3072,
            maxWidth: 3072,
            minHeight: 1920,
            maxHeight: 1920,
          },
        },
      });
      screenStream
        .getVideoTracks()
        .forEach((value) => this._stream.addTrack(value));
      this.$refs.preview.srcObject = screenStream;
      this._recorder = new MediaRecorder(this._stream, {
        mimeType: 'video/webm;codes=h264',
      });
      this._recorder.ondataavailable = async (e) => {
        let path = electron.remote.dialog.showSaveDialogSync(
          electron.remote.getCurrentWindow(),
          {
            title: '保存文件',
            defaultPath: 'ScreenData.webm',
          }
        );

        let dataArrayBuffer = await e.data.arrayBuffer();
        fs.writeFileSync(path, Buffer.from(dataArrayBuffer));
        // fs.writeFileSync(path, new Uint8Array(dataArrayBuffer));
      };
      this._recorder.start();
      this.recording = true;
    },
    btnStopRecordClicked(e) {
      this.recording = false;
      this._recorder.stop();
    },
  },
});

源码下载:

HTML/JS 实现视频、音频录制

Web 使用 HTML/JS 实现摄像头、麦克风的录制,可实现:录制、暂停、继续、停止、回放。

最终浏览器会提供一个 Bolb 数据,需要保存到本地的话,可自行搜索相关操作。

实现截图:

录制完成后,点击 Play,然后点击播放器的播放按钮即可播放。

代码:

Flex 各种常用布局示例

一、传统布局与 flex 布局

参考视频:点击进入

Flex布局笔记:下载Flex笔记.html(内容同本文)

传统布局:

  • 兼容性好
  • 布局繁琐
  • 局限性,不能在移动端很好的布局

flex 弹性布局

  • 操作方便,布局极为简单,移动端应用很广泛
  • IE 10 (含)以下浏览器支持情况较差

建议:

1. 如果是 PC 端页面布局,我们还是使用传统布局
2. 如果是移动端布局或者不考虑兼容性问题的 PC 端页面布局,我们还是使用 flex 弹性布局

布局原理:flex 是 flexible Box 的缩写,意为“弹性布局”,用来为盒状模型提供最大的灵活性,任何一个容器都可以指定为 flex 布局。

二、开始学习 Flex 布局

以下由 6 个属性是对父元素设置的:

  • flex-direction:设置主轴的方向
  • justify-content:设置主轴上的子元素排列方式
  • flex-wrap:设置子元素是否允许换行
  • align-content:设置侧轴上的子元素的排列方式(多行)
  • align-items:设置侧轴上的子元素排列方式(单行)
  • flex-flow:复合属性,相当于同时设置了 flex-direction 和 flex-wrap

2.1 基础排列

<style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
</style>
      
<div style="display: flex; background-color: #e6f7ff">
        <div class="card">1</div>
        <div class="card">2</div>
        <div class="card">3</div>
</div>

2.2 垂直排列

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
      </style>
      
      <div style="display: flex; flex-direction: column; background-color: #e6f7ff">
        <div class="card">1</div>
        <div class="card">2</div>
        <div class="card">3</div>
      </div>

2.3 靠末尾对齐(靠右、靠下)

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: flex-end; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
        </div>

2.4 中间对齐

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: center; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
        </div>

2.5 平分剩余空间

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: space-around; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
        </div>

2.6 两边贴边,再平均分配剩余空间

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: space-between; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
        </div>

2.7 超过空间长度,自动换行

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; flex-wrap: wrap; width: 280px; height: 400px; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
        </div>

Tips:中间出现了大面积空白区域,并没有跟紧第一行进行排列,可参考示例 9. 横向+纵向居中(多行) 添加 align-content: flex-start; 即可解决该问题。

2.8 横向+纵向居中(单行)

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: center; align-items: center; height: 400px; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
        </div>

之前的示例,一直设置的都是主轴justify-content属性,我们再加上侧轴的align-items属性可以实现更多排列可能性

两者都设置为center则完成了整体画面居中的效果。

注意:该排列方式只适用于单行排列

2.9 横向+纵向居中(多行)

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: center; align-content: center; flex-wrap: wrap; height: 400px; width: 400px; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
          <div class="card">4</div>
          <div class="card">5</div>
          <div class="card">6</div>
        </div>

2.10 上下平均分配(多行)

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: center; align-content: space-around; flex-wrap: wrap; height: 400px; width: 400px; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
          <div class="card">4</div>
          <div class="card">5</div>
          <div class="card">6</div>
        </div>

2.11 上下贴到底后,再平均分配(多行)

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; justify-content: center; align-content: space-between; flex-wrap: wrap; height: 400px; width: 400px; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
          <div class="card">4</div>
          <div class="card">5</div>
          <div class="card">6</div>
          <div class="card">7</div>
          <div class="card">8</div>
          <div class="card">9</div>
        </div>

2.12 flex-flow 复合属性

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex-flow: column wrap; height: 400px; background-color: #e6f7ff">
          <div class="card">1</div>
          <div class="card">2</div>
          <div class="card">3</div>
          <div class="card">4</div>
          <div class="card">5</div>
        </div>

相当于同时设置了flex-direction: column;以及flex-wrap: wrap; 属性的一种简写形式

父级 flex 属性示例结束提示

如果以上示例没有您想要的效果,请尝试以上相似示例并多加上一个flex-direction: column;来设置主轴的方向(也就是主要排列方向)

三、子项属性布局示例

3.1 flex 属性实现(圣杯布局)

实现目标:左侧占据100px、右侧占据100px、中间占满剩余

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; background-color: #e6f7ff">
          <div class="card" style="width: 100px; background-color: #ff7875;">1</div>
          <div class="card" style="flex: 1;">2</div>
          <div class="card" style="width: 100px; background-color: #ffc53d;">3</div>
        </div>

子项的flex:1属性,表示一共占用多少份,这里的 1 ,即表示占掉1份。 因为没有其他的同级元素有赋值 flex: number 属性,所以它占满全部。
请前往 3.2.flex 属性实现平均分配 例子进行参考 flex 属性的应用

3.2 flex 属性实现平均分配

实现目标:左侧1/4,中间2/4,右边1/4 的宽度占比

        <style>
        .card {
          background-color: #91d5ff;
          width: 100px;
          height: 100px;
          margin: 4px;
        }
        </style>
        
        <div style="display: flex; background-color: #e6f7ff">
          <div class="card" style="flex: 1; background-color: #ff7875;">1</div>
          <div class="card" style="flex: 2;">2</div>
          <div class="card" style="flex: 1; background-color: #ffc53d;">3</div>
        </div>

至此,flex 常用布局就完结了, flex 的属性不止于本文档中所示。它还有其他的不太常用的属性,可以自行查阅相关文档进行学习。

直播流转码 RTMP 转 HTTP-FLV 用于 WEB 播放解决流程(续)

一、如何安装以及配置基础环境

1.1 简述

本文涉及以下内容及资料,需要您提前做好以下知识点的基础认知,可极大的提升在本文后面的内容做
理解以及实践。

  1. 涉及技术及工具内容:NginxFFmpegFlv.js
  2. 涉及直播流信息:RTMPRTSPHTTP-FLV
  3. 涉及编码格式:H265H264

以上资料参考:

Nginx 基础简介:https://www.runoob.com/w3cnote/nginx-setup-intro.html
FFmpeg 基础使用:http://www.ruanyifeng.com/blog/2020/01/ffmpeg.html Flv.js
前端播放组件:https://github.com/Bilibili/flv.js

1.2 安装教程

由于浏览器对 Flash 对禁用,Web 播放 RTMP 于是成为了一个难题,目前主流的 Web 直播流都为 http-
flv 格式,本文着重讲解如何将 RTMP 转为 Http-Flv。 、

具体安装教程:https://wxzzz.com/425.html

二、如何正确的配置高性能直播环境

2.1 简述

基于本文以上内容,想必您已经正确的安装了基础环境并实现了网⻚端的直播支持。

但它仍然存在一些性能上的问题,它目前的配置单个网⻚⻚面仅能播放 6 个直播视频,并且播放有较大 的卡顿、延迟。

2.2 发现问题所在

通过摸索、资料查询、实践得出以下结论。

  1. 关于 Flash 的支持:2021年后 Chrome 浏览器已经停止对 Flash 插件的支持。
  2. 关于单个网站⻚面最多只能播放 6 个视频:Chrome 以及大部分浏览器截止目前版本仅支持单个网
    ⻚播放 6 个视频(资料显示由于 HTTP 1.0/1.1 协议仅支持单个链接限制最大仅 6 路数据流支
    持)。
  3. 关于卡顿、延迟:由于流的实时拉取、再推送、再输送直播流整个流程的衔接,需要网络的完美支
    持,没有缓冲区、主动推流等配置它依然会卡顿(具体每一项的配置请参照 nginx-http-flv-module 官方文档nginx-rtmp-module 官方文档 确保正确配置您的程序)

2.3 解决问题

2.3.1 配置 Nginx 基础环境

  1. 配置网⻚端为 HTTPS / HTTP2.0 协议。
  2. 配置 推流端的访问为 HTTP 普通协议 、并且配置 HTTPS / HTTP2.0协议代理 HTTP 推流端

以上配置可自行百度解决相应的配置信息。

配置 Nginx 中的 RTMP 段,以解决性能卡顿、延迟问题:

配置(一):

 
server {
    listen 1935;
    server_name localhost;
    application myapp {
        live on;
        gop_cache on; #打开 GOP 缓存,减少首屏等待时间 }
}

关键配置: gop_cache on;

配置(二):

 
rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp;
rtmp{
    out_queue 4096;
 
out_cork 8;
    max_streams 128;
    timeout 15s;
    drop_idle_publisher 15s;
    log_interval 5s;
    log_size 1m;
    server{
        listen 1935;
        application myapp{
            live on;
            gop_cache on;
        }
    // ....
    }
}

关键配置:第 1 - 3 行、 6 - 12 行 配置。

至此,基于以上配置已经可实现高性能的直播请求访问。(当然,此处的”高性能“是针对基础配置而来
的,需要更高的性能直播请求,还需要更深一步的技术挖掘,道路漫漫⻓)

三、参考文献

  1. ubuntu16 nginx +rtmp+nginx-http-flv-module 环境搭建
  2. FFmpeg 视频处理入⻔教程 – 阮一峰
  3. nginx-http-flv-module 说明文档(中文)
  4. nginx-rtmp-module 说明文档
  5. flv.js 说明文档

直播流转码 RTMP 转 HTTP-FLV 用于 WEB 播放解决流程

由于浏览器对 flash 对禁用,Web 播放 RTMP 于是成为了一个难题,目前主流的 Web 直播流都为 http-flv 格式,本文着重讲解如何将 RTMP 转为 Http-Flv。

(重要)补充说明:通过对文档对阅读,如果您只是想推送 RTMP 流为 http-flv 的话,无需 ffmpeg 自行推流,可直接在 nginx 中的 rtmp 使用 “pull” 配置,如下图所示:

1. 实现方式简述

通过服务端将其 RTMP 流实时转为 http-flv 流,从而浏览器可直接使用该流进行直播(使用bilibili提供的 flv.js )。

所需资料集(以及下载地址):

  1. nginx
  2. nginx-http-flv-module
  3. ffmpeg
  4. flv.js

2. 配置环境

本文以 CentOS 系统为例,其操作大同小异。

2.1 下载 nginx 及 nginx-http-flv-module 进行编译安装

  1. 下载 nginxnginx-http-flv-module/opt/tools/ 目录中。
  2. 依次安装系统相关依赖项
yum -y install unzip
yum -y install gcc-c++ 
yum -y install pcre pcre-devel  
yum -y install zlib zlib-devel 
yum -y install openssl openssl-devel

3. 解压 nginx-http-flv-module-1.2.6.zip 解压到/usr/local/nginx 目录中

cd /opt/tools
unzip nginx-http-flv-module-1.2.6.zip
cp -r /opt/toos/nginx-http-flv-module /usr/local/nginx/nginx-http-flv-module

4. 将 nginx-http-flv-module 模板添加到 nginx 中,生成 make 文件 并安装 nginx

cd /opt/tools
tar -zxvf nginx-1.8.1.tar.gz
cd nginx-1.8.1
./configure --prefix=/usr/local/nginx  --add-module=/usr/local/nginx/nginx-http-flv-module
make && make install

5. 修改 nginx 配置文件,这里我贴出我自己的文件,根据个人情况可做相应修改。


worker_processes  1;

events {
    worker_connections  1024;
}

rtmp {
    server {
        listen 1985;

        application myapp {
            live on;
        }
    }
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;


    server {
        listen       80;
        server_name  localhost;

        location / {
            add_header 'Access-Control-Allow-Origin' '*';
            root html;
            index  index.html index.htm;
        }

        location /live {
            flv_live on;
        }

        location /flv {
            add_header 'Access-Control-Allow-Origin' '*';
            flv_live on;
            chunked_transfer_encoding on;
        }
        
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

6. 启动 nginx 服务

cd /usr/local/nginx/sbin
./nginx

Tips:停止nginx服务“ ./nginx -s stop ” , 重新加载配置 “ ./nginx -s reload

通过以上配置,即可完成服务端的直播环境配置。

2.2 ffmpeg 推送 RTMP 直播流到 nginx

安装ffmpeg:首先推送直播流到电脑(可以是同 nginx 一台服务器,性能会更好)必须已经安装了 ffmpeg 到环境,具体安装教程百度、Google一大把。

通过以上到 nginx 配置后,我们可以使用端口 1985,应用名称 myapp 来进行推送 RTMP 流给服务器,假定我们到服务器IP为“ 10.211.55.5 ” ,以下 ffmpeg 命令即可推送:

ffmpeg -re -i rtmp://58.200.131.2:1935/livetv/cctv1 -c copy -f flv rtmp://10.211.55.5:1985/myapp/testv

以上参数简单解释:1985 为端口,myapp 为对应nginx配置的应用名称,testv 为直播流名称可以随意填写,后面可以用这个名称,具体配置解释文档进行查阅:https://github.com/winshining/nginx-http-flv-module

运行正常结果如下图所示:

通过以上即可配置完成。

3. 使用 Web 进行直播

配置完成后,咱们就可以使用 nginx 配置好的路径进行直播啦,目前配置了两个路径如下:

http://10.211.55.5/live?port=1985&app=myapp&stream=testv  // 原始转播地址

http://10.211.55.5/flv?port=1985&app=myapp&stream=testv   // flv 转播地址

flv.js 在线测试地址:http://bilibili.github.io/flv.js/demo/

运行效果:

Tips,为了测试流是否正常,也可以使用 VLC (百度一下) 进行播放测试,如下图所示:

4. 以上,大功告成。

更多的细节,配置,及内容都可以到相应到网站进行查阅相关文档进行调整,处理。

本文的后续更新,参考地址:直播流转码 RTMP 转 HTTP-FLV 用于 WEB 播放解决流程(续)

Mac Nginx 安装与配置

1. 安装 Nginx

brew install nginx

2. 启动、停止 Nginx

2.1 启动 Nginx,直接在终端输入“nginx” 即可

nginx

2.2 停止 Nginx,在终端输入以下内容:

nginx -s stop

Tips:更多命令可输入 nginx -h 查看。

Nginx 启动后,默认端口为 8080 , 可直接通过浏览器访问:http://localhost:8080/ 即可得到以下界面:

3. 配置 Nginx

/usr/local/etc/nginx/nginx.conf (配置文件路径)
/usr/local/var/www (服务器默认路径)