您现在的位置是:网站首页> HTML&JS

开发库收集及使用

  • HTML&JS
  • 2024-10-10
  • 282人已阅读
摘要

开发库收集及使用


使用flv.js做直播

如何利用 `ffmpeg.js` 实现绿幕抠像播放

在H5页面如何将获得流手动喂给video元素



使用flv.js做直播

为什么要在这个时候探索flv.js做直播呢?原因在于各大浏览器厂商已经默认禁用Flash,之前常见的Flash直播方案需要用户同意使用Flash后才可以正常使用直播功能,这样的用户体验很致命。

点击查看flv.js源码

常见直播协议

RTMP: 底层基于TCP,在浏览器端依赖Flash。

HTTP-FLV: 基于HTTP流式IO传输FLV,依赖浏览器支持播放FLV。

WebSocket-FLV: 基于WebSocket传输FLV,依赖浏览器支持播放FLV。WebSocket建立在HTTP之上,建立WebSocket连接前还要先建立HTTP连接。

HLS: Http Live Streaming,苹果提出基于HTTP的流媒体传输协议。HTML5可以直接打开播放。

RTP: 基于UDP,延迟1秒,浏览器不支持。


在支持浏览器的协议里,延迟排序是:

RTMP = HTTP-FLV = WebSocket-FLV < HLS

而性能排序恰好相反:

RTMP > HTTP-FLV = WebSocket-FLV > HLS

也就是说延迟小的性能不好。


flv.js 简介

flv.js是来自Bilibli的开源项目。它解析FLV文件喂给原生HTML5 Video标签播放音视频数据,使浏览器在不借助Flash的情况下播放FLV成为可能。


flv.js 优势

由于浏览器对原生Video标签采用了硬件加速,性能很好,支持高清。

同时支持录播和直播

去掉对Flash的依赖


flv.js 原理

flv.js只做了一件事,在获取到FLV格式的音视频数据后通过原生的JS去解码FLV数据,再通过Media Source Extensions API 喂给原生HTML5 Video标签。(HTML5 原生仅支持播放 mp4/webm 格式,不支持 FLV)


flv.js 为什么要绕一圈,从服务器获取FLV再解码转换后再喂给Video标签呢?原因如下:

兼容目前的直播方案:目前大多数直播方案的音视频服务都是采用FLV容器格式传输音视频数据。

FLV容器格式相比于MP4格式更加简单,解析起来更快更方便。


搭建音视频服务

主播推流到音视频服务,音视频服务再转发给所有连接的客户端。为了让你快速搭建服务推荐我用go语言实现的livego,因为它可以运行在任何操作系统上,对Golang感兴趣?请看Golang 中文学习资料汇总。

下载livego,注意选对你的操作系统和位数。

解压,执行livego,服务就启动好了。它会启动RTMP(1935端口)服务用于主播推流,以及HTTP-FLV(7001端口)服务用于播放。


实现播放页

在react体系里使用react flv.js 组件reflv 快速实现。

先安装npm i reflv,再写代码:

import React, { PureComponent } from 'react';

import Reflv from 'reflv';


export class HttpFlv extends PureComponent {

  render() {

    return (

      <Reflv

        url={`http://localhost:7001/live/test.flv`}

        type="flv"

        isLive

        cors

      />

    )

  }

}


flv.js延迟优化

按照上面的教程运行起来的直播延迟大概有3秒,经过优化可以到1秒。在教你怎么优化前先要介绍下直播运行流程:


主播端在采集到一段时间的音视频原数据后,因为音视频原数据庞大需要先压缩数据:


通过H264视频编码压缩数据数据

通过PCM音频编码压缩音频AAC数据

压缩完后再通过FLV容器格式封装压缩后的数据,封装成一个FLV TAG


再把FLV TAG通过RTMP协议推流到音视频服务器,音视频服务器再从RTMP协议里解析出FLV TAG。


音视频服务器再通过HTTP协议通过和浏览器建立的长链接流式把FLV TAG传给浏览器。


flv.js 获取FLV TAG后解析出压缩后的音视频数据喂给Video播放。


知道流程后我们就知道从哪入手优化了:


主播端采集时收集了一段时间的音视频原数据,它专业的叫法是GOP。缩短这个收集时间(也就是减少GOP长度)可以优化延迟,但这样做的坏处是导致视频压缩率不高,传输效率低。

关闭音视频服务器的I桢缓存可以优化延迟,坏处是用户看到直播首屏的时间变大。

减少音视频服务器的buffer可以优化延迟,坏处是音视频服务器处理效率降低。

减少浏览器端flv.js的buffer可以优化延迟,坏处是浏览器端处理效率降低。

浏览器端开启flv.js的Worker,多线程运行flv.js提升解析速度可以优化延迟,这样做的flv.js配置代码是:

{

          enableWorker: true,

          enableStashBuffer: false,

          stashInitialSize: 128,// 减少首桢显示等待时长

}

这里是优化后的完整代码

import React, { PureComponent } from 'react';

import Reflv from '../../src/index';

import { HOST } from './index';


export class HttpFlv extends PureComponent {


