winlin

refine srs player for hls, extract a HlsNetStream.

1 -package  
2 -{  
3 - public class Consts  
4 - {  
5 - // refresh every ts_fragment_seconds*M3u8RefreshRatio  
6 - public static var M3u8RefreshRatio:Number = 0.5;  
7 -  
8 - // parse ts every this ms.  
9 - public static var TsParseAsyncInterval:Number = 80;  
10 - }  
11 -}  
1 -package  
2 -{  
3 - import flash.utils.Dictionary;  
4 -  
5 - public class Dict  
6 - {  
7 - private var _dict:Dictionary;  
8 - private var _size:uint;  
9 -  
10 - public function Dict()  
11 - {  
12 - clear();  
13 - }  
14 -  
15 - /**  
16 - * get the underlayer dict.  
17 - * @remark for core-ng.  
18 - */  
19 - public function get dict():Dictionary  
20 - {  
21 - return _dict;  
22 - }  
23 -  
24 - public function has(key:Object):Boolean  
25 - {  
26 - return (key in _dict);  
27 - }  
28 -  
29 - public function get(key:Object):Object  
30 - {  
31 - return ((key in _dict) ? _dict[key] : null);  
32 - }  
33 -  
34 - public function set(key:Object, object:Object):void  
35 - {  
36 - if (!(key in _dict))  
37 - {  
38 - _size++;  
39 - }  
40 - _dict[key] = object;  
41 - }  
42 -  
43 - public function remove(key:Object):Object  
44 - {  
45 - var object:Object;  
46 - if (key in _dict)  
47 - {  
48 - object = _dict[key];  
49 - delete _dict[key];  
50 - _size--;  
51 - }  
52 - return (object);  
53 - }  
54 -  
55 - public function keys():Array  
56 - {  
57 - var array:Array = new Array(_size);  
58 - var index:int;  
59 - for (var key:Object in _dict)  
60 - {  
61 - var _local6:int = index++;  
62 - array[_local6] = key;  
63 - }  
64 - return (array);  
65 - }  
66 -  
67 - public function values():Array  
68 - {  
69 - var array:Array = new Array(_size);  
70 - var index:int;  
71 - for each (var value:Object in _dict)  
72 - {  
73 - var _local6:int = index++;  
74 - array[_local6] = value;  
75 - };  
76 - return (array);  
77 - }  
78 -  
79 - public function clear():void  
80 - {  
81 - _dict = new Dictionary();  
82 - _size = 0;  
83 - }  
84 -  
85 - public function toArray():Array  
86 - {  
87 - var array:Array = new Array(_size * 2);  
88 - var index:int;  
89 - for (var key:Object in _dict)  
90 - {  
91 - var _local6:int = index++;  
92 - array[_local6] = key;  
93 - var _local7:int = index++;  
94 - array[_local7] = _dict[key];  
95 - };  
96 - return (array);  
97 - }  
98 -  
99 - public function toObject():Object  
100 - {  
101 - return (toArray());  
102 - }  
103 -  
104 - public function fromObject(object:Object):void  
105 - {  
106 - clear();  
107 - var index:uint;  
108 - while (index < (object as Array).length) {  
109 - set((object as Array)[index], (object as Array)[(index + 1)]);  
110 - index += 2;  
111 - };  
112 - }  
113 -  
114 - public function get size():uint  
115 - {  
116 - return (_size);  
117 - }  
118 -  
119 - }  
120 -}  
1 -package  
2 -{  
3 - import flash.utils.ByteArray;  
4 -  
5 - /**  
6 - * a piece of flv, fetch from cdn or p2p.  
7 - */  
8 - public class FlvPiece  
9 - {  
10 - private var _pieceId:Number;  
11 - protected var _flv:ByteArray;  
12 - /**  
13 - * the private object for the channel,  
14 - * for example, the cdn channel will set to CdnEdge object.  
15 - */  
16 - private var _privateObject:Object;  
17 - /**  
18 - * when encoder error, this piece cannot be generated,  
19 - * and it should be skip. default to false.  
20 - */  
21 - private var _skip:Boolean;  
22 -  
23 - public function FlvPiece(pieceId:Number)  
24 - {  
25 - _pieceId = pieceId;  
26 - _flv = null;  
27 - _skip = false;  
28 - }  
29 -  
30 - /**  
31 - * when piece is fetch ok.  
32 - */  
33 - public function onPieceDone(flv:ByteArray):void  
34 - {  
35 - // save body.  
36 - _flv = flv;  
37 - }  
38 -  
39 - /**  
40 - * when piece is fetch error.  
41 - */  
42 - public function onPieceError():void  
43 - {  
44 - }  
45 -  
46 - /**  
47 - * when piece is empty.  
48 - */  
49 - public function onPieceEmpty():void  
50 - {  
51 - }  
52 -  
53 - /**  
54 - * destroy the object, set reference to null.  
55 - */  
56 - public function destroy():void  
57 - {  
58 - _privateObject = null;  
59 - _flv = null;  
60 - }  
61 -  
62 - public function get privateObject():Object  
63 - {  
64 - return _privateObject;  
65 - }  
66 -  
67 - public function set privateObject(v:Object):void  
68 - {  
69 - _privateObject = v;  
70 - }  
71 -  
72 - public function get skip():Boolean  
73 - {  
74 - return _skip;  
75 - }  
76 -  
77 - public function set skip(v:Boolean):void  
78 - {  
79 - _skip = v;  
80 - }  
81 -  
82 - public function get pieceId():Number  
83 - {  
84 - return _pieceId;  
85 - }  
86 -  
87 - public function get flv():ByteArray  
88 - {  
89 - return _flv;  
90 - }  
91 -  
92 - public function get completed():Boolean  
93 - {  
94 - return _flv != null;  
95 - }  
96 - }  
97 -}  
1 package 1 package
2 { 2 {
3 import flash.events.Event; 3 import flash.events.Event;
  4 + import flash.events.ProgressEvent;
  5 + import flash.external.ExternalInterface;
  6 + import flash.net.NetConnection;
  7 + import flash.net.NetStream;
  8 + import flash.net.NetStreamAppendBytesAction;
4 import flash.net.URLLoader; 9 import flash.net.URLLoader;
  10 + import flash.net.URLLoaderDataFormat;
5 import flash.net.URLRequest; 11 import flash.net.URLRequest;
  12 + import flash.net.URLRequestHeader;
6 import flash.net.URLRequestMethod; 13 import flash.net.URLRequestMethod;
  14 + import flash.net.URLStream;
  15 + import flash.net.URLVariables;
7 import flash.utils.ByteArray; 16 import flash.utils.ByteArray;
  17 + import flash.utils.setTimeout;
8 18
9 - /**  
10 - * the hls main class.  
11 - */  
12 - public class Hls 19 + // the NetStream to play hls or hls+.
  20 + public class HlsNetStream extends NetStream
13 { 21 {
14 - private var m3u8:M3u8;  
15 -  
16 - private var avc:SrsRawH264Stream;  
17 - private var h264_sps:ByteArray;  
18 - private var h264_pps:ByteArray;  
19 -  
20 - private var aac:SrsRawAacStream;  
21 - private var aac_specific_config:ByteArray;  
22 - private var width:int;  
23 - private var height:int;  
24 -  
25 - private var video_sh_tag:ByteArray;  
26 - private var audio_sh_tag:ByteArray; 22 + private var hls:HlsCodec = null; // parse m3u8 and ts
27 23
28 - private var owner:M3u8Player;  
29 - private var _log:ILogger = new TraceLogger("HLS");  
30 -  
31 - public static const SRS_TS_PACKET_SIZE:int = 188;  
32 -  
33 - public function Hls(o:M3u8Player)  
34 - {  
35 - owner = o;  
36 - m3u8 = new M3u8(this);  
37 -  
38 - reset();  
39 - }  
40 -  
41 - /**  
42 - * parse the m3u8.  
43 - * @param url, the m3u8 url, for m3u8 to generate the ts url.  
44 - * @param v, the m3u8 string.  
45 - */  
46 - public function parse(url:String, v:String):void  
47 - {  
48 - // TODO: FIXME: reset the hls when parse.  
49 - m3u8.parse(url, v);  
50 - }  
51 -  
52 - /**  
53 - * get the total count of ts in m3u8.  
54 - */  
55 - public function get tsCount():Number 24 + // for hls codec.
  25 + public var m3u8_refresh_ratio:Number;
  26 + public var ts_parse_async_interval:Number;
  27 +
  28 + // play param url.
  29 + private var user_url:String = null;
  30 +
  31 + /**
  32 + * create stream to play hls.
  33 + * @param m3u8_refresh_ratio, for example, 0.5, fetch m3u8 every 0.5*ts_duration.
  34 + * @param ts_parse_async_interval, for example, 80ms to parse ts async.
  35 + */
  36 + public function HlsNetStream(m3u8_refresh_ratio:Number, ts_parse_async_interval:Number, conn:NetConnection)
56 { 37 {
57 - return m3u8.tsCount; 38 + super(conn);
  39 +
  40 + this.m3u8_refresh_ratio = m3u8_refresh_ratio;
  41 + this.ts_parse_async_interval = ts_parse_async_interval;
  42 + hls = new HlsCodec(this);
58 } 43 }
59 -  
60 - /**  
61 - * get the total duration in seconds of m3u8.  
62 - */  
63 - public function get duration():Number  
64 - {  
65 - return m3u8.duration; 44 +
  45 + /**
  46 + * to play the hls stream.
  47 + * for example, HlsNetStream.play("http://ossrs.net:8080/live/livestream.m3u8").
  48 + * user can set the metadata callback by:
  49 + * var ns:NetStream = new HlsNetStream(...);
  50 + * ns.client = {};
  51 + * ns.client.onMetaData = system_on_metadata;
  52 + */
  53 + public override function play(... params):void
  54 + {
  55 + super.play(null);
  56 + user_url = params[0] as String;
  57 + refresh_m3u8();
  58 + }
  59 +
  60 + /////////////////////////////////////////////////////////////////////////////////////////
  61 + ////////////////////////////Private Section//////////////////////////////////////////////
  62 + /////////////////////////////////////////////////////////////////////////////////////////
  63 +
  64 + private var parsed_ts_seq_no:Number = -1;
  65 + private function refresh_m3u8():void {
  66 + download(user_url, function(stream:ByteArray):void {
  67 + var m3u8:String = stream.toString();
  68 + hls.parse(user_url, m3u8);
  69 +
  70 + // redirect by variant m3u8.
  71 + if (hls.variant) {
  72 + var smu:String = hls.getTsUrl(0);
  73 + log("variant hls=" + user_url + ", redirect2=" + smu);
  74 + user_url = smu;
  75 + setTimeout(refresh_m3u8, 0);
  76 + return;
  77 + }
  78 +
  79 + // fetch from the last one.
  80 + if (parsed_ts_seq_no == -1) {
  81 + parsed_ts_seq_no = hls.seq_no + hls.tsCount - 1;
  82 + }
  83 +
  84 + // not changed.
  85 + if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
  86 + refresh_ts();
  87 + return;
  88 + }
  89 +
  90 + // parse each ts.
  91 + var nb_ts:Number = hls.seq_no + hls.tsCount - parsed_ts_seq_no;
  92 + log("m3u8 changed, got " + nb_ts + " new ts, count=" + hls.tsCount + ", seqno=" + hls.seq_no + ", parsed=" + parsed_ts_seq_no);
  93 +
  94 + refresh_ts();
  95 + })
  96 + }
  97 + private function refresh_ts():void {
  98 + // all ts parsed.
  99 + if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
  100 + var to:Number = 1000;
  101 + if (hls.tsCount > 0) {
  102 + to = hls.duration * 1000 / hls.tsCount * m3u8_refresh_ratio;
  103 + }
  104 + setTimeout(refresh_m3u8, to);
  105 + log("m3u8 not changed, retry after " + to.toFixed(2) + "ms");
  106 + return;
  107 + }
  108 +
  109 + // parse current ts.
  110 + var uri:String = hls.getTsUrl(parsed_ts_seq_no - hls.seq_no);
  111 +
  112 + // parse metadata from uri.
  113 + if (uri.indexOf("?") >= 0) {
  114 + var uv:URLVariables = new URLVariables(uri.substr(uri.indexOf("?") + 1));
  115 + var obj:Object = {};
  116 + for (var k:String in uv) {
  117 + var v:String = uv[k];
  118 + if (k == "shp_sip1") {
  119 + obj.srs_server_ip = v;
  120 + } else if (k == "shp_cid") {
  121 + obj.srs_id = v;
  122 + } else if (k == "shp_pid") {
  123 + obj.srs_pid = v;
  124 + }
  125 + //log("uv[" + k + "]=" + v);
  126 + }
  127 +
  128 + if (client && client.hasOwnProperty("onMetaData")) {
  129 + client.onMetaData(obj);
  130 + }
  131 + }
  132 +
  133 + download(uri, function(stream:ByteArray):void{
  134 + log("got ts seqno=" + parsed_ts_seq_no + ", " + stream.length + " bytes");
  135 +
  136 + var flv:FlvPiece = new FlvPiece(parsed_ts_seq_no);
  137 + var body:ByteArray = new ByteArray();
  138 + stream.position = 0;
  139 + hls.parseBodyAsync(flv, stream, body, function():void{
  140 + body.position = 0;
  141 + //log("ts parsed, seqno=" + parsed_ts_seq_no + ", flv=" + body.length + "B");
  142 + onFlvBody(uri, body);
  143 +
  144 + parsed_ts_seq_no++;
  145 + setTimeout(refresh_ts, 0);
  146 + });
  147 + });
  148 + }
  149 + private function download(uri:String, completed:Function):void {
  150 + // http get.
  151 + var url:URLStream = new URLStream();
  152 + var stream:ByteArray = new ByteArray();
  153 +
  154 + url.addEventListener(ProgressEvent.PROGRESS, function(evt:ProgressEvent):void {
  155 + if (url.bytesAvailable <= 0) {
  156 + return;
  157 + }
  158 +
  159 + //log(uri + " total=" + evt.bytesTotal + ", loaded=" + evt.bytesLoaded + ", available=" + url.bytesAvailable);
  160 + var bytes:ByteArray = new ByteArray();
  161 + url.readBytes(bytes, 0, url.bytesAvailable);
  162 + stream.writeBytes(bytes);
  163 + });
  164 +
  165 + url.addEventListener(Event.COMPLETE, function(evt:Event):void {
  166 + log(uri + " completed, total=" + stream.length + "bytes");
  167 + if (url.bytesAvailable <= 0) {
  168 + completed(stream);
  169 + return;
  170 + }
  171 +
  172 + //log(uri + " completed" + ", available=" + url.bytesAvailable);
  173 + var bytes:ByteArray = new ByteArray();
  174 + url.readBytes(bytes, 0, url.bytesAvailable);
  175 + stream.writeBytes(bytes);
  176 +
  177 + completed(stream);
  178 + });
  179 +
  180 + // we set to the query.
  181 + uri += ((uri.indexOf("?") == -1)? "?":"&") + "shp_xpsid=" + XPlaybackSessionId;
  182 + var r:URLRequest = new URLRequest(uri);
  183 + // seems flash not allow set this header.
  184 + // @remark disable it for it will cause security exception.
  185 + //r.requestHeaders.push(new URLRequestHeader("X-Playback-Session-Id", XPlaybackSessionId));
  186 +
  187 + log("start download " + uri);
  188 + url.load(r);
  189 + }
  190 +
  191 + // the uuid similar to Safari, to identify this play session.
  192 + // @see https://github.com/winlinvip/srs-plus/blob/bms/trunk/src/app/srs_app_http_stream.cpp#L45
  193 + public var XPlaybackSessionId:String = createRandomIdentifier(32);
  194 +
  195 + private function createRandomIdentifier(length:uint, radix:uint = 61):String {
  196 + var characters:Array = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  197 + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
  198 + 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
  199 + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
  200 + 'z');
  201 + var id:Array = new Array();
  202 + radix = (radix > 61) ? 61 : radix;
  203 + while (length--) {
  204 + id.push(characters[randomIntegerWithinRange(0, radix)]);
  205 + }
  206 + return id.join('');
66 } 207 }
67 208
68 - /**  
69 - * get the sequence number, the id of first ts.  
70 - */  
71 - public function get seq_no():Number  
72 - {  
73 - return m3u8.seq_no; 209 + private function randomIntegerWithinRange(min:int, max:int):int {
  210 + return Math.floor(Math.random() * (1 + max - min) + min);
74 } 211 }
75 212
76 - /**  
77 - * whether the m3u8 contains variant m3u8.  
78 - */  
79 - public function get variant():Boolean  
80 - {  
81 - return m3u8.variant; 213 + // callback for hls.
  214 + public var flvHeader:ByteArray = null;
  215 + public function onSequenceHeader():void {
  216 + var s:NetStream = super;
  217 + s.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
  218 + s.appendBytes(flvHeader);
  219 + log("FLV: sps/pps " + flvHeader.length + " bytes");
  220 +
  221 + writeFlv(flvHeader);
82 } 222 }
83 -  
84 - /**  
85 - * dumps the metadata, for example, set the width and height,  
86 - * which is decoded from sps.  
87 - */  
88 - public function dumpMetaData(metadata:Object):void  
89 - {  
90 - if (width > 0) {  
91 - metadata.width = width;  
92 - }  
93 - if (height > 0) {  
94 - metadata.height = height; 223 +
  224 + private function onFlvBody(uri:String, flv:ByteArray):void {
  225 + if (!flvHeader) {
  226 + return;
95 } 227 }
  228 +
  229 + var s:NetStream = super;
  230 + s.appendBytes(flv);
  231 + log("FLV: ts " + uri + " parsed to flv " + flv.length + " bytes");
  232 +
  233 + writeFlv(flv);
96 } 234 }
97 -  
98 - /**  
99 - * get the ts url by piece id, which is actually the piece index.  
100 - */  
101 - public function getTsUrl(pieceId:Number):String  
102 - {  
103 - return m3u8.getTsUrl(pieceId); 235 +
  236 + private function writeFlv(data:ByteArray):void {
  237 + return;
  238 +
  239 + var r:URLRequest = new URLRequest("http://192.168.1.117:8088/api/v1/flv");
  240 + r.method = URLRequestMethod.POST;
  241 + r.data = data;
  242 +
  243 + var pf:URLLoader = new URLLoader();
  244 + pf.dataFormat = URLLoaderDataFormat.BINARY;
  245 + pf.load(r);
104 } 246 }
105 -  
106 - /**  
107 - * reset the HLS when parse m3u8.  
108 - */  
109 - public function reset():void  
110 - {  
111 - avc = new SrsRawH264Stream();  
112 - h264_sps = new ByteArray();  
113 - h264_pps = new ByteArray();  
114 -  
115 - aac = new SrsRawAacStream();  
116 - aac_specific_config = new ByteArray();  
117 -  
118 - width = 0;  
119 - height = 0;  
120 -  
121 - video_sh_tag = new ByteArray();  
122 - audio_sh_tag = new ByteArray(); 247 +
  248 + private function log(msg:String):void {
  249 + msg = "[" + new Date() +"][srs-player] " + msg;
  250 +
  251 + trace(msg);
  252 +
  253 + if (!flash.external.ExternalInterface.available) {
  254 + return;
  255 + }
  256 +
  257 + ExternalInterface.call("console.log", msg);
  258 + }
  259 + }
  260 +}
  261 +
  262 +import flash.events.Event;
  263 +import flash.net.URLLoader;
  264 +import flash.net.URLRequest;
  265 +import flash.net.URLRequestMethod;
  266 +import flash.utils.ByteArray;
  267 +
  268 +/**
  269 + * the hls main class.
  270 + */
  271 +class HlsCodec
  272 +{
  273 + private var m3u8:M3u8;
  274 +
  275 + private var avc:SrsRawH264Stream;
  276 + private var h264_sps:ByteArray;
  277 + private var h264_pps:ByteArray;
  278 +
  279 + private var aac:SrsRawAacStream;
  280 + private var aac_specific_config:ByteArray;
  281 + private var width:int;
  282 + private var height:int;
  283 +
  284 + private var video_sh_tag:ByteArray;
  285 + private var audio_sh_tag:ByteArray;
  286 +
  287 + private var owner:HlsNetStream;
  288 + private var _log:ILogger = new TraceLogger("HLS");
  289 +
  290 + public static const SRS_TS_PACKET_SIZE:int = 188;
  291 +
  292 + public function HlsCodec(o:HlsNetStream)
  293 + {
  294 + owner = o;
  295 + m3u8 = new M3u8(this);
  296 +
  297 + reset();
  298 + }
  299 +
  300 + /**
  301 + * parse the m3u8.
  302 + * @param url, the m3u8 url, for m3u8 to generate the ts url.
  303 + * @param v, the m3u8 string.
  304 + */
  305 + public function parse(url:String, v:String):void
  306 + {
  307 + // TODO: FIXME: reset the hls when parse.
  308 + m3u8.parse(url, v);
  309 + }
  310 +
  311 + /**
  312 + * get the total count of ts in m3u8.
  313 + */
  314 + public function get tsCount():Number
  315 + {
  316 + return m3u8.tsCount;
  317 + }
  318 +
  319 + /**
  320 + * get the total duration in seconds of m3u8.
  321 + */
  322 + public function get duration():Number
  323 + {
  324 + return m3u8.duration;
  325 + }
  326 +
  327 + /**
  328 + * get the sequence number, the id of first ts.
  329 + */
  330 + public function get seq_no():Number
  331 + {
  332 + return m3u8.seq_no;
  333 + }
  334 +
  335 + /**
  336 + * whether the m3u8 contains variant m3u8.
  337 + */
  338 + public function get variant():Boolean
  339 + {
  340 + return m3u8.variant;
  341 + }
  342 +
  343 + /**
  344 + * dumps the metadata, for example, set the width and height,
  345 + * which is decoded from sps.
  346 + */
  347 + public function dumpMetaData(metadata:Object):void
  348 + {
  349 + if (width > 0) {
  350 + metadata.width = width;
123 } 351 }
124 -  
125 - /**  
126 - * parse the piece in hls format,  
127 - * set the piece.skip if error.  
128 - * @param onParsed, a function(piece:FlvPiece, body:ByteArray):void callback.  
129 - */  
130 - public function parseBodyAsync(piece:FlvPiece, data:ByteArray, body:ByteArray, onParsed:Function):void  
131 - { 352 + if (height > 0) {
  353 + metadata.height = height;
  354 + }
  355 + }
  356 +
  357 + /**
  358 + * get the ts url by piece id, which is actually the piece index.
  359 + */
  360 + public function getTsUrl(pieceId:Number):String
  361 + {
  362 + return m3u8.getTsUrl(pieceId);
  363 + }
  364 +
  365 + /**
  366 + * reset the HLS when parse m3u8.
  367 + */
  368 + public function reset():void
  369 + {
  370 + avc = new SrsRawH264Stream();
  371 + h264_sps = new ByteArray();
  372 + h264_pps = new ByteArray();
  373 +
  374 + aac = new SrsRawAacStream();
  375 + aac_specific_config = new ByteArray();
  376 +
  377 + width = 0;
  378 + height = 0;
  379 +
  380 + video_sh_tag = new ByteArray();
  381 + audio_sh_tag = new ByteArray();
  382 + }
  383 +
  384 + /**
  385 + * parse the piece in hls format,
  386 + * set the piece.skip if error.
  387 + * @param onParsed, a function(piece:FlvPiece, body:ByteArray):void callback.
  388 + */
  389 + public function parseBodyAsync(piece:FlvPiece, data:ByteArray, body:ByteArray, onParsed:Function):void
  390 + {
  391 + var handler:SrsTsHanlder = new SrsTsHanlder(
  392 + avc, aac,
  393 + h264_sps, h264_pps,
  394 + aac_specific_config,
  395 + video_sh_tag, audio_sh_tag,
  396 + this, body,
  397 + _on_size_changed, _on_sequence_changed
  398 + );
  399 +
  400 + // the context used to parse the whole ts file.
  401 + var context:SrsTsContext = new SrsTsContext(this);
  402 +
  403 + // we assumpt to parse the piece in 10 times.
  404 + // the total parse time is 10*AlgP2P.HlsAsyncParseTimeout
  405 + var ts_packets:uint = data.length / SRS_TS_PACKET_SIZE;
  406 + var each_parse:uint = ts_packets / 10;
  407 + var nb_parsed:uint = 0;
  408 + var aysncParse:Function = function():void {
  409 + try {
  410 + // do the parse.
  411 + doParseBody(piece, data, body, handler, context, each_parse);
  412 +
  413 + // check whether parsed.
  414 + nb_parsed += each_parse;
  415 +
  416 + if (nb_parsed < ts_packets) {
  417 + flash.utils.setTimeout(aysncParse, owner.ts_parse_async_interval);
  418 + return;
  419 + }
  420 +
  421 + // flush the messages in queue.
  422 + handler.flush_message_queue(body);
  423 +
  424 + __report(body);
  425 + _log.info("hls async parsed to flv, piece={0}, hls={1}B, flv={2}B", piece.pieceId, data.length, body.length);
  426 + } catch (e:Error) {
  427 + piece.skip = true;
  428 + _log.error("hls async parse piece={0}, exception={1}, stack={2}",
  429 + piece.pieceId, e.message, e.getStackTrace());
  430 + }
  431 +
  432 + onParsed(piece, body);
  433 + };
  434 +
  435 + aysncParse();
  436 + }
  437 +
  438 + /**
  439 + * parse the piece in hls format,
  440 + * set the piece.skip if error.
  441 + */
  442 + public function parseBody(piece:FlvPiece, data:ByteArray, body:ByteArray):void
  443 + {
  444 + try {
132 var handler:SrsTsHanlder = new SrsTsHanlder( 445 var handler:SrsTsHanlder = new SrsTsHanlder(
133 avc, aac, 446 avc, aac,
134 h264_sps, h264_pps, 447 h264_sps, h264_pps,
135 aac_specific_config, 448 aac_specific_config,
136 - video_sh_tag, audio_sh_tag, 449 + video_sh_tag, audio_sh_tag,
137 this, body, 450 this, body,
138 _on_size_changed, _on_sequence_changed 451 _on_size_changed, _on_sequence_changed
139 ); 452 );
140 - 453 +
141 // the context used to parse the whole ts file. 454 // the context used to parse the whole ts file.
142 var context:SrsTsContext = new SrsTsContext(this); 455 var context:SrsTsContext = new SrsTsContext(this);
143 -  
144 - // we assumpt to parse the piece in 10 times.  
145 - // the total parse time is 10*AlgP2P.HlsAsyncParseTimeout  
146 - var ts_packets:uint = data.length / SRS_TS_PACKET_SIZE;  
147 - var each_parse:uint = ts_packets / 10;  
148 - var nb_parsed:uint = 0;  
149 - var aysncParse:Function = function():void {  
150 - try {  
151 - // do the parse.  
152 - doParseBody(piece, data, body, handler, context, each_parse);  
153 -  
154 - // check whether parsed.  
155 - nb_parsed += each_parse;  
156 -  
157 - if (nb_parsed < ts_packets) {  
158 - flash.utils.setTimeout(aysncParse, Consts.TsParseAsyncInterval);  
159 - return;  
160 - }  
161 -  
162 - // flush the messages in queue.  
163 - handler.flush_message_queue(body);  
164 -  
165 - __report(body);  
166 - _log.info("hls async parsed to flv, piece={0}, hls={1}B, flv={2}B", piece.pieceId, data.length, body.length);  
167 - } catch (e:Error) {  
168 - piece.skip = true;  
169 - _log.error("hls async parse piece={0}, exception={1}, stack={2}",  
170 - piece.pieceId, e.message, e.getStackTrace());  
171 - }  
172 -  
173 - onParsed(piece, body);  
174 - };  
175 -  
176 - aysncParse(); 456 +
  457 + // do the parse.
  458 + doParseBody(piece, data, body, handler, context, -1);
  459 +
  460 + // flush the messages in queue.
  461 + handler.flush_message_queue(body);
  462 +
  463 + __report(body);
  464 + _log.info("hls sync parsed to flv, piece={0}, hls={1}B, flv={2}B", piece.pieceId, data.length, body.length);
  465 + } catch (e:Error) {
  466 + piece.skip = true;
  467 + _log.error("hls sync parse piece={0}, exception={1}, stack={2}",
  468 + piece.pieceId, e.message, e.getStackTrace());
177 } 469 }
178 -  
179 - /**  
180 - * parse the piece in hls format,  
181 - * set the piece.skip if error.  
182 - */  
183 - public function parseBody(piece:FlvPiece, data:ByteArray, body:ByteArray):void  
184 - {  
185 - try {  
186 - var handler:SrsTsHanlder = new SrsTsHanlder(  
187 - avc, aac,  
188 - h264_sps, h264_pps,  
189 - aac_specific_config,  
190 - video_sh_tag, audio_sh_tag,  
191 - this, body,  
192 - _on_size_changed, _on_sequence_changed  
193 - );  
194 -  
195 - // the context used to parse the whole ts file.  
196 - var context:SrsTsContext = new SrsTsContext(this);  
197 -  
198 - // do the parse.  
199 - doParseBody(piece, data, body, handler, context, -1);  
200 -  
201 - // flush the messages in queue.  
202 - handler.flush_message_queue(body);  
203 -  
204 - __report(body);  
205 - _log.info("hls sync parsed to flv, piece={0}, hls={1}B, flv={2}B", piece.pieceId, data.length, body.length);  
206 - } catch (e:Error) {  
207 - piece.skip = true;  
208 - _log.error("hls sync parse piece={0}, exception={1}, stack={2}",  
209 - piece.pieceId, e.message, e.getStackTrace()); 470 + }
  471 +
  472 + private function _on_size_changed(w:int, h:int):void
  473 + {
  474 + width = w;
  475 + height = h;
  476 + }
  477 +
  478 + private function _on_sequence_changed(
  479 + pavc:SrsRawH264Stream, paac:SrsRawAacStream,
  480 + ph264_sps:ByteArray, ph264_pps:ByteArray,
  481 + paac_specific_config:ByteArray,
  482 + pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray,
  483 + sh:ByteArray):void
  484 + {
  485 + // when sequence header not changed, ignore.
  486 + if (SrsUtils.array_equals(h264_sps, ph264_sps)) {
  487 + if (SrsUtils.array_equals(h264_pps, ph264_pps)) {
  488 + if (SrsUtils.array_equals(aac_specific_config, paac_specific_config)) {
  489 + return;
  490 + }
210 } 491 }
211 } 492 }
212 -  
213 - private function _on_size_changed(w:int, h:int):void 493 +
  494 + avc = pavc;
  495 + h264_sps = ph264_sps;
  496 + h264_pps = ph264_pps;
  497 +
  498 + aac = paac;
  499 + aac_specific_config = paac_specific_config;
  500 +
  501 + video_sh_tag = pvideo_sh_tag;
  502 + audio_sh_tag = paudio_sh_tag;
  503 +
  504 + _log.info("hls: got sequence header, ash={0}B, bsh={1}B", audio_sh_tag.length, video_sh_tag.length);
  505 + owner.flvHeader = sh;
  506 + owner.onSequenceHeader();
  507 +
  508 + __report(sh);
  509 + }
  510 +
  511 + /**
  512 + * do the parse.
  513 + * @maxTsPackets the max ts packets to parse, stop when exceed this ts packet.
  514 + * -1 to parse all packets.
  515 + */
  516 + private function doParseBody(
  517 + piece:FlvPiece, data:ByteArray, body:ByteArray,
  518 + handler:SrsTsHanlder, context:SrsTsContext, maxTsPackets:int):void
  519 + {
  520 + for (var i:int = 0; (maxTsPackets == -1 || i < maxTsPackets) && data.bytesAvailable > 0; i++) {
  521 + var tsBytes:ByteArray = new ByteArray();
  522 + data.readBytes(tsBytes, 0, HlsCodec.SRS_TS_PACKET_SIZE);
  523 + context.decode(tsBytes, handler);
  524 + }
  525 + }
  526 +
  527 + private function __report(flv:ByteArray):void
  528 + {
  529 + // report only for debug.
  530 + return;
  531 +
  532 + var url:URLRequest = new URLRequest("http://192.168.10.108:1980/api/v3/file");
  533 + url.data = flv;
  534 + url.method = URLRequestMethod.POST;
  535 +
  536 + var loader:URLLoader = new URLLoader();
  537 + loader.addEventListener(Event.COMPLETE, function(e:Event):void {
  538 + loader.close();
  539 + });
  540 + loader.load(url);
  541 + }
  542 +}
  543 +
  544 +import flash.utils.Dictionary;
  545 +
  546 +class Dict
  547 +{
  548 + private var _dict:Dictionary;
  549 + private var _size:uint;
  550 +
  551 + public function Dict()
  552 + {
  553 + clear();
  554 + }
  555 +
  556 + /**
  557 + * get the underlayer dict.
  558 + * @remark for core-ng.
  559 + */
  560 + public function get dict():Dictionary
  561 + {
  562 + return _dict;
  563 + }
  564 +
  565 + public function has(key:Object):Boolean
  566 + {
  567 + return (key in _dict);
  568 + }
  569 +
  570 + public function get(key:Object):Object
  571 + {
  572 + return ((key in _dict) ? _dict[key] : null);
  573 + }
  574 +
  575 + public function set(key:Object, object:Object):void
  576 + {
  577 + if (!(key in _dict))
214 { 578 {
215 - width = w;  
216 - height = h; 579 + _size++;
217 } 580 }
218 -  
219 - private function _on_sequence_changed(  
220 - pavc:SrsRawH264Stream, paac:SrsRawAacStream,  
221 - ph264_sps:ByteArray, ph264_pps:ByteArray,  
222 - paac_specific_config:ByteArray,  
223 - pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray,  
224 - sh:ByteArray):void 581 + _dict[key] = object;
  582 + }
  583 +
  584 + public function remove(key:Object):Object
  585 + {
  586 + var object:Object;
  587 + if (key in _dict)
225 { 588 {
226 - // when sequence header not changed, ignore.  
227 - if (SrsUtils.array_equals(h264_sps, ph264_sps)) {  
228 - if (SrsUtils.array_equals(h264_pps, ph264_pps)) {  
229 - if (SrsUtils.array_equals(aac_specific_config, paac_specific_config)) {  
230 - return;  
231 - }  
232 - }  
233 - }  
234 -  
235 - avc = pavc;  
236 - h264_sps = ph264_sps;  
237 - h264_pps = ph264_pps;  
238 -  
239 - aac = paac;  
240 - aac_specific_config = paac_specific_config;  
241 -  
242 - video_sh_tag = pvideo_sh_tag;  
243 - audio_sh_tag = paudio_sh_tag;  
244 -  
245 - _log.info("hls: got sequence header, ash={0}B, bsh={1}B", audio_sh_tag.length, video_sh_tag.length);  
246 - owner.flvHeader = sh;  
247 - owner.onSequenceHeader();  
248 -  
249 - __report(sh); 589 + object = _dict[key];
  590 + delete _dict[key];
  591 + _size--;
250 } 592 }
251 -  
252 - /**  
253 - * do the parse.  
254 - * @maxTsPackets the max ts packets to parse, stop when exceed this ts packet.  
255 - * -1 to parse all packets.  
256 - */  
257 - private function doParseBody(  
258 - piece:FlvPiece, data:ByteArray, body:ByteArray,  
259 - handler:SrsTsHanlder, context:SrsTsContext, maxTsPackets:int):void 593 + return (object);
  594 + }
  595 +
  596 + public function keys():Array
  597 + {
  598 + var array:Array = new Array(_size);
  599 + var index:int;
  600 + for (var key:Object in _dict)
260 { 601 {
261 - for (var i:int = 0; (maxTsPackets == -1 || i < maxTsPackets) && data.bytesAvailable > 0; i++) {  
262 - var tsBytes:ByteArray = new ByteArray();  
263 - data.readBytes(tsBytes, 0, Hls.SRS_TS_PACKET_SIZE);  
264 - context.decode(tsBytes, handler);  
265 - } 602 + var _local6:int = index++;
  603 + array[_local6] = key;
266 } 604 }
267 -  
268 - private function __report(flv:ByteArray):void 605 + return (array);
  606 + }
  607 +
  608 + public function values():Array
  609 + {
  610 + var array:Array = new Array(_size);
  611 + var index:int;
  612 + for each (var value:Object in _dict)
  613 + {
  614 + var _local6:int = index++;
  615 + array[_local6] = value;
  616 + };
  617 + return (array);
  618 + }
  619 +
  620 + public function clear():void
  621 + {
  622 + _dict = new Dictionary();
  623 + _size = 0;
  624 + }
  625 +
  626 + public function toArray():Array
  627 + {
  628 + var array:Array = new Array(_size * 2);
  629 + var index:int;
  630 + for (var key:Object in _dict)
269 { 631 {
270 - // report only for debug. 632 + var _local6:int = index++;
  633 + array[_local6] = key;
  634 + var _local7:int = index++;
  635 + array[_local7] = _dict[key];
  636 + };
  637 + return (array);
  638 + }
  639 +
  640 + public function toObject():Object
  641 + {
  642 + return (toArray());
  643 + }
  644 +
  645 + public function fromObject(object:Object):void
  646 + {
  647 + clear();
  648 + var index:uint;
  649 + while (index < (object as Array).length) {
  650 + set((object as Array)[index], (object as Array)[(index + 1)]);
  651 + index += 2;
  652 + };
  653 + }
  654 +
  655 + public function get size():uint
  656 + {
  657 + return (_size);
  658 + }
  659 +
  660 +}
  661 +
  662 +import flash.utils.ByteArray;
  663 +
  664 +/**
  665 + * a piece of flv, fetch from cdn or p2p.
  666 + */
  667 +class FlvPiece
  668 +{
  669 + private var _pieceId:Number;
  670 + protected var _flv:ByteArray;
  671 + /**
  672 + * the private object for the channel,
  673 + * for example, the cdn channel will set to CdnEdge object.
  674 + */
  675 + private var _privateObject:Object;
  676 + /**
  677 + * when encoder error, this piece cannot be generated,
  678 + * and it should be skip. default to false.
  679 + */
  680 + private var _skip:Boolean;
  681 +
  682 + public function FlvPiece(pieceId:Number)
  683 + {
  684 + _pieceId = pieceId;
  685 + _flv = null;
  686 + _skip = false;
  687 + }
  688 +
  689 + /**
  690 + * when piece is fetch ok.
  691 + */
  692 + public function onPieceDone(flv:ByteArray):void
  693 + {
  694 + // save body.
  695 + _flv = flv;
  696 + }
  697 +
  698 + /**
  699 + * when piece is fetch error.
  700 + */
  701 + public function onPieceError():void
  702 + {
  703 + }
  704 +
  705 + /**
  706 + * when piece is empty.
  707 + */
  708 + public function onPieceEmpty():void
  709 + {
  710 + }
  711 +
  712 + /**
  713 + * destroy the object, set reference to null.
  714 + */
  715 + public function destroy():void
  716 + {
  717 + _privateObject = null;
  718 + _flv = null;
  719 + }
  720 +
  721 + public function get privateObject():Object
  722 + {
  723 + return _privateObject;
  724 + }
  725 +
  726 + public function set privateObject(v:Object):void
  727 + {
  728 + _privateObject = v;
  729 + }
  730 +
  731 + public function get skip():Boolean
  732 + {
  733 + return _skip;
  734 + }
  735 +
  736 + public function set skip(v:Boolean):void
  737 + {
  738 + _skip = v;
  739 + }
  740 +
  741 + public function get pieceId():Number
  742 + {
  743 + return _pieceId;
  744 + }
  745 +
  746 + public function get flv():ByteArray
  747 + {
  748 + return _flv;
  749 + }
  750 +
  751 + public function get completed():Boolean
  752 + {
  753 + return _flv != null;
  754 + }
  755 +}
  756 +
  757 +interface ILogger
  758 +{
  759 + function debug0(message:String, ... rest):void;
  760 + function debug(message:String, ... rest):void;
  761 + function info(message:String, ... rest):void;
  762 + function warn(message:String, ... rest):void;
  763 + function error(message:String, ... rest):void;
  764 + function fatal(message:String, ... rest):void;
  765 +}
  766 +
  767 +import flash.globalization.DateTimeFormatter;
  768 +import flash.external.ExternalInterface;
  769 +
  770 +class TraceLogger implements ILogger
  771 +{
  772 + private var _category:String;
  773 +
  774 + public function get category():String
  775 + {
  776 + return _category;
  777 + }
  778 + public function TraceLogger(category:String)
  779 + {
  780 + _category = category;
  781 + }
  782 + public function debug0(message:String, ...rest):void
  783 + {
  784 + }
  785 +
  786 + public function debug(message:String, ...rest):void
  787 + {
  788 + }
  789 +
  790 + public function info(message:String, ...rest):void
  791 + {
  792 + logMessage(LEVEL_INFO, message, rest);
  793 + }
  794 +
  795 + public function warn(message:String, ...rest):void
  796 + {
  797 + logMessage(LEVEL_WARN, message, rest);
  798 + }
  799 +
  800 + public function error(message:String, ...rest):void
  801 + {
  802 + logMessage(LEVEL_ERROR, message, rest);
  803 + }
  804 +
  805 + public function fatal(message:String, ...rest):void
  806 + {
  807 + logMessage(LEVEL_FATAL, message, rest);
  808 + }
  809 + protected function logMessage(level:String, message:String, params:Array):void
  810 + {
  811 + var msg:String = "";
  812 +
  813 + // add datetime
  814 + var date:Date = new Date();
  815 + var dtf:DateTimeFormatter = new DateTimeFormatter("UTC");
  816 + dtf.setDateTimePattern("yyyy-MM-dd HH:mm:ss");
  817 +
  818 + // TODO: FIXME: the SSS format not run, use date.milliseconds instead.
  819 + msg += '[' + dtf.format(date) + "." + date.milliseconds + ']';
  820 + msg += " [" + level + "] ";
  821 +
  822 + // add category and params
  823 + msg += "[" + category + "] " + applyParams(message, params);
  824 +
  825 + // trace the message
  826 + trace(msg);
  827 +
  828 + if (!flash.external.ExternalInterface.available) {
271 return; 829 return;
272 -  
273 - var url:URLRequest = new URLRequest("http://192.168.10.108:1980/api/v3/file");  
274 - url.data = flv;  
275 - url.method = URLRequestMethod.POST;  
276 -  
277 - var loader:URLLoader = new URLLoader();  
278 - loader.addEventListener(Event.COMPLETE, function(e:Event):void {  
279 - loader.close();  
280 - });  
281 - loader.load(url);  
282 } 830 }
  831 +
  832 + ExternalInterface.call("console.log", msg);
  833 + }
  834 + private function leadingZeros(x:Number):String
  835 + {
  836 + if (x < 10) {
  837 + return "00" + x.toString();
  838 + }
  839 +
  840 + if (x < 100) {
  841 + return "0" + x.toString();
  842 + }
  843 +
  844 + return x.toString();
283 } 845 }
  846 + private function applyParams(message:String, params:Array):String
  847 + {
  848 + var result:String = message;
  849 +
  850 + var numParams:int = params.length;
  851 +
  852 + for (var i:int = 0; i < numParams; i++) {
  853 + result = result.replace(new RegExp("\\{" + i + "\\}", "g"), params[i]);
  854 + }
  855 + return result;
  856 + }
  857 +
  858 + private static const LEVEL_DEBUG:String = "DEBUG";
  859 + private static const LEVEL_WARN:String = "WARN";
  860 + private static const LEVEL_INFO:String = "INFO";
  861 + private static const LEVEL_ERROR:String = "ERROR";
  862 + private static const LEVEL_FATAL:String = "FATAL";
