winlin

support play hls

@@ -61,28 +61,46 @@ function build_default_rtmp_url() { @@ -61,28 +61,46 @@ function build_default_rtmp_url() {
61 var app = (query.app == undefined)? "live":query.app; 61 var app = (query.app == undefined)? "live":query.app;
62 var stream = (query.stream == undefined)? "demo":query.stream; 62 var stream = (query.stream == undefined)? "demo":query.stream;
63 63
64 - if (server == vhost || vhost == "") {  
65 - return schema + "://" + server + ":" + port + "/" + app + "/" + stream;  
66 - } else {  
67 - return schema + "://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream; 64 + var queries = [];
  65 + if (server != vhost && vhost != "__defaultVhost__") {
  66 + queries.push("vhost=" + vhost);
68 } 67 }
  68 + if (query.shp_identify) {
  69 + queries.push("shp_identify=" + query.shp_identify);
  70 + }
  71 +
  72 + var uri = schema + "://" + server + ":" + port + "/" + app + "/" + stream + "?" + queries.join('&');
  73 + while (uri.indexOf("?") == uri.length - 1) {
  74 + uri = uri.substr(0, uri.length - 1);
  75 + }
  76 +
  77 + return uri;
69 } 78 }
70 // for the chat to init the publish url. 79 // for the chat to init the publish url.
71 function build_default_publish_rtmp_url() { 80 function build_default_publish_rtmp_url() {
72 var query = parse_query_string(); 81 var query = parse_query_string();
73 82
  83 + var schema = (query.schema == undefined)? "rtmp":query.schema;
74 var server = (query.server == undefined)? window.location.hostname:query.server; 84 var server = (query.server == undefined)? window.location.hostname:query.server;
75 var port = (query.port == undefined)? 1935:query.port; 85 var port = (query.port == undefined)? 1935:query.port;
76 var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost; 86 var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost;
77 var app = (query.app == undefined)? "live":query.app; 87 var app = (query.app == undefined)? "live":query.app;
78 var stream = (query.stream == undefined)? "demo":query.stream; 88 var stream = (query.stream == undefined)? "demo":query.stream;
79 89
80 - if (server == vhost || vhost == "") {  
81 - return "rtmp://" + server + ":" + port + "/" + app + "/" + stream;  
82 - } else {  
83 - vhost = srs_get_player_chat_vhost(vhost);  
84 - return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream; 90 + var queries = [];
  91 + if (server != vhost && vhost != "__defaultVhost__") {
  92 + queries.push("vhost=" + srs_get_player_chat_vhost(vhost));
85 } 93 }
  94 + if (query.shp_identify) {
  95 + queries.push("shp_identify=" + query.shp_identify);
  96 + }
  97 +
  98 + var uri = schema + "://" + server + ":" + port + "/" + app + "/" + stream + "?" + queries.join('&');
  99 + while (uri.indexOf("?") == uri.length - 1) {
  100 + uri = uri.substr(0, uri.length - 1);
  101 + }
  102 +
  103 + return uri;
86 } 104 }
87 // for the bandwidth tool to init page 105 // for the bandwidth tool to init page
88 function build_default_bandwidth_rtmp_url() { 106 function build_default_bandwidth_rtmp_url() {
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 * depends: jquery1.10 5 * depends: jquery1.10
6 * https://code.csdn.net/snippets/147103 6 * https://code.csdn.net/snippets/147103
7 * @see: http://blog.csdn.net/win_lin/article/details/17994347 7 * @see: http://blog.csdn.net/win_lin/article/details/17994347
8 - * v 1.0.15 8 + * v 1.0.16
9 */ 9 */
10 10
11 /** 11 /**
@@ -243,8 +243,21 @@ function parse_query_string(){ @@ -243,8 +243,21 @@ function parse_query_string(){
243 } 243 }
244 } 244 }
245 245
  246 + __fill_query(query_string, obj);
  247 +
  248 + return obj;
  249 +}
  250 +
  251 +function __fill_query(query_string, obj) {
  252 + // pure user query object.
  253 + obj.user_query = {};
  254 +
  255 + if (query_string.length == 0) {
  256 + return;
  257 + }
  258 +
246 // split again for angularjs. 259 // split again for angularjs.
247 - if (query_string.indexOf("?") > 0) { 260 + if (query_string.indexOf("?") >= 0) {
248 query_string = query_string.split("?")[1]; 261 query_string = query_string.split("?")[1];
249 } 262 }
250 263
@@ -257,7 +270,10 @@ function parse_query_string(){ @@ -257,7 +270,10 @@ function parse_query_string(){
257 obj.user_query[query[0]] = query[1]; 270 obj.user_query[query[0]] = query[1];
258 } 271 }
259 272
260 - return obj; 273 + // alias domain for vhost.
  274 + if (obj.domain) {
  275 + obj.vhost = obj.domain;
  276 + }
261 } 277 }
262 278
263 /** 279 /**
@@ -277,7 +293,7 @@ function parse_query_string(){ @@ -277,7 +293,7 @@ function parse_query_string(){
277 function parse_rtmp_url(rtmp_url) { 293 function parse_rtmp_url(rtmp_url) {
278 // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri 294 // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
279 var a = document.createElement("a"); 295 var a = document.createElement("a");
280 - a.href = rtmp_url.replace("rtmp://", "http://").replace("?", "...").replace("=", "..."); 296 + a.href = rtmp_url.replace("rtmp://", "http://");
281 297
282 var vhost = a.hostname; 298 var vhost = a.hostname;
283 var port = (a.port == "")? "1935":a.port; 299 var port = (a.port == "")? "1935":a.port;
@@ -319,6 +335,7 @@ function parse_rtmp_url(rtmp_url) { @@ -319,6 +335,7 @@ function parse_rtmp_url(rtmp_url) {
319 server: a.hostname, port: port, 335 server: a.hostname, port: port,
320 vhost: vhost, app: app, stream: stream 336 vhost: vhost, app: app, stream: stream
321 }; 337 };
  338 + __fill_query(a.search, ret);
322 339
323 return ret; 340 return ret;
324 } 341 }
@@ -479,6 +479,8 @@ @@ -479,6 +479,8 @@
479 * app, the app of url. 479 * app, the app of url.
480 * stream, the stream of url, can endwith .flv or .mp4 or nothing for RTMP. 480 * stream, the stream of url, can endwith .flv or .mp4 or nothing for RTMP.
481 * autostart, whether auto play the stream. 481 * autostart, whether auto play the stream.
  482 + * extra params:
  483 + * shp_identify, hls+ param.
482 * for example: 484 * for example:
483 * http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream&server=ossrs.net&port=1935&autostart=true&schema=rtmp 485 * http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream&server=ossrs.net&port=1935&autostart=true&schema=rtmp
484 * http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream.flv&server=ossrs.net&port=8080&autostart=true&schema=http 486 * http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream.flv&server=ossrs.net&port=8080&autostart=true&schema=http
@@ -572,9 +574,17 @@ @@ -572,9 +574,17 @@
572 var apply_url_change = function() { 574 var apply_url_change = function() {
573 var rtmp = parse_rtmp_url($("#txt_url").val()); 575 var rtmp = parse_rtmp_url($("#txt_url").val());
574 var url = "http://" + query.host + query.pathname + "?" 576 var url = "http://" + query.host + query.pathname + "?"
575 - + "vhost=" + rtmp.vhost + "&app=" + rtmp.app + "&stream=" + rtmp.stream 577 + + "app=" + rtmp.app + "&stream=" + rtmp.stream
576 + "&server=" + rtmp.server + "&port=" + rtmp.port 578 + "&server=" + rtmp.server + "&port=" + rtmp.port
577 + "&autostart=true"; 579 + "&autostart=true";
  580 + if (query.shp_identify) {
  581 + url += "&shp_identify=" + query.shp_identify;
  582 + }
  583 + if (rtmp.vhost == "__defaultVhost__") {
  584 + url += "&vhost=" + rtmp.server;
  585 + } else {
  586 + url += "&vhost=" + rtmp.vhost;
  587 + }
578 if (rtmp.schema == "http") { 588 if (rtmp.schema == "http") {
579 url += "&schema=http"; 589 url += "&schema=http";
580 } 590 }
@@ -14,6 +14,7 @@ package @@ -14,6 +14,7 @@ package
14 import flash.media.Video; 14 import flash.media.Video;
15 import flash.net.NetConnection; 15 import flash.net.NetConnection;
16 import flash.net.NetStream; 16 import flash.net.NetStream;
  17 + import flash.net.URLVariables;
17 import flash.system.Security; 18 import flash.system.Security;
18 import flash.ui.ContextMenu; 19 import flash.ui.ContextMenu;
19 import flash.ui.ContextMenuItem; 20 import flash.ui.ContextMenuItem;
@@ -21,7 +22,7 @@ package @@ -21,7 +22,7 @@ package
21 import flash.utils.getTimer; 22 import flash.utils.getTimer;
22 import flash.utils.setTimeout; 23 import flash.utils.setTimeout;
23 24
24 - import flashx.textLayout.formats.Float; 25 + import flashx.textLayout.formats.Float;
25 26
26 /** 27 /**
27 * common player to play rtmp/flv stream, 28 * common player to play rtmp/flv stream,
@@ -119,6 +120,20 @@ package @@ -119,6 +120,20 @@ package
119 this.media_conn.connect(null); 120 this.media_conn.connect(null);
120 } else { 121 } else {
121 var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/")); 122 var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/"));
  123 + streamName = url.substr(url.lastIndexOf("/") + 1);
  124 +
  125 + // parse vhost from stream query.
  126 + if (streamName.indexOf("?") >= 0) {
  127 + var uv:URLVariables = new URLVariables(user_url.substr(user_url.indexOf("?") + 1));
  128 + var domain:String = uv["domain"];
  129 + if (!domain) {
  130 + domain = uv["vhost"];
  131 + }
  132 + if (domain) {
  133 + tcUrl += "?vhost=" + domain;
  134 + }
  135 + }
  136 +
122 this.media_conn.connect(tcUrl); 137 this.media_conn.connect(tcUrl);
123 } 138 }
124 } 139 }
  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
  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 +}
@@ -8,19 +8,25 @@ package @@ -8,19 +8,25 @@ package
8 import flash.events.FullScreenEvent; 8 import flash.events.FullScreenEvent;
9 import flash.events.MouseEvent; 9 import flash.events.MouseEvent;
10 import flash.events.NetStatusEvent; 10 import flash.events.NetStatusEvent;
  11 + import flash.events.ProgressEvent;
11 import flash.events.TimerEvent; 12 import flash.events.TimerEvent;
12 import flash.external.ExternalInterface; 13 import flash.external.ExternalInterface;
13 import flash.media.SoundTransform; 14 import flash.media.SoundTransform;
14 import flash.media.Video; 15 import flash.media.Video;
15 import flash.net.NetConnection; 16 import flash.net.NetConnection;
16 import flash.net.NetStream; 17 import flash.net.NetStream;
  18 + import flash.net.NetStreamAppendBytesAction;
  19 + import flash.net.URLRequest;
  20 + import flash.net.URLStream;
  21 + import flash.net.URLVariables;
17 import flash.system.Security; 22 import flash.system.Security;
18 import flash.ui.ContextMenu; 23 import flash.ui.ContextMenu;
19 import flash.ui.ContextMenuItem; 24 import flash.ui.ContextMenuItem;
  25 + import flash.utils.ByteArray;
20 import flash.utils.Timer; 26 import flash.utils.Timer;
21 import flash.utils.getTimer; 27 import flash.utils.getTimer;
22 import flash.utils.setTimeout; 28 import flash.utils.setTimeout;
23 - 29 +
24 import flashx.textLayout.formats.Float; 30 import flashx.textLayout.formats.Float;
25 31
26 /** 32 /**
@@ -37,9 +43,39 @@ package @@ -37,9 +43,39 @@ package
37 private var media_conn:NetConnection = null; 43 private var media_conn:NetConnection = null;
38 44
39 private var owner:srs_player = null; 45 private var owner:srs_player = null;
  46 + private var hls:Hls = null; // parse m3u8 and ts
  47 +
  48 + // callback for hls.
  49 + private var shok:Boolean = false;
  50 + public var flvHeader:ByteArray = null;
  51 + public function onSequenceHeader():void {
  52 + if (shok) {
  53 + return;
  54 + }
  55 + if (!media_stream) {
  56 + setTimeout(onSequenceHeader, 1000);
  57 + return;
  58 + }
  59 +
  60 + var s:NetStream = media_stream;
  61 + s.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
  62 + s.appendBytes(flvHeader);
  63 + log("FLV: sps/pps " + flvHeader.length + " bytes");
  64 + shok = true;
  65 + }
  66 + public function onFlvBody(flv:ByteArray):void {
  67 + if (!media_stream) {
  68 + return;
  69 + }
  70 +
  71 + var s:NetStream = media_stream;
  72 + s.appendBytes(flv);
  73 + log("FLV: AV " + flv.length + " bytes");
  74 + }
40 75
41 public function M3u8Player(o:srs_player) { 76 public function M3u8Player(o:srs_player) {
42 owner = o; 77 owner = o;
  78 + hls = new Hls(this);
43 } 79 }
44 80
45 public function init(flashvars:Object):void { 81 public function init(flashvars:Object):void {
@@ -50,35 +86,16 @@ package @@ -50,35 +86,16 @@ package
50 return this.media_stream; 86 return this.media_stream;
51 } 87 }
52 88
  89 + // owner.on_player_metadata(evt.info.data);
53 public function play(url:String):void { 90 public function play(url:String):void {
  91 + var streamName:String;
54 this.user_url = url; 92 this.user_url = url;
55 93
56 this.media_conn = new NetConnection(); 94 this.media_conn = new NetConnection();
57 this.media_conn.client = {}; 95 this.media_conn.client = {};
58 this.media_conn.client.onBWDone = function():void {}; 96 this.media_conn.client.onBWDone = function():void {};
59 this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void { 97 this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
60 - log("NetConnection: code=" + evt.info.code);  
61 -  
62 - if (evt.info.hasOwnProperty("data") && evt.info.data) {  
63 - owner.on_player_metadata(evt.info.data);  
64 - }  
65 -  
66 - // reject by server, maybe redirect.  
67 - if (evt.info.code == "NetConnection.Connect.Rejected") {  
68 - // RTMP 302 redirect.  
69 - if (evt.info.hasOwnProperty("ex") && evt.info.ex.code == 302) {  
70 - streamName = url.substr(url.lastIndexOf("/") + 1);  
71 - url = evt.info.ex.redirect + "/" + streamName;  
72 - log("Async RTMP 302 Redirect to: " + url);  
73 -  
74 - // notify server.  
75 - media_conn.call("Redirected", null, evt.info.ex.redirect);  
76 -  
77 - // do 302.  
78 - owner.on_player_302(url);  
79 - return;  
80 - }  
81 - } 98 + log("NetConnection: code=" + evt.info.code + ", data is " + evt.info.data);
82 99
83 // TODO: FIXME: failed event. 100 // TODO: FIXME: failed event.
84 if (evt.info.code != "NetConnection.Connect.Success") { 101 if (evt.info.code != "NetConnection.Connect.Success") {
@@ -103,12 +120,8 @@ package @@ -103,12 +120,8 @@ package
103 // setup stream before play. 120 // setup stream before play.
104 owner.on_player_before_play(); 121 owner.on_player_before_play();
105 122
106 - if (url.indexOf("http") == 0) {  
107 - media_stream.play(url);  
108 - } else {  
109 - streamName = url.substr(url.lastIndexOf("/") + 1);  
110 - media_stream.play(streamName);  
111 - } 123 + media_stream.play(null);
  124 + refresh_m3u8();
112 125
113 owner.on_player_play(); 126 owner.on_player_play();
114 }); 127 });
@@ -127,6 +140,124 @@ package @@ -127,6 +140,124 @@ package
127 } 140 }
128 } 141 }
129 142
  143 + private var parsed_ts_seq_no:Number = -1;
  144 + private function refresh_m3u8():void {
  145 + download(user_url, function(stream:ByteArray):void {
  146 + var m3u8:String = stream.toString();
  147 + hls.parse(user_url, m3u8);
  148 +
  149 + // redirect by variant m3u8.
  150 + if (hls.variant) {
  151 + var smu:String = hls.getTsUrl(0);
  152 + log("variant hls=" + user_url + ", redirect2=" + smu);
  153 + user_url = smu;
  154 + setTimeout(refresh_m3u8, 0);
  155 + return;
  156 + }
  157 +
  158 + // fetch from the last one.
  159 + if (parsed_ts_seq_no == -1) {
  160 + parsed_ts_seq_no = hls.seq_no + hls.tsCount - 1;
  161 + }
  162 +
  163 + // not changed.
  164 + if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
  165 + refresh_ts();
  166 + return;
  167 + }
  168 +
  169 + // parse each ts.
  170 + var nb_ts:Number = hls.seq_no + hls.tsCount - parsed_ts_seq_no;
  171 + log("m3u8 changed, got " + nb_ts + " new ts, count=" + hls.tsCount + ", seqno=" + hls.seq_no + ", parsed=" + parsed_ts_seq_no);
  172 +
  173 + refresh_ts();
  174 + })
  175 + }
  176 + private function refresh_ts():void {
  177 + // all ts parsed.
  178 + if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
  179 + var to:Number = 1000;
  180 + if (hls.tsCount > 0) {
  181 + to = hls.duration * 1000 / hls.tsCount * 0.5;
  182 + }
  183 + setTimeout(refresh_m3u8, to);
  184 + log("m3u8 not changed, retry after " + to.toFixed(2) + "ms");
  185 + return;
  186 + }
  187 +
  188 + // parse current ts.
  189 + var uri:String = hls.getTsUrl(parsed_ts_seq_no - hls.seq_no);
  190 +
  191 + // parse metadata from uri.
  192 + if (uri.indexOf("?") >= 0) {
  193 + var uv:URLVariables = new URLVariables(uri.substr(uri.indexOf("?") + 1));
  194 + var obj:Object = {};
  195 + for (var k:String in uv) {
  196 + var v:String = uv[k];
  197 + if (k == "shp_sip1") {
  198 + obj.srs_server_ip = v;
  199 + } else if (k == "shp_cid") {
  200 + obj.srs_id = v;
  201 + } else if (k == "shp_pid") {
  202 + obj.srs_pid = v;
  203 + }
  204 + //log("uv[" + k + "]=" + v);
  205 + }
  206 + owner.on_player_metadata(obj);
  207 + }
  208 +
  209 + download(uri, function(stream:ByteArray):void{
  210 + log("got ts seqno=" + parsed_ts_seq_no + ", " + stream.length + " bytes");
  211 + // reset and start to parse this ts.
  212 + hls.reset();
  213 +
  214 + var flv:FlvPiece = new FlvPiece(parsed_ts_seq_no);
  215 + var body:ByteArray = new ByteArray();
  216 + stream.position = 0;
  217 + hls.parseBodyAsync(flv, stream, body, function():void{
  218 + body.position = 0;
  219 + //log("ts parsed, seqno=" + parsed_ts_seq_no + ", flv=" + body.length + "B");
  220 + onFlvBody(body);
  221 +
  222 + parsed_ts_seq_no++;
  223 + setTimeout(refresh_ts, 0);
  224 + });
  225 + });
  226 + }
  227 + private function download(uri:String, completed:Function):void {
  228 + var url:URLStream = new URLStream();
  229 + var stream:ByteArray = new ByteArray();
  230 +
  231 + url.addEventListener(ProgressEvent.PROGRESS, function(evt:ProgressEvent):void {
  232 + if (url.bytesAvailable <= 0) {
  233 + return;
  234 + }
  235 +
  236 + //log(uri + " total=" + evt.bytesTotal + ", loaded=" + evt.bytesLoaded + ", available=" + url.bytesAvailable);
  237 + var bytes:ByteArray = new ByteArray();
  238 + url.readBytes(bytes, 0, url.bytesAvailable);
  239 + stream.writeBytes(bytes);
  240 + });
  241 +
  242 + url.addEventListener(Event.COMPLETE, function(evt:Event):void {
  243 + log(uri + " completed, total=" + stream.length + "bytes");
  244 + if (url.bytesAvailable <= 0) {
  245 + completed(stream);
  246 + return;
  247 + }
  248 +
  249 + //log(uri + " completed" + ", available=" + url.bytesAvailable);
  250 + var bytes:ByteArray = new ByteArray();
  251 + url.readBytes(bytes, 0, url.bytesAvailable);
  252 + stream.writeBytes(bytes);
  253 +
  254 + completed(stream);
  255 + });
  256 +
  257 + log("start download " + uri);
  258 + url.load(new URLRequest(uri));
  259 + }
  260 +
130 private function log(msg:String):void { 261 private function log(msg:String):void {
131 Utility.log(js_id, msg); 262 Utility.log(js_id, msg);
132 } 263 }
  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 +}
@@ -183,13 +183,17 @@ package @@ -183,13 +183,17 @@ package
183 * or got video dimension change event(the DAR notification), to update the metadata manually. 183 * or got video dimension change event(the DAR notification), to update the metadata manually.
184 */ 184 */
185 private function system_on_metadata(metadata:Object):void { 185 private function system_on_metadata(metadata:Object):void {
186 - this.media_metadata = metadata;  
187 -  
188 - // update the debug info.  
189 - if (metadata) {  
190 - on_debug_info(metadata); 186 + if (!media_metadata) {
  187 + media_metadata = {};
191 } 188 }
192 - 189 + for (var k:String in metadata) {
  190 + media_metadata[k] = metadata[k];
  191 + }
  192 +
  193 + // update the debug info.
  194 + on_debug_info(media_metadata);
  195 + update_context_items();
  196 +
193 // for js. 197 // for js.
194 var obj:Object = __get_video_size_object(); 198 var obj:Object = __get_video_size_object();
195 199
@@ -427,9 +431,14 @@ package @@ -427,9 +431,14 @@ package
427 ); 431 );
428 432
429 js_call_stop(); 433 js_call_stop();
  434 +
  435 + // trim last ?
  436 + while (Utility.stringEndswith(url, "?")) {
  437 + url = url.substr(0, url.length - 1);
  438 + }
430 439
431 // create player. 440 // create player.
432 - if (Utility.stringEndswith(url, ".m3u8") && Utility.stringStartswith(url, "http://")) { 441 + if (url.indexOf(".m3u8") > 0 && Utility.stringStartswith(url, "http://")) {
433 player = new M3u8Player(this); 442 player = new M3u8Player(this);
434 log("create M3U8 player."); 443 log("create M3U8 player.");
435 } else { 444 } else {
@@ -477,8 +486,7 @@ package @@ -477,8 +486,7 @@ package
477 setChildIndex(media_video, 0); 486 setChildIndex(media_video, 0);
478 } 487 }
479 public function on_player_metadata(data:Object):void { 488 public function on_player_metadata(data:Object):void {
480 - on_debug_info(data);  
481 - update_context_items(); 489 + system_on_metadata(data);
482 } 490 }
483 public function on_player_302(url:String):void { 491 public function on_player_302(url:String):void {
484 setTimeout(function():void{ 492 setTimeout(function():void{
@@ -503,6 +511,9 @@ package @@ -503,6 +511,9 @@ package
503 * 3. override with codec size if specified. 511 * 3. override with codec size if specified.
504 */ 512 */
505 private function __get_video_size_object():Object { 513 private function __get_video_size_object():Object {
  514 + if (!media_video) {
  515 + return {};
  516 + }
506 var obj:Object = { 517 var obj:Object = {
507 width: media_video.width, 518 width: media_video.width,
508 height: media_video.height 519 height: media_video.height