Flash视频播放器开发经验总结

HTTP协议更优

目前几乎全部的视频点播网站所有采用HTTP协议传输数据。由于相对于诸如RTMP等协议来讲,HTTP协议是无状态的,数据传输完毕就断开链接,这样服务器就能够腾出资源来服务更多的用户。而RTMP则会在用户播放期间一直维护一个链接,这样服务器的负载就很是有限。并且HTTP服务器,CDN等都已是很是成熟的技术,成本低性能好。另外HTTP的请求能够直接使用浏览器Cookie,容易和网站业务打通。最后,HTTP还能使用浏览器缓存,这算优势也算缺点,优势是请求一样的资源能够直接从缓存中取,缺点是安全性差了点。nginx

HTTP拥有更好的性能,可是无法传输太实时性的东西,不然性能还不如RTMP,好比视频聊天,直播这些。apache

安全性

有时候咱们被访问的视频可能须要作一些限制,好比防盗链,视频收费等等。若是采用HTTP协议的话,传统的鉴权方式就足够,Cookie里带token什么的判断是否有权限访问视频资源,细节我就不说了。惟一的问题是一旦用户有权限访问视频,就有可能把视频下载下来用做他用。浏览器

经过分段进一步提高负载能力

为了让HTTP能服务更多的用户,同时维护更少的链接,咱们须要传输尽快完毕。但这是咱们分段的理由吗?不是,由于不管分段不分段,一个用户加载彻底部的视频数据对服务器占用时间是必定的(假设传输速度必定),甚至会多占用不少建立链接和销毁链接的资源。缓存

可是咱们看到各大视频网站实际上都是有对视频分段的,这里我就谈谈视频分段的好处。安全

  1. 节约网站流量,也就是节约服务器资源提升负载能力。当用户打开一个视频的时候,颇有可能不会把视频看完,只看一部分。若是不对视频作分段,用户一打开网站就把全部视频数据加载完,那么对流量就是极大的浪费。把视频分段后咱们能够一段一段加载视频,作到用户看多少咱们就加载多少。
  2. 更加灵活的seek(拖动),对于一个不作任何分段的视频,好比HTTP服务器上的静态视频文件,咱们是没法经过NetStream对象seek方法跳转到未加载的视频部分的。因此为了解决这个问题,apache和nginx都提供了flv模块,支持start参数。当指定start参数的时候,咱们能够从新从指定位置加载视频,解决了上述问题。可是带来的问题是流量浪费,可能本来加载过的地方又要从新加载一遍。若是咱们采用分段的方式,就能够避免这个问题,具体实现方法后面会详细介绍。

NetStream对象

咱们在Flash端如何播放视频很大程度上受NetStream提供的功能所限。因此这里大体介绍下NetStream提供的功能和一些限制,这也是为何后面程序要这么设计的缘由。服务器

  1. NetStream提供两种能够播放HTTP视频的模式,普通模式和数据生成模式。
  2. 在普通模式下,往NetStream传入咱们要播放的HTTP视频资源地址,NetStream就会开始加载视频并开始播放。咱们能够暂停视频播放,可是不能暂停数据的加载,咱们能够在已经加载过的数据部分随意seek,可是不能seek到未加载的部分。数据加载完毕以后咱们任然能够进行播放,seek等操做,可是若是调用了close方法关闭流,那么若是数据未加载完毕,就会中止加载,而且不能作任何播放,seek等操做,这至关于咱们原来加载的数据都白费,不能再使用。因此若是咱们要把视频分段后随意在各个视频分段里来回seek,咱们必须让一个分段视频对应一个NetStream实例,换句话说有几个分段就须要几个NetStream伺候他们(咱们暂且这么认为,后面咱们会对这个问题作优化)。
  3. 在数据生成模式下,NetStream提供更加灵活的加载方式。NetStream经过appendBytes方法能够添加外部的二进制数据来播放视频,添加数据的顺序就是播放的顺序。这种状况下咱们能够经过URLStream对象加载视频文件数据,理论上全部加载过的数据均可以被重复利用。可是注意不要把全部数据往内存里塞,不然内存会被撑爆。具体的缓存策略后续具体讲。
  4. 和其余平台的视频播放器不一样,Flash不能直接访问本地文件,可是能够经过加载已经加载过的视频让浏览器从缓存中快速取得视频数据。因此如何有效利用缓存是优化的关键。
  5. 不要迷信NetStream的NetStatusEvent事件,在不一样服务器和浏览器环境下,这个事件发生的时机可能略有差异,因此事件只能作参考,须要另外作一些前提判断。

