Flash播放器,直播类网站不得不提的模块。首先对于一个完整的视频播放器,最核心的是播放功能,这里简单概述的话就是一个连接建立,流的连接和推流以及对连接状态和流状态的监听管理的过程。这是一条基本的线路,这其中为了提高用户体验又要扩展出缓冲区,设计QoS服务,重连方案等概念,为了业务的实现需求,又要增加播放的各种操作,弹幕显示等需求。下面就以上提到的几个问题,简要总结下一些在青果广场播放器在开发中的设计思路。
首先,作为一个播放器工程要有合理的工程结构,这里除了皮肤类我简单的概括为如下几个主要的类:
下面重点介绍下面几个重要的类和模块
1、 videoPlayer
实例化一个videoPlayer,videoPlayer继承自自定义UI类SkinnableComponent,而SkinnableComponent继承子UIComponent并扩展了Skin相关的功能。因此这里实例一个videoPlayer就是实例了一个播放器UI组件。在videoPlayer构造函数中,主要是定义了一些event handler
public function VideoPlayer() {
super();
addEventListener(Event.ADDED_TO_STAGE, onAddToStage);
addEventListener(FlexEvent.VALUE_COMMIT, onVolumeChange);
addEventListener(FlexEvent.MUTED_CHANGE, onMutedChange);
addEventListener(MediaEvent.MEDIA_ERROR, onMediaError);
addEventListener(MediaEvent.MEDIA_META_DATA, onMediaMetaData);
addEventListener(MediaEvent.MEDIA_RECONNECT, onMediaReconnect);
addEventListener(MediaEvent.MEDIA_UNPUBLISH, onMediaUnpublish);
addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
addEventListener(HeartbeatEvent.HEARTBEAT, onHeartbeat);
this.addEventListener(ChangeVbrEvent.CHANGEVBR, onChangeVbr);
}
这里有一个重要的方法onAddToStage,表示当所有的组件元素加载到stage中时,此时这个函数依次做了以下几个方法:
(1) init() 初始化播放器的各参数(弹幕的参数MaskService,QoS的实例,截图的参数初始化);
(2)定义监听事件以及一些定时任务,例如:onResizeBufferTimer,onCalcDownloadBPSTimer,onMiscTimer;
(3) 最后如果是在autoplay状态下,调用this.play()开始自动播放
在play()方法中我们去执行了createMedia方法,去实例一个RTMPVideoMedia,这个将在第2点中详细介绍。
在视频的播放过程中重连的机制是对于减少没必要的连接次数和提高用户体验方面有重要的作用,我们在上面提到的onMiscTimer事件的监听中设计了如下的重连机制:
if (_media && (_media.state == PlayerState.BUFFERING ||
_media.state == PlayerState.LOADING)) {
if (_lastBufferStartTime == 0) {
_lastBufferStartTime = now;
}
if (_media.state == PlayerState.LOADING) {
if (_triedReload == 0 && now - _checkNoLoadTime > 15000) {
if (_qos) _qos.sendLoadErr();
onLoadError();
if (_debugWidget) _debugWidget.info("Reload");// 15s没有load完,则重试一次
stop();
setTimeout(play, 1000);
_triedReload++;
_checkNoLoadTime = now; // 注意stop会重置_checkNoLoadTime
}
else if (_triedReload == 1 && now - _checkNoLoadTime > 15000) {
if (_qos) _qos.sendLoadErr();
onLoadError();
if (_debugWidget) _debugWidget.info("Reload");// 15s没有load完,则重试一次
stop();
setTimeout(play, 1000);
_triedReload++;
_checkNoLoadTime = now; // 注意stop会重置_checkNoLoadTime
}
else if (_triedReload == 2 && now - _checkNoLoadTime > 60000) {
// 最后一次60s还没load完,则提示错误
stop();
_triedReload = 0;
msgLabel.text = "无法加载视频,请刷新页面或重新点击播放";
try {
ExternalInterface.call("OnVideoStop", "");
} catch(error:Error) {
trace(error);
}
}
}
if(_media.state == PlayerState.BUFFERING && now - _lastBufferStartTime > 30000)
{
if(currentTime == 0)
{
//if (_qos) _qos.sendLoadErr();
}
if (_debugWidget) _debugWidget.info("buffer too long, Reload");
onLoadError();
stop();
setTimeout(play, 1000);
}
}
此外,videoPlayer定义了UI中点击事件的实现方法和一些Public Interfaces,play, pause,stop, seek, fullScreen,danmu等。
2、RTMPVideoMedia
我们再介绍下播放器中另一个重要的类,建立RTMP连接需要的媒体类。在这之前先介绍下MediaBase这个类,所有的播放器媒体播放类都继承于这个类,在base方法中定义play, pause, seek, stop等基础的播放相关的操作方法,在base中定义的这些方法都只是调用setState()去变更状态,而其他的逻辑在具体的协议链接实现的方法中实现。这里setState(protected function setState(newState:String, forceEvent:Boolean=false):void{})只做两个事情,定时器初始化和状态变更。
此外两个公用的方法也在底层设计实现
(1) tryReconnect第一次重试等待时间为随机0.1~10秒,避免所有人同时请求服务器
(2) calculateScaleDimension 计算视频播放的尺寸
下面来正式介绍RTMPVideoMedia的实现,RTMPVideoMedia构造函数中做了如下几个事情:
(1) initParam(source.videourl);播放视频的入口在视频的那个链接,所以先对url的参数初始化
(2) _netConn = new NetConnection();新建一个链接
(3) 监听链接的各种状态,这里有两个Handler,statusHandler 和errorHandler,在statusHandler中有NetConnection.Connect的各种状态和NetStream的各种状态,NetStream又包括(seek play Buffer等)
(4) 实例音频和视频对象 _sound = new SoundTransform();_video = new Video(0, 0);
在NetConnection.Connect.Success的时候,实例化一个NetStream,在netStream中定义了stream的statusHandler,执行_netStream.play(_file),更新_updateJitterTimer.start();这里引出了JitterBuffer的概念和UpdateJitter的方法,具体的JitterBuffer的一些实现,这里就不细展开了。
此外在RTMPVideoMedia中重写了play stop pause等方法
3、 Qos
Qos主要是根据业务的需求定义了不同的发送到服务端的content,这里主要介绍下send content时,对content的加密方式:
private function encrypt(content:String):String {
var timestamp:int = int(new Date().getTime()/1000);
var dynmickey:String = StringUtil.substitute("{0}{1}{2}", timestamp, RC4KEY, content.length);
var md5content:String = new MD5().calculate(content + dynmickey);
var rc4content:String = new RC4().encrypt(content, dynmickey);
var b64content:String = new Base64().encode(rc4content);
var result:String = StringUtil.substitute("{0},{1},{2}", b64content, timestamp, md5content);
var rc4result:String = new RC4().encrypt(result, RC4KEY);
var b64result:String = new Base64().encode(rc4result);
return b64result;
}
上面算法的设计思路是先生成一个dynmickey,然后再结合MD5和RC4两步加密,生成加密后的content,最后再来一次base64编码,生成加密后的result。
以上三部分主要介绍了在一个播放器的开发中,主流程中的核心类和方法,此外,播放器中根据业务的需求,还有弹幕,截图,全屏等非主流程的实现,具体在单独的文章中详细介绍。