refine srs player for hls, extract a HlsNetStream.

pull/609/head
winlin 9 years ago
parent 2492562d25
commit 77167f3331

@ -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,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,
* 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;
// play param url.
@ -40,7 +46,7 @@ package
private var owner:srs_player = null;
public function CommonPlayer(o:srs_player) {
public function Player(o:srs_player) {
owner = o;
}
@ -88,7 +94,11 @@ package
return;
}
media_stream = new NetStream(media_conn);
if (url.indexOf(".m3u8") > 0) {
media_stream = new HlsNetStream(M3u8RefreshRatio, TsParseAsyncInterval, media_conn);
} else {
media_stream = new NetStream(media_conn);
}
media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
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.
*/
public static function log(js_id:String, msg:String):void {
msg = "[" + new Date() +"][srs-player][" + js_id + "] " + msg;
if (js_id) {
msg = "[" + new Date() +"][srs-player][" + js_id + "] " + msg;
}
logData += msg + "\n";
trace(msg);
if (!flash.external.ExternalInterface.available) {
flash.utils.setTimeout(log, 300, msg);
flash.utils.setTimeout(log, 300, null, msg);
return;
}

@ -58,7 +58,7 @@ package
private var control_fs_mask:Sprite = new Sprite();
// the common player to play stream.
private var player:IPlayer = null;
private var player:Player = null;
// the flashvars config.
private var config:Object = null;
@ -438,13 +438,7 @@ package
}
// create player.
if (url.indexOf(".m3u8") > 0 && Utility.stringStartswith(url, "http://")) {
player = new M3u8Player(this);
log("create M3U8 player.");
} else {
player = new CommonPlayer(this);
log("create Common player.");
}
player = new Player(this);
// init player by config.
player.init(config);

Loading…
Cancel
Save