美文网首页
MediaRecorder简单实现屏幕共享

MediaRecorder简单实现屏幕共享

作者: 飘空鱼 | 来源:发表于2022-09-15 14:31 被阅读0次

    功能特色:

    使用前端技术实现局域网可用的屏幕共享程序,仅用于学习研究,商业化还需要解决很多问题。

    技术要点

    • MediaRecorder(chrome)
    • nodejs
    • scoket.io

    架构

    传播者(transmitter)> 中转器(nodejs)> 接收者(receiver)

    项目目录结构

    • client
      • transmitter.html
      • receiver.html

    index.js(中转器)

    实现细节

    传播者【transmitter.html】

    // 总体思路:获取到录屏数据arrayBuffer之后发送给中转器
    
    // 1、socket连接
    var socket = io('ws://' + location.hostname + ':9009', {
      query: {
        role: 'servant', // 身份标记
      }
    })
    var connected = false;
    
    socket.on('connect', function () {
      connected = true;
    });
    
    socket.on('disconnect', function () {
      connected = false;
    })
    
    // 录屏授权【chrome】
    navigator.mediaDevices.getDisplayMedia({
      video: true, // 这里我只需要视频,不要音频
    }).then(function (stream) {
      var mediaRecorder = new MediaRecorder(stream, {
        mimeType: 'video/webm\;codecs=vp8', //  这个很关键 #01
      });
    
      // 媒体片段
      mediaRecorder.ondataavailable = function (e) {
        if (e.data.size && connected) {
          e.data.arrayBuffer().then(function (arrayBuffer) {
            socket.emit('media', arrayBuffer); // 发送给中转器
          })
        }
      }
    
      mediaRecorder.start(200); // 每200毫秒触发一次片段
    })
    
    

    中转器【index.js】(nodejs执行这个脚本)

    const Koa = require('koa');
    const { createServer } = require('http')
    const { Server } = require('socket.io');
    const koaStatic = require('koa-static')
    const koaBody = require('koa-body');
    const path = require('path');
    
    const app = new Koa();
    const httpServer = createServer(app.callback());
    
    // 启动一个静态服务器
    app.use(koaBody({ multipart: true }))
    app.use(koaStatic(path.resolve(__dirname, './client')));
    app.listen(9000, () => console.log('服务已启动: 9000'));
    
    // 启动一个socket服务器
    const io = new Server(httpServer, { cors: true, maxhttpbuffersize: 1e6 * 50 });
    let firstData; // 记录开播的第一个数据块
    
    io.on('connection', (socket) => {
      const { role } = socket.handshake.query;
    
      if (role === 'transmitter') firstData = null; // 【传播者】如果重新开播,要刷新一下
    
      socket.join("default-room"); // 全部加入同一个房间
    
       // 开播过程中【 接收者】加入,是需要优先接收第一个数据块不然会异常
      if (firstData) io.sockets.to("default-room").emit('mediaData', firstData);
    
      // 收到【传播者】发送的媒体数据arrayBuffer
      socket.on('media', function (data) {
        if (!firstData) firstData = data;
    
        socket.to("default-room").emit('mediaData', data) // 发送媒体数据给【接收者】
      })
    })
    
    httpServer.listen(9009);
    

    接收者【receiver.html】

    <body>
      <video id="video" style="width: 1280px; height: 720px;"></video>
      <button id="look">观看</button>
    </body>
    
    window.onload = function () {
      var video = document.querySelector('#video');
      var btnLook = document.querySelector('#look');
      var mediaSource = new MediaSource();
      var chunks = [];
      var sourceBuffer;
    
      video.src = URL.createObjectURL(mediaSource);
      mediaSource.addEventListener('sourceopen', sourceOpen)
    
      function sourceOpen() {
        console.log('sourceOpen');
        URL.revokeObjectURL(video.src);
        var mime = 'video/webm; codecs=vp8'; // 这个很关键 #01
        sourceBuffer = mediaSource.addSourceBuffer(mime);
        sourceBuffer.mode = 'sequence';
      }
    
      // 把媒体片段加入视频播放缓存区
      function appendSourceBuffer() {
        if (chunks.length && sourceBuffer && !sourceBuffer.updating && mediaSource.readyState === 'open') {
          sourceBuffer.appendBuffer(chunks.shift());
          if (video.paused) video.play();
        }
    
        window.requestAnimationFrame(appendSourceBuffer);
      }
    
      // 开始播放
      btnLook.addEventListener('click', function () {
        appendSourceBuffer();
        btnLook.remove();
      });
    
      // 连接socket
      var socket = io('ws://' + location.hostname + ':9009', {
        query: {
          role: 'receiver'
        }
      })
    
      var connected = false;
    
      socket.on('connect', function () {
        connected = true;
      });
    
      socket.on('disconnect', function () {
        connected = false;
      })
    
      // 收到【传播者】发来的数据arrayBuffer
      socket.on('mediaData', function (data) {
        chunks.push(data);
      });
    }
    

    期间遇到的难点

    • MediaRecorder与mediaSource.addSourceBuffer的miniType有匹配关系,与编码格式有关。
    • 使用socket发送数据不易过大,不然容易断开
    • 【接收者】需要优先接收【传播者】开播的第一个媒体片段,不然addSourceBuffer之后会触发mediaSource关闭

    结语

    查了很多国内外的论坛资料才把这个场景下的功能需求完成,还有很多关于媒体方面的知识欠缺,还望路过的友人不吝赐教。

    相关文章

      网友评论

          本文标题:MediaRecorder简单实现屏幕共享

          本文链接:https://www.haomeiwen.com/subject/inmrortx.html