284 } 863 }
285 864
286 import flash.utils.ByteArray; 865 import flash.utils.ByteArray;
@@ -304,7 +883,7 @@ class SrsTsHanlder implements ISrsTsHandler @@ -304,7 +883,7 @@ class SrsTsHanlder implements ISrsTsHandler
304 private var queue:Array; 883 private var queue:Array;
305 884
306 // hls data. 885 // hls data.
307 - private var _hls:Hls; 886 + private var _hls:HlsCodec;
308 private var _body:ByteArray; 887 private var _body:ByteArray;
309 private var _on_size_changed:Function; 888 private var _on_size_changed:Function;
310 private var _on_sequence_changed:Function; 889 private var _on_sequence_changed:Function;
@@ -316,7 +895,7 @@ class SrsTsHanlder implements ISrsTsHandler @@ -316,7 +895,7 @@ class SrsTsHanlder implements ISrsTsHandler
316 ph264_sps:ByteArray, ph264_pps:ByteArray, 895 ph264_sps:ByteArray, ph264_pps:ByteArray,
317 paac_specific_config:ByteArray, 896 paac_specific_config:ByteArray,
318 pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray, 897 pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray,
319 - hls:Hls, body:ByteArray, oszc:Function, oshc:Function) 898 + hls:HlsCodec, body:ByteArray, oszc:Function, oshc:Function)
320 { 899 {
321 _hls = hls; 900 _hls = hls;
322 _body = body; 901 _body = body;
@@ -2383,7 +2962,7 @@ interface ISrsTsHandler @@ -2383,7 +2962,7 @@ interface ISrsTsHandler
2383 */ 2962 */
2384 class SrsTsContext 2963 class SrsTsContext
2385 { 2964 {
2386 - private var _hls:Hls; 2965 + private var _hls:HlsCodec;
2387 2966
2388 // codec 2967 // codec
2389 // key, a Number indicates the pid, 2968 // key, a Number indicates the pid,
@@ -2393,7 +2972,7 @@ class SrsTsContext @@ -2393,7 +2972,7 @@ class SrsTsContext
2393 // whether hls pure audio stream. 2972 // whether hls pure audio stream.
2394 private var _pure_audio:Boolean; 2973 private var _pure_audio:Boolean;
2395 2974
2396 - public function SrsTsContext(hls:Hls) 2975 + public function SrsTsContext(hls:HlsCodec)
2397 { 2976 {
2398 _hls = hls; 2977 _hls = hls;
2399 _pure_audio = false; 2978 _pure_audio = false;
@@ -2620,7 +3199,7 @@ class SrsTsPacket @@ -2620,7 +3199,7 @@ class SrsTsPacket
2620 } 3199 }
2621 3200
2622 // calc the user defined data size for payload. 3201 // calc the user defined data size for payload.
2623 - var nb_payload:int = Hls.SRS_TS_PACKET_SIZE - (stream.position - pos); 3202 + var nb_payload:int = HlsCodec.SRS_TS_PACKET_SIZE - (stream.position - pos);
2624 3203
2625 // optional: payload. 3204 // optional: payload.
2626 if (adaption_field_control == SrsTsAdaptationFieldType.PayloadOnly 3205 if (adaption_field_control == SrsTsAdaptationFieldType.PayloadOnly
@@ -4583,7 +5162,7 @@ class SrsTsPayloadPMT extends SrsTsPayloadPSI @@ -4583,7 +5162,7 @@ class SrsTsPayloadPMT extends SrsTsPayloadPSI
4583 */ 5162 */
4584 class M3u8 5163 class M3u8
4585 { 5164 {
4586 - private var _hls:Hls; 5165 + private var _hls:HlsCodec;
4587 private var _log:ILogger = new TraceLogger("HLS"); 5166 private var _log:ILogger = new TraceLogger("HLS");
4588 5167
4589 private var _tses:Array; 5168 private var _tses:Array;
@@ -4593,7 +5172,7 @@ class M3u8 @@ -4593,7 +5172,7 @@ class M3u8
4593 // when variant, all ts url is sub m3u8 url. 5172 // when variant, all ts url is sub m3u8 url.
4594 private var _variant:Boolean; 5173 private var _variant:Boolean;
4595 5174
4596 - public function M3u8(hls:Hls) 5175 + public function M3u8(hls:HlsCodec)
4597 { 5176 {
4598 _hls = hls; 5177 _hls = hls;
4599 _tses = new Array(); 5178 _tses = new Array();
1 -package  
2 -{  
3 - public interface ILogger  
4 - {  
5 - function debug0(message:String, ... rest):void;  
6 - function debug(message:String, ... rest):void;  
7 - function info(message:String, ... rest):void;  
8 - function warn(message:String, ... rest):void;  
9 - function error(message:String, ... rest):void;  
10 - function fatal(message:String, ... rest):void;  
11 - }  
12 -}  
1 -package  
2 -{  
3 - import flash.net.NetStream;  
4 -  
5 - /**  
6 - * the player interface.  
7 - */  
8 - public interface IPlayer  
9 - {  
10 - /**  
11 - * initialize the player by flashvars for config.  
12 - * @param flashvars the config.  
13 - */  
14 - function init(flashvars:Object):void;  
15 -  
16 - /**  
17 - * get the NetStream to play the stream.  
18 - * @return the underlayer stream object.  
19 - */  
20 - function stream():NetStream;  
21 -  
22 - /**  
23 - * connect and play url.  
24 - * @param url the stream url to play.  
25 - */  
26 - function play(url:String):void;  
27 -  
28 - /**  
29 - * close the player.  
30 - */  
31 - function close():void;  
32 - }  
33 -}  
1 -package  
2 -{  
3 - import flash.display.Sprite;  
4 - import flash.display.StageAlign;  
5 - import flash.display.StageDisplayState;  
6 - import flash.display.StageScaleMode;  
7 - import flash.events.Event;  
8 - import flash.events.FullScreenEvent;  
9 - import flash.events.MouseEvent;  
10 - import flash.events.NetStatusEvent;  
11 - import flash.events.ProgressEvent;  
12 - import flash.events.TimerEvent;  
13 - import flash.external.ExternalInterface;  
14 - import flash.media.SoundTransform;  
15 - import flash.media.Video;  
16 - import flash.net.NetConnection;  
17 - import flash.net.NetStream;  
18 - import flash.net.NetStreamAppendBytesAction;  
19 - import flash.net.URLLoader;  
20 - import flash.net.URLLoaderDataFormat;  
21 - import flash.net.URLRequest;  
22 - import flash.net.URLRequestHeader;  
23 - import flash.net.URLRequestMethod;  
24 - import flash.net.URLStream;  
25 - import flash.net.URLVariables;  
26 - import flash.system.Security;  
27 - import flash.ui.ContextMenu;  
28 - import flash.ui.ContextMenuItem;  
29 - import flash.utils.ByteArray;  
30 - import flash.utils.Timer;  
31 - import flash.utils.getTimer;  
32 - import flash.utils.setTimeout;  
33 -  
34 - import flashx.textLayout.formats.Float;  
35 -  
36 - /**  
37 - * the m3u8 player.  
38 - */  
39 - public class M3u8Player implements IPlayer  
40 - {  
41 - private var js_id:String = null;  
42 -  
43 - // play param url.  
44 - private var user_url:String = null;  
45 -  
46 - private var media_stream:NetStream = null;  
47 - private var media_conn:NetConnection = null;  
48 -  
49 - private var owner:srs_player = null;  
50 - private var hls:Hls = null; // parse m3u8 and ts  
51 -  
52 - // the uuid similar to Safari, to identify this play session.  
53 - // @see https://github.com/winlinvip/srs-plus/blob/bms/trunk/src/app/srs_app_http_stream.cpp#L45  
54 - public var XPlaybackSessionId:String = createRandomIdentifier(32);  
55 - private function createRandomIdentifier(length:uint, radix:uint = 61):String {  
56 - var characters:Array = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',  
57 - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',  
58 - 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',  
59 - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',  
60 - 'z');  
61 - var id:Array = new Array();  
62 - radix = (radix > 61) ? 61 : radix;  
63 - while (length--) {  
64 - id.push(characters[randomIntegerWithinRange(0, radix)]);  
65 - }  
66 - return id.join('');  
67 - }  
68 - private function randomIntegerWithinRange(min:int, max:int):int {  
69 - return Math.floor(Math.random() * (1 + max - min) + min);  
70 - }  
71 -  
72 - // callback for hls.  
73 - public var flvHeader:ByteArray = null;  
74 - public function onSequenceHeader():void {  
75 - if (!media_stream) {  
76 - setTimeout(onSequenceHeader, 1000);  
77 - return;  
78 - }  
79 -  
80 - var s:NetStream = media_stream;  
81 - s.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);  
82 - s.appendBytes(flvHeader);  
83 - log("FLV: sps/pps " + flvHeader.length + " bytes");  
84 -  
85 - writeFlv(flvHeader);  
86 - }  
87 - public function onFlvBody(uri:String, flv:ByteArray):void {  
88 - if (!media_stream) {  
89 - return;  
90 - }  
91 -  
92 - if (!flvHeader) {  
93 - return;  
94 - }  
95 -  
96 - var s:NetStream = media_stream;  
97 - s.appendBytes(flv);  
98 - log("FLV: ts " + uri + " parsed to flv " + flv.length + " bytes");  
99 -  
100 - writeFlv(flv);  
101 - }  
102 - private function writeFlv(data:ByteArray):void {  
103 - return;  
104 -  
105 - var r:URLRequest = new URLRequest("http://192.168.1.117:8088/api/v1/flv");  
106 - r.method = URLRequestMethod.POST;  
107 - r.data = data;  
108 -  
109 - var pf:URLLoader = new URLLoader();  
110 - pf.dataFormat = URLLoaderDataFormat.BINARY;  
111 - pf.load(r);  
112 - }  
113 -  
114 - public function M3u8Player(o:srs_player) {  
115 - owner = o;  
116 - hls = new Hls(this);  
117 - }  
118 -  
119 - public function init(flashvars:Object):void {  
120 - this.js_id = flashvars.id;  
121 - }  
122 -  
123 - public function stream():NetStream {  
124 - return this.media_stream;  
125 - }  
126 -  
127 - // owner.on_player_metadata(evt.info.data);  
128 - public function play(url:String):void {  
129 - var streamName:String;  
130 - this.user_url = url;  
131 -  
132 - this.media_conn = new NetConnection();  
133 - this.media_conn.client = {};  
134 - this.media_conn.client.onBWDone = function():void {};  
135 - this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {  
136 - log("NetConnection: code=" + evt.info.code + ", data is " + evt.info.data);  
137 -  
138 - // TODO: FIXME: failed event.  
139 - if (evt.info.code != "NetConnection.Connect.Success") {  
140 - return;  
141 - }  
142 -  
143 - media_stream = new NetStream(media_conn);  
144 - media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {  
145 - log("NetStream: code=" + evt.info.code);  
146 -  
147 - if (evt.info.code == "NetStream.Video.DimensionChange") {  
148 - owner.on_player_dimension_change();  
149 - } else if (evt.info.code == "NetStream.Buffer.Empty") {  
150 - owner.on_player_buffer_empty();  
151 - } else if (evt.info.code == "NetStream.Buffer.Full") {  
152 - owner.on_player_buffer_full();  
153 - }  
154 -  
155 - // TODO: FIXME: failed event.  
156 - });  
157 -  
158 - // setup stream before play.  
159 - owner.on_player_before_play();  
160 -  
161 - media_stream.play(null);  
162 - refresh_m3u8();  
163 -  
164 - owner.on_player_play();  
165 - });  
166 -  
167 - this.media_conn.connect(null);  
168 - }  
169 -  
170 - public function close():void {  
171 - if (this.media_stream) {  
172 - this.media_stream.close();  
173 - this.media_stream = null;  
174 - }  
175 - if (this.media_conn) {  
176 - this.media_conn.close();  
177 - this.media_conn = null;  
178 - }  
179 - }  
180 -  
181 - private var parsed_ts_seq_no:Number = -1;  
182 - private function refresh_m3u8():void {  
183 - download(user_url, function(stream:ByteArray):void {  
184 - var m3u8:String = stream.toString();  
185 - hls.parse(user_url, m3u8);  
186 -  
187 - // redirect by variant m3u8.  
188 - if (hls.variant) {  
189 - var smu:String = hls.getTsUrl(0);  
190 - log("variant hls=" + user_url + ", redirect2=" + smu);  
191 - user_url = smu;  
192 - setTimeout(refresh_m3u8, 0);  
193 - return;  
194 - }  
195 -  
196 - // fetch from the last one.  
197 - if (parsed_ts_seq_no == -1) {  
198 - parsed_ts_seq_no = hls.seq_no + hls.tsCount - 1;  
199 - }  
200 -  
201 - // not changed.  
202 - if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {  
203 - refresh_ts();  
204 - return;  
205 - }  
206 -  
207 - // parse each ts.  
208 - var nb_ts:Number = hls.seq_no + hls.tsCount - parsed_ts_seq_no;  
209 - log("m3u8 changed, got " + nb_ts + " new ts, count=" + hls.tsCount + ", seqno=" + hls.seq_no + ", parsed=" + parsed_ts_seq_no);  
210 -  
211 - refresh_ts();  
212 - })  
213 - }  
214 - private function refresh_ts():void {  
215 - // all ts parsed.  
216 - if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {  
217 - var to:Number = 1000;  
218 - if (hls.tsCount > 0) {  
219 - to = hls.duration * 1000 / hls.tsCount * Consts.M3u8RefreshRatio;  
220 - }  
221 - setTimeout(refresh_m3u8, to);  
222 - log("m3u8 not changed, retry after " + to.toFixed(2) + "ms");  
223 - return;  
224 - }  
225 -  
226 - // parse current ts.  
227 - var uri:String = hls.getTsUrl(parsed_ts_seq_no - hls.seq_no);  
228 -  
229 - // parse metadata from uri.  
230 - if (uri.indexOf("?") >= 0) {  
231 - var uv:URLVariables = new URLVariables(uri.substr(uri.indexOf("?") + 1));  
232 - var obj:Object = {};  
233 - for (var k:String in uv) {  
234 - var v:String = uv[k];  
235 - if (k == "shp_sip1") {  
236 - obj.srs_server_ip = v;  
237 - } else if (k == "shp_cid") {  
238 - obj.srs_id = v;  
239 - } else if (k == "shp_pid") {  
240 - obj.srs_pid = v;  
241 - }  
242 - //log("uv[" + k + "]=" + v);  
243 - }  
244 - owner.on_player_metadata(obj);  
245 - }  
246 -  
247 - download(uri, function(stream:ByteArray):void{  
248 - log("got ts seqno=" + parsed_ts_seq_no + ", " + stream.length + " bytes");  
249 -  
250 - var flv:FlvPiece = new FlvPiece(parsed_ts_seq_no);  
251 - var body:ByteArray = new ByteArray();  
252 - stream.position = 0;  
253 - hls.parseBodyAsync(flv, stream, body, function():void{  
254 - body.position = 0;  
255 - //log("ts parsed, seqno=" + parsed_ts_seq_no + ", flv=" + body.length + "B");  
256 - onFlvBody(uri, body);  
257 -  
258 - parsed_ts_seq_no++;  
259 - setTimeout(refresh_ts, 0);  
260 - });  
261 - });  
262 - }  
263 - private function download(uri:String, completed:Function):void {  
264 - var url:URLStream = new URLStream();  
265 - var stream:ByteArray = new ByteArray();  
266 -  
267 - url.addEventListener(ProgressEvent.PROGRESS, function(evt:ProgressEvent):void {  
268 - if (url.bytesAvailable <= 0) {  
269 - return;  
270 - }  
271 -  
272 - //log(uri + " total=" + evt.bytesTotal + ", loaded=" + evt.bytesLoaded + ", available=" + url.bytesAvailable);  
273 - var bytes:ByteArray = new ByteArray();  
274 - url.readBytes(bytes, 0, url.bytesAvailable);  
275 - stream.writeBytes(bytes);  
276 - });  
277 -  
278 - url.addEventListener(Event.COMPLETE, function(evt:Event):void {  
279 - log(uri + " completed, total=" + stream.length + "bytes");  
280 - if (url.bytesAvailable <= 0) {  
281 - completed(stream);  
282 - return;  
283 - }  
284 -  
285 - //log(uri + " completed" + ", available=" + url.bytesAvailable);  
286 - var bytes:ByteArray = new ByteArray();  
287 - url.readBytes(bytes, 0, url.bytesAvailable);  
288 - stream.writeBytes(bytes);  
289 -  
290 - completed(stream);  
291 - });  
292 -  
293 - // we set to the query.  
294 - uri += ((uri.indexOf("?") == -1)? "?":"&") + "shp_xpsid=" + XPlaybackSessionId;  
295 - var r:URLRequest = new URLRequest(uri);  
296 - // seems flash not allow set this header.  
297 - r.requestHeaders.push(new URLRequestHeader("X-Playback-Session-Id", XPlaybackSessionId));  
298 -  
299 - log("start download " + uri);  
300 - url.load(r);  
301 - }  
302 -  
303 - private function log(msg:String):void {  
304 - Utility.log(js_id, msg);  
305 - }  
306 - }  
307 -}  
@@ -28,8 +28,14 @@ package @@ -28,8 +28,14 @@ package
28 * common player to play rtmp/flv stream, 28 * common player to play rtmp/flv stream,
29 * use system NetStream. 29 * use system NetStream.
30 */ 30 */
31 - public class CommonPlayer implements IPlayer 31 + public class Player
32 { 32 {
  33 + // refresh every ts_fragment_seconds*M3u8RefreshRatio
  34 + public static var M3u8RefreshRatio:Number = 0.5;
  35 +
  36 + // parse ts every this ms.
  37 + public static var TsParseAsyncInterval:Number = 80;
  38 +
33 private var js_id:String = null; 39 private var js_id:String = null;
34 40
35 // play param url. 41 // play param url.
@@ -40,7 +46,7 @@ package @@ -40,7 +46,7 @@ package
40 46
41 private var owner:srs_player = null; 47 private var owner:srs_player = null;
42 48
43 - public function CommonPlayer(o:srs_player) { 49 + public function Player(o:srs_player) {
44 owner = o; 50 owner = o;
45 } 51 }
46 52
@@ -88,7 +94,11 @@ package @@ -88,7 +94,11 @@ package
88 return; 94 return;
89 } 95 }
90 96
91 - media_stream = new NetStream(media_conn); 97 + if (url.indexOf(".m3u8") > 0) {
  98 + media_stream = new HlsNetStream(M3u8RefreshRatio, TsParseAsyncInterval, media_conn);
  99 + } else {
  100 + media_stream = new NetStream(media_conn);
  101 + }
92 media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void { 102 media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
93 log("NetStream: code=" + evt.info.code); 103 log("NetStream: code=" + evt.info.code);
94 104
1 -package  
2 -{  
3 - import flash.globalization.DateTimeFormatter;  
4 -  
5 - public class TraceLogger implements ILogger  
6 - {  
7 - private var _category:String;  
8 -  
9 - public function get category():String  
10 - {  
11 - return _category;  
12 - }  
13 - public function TraceLogger(category:String)  
14 - {  
15 - _category = category;  
16 - }  
17 - public function debug0(message:String, ...rest):void  
18 - {  
19 - }  
20 -  
21 - public function debug(message:String, ...rest):void  
22 - {  
23 - }  
24 -  
25 - public function info(message:String, ...rest):void  
26 - {  
27 - logMessage(LEVEL_INFO, message, rest);  
28 - }  
29 -  
30 - public function warn(message:String, ...rest):void  
31 - {  
32 - logMessage(LEVEL_WARN, message, rest);  
33 - }  
34 -  
35 - public function error(message:String, ...rest):void  
36 - {  
37 - logMessage(LEVEL_ERROR, message, rest);  
38 - }  
39 -  
40 - public function fatal(message:String, ...rest):void  
41 - {  
42 - logMessage(LEVEL_FATAL, message, rest);  
43 - }  
44 - protected function logMessage(level:String, message:String, params:Array):void  
45 - {  
46 - var msg:String = "";  
47 -  
48 - // add datetime  
49 - var date:Date = new Date();  
50 - var dtf:DateTimeFormatter = new DateTimeFormatter("UTC");  
51 - dtf.setDateTimePattern("yyyy-MM-dd HH:mm:ss");  
52 -  
53 - // TODO: FIXME: the SSS format not run, use date.milliseconds instead.  
54 - msg += '[' + dtf.format(date) + "." + date.milliseconds + ']';  
55 - msg += " [" + level + "] ";  
56 -  
57 - // add category and params  
58 - msg += "[" + category + "] " + applyParams(message, params);  
59 -  
60 - // trace the message  
61 - Utility.log("CORE", msg);  
62 - }  
63 - private function leadingZeros(x:Number):String  
64 - {  
65 - if (x < 10) {  
66 - return "00" + x.toString();  
67 - }  
68 -  
69 - if (x < 100) {  
70 - return "0" + x.toString();  
71 - }  
72 -  
73 - return x.toString();  
74 - }  
75 - private function applyParams(message:String, params:Array):String  
76 - {  
77 - var result:String = message;  
78 -  
79 - var numParams:int = params.length;  
80 -  
81 - for (var i:int = 0; i < numParams; i++) {  
82 - result = result.replace(new RegExp("\\{" + i + "\\}", "g"), params[i]);  
83 - }  
84 - return result;  
85 - }  
86 -  
87 - private static const LEVEL_DEBUG:String = "DEBUG";  
88 - private static const LEVEL_WARN:String = "WARN";  
89 - private static const LEVEL_INFO:String = "INFO";  
90 - private static const LEVEL_ERROR:String = "ERROR";  
91 - private static const LEVEL_FATAL:String = "FATAL";  
92 - }  
93 -}  
@@ -32,14 +32,16 @@ package @@ -32,14 +32,16 @@ package
32 * @param msg the log message. 32 * @param msg the log message.
33 */ 33 */
34 public static function log(js_id:String, msg:String):void { 34 public static function log(js_id:String, msg:String):void {
35 - msg = "[" + new Date() +"][srs-player][" + js_id + "] " + msg; 35 + if (js_id) {
  36 + msg = "[" + new Date() +"][srs-player][" + js_id + "] " + msg;
  37 + }
36 38
37 logData += msg + "\n"; 39 logData += msg + "\n";
38 40
39 trace(msg); 41 trace(msg);
40 42
41 if (!flash.external.ExternalInterface.available) { 43 if (!flash.external.ExternalInterface.available) {
42 - flash.utils.setTimeout(log, 300, msg); 44 + flash.utils.setTimeout(log, 300, null, msg);
43 return; 45 return;
44 } 46 }
45 47
@@ -58,7 +58,7 @@ package @@ -58,7 +58,7 @@ package
58 private var control_fs_mask:Sprite = new Sprite(); 58 private var control_fs_mask:Sprite = new Sprite();
59 59
60 // the common player to play stream. 60 // the common player to play stream.
61 - private var player:IPlayer = null; 61 + private var player:Player = null;
62 // the flashvars config. 62 // the flashvars config.
63 private var config:Object = null; 63 private var config:Object = null;
64 64
@@ -438,13 +438,7 @@ package @@ -438,13 +438,7 @@ package
438 } 438 }
439 439
440 // create player. 440 // create player.
441 - if (url.indexOf(".m3u8") > 0 && Utility.stringStartswith(url, "http://")) {  
442 - player = new M3u8Player(this);  
443 - log("create M3U8 player.");  
444 - } else {  
445 - player = new CommonPlayer(this);  
446 - log("create Common player.");  
447 - } 441 + player = new Player(this);
448 442
449 // init player by config. 443 // init player by config.
450 player.init(config); 444 player.init(config);