├── .gitignore ├── LICENSE ├── README.md ├── project └── PinkPlayer │ ├── .actionScriptProperties │ ├── .project │ ├── html-template │ ├── history │ │ ├── history.css │ │ ├── history.js │ │ └── historyFrame.html │ ├── index.template.html │ ├── playerProductInstall.swf │ └── swfobject.js │ ├── lib │ └── PlayerSkinBlue.fla │ └── src │ ├── FPlayer.as │ └── com │ └── fenhongxiang │ ├── ADPlayer.as │ ├── HLSPlayer.as │ ├── cue │ ├── CueContainer.as │ ├── CueData.as │ ├── CuePoint.as │ └── CueUtil.as │ ├── event │ └── FResizeEvent.as │ ├── hls │ ├── HLS.as │ ├── HLSSettings.as │ ├── constant │ │ ├── HLSLoaderTypes.as │ │ ├── HLSMaxLevelCappingMode.as │ │ ├── HLSPlayStates.as │ │ ├── HLSSeekMode.as │ │ ├── HLSSeekStates.as │ │ └── HLSTypes.as │ ├── controller │ │ ├── AudioTrackController.as │ │ ├── BufferThresholdController.as │ │ ├── FPSController.as │ │ └── LevelController.as │ ├── demux │ │ ├── AACDemuxer.as │ │ ├── AVCC.as │ │ ├── AudioFrame.as │ │ ├── DemuxHelper.as │ │ ├── Demuxer.as │ │ ├── ExpGolomb.as │ │ ├── ID3.as │ │ ├── ID3Tag.as │ │ ├── MP3Demuxer.as │ │ ├── Nalu.as │ │ ├── PES.as │ │ ├── SPSInfo.as │ │ ├── TSDemuxer.as │ │ └── VideoFrame.as │ ├── event │ │ ├── HLSError.as │ │ ├── HLSEvent.as │ │ ├── HLSLoadMetrics.as │ │ ├── HLSMediatime.as │ │ └── HLSPlayMetrics.as │ ├── flv │ │ └── FLVTag.as │ ├── handler │ │ └── StatsHandler.as │ ├── loader │ │ ├── AltAudioFragmentLoader.as │ │ ├── AltAudioLevelLoader.as │ │ ├── FragmentLoader.as │ │ └── LevelLoader.as │ ├── model │ │ ├── AudioTrack.as │ │ ├── Fragment.as │ │ ├── FragmentData.as │ │ ├── Level.as │ │ └── Stats.as │ ├── playlist │ │ ├── AltAudioTrack.as │ │ ├── DataUri.as │ │ └── Manifest.as │ ├── stream │ │ ├── HLSNetStream.as │ │ ├── HLSNetStreamClient.as │ │ └── StreamBuffer.as │ └── utils │ │ ├── AES.as │ │ ├── DateUtil.as │ │ ├── FastAESKey.as │ │ ├── Hex.as │ │ ├── JSURLLoader.as │ │ ├── JSURLStream.as │ │ ├── Log.as │ │ ├── PTS.as │ │ ├── Params2Settings.as │ │ └── ScaleVideo.as │ ├── srt │ ├── SRTController.as │ ├── SRTLoader.as │ ├── SRTLoaderEvent.as │ ├── SRTModel.as │ └── SRTUtil.as │ ├── util │ ├── HtmlUtil.as │ ├── LogUtil.as │ ├── ObjectUtil.as │ ├── SkinLoader.as │ ├── TimeUtil.as │ └── TweenNano.as │ ├── view │ ├── CoverLoader.as │ ├── FProgressBar.as │ ├── FSprite.as │ └── ViewController.as │ └── vtt │ ├── ImageLoader.as │ ├── ImageLoaderEvent.as │ ├── VTTControler.as │ ├── VTTLoader.as │ ├── VTTLoaderEvent.as │ └── VTTModel.as └── shots ├── shot01.png ├── shot02.png ├── shot03.png ├── shot04.png ├── shot05.png ├── shot06.png ├── shot07.png └── shot08.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin/ 3 | bin-debug/ 4 | bin-release/ 5 | 6 | # Other files and folders 7 | .settings/ 8 | 9 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 10 | # should NOT be excluded as they contain compiler settings and other important 11 | # information for Eclipse / Flash Builder. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 LuoJiangHong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HLSPlayer 2 | 基于Flash插件的HLS播放器,支持VTT缩略图,SRT字幕,片头广告,暂停广告等 3 | 4 | 5 | 注意:播放器皮肤源文件位于工程的lib目录中 6 | 7 | 2016/10/15 :很抱歉。最近工作太忙,一直没有更新项目。现在HTML5对视频的支持已经越来越好,所以后续不准备继续维护Flash插件的播放器,会开源一个基于HTML5的HLS/DASH播放器。到时候会把链接更新到这里,谢谢关注。 8 | 9 | 10 | 11 | 在线预览地址:已经失效。 12 | 13 | 14 | 2016-04-29 更新字幕,皮肤。 15 | 16 | 17 | 1.播放器支持COVER 18 | 19 | 20 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot05.png?raw=true) 21 | 22 | 23 | 2.VTT缩略图 24 | 25 | 26 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot06.png?raw=true) 27 | 28 | 29 | 3.SRT字幕显示(UTF-8格式) 30 | 31 | 32 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot07.png?raw=true) 33 | 34 | 35 | 4.播放器缩放时,UI做了自适应 36 | 37 | 38 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot01.png?raw=true) 39 | 40 | 41 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot02.png?raw=true) 42 | 43 | 44 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot03.png?raw=true) 45 | 46 | 47 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot04.png?raw=true) 48 | 49 | 50 | 51 | 5.视频打点显示。目前是解析传过来的JSON打点信息,然后在进度条上生成对应显示点。 52 | 53 | 54 | ![image](https://github.com/luojianghong/HLSPlayer/blob/master/shots/shot08.png?raw=true) 55 | 56 | 57 | 58 | 待完成功能: 59 | 60 | 61 | 字幕切换,清晰度切换,字幕显示细节优化 62 | 63 | 64 | 感谢: 65 | 66 | 67 | 1.HLS插件基于:https://github.com/mangui/flashls 68 | 69 | 70 | 2.VTT缩略图制作工具:http://www.suu-design.com/projects.html 71 | -------------------------------------------------------------------------------- /project/PinkPlayer/.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /project/PinkPlayer/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | FPlayer 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.mylyn.wikitext.ui.wikiTextValidationBuilder 10 | 11 | 12 | 13 | 14 | com.adobe.flexbuilder.project.flexbuilder 15 | 16 | 17 | 18 | 19 | 20 | com.adobe.flexbuilder.project.actionscriptnature 21 | org.eclipse.mylyn.wikitext.ui.wikiTextNature 22 | 23 | 24 | -------------------------------------------------------------------------------- /project/PinkPlayer/html-template/history/history.css: -------------------------------------------------------------------------------- 1 | /* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */ 2 | 3 | #ie_historyFrame { width: 0px; height: 0px; display:none } 4 | #firefox_anchorDiv { width: 0px; height: 0px; display:none } 5 | #safari_formDiv { width: 0px; height: 0px; display:none } 6 | #safari_rememberDiv { width: 0px; height: 0px; display:none } 7 | -------------------------------------------------------------------------------- /project/PinkPlayer/html-template/history/historyFrame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /project/PinkPlayer/html-template/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${title} 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 55 | 56 | 57 |
58 |
59 |

60 | To view this page ensure that Adobe Flash Player version 61 | ${version_major}.${version_minor}.${version_revision} or greater is installed. 62 |

