前段时间做了个 Flex 的小项目,项目中需要流媒体播放器,参考了一些资料,自己做了一个,现发布出来与大家分享
直接上代码:
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="710" height="445" backgroundColor="#000000" creationComplete="completeHandler()" horizontalScrollPolicy="off" verticalScrollPolicy="off"> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import com.doc.view.docmanager.player.helper.PlayItem; import com.doc.view.docmanager.player.helper.PlayList; import mx.events.FlexEvent; import mx.events.MetadataEvent; import mx.events.SliderEvent; import mx.core.UIComponent; import mx.controls.Alert; import mx.utils.StringUtil; private var _source:String; private var _autoPlay:Boolean; public var videoWidth:Number = 0; public var videoHeight:Number = 0; private var nc:NetConnection; [Bindable] private var ns:NetStream; private var videoContainer:UIComponent; private var video:Video; private var rtmpUrl:String; private var fileName:String; private var timer:Timer; private var totalTime:Number; //播放列表 private var _playList:PlayList; //当前播放视频的索引 private var _currentIndex:int = 0; //用来控制音量的临时变量 private var volumn:SoundTransform = new SoundTransform(); //NetStream 对象所引用的client的对象 private var nsClient:Object; //是否已经初始化 private var isInit:Boolean = false; //是否在暂停状态 private var isPause:Boolean = false; //是否在播放状态 private var isPlay:Boolean = false; //是否静音 private var isMute:Boolean = false; //临时保存音量 private var tmpVolumn:Number = 5; /*图标定义*/ //播放 [Embed(source="assets/player/play.jpg")] [Bindable] private var playImage:Class; [Embed(source="assets/player/play_over.jpg")] [Bindable] private var playOverImage:Class; //暂停 [Embed(source="assets/player/pause.jpg")] [Bindable] private var pauseImage:Class; [Embed(source="assets/player/pause_over.jpg")] [Bindable] private var pauseOverImage:Class; //停止 [Embed(source="assets/player/stop.jpg")] [Bindable] private var stopImage:Class; [Embed(source="assets/player/stop_over.jpg")] [Bindable] private var stopOverImage:Class; //上一个 [Embed(source="assets/player/previous.jpg")] [Bindable] private var previousImage:Class; [Embed(source="assets/player/previous_over.jpg")] [Bindable] private var previousOverImage:Class; //下一个 [Embed(source="assets/player/next.jpg")] [Bindable] private var nextImage:Class; [Embed(source="assets/player/next_over.jpg")] [Bindable] private var nextOverImage:Class; //音量 [Embed(source="assets/player/sound.jpg")] [Bindable] private var soundImage:Class; [Embed(source="assets/player/sound_over.jpg")] [Bindable] private var soundOverImage:Class; //静音 [Embed(source="assets/player/mute.jpg")] [Bindable] private var muteImage:Class; [Embed(source="assets/player/mute_over.jpg")] [Bindable] private var muteOverImage:Class; // 设置flv地址,rtmp和http都支持,绝对路径 public function set playList(playList:PlayList):void{ this._playList = playList; var data:ArrayCollection = new ArrayCollection(); for(var i:int = 0; i < playList.size(); i++) { data.addItem(this._playList.getItem(i).name); } this.videoList.dataProvider = data; setCurrentIndex(0); } //设置当前播放的索引 private function setCurrentIndex(index:int):void { if(this._playList.validIndex(index)) { this._currentIndex = index; } else { if(index >= _playList.size()) { this._currentIndex = 0; } else { this._currentIndex = _playList.size() - 1; } } this.videoList.selectedIndex = _currentIndex; setSource(this._playList.getItem(this._currentIndex)); } //设置当前的播放文件 private function setSource(playItem:PlayItem):void { var beginIndex:int; var endIndex:int; this._source = playItem.url; if(playItem.url.indexOf("rtmp") == 0){ endIndex = this._source.lastIndexOf("/"); this.rtmpUrl = this._source.substring(0, endIndex); beginIndex = endIndex; endIndex = this._source.lastIndexOf("."); this.fileName = this._source.substring(beginIndex + 1, endIndex); } else{ this.rtmpUrl = null; this.fileName = playItem.url; } if(this._autoPlay){ this.play(); } } public function get source():String{ return this._source; } public function set autoPlay(auto:Boolean):void{ this._autoPlay = auto; } public function get autoPlay():Boolean{ return this._autoPlay; } //初始化完毕后设置video对象的大小,要把video对象加入到Flex的UI控件中,必须先用UIComponent或其子类包装。 private function completeHandler():void{ this.video = new Video(); this.video.width = this.width * 0.7; this.video.height = this.height - this.controlBar.height - this.sliderBar.height; this.videoContainer = new UIComponent(); this.videoContainer.addChild(this.video); this.videoBox.addChild(this.videoContainer); this.videoContainer.addEventListener(MouseEvent.CLICK, videoClickHandler); this.isPause = false; this.addEventListener(FlexEvent.EXIT_STATE, function():void{ close(); }); if(this._autoPlay){ this.isPlay = true; changeImage(1); showTip0("正在播放"); } else{ this.isPlay = false; changeImage(1); showTip0("可以开始播放"); } } private function init():void{ if(StringUtil.trim(this._source) != ""){ this.nc = new NetConnection(); this.nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); this.nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); this.nc.connect(this.rtmpUrl); connectStream(); this.isInit = true; } } private function connectStream():void{ this.nsClient = new Object(); this.nsClient.onMetaData = metaDataHandler; this.nsClient.onCuePoint = cuePointHandler; this.ns = new NetStream(this.nc); this.ns.bufferTime = 15; this.ns.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); this.ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); this.ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); showTip(this.fileName); this.ns.play(this.fileName); this.ns.client = this.nsClient; //设置声音 resetVolumn(); this.video.attachNetStream(this.ns); } private function netStatusHandler(event:NetStatusEvent):void{ switch(event.info.code){ case "NetConnection.Connect.Success": showTip0("连接成功"); break; case "NetConnection.Connect.Rejected": showTip0("没有访问权限"); this.slider.setThumbValueAt(0, 0); this.isPause = false; this.isPlay = false; changeImage(1); break; case "NetStream.Play.StreamNotFound": showTip0("找不到文件"); this.slider.setThumbValueAt(0, 0); this.isPause = false; this.isPlay = false; changeImage(1); break; case "NetStream.Play.Stop": this.isPause = false; this.isPlay = false; changeImage(1); // showTip0("已停止"); next(); break; case "NetStream.Buffer.Empty": showTip0("正在缓冲"); break; case "NetStream.Buffer.Full": if(isPlay) { showTip0("正在播放"); } break; } } //IO 错误 private function ioErrorHandler(event:IOErrorEvent):void{ } //安全错误 private function securityErrorHandler(event:SecurityErrorEvent):void{ } //同步错误 private function asyncErrorHandler(event:AsyncErrorEvent):void{ } //当获得媒体数据时,开始监听enterFrame事件,该事件是不断发生的,在该事件的handler中更新进度条 private function metaDataHandler(info:Object):void{ this.slider.enabled = true; this.totalTime = info.duration; this.slider.maximum = this.totalTime; this.addEventListener(Event.ENTER_FRAME, enterFrameHandler); this.volumnSlider.setThumbValueAt(0, this.ns.soundTransform.volume * 10); } private function enterFrameHandler(event:Event):void{ //在该事件中不断更新进度条的位置 this.slider.setThumbValueAt(0, this.ns.time); showTip(formatTime(this.ns.time) + "/" + formatTime(this.totalTime)); } private function cuePointHandler(info:Object):void{ showTip0("cuepoint: time=" + info.time + " name=" + info.name + " type=" + info.type); } //播放当前文件 public function play():void{ this.init(); try{ this.ns.close(); this.ns.play(this.fileName); } catch(error:Error){ showTip0("发生错误:" + error.message); this.isPause = false; this.isPlay = false; changeImage(1); } this.isPause = false; this.isPlay = true; changeImage(1); showTip0("正在播放"); } //播放上一个 public function previous():void { setCurrentIndex(this._currentIndex - 1); } //播放下一个 public function next():void { setCurrentIndex(this._currentIndex + 1); } //单击视频,实现播放暂停 private function videoClickHandler(event:MouseEvent):void{ this.playPause(); } //播放 or 暂停 private function playPause():void{ if(isPlay) { if(isPause){ this.isPause = false; changeImage(1); this.ns.resume(); showTip0("正在播放"); } else{ this.ns.pause(); this.isPause = true; changeImage(1); showTip0("已暂停"); } } else { this.play(); } } //停止播放 private function stop():void{ this.ns.seek(0); this.ns.pause(); this.slider.setThumbValueAt(0, 0); this.isPause = false; this.isPlay = false; changeImage(1); showTip0("已停止"); } //拖拽进度滑块 private function thumbDragHandler():void{ //拖拽进度条时,删除对enterFrame事件的监听,以便使此时的进度条与播放头脱离 if(!this.ns) return; this.removeEventListener(Event.ENTER_FRAME, enterFrameHandler); } //释放进度滑块 private function thumbReleaseHandler():void{ //拖拽释放后,根据进度条该点的值让视频的播放头跳到该时间点 if(!this.ns) return; this.ns.seek(slider.value); this.addEventListener(Event.ENTER_FRAME, enterFrameHandler); } //调整音量事件 private function volumnChangeHandler():void{ //视频音量控制 resetVolumn(); this.isMute = false; this.mute.source = soundImage; } //重置NetStream的音量 private function resetVolumn():void { if(!this.ns) return; volumn.volume = this.volumnSlider.value / 10; this.ns.soundTransform = volumn; } //格式化音量进度条的提示 private function formatVolumnSliderTooltip(value:Number):String { return (int(value * 10)) + "%"; } //格式化播放进度条的提示 private function formatTime(value:Number):String { var hour:int; var minute:int; var second:int; hour = int(value / 3600); minute = int(value / 60) - hour * 60; second = int(value % 60); if(hour > 0) { return (hour < 10 ? ("0" + hour) : hour) + ":" + (minute < 10 ? ("0" + minute) : minute) + ":" + (second < 10 ? ("0" + second) : second); } else { return (minute < 10 ? ("0" + minute) : minute) + ":" + (second < 10 ? ("0" + second) : second); } } //关闭所有连接 public function close():void { if(this.ns) ns.close(); if(this.nc) nc.close(); } //静音 private function resetMute():void { if(isMute) { this.volumnSlider.value = tmpVolumn; mute.source = soundImage; isMute = false; } else { this.tmpVolumn = this.volumnSlider.value; this.volumnSlider.value = 0; mute.source = muteImage; isMute = true; } resetVolumn(); } //全屏, 暂时不用 private function fullScreen():void { stage.displayState = (stage.displayState == StageDisplayState.NORMAL?StageDisplayState.FULL_SCREEN:StageDisplayState.NORMAL); } /*鼠标进入图标时,改变图标*/ private function changeOverImage(type:int):void { // 停止 if(type == 0) { this.stopBtn.source = stopOverImage; } //播放 else if(type == 1) { if(isPlay) { if(isPause) { this.playPauseBtn.source = playOverImage; } else { this.playPauseBtn.source = pauseOverImage; } } else { this.playPauseBtn.source = playOverImage; } } //音量 else if(type == 2) { if(isMute) { this.mute.source = muteOverImage; } else { this.mute.source = soundOverImage; } } //上一个 else if(type == 3) { this.previousBtn.source = previousOverImage; } //下一个 else if(type == 4) { this.nextBtn.source = nextOverImage; } } /*鼠标移出图标时,改变图标*/ private function changeImage(type:int):void { //停止 if(type == 0) { this.stopBtn.source = stopImage; } //播放 else if(type == 1) { if(isPlay) { if(isPause) { this.playPauseBtn.source = playImage; } else { this.playPauseBtn.source = pauseImage; } } else { this.playPauseBtn.source = playImage; } } //音量 else if(type == 2) { if(isMute) { this.mute.source = muteImage; } else { this.mute.source = soundImage; } } //上一个 else if(type == 3) { this.previousBtn.source = previousImage; } //下一个 else if(type == 4) { this.nextBtn.source = nextImage; } } //显示提示信息 private function showTip(msg:String):void { this.timpTip.text = msg; } //另一处提示信息 private function showTip0(msg:String):void { this.timpTip0.text = msg; } ]]> </mx:Script> <mx:Canvas width="100%" height="100%" x="0" y="0"> <mx:Canvas id="playArea" width="500" height="100%" x="-1" y="-1" horizontalScrollPolicy="off" verticalScrollPolicy="off" borderColor="#FFFFFF" borderStyle="solid"> <mx:Canvas id="videoBox" width="100%" x="0" y="0" height="360" horizontalScrollPolicy="off" verticalScrollPolicy="off"> </mx:Canvas> <mx:VBox id="sliderBar" y="360" height="18" width="100%" backgroundColor="#000000" horizontalScrollPolicy="off" verticalScrollPolicy="off"> <mx:HSlider id="slider" width="498" minimum="0" snapInterval="1" showTrackHighlight="true" liveDragging="true" thumbDrag="thumbDragHandler()" thumbRelease="thumbReleaseHandler()" dataTipFormatFunction="formatTime" allowTrackClick="false" y="420" trackColors="[0xEEEEEE,0x0000FF]" enabled="false"> </mx:HSlider> </mx:VBox> <mx:Canvas id="controlBar" width="100%" height="60" y="375" horizontalScrollPolicy="off" verticalScrollPolicy="off"> <mx:Image source="{stopImage}" id="stopBtn" click="stop()" mouseOver="changeOverImage(0)" mouseOut="changeImage(0)" width="32" height="32" x="7" y="14"/> <mx:Image source="{previousImage}" id="previousBtn" click="previous()" mouseOver="changeOverImage(3)" mouseOut="changeImage(3)" width="32" height="32" x="51" y="14"/> <mx:Image source="{playImage}" id="playPauseBtn" click="playPause()" mouseOver="changeOverImage(1)" mouseOut="changeImage(1)" width="64" height="64" x="85" y="-3"/> <mx:Image source="{nextImage}" id="nextBtn" click="next()" mouseOver="changeOverImage(4)" mouseOut="changeImage(4)" width="32" height="32" x="152" y="14"/> <mx:Label id="timpTip0" width="87" textAlign="center" x="188" y="22" color="#FFFFFF"/> <mx:Label id="timpTip" width="115" textAlign="center" x="273" y="22" color="#FFFFFF"/> <mx:Image id="mute" click="resetMute()" source="{soundImage}" mouseOver="changeOverImage(2)" mouseOut="changeImage(2)" width="32" height="32" x="392" y="14"/> <mx:HSlider id="volumnSlider" width="75" value="2" minimum="0" maximum="10" snapInterval="0.1" change="volumnChangeHandler()" liveDragging="true" x="421" y="18" dataTipFormatFunction="formatVolumnSliderTooltip"/> </mx:Canvas> </mx:Canvas> <mx:Canvas width="210" height="100%" x="500" y="0"> <mx:VBox width="100%" height="20" y="5" horizontalAlign="center"> <mx:Label textAlign="center" text="播放列表" color="#FFFFFF" width="79" fontFamily="宋体" fontSize="15" fontWeight="bold"/> </mx:VBox> <mx:List x="-1" y="25" width="209" height="420" backgroundColor="#000000" color="#FFFFFF" id="videoList" itemClick="{setCurrentIndex(this.videoList.selectedIndex)}"></mx:List> </mx:Canvas> </mx:Canvas> </mx:Canvas>
由于时间仓促,所以代码中还有很多可以优化的地方,现提供出来,仅供大家参考。
脚步无法到达的地方,目光可以到达;目光无法到达的地方,梦想可以到达。