前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >flv.js源码知识点

flv.js源码知识点

作者头像
阿珍
修改2022-07-08 11:32:13
1.2K0
修改2022-07-08 11:32:13
举报
文章被收录于专栏:用户9379088的专栏

1 网速计算

在音视频播放的场景中,用户的网速是影响体验的重要因素,播放器在播放的过程中,可以计算单位时间获取的数据量来衡量网速。flv.js的实例提供了statistics_info事件获取当前的网速。

代码语言:javascript
复制
flvPlayer.on('statistics_info', function(res) {
    console.log('statistics_info',res);
})

res结构如下:

代码语言:javascript
复制
{
    currentSegmentIndex: 0,
    decodedFrames: 15,
    droppedFrames: 0,
    hasRedirect: false,
    loaderType: "fetch-stream-loader",
    playerType: "FlvPlayer",
    speed: 395.19075278358656,
    totalSegmentCount: 1,
    url: "https:/example.com/1.flv"
}

其中的speed字段就是网速,单位是KB/s, 下面就看关于网速计算相关的部分。statistics_info事件中获取网速的整体流程如下图:

  • IOController中控制每次把加载的字节数添加到SpeedSampler中,对外提供的lastSecondKBps属性是最近有数据一秒的网速。
  • TransmuxingController中控制播放器在加载数据的时候开启定时器获取统计数据,向上触发事件。

核心的计算还是SpeedSampler类, lastSecondKBps是getter属性获取最近有数据一秒的网速,代码含义参考注释。

代码语言:javascript
复制
 get lastSecondKBps () {
    // 如果够1s计算 this._lastSecondBytes
    this.addBytes(0)
    
    // 上1秒的_lastSecondBytes有数据 就直接返回
    // 这个巧妙的是 感觉不是准确的1s 但是又是准确的 因为如果是超过1秒就不继续添加了 1秒内的就添加进去了。
    
    // 如果上一秒有数据则返回
    if (this._lastSecondBytes !== 0) {
      return this._lastSecondBytes / 1024
    } else {
      // 如果上一秒的速度是0,并且距离上次计算超过了500ms 则用_intervalBytes和durationSeconds进行计算
      if (this._now() - this._lastCheckpoint >= 500) {
        // if time interval since last checkpoint has exceeded 500ms
        // the speed is nearly accurate
        return this.currentKBps
      } else {
        // We don't know
        return 0
      }
    }
  }

下面是addBytes方法,根据本次调用的时间和上一次计算时间的差值做不同处理,具体参见代码注释,这种计算的思路是挺巧妙的,开始以为不准切,但是仔细思考是能准确计算最近有数据一秒的网速。一直强调是最近有数据一秒的网速而不是上一秒的网速。

代码语言:javascript
复制
addBytes (bytes) {
    // 如果是第一次调用则 记录_firstCheckpoint _lastCheckpoint
    if (this._firstCheckpoint === 0) {
      this._firstCheckpoint = this._now()
      this._lastCheckpoint = this._firstCheckpoint
      this._intervalBytes += bytes
      this._totalBytes += bytes
    } else if (this._now() - this._lastCheckpoint < 1000) {
      // 小于1s 就添加 _intervalBytes
      this._intervalBytes += bytes
      this._totalBytes += bytes
    } else { // duration >= 1000
    
      // 只有大于1秒的时候才计算_lastSecondBytes 
      // 就是这1s内的_intervalBytes
      this._lastSecondBytes = this._intervalBytes
      
      this._intervalBytes = bytes // 并且重新开始计算_intervalBytes 大于1秒的这次数据算在下1秒
      
      this._totalBytes += bytes
      this._lastCheckpoint = this._now()
    }
}

下面是currentKBps getter属性,在lastSecondKBps中只有当超过因为如果durationSeconds大于0.5时才使用currentKBps属性,因为如果durationSeconds过小,会过大估计了网速。

代码语言:javascript
复制
get currentKBps () {
    this.addBytes(0)

    let durationSeconds = (this._now() - this._lastCheckpoint) / 1000
    if (durationSeconds == 0) durationSeconds = 1
    return (this._intervalBytes / durationSeconds) / 1024
  }

平均网速averageKBps, 如果中途出现网络中断或者暂停的情况会拉低平均网速。

代码语言:javascript
复制
get averageKBps () {
    let durationSeconds = (this._now() - this._firstCheckpoint) / 1000
    return (this._totalBytes / durationSeconds) / 1024
 }

2 数据缓存处理

这里讲的缓存是指使用loader获取数据后到传给FLVDemuxer过程中的缓存。这个过程中为什么需要缓存呢?因为FLV格式数据的解封是以TAG为单位,而过来的数据是流式的字节,不可能每次是完整的TAG,所以FLVDemuxer每次只处理当前数据中完整的TAG,没有处理的部分就缓存起来,和下次获取的数据拼接。

通过上面的原理介绍,你应该可以猜到这个过程是放在IOController中,我们先分解缓存中使用到的几个关键API和操作方法。

2.1 二进制缓存区格式

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。 你不能直接操作 ArrayBuffer 的内容,而是要通过类型数组对象DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

这里的定义 关键有两点,一是ArrayBuffer是固定长度,所以扩展的话需要创建新的然后把数据复制过去,而是不能直接操作,二是 不能直接操作,需要用类型数据对象,我们这里用Uint8Array,因为8位无符号正好是以一个字节为单位。我们这里对缓存的处理,暂时不需要读取指定的字节,目前只需要能够读取指定位置的数据即可。

2.2 缓存区操作API

Uint8Array 数组类型表示一个8位无符号整型数组,创建时内容被初始化为0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。

new Uint8Array(buffer [, byteOffset [, length]]);

说明:在ArrayBuffer上创建Uint8Array对象,使缓存区可操作。 参数: bufferArrayBuffer对象,byteOffset指定ArrayBuffer的起始字节数,length指定创建的长度。

typedarray.set(typedarray[, offset]) 说明:Uint8Array属于typedarray, set方法可以从指定类型化数据中读取值,并将其存储在类型化数组中的指定位置。 参数:typedarray是指要拷贝的源数据,offset指拷贝到目标数据的起始位置。

2.3 方法一 扩展缓存

根据上面的api,把长度为100的ArrayBuffer扩展为长度为1000的ArrabyBuffer

代码语言:javascript
复制
const oldbuffer = new ArrayBuffer(100);
const u1 = new Uint8Array(oldbuffer, 0);
const newbuffer = new ArrayBuffer(1000);
const u2 = new Uint8Array(newbuffer,0);
u2.set(u1,0);
2.4 方法二 消费缓存

记录缓存消费位置,消费一部分后重新设置缓存。

代码语言:javascript
复制
let stashUsed = 100;
let bufferSize = 1024;
let stashBuffer = new ArrayBuffer(1024);

// 消费数据 返回消费的字节数

let consumed = dispatchChunks(stashBuffer.slice(0, stashUsed),stashUsed);
let allBuffer = new Uint8Array(stashBuffer, 0, bufferSize);
let remainBuffer = new Uint8Array(stashBuffer, consumed);
allBuffer.set(remainBuffer,0);
stashUsed = stashUsed-consumed;

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 网速计算
  • 2 数据缓存处理
    • 2.1 二进制缓存区格式
      • 2.2 缓存区操作API
        • 2.3 方法一 扩展缓存
          • 2.4 方法二 消费缓存
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档