flv.js的追幀、斷流重連及實時更新的直播優化方案

来源:https://www.cnblogs.com/xiahj/archive/2022/07/20/flvExtend.html
-Advertisement-
Play Games

1. 前言 最近在處理前端直播的業務,根據業務需要,使用 flv.js 的方案播放實時的flv視頻流。不得不承認,flv.js 是一個偉大的庫。 在使用flv.js開發的過程中,遇到了一些問題,也無外乎是視頻延遲,視頻卡頓等問題,經過在github issues里摸爬滾打,加上長時間的試錯,將這些問 ...


目錄

1. 前言

最近在處理前端直播的業務,根據業務需要,使用 flv.js 的方案播放實時的flv視頻流。不得不承認,flv.js 是一個偉大的庫。

在使用flv.js開發的過程中,遇到了一些問題,也無外乎是視頻延遲,視頻卡頓等問題,經過在github issues里摸爬滾打,加上長時間的試錯,將這些問題歸納出了對應的解決方案,也自己封裝了一個擴展插件 flvExtend

於是寫這篇文章來對我遇到的一些問題進行總結,我提出的解決方案不一定適合所有場景,如果有更好的解決方案,歡迎討論,這也是我寫這篇文章的目的,也是我寫文章的初心。

2. 前端直播

在講解 flv.js 的優化方案之前,我想先簡單的介紹一下前端直播的方案,為什麼要使用 flv.js,方便大家理解以及作為一項技術來儲備。

2.1 常見直播協議

  • 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 秒,瀏覽器不支持。

可以看到,在瀏覽器端,可以考慮的方案有:HTTP-FLVWebSocket-FLV 以及 HLS, 我們可以對比一下這幾個直播協議之間的性能:
(以下數據來源於網路,只做對比參考)

傳輸協議 播放器 延遲 記憶體 CPU
RTMP Flash 1s 430M 11%
HTTP-FLV Video 1s 310M 4.4%
HLS Video 20s 205M 3%

可以看出在瀏覽器里做直播,使用 HTTP-FLV 協議是不錯的,性能優於 RTMP+Flash,延遲可以做到和 RTMP+Flash 一樣甚至更好。

2.2 flv.js 的原理

flv.js 的主要工作就是,在獲取到 FLV 格式的音視頻數據後通過原生的 JS 去解碼 FLV 數據,再通過 Media Source Extensions API 喂給原生 HTML5 Video 標簽。(HTML5 原生僅支持播放 mp4/webm 格式,不支持 FLV)

flv.js 為什麼要繞一圈,從伺服器獲取 FLV 再解碼轉換後再喂給 Video 標簽呢?原因如下:

  1. 相容目前的直播方案:目前大多數直播方案的音視頻服務都是採用 FLV 容器格式傳輸音視頻數據。
  2. FLV 容器格式相比於 MP4 格式更加簡單,解析起來更快更方便。

2.3 flv.js 的簡單使用

<script src="flv.min.js"></script>
<video id="videoElement"></video>
<script>
  if (flvjs.isSupported()) {
    var videoElement = document.getElementById("videoElement");
    var flvPlayer = flvjs.createPlayer({
      type: "flv",
      isLive: true,
      url: "http://example.com/flv/video.flv",
    });
    flvPlayer.attachMediaElement(videoElement);
    flvPlayer.load();
    flvPlayer.play();
  }
</script>

主要流程就是:

  1. 創建flvjs.Player對象,可以傳遞兩個參數:MediaDataSource,以及 Config,具體的可以看下官方文檔
  2. 掛載元素
  3. 載入視頻流
  4. 播放視頻流

附:官方 API 文檔

3. flv.js 的優化方案

我們根據官方的例子,可以很容易地把 flv 直播流播起來,但是在實際項目中使用時,還會遇到一些問題,我們需要手動對這些問題進行優化處理

3.1 追幀-解決延遲累積問題

flv.js 有一個最大的問題,就是延遲問題,一方面是直播端的延遲,一方面是瀏覽器的延遲,而且瀏覽器的延遲如果不做特殊處理,會造成延時累積的問題,對直播的實時性影響很大。

解決方案需要從以下兩部分入手:

3.1.1 修改 config 配置

{
  enableWorker: true, // 啟用分離的線程進行轉換
  enableStashBuffer: false, // 關閉IO隱藏緩衝區
  stashInitialSize: 128, // 減少首幀顯示等待時長
}
  • 開啟 flv.js 的 Worker,多線程運行 flv.js 提升解析速度可以優化延遲
  • 關閉 buffer 緩存,這個選項可以明顯地降低延遲,缺點就是由於關閉了 buffer 緩存,網路不好的時候可能會出現 loading 載入
  • 調低 IO 緩衝區的初始尺寸,減少首幀顯示的等待時長

3.1.2 追幀設置

解決延時累加最有效的方式就是進行追幀設置

追幀,就是去判斷緩衝區末尾的 buffer 值與當前播放時間的差值,如果大於某個值,就進行追幀設置,具體的思路如下:

  1. 首先,在 progress 事件,或者定時器中進行追幀邏輯
  2. 判斷 buffer 的差值 delta
let end = this.player.buffered.end(0); //獲取當前buffered值(緩衝區末尾)
let delta = end - this.player.currentTime; //獲取buffered與當前播放位置的差值
  1. 如果 delta 值大於某個設定的值,則進行追幀操作
  2. 追幀有兩種方式
    1)一種是直接更新當前的時間:this.player.currentTime = this.player.buffered.end(0) - 1,缺點是如果頻繁觸發會導致跳幀,觀感差;
    2)一種是調快播放速度的方式來慢慢追幀: this.videoElement.playbackRate = 1.1,優點是穩定,缺點是如果 delta 值過大,通過這種方式追得太慢
    在實際使用中兩種方式可以結合起來。

代碼實現:

videoElement.addEventListener("progress", () => {
  let end = player.buffered.end(0); //獲取當前buffered值(緩衝區末尾)
  let delta = end - player.currentTime; //獲取buffered與當前播放位置的差值

  // 延遲過大,通過跳幀的方式更新視頻
  if (delta > 10 || delta < 0) {
    this.player.currentTime = this.player.buffered.end(0) - 1;
    return;
  }

  // 追幀
  if (delta > 1) {
    videoElement.playbackRate = 1.1;
  } else {
    videoElement.playbackRate = 1;
  }
});

3.2 斷流重連

斷流重連即在flvjs播放失敗的回調中,進行重建視頻的操作

代碼實現:

this.player.on(flvjs.Events.ERROR, (e) => {
  // destroy
  this.player.pause();
  this.player.unload();
  this.player.detachMediaElement();
  this.player.destroy();
  this.player = null;

  // 進行重建的邏輯,這裡不再展開
  this.init();
});

3.3 實時更新

直播需要保證視頻的實時性,以下兩種操作都會導致視頻的實時性得不到保證:

  • 用戶點擊了暫停,過一段時間後再點播放,這時候的直播視頻不是最新的
  • 網頁切到後臺,再重新切換回前臺,視頻不是最新的

所以需要根據這兩種情況來實時更新視頻

代碼實現:

// 點擊播放按鈕後,更新視頻
videoElement.addEventListener("play", () => {
  let end = player.buffered.end(0) - 1;
  this.player.currentTime = end;
});

// 網頁重新激活後,更新視頻
window.onfocus = () => {
  let end = player.buffered.end(0) - 1;
  this.player.currentTime = end;
};

3.4 解決 stuck 問題

有的時候,視頻在播放的過程中會突然卡住,或者控制台有時會報錯 “Playback seems stuck at 0, seek to 1.1”。

我們需要判斷視頻是否卡住了,然後重建視頻實例

思路就是判斷 decodedFrames 是否產生變化,如果視頻是播放狀態並且該值沒有產生變化,則可以判斷視頻卡住了。

代碼實現:

function handleStuck() {
  let lastDecodedFrames = 0;
  let stuckTime = 0;

  this.interval && clearInterval(this.interval);
  this.interval = setInterval(() => {
    const decodedFrames = this.player.statisticsInfo.decodedFrames;
    if (!decodedFrames) return;

    if (lastDecodedFrames === decodedFrames && !this.videoElement.paused) {
      // 可能卡住了,重載
      stuckTime++;
      if (stuckTime > 1) {
        console.log(`%c 卡住,重建視頻`, "background:red;color:#fff");
        // 先destroy,再重建視頻實例
        this.rebuild();
      }
    } else {
      lastDecodedFrames = decodedFrames;
      stuckTime = 0;
    }
  }, 800);
}

