mirror of
https://github.com/ossrs/srs.git
synced 2025-02-13 11:51:57 +00:00
support play hls
This commit is contained in:
parent
69ec66ee36
commit
46a31f4884
11 changed files with 5321 additions and 53 deletions
|
@ -61,28 +61,46 @@ function build_default_rtmp_url() {
|
||||||
var app = (query.app == undefined)? "live":query.app;
|
var app = (query.app == undefined)? "live":query.app;
|
||||||
var stream = (query.stream == undefined)? "demo":query.stream;
|
var stream = (query.stream == undefined)? "demo":query.stream;
|
||||||
|
|
||||||
if (server == vhost || vhost == "") {
|
var queries = [];
|
||||||
return schema + "://" + server + ":" + port + "/" + app + "/" + stream;
|
if (server != vhost && vhost != "__defaultVhost__") {
|
||||||
} else {
|
queries.push("vhost=" + vhost);
|
||||||
return schema + "://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
|
|
||||||
}
|
}
|
||||||
|
if (query.shp_identify) {
|
||||||
|
queries.push("shp_identify=" + query.shp_identify);
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = schema + "://" + server + ":" + port + "/" + app + "/" + stream + "?" + queries.join('&');
|
||||||
|
while (uri.indexOf("?") == uri.length - 1) {
|
||||||
|
uri = uri.substr(0, uri.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
// for the chat to init the publish url.
|
// for the chat to init the publish url.
|
||||||
function build_default_publish_rtmp_url() {
|
function build_default_publish_rtmp_url() {
|
||||||
var query = parse_query_string();
|
var query = parse_query_string();
|
||||||
|
|
||||||
|
var schema = (query.schema == undefined)? "rtmp":query.schema;
|
||||||
var server = (query.server == undefined)? window.location.hostname:query.server;
|
var server = (query.server == undefined)? window.location.hostname:query.server;
|
||||||
var port = (query.port == undefined)? 1935:query.port;
|
var port = (query.port == undefined)? 1935:query.port;
|
||||||
var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost;
|
var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost;
|
||||||
var app = (query.app == undefined)? "live":query.app;
|
var app = (query.app == undefined)? "live":query.app;
|
||||||
var stream = (query.stream == undefined)? "demo":query.stream;
|
var stream = (query.stream == undefined)? "demo":query.stream;
|
||||||
|
|
||||||
if (server == vhost || vhost == "") {
|
var queries = [];
|
||||||
return "rtmp://" + server + ":" + port + "/" + app + "/" + stream;
|
if (server != vhost && vhost != "__defaultVhost__") {
|
||||||
} else {
|
queries.push("vhost=" + srs_get_player_chat_vhost(vhost));
|
||||||
vhost = srs_get_player_chat_vhost(vhost);
|
|
||||||
return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
|
|
||||||
}
|
}
|
||||||
|
if (query.shp_identify) {
|
||||||
|
queries.push("shp_identify=" + query.shp_identify);
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = schema + "://" + server + ":" + port + "/" + app + "/" + stream + "?" + queries.join('&');
|
||||||
|
while (uri.indexOf("?") == uri.length - 1) {
|
||||||
|
uri = uri.substr(0, uri.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
// for the bandwidth tool to init page
|
// for the bandwidth tool to init page
|
||||||
function build_default_bandwidth_rtmp_url() {
|
function build_default_bandwidth_rtmp_url() {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* depends: jquery1.10
|
* depends: jquery1.10
|
||||||
* https://code.csdn.net/snippets/147103
|
* https://code.csdn.net/snippets/147103
|
||||||
* @see: http://blog.csdn.net/win_lin/article/details/17994347
|
* @see: http://blog.csdn.net/win_lin/article/details/17994347
|
||||||
* v 1.0.15
|
* v 1.0.16
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -243,8 +243,21 @@ function parse_query_string(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__fill_query(query_string, obj);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __fill_query(query_string, obj) {
|
||||||
|
// pure user query object.
|
||||||
|
obj.user_query = {};
|
||||||
|
|
||||||
|
if (query_string.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// split again for angularjs.
|
// split again for angularjs.
|
||||||
if (query_string.indexOf("?") > 0) {
|
if (query_string.indexOf("?") >= 0) {
|
||||||
query_string = query_string.split("?")[1];
|
query_string = query_string.split("?")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +270,10 @@ function parse_query_string(){
|
||||||
obj.user_query[query[0]] = query[1];
|
obj.user_query[query[0]] = query[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
// alias domain for vhost.
|
||||||
|
if (obj.domain) {
|
||||||
|
obj.vhost = obj.domain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -277,7 +293,7 @@ function parse_query_string(){
|
||||||
function parse_rtmp_url(rtmp_url) {
|
function parse_rtmp_url(rtmp_url) {
|
||||||
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
|
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
|
||||||
var a = document.createElement("a");
|
var a = document.createElement("a");
|
||||||
a.href = rtmp_url.replace("rtmp://", "http://").replace("?", "...").replace("=", "...");
|
a.href = rtmp_url.replace("rtmp://", "http://");
|
||||||
|
|
||||||
var vhost = a.hostname;
|
var vhost = a.hostname;
|
||||||
var port = (a.port == "")? "1935":a.port;
|
var port = (a.port == "")? "1935":a.port;
|
||||||
|
@ -319,6 +335,7 @@ function parse_rtmp_url(rtmp_url) {
|
||||||
server: a.hostname, port: port,
|
server: a.hostname, port: port,
|
||||||
vhost: vhost, app: app, stream: stream
|
vhost: vhost, app: app, stream: stream
|
||||||
};
|
};
|
||||||
|
__fill_query(a.search, ret);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -479,6 +479,8 @@
|
||||||
* app, the app of url.
|
* app, the app of url.
|
||||||
* stream, the stream of url, can endwith .flv or .mp4 or nothing for RTMP.
|
* stream, the stream of url, can endwith .flv or .mp4 or nothing for RTMP.
|
||||||
* autostart, whether auto play the stream.
|
* autostart, whether auto play the stream.
|
||||||
|
* extra params:
|
||||||
|
* shp_identify, hls+ param.
|
||||||
* for example:
|
* for example:
|
||||||
* http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream&server=ossrs.net&port=1935&autostart=true&schema=rtmp
|
* http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream&server=ossrs.net&port=1935&autostart=true&schema=rtmp
|
||||||
* http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream.flv&server=ossrs.net&port=8080&autostart=true&schema=http
|
* http://localhost:8088/players/srs_player.html?vhost=ossrs.net&app=live&stream=livestream.flv&server=ossrs.net&port=8080&autostart=true&schema=http
|
||||||
|
@ -572,9 +574,17 @@
|
||||||
var apply_url_change = function() {
|
var apply_url_change = function() {
|
||||||
var rtmp = parse_rtmp_url($("#txt_url").val());
|
var rtmp = parse_rtmp_url($("#txt_url").val());
|
||||||
var url = "http://" + query.host + query.pathname + "?"
|
var url = "http://" + query.host + query.pathname + "?"
|
||||||
+ "vhost=" + rtmp.vhost + "&app=" + rtmp.app + "&stream=" + rtmp.stream
|
+ "app=" + rtmp.app + "&stream=" + rtmp.stream
|
||||||
+ "&server=" + rtmp.server + "&port=" + rtmp.port
|
+ "&server=" + rtmp.server + "&port=" + rtmp.port
|
||||||
+ "&autostart=true";
|
+ "&autostart=true";
|
||||||
|
if (query.shp_identify) {
|
||||||
|
url += "&shp_identify=" + query.shp_identify;
|
||||||
|
}
|
||||||
|
if (rtmp.vhost == "__defaultVhost__") {
|
||||||
|
url += "&vhost=" + rtmp.server;
|
||||||
|
} else {
|
||||||
|
url += "&vhost=" + rtmp.vhost;
|
||||||
|
}
|
||||||
if (rtmp.schema == "http") {
|
if (rtmp.schema == "http") {
|
||||||
url += "&schema=http";
|
url += "&schema=http";
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ package
|
||||||
import flash.media.Video;
|
import flash.media.Video;
|
||||||
import flash.net.NetConnection;
|
import flash.net.NetConnection;
|
||||||
import flash.net.NetStream;
|
import flash.net.NetStream;
|
||||||
|
import flash.net.URLVariables;
|
||||||
import flash.system.Security;
|
import flash.system.Security;
|
||||||
import flash.ui.ContextMenu;
|
import flash.ui.ContextMenu;
|
||||||
import flash.ui.ContextMenuItem;
|
import flash.ui.ContextMenuItem;
|
||||||
|
@ -119,6 +120,20 @@ package
|
||||||
this.media_conn.connect(null);
|
this.media_conn.connect(null);
|
||||||
} else {
|
} else {
|
||||||
var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/"));
|
var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/"));
|
||||||
|
streamName = url.substr(url.lastIndexOf("/") + 1);
|
||||||
|
|
||||||
|
// parse vhost from stream query.
|
||||||
|
if (streamName.indexOf("?") >= 0) {
|
||||||
|
var uv:URLVariables = new URLVariables(user_url.substr(user_url.indexOf("?") + 1));
|
||||||
|
var domain:String = uv["domain"];
|
||||||
|
if (!domain) {
|
||||||
|
domain = uv["vhost"];
|
||||||
|
}
|
||||||
|
if (domain) {
|
||||||
|
tcUrl += "?vhost=" + domain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.media_conn.connect(tcUrl);
|
this.media_conn.connect(tcUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
120
trunk/research/players/srs_player/src/Dict.as
Executable file
120
trunk/research/players/srs_player/src/Dict.as
Executable file
|
@ -0,0 +1,120 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
97
trunk/research/players/srs_player/src/FlvPiece.as
Executable file
97
trunk/research/players/srs_player/src/FlvPiece.as
Executable file
|
@ -0,0 +1,97 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4744
trunk/research/players/srs_player/src/Hls.as
Executable file
4744
trunk/research/players/srs_player/src/Hls.as
Executable file
File diff suppressed because it is too large
Load diff
12
trunk/research/players/srs_player/src/ILogger.as
Normal file
12
trunk/research/players/srs_player/src/ILogger.as
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,15 +8,21 @@ package
|
||||||
import flash.events.FullScreenEvent;
|
import flash.events.FullScreenEvent;
|
||||||
import flash.events.MouseEvent;
|
import flash.events.MouseEvent;
|
||||||
import flash.events.NetStatusEvent;
|
import flash.events.NetStatusEvent;
|
||||||
|
import flash.events.ProgressEvent;
|
||||||
import flash.events.TimerEvent;
|
import flash.events.TimerEvent;
|
||||||
import flash.external.ExternalInterface;
|
import flash.external.ExternalInterface;
|
||||||
import flash.media.SoundTransform;
|
import flash.media.SoundTransform;
|
||||||
import flash.media.Video;
|
import flash.media.Video;
|
||||||
import flash.net.NetConnection;
|
import flash.net.NetConnection;
|
||||||
import flash.net.NetStream;
|
import flash.net.NetStream;
|
||||||
|
import flash.net.NetStreamAppendBytesAction;
|
||||||
|
import flash.net.URLRequest;
|
||||||
|
import flash.net.URLStream;
|
||||||
|
import flash.net.URLVariables;
|
||||||
import flash.system.Security;
|
import flash.system.Security;
|
||||||
import flash.ui.ContextMenu;
|
import flash.ui.ContextMenu;
|
||||||
import flash.ui.ContextMenuItem;
|
import flash.ui.ContextMenuItem;
|
||||||
|
import flash.utils.ByteArray;
|
||||||
import flash.utils.Timer;
|
import flash.utils.Timer;
|
||||||
import flash.utils.getTimer;
|
import flash.utils.getTimer;
|
||||||
import flash.utils.setTimeout;
|
import flash.utils.setTimeout;
|
||||||
|
@ -37,9 +43,39 @@ package
|
||||||
private var media_conn:NetConnection = null;
|
private var media_conn:NetConnection = null;
|
||||||
|
|
||||||
private var owner:srs_player = null;
|
private var owner:srs_player = null;
|
||||||
|
private var hls:Hls = null; // parse m3u8 and ts
|
||||||
|
|
||||||
|
// callback for hls.
|
||||||
|
private var shok:Boolean = false;
|
||||||
|
public var flvHeader:ByteArray = null;
|
||||||
|
public function onSequenceHeader():void {
|
||||||
|
if (shok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
shok = true;
|
||||||
|
}
|
||||||
|
public function onFlvBody(flv:ByteArray):void {
|
||||||
|
if (!media_stream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s:NetStream = media_stream;
|
||||||
|
s.appendBytes(flv);
|
||||||
|
log("FLV: AV " + flv.length + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
public function M3u8Player(o:srs_player) {
|
public function M3u8Player(o:srs_player) {
|
||||||
owner = o;
|
owner = o;
|
||||||
|
hls = new Hls(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function init(flashvars:Object):void {
|
public function init(flashvars:Object):void {
|
||||||
|
@ -50,35 +86,16 @@ package
|
||||||
return this.media_stream;
|
return this.media_stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// owner.on_player_metadata(evt.info.data);
|
||||||
public function play(url:String):void {
|
public function play(url:String):void {
|
||||||
|
var streamName:String;
|
||||||
this.user_url = url;
|
this.user_url = url;
|
||||||
|
|
||||||
this.media_conn = new NetConnection();
|
this.media_conn = new NetConnection();
|
||||||
this.media_conn.client = {};
|
this.media_conn.client = {};
|
||||||
this.media_conn.client.onBWDone = function():void {};
|
this.media_conn.client.onBWDone = function():void {};
|
||||||
this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
|
this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
|
||||||
log("NetConnection: code=" + evt.info.code);
|
log("NetConnection: code=" + evt.info.code + ", data is " + evt.info.data);
|
||||||
|
|
||||||
if (evt.info.hasOwnProperty("data") && evt.info.data) {
|
|
||||||
owner.on_player_metadata(evt.info.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reject by server, maybe redirect.
|
|
||||||
if (evt.info.code == "NetConnection.Connect.Rejected") {
|
|
||||||
// RTMP 302 redirect.
|
|
||||||
if (evt.info.hasOwnProperty("ex") && evt.info.ex.code == 302) {
|
|
||||||
streamName = url.substr(url.lastIndexOf("/") + 1);
|
|
||||||
url = evt.info.ex.redirect + "/" + streamName;
|
|
||||||
log("Async RTMP 302 Redirect to: " + url);
|
|
||||||
|
|
||||||
// notify server.
|
|
||||||
media_conn.call("Redirected", null, evt.info.ex.redirect);
|
|
||||||
|
|
||||||
// do 302.
|
|
||||||
owner.on_player_302(url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: FIXME: failed event.
|
// TODO: FIXME: failed event.
|
||||||
if (evt.info.code != "NetConnection.Connect.Success") {
|
if (evt.info.code != "NetConnection.Connect.Success") {
|
||||||
|
@ -103,12 +120,8 @@ package
|
||||||
// setup stream before play.
|
// setup stream before play.
|
||||||
owner.on_player_before_play();
|
owner.on_player_before_play();
|
||||||
|
|
||||||
if (url.indexOf("http") == 0) {
|
media_stream.play(null);
|
||||||
media_stream.play(url);
|
refresh_m3u8();
|
||||||
} else {
|
|
||||||
streamName = url.substr(url.lastIndexOf("/") + 1);
|
|
||||||
media_stream.play(streamName);
|
|
||||||
}
|
|
||||||
|
|
||||||
owner.on_player_play();
|
owner.on_player_play();
|
||||||
});
|
});
|
||||||
|
@ -127,6 +140,124 @@ package
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 * 0.5;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
// reset and start to parse this ts.
|
||||||
|
hls.reset();
|
||||||
|
|
||||||
|
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(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);
|
||||||
|
});
|
||||||
|
|
||||||
|
log("start download " + uri);
|
||||||
|
url.load(new URLRequest(uri));
|
||||||
|
}
|
||||||
|
|
||||||
private function log(msg:String):void {
|
private function log(msg:String):void {
|
||||||
Utility.log(js_id, msg);
|
Utility.log(js_id, msg);
|
||||||
}
|
}
|
||||||
|
|
93
trunk/research/players/srs_player/src/TraceLogger.as
Normal file
93
trunk/research/players/srs_player/src/TraceLogger.as
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -183,12 +183,16 @@ package
|
||||||
* or got video dimension change event(the DAR notification), to update the metadata manually.
|
* or got video dimension change event(the DAR notification), to update the metadata manually.
|
||||||
*/
|
*/
|
||||||
private function system_on_metadata(metadata:Object):void {
|
private function system_on_metadata(metadata:Object):void {
|
||||||
this.media_metadata = metadata;
|
if (!media_metadata) {
|
||||||
|
media_metadata = {};
|
||||||
|
}
|
||||||
|
for (var k:String in metadata) {
|
||||||
|
media_metadata[k] = metadata[k];
|
||||||
|
}
|
||||||
|
|
||||||
// update the debug info.
|
// update the debug info.
|
||||||
if (metadata) {
|
on_debug_info(media_metadata);
|
||||||
on_debug_info(metadata);
|
update_context_items();
|
||||||
}
|
|
||||||
|
|
||||||
// for js.
|
// for js.
|
||||||
var obj:Object = __get_video_size_object();
|
var obj:Object = __get_video_size_object();
|
||||||
|
@ -428,8 +432,13 @@ package
|
||||||
|
|
||||||
js_call_stop();
|
js_call_stop();
|
||||||
|
|
||||||
|
// trim last ?
|
||||||
|
while (Utility.stringEndswith(url, "?")) {
|
||||||
|
url = url.substr(0, url.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// create player.
|
// create player.
|
||||||
if (Utility.stringEndswith(url, ".m3u8") && Utility.stringStartswith(url, "http://")) {
|
if (url.indexOf(".m3u8") > 0 && Utility.stringStartswith(url, "http://")) {
|
||||||
player = new M3u8Player(this);
|
player = new M3u8Player(this);
|
||||||
log("create M3U8 player.");
|
log("create M3U8 player.");
|
||||||
} else {
|
} else {
|
||||||
|
@ -477,8 +486,7 @@ package
|
||||||
setChildIndex(media_video, 0);
|
setChildIndex(media_video, 0);
|
||||||
}
|
}
|
||||||
public function on_player_metadata(data:Object):void {
|
public function on_player_metadata(data:Object):void {
|
||||||
on_debug_info(data);
|
system_on_metadata(data);
|
||||||
update_context_items();
|
|
||||||
}
|
}
|
||||||
public function on_player_302(url:String):void {
|
public function on_player_302(url:String):void {
|
||||||
setTimeout(function():void{
|
setTimeout(function():void{
|
||||||
|
@ -503,6 +511,9 @@ package
|
||||||
* 3. override with codec size if specified.
|
* 3. override with codec size if specified.
|
||||||
*/
|
*/
|
||||||
private function __get_video_size_object():Object {
|
private function __get_video_size_object():Object {
|
||||||
|
if (!media_video) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
var obj:Object = {
|
var obj:Object = {
|
||||||
width: media_video.width,
|
width: media_video.width,
|
||||||
height: media_video.height
|
height: media_video.height
|
||||||
|
|
Loading…
Reference in a new issue