  render() {

    return (

      <Reflv

        url={`http://${HOST}:7001/live/${this.props.id}.flv`}

        type="flv"

        isLive

        cors

        config={{

          enableWorker: true,

          enableStashBuffer: false,

          stashInitialSize: 128,

        }}

      />

    )

  }

}



如何利用 `ffmpeg.js` 实现绿幕抠像播放


<!DOCTYPE html>

<html>


<head>

  <meta charset="UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>绿幕抠像播放示例</title>

</head>


<body>

  <video id="outputVideo" controls></video>


  <script src="ffmpeg.min.js"></script>

  <script>

    // 选择要处理的绿幕视频文件

    const inputFile = document.getElementById('yourFileInput').files[0];


    // 配置抠像参数

    const greenScreenColor = {

      hue: [100, 150],  // 绿色的色相范围

      saturation: [50, 100],  // 饱和度范围

      luminance: [50, 100]  // 亮度范围

    };


    // 初始化 ffmpeg

    const ffmpeg = createFFmpeg({

      corePath: 'ffmpeg-core.js',

      log: true

    });


    (async () => {

      await ffmpeg.load();


      ffmpeg.FS('writeFile', 'input.mp4', await new Response(inputFile).arrayBuffer());


      await ffmpeg.run('-i', 'input.mp4', '-vf', `chromakey=h=${greenScreenColor.hue[0]}-${greenScreenColor.hue[1]}:s=${greenScreenColor.saturation[0]}-${greenScreenColor.saturation[1]}:l=${greenScreenColor.luminance[0]}-${greenScreenColor.luminance[1]}`, 'output.mp4');


      const data = ffmpeg.FS('readFile', 'output.mp4');

      const url = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));


      document.getElementById('outputVideo').src = url;

    })();

  </script>

</body>


</html>



在H5页面如何将获得流手动喂给video元素

处理分段获得的字节数组流。在这种情况下,我们可以使用MediaSource API来实现将分段的字节数组流喂给video元素。以下是具体的步骤和示例代码:


1.创建MediaSource对象:

const mediaSource = new MediaSource();

const video = document.querySelector('video');

video.src = URL.createObjectURL(mediaSource);


2.等待MediaSource打开,然后创建SourceBuffer:

mediaSource.addEventListener('sourceopen', sourceOpen);


function sourceOpen() {

  const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');

  // 注意: MIME类型和编解码器可能需要根据你的视频格式进行调整

}


3.定义一个函数来添加数据到SourceBuffer:

function appendBuffer(buffer) {

  if (sourceBuffer.updating || mediaSource.readyState !== 'open') {

    setTimeout(() => appendBuffer(buffer), 50);

    return;

  }

  sourceBuffer.appendBuffer(buffer);

}


4.当你收到字节数组时,将其添加到SourceBuffer:

// 假设你有一个函数来获取字节数组

function getNextChunk() {

  // 这里是获取下一个字节数组的逻辑

  return new Uint8Array([/* 字节数据 */]);

}


function processNextChunk() {

  const chunk = getNextChunk();

  if (chunk) {

    appendBuffer(chunk);

    // 处理下一个块

    setTimeout(processNextChunk, 0);

  } else {

    // 所有数据都已处理完毕

    mediaSource.endOfStream();

  }

}


// 开始处理数据

processNextChunk();

完整的示例代码如下:

const mediaSource = new MediaSource();

const video = document.querySelector('video');

video.src = URL.createObjectURL(mediaSource);


let sourceBuffer;


mediaSource.addEventListener('sourceopen', sourceOpen);


function sourceOpen() {

  sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');

  processNextChunk();

}


function appendBuffer(buffer) {

  if (sourceBuffer.updating || mediaSource.readyState !== 'open') {

    setTimeout(() => appendBuffer(buffer), 50);

    return;

  }

  sourceBuffer.appendBuffer(buffer);

}


function getNextChunk() {

  // 这里是获取下一个字节数组的逻辑

  // 返回 null 表示所有数据已经处理完毕

  return new Uint8Array([/* 字节数据 */]);

}


function processNextChunk() {

  const chunk = getNextChunk();

  if (chunk) {

    appendBuffer(chunk);

    // 处理下一个块

    setTimeout(processNextChunk, 0);

  } else {

    // 所有数据都已处理完毕

    mediaSource.endOfStream();

  }

}

这个方法允许你将分段获得的字节数组流喂给video元素。你需要根据实际情况调整MIME类型和编解码器,以及实现getNextChunk()函数来获取实际的字节数组数据。


请注意,这种方法要求你的字节数组必须是有效的MP4(或其他支持的格式)片段。如果你的数据不是标准的视频格式,可能需要先进行一些预处理或使用其他的方法来播放。


JS获得buffer组件Uint8Array的例子

function decodeImage() {

var oReq = new XMLHttpRequest();

oReq.open("GET", "demo_encode.png", true);

oReq.responseType = "arraybuffer";

oReq.onload = function (oEvent) {

var arrayBuffer = oReq.response; // 注意:不是oReq.responseText

if (arrayBuffer) {

var byteArray = new Uint8Array(arrayBuffer);

var decodeData = decode('KEY_HERE', byteArray);

document.getElementById("decodeJavaImage").src = URL.createObjectURL(

new Blob([decodeData], { type: 'image/png' })

);

}

};
















Top