视频分段须要的服务器支持

  1. 静态分段:把视频分为固定的能够独立播放的几段保存到服务器上,播放的时候须要得到一个视频地址列表。每一个静态分片都只能从头开始请求不能从切片的中间开始请求。这是最容易作到也是性能作好的方式。
  2. 静态分片+start参数:第一个方案的改进,能够支持从分片的中间开始请求到分片的结尾。优酷土豆都是这么作的噢。这样方便seek。也有现成的nginx和apache模块能够支持。
  3. 动态分片:同时提供start和end参数,这样能够由播放器来决定如何请求分片,对播放器来讲更灵活,对服务器的文件管理来讲也更方便。这个解决服务器解决方案nginx和apache应该也有,没有细究过。
  4. 以上三种最后请求出来的数据都是一个能完整独立播放的视频文件,服务器会自动帮你加上视频文件头。若是Flash使用的是数据生成模式,那么实际上返回的直接是一个文件数据片断就好了,不须要另外加上文件头。

朴素的分段视频播放

直接播放单个视频文件的方式我就不说了,我这里介绍的是如何像播一个完整文件同样播放通过分段的视频。这个方案有些许瑕疵,后续的方案都是基于这个方案进行优化的。网络

服务器咱们采用上面提到的第一种最简单的静态分段。而且在视频开始播放前咱们会拿到一个包含视频分段的开始时间,结束时间,以及分段地址的列表,还有个总的视频metadata信息。app

当视频列表加载完毕后就能够开始依次经过NetStream加载播放各个视频分片了,每一个分片用一个NetStream实例控制。如图所示。性能

咱们能够设定一个最大缓冲距离,结合当前播放进度,算出一个容许缓冲位置,在这个容许缓冲位置以内的切片均可以依次开始加载,开始加载的时候暂停住不播放。当一个切片开始加载以后是不会中止的,因此实际缓冲进度可能会大于容许缓冲位置。优化

当一个切片播放完毕以后不要急着把它关掉,它可能须要留着供后续的seek使用。紧接着,咱们把下一个分片执行resume方法来让他播放。这样多个分片按照顺序播放,对外界来讲就像播放一个完整的视频同样。

这种结构下,若外界须要对视频进行seek操做,能够分三种状况:

  1. seek到已加载分片的已加载部分,这种状况效率最高,直接暂停当前播放的分片(若是是seek的位置就是当前切片这步均可以省了),让seek目标时间所在分片seek到对应位置恢复播放就好了。
  2. seek到已加载分片的未加载部分,操做和上面的相似,因为要seek的部分还未加载完,因此咱们只能seek到该分片已加载的最接近位置让视频尽快开始播放。
  3. seek到一个还未开始加载的切片的某个位置,一样暂停当前播放的切片,转到目标切片让目标切片开始加载并尽快开始播放。(当前正在加载的切片有两种处理策略,一种是还让切片继续加载完毕,另一种是直接关闭。还有一种折中策略,若是已经加载超过一半就让他继续加载完,不然关闭)

分段视频播放改进:增长start参数

因此咱们能够看到,静态分片方式的在seek的处理仍是仍是有不少不足的,对未加载部份内容的seek都不能作到很是精确。不过若是将切片切得比较短小的话这个问题能够有所改善,可是还会带来另外的问题,这个问题我后面讲。另外咱们能够再静态分片的基础上引入了start参数,也就是上文提到的“静态分片+start参数”类型服务器。

引入了start参数后对上面的二、3两种seek状况进行了改进:

  1. seek到已加载分片的未加载部分,关闭这个分片正在加载的流,并用这个分片的NetStream从新从seek位置指定的位置开始加载(经过指定start参数)。不过这个start参数不是随便什么均可以的,须要是视频关键帧位置,不然返回回来将不能播。关键帧位置在metadata里面能够查询到。
  2. seek到未加载切片也一样,从根据seek位置设定start参数后开始加载。

如此以来在任何状况下seek均可以精确到关键帧,缺点是把正在加载的切片关掉会形成数据浪费。从切片中间开始加载也会形成一个切片内容不完整。下次seek的时候若是不巧是在这个切片start位置以前,就须要从新加载该切片。这些都会形成数据浪费。好在通常用户不会吃饱了没事儿seek来seek去。

经过链接池来限制链接数量

从上文的几个策略能够看出,若是视频分得越短小,不管对seek的精确度,仍是数据浪费状况都是有好处的,可是这带来的一个问题是须要实例化更多的NetStream来维护切片。另外对于时长较长的视频来讲,NetStream的数量也会变得不少。但实际上NetStream能同时开启的链接数量是有限的,这不是内存问题,而是Flash提供的链接数有限。超过了这个限制NetStream就没办法正常工做了,并且也不报错。这个限制在不一样浏览器下还不同,我怀疑这和浏览器底层有关。