4. 封裝插件 flvExtend.js

我將這些優化方案封裝成了一個插件 flvExtend.js,它相當於是 flv.js 的一個功能擴展

插件地址:https://github.com/shady-xia/flvExtend

使用起來是這個樣子:

import FlvExtend from "flv-extend";

// 配置需要的功能
const flv = new FlvExtend({
  element: videoElement, // *必傳
  frameTracking: true, // 開啟追幀設置
  updateOnStart: true, // 點擊播放後更新視頻
  updateOnFocus: true, // 獲得焦點後更新視頻
  reconnect: true, // 開啟斷流重連
  reconnectInterval: 2000, // 斷流重連間隔
});

// 調用 init 方法初始化視頻
// init 方法的參數與 flvjs.createPlayer 相同,並返回 flvjs.player 實例
const player = flv.init(
  {
    type: "flv",
    url: "http://192.168.0.11/stream",
    isLive: true,
  },
  {
    enableStashBuffer: false, // 如果您需要實時(最小延遲)來進行實時流播放,則設置為false
    stashInitialSize: 128, // 減少首幀顯示等待時長
  }
);

// 直接調用play即可播放
player.play();

5. 其他問題

這裡打算長期記錄一下遇到的問題以及解決思路,歡迎大家討論,我會更新補充

1)多路視頻同時直播

由於瀏覽器對 http 1.0 的限制,以Chrome為例,同一個瀏覽器下,最多只能播6路同源地址下的視頻(包括多個標簽頁也會被合算在內)

目前的解決方案有:

  1. 使用http 2.0,由於http 2.0的多路復用,可以同屏播放多個視頻流
  2. 使用 websocket
  3. 通過為流分配不同的服務端地址

參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 04 | 深入淺出索引(上) 索引的出現其實就是為了提高數據查詢的效率,就像書的目錄一樣 索引的常見模型 哈希表、有序數組和搜索樹 哈希表 User2 和 User4 根據身份證號算出來的值都是 N,但沒關係,後面還跟了一個鏈表。 需要註意的是,圖中四個 ID_card_n hash 出來的值並不是 ...
  • 先上命令速查網站,菜鳥yyds https://www.runoob.com/redis/redis-strings.html 操作redis的包是go-redis/redis 官方文檔 https://redis.uptrace.dev/guide/ github https://github.c ...
  • SQL的多表查詢 多表查詢的概述 指從多張表中查詢數據; 各個表格之間相互關聯,基本分為:一對多(多對一)、多對多、一對一; 語法 select * from 表1,表2; 查詢分類 連接查詢: **內連接:**相當於查詢A、B表的交集部分數據; 外連接: **左外連接:**查詢左表所以數據,以及兩 ...
  • 03 | 事務隔離:為什麼你改了我還看不見? 事務 Transaction TRX 事務就是要保證一組資料庫操作,要麼全部成功,要麼全部失敗。 MySQL 原生的 MyISAM 引擎不支持事務 隔離性與隔離級別 SQL 標準的事務隔離級別包括:讀未提交(read uncommitted)、讀提交(r ...
  • 人臉識別目前已廣泛應用於手機解鎖、刷臉支付、閘機身份驗證等生活場景,然而,人臉識別能力雖帶來了極大的便利,卻無法鑒別人臉是否真實,比如使用高模擬圖片、精密石膏或3D建模面具,即可輕鬆攻破人臉識別演算法,單獨使用該能力存在極大的安全隱患。 華為機器學習服務的動作活體檢測能力,通過採用指令動作配合的方式進 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、什麼是WebSocket WebSocket 是一種在單個TCP連接上進行全雙工通信的協議。WebSocket 使得客戶端和伺服器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。 在 WebSocket API 中,瀏覽器 ...
  • 主要講解運用Webpack 5 中集成 ESLint 的方法與步驟 ...
  • const person = { name: 'Lydia' } Object.defineProperty(person, 'age', { value: 21, }) console.log(person) console.log(Object.keys(person)) 輸出結果 原因: de ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...