support play hls

pull/609/head
winlin 9 years ago
parent 69ec66ee36
commit 46a31f4884

@ -61,28 +61,46 @@ function build_default_rtmp_url() {
var app = (query.app == undefined)? "live":query.app;
var stream = (query.stream == undefined)? "demo":query.stream;
if (server == vhost || vhost == "") {
return schema + "://" + server + ":" + port + "/" + app + "/" + stream;
} else {
return schema + "://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
var queries = [];
if (server != vhost && vhost != "__defaultVhost__") {
queries.push("vhost=" + vhost);
}
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.
function build_default_publish_rtmp_url() {
var query = parse_query_string();
var schema = (query.schema == undefined)? "rtmp":query.schema;
var server = (query.server == undefined)? window.location.hostname:query.server;
var port = (query.port == undefined)? 1935:query.port;
var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost;
var app = (query.app == undefined)? "live":query.app;
var stream = (query.stream == undefined)? "demo":query.stream;
if (server == vhost || vhost == "") {
return "rtmp://" + server + ":" + port + "/" + app + "/" + stream;
} else {
vhost = srs_get_player_chat_vhost(vhost);
return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
var queries = [];
if (server != vhost && vhost != "__defaultVhost__") {
queries.push("vhost=" + srs_get_player_chat_vhost(vhost));
}
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
function build_default_bandwidth_rtmp_url() {

@ -5,7 +5,7 @@
* depends: jquery1.10
* https://code.csdn.net/snippets/147103
* @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.
if (query_string.indexOf("?") > 0) {
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
@ -257,7 +270,10 @@ function parse_query_string(){
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) {
// @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");
a.href = rtmp_url.replace("rtmp://", "http://").replace("?", "...").replace("=", "...");
a.href = rtmp_url.replace("rtmp://", "http://");
var vhost = a.hostname;
var port = (a.port == "")? "1935":a.port;
@ -319,6 +335,7 @@ function parse_rtmp_url(rtmp_url) {
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
__fill_query(a.search, ret);
return ret;
}

@ -479,6 +479,8 @@
* app, the app of url.
* stream, the stream of url, can endwith .flv or .mp4 or nothing for RTMP.
* autostart, whether auto play the stream.
* extra params:
* shp_identify, hls+ param.
* 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.flv&server=ossrs.net&port=8080&autostart=true&schema=http
@ -572,9 +574,17 @@
var apply_url_change = function() {
var rtmp = parse_rtmp_url($("#txt_url").val());
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
+ "&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") {
url += "&schema=http";
}

@ -14,6 +14,7 @@ package
import flash.media.Video;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.net.URLVariables;
import flash.system.Security;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
@ -21,7 +22,7 @@ package
import flash.utils.getTimer;
import flash.utils.setTimeout;
import flashx.textLayout.formats.Float;
import flashx.textLayout.formats.Float;
/**
* common player to play rtmp/flv stream,
@ -119,6 +120,20 @@ package
this.media_conn.connect(null);
} else {
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);
}
}

@ -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);
}
}
}

@ -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;
}
}
}

File diff suppressed because it is too large Load Diff

@ -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,19 +8,25 @@ package
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.URLRequest;
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;
/**
@ -37,9 +43,39 @@ package
private var media_conn:NetConnection = 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) {
owner = o;
hls = new Hls(this);
}
public function init(flashvars:Object):void {
@ -50,35 +86,16 @@ package
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);
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;
}
}
log("NetConnection: code=" + evt.info.code + ", data is " + evt.info.data);
// TODO: FIXME: failed event.
if (evt.info.code != "NetConnection.Connect.Success") {
@ -103,12 +120,8 @@ package
// setup stream before play.
owner.on_player_before_play();
if (url.indexOf("http") == 0) {
media_stream.play(url);
} else {
streamName = url.substr(url.lastIndexOf("/") + 1);
media_stream.play(streamName);
}
media_stream.play(null);
refresh_m3u8();
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 {
Utility.log(js_id, msg);
}

@ -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,13 +183,17 @@ package
* or got video dimension change event(the DAR notification), to update the metadata manually.
*/
private function system_on_metadata(metadata:Object):void {
this.media_metadata = metadata;
// update the debug info.
if (metadata) {
on_debug_info(metadata);
if (!media_metadata) {
media_metadata = {};
}
for (var k:String in metadata) {
media_metadata[k] = metadata[k];
}
// update the debug info.
on_debug_info(media_metadata);
update_context_items();
// for js.
var obj:Object = __get_video_size_object();
@ -427,9 +431,14 @@ package
);
js_call_stop();
// trim last ?
while (Utility.stringEndswith(url, "?")) {
url = url.substr(0, url.length - 1);
}
// create player.
if (Utility.stringEndswith(url, ".m3u8") && Utility.stringStartswith(url, "http://")) {
if (url.indexOf(".m3u8") > 0 && Utility.stringStartswith(url, "http://")) {
player = new M3u8Player(this);
log("create M3U8 player.");
} else {
@ -477,8 +486,7 @@ package
setChildIndex(media_video, 0);
}
public function on_player_metadata(data:Object):void {
on_debug_info(data);
update_context_items();
system_on_metadata(data);
}
public function on_player_302(url:String):void {
setTimeout(function():void{
@ -503,6 +511,9 @@ package
* 3. override with codec size if specified.
*/
private function __get_video_size_object():Object {
if (!media_video) {
return {};
}
var obj:Object = {
width: media_video.width,
height: media_video.height

Loading…
Cancel
Save