63 | 68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /project/PinkPlayer/html-template/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/project/PinkPlayer/html-template/playerProductInstall.swf -------------------------------------------------------------------------------- /project/PinkPlayer/lib/PlayerSkinBlue.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/project/PinkPlayer/lib/PlayerSkinBlue.fla -------------------------------------------------------------------------------- /project/PinkPlayer/src/FPlayer.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package 10 | { 11 | import com.fenhongxiang.ADPlayer; 12 | import com.fenhongxiang.HLSPlayer; 13 | import com.fenhongxiang.hls.HLSSettings; 14 | import com.fenhongxiang.hls.constant.HLSSeekMode; 15 | import com.fenhongxiang.srt.SRTController; 16 | import com.fenhongxiang.util.ObjectUtil; 17 | import com.fenhongxiang.util.SkinLoader; 18 | import com.fenhongxiang.view.ViewController; 19 | import com.fenhongxiang.vtt.VTTControler; 20 | 21 | import flash.display.InteractiveObject; 22 | import flash.display.MovieClip; 23 | import flash.display.Sprite; 24 | import flash.display.Stage; 25 | import flash.display.StageAlign; 26 | import flash.display.StageScaleMode; 27 | import flash.events.Event; 28 | import flash.text.TextField; 29 | import flash.ui.ContextMenu; 30 | import flash.ui.ContextMenuItem; 31 | 32 | [SWF(width="720", height="408", frameRate="24")] 33 | public class FPlayer extends Sprite 34 | { 35 | private var adPlayer:ADPlayer; 36 | private var autoPlay:Boolean=false; 37 | private var coverURL:String; 38 | private var hlsPlayer:HLSPlayer; 39 | private var hlsURL:String; 40 | private var pauseADClickURL:String; 41 | private var pauseADImageURL:String; 42 | private var prerollClickURL:String; 43 | private var prerollURL:String; 44 | private var skinURL:String; 45 | private var srtURL:String; 46 | private var thumbURL:String; 47 | 48 | public function FPlayer() 49 | { 50 | this.addEventListener(Event.ADDED_TO_STAGE, onPlayerdAddedToStageHandler, false, 0, true); 51 | } 52 | 53 | private function onPlayerdAddedToStageHandler(e:Event):void 54 | { 55 | this.removeEventListener(Event.ADDED_TO_STAGE, onPlayerdAddedToStageHandler); 56 | 57 | //设置舞台缩放模式 58 | stage.align = StageAlign.TOP_LEFT; 59 | stage.scaleMode = StageScaleMode.NO_SCALE; 60 | 61 | //初始化右键菜单 62 | createMenu(this); 63 | 64 | //获取相关参数 65 | getParameters(this.stage); 66 | 67 | //加载皮肤 68 | SkinLoader.getInstance().load(skinURL, skinLoadedHandler); 69 | } 70 | 71 | private function skinLoadedHandler(skinClip:*):void 72 | { 73 | if (skinClip != null) 74 | { 75 | initPlayer(skinClip); 76 | } 77 | else 78 | { 79 | showErrorMessage("播放器皮肤加载失败."); 80 | } 81 | } 82 | 83 | private function initPlayer(skin:MovieClip):void 84 | { 85 | hlsPlayer = new HLSPlayer(); 86 | 87 | var viewController:ViewController = new ViewController(hlsPlayer, skin); 88 | 89 | viewController.srtController = new SRTController(srtURL); 90 | viewController.vttController = new VTTControler(thumbURL); 91 | viewController.coverPath = coverURL; 92 | viewController.pauseADImagePath = pauseADImageURL; 93 | viewController.pauseADClickURL = pauseADClickURL 94 | viewController.stage = this.stage; 95 | viewController.onCoverButtonCallback = initADPlayer; 96 | 97 | HLSSettings.logDebug = false; 98 | HLSSettings.logInfo = false; 99 | HLSSettings.seekMode = HLSSeekMode.KEYFRAME_SEEK; 100 | 101 | this.addChild(skin); 102 | } 103 | 104 | private function initADPlayer():void 105 | { 106 | adPlayer = new ADPlayer(); 107 | adPlayer.volume = 0.3; 108 | adPlayer.onEnd = onADEnd; 109 | adPlayer.onError = onADEnd; 110 | adPlayer.jumpURL = prerollClickURL; 111 | adPlayer.play(prerollURL, 0); 112 | 113 | this.addChild(adPlayer); 114 | } 115 | 116 | private function onADEnd():void 117 | { 118 | adPlayer.onEnd = null; 119 | adPlayer.onError = null 120 | 121 | if (adPlayer.parent != null) 122 | { 123 | adPlayer.parent.removeChild(adPlayer); 124 | } 125 | 126 | adPlayer = null; 127 | 128 | hlsPlayer.autoPlay = autoPlay; 129 | hlsPlayer.preload = true; 130 | hlsPlayer.url = hlsURL; 131 | } 132 | 133 | //-------------------------------------------------------工具方法------------------------------------------------// 134 | private function createMenu(target:InteractiveObject):void 135 | { 136 | var verItem:ContextMenuItem = new ContextMenuItem("www.fenhongxiang.com", false, false); 137 | 138 | var customeMenu:ContextMenu = new ContextMenu(); 139 | customeMenu.hideBuiltInItems(); 140 | customeMenu.customItems.push(verItem); 141 | 142 | target.contextMenu = customeMenu; 143 | } 144 | 145 | private function getParameters(target:Stage):void 146 | { 147 | //获取参数 148 | coverURL = ObjectUtil.getSWFParameter("coverURL", target); 149 | hlsURL = ObjectUtil.getSWFParameter("hlsURL", target); 150 | prerollURL = ObjectUtil.getSWFParameter('prerollURL', target); 151 | prerollClickURL = ObjectUtil.getSWFParameter('prerollClickURL', target);//跳转地址需要对地址合法性进行验证,防止跨域攻击 152 | srtURL = ObjectUtil.getSWFParameter('srtURL', target); 153 | thumbURL = ObjectUtil.getSWFParameter('thumbURL', target); 154 | skinURL = ObjectUtil.getSWFParameter('skinURL', target); 155 | pauseADImageURL = ObjectUtil.getSWFParameter('pauseADImageURL', target); 156 | pauseADClickURL = ObjectUtil.getSWFParameter('pauseADClickURL', target);//跳转地址需要对地址合法性进行验证,防止跨域攻击 157 | autoPlay = ObjectUtil.parseBoolean(ObjectUtil.getSWFParameter('autoPlay', target)); 158 | } 159 | 160 | private function showErrorMessage(msg:String):void 161 | { 162 | this.graphics.clear(); 163 | this.graphics.beginFill(0x1F272A, 1.0); 164 | this.graphics.drawRect(0, 0, this.stage.stageWidth, this.stage.stageHeight); 165 | this.graphics.endFill(); 166 | 167 | var txt:TextField = new TextField(); 168 | txt.width = 300; 169 | txt.height = 30; 170 | txt.mouseEnabled = false; 171 | txt.selectable = false; 172 | txt.x = (this.stage.stageWidth - 300) / 2; 173 | txt.y = (this.stage.stageHeight - 30) / 2; 174 | txt.textColor = 0xFFFFFF; 175 | txt.htmlText = "

"+msg+"

"; 176 | 177 | this.addChild(txt); 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/cue/CueContainer.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.cue 10 | { 11 | import com.fenhongxiang.view.FSprite; 12 | import flash.text.TextField; 13 | public class CueContainer extends FSprite 14 | { 15 | public function CueContainer() 16 | { 17 | super(); 18 | 19 | init(); 20 | } 21 | 22 | private var _cueData:Vector.; 23 | 24 | private var _infoText:TextField; 25 | 26 | public function set data(value:Vector.):void 27 | { 28 | _cueData = sortPointsByTime(value); 29 | 30 | arrangePoints(); 31 | } 32 | 33 | public function get tipVisible():Boolean 34 | { 35 | return _infoText != null && _infoText.visible; 36 | } 37 | 38 | private function arrangePoints():void 39 | { 40 | removeAllCuePoints(); 41 | 42 | if (_cueData != null) 43 | { 44 | for (var i:int = 0; i < _cueData.length; i++) 45 | { 46 | var cuePoint:CuePoint = new CuePoint(); 47 | 48 | cuePoint.data = _cueData[i]; 49 | cuePoint.container = this; 50 | cuePoint.toogleTip = showCuePointTips; 51 | } 52 | } 53 | } 54 | 55 | private function init():void 56 | { 57 | border = false; 58 | backgroundAlpha = 0; 59 | mouseEnabled = false; 60 | 61 | _infoText = new TextField(); 62 | _infoText.background = true; 63 | _infoText.backgroundColor = 0; 64 | _infoText.y = -30; 65 | _infoText.wordWrap = true; 66 | _infoText.textColor = 0xFFFFFF; 67 | _infoText.visible = false; 68 | 69 | this.addChild(_infoText); 70 | } 71 | 72 | private function removeAllCuePoints():void 73 | { 74 | for (var i:int = this.numChildren - 1; i >= 0; i--) 75 | { 76 | if (this.getChildAt(i) is CuePoint) 77 | { 78 | this.removeChildAt(i); 79 | } 80 | } 81 | } 82 | 83 | private function showCuePointTips(cue:CuePoint):void 84 | { 85 | if (cue != null && cue.data != null) 86 | { 87 | var text:String = cue.data.text; 88 | 89 | if (text.length > 130) 90 | { 91 | _infoText.htmlText = "

" + text.substr(0, 27) + "...

"; 92 | } 93 | else 94 | { 95 | _infoText.htmlText = "

" + text + "

"; 96 | } 97 | 98 | _infoText.x = cue.x - _infoText.width / 2; 99 | _infoText.height = _infoText.numLines * 20; 100 | 101 | _infoText.y = -_infoText.height; 102 | _infoText.visible = true; 103 | } 104 | else 105 | { 106 | _infoText.text = ''; 107 | _infoText.visible = false; 108 | } 109 | } 110 | 111 | private function sortPointsByTime(points:Vector.):Vector. 112 | { 113 | if (points != null) 114 | { 115 | var result:Vector. = points.sort(function compare(x:CueData, y:CueData):Number 116 | { 117 | return x.pos - y.pos; 118 | }); 119 | 120 | return result; 121 | } 122 | else 123 | { 124 | return null; 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/cue/CueData.as: -------------------------------------------------------------------------------- 1 | package com.fenhongxiang.cue 2 | { 3 | public class CueData 4 | { 5 | public function CueData() 6 | { 7 | } 8 | 9 | public var pos:Number = 0.0; 10 | public var text:String = ""; 11 | } 12 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/cue/CuePoint.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.cue 10 | { 11 | import com.fenhongxiang.event.FResizeEvent; 12 | import com.fenhongxiang.view.FSprite; 13 | 14 | import flash.events.MouseEvent; 15 | 16 | public class CuePoint extends FSprite 17 | { 18 | public function CuePoint() 19 | { 20 | this.buttonMode = true; 21 | this.useHandCursor = true; 22 | 23 | border = false; 24 | backgroudColor = 0xffb81d; 25 | resize(3, 10); 26 | addEventListeners(); 27 | } 28 | 29 | private var _container:FSprite; 30 | private var _data:CueData; 31 | 32 | public function get data():CueData 33 | { 34 | return _data; 35 | } 36 | 37 | public function set toogleTip(value:Function):void 38 | { 39 | _toogleTip = value; 40 | } 41 | 42 | private var _toogleTip:Function; 43 | 44 | public function set container(value:FSprite):void 45 | { 46 | _container = value; 47 | 48 | if (_container != null) 49 | { 50 | _container.addEventListener(FResizeEvent.SIZE_CHANGE, onContainerResize, false, 0, true); 51 | 52 | this.x = (_data == null ? 0 : _data.pos * _container.width) + _container.x; 53 | this.y = 0; 54 | this.height = _container.height; 55 | 56 | _container.addChild(this); 57 | } 58 | } 59 | 60 | private function onContainerResize(e:FResizeEvent):void 61 | { 62 | this.x = (_data == null ? 0 : _data.pos * _container.width) + _container.x; 63 | this.y = 0; 64 | this.height = _container.height; 65 | } 66 | 67 | public function set data(value:CueData):void 68 | { 69 | _data = value; 70 | } 71 | 72 | private function addEventListeners():void 73 | { 74 | this.addEventListener(MouseEvent.MOUSE_OUT, hideTip, false, 0, true); 75 | this.addEventListener(MouseEvent.MOUSE_OVER, showTip, false, 0, true); 76 | } 77 | 78 | private function hideTip(e:MouseEvent):void 79 | { 80 | backgroudColor = 0xffb81d; 81 | 82 | if(_toogleTip != null) 83 | { 84 | _toogleTip(null); 85 | } 86 | } 87 | 88 | private function showTip(e:MouseEvent):void 89 | { 90 | backgroudColor = 0xffaf00; 91 | 92 | if(_toogleTip != null) 93 | { 94 | _toogleTip(this); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/cue/CueUtil.as: -------------------------------------------------------------------------------- 1 | package com.fenhongxiang.cue 2 | { 3 | import com.fenhongxiang.util.LogUtil; 4 | 5 | public class CueUtil 6 | { 7 | public function CueUtil() 8 | { 9 | // String -> Json objet 10 | // Json根据时间排序 11 | // 绘制时间点 12 | // 鼠标移到时间点上 会显示内容 13 | } 14 | 15 | public static const testJson:String = "["+ 16 | 17 | "{"+ 18 | "\"pos\": 0.03,"+ 19 | "\"text\": \"即将遭遇猎杀\""+ 20 | "},"+ 21 | "{"+ 22 | "\"pos\": 0.2,"+ 23 | "\"text\": \"遇见受伤的小龙\""+ 24 | "},"+ 25 | "{"+ 26 | "\"pos\": 0.56,"+ 27 | "\"text\": \"找到洞穴,准备进去一探究竟\""+ 28 | "}"+ 29 | "]"; 30 | 31 | public static function jsonToVector(json:String):Vector. 32 | { 33 | try 34 | { 35 | var cueArr:* = JSON.parse(json); 36 | } 37 | catch(e:Error) 38 | { 39 | 40 | } 41 | 42 | if (cueArr != null && cueArr is Array) 43 | { 44 | var result:Vector. = new Vector.(); 45 | 46 | for each(var obj:Object in cueArr) 47 | { 48 | var cueData:CueData = new CueData(); 49 | cueData.pos = obj['pos']; 50 | cueData.text = obj['text']; 51 | 52 | LogUtil.debug(cueData.pos + ":" + cueData.text); 53 | result.push(cueData); 54 | } 55 | 56 | return result; 57 | } 58 | else 59 | { 60 | return null; 61 | } 62 | } 63 | 64 | } 65 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/event/FResizeEvent.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.event 10 | { 11 | import flash.events.Event; 12 | public class FResizeEvent extends Event 13 | { 14 | public static const SIZE_CHANGE:String = "size change"; 15 | 16 | public function FResizeEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) 17 | { 18 | super(type, bubbles, cancelable); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/constant/HLSLoaderTypes.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.constant { 5 | /** Identifiers for the different stream types. **/ 6 | public class HLSLoaderTypes { 7 | // manifest loader 8 | public static const MANIFEST : int = 0; 9 | // playlist / level loader 10 | public static const LEVEL_MAIN : int = 1; 11 | // playlist / level loader 12 | public static const LEVEL_ALTAUDIO : int = 2; 13 | // main fragment loader 14 | public static const FRAGMENT_MAIN : int = 3; 15 | // alt audio fragment loader 16 | public static const FRAGMENT_ALTAUDIO : int = 4; 17 | } 18 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/constant/HLSMaxLevelCappingMode.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.constant { 5 | /** Max Level Capping Modes **/ 6 | public class HLSMaxLevelCappingMode { 7 | /** 8 | * max capped level should be the one with the dimensions equal or greater than the stage dimensions (so the video will be downscaled) 9 | */ 10 | public static const DOWNSCALE : String = "downscale"; 11 | 12 | /** 13 | * max capped level should be the one with the dimensions equal or lower than the stage dimensions (so the video will be upscaled) 14 | */ 15 | public static const UPSCALE : String = "upscale"; 16 | } 17 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/constant/HLSPlayStates.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.constant { 5 | /** Identifiers for the different playback states. **/ 6 | public class HLSPlayStates { 7 | /** idle state. **/ 8 | public static const IDLE : String = "IDLE"; 9 | /** playing state. **/ 10 | public static const PLAYING : String = "PLAYING"; 11 | /** paused state. **/ 12 | public static const PAUSED : String = "PAUSED"; 13 | /** playing/buffering state (playback is paused and will restart automatically as soon as buffer will contain enough data) **/ 14 | public static const PLAYING_BUFFERING : String = "PLAYING_BUFFERING"; 15 | /** paused/buffering state (playback is paused, and buffer is in low condition) **/ 16 | public static const PAUSED_BUFFERING : String = "PAUSED_BUFFERING"; 17 | } 18 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/constant/HLSSeekMode.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.constant { 5 | /** HLS Seek mode configuration **/ 6 | public class HLSSeekMode { 7 | /** seek on keyframe boundary **/ 8 | public static const KEYFRAME_SEEK : String = "KEYFRAME"; 9 | /** accurate seeking **/ 10 | public static const ACCURATE_SEEK : String = "ACCURATE"; 11 | } 12 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/constant/HLSSeekStates.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.constant { 5 | /** Identifiers for the different seeking states. **/ 6 | public class HLSSeekStates { 7 | /** idle state. **/ 8 | public static const IDLE : String = "IDLE"; 9 | /** seeking in progress state. **/ 10 | public static const SEEKING : String = "SEEKING"; 11 | /** seeked state. **/ 12 | public static const SEEKED : String = "SEEKED"; 13 | } 14 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/constant/HLSTypes.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.constant 5 | { 6 | 7 | /** Identifiers for the different stream types. **/ 8 | public class HLSTypes 9 | { 10 | /** Identifier for live events. **/ 11 | public static const LIVE:String="LIVE"; 12 | /** Identifier for on demand clips. **/ 13 | public static const VOD:String="VOD"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/controller/BufferThresholdController.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.controller { 5 | import com.fenhongxiang.hls.constant.HLSLoaderTypes; 6 | import com.fenhongxiang.hls.event.HLSEvent; 7 | import com.fenhongxiang.hls.event.HLSLoadMetrics; 8 | import com.fenhongxiang.hls.HLS; 9 | import com.fenhongxiang.hls.HLSSettings; 10 | 11 | CONFIG::LOGGING { 12 | import com.luojianghong.hls.utils.Log; 13 | } 14 | /** Class that manages buffer threshold values (minBufferLength/lowBufferLength) 15 | */ 16 | public class BufferThresholdController { 17 | /** Reference to the HLS controller. **/ 18 | private var _hls : HLS; 19 | // max nb of samples used for bw checking. the bigger it is, the more conservative it is. 20 | private static const MAX_SAMPLES : int = 30; 21 | private var _bw : Vector.; 22 | private var _nbSamples : uint; 23 | private var _targetduration : Number; 24 | private var _minBufferLength : Number; 25 | 26 | /** Create the loader. **/ 27 | public function BufferThresholdController(hls : HLS) : void { 28 | _hls = hls; 29 | _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler); 30 | _hls.addEventListener(HLSEvent.TAGS_LOADED, _fragmentLoadedHandler); 31 | _hls.addEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler); 32 | }; 33 | 34 | public function dispose() : void { 35 | _hls.removeEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler); 36 | _hls.removeEventListener(HLSEvent.TAGS_LOADED, _fragmentLoadedHandler); 37 | _hls.removeEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler); 38 | } 39 | 40 | public function get minBufferLength() : Number { 41 | if (HLSSettings.minBufferLength == -1) { 42 | return _minBufferLength; 43 | } else { 44 | return HLSSettings.minBufferLength; 45 | } 46 | } 47 | 48 | public function get lowBufferLength() : Number { 49 | if (HLSSettings.minBufferLength == -1) { 50 | // in automode, low buffer threshold should be less than min auto buffer 51 | return Math.min(minBufferLength / 2, HLSSettings.lowBufferLength); 52 | } else { 53 | return HLSSettings.lowBufferLength; 54 | } 55 | } 56 | 57 | private function _manifestLoadedHandler(event : HLSEvent) : void { 58 | _nbSamples = 0; 59 | _targetduration = event.levels[_hls.startLevel].targetduration; 60 | _bw = new Vector.(MAX_SAMPLES,true); 61 | _minBufferLength = _targetduration; 62 | }; 63 | 64 | private function _fragmentLoadedHandler(event : HLSEvent) : void { 65 | var metrics : HLSLoadMetrics = event.loadMetrics; 66 | // only monitor main fragment metrics for buffer threshold computing 67 | if(metrics.type == HLSLoaderTypes.FRAGMENT_MAIN) { 68 | var cur_bw : Number = metrics.bandwidth; 69 | _bw[_nbSamples % MAX_SAMPLES] = cur_bw; 70 | _nbSamples++; 71 | 72 | // compute min bw on MAX_SAMPLES 73 | var minBw : Number = Number.POSITIVE_INFINITY; 74 | var samples_max : int = Math.min(_nbSamples, MAX_SAMPLES); 75 | for (var i : int = 0; i < samples_max; i++) { 76 | minBw = Math.min(minBw, _bw[i]); 77 | } 78 | 79 | // give more weight to current bandwidth 80 | var bw_ratio : Number = 2 * cur_bw / (minBw + cur_bw); 81 | 82 | /* predict time to dl next segment using a conservative approach. 83 | * 84 | * heuristic is as follow : 85 | * 86 | * time to dl next segment = time to dl current segment * (playlist target duration / current segment duration) * bw_ratio 87 | * \---------------------------------------------------------------------------------/ 88 | * this part is a simple rule by 3, assuming we keep same dl bandwidth 89 | * bw ratio is the conservative factor, assuming that next segment will be downloaded with min bandwidth 90 | */ 91 | _minBufferLength = metrics.processing_duration * (_targetduration / metrics.duration) * bw_ratio; 92 | // avoid min > max 93 | if (HLSSettings.maxBufferLength) { 94 | _minBufferLength = Math.min(HLSSettings.maxBufferLength, _minBufferLength); 95 | } 96 | CONFIG::LOGGING { 97 | Log.debug2("AutoBufferController:minBufferLength:" + _minBufferLength); 98 | } 99 | }; 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/controller/FPSController.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.controller { 5 | // import flash.events.ThrottleEvent; 6 | // import flash.events.ThrottleType; 7 | import flash.events.Event; 8 | import flash.events.TimerEvent; 9 | import flash.system.Capabilities; 10 | import flash.utils.getTimer; 11 | import flash.utils.Timer; 12 | import com.fenhongxiang.hls.constant.HLSPlayStates; 13 | import com.fenhongxiang.hls.event.HLSEvent; 14 | import com.fenhongxiang.hls.HLS; 15 | import com.fenhongxiang.hls.HLSSettings; 16 | CONFIG::LOGGING { 17 | import com.luojianghong.hls.utils.Log; 18 | } 19 | /* 20 | * class that control/monitor FPS 21 | */ 22 | public class FPSController { 23 | /** Reference to the HLS controller. **/ 24 | private var _hls : HLS; 25 | private var _timer : Timer; 26 | private var _throttling : Boolean; 27 | private var _playing : Boolean; 28 | private var _lastTime : int; 29 | private var _lastDroppedFrames : int; 30 | // hardcode event name and state to avoid compilation issue with target player < 11.2 31 | private static const THROTTLE : String = "throttle"; 32 | private static const RESUME : String = "resume"; 33 | 34 | public function FPSController(hls : HLS) { 35 | _hls = hls; 36 | _throttling = false; 37 | _playing = false; 38 | _lastTime = 0; 39 | /** Check that Flash Player version is sufficient (11.2 or above) to use throttling event **/ 40 | if(_checkVersion() >= 11.2) { 41 | _hls.addEventListener(HLSEvent.PLAYBACK_STATE, _playbackStateHandler); 42 | _hls.addEventListener(HLSEvent.STAGE_SET, _stageSetHandler); 43 | } 44 | } 45 | 46 | private function _checkVersion() : Number { 47 | var verArray : Array = Capabilities.version.split(/\s|,/); 48 | return Number(String(verArray[1] + "." + verArray[2])); 49 | } 50 | 51 | public function dispose() : void { 52 | if(_checkVersion() >= 11.2) { 53 | _hls.removeEventListener(HLSEvent.PLAYBACK_STATE, _playbackStateHandler); 54 | _hls.removeEventListener(HLSEvent.STAGE_SET, _stageSetHandler); 55 | if(_timer) { 56 | _timer.stop(); 57 | } 58 | if(_hls.stage) { 59 | _hls.stage.removeEventListener(THROTTLE, onThrottle); 60 | } 61 | } 62 | } 63 | 64 | private function _stageSetHandler(event : HLSEvent) : void { 65 | CONFIG::LOGGING { 66 | Log.debug("FPSController:stage defined, listen to throttle event"); 67 | } 68 | _timer = new Timer(HLSSettings.fpsDroppedMonitoringPeriod,0); 69 | _timer.addEventListener(TimerEvent.TIMER, _checkFPS); 70 | _timer.start(); 71 | _hls.stage.addEventListener(THROTTLE, onThrottle); 72 | } 73 | 74 | private function _playbackStateHandler(event : HLSEvent) : void { 75 | switch(event.state) { 76 | case HLSPlayStates.PLAYING: 77 | // start fps monitoring when switching to playing state 78 | _playing = true; 79 | _lastTime = 0; 80 | CONFIG::LOGGING { 81 | Log.debug("FPSController:playback starting, start monitoring FPS"); 82 | } 83 | break; 84 | default: 85 | _playing = false; 86 | // stop fps monitoring in all other cases 87 | CONFIG::LOGGING { 88 | Log.debug("FPSController:playback stopped, stop monitoring FPS"); 89 | } 90 | break; 91 | } 92 | }; 93 | 94 | private function onThrottle(e : Object) : void { 95 | CONFIG::LOGGING { 96 | Log.debug("FPSController:onThrottle:" + e.state + ',fps:' + e.targetFrameRate); 97 | } 98 | switch(e.state) { 99 | case RESUME: 100 | _throttling=false; 101 | _lastTime = 0; 102 | break; 103 | default: 104 | _throttling=true; 105 | break; 106 | } 107 | } 108 | 109 | private function _checkFPS(e : Event) : void { 110 | var currentTime : int = getTimer(); 111 | var droppedFrames : int = _hls.stream.info.droppedFrames; 112 | // monitor only if not throttling AND playing AND we hold a time reference with nb of dropped frames 113 | if(_throttling == false && _playing == true && _lastTime) { 114 | var currentPeriod : int = currentTime-_lastTime; 115 | var currentDropped : int = droppedFrames - _lastDroppedFrames; 116 | var currentDropFPS : Number = 1000*currentDropped/currentPeriod; 117 | var currentFPS : Number = _hls.stream.currentFPS; 118 | CONFIG::LOGGING { 119 | Log.debug2("FPSController:currentDropped,currentPeriod,currentDropFPS," + currentDropped +',' + currentPeriod +',' + currentDropFPS.toFixed(1)); 120 | } 121 | if(currentDropFPS > HLSSettings.fpsDroppedMonitoringThreshold*currentFPS) { 122 | CONFIG::LOGGING { 123 | Log.warn("FPSController:drop FPS ratio >" + HLSSettings.fpsDroppedMonitoringThreshold + ',drop/displayed:' +currentDropFPS.toFixed(1)+","+currentFPS.toFixed(1) + " in the last " + HLSSettings.fpsDroppedMonitoringPeriod + "ms"); 124 | } 125 | _hls.dispatchEvent(new HLSEvent(HLSEvent.FPS_DROP, _hls.currentLevel)); 126 | if(HLSSettings.capLevelonFPSDrop) { 127 | var currentLevel : int = _hls.currentLevel; 128 | if(currentLevel > 0 && (_hls.autoLevelCapping == -1 || _hls.autoLevelCapping >= _hls.currentLevel)) { 129 | var capLevel : int = currentLevel-1; 130 | CONFIG::LOGGING { 131 | Log.warn("FPSController:cap level on FPS drop to " + capLevel); 132 | } 133 | _hls.autoLevelCapping = capLevel; 134 | _hls.dispatchEvent(new HLSEvent(HLSEvent.FPS_DROP_LEVEL_CAPPING, capLevel)); 135 | if(HLSSettings.smoothAutoSwitchonFPSDrop) { 136 | if(_hls.autoLevel == true) { 137 | CONFIG::LOGGING { 138 | Log.warn("FPSController:trigger smooth level switch on frame drop"); 139 | } 140 | _hls.nextLevel = -1; 141 | _hls.dispatchEvent(new HLSEvent(HLSEvent.FPS_DROP_SMOOTH_LEVEL_SWITCH)); 142 | } 143 | } 144 | } 145 | } 146 | } 147 | } 148 | _lastTime = currentTime; 149 | _lastDroppedFrames = droppedFrames; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/AVCC.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | 6 | 7 | import flash.utils.ByteArray; 8 | 9 | CONFIG::LOGGING { 10 | import com.luojianghong.hls.utils.Log; 11 | import com.luojianghong.hls.HLSSettings; 12 | } 13 | public class AVCC { 14 | 15 | /** H264 profiles. **/ 16 | private static const PROFILES : Object = {'66':'H264 Baseline', '77':'H264 Main', '100':'H264 High'}; 17 | /** Get Avcc header from AVC stream 18 | See ISO 14496-15, 5.2.4.1 for the description of AVCDecoderConfigurationRecord 19 | **/ 20 | public static function getAVCC(sps : ByteArray, ppsVect : Vector.) : ByteArray { 21 | // Write startbyte 22 | var avcc : ByteArray = new ByteArray(); 23 | avcc.writeByte(0x01); 24 | // Write profile, compatibility and level. 25 | avcc.writeBytes(sps, 1, 3); 26 | // reserved (6 bits), NALU length size - 1 (2 bits) 27 | avcc.writeByte(0xFC | 3); 28 | // reserved (3 bits), num of SPS (5 bits) 29 | avcc.writeByte(0xE0 | 1); 30 | // 2 bytes for length of SPS 31 | avcc.writeShort(sps.length); 32 | // data of SPS 33 | avcc.writeBytes(sps, 0, sps.length); 34 | // Number of PPS 35 | avcc.writeByte(ppsVect.length); 36 | for each (var pps : ByteArray in ppsVect) { 37 | // 2 bytes for length of PPS 38 | avcc.writeShort(pps.length); 39 | // data of PPS 40 | avcc.writeBytes(pps, 0, pps.length); 41 | } 42 | CONFIG::LOGGING { 43 | if (HLSSettings.logDebug) { 44 | // Grab profile/level 45 | sps.position = 1; 46 | var prf : int = sps.readByte(); 47 | sps.position = 3; 48 | var lvl : int = sps.readByte(); 49 | Log.debug("AVC: " + PROFILES[prf] + ' level ' + lvl); 50 | } 51 | } 52 | avcc.position = 0; 53 | return avcc; 54 | } 55 | ; 56 | } 57 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/AudioFrame.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | /** Audio Frame **/ 6 | public class AudioFrame { 7 | public var start : int; 8 | public var length : int; 9 | public var expected_length : int; 10 | public var rate : int; 11 | 12 | public function AudioFrame(start : int, length : int, expected_length : int, rate : int) { 13 | this.start = start; 14 | this.length = length; 15 | this.expected_length = expected_length; 16 | this.rate = rate; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/DemuxHelper.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | import flash.utils.ByteArray; 6 | import com.fenhongxiang.hls.model.Level; 7 | 8 | CONFIG::LOGGING { 9 | import com.luojianghong.hls.utils.Log; 10 | } 11 | public class DemuxHelper { 12 | public static function probe(data : ByteArray, level : Level, audioselect : Function, progress : Function, complete : Function, videometadata : Function, audioOnly : Boolean) : Demuxer { 13 | data.position = 0; 14 | CONFIG::LOGGING { 15 | Log.debug("probe fragment type"); 16 | } 17 | var aac_match : Boolean = AACDemuxer.probe(data); 18 | var mp3_match : Boolean = MP3Demuxer.probe(data); 19 | var ts_match : Boolean = TSDemuxer.probe(data); 20 | CONFIG::LOGGING { 21 | Log.debug("AAC/MP3/TS match:" + aac_match + "/" + mp3_match + "/" + ts_match); 22 | } 23 | /* prioritize level info : 24 | * if ts_match && codec_avc => TS demuxer 25 | * if aac_match && codec_aac => AAC demuxer 26 | * if mp3_match && codec_mp3 => MP3 demuxer 27 | * if no codec info in Manifest, use fallback order : AAC/MP3/TS 28 | */ 29 | if (ts_match && level.codec_h264) { 30 | CONFIG::LOGGING { 31 | Log.debug("TS match + H264 signaled in Manifest, use TS demuxer"); 32 | } 33 | return new TSDemuxer(audioselect, progress, complete, videometadata, audioOnly); 34 | } else if (aac_match && level.codec_aac) { 35 | CONFIG::LOGGING { 36 | Log.debug("AAC match + AAC signaled in Manifest, use AAC demuxer"); 37 | } 38 | return new AACDemuxer(audioselect, progress, complete); 39 | } else if (mp3_match && level.codec_mp3) { 40 | CONFIG::LOGGING { 41 | Log.debug("MP3 match + MP3 signaled in Manifest, use MP3 demuxer"); 42 | } 43 | return new MP3Demuxer(audioselect, progress, complete); 44 | } else if (aac_match) { 45 | return new AACDemuxer(audioselect, progress, complete); 46 | } else if (mp3_match) { 47 | return new MP3Demuxer(audioselect, progress, complete); 48 | } else if (ts_match) { 49 | return new TSDemuxer(audioselect, progress, complete, videometadata, audioOnly); 50 | } else { 51 | CONFIG::LOGGING { 52 | Log.debug("probe fails"); 53 | } 54 | return null; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/Demuxer.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | import flash.utils.ByteArray; 6 | 7 | public interface Demuxer { 8 | function append(data : ByteArray) : void; 9 | 10 | function notifycomplete() : void; 11 | 12 | function cancel() : void; 13 | 14 | function get audioExpected() : Boolean; 15 | 16 | function get videoExpected() : Boolean; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/ExpGolomb.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | import flash.utils.ByteArray; 6 | public class ExpGolomb { 7 | private var _data : ByteArray; 8 | private var _bit : int; 9 | private var _curByte : uint; 10 | 11 | public function ExpGolomb(data : ByteArray) { 12 | _data = data; 13 | _bit = -1; 14 | } 15 | 16 | private function _readBit() : uint { 17 | var res : uint; 18 | if (_bit == -1) { 19 | // read next 20 | _curByte = _data.readByte(); 21 | _bit = 7; 22 | } 23 | res = _curByte & (1 << _bit) ? 1 : 0; 24 | _bit--; 25 | return res; 26 | } 27 | 28 | public function readBoolean() : Boolean { 29 | return (_readBit() == 1); 30 | } 31 | 32 | public function readBits(nbBits : uint) : int { 33 | var val : int = 0; 34 | for (var i : uint = 0; i < nbBits; ++i) 35 | val = (val << 1) + _readBit(); 36 | return val; 37 | } 38 | 39 | public function readUE() : uint { 40 | var nbZero : uint = 0; 41 | while (_readBit() == 0) 42 | ++nbZero; 43 | var x : uint = readBits(nbZero); 44 | return x + (1 << nbZero) - 1; 45 | } 46 | 47 | public function readSE() : uint { 48 | var value : int = readUE(); 49 | // the number is odd if the low order bit is set 50 | if (0x01 & value) { 51 | // add 1 to make it even, and divide by 2 52 | return (1 + value) >> 1; 53 | } else { 54 | // divide by two then make it negative 55 | return -1 * (value >> 1); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/ID3.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | import flash.utils.ByteArray; 6 | 7 | CONFIG::LOGGING { 8 | import com.luojianghong.hls.utils.Log; 9 | } 10 | 11 | public class ID3 { 12 | public var len : int; 13 | public var hasTimestamp : Boolean = false; 14 | public var timestamp : Number; 15 | public var tags : Vector. = new Vector.(); 16 | 17 | /* create ID3 object by parsing ByteArray, looking for ID3 tag length, and timestamp */ 18 | public function ID3(data : ByteArray) { 19 | var tagSize : uint = 0; 20 | try { 21 | var pos : uint = data.position; 22 | var header : String; 23 | do { 24 | header = data.readUTFBytes(3); 25 | if (header == 'ID3') { 26 | // skip 24 bits 27 | data.position += 3; 28 | // retrieve tag(s) length 29 | var byte1 : uint = data.readUnsignedByte() & 0x7f; 30 | var byte2 : uint = data.readUnsignedByte() & 0x7f; 31 | var byte3 : uint = data.readUnsignedByte() & 0x7f; 32 | var byte4 : uint = data.readUnsignedByte() & 0x7f; 33 | tagSize = (byte1 << 21) + (byte2 << 14) + (byte3 << 7) + byte4; 34 | var endPos : uint = data.position + tagSize; 35 | CONFIG::LOGGING { 36 | Log.debug2("ID3 tag found, size/end pos:" + tagSize + "/" + endPos); 37 | } 38 | // read ID3 tags 39 | _parseID3Frames(data, endPos); 40 | data.position = endPos; 41 | } else if (header == '3DI') { 42 | // http://id3.org/id3v2.4.0-structure chapter 3.4. ID3v2 footer 43 | data.position += 7; 44 | CONFIG::LOGGING { 45 | Log.debug2("3DI footer found, end pos:" + data.position); 46 | } 47 | } else { 48 | data.position -= 3; 49 | len = data.position - pos; 50 | CONFIG::LOGGING { 51 | if (len) { 52 | Log.debug2("ID3 len:" + len); 53 | if (!hasTimestamp) { 54 | Log.warn("ID3 tag found, but no timestamp"); 55 | } 56 | } 57 | } 58 | return; 59 | } 60 | } while (true); 61 | } catch(e : Error) { 62 | } 63 | len = 0; 64 | return; 65 | }; 66 | 67 | /* Each Elementary Audio Stream segment MUST signal the timestamp of 68 | its first sample with an ID3 PRIV tag [ID3] at the beginning of 69 | the segment. The ID3 PRIV owner identifier MUST be 70 | "com.apple.streaming.transportStreamTimestamp". The ID3 payload 71 | MUST be a 33-bit MPEG-2 Program Elementary Stream timestamp 72 | expressed as a big-endian eight-octet number, with the upper 31 73 | bits set to zero. 74 | */ 75 | private function _parseID3Frames(data : ByteArray, endPos : uint) : void { 76 | while(data.position + 8 <= endPos) { 77 | var tag_id : String = data.readUTFBytes(4); 78 | var tag_len : int = data.readUnsignedInt(); 79 | var tag_flags : int = data.readUnsignedShort(); 80 | 81 | CONFIG::LOGGING { 82 | Log.debug("ID3 tag id:" + tag_id); 83 | } 84 | switch(tag_id) { 85 | case "PRIV": 86 | // owner should be "com.apple.streaming.transportStreamTimestamp" 87 | if (data.readUTFBytes(44) == 'com.apple.streaming.transportStreamTimestamp') { 88 | // smelling even better ! we found the right descriptor 89 | // skip null character (string end) + 3 first bytes 90 | data.position += 4; 91 | // timestamp is 33 bit expressed as a big-endian eight-octet number, with the upper 31 bits set to zero. 92 | var pts_33_bit : int = data.readUnsignedByte() & 0x1; 93 | hasTimestamp = true; 94 | timestamp = (data.readUnsignedInt() / 90); 95 | if (pts_33_bit) { 96 | timestamp += 47721858.84; // 2^32 / 90 97 | } 98 | timestamp = Math.round(timestamp); 99 | CONFIG::LOGGING { 100 | Log.debug("ID3 timestamp found:" + timestamp); 101 | } 102 | } 103 | break; 104 | default: 105 | var tag_data : *; 106 | var firstChar : String = tag_id.charAt(0); 107 | if(firstChar == 'T' || firstChar == 'W') { 108 | tag_data = new Array(); 109 | var cur_tag_pos : int; 110 | var end_tag_pos : int = data.position + tag_len; 111 | // skip character encoding byte 112 | data.position++; 113 | while(data.position < end_tag_pos - 1) { 114 | cur_tag_pos = data.position; 115 | var text : String = data.readUTFBytes(end_tag_pos-cur_tag_pos); 116 | CONFIG::LOGGING { 117 | Log.debug("text:" + text); 118 | } 119 | data.position = cur_tag_pos + text.length + 1; 120 | tag_data.push(text); 121 | } 122 | data.position = end_tag_pos; 123 | } else { 124 | tag_data = new ByteArray(); 125 | data.readBytes(tag_data, 0, tag_len); 126 | } 127 | tags.push(new ID3Tag(tag_id,tag_flags,tag_data)); 128 | break; 129 | } 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/ID3Tag.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | /** Video Frame **/ 6 | public class ID3Tag { 7 | public var id : String; 8 | public var flag : int; 9 | public var value : *; 10 | 11 | public function ID3Tag(id : String, flag : int, value : *) { 12 | this.id = id; 13 | this.flag = flag; 14 | this.value = value; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/MP3Demuxer.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | import flash.utils.ByteArray; 6 | 7 | import com.fenhongxiang.hls.model.AudioTrack; 8 | import com.fenhongxiang.hls.flv.FLVTag; 9 | 10 | CONFIG::LOGGING { 11 | import com.luojianghong.hls.utils.Log; 12 | } 13 | public class MP3Demuxer implements Demuxer { 14 | /* MPEG1-Layer3 syncword */ 15 | private static const SYNCWORD : uint = 0xFFFB; 16 | private static const RATES : Array = [44100, 48000, 32000]; 17 | private static const BIT_RATES : Array = [0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0]; 18 | private static const SAMPLES_PER_FRAME : uint = 1152; 19 | /** Byte data to be read **/ 20 | private var _data : ByteArray; 21 | /* callback functions for audio selection, and parsing progress/complete */ 22 | private var _callback_audioselect : Function; 23 | private var _callback_progress : Function; 24 | private var _callback_complete : Function; 25 | 26 | /** append new data */ 27 | public function append(data : ByteArray) : void { 28 | if (_data == null) { 29 | _data = new ByteArray(); 30 | } 31 | _data.writeBytes(data); 32 | } 33 | 34 | /** cancel demux operation */ 35 | public function cancel() : void { 36 | _data = null; 37 | } 38 | 39 | public function get audioExpected() : Boolean { 40 | return true; 41 | } 42 | 43 | public function get videoExpected() : Boolean { 44 | return false; 45 | } 46 | 47 | public function notifycomplete() : void { 48 | CONFIG::LOGGING { 49 | Log.debug("MP3: extracting MP3 tags"); 50 | } 51 | var audioTags : Vector. = new Vector.(); 52 | /* parse MP3, convert Elementary Streams to TAG */ 53 | _data.position = 0; 54 | var id3 : ID3 = new ID3(_data); 55 | // MP3 should contain ID3 tag filled with a timestamp 56 | var frames : Vector. = getFrames(_data, _data.position); 57 | var audioTag : FLVTag; 58 | var stamp : int; 59 | var i : int = 0; 60 | 61 | while (i < frames.length) { 62 | stamp = Math.round(id3.timestamp + i * 1024 * 1000 / frames[i].rate); 63 | audioTag = new FLVTag(FLVTag.MP3_RAW, stamp, stamp, false); 64 | if (i != frames.length - 1) { 65 | audioTag.push(_data, frames[i].start, frames[i].length); 66 | } else { 67 | audioTag.push(_data, frames[i].start, _data.length - frames[i].start); 68 | } 69 | audioTags.push(audioTag); 70 | i++; 71 | } 72 | var audiotracks : Vector. = new Vector.(); 73 | audiotracks.push(new AudioTrack('MP3 ES', AudioTrack.FROM_DEMUX, 0, true,false)); 74 | // report unique audio track. dont check return value as obviously the track will be selected 75 | _callback_audioselect(audiotracks); 76 | CONFIG::LOGGING { 77 | Log.debug("MP3: all tags extracted, callback demux"); 78 | } 79 | _data = null; 80 | _callback_progress(audioTags); 81 | _callback_complete(); 82 | } 83 | 84 | public function MP3Demuxer(callback_audioselect : Function, callback_progress : Function, callback_complete : Function) : void { 85 | _callback_audioselect = callback_audioselect; 86 | _callback_progress = callback_progress; 87 | _callback_complete = callback_complete; 88 | }; 89 | 90 | public static function probe(data : ByteArray) : Boolean { 91 | var pos : uint = data.position; 92 | var id3 : ID3 = new ID3(data); 93 | // MP3 should contain ID3 tag filled with a timestamp 94 | if (id3.hasTimestamp) { 95 | while (data.bytesAvailable > 1) { 96 | // Check for MP3 header 97 | var short : uint = data.readUnsignedShort(); 98 | if (short == SYNCWORD) { 99 | data.position = pos; 100 | return true; 101 | } else { 102 | data.position--; 103 | } 104 | } 105 | data.position = pos; 106 | } 107 | return false; 108 | } 109 | 110 | private static function getFrames(data : ByteArray, position : uint) : Vector. { 111 | var frames : Vector. = new Vector.(); 112 | var frame_start : uint; 113 | var frame_length : uint; 114 | var id3 : ID3 = new ID3(data); 115 | position += id3.len; 116 | // Get raw MP3 frames from audio stream. 117 | data.position = position; 118 | // we need at least 3 bytes, 2 for sync word, 1 for flags 119 | while (data.bytesAvailable > 3) { 120 | frame_start = data.position; 121 | // frame header described here : http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html 122 | var short : uint = data.readUnsignedShort(); 123 | if (short == SYNCWORD) { 124 | var flag : uint = data.readByte(); 125 | // (15,12)=(&0xf0 >>4) Bitrate index 126 | var bitrate : uint = BIT_RATES[(flag & 0xf0) >> 4]; 127 | // (11,10)=(&0xc >> 2) Sampling rate frequency index (values are in Hz) 128 | var samplerate : uint = RATES[(flag & 0xc) >> 2]; 129 | // (9)=(&2 >>1) Padding bit 130 | var padbit : uint = (flag & 2) >> 1; 131 | frame_length = (SAMPLES_PER_FRAME / 8) * bitrate / samplerate + padbit; 132 | frame_length = Math.round(frame_length); 133 | data.position = data.position + (frame_length - 3); 134 | frames.push(new AudioFrame(frame_start, frame_length, frame_length, samplerate)); 135 | } else { 136 | data.position = data.position - 1; 137 | } 138 | } 139 | data.position = position; 140 | return frames; 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/Nalu.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | 6 | import flash.utils.ByteArray; 7 | CONFIG::LOGGING { 8 | import com.luojianghong.hls.HLSSettings; 9 | import com.luojianghong.hls.utils.Log; 10 | } 11 | 12 | /** Constants and utilities for the H264 video format. **/ 13 | public class Nalu { 14 | 15 | private static var _audNalu : ByteArray; 16 | // static initializer 17 | { 18 | _audNalu = new ByteArray(); 19 | _audNalu.length = 2; 20 | _audNalu.writeByte(0x09); 21 | _audNalu.writeByte(0xF0); 22 | }; 23 | 24 | 25 | /** Return an array with NAL delimiter indexes. **/ 26 | public static function getNALU(nalu : ByteArray, position : uint) : Vector. { 27 | var len : uint = nalu.length,i : uint = position; 28 | var unitHeader : int,lastUnitHeader : int = 0; 29 | var unitStart : int,lastUnitStart : int = 0; 30 | var unitType : int,lastUnitType : int = 0; 31 | var audFound : Boolean = false; 32 | var value : uint,state : uint = 0; 33 | var units : Vector. = new Vector.(); 34 | // Loop through data to find NAL startcodes. 35 | while (i < len) { 36 | // finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01) 37 | value = nalu[i++]; 38 | switch(state) 39 | { 40 | case 0: 41 | if(!value) { 42 | state = 1; 43 | // unitHeader is NAL header offset 44 | unitHeader=i-1; 45 | } 46 | break; 47 | case 1: 48 | if(value) { 49 | state = 0; 50 | } else { 51 | state = 2; 52 | } 53 | break; 54 | case 2: 55 | case 3: 56 | if(value) { 57 | if(value === 1) { 58 | unitType = nalu[i] & 0x1f; 59 | if(unitType == 9) { 60 | audFound = true; 61 | } 62 | if(lastUnitStart) { 63 | // use Math.min(4,...) as max header size is 4. 64 | // in case there are any leading zeros 65 | // such as 00 00 00 00 00 00 01 66 | // ^^ 67 | // we need to ignore them as they are part of previous NAL unit 68 | units.push(new VideoFrame(Math.min(4,lastUnitStart-lastUnitHeader), i-state-1-lastUnitStart, lastUnitStart, lastUnitType)); 69 | } 70 | lastUnitStart = i; 71 | lastUnitType = unitType; 72 | lastUnitHeader = unitHeader; 73 | if(audFound == true && (unitType === 1 || unitType === 5)) { 74 | // OPTI !!! if AUD unit already parsed and if IDR/NDR unit, consider it is last NALu 75 | i = len; 76 | } 77 | } 78 | state = 0; 79 | } else { 80 | state = 3; 81 | } 82 | break; 83 | default: 84 | break; 85 | } 86 | } 87 | //push last unit 88 | if(lastUnitStart) { 89 | units.push(new VideoFrame(Math.min(4,lastUnitStart-lastUnitHeader), len-lastUnitStart, lastUnitStart, lastUnitType)); 90 | } 91 | // Reset position and return results. 92 | CONFIG::LOGGING { 93 | if (HLSSettings.logDebug2) { 94 | /** H264 NAL unit names. **/ 95 | const NAMES : Array = ['Unspecified',// 0 96 | 'NDR', // 1 97 | 'Partition A', // 2 98 | 'Partition B', // 3 99 | 'Partition C', // 4 100 | 'IDR', // 5 101 | 'SEI', // 6 102 | 'SPS', // 7 103 | 'PPS', // 8 104 | 'AUD', // 9 105 | 'End of Sequence', // 10 106 | 'End of Stream', // 11 107 | 'Filler Data'// 12 108 | ]; 109 | if (units.length) { 110 | var txt : String = "AVC: "; 111 | for (i = 0; i < units.length; i++) { 112 | txt += NAMES[units[i].type] + ","; //+ ":" + units[i].length 113 | } 114 | Log.debug2(txt.substr(0,txt.length-1) + " slices"); 115 | } else { 116 | Log.debug2('AVC: no NALU slices found'); 117 | } 118 | } 119 | } 120 | nalu.position = position; 121 | return units; 122 | }; 123 | 124 | public static function get AUD():ByteArray { 125 | return _audNalu; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/PES.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | import flash.utils.ByteArray; 6 | 7 | /** Representation of a Packetized Elementary Stream. **/ 8 | public class PES { 9 | /** The PES data (including headers). **/ 10 | public var data : ByteArray; 11 | /** PES prefix **/ 12 | public var prefix : uint; 13 | /** Start of the payload. **/ 14 | public var payload : uint; 15 | /** Timestamp from the PTS header. **/ 16 | public var pts : Number; 17 | /** Timestamp from the DTS header. **/ 18 | public var dts : Number; 19 | /** PES packet len **/ 20 | public var len : int; 21 | /** PES packet len **/ 22 | public var payload_len : int; 23 | 24 | /** Save the first chunk of PES data. **/ 25 | public function PES(dat : ByteArray) { 26 | data = dat; 27 | parse(); 28 | }; 29 | 30 | /** When all data is appended, parse the PES headers. **/ 31 | private function parse() : void { 32 | data.position = 0; 33 | // Start code prefix and packet ID. 34 | prefix = data.readUnsignedInt(); 35 | // read len 36 | len = data.readUnsignedShort(); 37 | // Ignore marker bits. 38 | data.position += 1; 39 | // Check for PTS 40 | var flags : uint = (data.readUnsignedByte() & 192) >> 6; 41 | // Check PES header length 42 | var length : uint = data.readUnsignedByte(); 43 | 44 | if (flags == 2 || flags == 3) { 45 | // Grab the timestamp from PTS data (spread out over 5 bytes): 46 | // XXXX---X -------- -------X -------- -------X 47 | 48 | var _pts : Number = Number((data.readUnsignedByte() & 0x0e)) * Number(1 << 29) + Number((data.readUnsignedShort() >> 1) << 15) + Number((data.readUnsignedShort() >> 1)); 49 | // check if greater than 2^32 -1 50 | if (_pts > 4294967295) { 51 | // decrement 2^33 52 | _pts -= 8589934592; 53 | } 54 | length -= 5; 55 | var _dts : Number = _pts; 56 | if (flags == 3) { 57 | // Grab the DTS (like PTS) 58 | _dts = Number((data.readUnsignedByte() & 0x0e)) * Number(1 << 29) + Number((data.readUnsignedShort() >> 1) << 15) + Number((data.readUnsignedShort() >> 1)); 59 | // check if greater than 2^32 -1 60 | if (_dts > 4294967295) { 61 | // decrement 2^33 62 | _dts -= 8589934592; 63 | } 64 | length -= 5; 65 | } 66 | pts = Math.round(_pts / 90); 67 | dts = Math.round(_dts / 90); 68 | // CONFIG::LOGGING { 69 | // Log.info("pts/dts: " + pts + "/"+ dts); 70 | // } 71 | } 72 | // Skip other header data and parse payload. 73 | data.position += length; 74 | payload = data.position; 75 | if (len) { 76 | payload_len = len - data.position + 6; 77 | } else { 78 | payload_len = 0; 79 | } 80 | }; 81 | } 82 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/SPSInfo.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | 6 | import flash.utils.ByteArray; 7 | 8 | /* inspired from https://github.com/aizvorski/h264bitstream/blob/master/h264_stream.c#L241-L342 */ 9 | 10 | public class SPSInfo { 11 | public var width : int; 12 | public var height : int; 13 | 14 | public function SPSInfo(sps : ByteArray) { 15 | var profile_idc : int; 16 | sps.position++; 17 | profile_idc = sps.readUnsignedByte(); 18 | var eg : ExpGolomb = new ExpGolomb(sps); 19 | // constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8) 20 | eg.readBits(16); 21 | // skip seq_parameter_set_id 22 | eg.readUE(); 23 | if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 144) { 24 | var chroma_format_idc : int = eg.readUE(); 25 | if (3 === chroma_format_idc) { 26 | // separate_colour_plane_flag 27 | eg.readBits(1); 28 | } 29 | // bit_depth_luma_minus8 30 | eg.readUE(); 31 | // bit_depth_chroma_minus8 32 | eg.readUE(); 33 | // qpprime_y_zero_transform_bypass_flag 34 | eg.readBits(1); 35 | // seq_scaling_matrix_present_flag 36 | var seq_scaling_matrix_present_flag : Boolean = eg.readBoolean(); 37 | if (seq_scaling_matrix_present_flag) { 38 | var imax : int = (chroma_format_idc != 3) ? 8 : 12; 39 | for (var i : int = 0; i < imax; ++i) { 40 | // seq_scaling_list_present_flag[ i ] 41 | if (eg.readBoolean()) { 42 | if (i < 6) { 43 | scaling_list(16, eg); 44 | } else { 45 | scaling_list(64, eg); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | // log2_max_frame_num_minus4 52 | eg.readUE(); 53 | var pic_order_cnt_type : int = eg.readUE(); 54 | if ( 0 === pic_order_cnt_type ) { 55 | // log2_max_pic_order_cnt_lsb_minus4 56 | eg.readUE(); 57 | } else if ( 1 === pic_order_cnt_type ) { 58 | // delta_pic_order_always_zero_flag 59 | eg.readBits(1); 60 | // offset_for_non_ref_pic 61 | eg.readUE(); 62 | // offset_for_top_to_bottom_field 63 | eg.readUE(); 64 | var num_ref_frames_in_pic_order_cnt_cycle : int = eg.readUE(); 65 | for (i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; ++i) { 66 | // offset_for_ref_frame[ i ] 67 | eg.readUE(); 68 | } 69 | } 70 | // max_num_ref_frames 71 | eg.readUE(); 72 | // gaps_in_frame_num_value_allowed_flag 73 | eg.readBits(1); 74 | var pic_width_in_mbs_minus1 : int = eg.readUE(); 75 | var pic_height_in_map_units_minus1 : int = eg.readUE(); 76 | var frame_mbs_only_flag : int = eg.readBits(1); 77 | if (0 === frame_mbs_only_flag) { 78 | // mb_adaptive_frame_field_flag 79 | eg.readBits(1); 80 | } 81 | // direct_8x8_inference_flag 82 | eg.readBits(1); 83 | var frame_cropping_flag : int = eg.readBits(1); 84 | if (frame_cropping_flag) { 85 | var frame_crop_left_offset : int = eg.readUE(); 86 | var frame_crop_right_offset : int = eg.readUE(); 87 | var frame_crop_top_offset : int = eg.readUE(); 88 | var frame_crop_bottom_offset : int = eg.readUE(); 89 | } 90 | width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2; 91 | height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2); 92 | } 93 | 94 | private static function scaling_list(sizeOfScalingList : int, eg : ExpGolomb) : void { 95 | var lastScale : int = 8; 96 | var nextScale : int = 8; 97 | var delta_scale : int; 98 | for (var j : int = 0; j < sizeOfScalingList; ++j) { 99 | if (nextScale != 0) { 100 | delta_scale = eg.readSE(); 101 | nextScale = (lastScale + delta_scale + 256) % 256; 102 | } 103 | lastScale = (nextScale == 0) ? lastScale : nextScale; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/demux/VideoFrame.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.demux { 5 | /** Video Frame **/ 6 | public class VideoFrame { 7 | public var header : int; 8 | public var start : int; 9 | public var length : int; 10 | public var type : int; 11 | 12 | public function VideoFrame(header : int, length : int, start : int, type : int) { 13 | this.header = header; 14 | this.start = start; 15 | this.length = length; 16 | this.type = type; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/event/HLSError.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.event { 5 | /** Error Identifier **/ 6 | public class HLSError { 7 | public static const OTHER_ERROR : int = 0; 8 | public static const MANIFEST_LOADING_CROSSDOMAIN_ERROR : int = 1; 9 | public static const MANIFEST_LOADING_IO_ERROR : int = 2; 10 | public static const MANIFEST_PARSING_ERROR : int = 3; 11 | public static const FRAGMENT_LOADING_CROSSDOMAIN_ERROR : int = 4; 12 | public static const FRAGMENT_LOADING_ERROR : int = 5; 13 | public static const FRAGMENT_PARSING_ERROR : int = 6; 14 | public static const KEY_LOADING_CROSSDOMAIN_ERROR : int = 7; 15 | public static const KEY_LOADING_ERROR : int = 8; 16 | public static const KEY_PARSING_ERROR : int = 9; 17 | public static const TAG_APPENDING_ERROR : int = 10; 18 | 19 | private var _code : int; 20 | private var _url : String; 21 | private var _msg : String; 22 | 23 | public function HLSError(code : int, url : String, msg : String) { 24 | _code = code; 25 | _url = url; 26 | _msg = msg; 27 | } 28 | 29 | public function get code() : int { 30 | return _code; 31 | } 32 | 33 | public function get msg() : String { 34 | return _msg; 35 | } 36 | 37 | public function get url() : String { 38 | return _url; 39 | } 40 | 41 | public function toString() : String { 42 | return "HLSError(code/url/msg)=" + _code + "/" + _url + "/" + _msg; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/event/HLSEvent.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.event { 5 | import com.fenhongxiang.hls.model.Level; 6 | 7 | import flash.events.Event; 8 | 9 | /** Event fired when an error prevents playback. **/ 10 | public class HLSEvent extends Event { 11 | /** Identifier for a manifest loading event, triggered after a call to hls.load(url) **/ 12 | public static const MANIFEST_LOADING : String = "hlsEventManifestLoading"; 13 | /** Identifier for a manifest parsed event, 14 | * triggered after main manifest has been retrieved and parsed. 15 | * hls playlist may not be playable yet, in case of adaptive streaming, start level playlist is not downloaded yet at that stage */ 16 | public static const MANIFEST_PARSED : String = "hlsEventManifestParsed"; 17 | /** Identifier for a manifest loaded event, when this event is received, main manifest and start level has been retrieved */ 18 | public static const MANIFEST_LOADED : String = "hlsEventManifestLoaded"; 19 | /** Identifier for a level loading event **/ 20 | public static const LEVEL_LOADING : String = "hlsEventLevelLoading"; 21 | /** Identifier for a level loaded event **/ 22 | public static const LEVEL_LOADED : String = "hlsEventLevelLoaded"; 23 | /** Identifier for a level switch event. **/ 24 | public static const LEVEL_SWITCH : String = "hlsEventLevelSwitch"; 25 | /** Identifier for a level ENDLIST event. **/ 26 | public static const LEVEL_ENDLIST : String = "hlsEventLevelEndList"; 27 | /** Identifier for a fragment loading event. **/ 28 | public static const FRAGMENT_LOADING : String = "hlsEventFragmentLoading"; 29 | /** Identifier for a fragment loaded event. **/ 30 | public static const FRAGMENT_LOADED : String = "hlsEventFragmentLoaded"; 31 | /** Identifier for a fragment playing event. **/ 32 | public static const FRAGMENT_PLAYING : String = "hlsEventFragmentPlaying"; 33 | /** Identifier for a audio tracks list change **/ 34 | public static const AUDIO_TRACKS_LIST_CHANGE : String = "audioTracksListChange"; 35 | /** Identifier for a audio track switch **/ 36 | public static const AUDIO_TRACK_SWITCH : String = "audioTrackSwitch"; 37 | /** Identifier for a audio level loading event **/ 38 | public static const AUDIO_LEVEL_LOADING : String = "hlsEventAudioLevelLoading"; 39 | /** Identifier for a audio level loaded event **/ 40 | public static const AUDIO_LEVEL_LOADED : String = "hlsEventAudioLevelLoaded"; 41 | /** Identifier for audio/video TAGS loaded event. **/ 42 | public static const TAGS_LOADED : String = "hlsEventTagsLoaded"; 43 | /** Identifier when last fragment of playlist has been loaded **/ 44 | public static const LAST_VOD_FRAGMENT_LOADED : String = "hlsEventLastFragmentLoaded"; 45 | /** Identifier for a playback error event. **/ 46 | public static const ERROR : String = "hlsEventError"; 47 | /** Identifier for a playback media time change event. **/ 48 | public static const MEDIA_TIME : String = "hlsEventMediaTime"; 49 | /** Identifier for a playback state switch event. **/ 50 | public static const PLAYBACK_STATE : String = "hlsPlaybackState"; 51 | /** Identifier for a seek state switch event. **/ 52 | public static const SEEK_STATE : String = "hlsSeekState"; 53 | /** Identifier for a playback complete event. **/ 54 | public static const PLAYBACK_COMPLETE : String = "hlsEventPlayBackComplete"; 55 | /** Identifier for a Playlist Duration updated event **/ 56 | public static const PLAYLIST_DURATION_UPDATED : String = "hlsPlayListDurationUpdated"; 57 | /** Identifier for a ID3 updated event **/ 58 | public static const ID3_UPDATED : String = "hlsID3Updated"; 59 | /** Identifier for a fps drop event **/ 60 | public static const FPS_DROP : String = "hlsFPSDrop"; 61 | /** Identifier for a fps drop level capping event **/ 62 | public static const FPS_DROP_LEVEL_CAPPING : String = "hlsFPSDropLevelCapping"; 63 | /** Identifier for a fps drop smooth level switch event **/ 64 | public static const FPS_DROP_SMOOTH_LEVEL_SWITCH : String = "hlsFPSDropSmoothLevelSwitch"; 65 | /** Identifier for a live loading stalled event **/ 66 | public static const LIVE_LOADING_STALLED : String = "hlsLiveLoadingStalled"; 67 | /** Identifier for a Stage set event **/ 68 | public static const STAGE_SET : String = "hlsStageSet"; 69 | 70 | /** The current url **/ 71 | public var url : String; 72 | /** The current quality level. **/ 73 | public var level : int; 74 | /** The current playlist duration. **/ 75 | public var duration : Number; 76 | /** The list with quality levels. **/ 77 | public var levels : Vector.; 78 | /** The error message. **/ 79 | public var error : HLSError; 80 | /** Load Metrics. **/ 81 | public var loadMetrics : HLSLoadMetrics; 82 | /** Play Metrics. **/ 83 | public var playMetrics : HLSPlayMetrics; 84 | /** The time position. **/ 85 | public var mediatime : HLSMediatime; 86 | /** The new playback state. **/ 87 | public var state : String; 88 | /** The current audio track **/ 89 | public var audioTrack : int; 90 | /** a complete ID3 payload from PES, as a hex dump **/ 91 | public var ID3Data : String; 92 | 93 | /** Assign event parameter and dispatch. **/ 94 | public function HLSEvent(type : String, parameter : *=null, parameter2 : *=null) { 95 | switch(type) { 96 | case MANIFEST_LOADING: 97 | case FRAGMENT_LOADING: 98 | url = parameter as String; 99 | break; 100 | case ERROR: 101 | error = parameter as HLSError; 102 | break; 103 | case TAGS_LOADED: 104 | case FRAGMENT_LOADED: 105 | case LEVEL_LOADED: 106 | case AUDIO_LEVEL_LOADED: 107 | loadMetrics = parameter as HLSLoadMetrics; 108 | break; 109 | case MANIFEST_PARSED: 110 | case MANIFEST_LOADED: 111 | levels = parameter as Vector.; 112 | if(parameter2) { 113 | loadMetrics = parameter2 as HLSLoadMetrics; 114 | } 115 | break; 116 | case MEDIA_TIME: 117 | mediatime = parameter as HLSMediatime; 118 | break; 119 | case PLAYBACK_STATE: 120 | case SEEK_STATE: 121 | state = parameter as String; 122 | break; 123 | case LEVEL_LOADING: 124 | case LEVEL_SWITCH: 125 | case AUDIO_LEVEL_LOADING: 126 | case FPS_DROP: 127 | case FPS_DROP_LEVEL_CAPPING: 128 | level = parameter as int; 129 | break; 130 | case PLAYLIST_DURATION_UPDATED: 131 | duration = parameter as Number; 132 | break; 133 | case ID3_UPDATED: 134 | ID3Data = parameter as String; 135 | break; 136 | case FRAGMENT_PLAYING: 137 | playMetrics = parameter as HLSPlayMetrics; 138 | break; 139 | default: 140 | break; 141 | } 142 | super(type, false, false); 143 | }; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/event/HLSLoadMetrics.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.event { 5 | /** Fragment Loading metrics **/ 6 | public class HLSLoadMetrics { 7 | /* Loader Type : refer to HLSLoaderTypes for enumeration */ 8 | public var type : int; 9 | /* level of loaded content */ 10 | public var level : int; 11 | /* id of loaded content : should be SN for fragment, startSN for playlist */ 12 | public var id : int; 13 | /* id2 of loaded content : endSN for playlist, nb tags for tags loaded */ 14 | public var id2 : int; 15 | /** fragment/playlist size **/ 16 | public var size : int; 17 | /** fragment/playlist duration **/ 18 | public var duration : Number; 19 | /** loading request/start/end time **/ 20 | public var loading_request_time : int; 21 | public var loading_begin_time : int; 22 | public var loading_end_time : int; 23 | /** decryption begin/end time (for fragment only) **/ 24 | public var decryption_begin_time : int; 25 | public var decryption_end_time : int; 26 | /** parsing begin/end time (for fragment only) */ 27 | public var parsing_begin_time : int; 28 | public var parsing_end_time : int; 29 | 30 | public function HLSLoadMetrics(type : int) { 31 | this.type = type; 32 | } 33 | 34 | public function get bandwidth() : Number { 35 | var bandwidth : Number = Math.round(size * 8000 / (parsing_end_time - loading_request_time)); 36 | return bandwidth; 37 | } 38 | 39 | public function get processing_duration() : int { 40 | return parsing_end_time-loading_request_time; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/event/HLSMediatime.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.event { 5 | /** Identifiers for the different stream types. **/ 6 | public class HLSMediatime { 7 | /** playback position (in seconds), relative to current playlist start. 8 | * this value could be negative in case of live playlist sliding : 9 | * this can happen in case current playback position 10 | * is in a fragment that has been removed from the playlist 11 | */ 12 | public var position : Number; 13 | /** current playlist duration (in seconds) **/ 14 | public var duration : Number; 15 | /** live main playlist sliding since previous out of buffer seek() (in seconds)**/ 16 | public var live_sliding_main : Number; 17 | /** live altaudio playlist sliding since previous out of buffer seek() (in seconds)**/ 18 | public var live_sliding_altaudio : Number; 19 | /** current buffer duration (in seconds) **/ 20 | public var buffer : Number; 21 | /** current buffer duration (in seconds) **/ 22 | public var backbuffer : Number; 23 | 24 | public function HLSMediatime(position : Number, duration : Number, buffer : Number, backbuffer : Number, live_sliding_main : Number, live_sliding_altaudio : Number) { 25 | this.position = position; 26 | this.duration = duration; 27 | this.buffer = buffer; 28 | this.backbuffer = backbuffer; 29 | this.live_sliding_main = live_sliding_main; 30 | this.live_sliding_altaudio = live_sliding_altaudio; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/event/HLSPlayMetrics.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.event { 5 | /** playback metrics, notified when playback of a given fragment starts **/ 6 | public class HLSPlayMetrics { 7 | public var level : int; 8 | public var seqnum : int; 9 | public var continuity_counter : int; 10 | public var duration : Number; 11 | public var audio_only : Boolean; 12 | public var program_date : Number; 13 | public var video_width : int; 14 | public var video_height : int; 15 | public var auto_level : Boolean; 16 | public var tag_list : Array; 17 | 18 | public function HLSPlayMetrics(level : int, seqnum : int, cc : int, duration : Number, audio_only : Boolean, program_date : Number, video_width : int, video_height : int, auto_level : Boolean, tag_list : Array) { 19 | this.level = level; 20 | this.seqnum = seqnum; 21 | this.continuity_counter = cc; 22 | this.duration = duration; 23 | this.audio_only = audio_only; 24 | this.program_date = program_date; 25 | this.video_width = video_width; 26 | this.video_height = video_height; 27 | this.auto_level = auto_level; 28 | this.tag_list = tag_list; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/handler/StatsHandler.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.handler { 5 | import flash.system.Capabilities; 6 | import com.fenhongxiang.hls.event.HLSEvent; 7 | import com.fenhongxiang.hls.event.HLSLoadMetrics; 8 | import com.fenhongxiang.hls.event.HLSPlayMetrics; 9 | import com.fenhongxiang.hls.HLS; 10 | import com.fenhongxiang.hls.model.Stats; 11 | CONFIG::LOGGING { 12 | import com.luojianghong.hls.utils.Log; 13 | } 14 | /* 15 | * class that handle per playback session stats 16 | */ 17 | public class StatsHandler { 18 | /** Reference to the HLS controller. **/ 19 | private var _hls : HLS; 20 | private var _stats : Stats; 21 | private var _sumLatency : int; 22 | private var _sumKbps : int; 23 | private var _sumAutoLevel : int; 24 | private var _levelLastAuto : Boolean; 25 | 26 | public function StatsHandler(hls : HLS) { 27 | _hls = hls; 28 | _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler); 29 | _hls.addEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler); 30 | _hls.addEventListener(HLSEvent.FRAGMENT_PLAYING,_fragmentPlayingHandler); 31 | _hls.addEventListener(HLSEvent.FPS_DROP, _fpsDropHandler); 32 | _hls.addEventListener(HLSEvent.FPS_DROP_LEVEL_CAPPING, _fpsDropLevelCappingHandler); 33 | _hls.addEventListener(HLSEvent.FPS_DROP_SMOOTH_LEVEL_SWITCH, _fpsDropSmoothLevelSwitchHandler); 34 | } 35 | 36 | public function dispose() : void { 37 | _hls.removeEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler); 38 | _hls.removeEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler); 39 | _hls.removeEventListener(HLSEvent.FRAGMENT_PLAYING, _fragmentPlayingHandler); 40 | _hls.removeEventListener(HLSEvent.FPS_DROP, _fpsDropHandler); 41 | _hls.removeEventListener(HLSEvent.FPS_DROP_LEVEL_CAPPING, _fpsDropLevelCappingHandler); 42 | _hls.removeEventListener(HLSEvent.FPS_DROP_SMOOTH_LEVEL_SWITCH, _fpsDropSmoothLevelSwitchHandler); 43 | } 44 | 45 | public function get stats() : Stats { 46 | return _stats; 47 | } 48 | 49 | private function _manifestLoadedHandler(event : HLSEvent) : void { 50 | _stats = new Stats(); 51 | _stats.levelNb = event.levels.length; 52 | _stats.levelStart = -1; 53 | _stats.tech = "flashls,"+Capabilities.version; 54 | _stats.fragBuffered = _stats.fragChangedAuto = _stats.fragChangedManual = 0; 55 | _stats.fpsDropEvent = _stats.fpsDropSmoothLevelSwitch = 0; 56 | }; 57 | 58 | private function _fragmentLoadedHandler(event : HLSEvent) : void { 59 | var metrics : HLSLoadMetrics = event.loadMetrics; 60 | var latency : int = metrics.loading_begin_time-metrics.loading_request_time; 61 | var bitrate : int = 8*metrics.size/(metrics.parsing_end_time-metrics.loading_begin_time); 62 | if(_stats.fragBuffered) { 63 | _stats.fragMinLatency = Math.min(_stats.fragMinLatency,latency); 64 | _stats.fragMaxLatency = Math.max(_stats.fragMaxLatency,latency); 65 | _stats.fragMinKbps = Math.min(_stats.fragMinKbps,bitrate); 66 | _stats.fragMaxKbps = Math.max(_stats.fragMaxKbps,bitrate); 67 | _stats.autoLevelCappingMin = Math.min(_stats.autoLevelCappingMin,_hls.autoLevelCapping); 68 | _stats.autoLevelCappingMax = Math.max(_stats.autoLevelCappingMax,_hls.autoLevelCapping); 69 | _stats.fragBuffered++; 70 | } else { 71 | _stats.fragMinLatency = _stats.fragMaxLatency = latency; 72 | _stats.fragMinKbps = _stats.fragMaxKbps = bitrate; 73 | _stats.fragBuffered = 1; 74 | _stats.fragBufferedBytes = 0; 75 | _stats.autoLevelCappingMin = _stats.autoLevelCappingMax = _hls.autoLevelCapping; 76 | _sumLatency=0; 77 | _sumKbps=0; 78 | } 79 | _sumLatency+=latency; 80 | _sumKbps+=bitrate; 81 | _stats.fragBufferedBytes+=metrics.size; 82 | _stats.fragAvgLatency = _sumLatency/_stats.fragBuffered; 83 | _stats.fragAvgKbps = _sumKbps/_stats.fragBuffered; 84 | _stats.autoLevelCappingLast = _hls.autoLevelCapping; 85 | } 86 | 87 | private function _fragmentPlayingHandler(event : HLSEvent) : void { 88 | var metrics : HLSPlayMetrics = event.playMetrics; 89 | var level : int = metrics.level; 90 | var autoLevel : Boolean = metrics.auto_level; 91 | if(_stats.levelStart == -1) { 92 | _stats.levelStart = level; 93 | } 94 | 95 | if(autoLevel) { 96 | if(_stats.fragChangedAuto) { 97 | _stats.autoLevelMin = Math.min(_stats.autoLevelMin,level); 98 | _stats.autoLevelMax = Math.max(_stats.autoLevelMax,level); 99 | _stats.fragChangedAuto++; 100 | if(_levelLastAuto && level !== _stats.autoLevelLast) { 101 | _stats.autoLevelSwitch++; 102 | } 103 | } else { 104 | _stats.autoLevelMin = _stats.autoLevelMax = level; 105 | _stats.autoLevelSwitch = 0; 106 | _stats.fragChangedAuto = 1; 107 | _sumAutoLevel = 0; 108 | } 109 | _sumAutoLevel+=level; 110 | _stats.autoLevelAvg = _sumAutoLevel/_stats.fragChangedAuto; 111 | _stats.autoLevelLast = level; 112 | } else { 113 | if(_stats.fragChangedManual) { 114 | _stats.manualLevelMin = Math.min(_stats.manualLevelMin,level); 115 | _stats.manualLevelMax = Math.max(_stats.manualLevelMax,level); 116 | _stats.fragChangedManual++; 117 | if(!_levelLastAuto && level !== _stats.manualLevelLast) { 118 | _stats.manualLevelSwitch++; 119 | } 120 | } else { 121 | _stats.manualLevelMin = _stats.manualLevelMax = level; 122 | _stats.manualLevelSwitch = 0; 123 | _stats.fragChangedManual = 1; 124 | } 125 | _stats.manualLevelLast = level; 126 | } 127 | _levelLastAuto = autoLevel; 128 | } 129 | private function _fpsDropHandler(event : HLSEvent) : void { 130 | _stats.fpsDropEvent++; 131 | _stats.fpsTotalDroppedFrames = _hls.stream.info.droppedFrames; 132 | }; 133 | private function _fpsDropLevelCappingHandler(event : HLSEvent) : void { 134 | _stats.fpsDropLevelCappingMin=event.level; 135 | }; 136 | private function _fpsDropSmoothLevelSwitchHandler(event : HLSEvent) : void { 137 | _stats.fpsDropSmoothLevelSwitch++; 138 | }; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/loader/AltAudioLevelLoader.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.loader { 5 | import flash.events.ErrorEvent; 6 | import flash.events.IOErrorEvent; 7 | import flash.events.SecurityErrorEvent; 8 | import flash.utils.clearTimeout; 9 | import flash.utils.getTimer; 10 | import flash.utils.setTimeout; 11 | import com.fenhongxiang.hls.constant.HLSPlayStates; 12 | import com.fenhongxiang.hls.event.HLSError; 13 | import com.fenhongxiang.hls.event.HLSEvent; 14 | import com.fenhongxiang.hls.event.HLSLoadMetrics; 15 | import com.fenhongxiang.hls.HLS; 16 | import com.fenhongxiang.hls.HLSSettings; 17 | import com.fenhongxiang.hls.model.AudioTrack; 18 | import com.fenhongxiang.hls.model.Fragment; 19 | import com.fenhongxiang.hls.model.Level; 20 | import com.fenhongxiang.hls.playlist.AltAudioTrack; 21 | import com.fenhongxiang.hls.playlist.Manifest; 22 | 23 | CONFIG::LOGGING { 24 | import com.luojianghong.hls.utils.Log; 25 | } 26 | public class AltAudioLevelLoader { 27 | /** Reference to the hls framework controller. **/ 28 | private var _hls : HLS; 29 | /** Link to the M3U8 file. **/ 30 | private var _url : String; 31 | /** Timeout ID for reloading live playlists. **/ 32 | private var _timeoutID : uint; 33 | /** last reload manifest time **/ 34 | private var _reloadPlaylistTimer : uint; 35 | /** current audio level **/ 36 | private var _currentTrack : int; 37 | /** reference to manifest being loaded **/ 38 | private var _manifestLoading : Manifest; 39 | /** is this loader closed **/ 40 | private var _closed : Boolean = false; 41 | /* playlist retry timeout */ 42 | private var _retryTimeout : Number; 43 | private var _retryCount : int; 44 | 45 | /** Setup the loader. **/ 46 | public function AltAudioLevelLoader(hls : HLS) { 47 | _hls = hls; 48 | _hls.addEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler); 49 | _hls.addEventListener(HLSEvent.AUDIO_TRACK_SWITCH, _audioTrackSwitchHandler); 50 | }; 51 | 52 | public function dispose() : void { 53 | _close(); 54 | _hls.removeEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler); 55 | _hls.removeEventListener(HLSEvent.AUDIO_TRACK_SWITCH, _audioTrackSwitchHandler); 56 | } 57 | 58 | /** Loading failed; return errors. **/ 59 | private function _errorHandler(event : ErrorEvent) : void { 60 | var txt : String; 61 | var code : int; 62 | if (event is SecurityErrorEvent) { 63 | code = HLSError.MANIFEST_LOADING_CROSSDOMAIN_ERROR; 64 | txt = "Cannot load M3U8: crossdomain access denied:" + event.text; 65 | } else if (event is IOErrorEvent && (HLSSettings.manifestLoadMaxRetry == -1 || _retryCount < HLSSettings.manifestLoadMaxRetry)) { 66 | CONFIG::LOGGING { 67 | Log.warn("I/O Error while trying to load Playlist, retry in " + _retryTimeout + " ms"); 68 | } 69 | _timeoutID = setTimeout(_loadAudioLevelPlaylist, _retryTimeout); 70 | /* exponential increase of retry timeout, capped to manifestLoadMaxRetryTimeout */ 71 | _retryTimeout = Math.min(HLSSettings.manifestLoadMaxRetryTimeout, 2 * _retryTimeout); 72 | _retryCount++; 73 | return; 74 | } else { 75 | code = HLSError.MANIFEST_LOADING_IO_ERROR; 76 | txt = "Cannot load M3U8: " + event.text; 77 | } 78 | var hlsError : HLSError = new HLSError(code, _url, txt); 79 | _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError)); 80 | }; 81 | 82 | /** parse a playlist **/ 83 | private function _parseAudioPlaylist(string : String, url : String, level : int, metrics : HLSLoadMetrics) : void { 84 | if (string != null && string.length != 0) { 85 | CONFIG::LOGGING { 86 | Log.debug("audio level " + level + " playlist:\n" + string); 87 | } 88 | var frags : Vector. = Manifest.getFragments(string, url, level); 89 | // set fragment and update sequence number range 90 | var audioTrack : AudioTrack = _hls.audioTracks[_currentTrack]; 91 | var audioLevel : Level = audioTrack.level; 92 | if(audioLevel == null) { 93 | audioLevel = audioTrack.level = new Level(); 94 | } 95 | audioLevel.updateFragments(frags); 96 | audioLevel.targetduration = Manifest.getTargetDuration(string); 97 | // if stream is live, arm a timer to periodically reload playlist 98 | if (!Manifest.hasEndlist(string)) { 99 | var timeout : Number = Math.max(100, _reloadPlaylistTimer + 1000 * audioLevel.averageduration - getTimer()); 100 | CONFIG::LOGGING { 101 | Log.debug("Alt Audio Level Live Playlist parsing finished: reload in " + timeout.toFixed(0) + " ms"); 102 | } 103 | _timeoutID = setTimeout(_loadAudioLevelPlaylist, timeout); 104 | } 105 | } 106 | metrics.id = audioLevel.start_seqnum; 107 | metrics.id2 = audioLevel.end_seqnum; 108 | _hls.dispatchEvent(new HLSEvent(HLSEvent.AUDIO_LEVEL_LOADED, metrics)); 109 | _manifestLoading = null; 110 | }; 111 | 112 | /** load/reload active M3U8 playlist **/ 113 | private function _loadAudioLevelPlaylist() : void { 114 | if (_closed) { 115 | return; 116 | } 117 | _reloadPlaylistTimer = getTimer(); 118 | var altAudioTrack : AltAudioTrack = _hls.altAudioTracks[_hls.audioTracks[_currentTrack].id]; 119 | _manifestLoading = new Manifest(); 120 | _manifestLoading.loadPlaylist(_hls,altAudioTrack.url, _parseAudioPlaylist, _errorHandler, _currentTrack, _hls.type, HLSSettings.flushLiveURLCache); 121 | _hls.dispatchEvent(new HLSEvent(HLSEvent.AUDIO_LEVEL_LOADING, _currentTrack)); 122 | }; 123 | 124 | /** When audio track switch occurs, assess the need of loading audio level playlist **/ 125 | private function _audioTrackSwitchHandler(event : HLSEvent) : void { 126 | _currentTrack = event.audioTrack; 127 | var audioTrack : AudioTrack = _hls.audioTracks[_currentTrack]; 128 | if (audioTrack.source == AudioTrack.FROM_PLAYLIST) { 129 | var altAudioTrack : AltAudioTrack = _hls.altAudioTracks[audioTrack.id]; 130 | if (altAudioTrack.url && audioTrack.level == null) { 131 | CONFIG::LOGGING { 132 | Log.debug("switch to audio track " + _currentTrack + ", load Playlist"); 133 | } 134 | _retryTimeout = 1000; 135 | _retryCount = 0; 136 | _closed = false; 137 | if(_manifestLoading) { 138 | _manifestLoading.close(); 139 | _manifestLoading = null; 140 | } 141 | clearTimeout(_timeoutID); 142 | _timeoutID = setTimeout(_loadAudioLevelPlaylist, 0); 143 | } 144 | } 145 | }; 146 | 147 | private function _close() : void { 148 | CONFIG::LOGGING { 149 | Log.debug("cancel any audio level load in progress"); 150 | } 151 | _closed = true; 152 | clearTimeout(_timeoutID); 153 | try { 154 | if (_manifestLoading) { 155 | _manifestLoading.close(); 156 | } 157 | } catch(e : Error) { 158 | } 159 | } 160 | 161 | /** When the framework idles out, stop reloading manifest **/ 162 | private function _stateHandler(event : HLSEvent) : void { 163 | if (event.state == HLSPlayStates.IDLE) { 164 | _close(); 165 | } 166 | }; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/model/AudioTrack.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.model { 5 | /** Audio Track identifier **/ 6 | public class AudioTrack { 7 | public static const FROM_DEMUX : int = 0; 8 | public static const FROM_PLAYLIST : int = 1; 9 | public var title : String; 10 | public var id : int; 11 | public var source : int; 12 | public var isDefault : Boolean; 13 | public var isAAC : Boolean; 14 | public var level : Level; 15 | 16 | public function AudioTrack(title : String, source : int, id : int, isDefault : Boolean, isAAC : Boolean) { 17 | this.title = title; 18 | this.source = source; 19 | this.id = id; 20 | this.isDefault = isDefault; 21 | this.isAAC = isAAC; 22 | } 23 | 24 | public function toString() : String { 25 | return "AudioTrack ID: " + id + " Title: " + title + " Source: " + source + " Default: " + isDefault + " AAC: " + isAAC; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/model/Fragment.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.model { 5 | 6 | import flash.net.ObjectEncoding; 7 | import flash.utils.ByteArray; 8 | import com.fenhongxiang.hls.flv.FLVTag; 9 | 10 | /** Fragment model **/ 11 | public class Fragment { 12 | /** Duration of this chunk. **/ 13 | public var duration : Number; 14 | /** Start time of this chunk. **/ 15 | public var start_time : Number; 16 | /** sequence number of this chunk. **/ 17 | public var seqnum : int; 18 | /** URL to this chunk. **/ 19 | public var url : String; 20 | /** level of this chunk. **/ 21 | public var level : int; 22 | /** continuity index of this chunk. **/ 23 | public var continuity : int; 24 | /** program date of this chunk. **/ 25 | public var program_date : Number; 26 | /** URL of the key used to decrypt content **/ 27 | public var decrypt_url : String; 28 | /** Initialization Vector to decrypt content **/ 29 | public var decrypt_iv : ByteArray; 30 | /** byte range start offset **/ 31 | public var byterange_start_offset : int; 32 | /** byte range offset **/ 33 | public var byterange_end_offset : int; 34 | /** data **/ 35 | public var data : FragmentData; 36 | /** custom tags **/ 37 | public var tag_list : Vector.; 38 | 39 | /** Create the fragment. **/ 40 | public function Fragment(url : String, duration : Number, level : int, seqnum : int, start_time : Number, continuity : int, program_date : Number, decrypt_url : String, decrypt_iv : ByteArray, byterange_start_offset : int, byterange_end_offset : int, tag_list : Vector.) { 41 | this.url = url; 42 | this.duration = duration; 43 | this.seqnum = seqnum; 44 | this.level = level; 45 | this.start_time = start_time; 46 | this.continuity = continuity; 47 | this.program_date = program_date; 48 | this.decrypt_url = decrypt_url; 49 | this.decrypt_iv = decrypt_iv; 50 | this.byterange_start_offset = byterange_start_offset; 51 | this.byterange_end_offset = byterange_end_offset; 52 | this.tag_list = tag_list; 53 | data = new FragmentData(); 54 | // CONFIG::LOGGING { 55 | // Log.info("Frag["+seqnum+"]:duration/start_time,cc="+duration+","+start_time+","+continuity); 56 | // } 57 | }; 58 | 59 | public function get metadataTag() : FLVTag { 60 | var tag : FLVTag = new FLVTag(FLVTag.METADATA, this.data.dts_min, this.data.dts_min, false); 61 | var data : ByteArray = new ByteArray(); 62 | data.objectEncoding = ObjectEncoding.AMF0; 63 | data.writeObject("onHLSFragmentChange"); 64 | data.writeObject(this.level); 65 | data.writeObject(this.seqnum); 66 | data.writeObject(this.continuity); 67 | data.writeObject(this.duration); 68 | data.writeObject(!this.data.video_found); 69 | data.writeObject(this.program_date); 70 | data.writeObject(this.data.video_width); 71 | data.writeObject(this.data.video_height); 72 | data.writeObject(this.data.auto_level); 73 | for each (var custom_tag : String in this.tag_list) { 74 | data.writeObject(custom_tag); 75 | } 76 | tag.push(data, 0, data.length); 77 | return tag; 78 | } 79 | 80 | public function get skippedTag() : FLVTag { 81 | var tag : FLVTag = new FLVTag(FLVTag.METADATA, this.data.pts_start_computed, this.data.pts_start_computed, false); 82 | var data : ByteArray = new ByteArray(); 83 | data.objectEncoding = ObjectEncoding.AMF0; 84 | data.writeObject("onHLSFragmentSkipped"); 85 | data.writeObject(this.level); 86 | data.writeObject(this.seqnum); 87 | data.writeObject(this.duration); 88 | tag.push(data, 0, data.length); 89 | return tag; 90 | } 91 | 92 | public function toString() : String { 93 | return "Fragment (seqnum: " + seqnum + ", start_time:" + start_time + ", duration:" + duration + ")"; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/model/FragmentData.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.model { 5 | import com.fenhongxiang.hls.utils.PTS; 6 | import com.fenhongxiang.hls.utils.AES; 7 | import com.fenhongxiang.hls.flv.FLVTag; 8 | 9 | import flash.utils.ByteArray; 10 | 11 | /** Fragment Data. **/ 12 | public class FragmentData { 13 | /** valid fragment **/ 14 | public var valid : Boolean; 15 | /** fragment byte array **/ 16 | public var bytes : ByteArray; 17 | /** bytes Loaded **/ 18 | public var bytesLoaded : int; 19 | /** AES decryption instance **/ 20 | public var decryptAES : AES; 21 | /** Start PTS of this chunk. **/ 22 | public var pts_start : Number; 23 | /** computed Start PTS of this chunk. **/ 24 | public var pts_start_computed : Number; 25 | /** min/max audio/video PTS/DTS of this chunk. **/ 26 | public var pts_min_audio : Number; 27 | public var pts_max_audio : Number; 28 | public var pts_min_video : Number; 29 | public var pts_max_video : Number; 30 | public var dts_min : Number; 31 | /** audio/video found ? */ 32 | public var audio_found : Boolean; 33 | public var video_found : Boolean; 34 | /** tag related stuff */ 35 | public var metadata_tag_injected : Boolean; 36 | private var tags_pts_min_audio : Number; 37 | private var tags_pts_max_audio : Number; 38 | private var tags_pts_min_video : Number; 39 | private var tags_pts_max_video : Number; 40 | private var tags_audio_found : Boolean; 41 | private var tags_video_found : Boolean; 42 | public var tags : Vector.; 43 | /* video dimension */ 44 | public var video_width : int; 45 | public var video_height : int; 46 | /* is fragment loaded selected by autolevel algo */ 47 | public var auto_level : Boolean; 48 | 49 | /** tag duration */ 50 | private var audio_tag_duration : Number; 51 | private var video_tag_duration : Number; 52 | private var audio_tag_last_dts : Number; 53 | private var video_tag_last_dts : Number; 54 | 55 | /** Fragment metrics **/ 56 | public function FragmentData() { 57 | this.pts_start = NaN; 58 | this.pts_start_computed = NaN; 59 | this.valid = true; 60 | this.video_width = 0; 61 | this.video_height = 0; 62 | }; 63 | 64 | public function appendTags(tags : Vector.) : void { 65 | // Audio PTS/DTS normalization + min/max computation 66 | for each (var tag : FLVTag in tags) { 67 | tag.pts = PTS.normalize(pts_start_computed, tag.pts); 68 | tag.dts = PTS.normalize(pts_start_computed, tag.dts); 69 | dts_min = Math.min(dts_min, tag.dts); 70 | switch( tag.type ) { 71 | case FLVTag.AAC_RAW: 72 | case FLVTag.AAC_HEADER: 73 | case FLVTag.MP3_RAW: 74 | audio_found = true; 75 | tags_audio_found = true; 76 | audio_tag_duration = tag.dts - audio_tag_last_dts; 77 | audio_tag_last_dts = tag.dts; 78 | tags_pts_min_audio = Math.min(tags_pts_min_audio, tag.pts); 79 | tags_pts_max_audio = Math.max(tags_pts_max_audio, tag.pts); 80 | pts_min_audio = Math.min(pts_min_audio, tag.pts); 81 | pts_max_audio = Math.max(pts_max_audio, tag.pts); 82 | break; 83 | case FLVTag.AVC_HEADER: 84 | case FLVTag.AVC_NALU: 85 | video_found = true; 86 | tags_video_found = true; 87 | video_tag_duration = tag.dts - video_tag_last_dts; 88 | video_tag_last_dts = tag.dts; 89 | tags_pts_min_video = Math.min(tags_pts_min_video, tag.pts); 90 | tags_pts_max_video = Math.max(tags_pts_max_video, tag.pts); 91 | pts_min_video = Math.min(pts_min_video, tag.pts); 92 | pts_max_video = Math.max(pts_max_video, tag.pts); 93 | break; 94 | case FLVTag.DISCONTINUITY: 95 | case FLVTag.METADATA: 96 | default: 97 | break; 98 | } 99 | this.tags.push(tag); 100 | } 101 | } 102 | 103 | public function flushTags() : void { 104 | // clean-up tags 105 | tags = new Vector.(); 106 | tags_audio_found = tags_video_found = false; 107 | metadata_tag_injected = false; 108 | pts_min_audio = pts_min_video = dts_min = tags_pts_min_audio = tags_pts_min_video = Number.POSITIVE_INFINITY; 109 | pts_max_audio = pts_max_video = tags_pts_max_audio = tags_pts_max_video = Number.NEGATIVE_INFINITY; 110 | audio_found = video_found = tags_audio_found = tags_video_found = false; 111 | } 112 | 113 | public function shiftTags() : void { 114 | tags = new Vector.(); 115 | if (tags_audio_found) { 116 | tags_pts_min_audio = tags_pts_max_audio; 117 | tags_audio_found = false; 118 | } 119 | if (tags_video_found) { 120 | tags_pts_min_video = tags_pts_max_video; 121 | tags_video_found = false; 122 | } 123 | } 124 | 125 | public function get pts_min() : Number { 126 | if (audio_found) { 127 | return pts_min_audio; 128 | } else { 129 | return pts_min_video; 130 | } 131 | } 132 | 133 | public function get pts_max() : Number { 134 | if (audio_found) { 135 | return pts_max_audio; 136 | } else { 137 | return pts_max_video; 138 | } 139 | } 140 | 141 | public function get tag_duration() : Number { 142 | var duration : Number; 143 | if (audio_found) { 144 | duration = audio_tag_duration; 145 | } else { 146 | duration = video_tag_duration; 147 | } 148 | if(isNaN(duration)) { 149 | duration = 0; 150 | } 151 | return duration; 152 | } 153 | 154 | public function get tag_pts_min() : Number { 155 | if (audio_found) { 156 | return tags_pts_min_audio; 157 | } else { 158 | return tags_pts_min_video; 159 | } 160 | } 161 | 162 | public function get tag_pts_max() : Number { 163 | if (audio_found) { 164 | return tags_pts_max_audio; 165 | } else { 166 | return tags_pts_max_video; 167 | } 168 | } 169 | 170 | public function get tag_pts_start_offset() : Number { 171 | if (tags_audio_found) { 172 | return tags_pts_min_audio - pts_min_audio; 173 | } else { 174 | return tags_pts_min_video - pts_min_video; 175 | } 176 | } 177 | 178 | public function get tag_pts_end_offset() : Number { 179 | if (tags_audio_found) { 180 | return tags_pts_max_audio - pts_min_audio; 181 | } else { 182 | return tags_pts_max_video - pts_min_video; 183 | } 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/model/Stats.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.model { 5 | /** Audio Track identifier **/ 6 | public class Stats { 7 | public var tech : String; 8 | public var levelNb : int; 9 | public var levelStart : int; 10 | public var autoLevelMin : int; 11 | public var autoLevelMax : int; 12 | public var autoLevelAvg : Number; 13 | public var autoLevelLast : int; 14 | public var autoLevelSwitch : int; 15 | public var autoLevelCappingMin : int; 16 | public var autoLevelCappingMax : int; 17 | public var autoLevelCappingLast : int; 18 | public var manualLevelMin : int; 19 | public var manualLevelMax : int; 20 | public var manualLevelLast : int; 21 | public var manualLevelSwitch : int; 22 | public var fragMinKbps : int; 23 | public var fragMaxKbps : int; 24 | public var fragAvgKbps : int; 25 | public var fragMinLatency : int; 26 | public var fragMaxLatency : int; 27 | public var fragAvgLatency : int; 28 | public var fragBuffered : int; 29 | public var fragBufferedBytes : int; 30 | public var fragChangedAuto : int; 31 | public var fragChangedManual : int; 32 | public var fpsDropEvent : int; 33 | public var fpsTotalDroppedFrames : int; 34 | public var fpsDropLevelCappingMin : int; 35 | public var fpsDropSmoothLevelSwitch : int; 36 | public function Stats() { 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/playlist/AltAudioTrack.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.playlist { 5 | public class AltAudioTrack { 6 | public var group_id : String; 7 | public var lang : String; 8 | public var name : String; 9 | public var autoselect : Boolean; 10 | public var default_track : Boolean; 11 | public var url : String; 12 | 13 | /** Create the quality level. **/ 14 | public function AltAudioTrack(alt_group_id : String, alt_lang : String, alt_name : String, alt_autoselect : Boolean, alt_default : Boolean, alt_url : String) { 15 | group_id = alt_group_id; 16 | lang = alt_lang; 17 | name = alt_name; 18 | autoselect = alt_autoselect; 19 | default_track = alt_default; 20 | url = alt_url; 21 | }; 22 | 23 | public function toString() : String { 24 | return "AltAudioTrack url: " + url + " group_id: " + group_id + " lang: " + lang + " name: " + name + ' default: ' + default_track ; 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/playlist/DataUri.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.playlist { 5 | CONFIG::LOGGING { 6 | import com.luojianghong.hls.utils.Log; 7 | } 8 | 9 | /** 10 | * Facilitates extracting information from a data URI. 11 | */ 12 | public class DataUri { 13 | 14 | private static const DATA_PROTOCOL : String = "data:"; 15 | private static const BASE_64 : String = "base64"; 16 | 17 | private var _dataUri : String; 18 | 19 | public function DataUri(dataUri : String) { 20 | _dataUri = dataUri; 21 | } 22 | 23 | /** 24 | * @return Returns the data portion of the data URI if it is able extract the information, 25 | * null otherwise. 26 | */ 27 | public function extractData() : String { 28 | if (_dataUri == null) { 29 | return null; 30 | } 31 | 32 | var base64Index : int = _dataUri.indexOf(BASE_64 + ','); 33 | var dataIndex : int = _dataUri.indexOf(',') + 1; 34 | 35 | if (dataIndex > _dataUri.length) { 36 | return null; 37 | } 38 | 39 | var data : String = _dataUri.substr(dataIndex); 40 | return (base64Index === -1) ? _extractPlainData(data) : _extractBase64Data(data); 41 | } 42 | 43 | /** 44 | * Data URIs support base 64 encoding the data section. 45 | * This is not typically used for plain text files, which includes HLS manifests. 46 | * As such, decoded base 64 data sections is not currently (6/18/14) supported. 47 | * @param data 48 | * @return 49 | */ 50 | private function _extractBase64Data(data : String) : String { 51 | CONFIG::LOGGING { 52 | Log.warn("Base 64 encoded Data URIs are not supported."); 53 | } 54 | return null; 55 | } 56 | 57 | /** 58 | * @param data 59 | * @return The URL decoded data section from the data URI. 60 | */ 61 | private function _extractPlainData(data : String) : String { 62 | var decodedData : String = decodeURIComponent(data); 63 | CONFIG::LOGGING { 64 | Log.debug2("Decoded data from data URI into: " + decodedData); 65 | } 66 | return decodedData; 67 | } 68 | 69 | /** 70 | * @param dataUri 71 | * @return True if the provided string is a data URI, false otherwise. 72 | */ 73 | public static function isDataUri(dataUri : String) : Boolean { 74 | return dataUri != null && dataUri.indexOf(DATA_PROTOCOL) === 0; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/stream/HLSNetStreamClient.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.stream { 5 | import flash.utils.flash_proxy; 6 | import flash.utils.Proxy; 7 | 8 | /** Proxy that allows dispatching internal events fired by Netstream cues to 9 | * internal listeners as well as the traditional client object 10 | */ 11 | public class HLSNetStreamClient extends Proxy { 12 | private var _delegate : Object; 13 | private var _callbacks : Object = new Object(); 14 | 15 | public function HLSNetStreamClient() { 16 | } 17 | 18 | public function set delegate(client : Object) : void { 19 | this._delegate = client; 20 | } 21 | 22 | public function get delegate() : Object { 23 | return this._delegate; 24 | } 25 | 26 | public function registerCallback(name : String, callback : Function) : void { 27 | _callbacks[name] = callback; 28 | } 29 | 30 | override flash_proxy function callProperty(methodName : *, ... args) : * { 31 | var r : * = null; 32 | 33 | if (_callbacks && _callbacks.hasOwnProperty(methodName)) { 34 | r = _callbacks[methodName](args); 35 | } 36 | 37 | if (_delegate && _delegate.hasOwnProperty(methodName)) { 38 | r = _delegate[methodName](args); 39 | } 40 | 41 | return r; 42 | } 43 | 44 | override flash_proxy function getProperty(name : *) : * { 45 | var r : *; 46 | if (_callbacks && _callbacks.hasOwnProperty(name)) { 47 | r = _callbacks[name]; 48 | } 49 | 50 | if (_delegate && _delegate.hasOwnProperty(name)) { 51 | r = _delegate[name]; 52 | } 53 | 54 | return r; 55 | } 56 | 57 | override flash_proxy function hasProperty(name : *) : Boolean { 58 | return (_delegate && _delegate.hasOwnProperty(name)) || _callbacks.hasOwnProperty(name); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/AES.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.utils { 5 | import flash.utils.getTimer; 6 | import flash.display.DisplayObject; 7 | import flash.utils.ByteArray; 8 | import flash.events.Event; 9 | 10 | /** 11 | * Contains Utility functions for AES-128 CBC Decryption 12 | */ 13 | public class AES { 14 | private var _key : FastAESKey; 15 | //private var _keyArray : ByteArray; 16 | private var iv0 : uint; 17 | private var iv1 : uint; 18 | private var iv2 : uint; 19 | private var iv3 : uint; 20 | /* callback function upon decrypt progress */ 21 | private var _progress : Function; 22 | /* callback function upon decrypt complete */ 23 | private var _complete : Function; 24 | /** Byte data to be decrypt **/ 25 | private var _data : ByteArray; 26 | /** read position **/ 27 | private var _readPosition : uint; 28 | /** write position **/ 29 | private var _writePosition : uint; 30 | /** chunk size to avoid blocking **/ 31 | private static const CHUNK_SIZE : uint = 2048; 32 | /** is bytearray full ? **/ 33 | private var _dataComplete : Boolean; 34 | /** display object used for ENTER_FRAME listener */ 35 | private var _displayObject : DisplayObject; 36 | 37 | public function AES(displayObject : DisplayObject, key : ByteArray, iv : ByteArray, notifyprogress : Function, notifycomplete : Function) { 38 | // _keyArray = key; 39 | _key = new FastAESKey(key); 40 | iv.position = 0; 41 | iv0 = iv.readUnsignedInt(); 42 | iv1 = iv.readUnsignedInt(); 43 | iv2 = iv.readUnsignedInt(); 44 | iv3 = iv.readUnsignedInt(); 45 | _data = new ByteArray(); 46 | _dataComplete = false; 47 | _progress = notifyprogress; 48 | _complete = notifycomplete; 49 | _readPosition = 0; 50 | _writePosition = 0; 51 | _displayObject = displayObject; 52 | } 53 | 54 | public function append(data : ByteArray) : void { 55 | // CONFIG::LOGGING { 56 | // Log.info("notify append"); 57 | // } 58 | _data.position = _writePosition; 59 | _data.writeBytes(data); 60 | if (_writePosition == 0) { 61 | _displayObject.addEventListener(Event.ENTER_FRAME, _decryptTimer); 62 | } 63 | _writePosition += data.length; 64 | } 65 | 66 | public function notifycomplete() : void { 67 | // CONFIG::LOGGING { 68 | // Log.info("notify complete"); 69 | // } 70 | _dataComplete = true; 71 | } 72 | 73 | public function cancel() : void { 74 | _displayObject.removeEventListener(Event.ENTER_FRAME, _decryptTimer); 75 | } 76 | 77 | private function _decryptTimer(e : Event) : void { 78 | var start_time : int = getTimer(); 79 | var decrypted : Boolean; 80 | do { 81 | decrypted = _decryptChunk(); 82 | // dont spend more than 20 ms in the decrypt timer to avoid blocking/freezing video 83 | } while (decrypted && (getTimer() - start_time) < 20); 84 | } 85 | 86 | /** decrypt a small chunk of packets each time to avoid blocking **/ 87 | private function _decryptChunk() : Boolean { 88 | _data.position = _readPosition; 89 | var decryptdata : ByteArray; 90 | if (_data.bytesAvailable) { 91 | if (_data.bytesAvailable <= CHUNK_SIZE) { 92 | if (_dataComplete) { 93 | // CONFIG::LOGGING { 94 | // Log.info("data complete, last chunk"); 95 | // } 96 | _readPosition += _data.bytesAvailable; 97 | decryptdata = _decryptCBC(_data, _data.bytesAvailable); 98 | unpad(decryptdata); 99 | } else { 100 | // data not complete, and available data less than chunk size, return 101 | return false; 102 | } 103 | } else { 104 | _readPosition += CHUNK_SIZE; 105 | decryptdata = _decryptCBC(_data, CHUNK_SIZE); 106 | } 107 | _progress(decryptdata); 108 | return true; 109 | } else { 110 | if (_dataComplete) { 111 | CONFIG::LOGGING { 112 | Log.debug("AES:data+decrypt completed, callback"); 113 | } 114 | // callback 115 | _complete(); 116 | _displayObject.removeEventListener(Event.ENTER_FRAME, _decryptTimer); 117 | } 118 | return false; 119 | } 120 | } 121 | 122 | /* Cypher Block Chaining Decryption, refer to 123 | * http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_ 124 | * for algorithm description 125 | */ 126 | private function _decryptCBC(crypt : ByteArray, len : uint) : ByteArray { 127 | var src : Vector. = new Vector.(4); 128 | var dst : Vector. = new Vector.(4); 129 | var decrypt : ByteArray = new ByteArray(); 130 | decrypt.length = len; 131 | 132 | for (var i : uint = 0; i < len / 16; i++) { 133 | // read src byte array 134 | src[0] = crypt.readUnsignedInt(); 135 | src[1] = crypt.readUnsignedInt(); 136 | src[2] = crypt.readUnsignedInt(); 137 | src[3] = crypt.readUnsignedInt(); 138 | 139 | // AES decrypt src vector into dst vector 140 | _key.decrypt128(src, dst); 141 | 142 | // CBC : write output = XOR(decrypted,IV) 143 | decrypt.writeUnsignedInt(dst[0] ^ iv0); 144 | decrypt.writeUnsignedInt(dst[1] ^ iv1); 145 | decrypt.writeUnsignedInt(dst[2] ^ iv2); 146 | decrypt.writeUnsignedInt(dst[3] ^ iv3); 147 | 148 | // CBC : next IV = (input) 149 | iv0 = src[0]; 150 | iv1 = src[1]; 151 | iv2 = src[2]; 152 | iv3 = src[3]; 153 | } 154 | decrypt.position = 0; 155 | return decrypt; 156 | } 157 | 158 | public function unpad(a : ByteArray) : void { 159 | var c : uint = a.length % 16; 160 | if (c != 0) throw new Error("PKCS#5::unpad: ByteArray.length isn't a multiple of the blockSize"); 161 | c = a[a.length - 1]; 162 | for (var i : uint = c; i > 0; i--) { 163 | var v : uint = a[a.length - 1]; 164 | a.length--; 165 | if (c != v) throw new Error("PKCS#5:unpad: Invalid padding value. expected [" + c + "], found [" + v + "]"); 166 | } 167 | } 168 | 169 | public function destroy() : void { 170 | _key.dispose(); 171 | // _key = null; 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/DateUtil.as: -------------------------------------------------------------------------------- 1 | /** 2 | * DateUtil 3 | * 4 | * inspired by https://code.google.com/p/as3corelib/source/browse/trunk/src/com/adobe/utils/DateUtil.as#531 5 | */ 6 | package com.fenhongxiang.hls.utils { 7 | 8 | public class DateUtil { 9 | public static function parseW3CDTF(str:String):Date 10 | { 11 | var finalDate:Date; 12 | try 13 | { 14 | var dateStr:String = str.substring(0, str.indexOf("T")); 15 | var timeStr:String = str.substring(str.indexOf("T")+1, str.length); 16 | var dateArr:Array = dateStr.split("-"); 17 | var year:Number = Number(dateArr.shift()); 18 | var month:Number = Number(dateArr.shift()); 19 | var date:Number = Number(dateArr.shift()); 20 | 21 | var multiplier:Number; 22 | var offsetHours:Number; 23 | var offsetMinutes:Number; 24 | var offsetStr:String; 25 | 26 | if (timeStr.indexOf("Z") != -1) 27 | { 28 | multiplier = 1; 29 | offsetHours = 0; 30 | offsetMinutes = 0; 31 | timeStr = timeStr.replace("Z", ""); 32 | } 33 | else if (timeStr.indexOf("+") != -1) 34 | { 35 | multiplier = 1; 36 | offsetStr = timeStr.substring(timeStr.indexOf("+")+1, timeStr.length); 37 | offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":"))); 38 | offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length)); 39 | timeStr = timeStr.substring(0, timeStr.indexOf("+")); 40 | } 41 | else // offset is - 42 | { 43 | multiplier = -1; 44 | offsetStr = timeStr.substring(timeStr.indexOf("-")+1, timeStr.length); 45 | offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":"))); 46 | offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length)); 47 | timeStr = timeStr.substring(0, timeStr.indexOf("-")); 48 | } 49 | var timeArr:Array = timeStr.split(":"); 50 | var hour:Number = Number(timeArr.shift()); 51 | var minutes:Number = Number(timeArr.shift()); 52 | var secondsArr:Array = (timeArr.length > 0) ? String(timeArr.shift()).split(".") : null; 53 | var seconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0; 54 | //var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0; 55 | 56 | var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? 1000*parseFloat("0." + secondsArr.shift()) : 0; 57 | var utc:Number = Date.UTC(year, month-1, date, hour, minutes, seconds, milliseconds); 58 | var offset:Number = (((offsetHours * 3600000) + (offsetMinutes * 60000)) * multiplier); 59 | finalDate = new Date(utc - offset); 60 | 61 | if (finalDate.toString() == "Invalid Date") 62 | { 63 | throw new Error("This date does not conform to W3CDTF."); 64 | } 65 | } 66 | catch (e:Error) 67 | { 68 | var eStr:String = "Unable to parse the string [" +str+ "] into a date. "; 69 | eStr += "The internal error was: " + e.toString(); 70 | throw new Error(eStr); 71 | } 72 | return finalDate; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/Hex.as: -------------------------------------------------------------------------------- 1 | /** 2 | * Hex 3 | * 4 | * Utility class to convert Hex strings to ByteArray or String types. 5 | * Copyright (c) 2007 Henri Torgemane 6 | * 7 | * See LICENSE.txt for full license information. 8 | */ 9 | package com.fenhongxiang.hls.utils { 10 | import flash.utils.ByteArray; 11 | 12 | public class Hex { 13 | /** 14 | * Generates byte-array from given hexadecimal string 15 | * 16 | * Supports straight and colon-laced hex (that means 23:03:0e:f0, but *NOT* 23:3:e:f0) 17 | * The first nibble (hex digit) may be omitted. 18 | * Any whitespace characters are ignored. 19 | */ 20 | public static function toArray(hex : String) : ByteArray { 21 | hex = hex.replace(/^0x|\s|:/gm, ''); 22 | var a : ByteArray = new ByteArray; 23 | var len : uint = hex.length; 24 | if ((len & 1) == 1) hex = "0" + hex; 25 | for (var i : uint = 0; i < len; i += 2) { 26 | a[i / 2] = parseInt(hex.substr(i, 2), 16); 27 | } 28 | return a; 29 | } 30 | 31 | /** 32 | * Generates lowercase hexadecimal string from given byte-array 33 | */ 34 | public static function fromArray(array : ByteArray, colons : Boolean = false) : String { 35 | var s : String = ""; 36 | var len : uint = array.length; 37 | for (var i : uint = 0; i < len; i++) { 38 | s += ("0" + array[i].toString(16)).substr(-2, 2); 39 | if (colons) { 40 | if (i < len - 1) s += ":"; 41 | } 42 | } 43 | return s; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/JSURLLoader.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.utils { 5 | import com.fenhongxiang.hls.HLS; 6 | import flash.events.Event; 7 | import flash.events.IOErrorEvent; 8 | import flash.events.ProgressEvent; 9 | import flash.events.TimerEvent; 10 | import flash.external.ExternalInterface; 11 | import flash.net.URLRequest; 12 | import flash.net.URLLoader; 13 | 14 | CONFIG::LOGGING { 15 | } 16 | 17 | // Playlist Loader 18 | public dynamic class JSURLLoader extends URLLoader { 19 | private var _resource : String = new String(); 20 | /* callback names */ 21 | private var _callbackLoaded : String; 22 | private var _callbackFailure : String; 23 | private static var _instanceCount : int = 0; 24 | /** JS callbacks prefix */ 25 | protected static var _callbackName : String = 'JSLoaderPlaylist'; 26 | 27 | public function JSURLLoader() { 28 | ExternalInterface.marshallExceptions = true; 29 | super(); 30 | 31 | // Connect calls to JS. 32 | if (ExternalInterface.available) { 33 | CONFIG::LOGGING { 34 | Log.debug("add callback textLoaded, id:" + _instanceCount); 35 | } 36 | _callbackLoaded = "textLoaded" + _instanceCount; 37 | _callbackFailure = "textLoadingError" + _instanceCount; 38 | // dynamically register callbacks 39 | this[_callbackLoaded] = function(res:String): void { resourceLoaded(res)}; 40 | this[_callbackFailure] = function() : void { resourceLoadingError()}; 41 | ExternalInterface.addCallback(_callbackLoaded, this[_callbackLoaded]); 42 | ExternalInterface.addCallback(_callbackFailure, this[_callbackFailure]); 43 | _instanceCount++; 44 | } 45 | } 46 | 47 | public static function set externalCallback(callbackName: String) : void { 48 | _callbackName = callbackName; 49 | } 50 | 51 | protected function _trigger(event : String, ...args) : void { 52 | if (ExternalInterface.available) { 53 | ExternalInterface.call(_callbackName, event, args); 54 | } 55 | } 56 | 57 | override public function close() : void { 58 | if (ExternalInterface.available) { 59 | _trigger('abortPlaylist', ExternalInterface.objectID); 60 | } else { 61 | super.close(); 62 | } 63 | } 64 | 65 | override public function load(request : URLRequest) : void { 66 | CONFIG::LOGGING { 67 | Log.debug("JSURLLoader.load:" + request.url); 68 | } 69 | bytesLoaded = bytesTotal = 0; 70 | data = null; 71 | if (ExternalInterface.available) { 72 | _trigger('requestPlaylist', ExternalInterface.objectID, request.url, _callbackLoaded, _callbackFailure); 73 | this.dispatchEvent(new Event(Event.OPEN)); 74 | } else { 75 | super.load(request); 76 | } 77 | } 78 | 79 | protected function resourceLoaded(resource : String) : void { 80 | CONFIG::LOGGING { 81 | Log.debug("resourceLoaded"); 82 | } 83 | data = resource; 84 | bytesLoaded = bytesTotal = resource.length; 85 | this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, bytesLoaded, bytesTotal)); 86 | this.dispatchEvent(new Event(Event.COMPLETE)); 87 | } 88 | 89 | protected function resourceLoadingError() : void { 90 | CONFIG::LOGGING { 91 | Log.debug("resourceLoadingError"); 92 | } 93 | this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR)); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/JSURLStream.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.utils { 5 | import by.blooddy.crypto.Base64; 6 | import flash.events.Event; 7 | import flash.events.IOErrorEvent; 8 | import flash.events.ProgressEvent; 9 | import flash.events.TimerEvent; 10 | import flash.external.ExternalInterface; 11 | import flash.net.URLRequest; 12 | import flash.net.URLStream; 13 | import flash.utils.ByteArray; 14 | import flash.utils.getTimer; 15 | import flash.utils.Timer; 16 | 17 | CONFIG::LOGGING { 18 | } 19 | 20 | // Fragment Loader 21 | public dynamic class JSURLStream extends URLStream { 22 | private var _connected : Boolean; 23 | private var _resource : ByteArray = new ByteArray(); 24 | /** Timer for decode packets **/ 25 | private var _timer : Timer; 26 | /** base64 read position **/ 27 | private var _readPosition : uint; 28 | /** final length **/ 29 | private var _finalLength : uint; 30 | /** read position **/ 31 | private var _base64Resource : String; 32 | /* callback names */ 33 | private var _callbackLoaded : String; 34 | private var _callbackFailure : String; 35 | /** chunk size to avoid blocking **/ 36 | private static const CHUNK_SIZE : uint = 65536; 37 | private static var _instanceCount : int = 0; 38 | /** JS callbacks prefix */ 39 | protected static var _callbackName : String = 'JSLoaderFragment'; 40 | 41 | public function JSURLStream() { 42 | addEventListener(Event.OPEN, onOpen); 43 | ExternalInterface.marshallExceptions = true; 44 | super(); 45 | 46 | // Connect calls to JS. 47 | if (ExternalInterface.available) { 48 | CONFIG::LOGGING { 49 | Log.debug("add callback resourceLoaded, id:" + _instanceCount); 50 | } 51 | _callbackLoaded = "resourceLoaded" + _instanceCount; 52 | _callbackFailure = "resourceLoadingError" + _instanceCount; 53 | // dynamically register callbacks 54 | this[_callbackLoaded] = function(res:String,len:uint): void { resourceLoaded(res,len)}; 55 | this[_callbackFailure] = function() : void { resourceLoadingError()}; 56 | ExternalInterface.addCallback(_callbackLoaded, this[_callbackLoaded]); 57 | ExternalInterface.addCallback(_callbackFailure, this[_callbackFailure]); 58 | _instanceCount++; 59 | } 60 | } 61 | 62 | public static function set externalCallback(callbackName: String) : void { 63 | _callbackName = callbackName; 64 | } 65 | 66 | protected function _trigger(event : String, ...args) : void { 67 | if (ExternalInterface.available) { 68 | ExternalInterface.call(_callbackName, event, args); 69 | } 70 | } 71 | 72 | override public function get connected() : Boolean { 73 | return _connected; 74 | } 75 | 76 | override public function get bytesAvailable() : uint { 77 | return _resource.bytesAvailable; 78 | } 79 | 80 | override public function readByte() : int { 81 | return _resource.readByte(); 82 | } 83 | 84 | override public function readUnsignedShort() : uint { 85 | return _resource.readUnsignedShort(); 86 | } 87 | 88 | override public function readBytes(bytes : ByteArray, offset : uint = 0, length : uint = 0) : void { 89 | _resource.readBytes(bytes, offset, length); 90 | } 91 | 92 | override public function close() : void { 93 | if(_timer) { 94 | _timer.stop(); 95 | } 96 | if (ExternalInterface.available) { 97 | _trigger('abortFragment', ExternalInterface.objectID); 98 | } else { 99 | super.close(); 100 | } 101 | } 102 | 103 | override public function load(request : URLRequest) : void { 104 | CONFIG::LOGGING { 105 | Log.debug("JSURLStream.load:" + request.url); 106 | } 107 | if (ExternalInterface.available) { 108 | _trigger('requestFragment', ExternalInterface.objectID, request.url, _callbackLoaded, _callbackFailure); 109 | this.dispatchEvent(new Event(Event.OPEN)); 110 | } else { 111 | super.load(request); 112 | } 113 | } 114 | 115 | private function onOpen(event : Event) : void { 116 | _connected = true; 117 | } 118 | 119 | protected function resourceLoaded(base64Resource : String, len : uint) : void { 120 | CONFIG::LOGGING { 121 | Log.debug("resourceLoaded"); 122 | } 123 | _resource = new ByteArray(); 124 | _readPosition = 0; 125 | _finalLength = len; 126 | _timer = new Timer(20, 0); 127 | _timer.addEventListener(TimerEvent.TIMER, _decodeData); 128 | _timer.start(); 129 | _base64Resource = base64Resource; 130 | } 131 | 132 | protected function resourceLoadingError() : void { 133 | CONFIG::LOGGING { 134 | Log.debug("resourceLoadingError"); 135 | } 136 | if(_timer) { 137 | _timer.stop(); 138 | } 139 | this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR)); 140 | } 141 | 142 | protected function resourceLoadingSuccess() : void { 143 | CONFIG::LOGGING { 144 | Log.debug("resourceLoaded and decoded"); 145 | } 146 | _timer.stop(); 147 | this.dispatchEvent(new Event(Event.COMPLETE)); 148 | } 149 | 150 | /** decrypt a small chunk of packets each time to avoid blocking **/ 151 | private function _decodeData(e : Event) : void { 152 | var startTime : int = getTimer(); 153 | var decodeCompleted : Boolean = false; 154 | // dont spend more than 20ms base64 decoding to avoid fps drop 155 | while ((!decodeCompleted) && ((getTimer() - startTime) < 10)) { 156 | var startPos : uint = _readPosition,endPos : uint; 157 | if (_base64Resource.length <= _readPosition + CHUNK_SIZE) { 158 | endPos = _base64Resource.length; 159 | decodeCompleted = true; 160 | } else { 161 | endPos = _readPosition + CHUNK_SIZE; 162 | _readPosition = endPos; 163 | } 164 | var tmpString : String = _base64Resource.substring(startPos, endPos); 165 | var savePosition : uint = _resource.position; 166 | try { 167 | _resource.position = _resource.length; 168 | _resource.writeBytes(Base64.decode(tmpString)); 169 | _resource.position = savePosition; 170 | } catch (error:Error) { 171 | resourceLoadingError(); 172 | } 173 | } 174 | this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, _resource.length, _finalLength)); 175 | if (decodeCompleted) { 176 | resourceLoadingSuccess(); 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/Log.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.utils { 5 | import flash.external.ExternalInterface; 6 | 7 | import com.fenhongxiang.hls.HLSSettings; 8 | 9 | /** Class that sends log messages to browser console. **/ 10 | public class Log { 11 | private static const LEVEL_INFO : String = "INFO:"; 12 | private static const LEVEL_DEBUG : String = "DEBUG:"; 13 | private static const LEVEL_WARN : String = "WARN:"; 14 | private static const LEVEL_ERROR : String = "ERROR:"; 15 | 16 | public static function info(message : *) : void { 17 | if (HLSSettings.logInfo) 18 | outputlog(LEVEL_INFO, String(message)); 19 | }; 20 | 21 | public static function debug(message : *) : void { 22 | if (HLSSettings.logDebug) 23 | outputlog(LEVEL_DEBUG, String(message)); 24 | }; 25 | 26 | public static function debug2(message : *) : void { 27 | if (HLSSettings.logDebug2) 28 | outputlog(LEVEL_DEBUG, String(message)); 29 | }; 30 | 31 | public static function warn(message : *) : void { 32 | if (HLSSettings.logWarn) 33 | outputlog(LEVEL_WARN, String(message)); 34 | }; 35 | 36 | public static function error(message : *) : void { 37 | if (HLSSettings.logError) 38 | outputlog(LEVEL_ERROR, String(message)); 39 | }; 40 | 41 | /** Log a message to the console. **/ 42 | private static function outputlog(level : String, message : String) : void { 43 | 44 | trace(level + message); 45 | 46 | if (ExternalInterface.available) 47 | ExternalInterface.call('console.log', level + message); 48 | else trace(level + message); 49 | } 50 | }; 51 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/PTS.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.utils { 5 | public class PTS { 6 | /* find PTS value nearest a given reference PTS value 7 | * 8 | * PTS retrieved from demux are within a range of 9 | * (+/-) 2^32/90 - 1 = (+/-) 47721858 10 | * when reaching upper limit, PTS will loop to lower limit 11 | * this cause some issues with fragment duration calculation 12 | * this method will normalize a given PTS value and output a result 13 | * that is closest to provided PTS reference value. 14 | * i.e it could output values bigger than the (+/-) 2^32/90. 15 | * this will avoid PTS looping issues. 16 | */ 17 | public static function normalize(reference : Number, value : Number) : Number { 18 | var offset : Number; 19 | if (reference < value) { 20 | // - 2^33/90 21 | offset = -95443717; 22 | } else { 23 | // + 2^33/90 24 | offset = 95443717; 25 | } 26 | // 2^32 / 90 27 | while (!isNaN(reference) && (Math.abs(value - reference) > 47721858)) { 28 | value += offset; 29 | } 30 | return value; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/Params2Settings.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.utils { 5 | import com.fenhongxiang.hls.HLSSettings; 6 | 7 | import flash.utils.describeType; 8 | import flash.utils.Dictionary; 9 | import flash.utils.getDefinitionByName; 10 | import flash.utils.getQualifiedClassName; 11 | 12 | /** 13 | * Params2Settings is an helper class that holds every legal external params names 14 | * which can be used to customize HLSSettings and maps them to the relevant HLSSettings values 15 | */ 16 | public class Params2Settings { 17 | /** 18 | * HLSSettings <-> params maping 19 | */ 20 | private static var _paramMap : Dictionary = new Dictionary(); 21 | 22 | // static initializer 23 | { 24 | _initParams(); 25 | } 26 | 27 | 28 | /* build map between param name and HLSSettings property 29 | this is done by enumerating properties : http://stackoverflow.com/questions/13294997/as3-iterating-through-class-variables 30 | */ 31 | private static function _initParams() : void { 32 | var description:XML = describeType(HLSSettings); 33 | var variables:XMLList = description..variable; 34 | for each(var variable:XML in variables) { 35 | var name : String = variable.@name; 36 | var param : String; 37 | if(name.indexOf("log") == 0) { 38 | // loggers params don't need prefix 39 | param = name.substr(3); 40 | } else { 41 | param = name; 42 | } 43 | // for historical (bad ?) reasons, param names are lowercase 44 | param = param.toLowerCase(); 45 | _paramMap[param] = name; 46 | } 47 | } 48 | 49 | public static function set(key : String, value : Object) : void { 50 | var param : String = _paramMap[key]; 51 | if (param) { 52 | // try to assign value with proper object type 53 | try { 54 | var cName : String = getQualifiedClassName(HLSSettings[param]); 55 | // AS3 bug: "getDefinitionByName" considers var value, not type, and wrongly (e.g. 3.0 >> "int"; 3.1 >> "Number"). 56 | var c : Class = cName === "int" ? Number : getDefinitionByName(cName) as Class; 57 | // get HLSSetting type 58 | HLSSettings[param] = c(value); 59 | CONFIG::LOGGING { 60 | Log.info("HLSSettings." + param + " = " + HLSSettings[param]); 61 | } 62 | } catch(error : Error) { 63 | CONFIG::LOGGING { 64 | Log.warn("Can't set HLSSettings." + param); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/hls/utils/ScaleVideo.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.hls.utils { 5 | import flash.geom.Rectangle; 6 | 7 | public class ScaleVideo { 8 | public static function resizeRectangle(videoWidth : int, videoHeight : int, containerWidth : int, containerHeight : int) : Rectangle { 9 | var rect : Rectangle = new Rectangle(); 10 | var xscale : Number = containerWidth / videoWidth; 11 | var yscale : Number = containerHeight / videoHeight; 12 | if (xscale >= yscale) { 13 | rect.width = Math.min(videoWidth * yscale, containerWidth); 14 | rect.height = videoHeight * yscale; 15 | } else { 16 | rect.width = Math.min(videoWidth * xscale, containerWidth); 17 | rect.height = videoHeight * xscale; 18 | } 19 | rect.width = Math.ceil(rect.width); 20 | rect.height = Math.ceil(rect.height); 21 | rect.x = Math.round((containerWidth - rect.width) / 2); 22 | rect.y = Math.round((containerHeight - rect.height) / 2); 23 | CONFIG::LOGGING { 24 | Log.debug("width:" + rect.width); 25 | Log.debug("height:" + rect.height); 26 | Log.debug("x:" + rect.x); 27 | Log.debug("y:" + rect.y); 28 | } 29 | return rect; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/srt/SRTController.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.srt 10 | { 11 | import flash.geom.Rectangle; 12 | import flash.text.TextField; 13 | public final class SRTController 14 | { 15 | public function SRTController(url:String) 16 | { 17 | load(url); 18 | } 19 | 20 | private var _srtData:Vector.; 21 | private var currentIndex:int = 0; 22 | private var currentTime:Rectangle = new Rectangle(0, 0, 0, 0); 23 | private var srtLoader:SRTLoader; 24 | 25 | public function load(path:String):void 26 | { 27 | if (srtLoader == null) 28 | { 29 | srtLoader = new SRTLoader(); 30 | srtLoader.addEventListener(SRTLoaderEvent.LOADED, onSRTFileLoaded, false, 0, true); 31 | } 32 | 33 | srtLoader.load(path); 34 | } 35 | 36 | /** 37 | * 38 | * @param time 时间点 39 | * @param txt TextField对象引用 40 | * @param txtColor 文本颜色(只支持十六进制颜色 (#FFFFFF) 值) 41 | * @param fontSize 字体大小 。您可以使用绝对像素大小(如 16 或 18),也可以使用相对点值(如 +2 或 -4) 42 | * 43 | */ 44 | public function renderSRT(time:Number, txt:TextField, txtColor:String = "#FFFFFF", fontSize:int = 12):void 45 | { 46 | if (txt) 47 | { 48 | if (!currentTime || !currentTime.contains(time, 0)) 49 | { 50 | txt.htmlText = "

" + getContentByTime(time) + "

"; 51 | } 52 | } 53 | } 54 | 55 | private function onSRTFileLoaded(e:SRTLoaderEvent):void 56 | { 57 | this._srtData = e.data; 58 | } 59 | 60 | private function getContentByTime(time:Number):String 61 | { 62 | var str:String = ""; 63 | 64 | if (_srtData && _srtData.length > 0) 65 | { 66 | 67 | if (time >= currentTime.x) 68 | { 69 | //先顺着上次找到的位置往下找 70 | var len:int = _srtData.length; 71 | for (var i:int = currentIndex; i < len; i++) 72 | { 73 | if (_srtData[i].contains(time)) 74 | { 75 | currentIndex = i; 76 | currentTime = _srtData[i].time; 77 | str = _srtData[i].data; 78 | break; 79 | } 80 | } 81 | } 82 | else 83 | { 84 | //顺着index往前找,直到数组开始位置 85 | if (!str) 86 | { 87 | for (var j:int = currentIndex; j >= 0; j--) 88 | { 89 | if (_srtData[j].contains(time)) 90 | { 91 | currentIndex = j; 92 | currentTime = _srtData[j].time; 93 | str = _srtData[j].data; 94 | break; 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | return str; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/srt/SRTLoader.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.srt 10 | { 11 | import flash.errors.EOFError; 12 | import flash.events.Event; 13 | import flash.events.EventDispatcher; 14 | import flash.events.IOErrorEvent; 15 | import flash.events.SecurityErrorEvent; 16 | import flash.net.URLRequest; 17 | import flash.net.URLStream; 18 | 19 | public class SRTLoader extends EventDispatcher 20 | { 21 | public function SRTLoader() 22 | { 23 | } 24 | 25 | private var _loader:URLStream; 26 | 27 | public function load(url:String):void 28 | { 29 | _loader = getLoaderInstance(); 30 | 31 | try 32 | { 33 | _loader.load(new URLRequest(url)); 34 | } 35 | catch (e:*) 36 | { 37 | errorHandler(); 38 | } 39 | } 40 | 41 | //----------------------------event handlers-------------------------------------------------// 42 | private function errorHandler(e:* = null):void 43 | { 44 | removeListeners(); 45 | dispatchEvent(new SRTLoaderEvent(SRTLoaderEvent.ERROR, null, true)); 46 | } 47 | 48 | private function loadedHandler(e:Event):void 49 | { 50 | var srtStr:String; 51 | 52 | //防止中文乱码 53 | try 54 | { 55 | srtStr = _loader.readMultiByte(_loader.bytesAvailable, "utf-8"); 56 | } 57 | catch (e:EOFError) 58 | { 59 | 60 | } 61 | 62 | var srtData:Vector. = parseSRT(srtStr); 63 | 64 | if (srtData) 65 | { 66 | removeListeners(); 67 | dispatchEvent(new SRTLoaderEvent(SRTLoaderEvent.LOADED, srtData, true)); 68 | } 69 | else 70 | { 71 | errorHandler(); 72 | } 73 | } 74 | 75 | //----------------------------tool function-------------------------------------------------// 76 | 77 | private function parseSRT(src:String):Vector. 78 | { 79 | var srtDataArr:Vector. = new Vector.(); 80 | 81 | if (src && src != "") 82 | { 83 | var srtArr:Array = src.replace(/\r/g, '').split("\n"); 84 | 85 | if (srtArr != null) 86 | { 87 | var len:int = srtArr.length; 88 | 89 | var currentData:SRTModel = new SRTModel("00", ""); 90 | var tagFound:Boolean = false; 91 | var currentLineStr:String; 92 | 93 | for (var i:int = 0; i < len; i++) 94 | { 95 | currentLineStr = srtArr[i]; 96 | 97 | if (currentLineStr.match(SRTModel.TIME_PATTERN)) 98 | { 99 | tagFound = true; 100 | currentData = new SRTModel(srtArr[i], null); 101 | } 102 | else 103 | { 104 | //空行是下一个字幕的开始 105 | if (currentLineStr == "") 106 | { 107 | tagFound = false; 108 | srtDataArr.push(currentData); 109 | } 110 | else 111 | { 112 | if (tagFound) 113 | currentData.appendContent(currentLineStr); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | return srtDataArr; 121 | } 122 | 123 | private function removeListeners():void 124 | { 125 | if (_loader != null) 126 | { 127 | _loader.removeEventListener(Event.COMPLETE, loadedHandler); 128 | _loader.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler); 129 | _loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler); 130 | } 131 | } 132 | 133 | private function getLoaderInstance():URLStream 134 | { 135 | if (_loader == null) 136 | { 137 | _loader = new URLStream(); 138 | _loader.addEventListener(Event.COMPLETE, loadedHandler, false, 0, true); 139 | _loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler, false, 0, true); 140 | _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler, false, 0, true); 141 | } 142 | 143 | try 144 | { 145 | _loader.close() 146 | } 147 | catch(e:Error) 148 | { 149 | 150 | } 151 | 152 | return _loader; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/srt/SRTLoaderEvent.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | package com.fenhongxiang.srt 9 | { 10 | import flash.events.Event; 11 | public class SRTLoaderEvent extends Event 12 | { 13 | public static const ERROR:String = "字幕加载失败"; 14 | public static const LOADED:String = "字幕加载完成"; 15 | 16 | public function SRTLoaderEvent(type:String, data:*, bubbles:Boolean = false, cancelable:Boolean = false) 17 | { 18 | _data = data; 19 | super(type, bubbles, cancelable); 20 | } 21 | 22 | private var _data:*; 23 | 24 | public function get data():* 25 | { 26 | return _data; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/srt/SRTModel.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.srt 10 | { 11 | import flash.geom.Rectangle; 12 | public class SRTModel 13 | { 14 | public static const TIME_PATTERN:RegExp = new RegExp('[0-9][0-9]:[0-9][0-9].[0-9][0-9],[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9].[0-9][0-9],[0-9][0-9][0-9]', 'i'); 15 | 16 | /** 17 | * 18 | * @param timeString 时间格式为 00:00:00,000 --> 00:00:12,700的字符串 19 | * @param data 字幕文本内容 20 | * 21 | * */ 22 | public function SRTModel(time:String=null, data:String=null) 23 | { 24 | _time = time; 25 | _data = data; 26 | } 27 | 28 | private var _data:String; 29 | private var _time:String; 30 | private var _timeRange:Rectangle = null; 31 | 32 | public function appendContent(str:String):void 33 | { 34 | _data = "

"+(_data == null ? "":_data) + str + "

"; 35 | // _data = (_data == null ? "":_data) + str; 36 | } 37 | 38 | public function contains(time:Number):Boolean 39 | { 40 | //还未初始化 41 | if (_timeRange == null) 42 | { 43 | timeRange = _time; 44 | } 45 | 46 | return _timeRange != null && _timeRange.contains(time, 0); 47 | } 48 | 49 | public function get data():String 50 | { 51 | return _data; 52 | } 53 | 54 | public function get time():Rectangle 55 | { 56 | //还未初始化 57 | if (_timeRange == null) 58 | { 59 | timeRange = _time; 60 | } 61 | 62 | return _timeRange; 63 | } 64 | 65 | /** 66 | * 时间点的秒数形式 67 | * 68 | * @param value 格式为:00:00:20,000 69 | * 70 | * */ 71 | private function getTotalSeconds(value:String):Number 72 | { 73 | if (value) 74 | { 75 | var timeArr:Array = value.split(':'); 76 | var secPart:Array = String(timeArr[2]).split(","); 77 | 78 | return parseFloat(timeArr[0])*3600 + parseFloat(timeArr[1])*60 + parseFloat(secPart[0]) + parseFloat(secPart[1])/1000; 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | /** 85 | * 时间格式为 00:00:20.000 --> 00:00:25.000的字符串 86 | * 87 | * */ 88 | private function set timeRange(str:String):void 89 | { 90 | _timeRange = new Rectangle(0, 0, 0, 1); 91 | 92 | if (str != null && str != "") 93 | { 94 | var timeArray:Array = str.match(TIME_PATTERN); 95 | var timeStr:String; 96 | 97 | if (timeArray != null && timeArray.length > 0) 98 | { 99 | timeStr =timeArray[0]; 100 | timeArray = str.split('-->'); 101 | 102 | _timeRange.x = getTotalSeconds(timeArray[0]); 103 | _timeRange.width = getTotalSeconds(timeArray[1]) - _timeRange.x; 104 | } 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/srt/SRTUtil.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.srt 10 | { 11 | import flash.events.IOErrorEvent; 12 | import flash.events.ProgressEvent; 13 | import flash.events.SecurityErrorEvent; 14 | import flash.net.URLRequest; 15 | import flash.net.URLStream; 16 | import flash.utils.Dictionary; 17 | 18 | public final class SRTUtil 19 | { 20 | public function SRTUtil() 21 | { 22 | 23 | } 24 | 25 | private var urlCallBack:Function; 26 | private var urlDic:Dictionary = new Dictionary(); 27 | private var urlList:Array = []; 28 | private var urlReq:URLRequest; 29 | private var urlStream:URLStream; 30 | 31 | /** 32 | * 33 | * @param urls 包含测试连接的数组。如:['www.a.com/a.srt','www.b.com/b.srt'] 34 | * @param callBack 测试完成后,结果回调函数。格式为 35 | * function a(data:Dictionary):void 36 | * { 37 | * //判断测试的地址是否可用 38 | * //访问 data[测试地址](ture或者false) 39 | * } 40 | * 41 | */ 42 | public function testURLs(urls:Array, callBack:Function):void 43 | { 44 | urlList = urls; 45 | 46 | if (urlList) 47 | { 48 | for each (var url:* in urls) 49 | { 50 | urlDic[url] = false; 51 | } 52 | 53 | testURL(urlList.shift()); 54 | } 55 | else 56 | { 57 | if (urlCallBack) 58 | { 59 | urlCallBack(null); 60 | } 61 | } 62 | } 63 | 64 | private function onErrorHandler(e:*):void 65 | { 66 | urlDic[urlReq.url] = false; 67 | testURL(urlList.shift()); 68 | } 69 | 70 | //-----------------------------事件处理函数------------------------------------------// 71 | private function onProgressHandler(e:ProgressEvent):void 72 | { 73 | if (e.bytesLoaded > 0) 74 | { 75 | urlDic[urlReq.url] = true; 76 | urlStream.close(); 77 | testURL(urlList.shift()); 78 | } 79 | } 80 | 81 | private function testURL(url:String):void 82 | { 83 | if (url) 84 | { 85 | urlReq = new URLRequest(url); 86 | 87 | urlStream = new URLStream(); 88 | urlStream.addEventListener(ProgressEvent.PROGRESS, onProgressHandler); 89 | urlStream.addEventListener(IOErrorEvent.IO_ERROR, onErrorHandler); 90 | urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onErrorHandler); 91 | urlStream.load(urlReq); 92 | } 93 | else 94 | { 95 | if (urlList.length == 0 && urlCallBack) 96 | { 97 | urlCallBack(urlDic); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/util/HtmlUtil.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.util 10 | { 11 | import flash.external.ExternalInterface; 12 | import flash.net.LocalConnection; 13 | import flash.net.URLRequest; 14 | import flash.net.navigateToURL; 15 | public final class HtmlUtil 16 | { 17 | 18 | // AS3 Regular expression pattern match for URLs that start with http:// and https:// plus your domain name. 19 | public static function checkProtocol(flashVarURL:String):Boolean 20 | { 21 | // Get the domain name for the SWF if it is not known at compile time. 22 | // If the domain is known at compile time, then the following two lines can be replaced with a hard coded string. 23 | var my_lc:LocalConnection = new LocalConnection(); 24 | var domainName:String = my_lc.domain; 25 | 26 | if (ExternalInterface.available) 27 | { 28 | ExternalInterface.call('console.log', domainName); 29 | } 30 | // Build the RegEx to test the URL. 31 | // This RegEx assumes that there is at least one "/" after the // domain. http://www.mysite.com will not match. 32 | var pattern:RegExp = new RegExp("ˆhttp[s]?\:\\/\\/([ˆ\\/]+)\\/"); 33 | var result:Object = pattern.exec(flashVarURL); 34 | 35 | if (result == null || result[1] != domainName || flashVarURL.length >= 4096) 36 | { 37 | return false; 38 | } 39 | else 40 | { 41 | return true; 42 | } 43 | } 44 | 45 | public static function getBrowserString():String 46 | { 47 | if (ExternalInterface.available) 48 | { 49 | var broswerStr:String = ExternalInterface.call("function getBrowser(){return navigator.userAgent;}") as String; 50 | return broswerStr; 51 | } 52 | else 53 | { 54 | return ""; 55 | } 56 | } 57 | 58 | public static function gotoURL(url:String, window:String = "_blank"):void 59 | { 60 | var broswer:String = getBrowserString(); 61 | 62 | if (broswer && (broswer.indexOf("Firefox") != -1 || broswer.indexOf("MSIE") != -1)) 63 | { 64 | navigateToURL(new URLRequest(url), window); 65 | } 66 | else 67 | { 68 | var eval:String = "function openNewHtml(){window.open('" + url + "')}"; 69 | ExternalInterface.call("eval", eval); 70 | ExternalInterface.call("openNewHtml"); 71 | } 72 | } 73 | 74 | public static function pageRefresh(url:String):void 75 | { 76 | var eval:String = "function refresh(){window.location.href = '" + url + "'}"; 77 | ExternalInterface.call("eval", eval); 78 | ExternalInterface.call("refresh"); 79 | } 80 | 81 | public function HtmlUtil() 82 | { 83 | 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/util/LogUtil.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | package com.fenhongxiang.util 9 | { 10 | import flash.external.ExternalInterface; 11 | public final class LogUtil 12 | { 13 | private static const LEVEL_DEBUG:String = "DEBUG:"; 14 | private static const LEVEL_ERROR:String = "ERROR:"; 15 | 16 | private static const LEVEL_INFO:String = "INFO:"; 17 | private static const LEVEL_WARN:String = "WARN:"; 18 | 19 | public static function debug(message:*):void 20 | { 21 | outputlog(LEVEL_DEBUG, String(message)); 22 | } 23 | 24 | public static function debug2(message:*):void 25 | { 26 | outputlog(LEVEL_DEBUG, String(message)); 27 | } 28 | 29 | public static function error(message:*):void 30 | { 31 | outputlog(LEVEL_ERROR, String(message)); 32 | } 33 | 34 | public static function info(message:*):void 35 | { 36 | outputlog(LEVEL_INFO, String(message)); 37 | } 38 | 39 | public static function warn(message:*):void 40 | { 41 | outputlog(LEVEL_WARN, String(message)); 42 | } 43 | 44 | private static function outputlog(level:String, message:String):void 45 | { 46 | if (ExternalInterface.available) 47 | { 48 | ExternalInterface.call('console.log', level + message); 49 | } 50 | else 51 | { 52 | trace(level + message); 53 | } 54 | } 55 | 56 | public function LogUtil() 57 | { 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/util/ObjectUtil.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.util 10 | { 11 | import flash.display.Stage; 12 | public final class ObjectUtil 13 | { 14 | 15 | public static function available(obj:Object, ... args):Boolean 16 | { 17 | if (obj == null) 18 | { 19 | return false; 20 | } 21 | else 22 | { 23 | for each (var prop:* in args) 24 | { 25 | if (!obj.hasOwnProperty(prop) || obj[prop] == null) 26 | { 27 | return false; 28 | break; 29 | } 30 | } 31 | 32 | return true; 33 | } 34 | } 35 | 36 | public static function getSWFParameter(name:String, stage:Stage):String 37 | { 38 | if (stage != null && name) 39 | { 40 | return stage.loaderInfo.parameters[name]; 41 | } 42 | else 43 | { 44 | return ""; 45 | } 46 | } 47 | 48 | public static function parseBoolean(value:*):Boolean 49 | { 50 | if (value is String) 51 | { 52 | return (value == "true") ? true : false; 53 | } 54 | else 55 | { 56 | return Boolean(value); 57 | } 58 | } 59 | 60 | public function ObjectUtil() 61 | { 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/util/SkinLoader.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.util 10 | { 11 | import flash.display.Loader; 12 | import flash.display.MovieClip; 13 | import flash.events.Event; 14 | import flash.events.EventDispatcher; 15 | import flash.events.IOErrorEvent; 16 | import flash.events.SecurityErrorEvent; 17 | import flash.net.URLRequest; 18 | 19 | public class SkinLoader extends EventDispatcher 20 | { 21 | 22 | private static var instance:SkinLoader; 23 | 24 | public static function getInstance():SkinLoader 25 | { 26 | if (SkinLoader.instance == null) 27 | { 28 | SkinLoader.instance = new SkinLoader(new Enforcer()); 29 | } 30 | 31 | return SkinLoader.instance; 32 | } 33 | 34 | public function SkinLoader(e:Enforcer) 35 | { 36 | if (e == null) 37 | { 38 | throw new Error("SkinLoader是一个单例对象"); 39 | } 40 | } 41 | 42 | private var _callBackFunction:Function; 43 | private var _loader:Loader; 44 | private var _skinContent:MovieClip; 45 | 46 | public function getSkinPart(name:String, prop:String = null):* 47 | { 48 | if (_skinContent != null && _skinContent.hasOwnProperty(name)) 49 | { 50 | if (prop) 51 | { 52 | return _skinContent[name][prop]; 53 | } 54 | else 55 | { 56 | return _skinContent[name]; 57 | } 58 | } 59 | else 60 | { 61 | return null; 62 | } 63 | } 64 | 65 | public function load(url:String, callBack:Function):void 66 | { 67 | _callBackFunction = callBack; 68 | 69 | if (_loader == null) 70 | { 71 | _loader = new Loader(); 72 | _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadedHandler, false, 0, true); 73 | _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadErrorHandler, false, 0, true); 74 | _loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadErrorHandler, false, 0, true); 75 | } 76 | 77 | try 78 | { 79 | _loader.close(); 80 | } 81 | catch (e:*) 82 | { 83 | // 84 | } 85 | 86 | try 87 | { 88 | _loader.load(new URLRequest(url)); 89 | } 90 | catch (e:*) 91 | { 92 | onLoadErrorHandler(); 93 | } 94 | } 95 | 96 | public function get skinContent():MovieClip 97 | { 98 | return _skinContent; 99 | } 100 | 101 | private function onLoadErrorHandler(e:* = null):void 102 | { 103 | _skinContent = null; 104 | 105 | if (_callBackFunction != null) 106 | { 107 | _callBackFunction(null); 108 | } 109 | 110 | dispose(); 111 | } 112 | 113 | private function onLoadedHandler(e:Event):void 114 | { 115 | 116 | _skinContent = e.target.content; 117 | 118 | if (_callBackFunction != null) 119 | { 120 | _callBackFunction(_skinContent); 121 | } 122 | 123 | dispose(); 124 | } 125 | 126 | private function dispose():void 127 | { 128 | if (_loader != null) 129 | { 130 | _loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoadedHandler); 131 | _loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onLoadErrorHandler); 132 | _loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadErrorHandler); 133 | _loader = null; 134 | } 135 | 136 | _callBackFunction = null; 137 | } 138 | } 139 | } 140 | 141 | class Enforcer 142 | { 143 | } 144 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/util/TimeUtil.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 R2Games 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.util 10 | { 11 | public final class TimeUtil 12 | { 13 | 14 | public static function getTimeString(value:Number):String 15 | { 16 | //把时间后面的毫秒数去掉,如59.885秒变为59秒 17 | var time:Number = Math.floor(value); 18 | 19 | if (time < 3600) 20 | { 21 | var min1:int = Math.floor(time / 60); 22 | var sec1:int = Math.floor(time % 60); 23 | 24 | return (min1 < 10 ? "0" + min1 : min1) + ":" + (sec1 < 10 ? "0" + sec1 : sec1); 25 | } 26 | else 27 | { 28 | var hour:int = Math.floor(time / 3600); 29 | var min:int = Math.floor((time - hour * 3600) / 60); 30 | var sec:int = Math.floor((time - hour * 3600) % 60); 31 | 32 | return (hour < 10 ? ("0" + hour) : hour) + ":" + (min < 10 ? "0" + min : min) + ":" + (sec < 10 ? "0" + sec : sec); 33 | } 34 | } 35 | 36 | public function TimeUtil() 37 | { 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/view/CoverLoader.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.view 10 | { 11 | import flash.display.Loader; 12 | import flash.display.Sprite; 13 | import flash.events.Event; 14 | import flash.events.IOErrorEvent; 15 | import flash.net.URLRequest; 16 | public class CoverLoader extends Sprite 17 | { 18 | public function CoverLoader(w:Number, h:Number) 19 | { 20 | drawBackground(w, h); 21 | } 22 | 23 | private var _h:Number = 0; 24 | private var _w:Number = 0; 25 | private var ldr:Loader; 26 | 27 | public function load(path:String):void 28 | { 29 | if (ldr == null) 30 | { 31 | ldr = new Loader(); 32 | ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onCoverLoadedHandler, false, 0, true); 33 | ldr.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onCoverLoadErrorHandler, false, 0, true); 34 | } 35 | 36 | ldr.load(new URLRequest(path)); 37 | } 38 | 39 | private function drawBackground(w:Number, h:Number):void 40 | { 41 | _w = w; 42 | _h = h; 43 | 44 | this.graphics.clear(); 45 | this.graphics.beginFill(0x1F272A, 1); 46 | this.graphics.drawRect(0, 0, w, h); 47 | this.graphics.endFill(); 48 | } 49 | 50 | private function onCoverLoadErrorHandler(e:IOErrorEvent):void 51 | { 52 | 53 | } 54 | 55 | private function onCoverLoadedHandler(e:Event):void 56 | { 57 | ldr.width = _w; 58 | ldr.height = _h; 59 | 60 | if (!this.contains(ldr)) 61 | { 62 | this.addChild(ldr); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/view/FProgressBar.as: -------------------------------------------------------------------------------- 1 | package com.fenhongxiang.view 2 | { 3 | import flash.events.Event; 4 | import flash.events.MouseEvent; 5 | 6 | public class FProgressBar extends FSprite 7 | { 8 | public function FProgressBar() 9 | { 10 | super(); 11 | 12 | this.addEventListener(MouseEvent.CLICK, onProgressBarClickHandler, false, 0, true); 13 | } 14 | 15 | private var _viewDirty:Boolean = false; 16 | private var _loadedPer:Number = 0.0; 17 | private var _playedPer:Number = 0.0; 18 | private var _seekPer:Number = 0.0; 19 | 20 | public function updateView(loadedPer:Number, playedPer:Number):void 21 | { 22 | _viewDirty = true; 23 | _loadedPer = loadedPer; 24 | _playedPer = playedPer; 25 | 26 | invalidateDisplaylist(); 27 | } 28 | 29 | private function onProgressBarClickHandler(e:MouseEvent):void 30 | { 31 | _seekPer = e.localX / this.width; 32 | 33 | updateView(_loadedPer, _seekPer); 34 | } 35 | 36 | override public function onRenderHandler(e:Event):void 37 | { 38 | if (_viewDirty) 39 | { 40 | this.graphics.clear(); 41 | 42 | var radius:Number = 0; 43 | 44 | 45 | //background 46 | this.graphics.beginFill(0xFFFFFF, this.backgroundAlpha); 47 | this.graphics.drawRoundRect(0, 0, this.width, this.height, radius, radius); 48 | this.graphics.endFill(); 49 | 50 | 51 | //loaded 52 | this.graphics.beginFill(0xd0e4f5, 1.0); 53 | 54 | if (_loadedPer <= (1 - radius/this.width)) 55 | { 56 | this.graphics.drawRoundRectComplex(0, 0, this.width*_loadedPer, this.height, radius, 0.0, radius, 0.0); 57 | } 58 | else 59 | { 60 | this.graphics.drawRoundRectComplex(0, 0, this.width*_loadedPer, this.height, radius, radius, radius, radius); 61 | } 62 | this.graphics.endFill(); 63 | 64 | //played 65 | this.graphics.beginFill(0x64b5f6, 1.0); 66 | 67 | if (_playedPer <= (1 - 10/this.width)) 68 | { 69 | this.graphics.drawRoundRectComplex(0, 0, this.width*_playedPer, this.height, radius, 0.0, radius, 0.0); 70 | } 71 | else 72 | { 73 | this.graphics.drawRoundRectComplex(0, 0, this.width*_playedPer, this.height, radius, radius, radius, radius); 74 | } 75 | 76 | this.graphics.endFill(); 77 | 78 | _viewDirty = false; 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/view/FSprite.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.view 10 | { 11 | import com.fenhongxiang.cue.CueData; 12 | import com.fenhongxiang.event.FResizeEvent; 13 | 14 | import flash.display.Sprite; 15 | import flash.events.Event; 16 | public class FSprite extends Sprite 17 | { 18 | public function FSprite() 19 | { 20 | this.addEventListener(Event.ADDED_TO_STAGE, onAddToStageHandler, false, 0, true); 21 | this.addEventListener(Event.RENDER, onRenderHandler, false, 0, true); 22 | } 23 | 24 | private var _backgroudColor:uint = 0xffffff; 25 | private var _backgroundAlpha:Number = 1.0; 26 | private var _border:Boolean = false; 27 | private var _borderColor:uint = 0; 28 | private var _data:CueData; 29 | private var _height:Number = 300; 30 | private var _viewDirty:Boolean = false; 31 | private var _width:Number = 300; 32 | 33 | public function get backgroundAlpha():Number 34 | { 35 | return _backgroundAlpha; 36 | } 37 | 38 | public function get backgroudColor():uint 39 | { 40 | return _backgroudColor; 41 | } 42 | 43 | public function set backgroudColor(value:uint):void 44 | { 45 | if (_backgroudColor != value) 46 | { 47 | _backgroudColor = value; 48 | _viewDirty = true; 49 | invalidateDisplaylist(); 50 | } 51 | } 52 | 53 | public function set backgroundAlpha(value:Number):void 54 | { 55 | if (_backgroundAlpha != value) 56 | { 57 | _backgroundAlpha = value; 58 | _viewDirty = true; 59 | invalidateDisplaylist(); 60 | } 61 | } 62 | 63 | 64 | 65 | public function get border():Boolean 66 | { 67 | return _border; 68 | } 69 | 70 | public function set border(value:Boolean):void 71 | { 72 | if (_border != value) 73 | { 74 | _border = value; 75 | _viewDirty = true; 76 | invalidateDisplaylist(); 77 | } 78 | } 79 | 80 | override public function get height():Number 81 | { 82 | return _height; 83 | } 84 | 85 | override public function set height(value:Number):void 86 | { 87 | if (_height != value) 88 | { 89 | _height = value; 90 | _viewDirty = true; 91 | invalidateDisplaylist(); 92 | this.dispatchEvent(new FResizeEvent(FResizeEvent.SIZE_CHANGE)); 93 | } 94 | } 95 | 96 | public function onRenderHandler(e:Event):void 97 | { 98 | if (_viewDirty) 99 | { 100 | this.graphics.clear(); 101 | 102 | if (_border) 103 | { 104 | this.graphics.lineStyle(1, _borderColor); 105 | } 106 | 107 | this.graphics.beginFill(_backgroudColor, _backgroundAlpha); 108 | this.graphics.drawRect(0, 0, _width, _height); 109 | this.graphics.endFill(); 110 | 111 | _viewDirty = false; 112 | } 113 | } 114 | 115 | public function resize(w:Number, h:Number):void 116 | { 117 | width = w; 118 | height = h; 119 | } 120 | 121 | override public function get width():Number 122 | { 123 | return _width; 124 | } 125 | 126 | override public function set width(value:Number):void 127 | { 128 | if (_width != value) 129 | { 130 | _width = value; 131 | _viewDirty = true; 132 | invalidateDisplaylist(); 133 | this.dispatchEvent(new FResizeEvent(FResizeEvent.SIZE_CHANGE)); 134 | } 135 | } 136 | 137 | protected function invalidateDisplaylist():void 138 | { 139 | if (this.stage) 140 | { 141 | this.stage.invalidate(); 142 | } 143 | } 144 | 145 | private function onAddToStageHandler(e:Event):void 146 | { 147 | this.removeEventListener(Event.ADDED_TO_STAGE, onAddToStageHandler); 148 | 149 | _viewDirty = true; 150 | invalidateDisplaylist(); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/vtt/ImageLoader.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.vtt 10 | { 11 | import flash.display.Bitmap; 12 | import flash.display.BitmapData; 13 | import flash.display.Loader; 14 | import flash.events.Event; 15 | import flash.events.EventDispatcher; 16 | import flash.events.IEventDispatcher; 17 | import flash.events.IOErrorEvent; 18 | import flash.events.SecurityErrorEvent; 19 | import flash.net.URLRequest; 20 | 21 | internal class ImageLoader extends EventDispatcher 22 | { 23 | public function ImageLoader(target:IEventDispatcher = null) 24 | { 25 | super(target); 26 | } 27 | 28 | private var _loader:Loader; 29 | 30 | public function load(url:String):void 31 | { 32 | var ldr:Loader = getLoaderInstance(); 33 | 34 | try 35 | { 36 | ldr.load(new URLRequest(url)); 37 | } 38 | catch (e:*) 39 | { 40 | onLoadErrorHandler(); 41 | } 42 | } 43 | 44 | private function onLoadErrorHandler(e:* = null):void 45 | { 46 | dispatchEvent(new ImageLoaderEvent(ImageLoaderEvent.ERROR, null, true)); 47 | dispose(); 48 | } 49 | 50 | private function onLoadedHandler(e:Event):void 51 | { 52 | var bitMap:Bitmap = e.target.content as Bitmap; 53 | 54 | if (bitMap != null) 55 | { 56 | var imgData:BitmapData = bitMap.bitmapData.clone(); 57 | bitMap.bitmapData.dispose(); 58 | 59 | dispatchEvent(new ImageLoaderEvent(ImageLoaderEvent.LOADED, imgData, true)); 60 | 61 | dispose(); 62 | } 63 | else 64 | { 65 | //加载的数据不是位图,则也视为加载失败 66 | onLoadErrorHandler(); 67 | } 68 | } 69 | 70 | private function getLoaderInstance():Loader 71 | { 72 | if (_loader == null) 73 | { 74 | _loader = new Loader(); 75 | _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadedHandler, false, 0, true); 76 | _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadErrorHandler, false, 0, true); 77 | _loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadErrorHandler, false, 0, true); 78 | } 79 | 80 | try 81 | { 82 | _loader.close(); 83 | } 84 | catch (e:*) 85 | { 86 | // 87 | } 88 | 89 | return _loader; 90 | } 91 | 92 | private function dispose():void 93 | { 94 | if (_loader != null) 95 | { 96 | _loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoadedHandler); 97 | _loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onLoadErrorHandler); 98 | _loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadErrorHandler); 99 | 100 | _loader.close(); 101 | _loader = null; 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/vtt/ImageLoaderEvent.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.vtt 10 | { 11 | import flash.display.BitmapData; 12 | import flash.events.Event; 13 | 14 | public class ImageLoaderEvent extends Event 15 | { 16 | public static const ERROR:String = "图像加载失败"; 17 | public static const LOADED:String = "图像加载完成"; 18 | 19 | public function ImageLoaderEvent(type:String, data:BitmapData, bubbles:Boolean = false, cancelable:Boolean = false) 20 | { 21 | super(type, bubbles, cancelable); 22 | _data = data; 23 | } 24 | 25 | private var _data:BitmapData; 26 | 27 | public function get data():BitmapData 28 | { 29 | return _data; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/vtt/VTTControler.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.vtt 10 | { 11 | import flash.display.BitmapData; 12 | import flash.geom.Point; 13 | import flash.geom.Rectangle; 14 | 15 | public final class VTTControler 16 | { 17 | 18 | 19 | public function VTTControler(url:String):void 20 | { 21 | load(url); 22 | } 23 | 24 | private var _imageData:BitmapData; 25 | private var _path:String; 26 | private var _vttData:Vector.; 27 | private var ldr:ImageLoader; 28 | private var vttLoader:VTTLoader; 29 | 30 | public function load(path:String):void 31 | { 32 | if (vttLoader == null) 33 | { 34 | vttLoader = new VTTLoader(); 35 | vttLoader.addEventListener(VTTLoaderEvent.LOADED, onVTTFileLoaded, false, 0, true); 36 | } 37 | 38 | vttLoader.load(path); 39 | } 40 | 41 | /** 42 | * 43 | * @param time 44 | * @param img 45 | * 46 | */ 47 | public function renderImage(time:Number, img:BitmapData):void 48 | { 49 | if (img) 50 | { 51 | img.fillRect(img.rect, 0x00FFFFFF); 52 | } 53 | 54 | if (_imageData) 55 | { 56 | var range:Rectangle = getImageRangeByTime(time); 57 | if (range) 58 | { 59 | try 60 | { 61 | img.copyPixels(this._imageData, range, new Point(0, 0)); 62 | } 63 | catch (error:Error) 64 | { 65 | //do things here... 66 | } 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * 73 | * get image position by time 74 | * 75 | * */ 76 | private function getImageRangeByTime(time:Number):Rectangle 77 | { 78 | var imageRange:Rectangle = null; 79 | 80 | if (_vttData && _vttData.length) 81 | { 82 | for each (var obj:VTTModel in _vttData) 83 | { 84 | imageRange = obj.getImageRange(time); 85 | 86 | if (imageRange) 87 | { 88 | return imageRange; 89 | } 90 | } 91 | } 92 | 93 | return imageRange; 94 | } 95 | 96 | //---------------------------------- setters and getters ----------------------------------// 97 | private function set imageData(value:*):void 98 | { 99 | if (_imageData != null) 100 | { 101 | _imageData.dispose(); 102 | } 103 | 104 | _imageData = value; 105 | } 106 | 107 | private function set imagePath(path:String):void 108 | { 109 | if (path && _path != path) 110 | { 111 | _path = path; 112 | 113 | ldr = new ImageLoader(); 114 | ldr.addEventListener(ImageLoaderEvent.LOADED, onThumbLoadedHandler); 115 | ldr.load(path); 116 | } 117 | } 118 | 119 | //---------------------------------- event handlers ----------------------------------// 120 | private function onThumbLoadedHandler(e:ImageLoaderEvent):void 121 | { 122 | if (e.data) 123 | { 124 | _imageData = e.data.clone(); 125 | e.data.dispose(); 126 | } 127 | else 128 | { 129 | _imageData = null; 130 | } 131 | } 132 | 133 | private function onVTTFileLoaded(e:VTTLoaderEvent):void 134 | { 135 | this.vttData = e.data; 136 | } 137 | 138 | private function set vttData(value:Vector.):void 139 | { 140 | _vttData = value; 141 | 142 | if (_vttData && _vttData.length) 143 | { 144 | imagePath = VTTLoader.imageURL + '/' + _vttData[0].path; 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/vtt/VTTLoader.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.vtt 10 | { 11 | import flash.events.Event; 12 | import flash.events.EventDispatcher; 13 | import flash.events.IOErrorEvent; 14 | import flash.events.SecurityErrorEvent; 15 | import flash.net.URLLoader; 16 | import flash.net.URLRequest; 17 | 18 | public class VTTLoader extends EventDispatcher 19 | { 20 | 21 | public function VTTLoader() 22 | { 23 | } 24 | 25 | private var _loader:URLLoader; 26 | public static var imageURL:String; 27 | 28 | public function load(url:String):void 29 | { 30 | imageURL = url; 31 | 32 | if (imageURL) 33 | { 34 | imageURL = imageURL.substr(0, imageURL.lastIndexOf("/")); 35 | } 36 | 37 | _loader = getLoaderInstance(); 38 | 39 | try 40 | { 41 | _loader.load(new URLRequest(url)); 42 | } 43 | catch (e:*) 44 | { 45 | errorHandler(); 46 | } 47 | } 48 | //----------------------------event handlers-------------------------------------------------// 49 | 50 | private function errorHandler(e:* = null):void 51 | { 52 | removeListeners(); 53 | dispatchEvent(new VTTLoaderEvent(VTTLoaderEvent.ERROR, null, true)); 54 | } 55 | 56 | private function loadedHandler(e:Event):void 57 | { 58 | var vttStr:String = e.target.data; 59 | var vttData:Vector. = parseVTT(vttStr); 60 | 61 | if (vttData) 62 | { 63 | removeListeners(); 64 | dispatchEvent(new VTTLoaderEvent(VTTLoaderEvent.LOADED, vttData, true)); 65 | } 66 | else 67 | { 68 | errorHandler(); 69 | } 70 | } 71 | 72 | //----------------------------tool function-------------------------------------------------// 73 | private function removeListeners():void 74 | { 75 | if (_loader != null) 76 | { 77 | _loader.removeEventListener(Event.COMPLETE, loadedHandler); 78 | _loader.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler); 79 | _loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler); 80 | } 81 | } 82 | 83 | private function getLoaderInstance():URLLoader 84 | { 85 | if (_loader == null) 86 | { 87 | _loader = new URLLoader(); 88 | _loader.addEventListener(Event.COMPLETE, loadedHandler, false, 0, true); 89 | _loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler, false, 0, true); 90 | _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler, false, 0, true); 91 | } 92 | else 93 | { 94 | try 95 | { 96 | _loader.close(); 97 | } 98 | catch (e:*) 99 | { 100 | 101 | } 102 | } 103 | 104 | return _loader; 105 | } 106 | 107 | private function parseVTT(src:String):Vector. 108 | { 109 | if (src && src != "") 110 | { 111 | var vttArr:Array = src.replace(/\r/g, '').split("\n").filter(vttFilter); 112 | 113 | if (vttArr != null && vttArr.length > 0) 114 | { 115 | var vttDataArr:Vector. = new Vector.(); 116 | 117 | var len:int = vttArr.length; 118 | 119 | for (var i:int = 0; i < len; i += 2) 120 | { 121 | vttDataArr.push(new VTTModel(vttArr[i], vttArr[i + 1])); 122 | } 123 | 124 | return vttDataArr; 125 | } 126 | 127 | return null; 128 | } 129 | else 130 | { 131 | return null; 132 | } 133 | } 134 | 135 | private function vttFilter(item:*, index:int, array:Array):Boolean 136 | { 137 | return item != "WEBVTT" && item != ""; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/vtt/VTTLoaderEvent.as: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | package com.fenhongxiang.vtt 5 | { 6 | import flash.events.Event; 7 | 8 | public class VTTLoaderEvent extends Event 9 | { 10 | public static const LOADED:String = "加载完成"; 11 | public static const ERROR:String = "加载失败"; 12 | 13 | private var _data:*; 14 | 15 | public function VTTLoaderEvent(type:String, data:*, bubbles:Boolean=false, cancelable:Boolean=false) 16 | { 17 | _data = data; 18 | super(type, bubbles, cancelable); 19 | } 20 | 21 | public function get data():* 22 | { 23 | return _data; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /project/PinkPlayer/src/com/fenhongxiang/vtt/VTTModel.as: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright 2016 www.fenhongxiang.com 4 | // All rights reserved. 5 | // By :ljh 6 | // 7 | //------------------------------------------------------------------------------ 8 | 9 | package com.fenhongxiang.vtt 10 | { 11 | import flash.geom.Rectangle; 12 | 13 | public class VTTModel 14 | { 15 | 16 | /** 17 | * 18 | * @param timeString 时间格式为 00:00:20.000 --> 00:00:25.000的字符串 19 | * @param data 时间区间对应的内容,格式为:/assets/306560850.mp4.jpg#xywh=6,71,100,60 20 | * 21 | * */ 22 | public function VTTModel(time:String, data:String) 23 | { 24 | timeRange = time; 25 | imageRange = data; 26 | } 27 | 28 | private var _path:String; 29 | private var _imageRect:Rectangle = new Rectangle(0, 0, 0, 0); 30 | private var _timeRange:Rectangle = new Rectangle(0, 0, 0, 1); 31 | 32 | public function getImageRange(time:Number):Rectangle 33 | { 34 | if (time > _timeRange.x && _timeRange.contains(time, 0)) 35 | { 36 | return _imageRect; 37 | } 38 | 39 | return null; 40 | } 41 | 42 | public function get path():String 43 | { 44 | return _path; 45 | } 46 | 47 | public function set path(value:String):void 48 | { 49 | _path = value; 50 | } 51 | 52 | /** 53 | * 时间点的秒数形式 54 | * 55 | * @param value 格式为:00:00:20.000 56 | * 57 | * */ 58 | private function getTotalSeconds(value:String):Number 59 | { 60 | if (value) 61 | { 62 | var timeArr:Array = value.split(':'); 63 | return timeArr[0] * 3600 + timeArr[1] * 60 + timeArr[2] * 1; 64 | } 65 | 66 | return 0; 67 | } 68 | 69 | /** 70 | * 包含缩略图文件信息的字符串,格式为:/assets/306560850.mp4.jpg#xywh=6,71,100,60 71 | * 72 | * */ 73 | private function set imageRange(str:String):void 74 | { 75 | if (str != null && str != "") 76 | { 77 | var thumbArr:Array = str.split("#"); 78 | 79 | if (thumbArr != null && thumbArr.length == 2) 80 | { 81 | path = thumbArr[0]; 82 | 83 | //xywh=\d.\d{1,},\d{1,},\d{1,} 84 | var thumbPos:String = thumbArr[1]; //xywh=6,71,100,60 85 | var thumbData:Array = String(thumbPos.split("=")[1]).split(","); 86 | 87 | if (thumbData && thumbData.length == 4) 88 | { 89 | _imageRect.x = thumbData[0]; 90 | _imageRect.y = thumbData[1]; 91 | _imageRect.width = thumbData[2]; 92 | _imageRect.height = thumbData[3]; 93 | } 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * 时间格式为 00:00:20.000 --> 00:00:25.000的字符串 100 | * 101 | * */ 102 | private function set timeRange(str:String):void 103 | { 104 | var reg:RegExp = new RegExp('[0-9][0-9]:[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9]', 'i') 105 | 106 | if (str != null && str != "") 107 | { 108 | var timeArray:Array = str.match(reg); 109 | var timeStr:String; 110 | 111 | if (timeArray != null && timeArray.length > 0) 112 | { 113 | timeStr = timeArray[0]; 114 | timeArray = str.split('-->'); 115 | 116 | _timeRange.x = getTotalSeconds(timeArray[0]); 117 | 118 | _timeRange.width = getTotalSeconds(timeArray[1]) - _timeRange.x; 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /shots/shot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot01.png -------------------------------------------------------------------------------- /shots/shot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot02.png -------------------------------------------------------------------------------- /shots/shot03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot03.png -------------------------------------------------------------------------------- /shots/shot04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot04.png -------------------------------------------------------------------------------- /shots/shot05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot05.png -------------------------------------------------------------------------------- /shots/shot06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot06.png -------------------------------------------------------------------------------- /shots/shot07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot07.png -------------------------------------------------------------------------------- /shots/shot08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luo-kai-wen/HLSPlayer/715d7282849e020298f69f06443b5e4374821d45/shots/shot08.png --------------------------------------------------------------------------------