关于视频处理:H5播放webrtc视频

8次阅读

共计 9599 个字符,预计需要花费 24 分钟才能阅读完成。

一、简介

WebRTC 概念

WebRTC 是由 Google 主导的,由一组规范、协定和 JavaScript API 组成,用于实现浏览器之间(端到端之间)的音频、视频及数据共享。WebRTC 不须要装置任何插件,通过简略的 JavaScript API 就能够使得实时通信变成一种规范性能。

为什么应用 webrtc

当初各大浏览器以及终曾经逐步加大对 WebRTC 技术的反对。下图是 webrtc 官网给出的当初曾经提供反对了的浏览器和平台。

二、H5 播放 webrtc

webrtc 播放通过一直摸索,基本上没有现行的库来间接播放一个 webrtc 的 url 的库,很大一部分的思路是通过 websocket+webrtc 来实现数据传输和播放的,也有很多应用 EMScript 将 c 代码编解码库编译成 js 代码来应用。

不过庆幸的是,我找了一个库是反对 srs 的 webrtc 流的,它就是开源的 jswebrtc,应用代码如下:

通过 html 播放

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTCPlayer</title>
    <style type="text/css">
        html, body {
            background-color: #111;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="jswebrtc" data-url="webrtc://192.168.12.187/live/1"></div>
    <script type="text/javascript" src="jswebrtc.min.js"></script>
</body>
</html>

通过 js 播放

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTCPlayer</title>
</head>
<body>
    <video id="video-webrtc" controls></video>
    <script type="text/javascript" src="jswebrtc.min.js"></script>
    <script type="text/javascript">
        var video = document.getElementById('video-webrtc');
        var url = 'webrtc://192.168.12.187/live/1';
        var player = new JSWebrtc.Player(url, { video: video, autoplay: true, onPlay: (obj) => {console.log("start play") } });
    </script>
</body>
</html>

播放成果如下:

这里要阐明的是 srs 尽管提供了 webrtc 协定转换,然而 webrtc 是基于 udp 的,可能是 udp 丢包过于重大,srs 并没有解决好,所以画面如果有动画,基本上是动画的中央会有花屏!!不过实时性的确不错,能够与 rtmp 实时协定相差无几。

三、扩大

webrtc 是一种标准协议,不像只反对 IE 的 OCX(ActiveX)插件或广泛浏览器都反对的 flash 插件(重点是 google 的 chrome 浏览器在 2020 年 12 月份之后不再反对 flash 插件),所以后续的无插件实时视频播放的重点就落在了 webrtc 上,所以从久远来讲它的特点(无插件、规范通用协定)使得 webrtc 被宽泛和短暂应用。

既然 webrtc 如此重要,在安防或互联网直播畛域就少不了视频采集或视频公布性能,所以就防止不了采集本地摄像头的音视频性能,如下就是我要讲的如何通过 webrtc 协定采集本地音视频,H5 代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="WebRTC code samples">
<meta name="viewport"
    content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
<meta itemprop="description" content="Client-side WebRTC code samples">
<meta itemprop="name" content="WebRTC code samples">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#ffffff">
<base target="_blank">
<title>MediaStream Recording</title>
</head>
<body>
    <div id="container">
        <h1>
            <span>WebRTC 实例 - 媒体录制器 </span>
        </h1>
        <video id="gum" playsinline autoplay muted></video>
        <video id="recorded" playsinline loop></video>
        <div>
            <button id="start"> 关上摄像头 </button>
            <button id="record" disabled class="off"> 开始录像 </button>
            <button id="play" disabled> 播放 </button>
            <button id="download" disabled> 下载 </button>
        </div>
        <div>
            <h4> 媒体流束缚选项:</h4>
            <p>
                打消回声: <input type="checkbox" id="echoCancellation">
            </p>
        </div>
        <div>
            <span id="errorMsg"></span>
        </div>
    </div>
    <script async>
'use strict';
const mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', handleSourceOpen, false);
let mediaRecorder;
let recordedBlobs;
let sourceBuffer;
const errorMsgElement = document.querySelector('span#errorMsg');
const recordedVideo = document.querySelector('video#recorded');
const recordButton = document.querySelector('button#record');
recordButton.addEventListener('click', () => {if (recordButton.className === 'off') {startRecording();
  } else {stopRecording();
    recordButton.textContent = '开始录像';
    recordButton.className = 'off';
    playButton.disabled = false;
    downloadButton.disabled = false;
  }
});
const playButton = document.querySelector('button#play');
playButton.addEventListener('click', () => {const superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
  recordedVideo.src = null;
  recordedVideo.srcObject = null;
  recordedVideo.src = window.URL.createObjectURL(superBuffer);
  recordedVideo.controls = true;
  recordedVideo.play();});
const downloadButton = document.querySelector('button#download');
downloadButton.addEventListener('click', () => {const blob = new Blob(recordedBlobs, {type: 'video/webm'});
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.style.display = 'none';
  a.href = url;
  a.download = 'test.webm';
  document.body.appendChild(a);
  a.click();
  setTimeout(() => {document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
});
function handleSourceOpen(event) {console.log('MediaSource opened');
  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
  console.log('Source buffer:', sourceBuffer);
}
function handleDataAvailable(event) {if (event.data && event.data.size > 0) {recordedBlobs.push(event.data);
  }
}
function startRecording() {recordedBlobs = [];
  let options = {mimeType: 'video/webm;codecs=vp9'};
  if (!MediaRecorder.isTypeSupported(options.mimeType)) {console.error(`${options.mimeType} is not Supported`);
    errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
    options = {mimeType: 'video/webm;codecs=vp8'};
    if (!MediaRecorder.isTypeSupported(options.mimeType)) {console.error(`${options.mimeType} is not Supported`);
      errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
      options = {mimeType: 'video/webm'};
      if (!MediaRecorder.isTypeSupported(options.mimeType)) {console.error(`${options.mimeType} is not Supported`);
        errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
        options = {mimeType: ''};
      }
    }
  }
  try {mediaRecorder = new MediaRecorder(window.stream, options);
  } catch (e) {console.error('Exception while creating MediaRecorder:', e);
    errorMsgElement.innerHTML = `Exception while creating MediaRecorder: ${JSON.stringify(e)}`;
    return;
  }
  console.log('Created MediaRecorder', mediaRecorder, 'with options', options);
  recordButton.textContent = '进行录像';
  recordButton.className = 'on';
  playButton.disabled = true;
  downloadButton.disabled = true;
  mediaRecorder.onstop = (event) => {console.log('Recorder stopped:', event);
  };
  mediaRecorder.ondataavailable = handleDataAvailable;
  mediaRecorder.start(10); // collect 10ms of data
  console.log('MediaRecorder started', mediaRecorder);
}
function stopRecording() {mediaRecorder.stop();
  console.log('Recorded Blobs:', recordedBlobs);
}
function handleSuccess(stream) {
  recordButton.disabled = false;
  console.log('getUserMedia() got stream:', stream);
  window.stream = stream;
  const gumVideo = document.querySelector('video#gum');
  gumVideo.srcObject = stream;
}
async function init(constraints) {
  try {const stream = await navigator.mediaDevices.getUserMedia(constraints);
    handleSuccess(stream);
  } catch (e) {console.error('navigator.getUserMedia error:', e);
    errorMsgElement.innerHTML = `navigator.getUserMedia error:${e.toString()}`;
  }
}
document.querySelector('button#start').addEventListener('click', async () => {const hasEchoCancellation = document.querySelector('#echoCancellation').checked;
  const constraints = {
    audio: {echoCancellation: {exact: hasEchoCancellation}
    },
    video: {width: 1280, height: 720}
  };
  console.log('Using media constraints:', constraints);
  await init(constraints);
});
</script>
    <style>
.hidden {display: none;}

.highlight {
    background-color: #eee;
    font-size: 1.2em;
    margin: 0 0 30px 0;
    padding: 0.2em 1.5em;
}

.warning {
    color: red;
    font-weight: 400;
}

@media screen and (min-width: 1000px) {
    /* hack! to detect non-touch devices */
    div#links a {line-height: 0.8em;}
}

audio {max-width: 100%;}

body {
    font-family: 'Roboto', sans-serif;
    font-weight: 300;
    margin: 0;
    padding: 1em;
    word-break: break-word;
}

button {
    background-color: #d84a38;
    border: none;
    border-radius: 2px;
    color: white;
    font-family: 'Roboto', sans-serif;
    font-size: 0.8em;
    margin: 0 0 1em 0;
    padding: 0.5em 0.7em 0.6em 0.7em;
}

button:active {background-color: #2fcf5f;}

button:hover {background-color: #cf402f;}

button[disabled] {color: #ccc;}

button[disabled]:hover {background-color: #d84a38;}

canvas {
    background-color: #ccc;
    max-width: 100%;
    width: 100%;
}

code {
    font-family: 'Roboto', sans-serif;
    font-weight: 400;
}

div#container {
    margin: 0 auto 0 auto;
    max-width: 60em;
    padding: 1em 1.5em 1.3em 1.5em;
}

div#links {padding: 0.5em 0 0 0;}

h1 {
    border-bottom: 1px solid #ccc;
    font-family: 'Roboto', sans-serif;
    font-weight: 500;
    margin: 0 0 0.8em 0;
    padding: 0 0 0.2em 0;
}

h2 {
    color: #444;
    font-weight: 500;
}

h3 {
    border-top: 1px solid #eee;
    color: #666;
    font-weight: 500;
    margin: 10px 0 10px 0;
    white-space: nowrap;
}

li {margin: 0 0 0.4em 0;}

html {overflow-y: scroll;}

img {
    border: none;
    max-width: 100%;
}

input[type=radio] {
    position: relative;
    top: -1px;
}

p#data {
    border-top: 1px dotted #666;
    font-family: Courier New, monospace;
    line-height: 1.3em;
    max-height: 1000px;
    overflow-y: auto;
    padding: 1em 0 0 0;
}

section p:last-of-type {margin: 0;}

section {
    border-bottom: 1px solid #eee;
    margin: 0 0 30px 0;
    padding: 0 0 20px 0;
}

section:last-of-type {
    border-bottom: none;
    padding: 0 0 1em 0;
}

select {
    margin: 0 1em 1em 0;
    position: relative;
    top: -1px;
}

video {
    background: #222;
    margin: 0 0 20px 0; -
    -width: 100%;
    width: var(- -width);
    height: calc(var(- -width)* 0.75);
}

@media screen and (max-width: 450px) {
    h1 {font-size: 20px;}
}

button {
    margin: 0 3px 10px 0;
    padding-left: 2px;
    padding-right: 2px;
    width: 99px;
}

button:last-of-type {margin: 0;}

p.borderBelow {
    margin: 0 0 20px 0;
    padding: 0 0 20px 0;
}

video {
    vertical-align: top; -
    -width: 25vw;
    width: var(- -width);
    height: calc(var(- -width)* 0.5625);
}

video:last-of-type {margin: 0 0 20px 0;}

video#gumVideo {margin: 0 20px 20px 0;}
</style>
</body>
</html>

留神:HTTPS 加密通信能力获取 getUserMedia(), 否则报错:
TypeError: Cannot read property ‘getUserMedia’ of undefined

咱们能够将以上代码拆散和简化成 html 和 js 文件,那么 index.html 的采集渲染代码如下:

<!DOCTYPE html>
<html>
      <head>
            <meta charset="utf-8"> 
            <title> 学习 webrtc</title> 
       </head>
       <body>
            <video autoplay></video> 
            <script src="main.js"></script>
      </body>
 </html>

main.js 采集代码如下:

// 判断是否反对调用设施 api,因为浏览器不同所以判断形式不同哦  
function hasUserMedia() {
    return !!(navigator.getUserMedia || navigator.webkitGetUserMedia
            || navigator.mozGetUserMedia || navigator.msGetUserMedia);
}
if (hasUserMedia()) {//alert("浏览器反对")  
    navigator.getUserMedia = navigator.getUserMedia
            || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
            || navigator.msGetUserMedia;
    navigator.getUserMedia({
        video : true,// 开启视频  
        audio : false
    // 先敞开音频,因为会有回响,当前两台电脑通信不会有响声  
    }, function(stream) {// 将视频流交给 video  
        var video = document.querySelector("video");
        //video.src=window.URL.createObjectURL(stream);     
        try {video.srcObject = stream;} catch (error) {video.src = window.URL.createObjectURL(stream);
        }
    }, function(err) {console.log("capturing", err)
    });
} else {alert("浏览器暂不反对")
}

通过以上代码,咱们就能够采集本地的摄像头播放本地的采集的视频和音频了!

源码获取、单干、技术交换请获取如下联系方式:
QQ 交换群:961179337

微信账号:lixiang6153
公众号:IT 技术快餐
电子邮箱:lixx2048@163.com

正文完
 0