正在显示
13 个修改的文件
包含
846 行增加
和
933 行删除
trunk/research/players/srs_player/release/srs_player.swf
100755 → 100644
不能预览此文件类型
| 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
→
trunk/research/players/srs_player/src/HlsNetStream.as
| 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); |
-
请 注册 或 登录 后发表评论