因此为了限制NetStream的数量,咱们须要设计一个NetStream链接池来管理全部的NetStream。链接池上限不能小于最大缓冲举例可能加载的最多分片数,不然逻辑上就是有问题滴。

咱们能够从链接池中取得一个新的NetStream来使用(这个NetStream多是别的NetStream关闭后的,不过你能够把它当新的用)当链接池数量满的时候,他就会自动把一些老的处于链接状态的NetStream关闭掉。这个淘汰原则是基于空间局部性原理的,也就是说和当前播放头位置距离最远的切片应该首先被关掉(处于最大缓冲距离以内的切片不能关闭)。由于根据几率统计发现大部分的seek都出如今播放头附近(可能为了找什么情节)。

推荐使用数据生成模式

经过多个NetStream切换的方式播放视频,在切换的时候会出现不明显的爆音,可是仔细听仍是可以发现。这也是我在上文中提到的文件分割得过短小出现的另一个问题,爆音太频繁了,可能影响视频观看。

因此要从根本上解决这个问题,咱们就要放弃NetStream切换的方式,转用数据生成模式。数据生成模式能够把请求的切片作得很小(但也不要过小,不然服务器性能下降)。切片作小的一个好处是请求更快的完成,那么请求被打断的概率就会下降,当请求完成以后,下次请求一样的资源就能从浏览器缓冲中取。因此小切片更容易被缓存。而上文中的小切片产生的问题在这里不复存在。如图所示。

咱们根据播放头的位置,日后加载分片数据,直到最大缓冲距离,这和前面提到的方式相似。然后咱们把这些加载的二进制数据保存在内存中。从播放后日后必定的距离(咱们称做NS缓冲长度),若是有分片进入,那么就把它appendBytes到用于播放的NetStream中。图中所示的蓝色部分就是保存在内存中的数据,它也有前面提到的链接池相似的淘汰机制用于控制内存总大小。被从内存中释放掉的数据,咱们能够在浏览器缓存中找到(由于已经加载过了),若是要使用的话,咱们能够像请求服务端数据同样的方式快速请求到这些数据(固然比从内存中慢一些)。图中白色方框的是还未加载过的数据,他们在服务器上等待加载。如图所示就是数据的三级查询。

若是用户进行seek:

  1. seek的位置位于内存中的数据:先清空NetStream的缓冲,而后把内存中响应位置的数据日后必定距离(NS缓冲长度以上)加入到NetStream中用于播放。
  2. seek的位置位于缓存中:先把缓存中的数据加载到内存中,而后经过第一条的方式实现。
  3. seek的位置位于服务器上:从服务器上加载数据分片数据到内存中,而后经过第一条的方式实现。

若是分片数据较大,seek的位置在分片中间,那么也能够从分片中间开始加载,这样能够从逻辑上把一个分片分为了两个。

数据生成模式从本质上保证了播放质量,杜绝了数据浪费,保证了seek精确度,服务器实现上也异常简单,真是视频播放首选!

赠品:用分段方式作直播

这种方式须要服务器作实时分片并分发到CDN。好比服务器从直播数据源里把30秒的视频数据打包成一个数据包分发到CDN上,因此理论上直播至少会延迟30秒。不过对于实时性不是特别强的直播,这种方式的负载能力会更好。

传统的长链接方式直播,须要客户端和服务器一直保持链接,服务器须要维护每一个客户端的链接,但实际上传输30秒的视频数据只须要1秒,因此若是采用HTTP的方式,由于传输完毕就能够服务别人了,因此理论上维护链接的效率能够提升30倍。

这里咱们要求服务器提供一个视频地址列表,列表里提供了最新的N个视频分片地址。这样客户端经过轮询这个视频列表就能让客户端和直播保持同步。

如图所示客户端维护着一个切片列表队列,经过轮询服务器,咱们把最新的视频地址添加到队列中,而播放模块则从队列中取出最老的切片地址加载播放。

若是用户网络较差,那么播放就会卡顿,因此从队列中取出切片地址的频率就会下降,队列会愈来愈长。队列越长说明视频播放的延迟越大。

因此当队列长于某一个临界值时(咱们设定的),咱们就把队列清空到只剩一个最近的地址,直到下一次这个地址被取出时,才容许队列继续变长。这个队列清空的操做其实是对因播放卡顿引发的延迟作了矫正,让直播不要延迟得太厉害。

相关文章
相关标签/搜索