├── .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 | 
21 |
22 |
23 | 2.VTT缩略图
24 |
25 |
26 | 
27 |
28 |
29 | 3.SRT字幕显示(UTF-8格式)
30 |
31 |
32 | 
33 |
34 |
35 | 4.播放器缩放时,UI做了自适应
36 |
37 |
38 | 
39 |
40 |
41 | 
42 |
43 |
44 | 
45 |
46 |
47 | 
48 |
49 |
50 |
51 | 5.视频打点显示。目前是解析传过来的JSON打点信息,然后在进度条上生成对应显示点。
52 |
53 |
54 | 
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
--------------------------------------------------------------------------------