mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
refine srs player for hls, extract a HlsNetStream.
This commit is contained in:
parent
2492562d25
commit
77167f3331
13 changed files with 862 additions and 949 deletions
|
@ -41,3 +41,4 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
trunk/research/players/srs_player/release/srs_player.swf
Executable file → Normal file
BIN
trunk/research/players/srs_player/release/srs_player.swf
Executable file → Normal file
Binary file not shown.
|
@ -1,11 +0,0 @@
|
||||||
package
|
|
||||||
{
|
|
||||||
public class Consts
|
|
||||||
{
|
|
||||||
// refresh every ts_fragment_seconds*M3u8RefreshRatio
|
|
||||||
public static var M3u8RefreshRatio:Number = 0.5;
|
|
||||||
|
|
||||||
// parse ts every this ms.
|
|
||||||
public static var TsParseAsyncInterval:Number = 80;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
package
|
|
||||||
{
|
|
||||||
import flash.utils.Dictionary;
|
|
||||||
|
|
||||||
public class Dict
|
|
||||||
{
|
|
||||||
private var _dict:Dictionary;
|
|
||||||
private var _size:uint;
|
|
||||||
|
|
||||||
public function Dict()
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the underlayer dict.
|
|
||||||
* @remark for core-ng.
|
|
||||||
*/
|
|
||||||
public function get dict():Dictionary
|
|
||||||
{
|
|
||||||
return _dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function has(key:Object):Boolean
|
|
||||||
{
|
|
||||||
return (key in _dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(key:Object):Object
|
|
||||||
{
|
|
||||||
return ((key in _dict) ? _dict[key] : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set(key:Object, object:Object):void
|
|
||||||
{
|
|
||||||
if (!(key in _dict))
|
|
||||||
{
|
|
||||||
_size++;
|
|
||||||
}
|
|
||||||
_dict[key] = object;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove(key:Object):Object
|
|
||||||
{
|
|
||||||
var object:Object;
|
|
||||||
if (key in _dict)
|
|
||||||
{
|
|
||||||
object = _dict[key];
|
|
||||||
delete _dict[key];
|
|
||||||
_size--;
|
|
||||||
}
|
|
||||||
return (object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function keys():Array
|
|
||||||
{
|
|
||||||
var array:Array = new Array(_size);
|
|
||||||
var index:int;
|
|
||||||
for (var key:Object in _dict)
|
|
||||||
{
|
|
||||||
var _local6:int = index++;
|
|
||||||
array[_local6] = key;
|
|
||||||
}
|
|
||||||
return (array);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function values():Array
|
|
||||||
{
|
|
||||||
var array:Array = new Array(_size);
|
|
||||||
var index:int;
|
|
||||||
for each (var value:Object in _dict)
|
|
||||||
{
|
|
||||||
var _local6:int = index++;
|
|
||||||
array[_local6] = value;
|
|
||||||
};
|
|
||||||
return (array);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clear():void
|
|
||||||
{
|
|
||||||
_dict = new Dictionary();
|
|
||||||
_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toArray():Array
|
|
||||||
{
|
|
||||||
var array:Array = new Array(_size * 2);
|
|
||||||
var index:int;
|
|
||||||
for (var key:Object in _dict)
|
|
||||||
{
|
|
||||||
var _local6:int = index++;
|
|
||||||
array[_local6] = key;
|
|
||||||
var _local7:int = index++;
|
|
||||||
array[_local7] = _dict[key];
|
|
||||||
};
|
|
||||||
return (array);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toObject():Object
|
|
||||||
{
|
|
||||||
return (toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fromObject(object:Object):void
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
var index:uint;
|
|
||||||
while (index < (object as Array).length) {
|
|
||||||
set((object as Array)[index], (object as Array)[(index + 1)]);
|
|
||||||
index += 2;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get size():uint
|
|
||||||
{
|
|
||||||
return (_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
package
|
|
||||||
{
|
|
||||||
import flash.utils.ByteArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a piece of flv, fetch from cdn or p2p.
|
|
||||||
*/
|
|
||||||
public class FlvPiece
|
|
||||||
{
|
|
||||||
private var _pieceId:Number;
|
|
||||||
protected var _flv:ByteArray;
|
|
||||||
/**
|
|
||||||
* the private object for the channel,
|
|
||||||
* for example, the cdn channel will set to CdnEdge object.
|
|
||||||
*/
|
|
||||||
private var _privateObject:Object;
|
|
||||||
/**
|
|
||||||
* when encoder error, this piece cannot be generated,
|
|
||||||
* and it should be skip. default to false.
|
|
||||||
*/
|
|
||||||
private var _skip:Boolean;
|
|
||||||
|
|
||||||
public function FlvPiece(pieceId:Number)
|
|
||||||
{
|
|
||||||
_pieceId = pieceId;
|
|
||||||
_flv = null;
|
|
||||||
_skip = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* when piece is fetch ok.
|
|
||||||
*/
|
|
||||||
public function onPieceDone(flv:ByteArray):void
|
|
||||||
{
|
|
||||||
// save body.
|
|
||||||
_flv = flv;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* when piece is fetch error.
|
|
||||||
*/
|
|
||||||
public function onPieceError():void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* when piece is empty.
|
|
||||||
*/
|
|
||||||
public function onPieceEmpty():void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* destroy the object, set reference to null.
|
|
||||||
*/
|
|
||||||
public function destroy():void
|
|
||||||
{
|
|
||||||
_privateObject = null;
|
|
||||||
_flv = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get privateObject():Object
|
|
||||||
{
|
|
||||||
return _privateObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set privateObject(v:Object):void
|
|
||||||
{
|
|
||||||
_privateObject = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get skip():Boolean
|
|
||||||
{
|
|
||||||
return _skip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set skip(v:Boolean):void
|
|
||||||
{
|
|
||||||
_skip = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get pieceId():Number
|
|
||||||
{
|
|
||||||
return _pieceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get flv():ByteArray
|
|
||||||
{
|
|
||||||
return _flv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get completed():Boolean
|
|
||||||
{
|
|
||||||
return _flv != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,264 @@
|
||||||
package
|
package
|
||||||
{
|
{
|
||||||
|
import flash.events.Event;
|
||||||
|
import flash.events.ProgressEvent;
|
||||||
|
import flash.external.ExternalInterface;
|
||||||
|
import flash.net.NetConnection;
|
||||||
|
import flash.net.NetStream;
|
||||||
|
import flash.net.NetStreamAppendBytesAction;
|
||||||
|
import flash.net.URLLoader;
|
||||||
|
import flash.net.URLLoaderDataFormat;
|
||||||
|
import flash.net.URLRequest;
|
||||||
|
import flash.net.URLRequestHeader;
|
||||||
|
import flash.net.URLRequestMethod;
|
||||||
|
import flash.net.URLStream;
|
||||||
|
import flash.net.URLVariables;
|
||||||
|
import flash.utils.ByteArray;
|
||||||
|
import flash.utils.setTimeout;
|
||||||
|
|
||||||
|
// the NetStream to play hls or hls+.
|
||||||
|
public class HlsNetStream extends NetStream
|
||||||
|
{
|
||||||
|
private var hls:HlsCodec = null; // parse m3u8 and ts
|
||||||
|
|
||||||
|
// for hls codec.
|
||||||
|
public var m3u8_refresh_ratio:Number;
|
||||||
|
public var ts_parse_async_interval:Number;
|
||||||
|
|
||||||
|
// play param url.
|
||||||
|
private var user_url:String = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create stream to play hls.
|
||||||
|
* @param m3u8_refresh_ratio, for example, 0.5, fetch m3u8 every 0.5*ts_duration.
|
||||||
|
* @param ts_parse_async_interval, for example, 80ms to parse ts async.
|
||||||
|
*/
|
||||||
|
public function HlsNetStream(m3u8_refresh_ratio:Number, ts_parse_async_interval:Number, conn:NetConnection)
|
||||||
|
{
|
||||||
|
super(conn);
|
||||||
|
|
||||||
|
this.m3u8_refresh_ratio = m3u8_refresh_ratio;
|
||||||
|
this.ts_parse_async_interval = ts_parse_async_interval;
|
||||||
|
hls = new HlsCodec(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to play the hls stream.
|
||||||
|
* for example, HlsNetStream.play("http://ossrs.net:8080/live/livestream.m3u8").
|
||||||
|
* user can set the metadata callback by:
|
||||||
|
* var ns:NetStream = new HlsNetStream(...);
|
||||||
|
* ns.client = {};
|
||||||
|
* ns.client.onMetaData = system_on_metadata;
|
||||||
|
*/
|
||||||
|
public override function play(... params):void
|
||||||
|
{
|
||||||
|
super.play(null);
|
||||||
|
user_url = params[0] as String;
|
||||||
|
refresh_m3u8();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////////////Private Section//////////////////////////////////////////////
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private var parsed_ts_seq_no:Number = -1;
|
||||||
|
private function refresh_m3u8():void {
|
||||||
|
download(user_url, function(stream:ByteArray):void {
|
||||||
|
var m3u8:String = stream.toString();
|
||||||
|
hls.parse(user_url, m3u8);
|
||||||
|
|
||||||
|
// redirect by variant m3u8.
|
||||||
|
if (hls.variant) {
|
||||||
|
var smu:String = hls.getTsUrl(0);
|
||||||
|
log("variant hls=" + user_url + ", redirect2=" + smu);
|
||||||
|
user_url = smu;
|
||||||
|
setTimeout(refresh_m3u8, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch from the last one.
|
||||||
|
if (parsed_ts_seq_no == -1) {
|
||||||
|
parsed_ts_seq_no = hls.seq_no + hls.tsCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not changed.
|
||||||
|
if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
|
||||||
|
refresh_ts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse each ts.
|
||||||
|
var nb_ts:Number = hls.seq_no + hls.tsCount - parsed_ts_seq_no;
|
||||||
|
log("m3u8 changed, got " + nb_ts + " new ts, count=" + hls.tsCount + ", seqno=" + hls.seq_no + ", parsed=" + parsed_ts_seq_no);
|
||||||
|
|
||||||
|
refresh_ts();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
private function refresh_ts():void {
|
||||||
|
// all ts parsed.
|
||||||
|
if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
|
||||||
|
var to:Number = 1000;
|
||||||
|
if (hls.tsCount > 0) {
|
||||||
|
to = hls.duration * 1000 / hls.tsCount * m3u8_refresh_ratio;
|
||||||
|
}
|
||||||
|
setTimeout(refresh_m3u8, to);
|
||||||
|
log("m3u8 not changed, retry after " + to.toFixed(2) + "ms");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse current ts.
|
||||||
|
var uri:String = hls.getTsUrl(parsed_ts_seq_no - hls.seq_no);
|
||||||
|
|
||||||
|
// parse metadata from uri.
|
||||||
|
if (uri.indexOf("?") >= 0) {
|
||||||
|
var uv:URLVariables = new URLVariables(uri.substr(uri.indexOf("?") + 1));
|
||||||
|
var obj:Object = {};
|
||||||
|
for (var k:String in uv) {
|
||||||
|
var v:String = uv[k];
|
||||||
|
if (k == "shp_sip1") {
|
||||||
|
obj.srs_server_ip = v;
|
||||||
|
} else if (k == "shp_cid") {
|
||||||
|
obj.srs_id = v;
|
||||||
|
} else if (k == "shp_pid") {
|
||||||
|
obj.srs_pid = v;
|
||||||
|
}
|
||||||
|
//log("uv[" + k + "]=" + v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client && client.hasOwnProperty("onMetaData")) {
|
||||||
|
client.onMetaData(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
download(uri, function(stream:ByteArray):void{
|
||||||
|
log("got ts seqno=" + parsed_ts_seq_no + ", " + stream.length + " bytes");
|
||||||
|
|
||||||
|
var flv:FlvPiece = new FlvPiece(parsed_ts_seq_no);
|
||||||
|
var body:ByteArray = new ByteArray();
|
||||||
|
stream.position = 0;
|
||||||
|
hls.parseBodyAsync(flv, stream, body, function():void{
|
||||||
|
body.position = 0;
|
||||||
|
//log("ts parsed, seqno=" + parsed_ts_seq_no + ", flv=" + body.length + "B");
|
||||||
|
onFlvBody(uri, body);
|
||||||
|
|
||||||
|
parsed_ts_seq_no++;
|
||||||
|
setTimeout(refresh_ts, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private function download(uri:String, completed:Function):void {
|
||||||
|
// http get.
|
||||||
|
var url:URLStream = new URLStream();
|
||||||
|
var stream:ByteArray = new ByteArray();
|
||||||
|
|
||||||
|
url.addEventListener(ProgressEvent.PROGRESS, function(evt:ProgressEvent):void {
|
||||||
|
if (url.bytesAvailable <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//log(uri + " total=" + evt.bytesTotal + ", loaded=" + evt.bytesLoaded + ", available=" + url.bytesAvailable);
|
||||||
|
var bytes:ByteArray = new ByteArray();
|
||||||
|
url.readBytes(bytes, 0, url.bytesAvailable);
|
||||||
|
stream.writeBytes(bytes);
|
||||||
|
});
|
||||||
|
|
||||||
|
url.addEventListener(Event.COMPLETE, function(evt:Event):void {
|
||||||
|
log(uri + " completed, total=" + stream.length + "bytes");
|
||||||
|
if (url.bytesAvailable <= 0) {
|
||||||
|
completed(stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//log(uri + " completed" + ", available=" + url.bytesAvailable);
|
||||||
|
var bytes:ByteArray = new ByteArray();
|
||||||
|
url.readBytes(bytes, 0, url.bytesAvailable);
|
||||||
|
stream.writeBytes(bytes);
|
||||||
|
|
||||||
|
completed(stream);
|
||||||
|
});
|
||||||
|
|
||||||
|
// we set to the query.
|
||||||
|
uri += ((uri.indexOf("?") == -1)? "?":"&") + "shp_xpsid=" + XPlaybackSessionId;
|
||||||
|
var r:URLRequest = new URLRequest(uri);
|
||||||
|
// seems flash not allow set this header.
|
||||||
|
// @remark disable it for it will cause security exception.
|
||||||
|
//r.requestHeaders.push(new URLRequestHeader("X-Playback-Session-Id", XPlaybackSessionId));
|
||||||
|
|
||||||
|
log("start download " + uri);
|
||||||
|
url.load(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the uuid similar to Safari, to identify this play session.
|
||||||
|
// @see https://github.com/winlinvip/srs-plus/blob/bms/trunk/src/app/srs_app_http_stream.cpp#L45
|
||||||
|
public var XPlaybackSessionId:String = createRandomIdentifier(32);
|
||||||
|
|
||||||
|
private function createRandomIdentifier(length:uint, radix:uint = 61):String {
|
||||||
|
var characters:Array = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
|
||||||
|
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||||
|
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
|
||||||
|
'z');
|
||||||
|
var id:Array = new Array();
|
||||||
|
radix = (radix > 61) ? 61 : radix;
|
||||||
|
while (length--) {
|
||||||
|
id.push(characters[randomIntegerWithinRange(0, radix)]);
|
||||||
|
}
|
||||||
|
return id.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function randomIntegerWithinRange(min:int, max:int):int {
|
||||||
|
return Math.floor(Math.random() * (1 + max - min) + min);
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback for hls.
|
||||||
|
public var flvHeader:ByteArray = null;
|
||||||
|
public function onSequenceHeader():void {
|
||||||
|
var s:NetStream = super;
|
||||||
|
s.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
|
||||||
|
s.appendBytes(flvHeader);
|
||||||
|
log("FLV: sps/pps " + flvHeader.length + " bytes");
|
||||||
|
|
||||||
|
writeFlv(flvHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onFlvBody(uri:String, flv:ByteArray):void {
|
||||||
|
if (!flvHeader) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s:NetStream = super;
|
||||||
|
s.appendBytes(flv);
|
||||||
|
log("FLV: ts " + uri + " parsed to flv " + flv.length + " bytes");
|
||||||
|
|
||||||
|
writeFlv(flv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeFlv(data:ByteArray):void {
|
||||||
|
return;
|
||||||
|
|
||||||
|
var r:URLRequest = new URLRequest("http://192.168.1.117:8088/api/v1/flv");
|
||||||
|
r.method = URLRequestMethod.POST;
|
||||||
|
r.data = data;
|
||||||
|
|
||||||
|
var pf:URLLoader = new URLLoader();
|
||||||
|
pf.dataFormat = URLLoaderDataFormat.BINARY;
|
||||||
|
pf.load(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function log(msg:String):void {
|
||||||
|
msg = "[" + new Date() +"][srs-player] " + msg;
|
||||||
|
|
||||||
|
trace(msg);
|
||||||
|
|
||||||
|
if (!flash.external.ExternalInterface.available) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalInterface.call("console.log", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
import flash.events.Event;
|
import flash.events.Event;
|
||||||
import flash.net.URLLoader;
|
import flash.net.URLLoader;
|
||||||
import flash.net.URLRequest;
|
import flash.net.URLRequest;
|
||||||
|
@ -9,7 +268,7 @@ package
|
||||||
/**
|
/**
|
||||||
* the hls main class.
|
* the hls main class.
|
||||||
*/
|
*/
|
||||||
public class Hls
|
class HlsCodec
|
||||||
{
|
{
|
||||||
private var m3u8:M3u8;
|
private var m3u8:M3u8;
|
||||||
|
|
||||||
|
@ -25,12 +284,12 @@ package
|
||||||
private var video_sh_tag:ByteArray;
|
private var video_sh_tag:ByteArray;
|
||||||
private var audio_sh_tag:ByteArray;
|
private var audio_sh_tag:ByteArray;
|
||||||
|
|
||||||
private var owner:M3u8Player;
|
private var owner:HlsNetStream;
|
||||||
private var _log:ILogger = new TraceLogger("HLS");
|
private var _log:ILogger = new TraceLogger("HLS");
|
||||||
|
|
||||||
public static const SRS_TS_PACKET_SIZE:int = 188;
|
public static const SRS_TS_PACKET_SIZE:int = 188;
|
||||||
|
|
||||||
public function Hls(o:M3u8Player)
|
public function HlsCodec(o:HlsNetStream)
|
||||||
{
|
{
|
||||||
owner = o;
|
owner = o;
|
||||||
m3u8 = new M3u8(this);
|
m3u8 = new M3u8(this);
|
||||||
|
@ -155,7 +414,7 @@ package
|
||||||
nb_parsed += each_parse;
|
nb_parsed += each_parse;
|
||||||
|
|
||||||
if (nb_parsed < ts_packets) {
|
if (nb_parsed < ts_packets) {
|
||||||
flash.utils.setTimeout(aysncParse, Consts.TsParseAsyncInterval);
|
flash.utils.setTimeout(aysncParse, owner.ts_parse_async_interval);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +519,7 @@ package
|
||||||
{
|
{
|
||||||
for (var i:int = 0; (maxTsPackets == -1 || i < maxTsPackets) && data.bytesAvailable > 0; i++) {
|
for (var i:int = 0; (maxTsPackets == -1 || i < maxTsPackets) && data.bytesAvailable > 0; i++) {
|
||||||
var tsBytes:ByteArray = new ByteArray();
|
var tsBytes:ByteArray = new ByteArray();
|
||||||
data.readBytes(tsBytes, 0, Hls.SRS_TS_PACKET_SIZE);
|
data.readBytes(tsBytes, 0, HlsCodec.SRS_TS_PACKET_SIZE);
|
||||||
context.decode(tsBytes, handler);
|
context.decode(tsBytes, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,6 +540,326 @@ package
|
||||||
loader.load(url);
|
loader.load(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import flash.utils.Dictionary;
|
||||||
|
|
||||||
|
class Dict
|
||||||
|
{
|
||||||
|
private var _dict:Dictionary;
|
||||||
|
private var _size:uint;
|
||||||
|
|
||||||
|
public function Dict()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the underlayer dict.
|
||||||
|
* @remark for core-ng.
|
||||||
|
*/
|
||||||
|
public function get dict():Dictionary
|
||||||
|
{
|
||||||
|
return _dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has(key:Object):Boolean
|
||||||
|
{
|
||||||
|
return (key in _dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(key:Object):Object
|
||||||
|
{
|
||||||
|
return ((key in _dict) ? _dict[key] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(key:Object, object:Object):void
|
||||||
|
{
|
||||||
|
if (!(key in _dict))
|
||||||
|
{
|
||||||
|
_size++;
|
||||||
|
}
|
||||||
|
_dict[key] = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(key:Object):Object
|
||||||
|
{
|
||||||
|
var object:Object;
|
||||||
|
if (key in _dict)
|
||||||
|
{
|
||||||
|
object = _dict[key];
|
||||||
|
delete _dict[key];
|
||||||
|
_size--;
|
||||||
|
}
|
||||||
|
return (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function keys():Array
|
||||||
|
{
|
||||||
|
var array:Array = new Array(_size);
|
||||||
|
var index:int;
|
||||||
|
for (var key:Object in _dict)
|
||||||
|
{
|
||||||
|
var _local6:int = index++;
|
||||||
|
array[_local6] = key;
|
||||||
|
}
|
||||||
|
return (array);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function values():Array
|
||||||
|
{
|
||||||
|
var array:Array = new Array(_size);
|
||||||
|
var index:int;
|
||||||
|
for each (var value:Object in _dict)
|
||||||
|
{
|
||||||
|
var _local6:int = index++;
|
||||||
|
array[_local6] = value;
|
||||||
|
};
|
||||||
|
return (array);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clear():void
|
||||||
|
{
|
||||||
|
_dict = new Dictionary();
|
||||||
|
_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray():Array
|
||||||
|
{
|
||||||
|
var array:Array = new Array(_size * 2);
|
||||||
|
var index:int;
|
||||||
|
for (var key:Object in _dict)
|
||||||
|
{
|
||||||
|
var _local6:int = index++;
|
||||||
|
array[_local6] = key;
|
||||||
|
var _local7:int = index++;
|
||||||
|
array[_local7] = _dict[key];
|
||||||
|
};
|
||||||
|
return (array);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toObject():Object
|
||||||
|
{
|
||||||
|
return (toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromObject(object:Object):void
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
var index:uint;
|
||||||
|
while (index < (object as Array).length) {
|
||||||
|
set((object as Array)[index], (object as Array)[(index + 1)]);
|
||||||
|
index += 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get size():uint
|
||||||
|
{
|
||||||
|
return (_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
import flash.utils.ByteArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a piece of flv, fetch from cdn or p2p.
|
||||||
|
*/
|
||||||
|
class FlvPiece
|
||||||
|
{
|
||||||
|
private var _pieceId:Number;
|
||||||
|
protected var _flv:ByteArray;
|
||||||
|
/**
|
||||||
|
* the private object for the channel,
|
||||||
|
* for example, the cdn channel will set to CdnEdge object.
|
||||||
|
*/
|
||||||
|
private var _privateObject:Object;
|
||||||
|
/**
|
||||||
|
* when encoder error, this piece cannot be generated,
|
||||||
|
* and it should be skip. default to false.
|
||||||
|
*/
|
||||||
|
private var _skip:Boolean;
|
||||||
|
|
||||||
|
public function FlvPiece(pieceId:Number)
|
||||||
|
{
|
||||||
|
_pieceId = pieceId;
|
||||||
|
_flv = null;
|
||||||
|
_skip = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when piece is fetch ok.
|
||||||
|
*/
|
||||||
|
public function onPieceDone(flv:ByteArray):void
|
||||||
|
{
|
||||||
|
// save body.
|
||||||
|
_flv = flv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when piece is fetch error.
|
||||||
|
*/
|
||||||
|
public function onPieceError():void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when piece is empty.
|
||||||
|
*/
|
||||||
|
public function onPieceEmpty():void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroy the object, set reference to null.
|
||||||
|
*/
|
||||||
|
public function destroy():void
|
||||||
|
{
|
||||||
|
_privateObject = null;
|
||||||
|
_flv = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get privateObject():Object
|
||||||
|
{
|
||||||
|
return _privateObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set privateObject(v:Object):void
|
||||||
|
{
|
||||||
|
_privateObject = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get skip():Boolean
|
||||||
|
{
|
||||||
|
return _skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set skip(v:Boolean):void
|
||||||
|
{
|
||||||
|
_skip = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get pieceId():Number
|
||||||
|
{
|
||||||
|
return _pieceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get flv():ByteArray
|
||||||
|
{
|
||||||
|
return _flv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get completed():Boolean
|
||||||
|
{
|
||||||
|
return _flv != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ILogger
|
||||||
|
{
|
||||||
|
function debug0(message:String, ... rest):void;
|
||||||
|
function debug(message:String, ... rest):void;
|
||||||
|
function info(message:String, ... rest):void;
|
||||||
|
function warn(message:String, ... rest):void;
|
||||||
|
function error(message:String, ... rest):void;
|
||||||
|
function fatal(message:String, ... rest):void;
|
||||||
|
}
|
||||||
|
|
||||||
|
import flash.globalization.DateTimeFormatter;
|
||||||
|
import flash.external.ExternalInterface;
|
||||||
|
|
||||||
|
class TraceLogger implements ILogger
|
||||||
|
{
|
||||||
|
private var _category:String;
|
||||||
|
|
||||||
|
public function get category():String
|
||||||
|
{
|
||||||
|
return _category;
|
||||||
|
}
|
||||||
|
public function TraceLogger(category:String)
|
||||||
|
{
|
||||||
|
_category = category;
|
||||||
|
}
|
||||||
|
public function debug0(message:String, ...rest):void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function debug(message:String, ...rest):void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function info(message:String, ...rest):void
|
||||||
|
{
|
||||||
|
logMessage(LEVEL_INFO, message, rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function warn(message:String, ...rest):void
|
||||||
|
{
|
||||||
|
logMessage(LEVEL_WARN, message, rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function error(message:String, ...rest):void
|
||||||
|
{
|
||||||
|
logMessage(LEVEL_ERROR, message, rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fatal(message:String, ...rest):void
|
||||||
|
{
|
||||||
|
logMessage(LEVEL_FATAL, message, rest);
|
||||||
|
}
|
||||||
|
protected function logMessage(level:String, message:String, params:Array):void
|
||||||
|
{
|
||||||
|
var msg:String = "";
|
||||||
|
|
||||||
|
// add datetime
|
||||||
|
var date:Date = new Date();
|
||||||
|
var dtf:DateTimeFormatter = new DateTimeFormatter("UTC");
|
||||||
|
dtf.setDateTimePattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
// TODO: FIXME: the SSS format not run, use date.milliseconds instead.
|
||||||
|
msg += '[' + dtf.format(date) + "." + date.milliseconds + ']';
|
||||||
|
msg += " [" + level + "] ";
|
||||||
|
|
||||||
|
// add category and params
|
||||||
|
msg += "[" + category + "] " + applyParams(message, params);
|
||||||
|
|
||||||
|
// trace the message
|
||||||
|
trace(msg);
|
||||||
|
|
||||||
|
if (!flash.external.ExternalInterface.available) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalInterface.call("console.log", msg);
|
||||||
|
}
|
||||||
|
private function leadingZeros(x:Number):String
|
||||||
|
{
|
||||||
|
if (x < 10) {
|
||||||
|
return "00" + x.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x < 100) {
|
||||||
|
return "0" + x.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.toString();
|
||||||
|
}
|
||||||
|
private function applyParams(message:String, params:Array):String
|
||||||
|
{
|
||||||
|
var result:String = message;
|
||||||
|
|
||||||
|
var numParams:int = params.length;
|
||||||
|
|
||||||
|
for (var i:int = 0; i < numParams; i++) {
|
||||||
|
result = result.replace(new RegExp("\\{" + i + "\\}", "g"), params[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static const LEVEL_DEBUG:String = "DEBUG";
|
||||||
|
private static const LEVEL_WARN:String = "WARN";
|
||||||
|
private static const LEVEL_INFO:String = "INFO";
|
||||||
|
private static const LEVEL_ERROR:String = "ERROR";
|
||||||
|
private static const LEVEL_FATAL:String = "FATAL";
|
||||||
}
|
}
|
||||||
|
|
||||||
import flash.utils.ByteArray;
|
import flash.utils.ByteArray;
|
||||||
|
@ -304,7 +883,7 @@ class SrsTsHanlder implements ISrsTsHandler
|
||||||
private var queue:Array;
|
private var queue:Array;
|
||||||
|
|
||||||
// hls data.
|
// hls data.
|
||||||
private var _hls:Hls;
|
private var _hls:HlsCodec;
|
||||||
private var _body:ByteArray;
|
private var _body:ByteArray;
|
||||||
private var _on_size_changed:Function;
|
private var _on_size_changed:Function;
|
||||||
private var _on_sequence_changed:Function;
|
private var _on_sequence_changed:Function;
|
||||||
|
@ -316,7 +895,7 @@ class SrsTsHanlder implements ISrsTsHandler
|
||||||
ph264_sps:ByteArray, ph264_pps:ByteArray,
|
ph264_sps:ByteArray, ph264_pps:ByteArray,
|
||||||
paac_specific_config:ByteArray,
|
paac_specific_config:ByteArray,
|
||||||
pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray,
|
pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray,
|
||||||
hls:Hls, body:ByteArray, oszc:Function, oshc:Function)
|
hls:HlsCodec, body:ByteArray, oszc:Function, oshc:Function)
|
||||||
{
|
{
|
||||||
_hls = hls;
|
_hls = hls;
|
||||||
_body = body;
|
_body = body;
|
||||||
|
@ -2383,7 +2962,7 @@ interface ISrsTsHandler
|
||||||
*/
|
*/
|
||||||
class SrsTsContext
|
class SrsTsContext
|
||||||
{
|
{
|
||||||
private var _hls:Hls;
|
private var _hls:HlsCodec;
|
||||||
|
|
||||||
// codec
|
// codec
|
||||||
// key, a Number indicates the pid,
|
// key, a Number indicates the pid,
|
||||||
|
@ -2393,7 +2972,7 @@ class SrsTsContext
|
||||||
// whether hls pure audio stream.
|
// whether hls pure audio stream.
|
||||||
private var _pure_audio:Boolean;
|
private var _pure_audio:Boolean;
|
||||||
|
|
||||||
public function SrsTsContext(hls:Hls)
|
public function SrsTsContext(hls:HlsCodec)
|
||||||
{
|
{
|
||||||
_hls = hls;
|
_hls = hls;
|
||||||
_pure_audio = false;
|
_pure_audio = false;
|
||||||
|
@ -2620,7 +3199,7 @@ class SrsTsPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
// calc the user defined data size for payload.
|
// calc the user defined data size for payload.
|
||||||
var nb_payload:int = Hls.SRS_TS_PACKET_SIZE - (stream.position - pos);
|
var nb_payload:int = HlsCodec.SRS_TS_PACKET_SIZE - (stream.position - pos);
|
||||||
|
|
||||||
// optional: payload.
|
// optional: payload.
|
||||||
if (adaption_field_control == SrsTsAdaptationFieldType.PayloadOnly
|
if (adaption_field_control == SrsTsAdaptationFieldType.PayloadOnly
|
||||||
|
@ -4583,7 +5162,7 @@ class SrsTsPayloadPMT extends SrsTsPayloadPSI
|
||||||
*/
|
*/
|
||||||
class M3u8
|
class M3u8
|
||||||
{
|
{
|
||||||
private var _hls:Hls;
|
private var _hls:HlsCodec;
|
||||||
private var _log:ILogger = new TraceLogger("HLS");
|
private var _log:ILogger = new TraceLogger("HLS");
|
||||||
|
|
||||||
private var _tses:Array;
|
private var _tses:Array;
|
||||||
|
@ -4593,7 +5172,7 @@ class M3u8
|
||||||
// when variant, all ts url is sub m3u8 url.
|
// when variant, all ts url is sub m3u8 url.
|
||||||
private var _variant:Boolean;
|
private var _variant:Boolean;
|
||||||
|
|
||||||
public function M3u8(hls:Hls)
|
public function M3u8(hls:HlsCodec)
|
||||||
{
|
{
|
||||||
_hls = hls;
|
_hls = hls;
|
||||||
_tses = new Array();
|
_tses = new Array();
|
|
@ -1,12 +0,0 @@
|
||||||
package
|
|
||||||
{
|
|
||||||
public interface ILogger
|
|
||||||
{
|
|
||||||
function debug0(message:String, ... rest):void;
|
|
||||||
function debug(message:String, ... rest):void;
|
|
||||||
function info(message:String, ... rest):void;
|
|
||||||
function warn(message:String, ... rest):void;
|
|
||||||
function error(message:String, ... rest):void;
|
|
||||||
function fatal(message:String, ... rest):void;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package
|
|
||||||
{
|
|
||||||
import flash.net.NetStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the player interface.
|
|
||||||
*/
|
|
||||||
public interface IPlayer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* initialize the player by flashvars for config.
|
|
||||||
* @param flashvars the config.
|
|
||||||
*/
|
|
||||||
function init(flashvars:Object):void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the NetStream to play the stream.
|
|
||||||
* @return the underlayer stream object.
|
|
||||||
*/
|
|
||||||
function stream():NetStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* connect and play url.
|
|
||||||
* @param url the stream url to play.
|
|
||||||
*/
|
|
||||||
function play(url:String):void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* close the player.
|
|
||||||
*/
|
|
||||||
function close():void;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,307 +0,0 @@
|
||||||
package
|
|
||||||
{
|
|
||||||
import flash.display.Sprite;
|
|
||||||
import flash.display.StageAlign;
|
|
||||||
import flash.display.StageDisplayState;
|
|
||||||
import flash.display.StageScaleMode;
|
|
||||||
import flash.events.Event;
|
|
||||||
import flash.events.FullScreenEvent;
|
|
||||||
import flash.events.MouseEvent;
|
|
||||||
import flash.events.NetStatusEvent;
|
|
||||||
import flash.events.ProgressEvent;
|
|
||||||
import flash.events.TimerEvent;
|
|
||||||
import flash.external.ExternalInterface;
|
|
||||||
import flash.media.SoundTransform;
|
|
||||||
import flash.media.Video;
|
|
||||||
import flash.net.NetConnection;
|
|
||||||
import flash.net.NetStream;
|
|
||||||
import flash.net.NetStreamAppendBytesAction;
|
|
||||||
import flash.net.URLLoader;
|
|
||||||
import flash.net.URLLoaderDataFormat;
|
|
||||||
import flash.net.URLRequest;
|
|
||||||
import flash.net.URLRequestHeader;
|
|
||||||
import flash.net.URLRequestMethod;
|
|
||||||
import flash.net.URLStream;
|
|
||||||
import flash.net.URLVariables;
|
|
||||||
import flash.system.Security;
|
|
||||||
import flash.ui.ContextMenu;
|
|
||||||
import flash.ui.ContextMenuItem;
|
|
||||||
import flash.utils.ByteArray;
|
|
||||||
import flash.utils.Timer;
|
|
||||||
import flash.utils.getTimer;
|
|
||||||
import flash.utils.setTimeout;
|
|
||||||
|
|
||||||
import flashx.textLayout.formats.Float;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the m3u8 player.
|
|
||||||
*/
|
|
||||||
public class M3u8Player implements IPlayer
|
|
||||||
{
|
|
||||||
private var js_id:String = null;
|
|
||||||
|
|
||||||
// play param url.
|
|
||||||
private var user_url:String = null;
|
|
||||||
|
|
||||||
private var media_stream:NetStream = null;
|
|
||||||
private var media_conn:NetConnection = null;
|
|
||||||
|
|
||||||
private var owner:srs_player = null;
|
|
||||||
private var hls:Hls = null; // parse m3u8 and ts
|
|
||||||
|
|
||||||
// the uuid similar to Safari, to identify this play session.
|
|
||||||
// @see https://github.com/winlinvip/srs-plus/blob/bms/trunk/src/app/srs_app_http_stream.cpp#L45
|
|
||||||
public var XPlaybackSessionId:String = createRandomIdentifier(32);
|
|
||||||
private function createRandomIdentifier(length:uint, radix:uint = 61):String {
|
|
||||||
var characters:Array = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
|
|
||||||
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
|
||||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
|
|
||||||
'z');
|
|
||||||
var id:Array = new Array();
|
|
||||||
radix = (radix > 61) ? 61 : radix;
|
|
||||||
while (length--) {
|
|
||||||
id.push(characters[randomIntegerWithinRange(0, radix)]);
|
|
||||||
}
|
|
||||||
return id.join('');
|
|
||||||
}
|
|
||||||
private function randomIntegerWithinRange(min:int, max:int):int {
|
|
||||||
return Math.floor(Math.random() * (1 + max - min) + min);
|
|
||||||
}
|
|
||||||
|
|
||||||
// callback for hls.
|
|
||||||
public var flvHeader:ByteArray = null;
|
|
||||||
public function onSequenceHeader():void {
|
|
||||||
if (!media_stream) {
|
|
||||||
setTimeout(onSequenceHeader, 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var s:NetStream = media_stream;
|
|
||||||
s.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
|
|
||||||
s.appendBytes(flvHeader);
|
|
||||||
log("FLV: sps/pps " + flvHeader.length + " bytes");
|
|
||||||
|
|
||||||
writeFlv(flvHeader);
|
|
||||||
}
|
|
||||||
public function onFlvBody(uri:String, flv:ByteArray):void {
|
|
||||||
if (!media_stream) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!flvHeader) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var s:NetStream = media_stream;
|
|
||||||
s.appendBytes(flv);
|
|
||||||
log("FLV: ts " + uri + " parsed to flv " + flv.length + " bytes");
|
|
||||||
|
|
||||||
writeFlv(flv);
|
|
||||||
}
|
|
||||||
private function writeFlv(data:ByteArray):void {
|
|
||||||
return;
|
|
||||||
|
|
||||||
var r:URLRequest = new URLRequest("http://192.168.1.117:8088/api/v1/flv");
|
|
||||||
r.method = URLRequestMethod.POST;
|
|
||||||
r.data = data;
|
|
||||||
|
|
||||||
var pf:URLLoader = new URLLoader();
|
|
||||||
pf.dataFormat = URLLoaderDataFormat.BINARY;
|
|
||||||
pf.load(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function M3u8Player(o:srs_player) {
|
|
||||||
owner = o;
|
|
||||||
hls = new Hls(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init(flashvars:Object):void {
|
|
||||||
this.js_id = flashvars.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function stream():NetStream {
|
|
||||||
return this.media_stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
// owner.on_player_metadata(evt.info.data);
|
|
||||||
public function play(url:String):void {
|
|
||||||
var streamName:String;
|
|
||||||
this.user_url = url;
|
|
||||||
|
|
||||||
this.media_conn = new NetConnection();
|
|
||||||
this.media_conn.client = {};
|
|
||||||
this.media_conn.client.onBWDone = function():void {};
|
|
||||||
this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
|
|
||||||
log("NetConnection: code=" + evt.info.code + ", data is " + evt.info.data);
|
|
||||||
|
|
||||||
// TODO: FIXME: failed event.
|
|
||||||
if (evt.info.code != "NetConnection.Connect.Success") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
media_stream = new NetStream(media_conn);
|
|
||||||
media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
|
|
||||||
log("NetStream: code=" + evt.info.code);
|
|
||||||
|
|
||||||
if (evt.info.code == "NetStream.Video.DimensionChange") {
|
|
||||||
owner.on_player_dimension_change();
|
|
||||||
} else if (evt.info.code == "NetStream.Buffer.Empty") {
|
|
||||||
owner.on_player_buffer_empty();
|
|
||||||
} else if (evt.info.code == "NetStream.Buffer.Full") {
|
|
||||||
owner.on_player_buffer_full();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: FIXME: failed event.
|
|
||||||
});
|
|
||||||
|
|
||||||
// setup stream before play.
|
|
||||||
owner.on_player_before_play();
|
|
||||||
|
|
||||||
media_stream.play(null);
|
|
||||||
refresh_m3u8();
|
|
||||||
|
|
||||||
owner.on_player_play();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.media_conn.connect(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close():void {
|
|
||||||
if (this.media_stream) {
|
|
||||||
this.media_stream.close();
|
|
||||||
this.media_stream = null;
|
|
||||||
}
|
|
||||||
if (this.media_conn) {
|
|
||||||
this.media_conn.close();
|
|
||||||
this.media_conn = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var parsed_ts_seq_no:Number = -1;
|
|
||||||
private function refresh_m3u8():void {
|
|
||||||
download(user_url, function(stream:ByteArray):void {
|
|
||||||
var m3u8:String = stream.toString();
|
|
||||||
hls.parse(user_url, m3u8);
|
|
||||||
|
|
||||||
// redirect by variant m3u8.
|
|
||||||
if (hls.variant) {
|
|
||||||
var smu:String = hls.getTsUrl(0);
|
|
||||||
log("variant hls=" + user_url + ", redirect2=" + smu);
|
|
||||||
user_url = smu;
|
|
||||||
setTimeout(refresh_m3u8, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch from the last one.
|
|
||||||
if (parsed_ts_seq_no == -1) {
|
|
||||||
parsed_ts_seq_no = hls.seq_no + hls.tsCount - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not changed.
|
|
||||||
if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
|
|
||||||
refresh_ts();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse each ts.
|
|
||||||
var nb_ts:Number = hls.seq_no + hls.tsCount - parsed_ts_seq_no;
|
|
||||||
log("m3u8 changed, got " + nb_ts + " new ts, count=" + hls.tsCount + ", seqno=" + hls.seq_no + ", parsed=" + parsed_ts_seq_no);
|
|
||||||
|
|
||||||
refresh_ts();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
private function refresh_ts():void {
|
|
||||||
// all ts parsed.
|
|
||||||
if (parsed_ts_seq_no >= hls.seq_no + hls.tsCount) {
|
|
||||||
var to:Number = 1000;
|
|
||||||
if (hls.tsCount > 0) {
|
|
||||||
to = hls.duration * 1000 / hls.tsCount * Consts.M3u8RefreshRatio;
|
|
||||||
}
|
|
||||||
setTimeout(refresh_m3u8, to);
|
|
||||||
log("m3u8 not changed, retry after " + to.toFixed(2) + "ms");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse current ts.
|
|
||||||
var uri:String = hls.getTsUrl(parsed_ts_seq_no - hls.seq_no);
|
|
||||||
|
|
||||||
// parse metadata from uri.
|
|
||||||
if (uri.indexOf("?") >= 0) {
|
|
||||||
var uv:URLVariables = new URLVariables(uri.substr(uri.indexOf("?") + 1));
|
|
||||||
var obj:Object = {};
|
|
||||||
for (var k:String in uv) {
|
|
||||||
var v:String = uv[k];
|
|
||||||
if (k == "shp_sip1") {
|
|
||||||
obj.srs_server_ip = v;
|
|
||||||
} else if (k == "shp_cid") {
|
|
||||||
obj.srs_id = v;
|
|
||||||
} else if (k == "shp_pid") {
|
|
||||||
obj.srs_pid = v;
|
|
||||||
}
|
|
||||||
//log("uv[" + k + "]=" + v);
|
|
||||||
}
|
|
||||||
owner.on_player_metadata(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
download(uri, function(stream:ByteArray):void{
|
|
||||||
log("got ts seqno=" + parsed_ts_seq_no + ", " + stream.length + " bytes");
|
|
||||||
|
|
||||||
var flv:FlvPiece = new FlvPiece(parsed_ts_seq_no);
|
|
||||||
var body:ByteArray = new ByteArray();
|
|
||||||
stream.position = 0;
|
|
||||||
hls.parseBodyAsync(flv, stream, body, function():void{
|
|
||||||
body.position = 0;
|
|
||||||
//log("ts parsed, seqno=" + parsed_ts_seq_no + ", flv=" + body.length + "B");
|
|
||||||
onFlvBody(uri, body);
|
|
||||||
|
|
||||||
parsed_ts_seq_no++;
|
|
||||||
setTimeout(refresh_ts, 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private function download(uri:String, completed:Function):void {
|
|
||||||
var url:URLStream = new URLStream();
|
|
||||||
var stream:ByteArray = new ByteArray();
|
|
||||||
|
|
||||||
url.addEventListener(ProgressEvent.PROGRESS, function(evt:ProgressEvent):void {
|
|
||||||
if (url.bytesAvailable <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//log(uri + " total=" + evt.bytesTotal + ", loaded=" + evt.bytesLoaded + ", available=" + url.bytesAvailable);
|
|
||||||
var bytes:ByteArray = new ByteArray();
|
|
||||||
url.readBytes(bytes, 0, url.bytesAvailable);
|
|
||||||
stream.writeBytes(bytes);
|
|
||||||
});
|
|
||||||
|
|
||||||
url.addEventListener(Event.COMPLETE, function(evt:Event):void {
|
|
||||||
log(uri + " completed, total=" + stream.length + "bytes");
|
|
||||||
if (url.bytesAvailable <= 0) {
|
|
||||||
completed(stream);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//log(uri + " completed" + ", available=" + url.bytesAvailable);
|
|
||||||
var bytes:ByteArray = new ByteArray();
|
|
||||||
url.readBytes(bytes, 0, url.bytesAvailable);
|
|
||||||
stream.writeBytes(bytes);
|
|
||||||
|
|
||||||
completed(stream);
|
|
||||||
});
|
|
||||||
|
|
||||||
// we set to the query.
|
|
||||||
uri += ((uri.indexOf("?") == -1)? "?":"&") + "shp_xpsid=" + XPlaybackSessionId;
|
|
||||||
var r:URLRequest = new URLRequest(uri);
|
|
||||||
// seems flash not allow set this header.
|
|
||||||
r.requestHeaders.push(new URLRequestHeader("X-Playback-Session-Id", XPlaybackSessionId));
|
|
||||||
|
|
||||||
log("start download " + uri);
|
|
||||||
url.load(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function log(msg:String):void {
|
|
||||||
Utility.log(js_id, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,8 +28,14 @@ package
|
||||||
* common player to play rtmp/flv stream,
|
* common player to play rtmp/flv stream,
|
||||||
* use system NetStream.
|
* use system NetStream.
|
||||||
*/
|
*/
|
||||||
public class CommonPlayer implements IPlayer
|
public class Player
|
||||||
{
|
{
|
||||||
|
// refresh every ts_fragment_seconds*M3u8RefreshRatio
|
||||||
|
public static var M3u8RefreshRatio:Number = 0.5;
|
||||||
|
|
||||||
|
// parse ts every this ms.
|
||||||
|
public static var TsParseAsyncInterval:Number = 80;
|
||||||
|
|
||||||
private var js_id:String = null;
|
private var js_id:String = null;
|
||||||
|
|
||||||
// play param url.
|
// play param url.
|
||||||
|
@ -40,7 +46,7 @@ package
|
||||||
|
|
||||||
private var owner:srs_player = null;
|
private var owner:srs_player = null;
|
||||||
|
|
||||||
public function CommonPlayer(o:srs_player) {
|
public function Player(o:srs_player) {
|
||||||
owner = o;
|
owner = o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +94,11 @@ package
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (url.indexOf(".m3u8") > 0) {
|
||||||
|
media_stream = new HlsNetStream(M3u8RefreshRatio, TsParseAsyncInterval, media_conn);
|
||||||
|
} else {
|
||||||
media_stream = new NetStream(media_conn);
|
media_stream = new NetStream(media_conn);
|
||||||
|
}
|
||||||
media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
|
media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
|
||||||
log("NetStream: code=" + evt.info.code);
|
log("NetStream: code=" + evt.info.code);
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
package
|
|
||||||
{
|
|
||||||
import flash.globalization.DateTimeFormatter;
|
|
||||||
|
|
||||||
public class TraceLogger implements ILogger
|
|
||||||
{
|
|
||||||
private var _category:String;
|
|
||||||
|
|
||||||
public function get category():String
|
|
||||||
{
|
|
||||||
return _category;
|
|
||||||
}
|
|
||||||
public function TraceLogger(category:String)
|
|
||||||
{
|
|
||||||
_category = category;
|
|
||||||
}
|
|
||||||
public function debug0(message:String, ...rest):void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debug(message:String, ...rest):void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function info(message:String, ...rest):void
|
|
||||||
{
|
|
||||||
logMessage(LEVEL_INFO, message, rest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function warn(message:String, ...rest):void
|
|
||||||
{
|
|
||||||
logMessage(LEVEL_WARN, message, rest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function error(message:String, ...rest):void
|
|
||||||
{
|
|
||||||
logMessage(LEVEL_ERROR, message, rest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fatal(message:String, ...rest):void
|
|
||||||
{
|
|
||||||
logMessage(LEVEL_FATAL, message, rest);
|
|
||||||
}
|
|
||||||
protected function logMessage(level:String, message:String, params:Array):void
|
|
||||||
{
|
|
||||||
var msg:String = "";
|
|
||||||
|
|
||||||
// add datetime
|
|
||||||
var date:Date = new Date();
|
|
||||||
var dtf:DateTimeFormatter = new DateTimeFormatter("UTC");
|
|
||||||
dtf.setDateTimePattern("yyyy-MM-dd HH:mm:ss");
|
|
||||||
|
|
||||||
// TODO: FIXME: the SSS format not run, use date.milliseconds instead.
|
|
||||||
msg += '[' + dtf.format(date) + "." + date.milliseconds + ']';
|
|
||||||
msg += " [" + level + "] ";
|
|
||||||
|
|
||||||
// add category and params
|
|
||||||
msg += "[" + category + "] " + applyParams(message, params);
|
|
||||||
|
|
||||||
// trace the message
|
|
||||||
Utility.log("CORE", msg);
|
|
||||||
}
|
|
||||||
private function leadingZeros(x:Number):String
|
|
||||||
{
|
|
||||||
if (x < 10) {
|
|
||||||
return "00" + x.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x < 100) {
|
|
||||||
return "0" + x.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return x.toString();
|
|
||||||
}
|
|
||||||
private function applyParams(message:String, params:Array):String
|
|
||||||
{
|
|
||||||
var result:String = message;
|
|
||||||
|
|
||||||
var numParams:int = params.length;
|
|
||||||
|
|
||||||
for (var i:int = 0; i < numParams; i++) {
|
|
||||||
result = result.replace(new RegExp("\\{" + i + "\\}", "g"), params[i]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static const LEVEL_DEBUG:String = "DEBUG";
|
|
||||||
private static const LEVEL_WARN:String = "WARN";
|
|
||||||
private static const LEVEL_INFO:String = "INFO";
|
|
||||||
private static const LEVEL_ERROR:String = "ERROR";
|
|
||||||
private static const LEVEL_FATAL:String = "FATAL";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,14 +32,16 @@ package
|
||||||
* @param msg the log message.
|
* @param msg the log message.
|
||||||
*/
|
*/
|
||||||
public static function log(js_id:String, msg:String):void {
|
public static function log(js_id:String, msg:String):void {
|
||||||
|
if (js_id) {
|
||||||
msg = "[" + new Date() +"][srs-player][" + js_id + "] " + msg;
|
msg = "[" + new Date() +"][srs-player][" + js_id + "] " + msg;
|
||||||
|
}
|
||||||
|
|
||||||
logData += msg + "\n";
|
logData += msg + "\n";
|
||||||
|
|
||||||
trace(msg);
|
trace(msg);
|
||||||
|
|
||||||
if (!flash.external.ExternalInterface.available) {
|
if (!flash.external.ExternalInterface.available) {
|
||||||
flash.utils.setTimeout(log, 300, msg);
|
flash.utils.setTimeout(log, 300, null, msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ package
|
||||||
private var control_fs_mask:Sprite = new Sprite();
|
private var control_fs_mask:Sprite = new Sprite();
|
||||||
|
|
||||||
// the common player to play stream.
|
// the common player to play stream.
|
||||||
private var player:IPlayer = null;
|
private var player:Player = null;
|
||||||
// the flashvars config.
|
// the flashvars config.
|
||||||
private var config:Object = null;
|
private var config:Object = null;
|
||||||
|
|
||||||
|
@ -438,13 +438,7 @@ package
|
||||||
}
|
}
|
||||||
|
|
||||||
// create player.
|
// create player.
|
||||||
if (url.indexOf(".m3u8") > 0 && Utility.stringStartswith(url, "http://")) {
|
player = new Player(this);
|
||||||
player = new M3u8Player(this);
|
|
||||||
log("create M3U8 player.");
|
|
||||||
} else {
|
|
||||||
player = new CommonPlayer(this);
|
|
||||||
log("create Common player.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// init player by config.
|
// init player by config.
|
||||||
player.init(config);
|
player.init(config);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue