正在显示
11 个修改的文件
包含
577 行增加
和
53 行删除
| @@ -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 | +} |
trunk/research/players/srs_player/src/Hls.as
0 → 100755
此 diff 太大无法显示。
| 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 |
-
请 注册 或 登录 后发表评论