1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

add linux version of band check app; add web version of band check app

This commit is contained in:
wenjiegit 2013-12-26 06:06:16 +08:00
commit 237fb13e1b
35 changed files with 1966 additions and 218 deletions

View file

@ -6,8 +6,8 @@ srs is a simple, high-performance, running in single process, origin live server
srs supports vhost, rtmp, HLS, transcoding, forward, http hooks. <br/> srs supports vhost, rtmp, HLS, transcoding, forward, http hooks. <br/>
blog: [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/> blog: [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/>
see also: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server) <br/> see also: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server) <br/>
see also: [http://winlinvip.github.io/simple-rtmp-server](http://winlinvip.github.io/simple-rtmp-server) see also: [http://winlinvip.github.io/simple-rtmp-server](http://winlinvip.github.io/simple-rtmp-server) <br/>
TencentQQ: http://url.cn/WAHICw (Group: 212189142) TencentQQ: [http://url.cn/WAHICw](http://url.cn/WAHICw) (Group: 212189142)
### Contributors ### Contributors
winlin([winterserver](#)): [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/> winlin([winterserver](#)): [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/>
@ -253,7 +253,8 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw
</pre> </pre>
### Releases ### Releases
* 2013-12-08, [release v0.8](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.8), support http hooks callback, update st-load. 19186 lines.<br/> * 2013-12-25, [release v0.9](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.9), support bandwidth test, add player/encoder/chat demos. 20926 lines.<br/>
* 2013-12-08, [release v0.8](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.8), support http hooks callback, update [st_load](https://github.com/winlinvip/st-load). 19186 lines.<br/>
* 2013-12-03, [release v0.7](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.7), support live stream transcoding. 17605 lines.<br/> * 2013-12-03, [release v0.7](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.7), support live stream transcoding. 17605 lines.<br/>
* 2013-11-29, [release v0.6](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.6), support forward stream to origin/edge. 16094 lines.<br/> * 2013-11-29, [release v0.6](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.6), support forward stream to origin/edge. 16094 lines.<br/>
* 2013-11-26, [release v0.5](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.5), support HLS(m3u8), fragment and window. 14449 lines.<br/> * 2013-11-26, [release v0.5](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.5), support HLS(m3u8), fragment and window. 14449 lines.<br/>
@ -264,6 +265,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw
* 2013-10-17, created.<br/> * 2013-10-17, created.<br/>
### Compare ### Compare
* srs v0.9: 20926 lines. player/encoder/chat demos. bandwidth test for encoder/CDN.<br/>
* srs v0.8: 19186 lines. implements http hooks refer to [nginx-rtmp](https://github.com/arut/nginx-rtmp-module). <br/> * srs v0.8: 19186 lines. implements http hooks refer to [nginx-rtmp](https://github.com/arut/nginx-rtmp-module). <br/>
* srs v0.7: 17605 lines. implements transcoding(FFMPEG) feature refer to [wowza](http://www.wowza.com). <br/> * srs v0.7: 17605 lines. implements transcoding(FFMPEG) feature refer to [wowza](http://www.wowza.com). <br/>
* srs v0.6: 16094 lines. important feature forward for CDN. <br/> * srs v0.6: 16094 lines. important feature forward for CDN. <br/>
@ -276,6 +278,8 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw
* nginx v1.5.0: 139524 lines <br/> * nginx v1.5.0: 139524 lines <br/>
### History ### History
* v0.9, 2013-12-25, [v0.9](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.9) released. 20926 lines.
* v0.9, 2013-12-25, fix the bitrate bug(in Bps), use enhanced microphone.
* v0.9, 2013-12-22, demo video meeting or chat(srs+cherrypy+jquery+bootstrap). * v0.9, 2013-12-22, demo video meeting or chat(srs+cherrypy+jquery+bootstrap).
* v0.9, 2013-12-22, merge from wenjie, support banwidth test. * v0.9, 2013-12-22, merge from wenjie, support banwidth test.
* v0.9, 2013-12-22, merge from wenjie: support set chunk size at vhost level * v0.9, 2013-12-22, merge from wenjie: support set chunk size at vhost level

View file

@ -261,11 +261,8 @@ if [ $SRS_HLS = YES ]; then
ln -sf `pwd`/research/players/crossdomain.xml ${SRS_OBJS}/nginx/html/crossdomain.xml ln -sf `pwd`/research/players/crossdomain.xml ${SRS_OBJS}/nginx/html/crossdomain.xml
# override the default index. # override the default index.
cat <<END > ${SRS_OBJS}/nginx/html/index.html rm -f ${SRS_OBJS}/nginx/html/index.html &&
<script type="text/javascript"> ln -sf `pwd`/research/players/nginx_index.html ${SRS_OBJS}/nginx/html/index.html
window.location.href = "players/index.html";
</script>
END
fi fi
if [ $SRS_HLS = YES ]; then if [ $SRS_HLS = YES ]; then
@ -300,6 +297,12 @@ else
echo "#undef SRS_HTTP" >> $SRS_AUTO_HEADERS_H echo "#undef SRS_HTTP" >> $SRS_AUTO_HEADERS_H
fi fi
echo "link players to cherrypy static-dir"
rm -f research/api-server/static-dir/players &&
ln -sf `pwd`/research/players research/api-server/static-dir/players &&
rm -f research/api-server/static-dir/crossdomain.xml &&
ln -sf `pwd`/research/players/crossdomain.xml research/api-server/static-dir/crossdomain.xml
##################################################################################### #####################################################################################
# openssl, for rtmp complex handshake # openssl, for rtmp complex handshake
##################################################################################### #####################################################################################

View file

@ -343,7 +343,7 @@ class RESTChats(object):
self.__chat_lock = threading.Lock(); self.__chat_lock = threading.Lock();
# dead time in seconds, if exceed, remove the chat. # dead time in seconds, if exceed, remove the chat.
self.__dead_time = 30; self.__dead_time = 15;
def GET(self): def GET(self):
enable_crossdomain() enable_crossdomain()
@ -474,7 +474,8 @@ if len(sys.argv) <= 1:
# parse port from user options. # parse port from user options.
port = int(sys.argv[1]) port = int(sys.argv[1])
trace("api server listen at port: %s"%(port)) static_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "static-dir"))
trace("api server listen at port: %s, static_dir: %s"%(port, static_dir))
# cherrypy config. # cherrypy config.
conf = { conf = {
@ -483,9 +484,12 @@ conf = {
'server.socket_host': '0.0.0.0', 'server.socket_host': '0.0.0.0',
'server.socket_port': port, 'server.socket_port': port,
'tools.encode.on': True, 'tools.encode.on': True,
'tools.staticdir.on': True,
'tools.encode.encoding': "utf-8" 'tools.encode.encoding': "utf-8"
}, },
'/': { '/': {
'tools.staticdir.dir': static_dir,
'tools.staticdir.index': "index.html",
# for cherrypy RESTful api support # for cherrypy RESTful api support
'request.dispatch': cherrypy.dispatch.MethodDispatcher() 'request.dispatch': cherrypy.dispatch.MethodDispatcher()
} }

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<title>SRS</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="players/css/bootstrap.min.css"/>
<script type="text/javascript" src="players/js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="players/js/bootstrap.min.js"></script>
<script type="text/javascript" src="players/js/swfobject.js"></script>
<script type="text/javascript" src="players/js/srs.page.js"></script>
<style>
body{
padding-top: 55px;
}
</style>
<script type="text/javascript">
$(function(){
update_nav();
// direct to the default vhost for players.
window.location.href = "players/index.html" + window.location.search;
});
</script>
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="index.html">SRS</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
<li><a id="nav_vlc" href="vlc.html">VLC播放器</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<hr>
<footer>
<p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team &copy; 2013</a></p>
</footer>
</div>
</body>

View file

@ -7,7 +7,8 @@
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script> <script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/srs.page.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
@ -17,8 +18,19 @@
$(function(){ $(function(){
update_nav(); update_nav();
// direct to the default vhost for players. var query = parse_query_string();
window.location.href = "srs_player.html?vhost=" + srs_get_player_vhost(); var url = "srs_chat.html?vhost=" + srs_get_player_vhost();
for (var key in query.user_query) {
if (key == "vhost") {
continue;
}
url += "&" + key + "=" + query[key];
}
setTimeout(function(){
window.location.href = url;
}, 100);
}); });
</script> </script>
</head> </head>
@ -26,7 +38,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>

0
trunk/research/players/js/AdobeFlashPlayerInstall.swf Executable file → Normal file
View file

0
trunk/research/players/js/bootstrap.min.js vendored Executable file → Normal file
View file

0
trunk/research/players/js/jquery-1.10.2.min.js vendored Executable file → Normal file
View file

0
trunk/research/players/js/jquery-1.10.2.min.map Executable file → Normal file
View file

View file

@ -0,0 +1,486 @@
/*
json2.js
2013-05-26
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, regexp: true */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (typeof JSON !== 'object') {
JSON = {};
}
(function () {
'use strict';
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function () {
return isFinite(this.valueOf())
? this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z'
: null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function () {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string'
? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0
? '[]'
: gap
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
: '[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0
? '{}'
: gap
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
: '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function'
? walk({'': j}, '')
: j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

View file

@ -0,0 +1,37 @@
/**
* log specified, there must be a log element as:
<!-- for the log -->
<div class="alert alert-info fade in" id="txt_log">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong><span id="txt_log_title">标题:</span></strong>
<span id="txt_log_msg">日志内容</span>
</div>
*/
var srs_log_disabled = false;
function info(desc) {
if (srs_log_disabled) {
return;
}
$("#txt_log").addClass("alert-info").removeClass("alert-error").removeClass("alert-warn");
$("#txt_log_title").text("Info:");
$("#txt_log_msg").html(desc);
}
function warn(code, desc) {
if (srs_log_disabled) {
return;
}
$("#txt_log").removeClass("alert-info").removeClass("alert-error").addClass("alert-warn");
$("#txt_log_title").text("Warn:");
$("#txt_log_msg").html("code: " + code + ", " + desc);
}
function error(code, desc) {
if (srs_log_disabled) {
return;
}
$("#txt_log").removeClass("alert-info").addClass("alert-error").removeClass("alert-warn");
$("#txt_log_title").text("Error:");
$("#txt_log_msg").html("code: " + code + ", " + desc);
}

View file

@ -0,0 +1,363 @@
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/**
* player specified size.
*/
function srs_get_player_modal() { return 740; }
function srs_get_player_width() { return srs_get_player_modal() - 30; }
function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
// to query the swf anti cache.
function srs_get_version_code() { return "1.17"; }
// get the default vhost for players.
function srs_get_player_vhost() { return "players"; }
// the api server port, for chat room.
function srs_get_api_server_port() { return 8085; }
// get the stream published to vhost,
// generally we need to transcode the stream to support HLS and filters.
// for example, src_vhost is "players", we transcode stream to vhost "players_pub".
// if not equals to the player vhost, return the orignal vhost.
function srs_get_player_publish_vhost(src_vhost) { return (src_vhost != srs_get_player_vhost())? src_vhost:(src_vhost + "_pub"); }
// for chat, use rtmp only vhost, low latecy, without gop cache.
function srs_get_player_chat_vhost(src_vhost) { return (src_vhost != srs_get_player_vhost())? src_vhost:(src_vhost + "_pub_rtmp"); }
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/**
* update the navigator, add same query string.
*/
function update_nav() {
$("#srs_index").attr("href", "index.html" + window.location.search);
$("#nav_srs_player").attr("href", "srs_player.html" + window.location.search);
$("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search);
$("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search);
$("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search);
$("#nav_jwplayer6").attr("href", "jwplayer6.html" + window.location.search);
$("#nav_osmf").attr("href", "osmf.html" + window.location.search);
$("#nav_vlc").attr("href", "vlc.html" + window.location.search);
}
/**
@param server the ip of server. default to window.location.hostname
@param vhost the vhost of rtmp. default to window.location.hostname
@param port the port of rtmp. default to 1935
@param app the app of rtmp. default to live.
@param stream the stream of rtmp. default to livestream.
*/
function build_default_rtmp_url() {
var query = parse_query_string();
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)? "livestream":query.stream;
if (server == vhost || vhost == "") {
return "rtmp://" + server + ":" + port + "/" + app + "/" + stream;
} else {
return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
}
}
// for the chat to init the publish url.
function build_default_publish_rtmp_url() {
var query = parse_query_string();
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)? "livestream":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;
}
}
/**
@param server the ip of server. default to window.location.hostname
@param vhost the vhost of hls. default to window.location.hostname
@param hls_vhost the vhost of hls. override the server if specified.
@param hls_port the port of hls. default to window.location.port
@param app the app of hls. default to live.
@param stream the stream of hls. default to livestream.
*/
function build_default_hls_url() {
var query = parse_query_string();
// for http, use hls_vhost to override server if specified.
var server = window.location.hostname;
if (query.server != undefined) {
server = query.server;
} else if (query.hls_vhost != undefined) {
server = query.hls_vhost;
}
var port = (query.hls_port == undefined)? window.location.port:query.hls_port;
var app = (query.app == undefined)? "live":query.app;
var stream = (query.stream == undefined)? "livestream":query.stream;
if (port == "" || port == null || port == undefined) {
port = 80;
}
return "http://" + server + ":" + port + "/" + app + "/" + stream + ".m3u8";
}
/**
* initialize the page.
* @param rtmp_url the div id contains the rtmp stream url to play
* @param hls_url the div id contains the hls stream url to play
* @param modal_player the div id contains the modal player
*/
function srs_init(rtmp_url, hls_url, modal_player) {
update_nav();
if (rtmp_url) {
$(rtmp_url).val(build_default_rtmp_url());
}
if (hls_url) {
$(hls_url).val(build_default_hls_url());
}
if (modal_player) {
$(modal_player).width(srs_get_player_modal() + "px");
$(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px");
}
}
// for the chat to init the publish url.
function srs_init_publish(rtmp_url) {
update_nav();
if (rtmp_url) {
$(rtmp_url).val(build_default_publish_rtmp_url());
}
}
// check whether can republish
function srs_can_republish() {
var browser = get_browser_agents();
if (browser.Chrome || browser.Firefox) {
return true;
}
if (browser.MSIE || browser.QQBrowser) {
return false;
}
return false;
}
// without default values set.
function srs_initialize_codec_page(
cameras, microphones,
sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
) {
$(sl_cameras).empty();
for (var i = 0; i < cameras.length; i++) {
$(sl_cameras).append("<option value='" + i + "'>" + cameras[i] + "</option");
}
// optional: select the except matches
matchs = ["virtual"];
for (var i = 0; i < cameras.length; i++) {
for (var j = 0; j < matchs.length; j++) {
if (cameras[i].toLowerCase().indexOf(matchs[j]) == -1) {
$(sl_cameras + " option[value='" + i + "']").attr("selected", true);
break;
}
}
if (j < matchs.length) {
break;
}
}
// optional: select the first matched.
matchs = ["truevision", "integrated"];
for (var i = 0; i < cameras.length; i++) {
for (var j = 0; j < matchs.length; j++) {
if (cameras[i].toLowerCase().indexOf(matchs[j]) >= 0) {
$(sl_cameras + " option[value='" + i + "']").attr("selected", true);
break;
}
}
if (j < matchs.length) {
break;
}
}
$(sl_microphones).empty();
for (var i = 0; i < microphones.length; i++) {
$(sl_microphones).append("<option value='" + i + "'>" + microphones[i] + "</option");
}
// optional: select the except matches
matchs = ["default"];
for (var i = 0; i < microphones.length; i++) {
for (var j = 0; j < matchs.length; j++) {
if (microphones[i].toLowerCase().indexOf(matchs[j]) == -1) {
$(sl_microphones + " option[value='" + i + "']").attr("selected", true);
break;
}
}
if (j < matchs.length) {
break;
}
}
// optional: select the first matched.
matchs = ["realtek", "内置式麦克风"];
for (var i = 0; i < microphones.length; i++) {
for (var j = 0; j < matchs.length; j++) {
if (microphones[i].toLowerCase().indexOf(matchs[j]) >= 0) {
$(sl_microphones + " option[value='" + i + "']").attr("selected", true);
break;
}
}
if (j < matchs.length) {
break;
}
}
$(sl_vcodec).empty();
var vcodecs = ["h264", "vp6"];
vcodecs = ["h264"]; // h264 only.
for (var i = 0; i < vcodecs.length; i++) {
$(sl_vcodec).append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");
}
$(sl_profile).empty();
var profiles = ["baseline", "main"];
for (var i = 0; i < profiles.length; i++) {
$(sl_profile).append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");
}
$(sl_level).empty();
var levels = ["1", "1b", "1.1", "1.2", "1.3",
"2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
for (var i = 0; i < levels.length; i++) {
$(sl_level).append("<option value='" + levels[i] + "'>" + levels[i] + "</option");
}
$(sl_gop).empty();
var gops = ["0.3", "0.5", "1", "2", "3", "4",
"5", "6", "7", "8", "9", "10", "15", "20"];
for (var i = 0; i < gops.length; i++) {
$(sl_gop).append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");
}
$(sl_size).empty();
var sizes = ["176x144", "320x240", "352x240",
"352x288", "480x360", "640x480", "720x480", "720x576", "800x600",
"1024x768", "1280x720", "1360x768", "1920x1080"];
for (i = 0; i < sizes.length; i++) {
$(sl_size).append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");
}
$(sl_fps).empty();
var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
for (i = 0; i < fpses.length; i++) {
$(sl_fps).append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");
}
$(sl_bitrate).empty();
var bitrates = ["50", "200", "350", "500", "650", "800",
"950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
for (i = 0; i < bitrates.length; i++) {
$(sl_bitrate).append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");
}
}
/**
* when publisher ready, init the page elements.
*/
function srs_publisher_initialize_page(
cameras, microphones,
sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
) {
srs_initialize_codec_page(
cameras, microphones,
sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
);
//var profiles = ["baseline", "main"];
$(sl_profile + " option[value='main']").attr("selected", true);
//var levels = ["1", "1b", "1.1", "1.2", "1.3",
// "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
$(sl_level + " option[value='4.1']").attr("selected", true);
//var gops = ["0.3", "0.5", "1", "2", "3", "4",
// "5", "6", "7", "8", "9", "10", "15", "20"];
$(sl_gop + " option[value='10']").attr("selected", true);
//var sizes = ["176x144", "320x240", "352x240",
// "352x288", "480x360", "640x480", "720x480", "720x576", "800x600",
// "1024x768", "1280x720", "1360x768", "1920x1080"];
$(sl_size + " option[value='640x480']").attr("selected", true);
//var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
$(sl_fps + " option[value='20']").attr("selected", true);
//var bitrates = ["50", "200", "350", "500", "650", "800",
// "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
$(sl_bitrate + " option[value='500']").attr("selected", true);
}
/**
* for chat, use low latecy settings.
*/
function srs_chat_initialize_page(
cameras, microphones,
sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
) {
srs_initialize_codec_page(
cameras, microphones,
sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
);
//var profiles = ["baseline", "main"];
$(sl_profile + " option[value='baseline']").attr("selected", true);
//var levels = ["1", "1b", "1.1", "1.2", "1.3",
// "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
$(sl_level + " option[value='3.1']").attr("selected", true);
//var gops = ["0.3", "0.5", "1", "2", "3", "4",
// "5", "6", "7", "8", "9", "10", "15", "20"];
$(sl_gop + " option[value='2']").attr("selected", true);
//var sizes = ["176x144", "320x240", "352x240",
// "352x288", "480x360", "640x480", "720x480", "720x576", "800x600",
// "1024x768", "1280x720", "1360x768", "1920x1080"];
$(sl_size + " option[value='480x360']").attr("selected", true);
//var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
$(sl_fps + " option[value='15']").attr("selected", true);
//var bitrates = ["50", "200", "350", "500", "650", "800",
// "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
$(sl_bitrate + " option[value='350']").attr("selected", true);
}
/**
* get the vcodec and acodec.
*/
function srs_publiser_get_codec(
vcodec, acodec,
sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
) {
acodec.device_code = $(sl_microphones).val();
acodec.device_name = $(sl_microphones).text();
vcodec.device_code = $(sl_cameras).find("option:selected").val();
vcodec.device_name = $(sl_cameras).find("option:selected").text();
vcodec.codec = $(sl_vcodec).find("option:selected").val();
vcodec.profile = $(sl_profile).find("option:selected").val();
vcodec.level = $(sl_level).find("option:selected").val();
vcodec.fps = $(sl_fps).find("option:selected").val();
vcodec.gop = $(sl_gop).find("option:selected").val();
vcodec.size = $(sl_size).find("option:selected").val();
vcodec.bitrate = $(sl_bitrate).find("option:selected").val();
}

View file

@ -0,0 +1,194 @@
/**
* the SrsPlayer object.
* @param container the html container id.
* @param width a float value specifies the width of player.
* @param height a float value specifies the height of player.
* @param private_object [optional] an object that used as private object,
* for example, the logic chat object which owner this player.
*/
function SrsPlayer(container, width, height, private_object) {
if (!SrsPlayer.__id) {
SrsPlayer.__id = 100;
}
if (!SrsPlayer.__players) {
SrsPlayer.__players = [];
}
SrsPlayer.__players.push(this);
this.private_object = private_object;
this.container = container;
this.width = width;
this.height = height;
this.id = SrsPlayer.__id++;
this.stream_url = null;
this.buffer_time = 0.8; // default to 0.8
this.volume = 1.0; // default to 100%
this.callbackObj = null;
// callback set the following values.
this.meatadata = {}; // for on_player_metadata
this.time = 0; // current stream time.
this.buffer_length = 0; // current stream buffer length.
}
/**
* user can set some callback, then start the player.
* @param url the default url.
* callbacks:
* on_player_ready():int, when srs player ready, user can play.
* on_player_metadata(metadata:Object):int, when srs player get metadata.
*/
SrsPlayer.prototype.start = function(url) {
if (url) {
this.stream_url = url;
}
// embed the flash.
var flashvars = {};
flashvars.id = this.id;
flashvars.on_player_ready = "__srs_on_player_ready";
flashvars.on_player_metadata = "__srs_on_player_metadata";
flashvars.on_player_timer = "__srs_on_player_timer";
var params = {};
params.wmode = "opaque";
params.allowFullScreen = "true";
params.allowScriptAccess = "always";
var attributes = {};
var self = this;
swfobject.embedSWF(
"srs_player/release/srs_player.swf?_version="+srs_get_version_code(),
this.container,
this.width, this.height,
"11.1", "js/AdobeFlashPlayerInstall.swf",
flashvars, params, attributes,
function(callbackObj){
self.callbackObj = callbackObj;
}
);
return this;
}
/**
* play the stream.
* @param stream_url the url of stream, rtmp or http.
* @param volume the volume, 0 is mute, 1 is 100%, 2 is 200%.
*/
SrsPlayer.prototype.play = function(url, volume) {
this.stop();
SrsPlayer.__players.push(this);
if (url) {
this.stream_url = url;
}
// volume maybe 0, so never use if(volume) to check its value.
if (volume != null && volume != undefined) {
this.volume = volume;
}
this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time, this.volume);
}
SrsPlayer.prototype.stop = function() {
for (var i = 0; i < SrsPlayer.__players.length; i++) {
var player = SrsPlayer.__players[i];
if (player.id != this.id) {
continue;
}
SrsPlayer.__players.splice(i, 1);
break;
}
this.callbackObj.ref.__stop();
}
SrsPlayer.prototype.pause = function() {
this.callbackObj.ref.__pause();
}
SrsPlayer.prototype.resume = function() {
this.callbackObj.ref.__resume();
}
/**
* to set the DAR, for example, DAR=16:9 where num=16,den=9.
* @param num, for example, 16.
* use metadata width if 0.
* use user specified width if -1.
* @param den, for example, 9.
* use metadata height if 0.
* use user specified height if -1.
*/
SrsPlayer.prototype.set_dar = function(num, den) {
this.callbackObj.ref.__set_dar(num, den);
}
/**
* set the fullscreen size data.
* @refer the refer fullscreen mode. it can be:
* video: use video orignal size.
* screen: use screen size to rescale video.
* @param percent, the rescale percent, where
* 100 means 100%.
*/
SrsPlayer.prototype.set_fs = function(refer, percent) {
this.callbackObj.ref.__set_fs(refer, percent);
}
/**
* set the stream buffer time in seconds.
* @buffer_time the buffer time in seconds.
*/
SrsPlayer.prototype.set_bt = function(buffer_time) {
this.buffer_time = buffer_time;
this.callbackObj.ref.__set_bt(buffer_time);
}
SrsPlayer.prototype.on_player_ready = function() {
}
SrsPlayer.prototype.on_player_metadata = function(metadata) {
// ignore.
}
SrsPlayer.prototype.on_player_timer = function(time, buffer_length) {
// ignore.
}
function __srs_find_player(id) {
for (var i = 0; i < SrsPlayer.__players.length; i++) {
var player = SrsPlayer.__players[i];
if (player.id != id) {
continue;
}
return player;
}
throw new Error("player not found. id=" + id);
}
function __srs_on_player_ready(id) {
var player = __srs_find_player(id);
player.on_player_ready();
}
function __srs_on_player_metadata(id, metadata) {
var player = __srs_find_player(id);
// user may override the on_player_metadata,
// so set the data before invoke it.
player.metadata = metadata;
player.on_player_metadata(metadata);
}
function __srs_on_player_timer(id, time, buffer_length) {
var player = __srs_find_player(id);
buffer_length = Math.max(0, buffer_length);
buffer_length = Math.min(player.buffer_time, buffer_length);
time = Math.max(0, time);
// user may override the on_player_timer,
// so set the data before invoke it.
player.time = time;
player.buffer_length = buffer_length;
player.on_player_timer(time, buffer_length);
}

View file

@ -0,0 +1,173 @@
/**
* the SrsPublisher object.
* @param container the html container id.
* @param width a float value specifies the width of publisher.
* @param height a float value specifies the height of publisher.
* @param private_object [optional] an object that used as private object,
* for example, the logic chat object which owner this publisher.
*/
function SrsPublisher(container, width, height, private_object) {
if (!SrsPublisher.__id) {
SrsPublisher.__id = 100;
}
if (!SrsPublisher.__publishers) {
SrsPublisher.__publishers = [];
}
SrsPublisher.__publishers.push(this);
this.private_object = private_object;
this.container = container;
this.width = width;
this.height = height;
this.id = SrsPublisher.__id++;
this.callbackObj = null;
// set the values when publish.
this.url = null;
this.vcodec = {};
this.acodec = {};
// callback set the following values.
this.cameras = [];
this.microphones = [];
this.code = 0;
// error code defines.
this.errors = {
"100": "无法获取指定的摄像头。", //error_camera_get
"101": "无法获取指定的麦克风。", //error_microphone_get
"102": "摄像头为禁用状态推流时请允许flash访问摄像头。", //error_camera_muted
"103": "服务器关闭了连接。", //error_connection_closed
"104": "服务器连接失败。", //error_connection_failed
"199": "未知错误。"
};
}
/**
* user can set some callback, then start the publisher.
* callbacks:
* on_publisher_ready(cameras, microphones):int, when srs publisher ready, user can publish.
* on_publisher_error(code, desc):int, when srs publisher error, callback this method.
* on_publisher_warn(code, desc):int, when srs publisher warn, callback this method.
*/
SrsPublisher.prototype.start = function() {
// embed the flash.
var flashvars = {};
flashvars.id = this.id;
flashvars.width = this.width;
flashvars.height = this.height;
flashvars.on_publisher_ready = "__srs_on_publisher_ready";
flashvars.on_publisher_error = "__srs_on_publisher_error";
flashvars.on_publisher_warn = "__srs_on_publisher_warn";
var params = {};
params.wmode = "opaque";
params.allowFullScreen = "true";
params.allowScriptAccess = "always";
var attributes = {};
var self = this;
swfobject.embedSWF(
"srs_publisher/release/srs_publisher.swf?_version="+srs_get_version_code(),
this.container,
this.width, this.height,
"11.1", "js/AdobeFlashPlayerInstall.swf",
flashvars, params, attributes,
function(callbackObj){
self.callbackObj = callbackObj;
}
);
return this;
}
/**
* publish stream to server.
* @param url a string indicates the rtmp url to publish.
* @param vcodec an object contains the video codec info.
* @param acodec an object contains the audio codec info.
*/
SrsPublisher.prototype.publish = function(url, vcodec, acodec) {
this.stop();
SrsPublisher.__publishers.push(this);
if (url) {
this.url = url;
}
if (vcodec) {
this.vcodec = vcodec;
}
if (acodec) {
this.acodec = acodec;
}
this.callbackObj.ref.__publish(this.url, this.width, this.height, this.vcodec, this.acodec);
}
SrsPublisher.prototype.stop = function() {
for (var i = 0; i < SrsPublisher.__publishers.length; i++) {
var player = SrsPublisher.__publishers[i];
if (player.id != this.id) {
continue;
}
SrsPublisher.__publishers.splice(i, 1);
break;
}
this.callbackObj.ref.__stop();
}
/**
* when publisher ready.
* @param cameras a string array contains the names of cameras.
* @param microphones a string array contains the names of microphones.
*/
SrsPublisher.prototype.on_publisher_ready = function(cameras, microphones) {
}
/**
* when publisher error.
* @code the error code.
* @desc the error desc message.
*/
SrsPublisher.prototype.on_publisher_error = function(code, desc) {
throw new Error("publisher error. code=" + code + ", desc=" + desc);
}
SrsPublisher.prototype.on_publisher_warn = function(code, desc) {
throw new Error("publisher warn. code=" + code + ", desc=" + desc);
}
function __srs_find_publisher(id) {
for (var i = 0; i < SrsPublisher.__publishers.length; i++) {
var publisher = SrsPublisher.__publishers[i];
if (publisher.id != id) {
continue;
}
return publisher;
}
throw new Error("publisher not found. id=" + id);
}
function __srs_on_publisher_ready(id, cameras, microphones) {
var publisher = __srs_find_publisher(id);
publisher.cameras = cameras;
publisher.microphones = microphones;
publisher.on_publisher_ready(cameras, microphones);
}
function __srs_on_publisher_error(id, code) {
var publisher = __srs_find_publisher(id);
publisher.code = code;
publisher.on_publisher_error(code, publisher.errors[""+code]);
}
function __srs_on_publisher_warn(id, code) {
var publisher = __srs_find_publisher(id);
publisher.code = code;
publisher.on_publisher_warn(code, publisher.errors[""+code]);
}

View file

@ -0,0 +1,139 @@
/**
* padding the output.
* padding(3, 5, '0') is 00003
* padding(3, 5, 'x') is xxxx3
* @see http://blog.csdn.net/win_lin/article/details/12065413
*/
function padding(number, length, prefix) {
if(String(number).length >= length){
return String(number);
}
return padding(prefix+number, length, prefix);
}
/**
* parse the query string to object.
*/
function parse_query_string(){
var obj = {};
// add the uri object.
// parse the host(hostname:http_port), pathname(dir/filename)
obj.host = window.location.host;
obj.hostname = window.location.hostname;
obj.http_port = (window.location.port == "")? 80:window.location.port;
obj.pathname = window.location.pathname;
if (obj.pathname.lastIndexOf("/") <= 0) {
obj.dir = "/";
obj.filename = "";
} else {
obj.dir = obj.pathname.substr(0, obj.pathname.lastIndexOf("/"));
obj.filename = obj.pathname.substr(obj.pathname.lastIndexOf("/"));
}
// pure user query object.
obj.user_query = {};
// parse the query string.
var query_string = String(window.location.search).replace(" ", "").split("?")[1];
if(query_string == undefined){
return obj;
}
var queries = query_string.split("&");
$(queries).each(function(){
var query = this.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
});
return obj;
}
/**
* parse the rtmp url,
* for example: rtmp://demo.srs.com:1935/live...vhost...players/livestream
* @return object {server, port, vhost, app, stream}
*/
function srs_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://");
var vhost = a.hostname;
var port = (a.port == "")? "1935":a.port;
var app = a.pathname.substr(1, a.pathname.lastIndexOf("/") - 1);
var stream = a.pathname.substr(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.substr(app.indexOf("?"));
app = app.substr(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.substr(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.substr(0, vhost.indexOf("&"));
}
}
}
var ret = {
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
return ret;
}
/**
* get the agent.
* @return an object specifies some browser.
* for example, get_browser_agents().MSIE
*/
function get_browser_agents() {
var agent = navigator.userAgent;
/**
WindowsPC platform, Win7:
chrome 31.0.1650.63:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
firefox 23.0.1:
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101
Firefox/23.0
safari 5.1.7(7534.57.2):
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2
(KHTML, like Gecko) Version/5.1.7 Safari/534.57.2
opera 15.0.1147.153:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
OPR/15.0.1147.153
360 6.2.1.272:
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
.NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
.NET4.0E)
IE 10.0.9200.16750(update: 10.0.12):
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
.NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
.NET4.0E)
*/
return {
// platform
Android: agent.indexOf("Android") != -1,
Windows: agent.indexOf("Windows") != -1,
iPhone: agent.indexOf("iPhone") != -1,
// Windows Browsers
Chrome: agent.indexOf("Chrome") != -1,
Firefox: agent.indexOf("Firefox") != -1,
QQBrowser: agent.indexOf("QQBrowser") != -1,
MSIE: agent.indexOf("MSIE") != -1,
// Android Browsers
Opera: agent.indexOf("Presto") != -1,
MQQBrowser: agent.indexOf("MQQBrowser") != -1
};
}

View file

@ -6,7 +6,11 @@
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/srs.page.js"></script>
<script type="text/javascript" src="js/srs.log.js"></script>
<script type="text/javascript" src="js/srs.player.js"></script>
<script type="text/javascript" src="js/srs.publisher.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
@ -78,7 +82,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>

View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>SRS</title>
<meta charset="utf-8">
<script type="text/javascript" src="players/js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="players/js/srs.page.js"></script>
<script type="text/javascript" src="players/js/srs.utility.js"></script>
</head>
<body>
<script type="text/javascript">
var query = parse_query_string();
var url = window.location.protocol + "//" + query.hostname + ":" + srs_get_api_server_port() + "/index.html" + window.location.search;
document.write("请确认api-server已经开启跳转到api-server的页面解决IE跨域问题<br/>");
document.write("正在跳转,若您的浏览器没有跳转,请点击: <a href='" + url + "'>" + url + "</a>");
setTimeout(function(){
window.location.href = url;
}, 3000);
</script>
</body>

View file

@ -7,7 +7,11 @@
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script> <script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/srs.page.js"></script>
<script type="text/javascript" src="js/srs.log.js"></script>
<script type="text/javascript" src="js/srs.player.js"></script>
<script type="text/javascript" src="js/srs.publisher.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
@ -74,7 +78,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>

View file

@ -7,7 +7,13 @@
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script> <script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/srs.page.js"></script>
<script type="text/javascript" src="js/srs.log.js"></script>
<script type="text/javascript" src="js/srs.player.js"></script>
<script type="text/javascript" src="js/srs.publisher.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<script type="text/javascript" src="js/srs.bandwidth.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
@ -119,7 +125,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>

View file

@ -7,11 +7,19 @@
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script> <script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript" src="js/srs.page.js"></script>
<script type="text/javascript" src="js/srs.log.js"></script>
<script type="text/javascript" src="js/srs.player.js"></script>
<script type="text/javascript" src="js/srs.publisher.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
} }
.accordion-group {
width: 310px;
}
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
var srs_publisher = null; var srs_publisher = null;
@ -22,18 +30,36 @@
var previous_chats = []; var previous_chats = [];
var no_play = false; var no_play = false;
var query = parse_query_string();
$(function(){ $(function(){
// get the vhost and port to set the default url. // get the vhost and port to set the default url.
// for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo
// url set to: rtmp://demo:1935/live/livestream // url set to: rtmp://demo:1935/live/livestream
srs_init_publish("#txt_url"); srs_init_publish("#txt_url");
// support 5x3+1 users
for (var i = 0; i < 5; i++) {
var tr = $("<tr></tr>").hide();
$("#lst_chats").append(tr);
for (var j = 0; j < 3; j++) {
tr.append($("<td></td>").attr("id", "td_" + ((i+1) * 8 + j)));
}
}
// remove border of row.
$("#lst_chats").find("td").css("border", "none").css("padding", "2px")
.css("padding-left", "0px").css("width", "327px");
if (query.agent == "true") {
document.write(navigator.userAgent);
return;
}
$("#realtime_player_url").tooltip({ $("#realtime_player_url").tooltip({
title: "右键复制RTMP地址" title: "右键复制RTMP地址"
}); });
// if no play specified, donot show the player, for debug the publisher. // if no play specified, donot show the player, for debug the publisher.
var query = parse_query_string();
if (query.no_play == "true") { if (query.no_play == "true") {
no_play = true; no_play = true;
} }
@ -50,7 +76,7 @@
$("#txt_url").val($("#txt_url").val() + "." + new Date().getTime()); $("#txt_url").val($("#txt_url").val() + "." + new Date().getTime());
// start the publisher. // start the publisher.
srs_publisher = new SrsPublisher("local_publisher", 430, 185); srs_publisher = new SrsPublisher("local_publisher", 280, 180);
srs_publisher.on_publisher_ready = function(cameras, microphones) { srs_publisher.on_publisher_ready = function(cameras, microphones) {
srs_chat_initialize_page( srs_chat_initialize_page(
cameras, microphones, cameras, microphones,
@ -60,7 +86,11 @@
); );
}; };
srs_publisher.on_publisher_error = function(code, desc) { srs_publisher.on_publisher_error = function(code, desc) {
error(code, desc); if (!on_publish_stop()) {
return;
}
error(code, desc + "请重试。");
}; };
srs_publisher.on_publisher_warn = function(code, desc) { srs_publisher.on_publisher_warn = function(code, desc) {
warn(code, desc); warn(code, desc);
@ -71,18 +101,37 @@
if (!no_play) { if (!no_play) {
// start the realtime player. // start the realtime player.
realtime_player = new SrsPlayer("realtime_player", 430, 185); realtime_player = new SrsPlayer("realtime_player", 280, 180);
realtime_player.on_player_ready = function() { realtime_player.on_player_ready = function() {
this.set_bt(0.5); this.set_bt(0.5);
this.set_fs("screen", 100);
}; };
realtime_player.on_player_metadata = function(metadata) {
this.set_dar(0, 0);
this.set_fs("screen", 100);
info("推流到服务器成功。请戴耳机聊天,否则音箱的声音会进入麦克风造成回声。");
}
realtime_player.start(); realtime_player.start();
} }
$("#txt_name").focus();
api_server = "http://" + query.hostname + ":" + srs_get_api_server_port() + "/api/v1/chats"; api_server = "http://" + query.hostname + ":" + srs_get_api_server_port() + "/api/v1/chats";
refresh(); refresh();
}); });
function on_publish_stop() {
if (!srs_can_republish()) {
$("#btn_join").attr("disabled", true);
error(0, "您使用的浏览器很弱,请关闭页面后重新打开页面(刷新也不管用)。<br/>推荐使用Chrome/Firefox/Safari/Opera浏览器支持重试");
srs_log_disabled = true;
return false;
}
return true;
}
function update_play_url() { function update_play_url() {
var url = $("#txt_url").val(); var url = $("#txt_url").val();
@ -180,86 +229,44 @@
} }
}); });
} }
function render_chat_room(chats) { function render_chat_room(original_chats) {
if (!self_chat) { if (!self_chat) {
return; return;
} }
var chats = [];
for (var i = 0; i < original_chats.length; i++) {
var chat = original_chats[i];
// ignore the self.
if (self_chat && self_chat.id == chat.id) {
continue;
}
chats.push(chat);
}
// new added chat // new added chat
for (var i = 0; i < chats.length; i++) { for (var i = 0; i < chats.length; i++) {
var chat = chats[i]; var chat = chats[i];
// ignore the self.
if (self_chat && self_chat.id == chat.id) {
continue;
}
// if previous exists, ignore, only add new here. // if previous exists, ignore, only add new here.
var previous_chat = get_previous_chat_user(previous_chats, chat.id); var previous_chat = get_previous_chat_user(previous_chats, chat.id);
if (previous_chat) { if (previous_chat) {
// update reference. // update reference.
chat.ui = previous_chat.ui;
chat.parent = previous_chat.parent;
chat.player = previous_chat.player; chat.player = previous_chat.player;
chat.player.private_object = chat; if (chat.player) {
chat.player.private_object = chat;
}
continue; continue;
} }
global_chat_user_id++;
// username: a str indicates the user name.
// url: a str indicates the url of user stream.
// join_date: a str indicates the join timestamp in seconds.
// join_date_str: friendly formated time.
var obj = $("<div/>").html($("#template").html());
$(obj).attr("chat_id", chat.id);
$(obj).attr("id", "div_" + chat.id); // for specifed chat: $("#div_" + chat_id)
$(obj).attr("class", "div_chat"); // for all chats: $(".div_chat")
$(obj).find("#chat_player").attr("id", "rp_" + chat.id); // for specifed player: $("#rp_" + chat_id)
$(obj).find("#chat_player_raw").attr("id", "rp_raw_" + chat.id); // for specifed player: $("#rp_raw_" + chat_id)
$(obj).find("#user_name").text(chat.username);
$(obj).find("#join_date").text(chat.join_date_str);
$(obj).find("#collapseM").attr("id", "collapse_" + global_chat_user_id);
$(obj).find("#headerN").attr("href", "#collapse_" + global_chat_user_id);
$("#lst_chats").append(obj);
if (!no_play) {
// start the realtime player.
var _player = new SrsPlayer("rp_raw_" + chat.id, 600, 300, chat);
_player.on_player_ready = function() {
this.set_bt(0.5);
this.set_fs("screen", 100);
};
_player.start(chat.url);
chat.player = _player;
} else {
chat.player = null;
}
$(obj).find("#collapse_main").on('hidden', function(){
var id = $(this).parent().attr("chat_id");
var chat = get_previous_chat_user(previous_chats, id);
if (!chat || !chat.player) {
return;
}
chat.player.stop();
});
$(obj).find("#collapse_main").on('shown', function(){
var id = $(this).parent().attr("chat_id");
var chat = get_previous_chat_user(previous_chats, id);
if (!chat || !chat.player) {
return;
}
chat.player.play();
});
} }
// removed chat // removed chat
for (var i = 0; i < previous_chats.length; i++) { for (var i = 0; i < previous_chats.length; i++) {
var chat = previous_chats[i]; var chat = previous_chats[i];
// ignore the self.
if (self_chat && self_chat.id == chat.id) {
continue;
}
var new_chat = get_previous_chat_user(chats, chat.id); var new_chat = get_previous_chat_user(chats, chat.id);
if (new_chat) { if (new_chat) {
@ -272,6 +279,118 @@
$("#div_" + chat.id).remove(); $("#div_" + chat.id).remove();
} }
// hide empty rows.
$("#lst_chats").find("tr").each(function(){
var empty = true;
$(this).find("td").each(function(){
if ($(this).html() != "") {
empty = false;
return false; // abort each
}
return true;
});
if (empty) {
$(this).hide();
}
});
// render the chat
for (var i = 0; i < chats.length; i++) {
var chat = chats[i];
// if redered, ignore.
if (chat.parent) {
continue;
}
// generate the ui of chat
if (!chat.ui) {
global_chat_user_id++;
// username: a str indicates the user name.
// url: a str indicates the url of user stream.
// join_date: a str indicates the join timestamp in seconds.
// join_date_str: friendly formated time.
var obj = $("<div/>").html($("#template").html());
if (true) {
// save current ui object to the chat.
chat.ui = obj;
$(obj).attr("chat_id", chat.id);
$(obj).attr("id", "div_" + chat.id); // for specifed chat: $("#div_" + chat_id)
$(obj).attr("class", "div_chat"); // for all chats: $(".div_chat")
$(obj).find("#chat_player").attr("id", "rp_" + chat.id); // for specifed player: $("#rp_" + chat_id)
$(obj).find("#chat_player_raw").attr("id", "rp_raw_" + chat.id); // for specifed player: $("#rp_raw_" + chat_id)
$(obj).find("#user_name").text(chat.username);
$(obj).find("#user_player_url").attr("href", chat.url);
$(obj).find("#join_date").text(chat.join_date_str.split(" ")[1]);
$(obj).find("#collapseM").attr("id", "collapse_" + global_chat_user_id);
$(obj).find("#headerN").attr("href", "#collapse_" + global_chat_user_id);
}
}
// find a idle td to render the chat.
var parent = null;
$("#lst_chats").find("td").each(function(){
if ($(this).html() != "") {
return true;
}
parent = $(this);
return false; // abort each
});
if (!parent) {
warn("没有可用的位置展示流。");
break;
}
$(parent).append(chat.ui);
$(parent).parent().show();
// when ui elements appent to the page,
// create the player, or flash will failed.
if (!chat.parent) {
chat.parent = $(parent);
if (!no_play) {
// start the realtime player.
var _player = new SrsPlayer("rp_raw_" + chat.id, 240, 180, chat);
_player.on_player_ready = function() {
this.set_bt(0.5);
this.play();
};
_player.on_player_metadata = function(metadata) {
this.set_dar(0, 0);
this.set_fs("screen", 100);
}
_player.start(chat.url);
chat.player = _player;
} else {
chat.player = null;
}
$(obj).find("#collapse_main").on('hidden', function(){
var id = $(this).parent().attr("chat_id");
var chat = get_previous_chat_user(previous_chats, id);
if (!chat || !chat.player) {
return;
}
chat.player.stop();
});
$(obj).find("#collapse_main").on('shown', function(){
var id = $(this).parent().attr("chat_id");
var chat = get_previous_chat_user(previous_chats, id);
if (!chat || !chat.player) {
return;
}
chat.player.play();
});
}
}
previous_chats = chats; previous_chats = chats;
} }
function get_previous_chat_user(arr, id) { function get_previous_chat_user(arr, id) {
@ -285,7 +404,7 @@
} }
function on_user_publish() { function on_user_publish() {
if ($("#txt_name").val().trim() == "") { if ($("#txt_name").val() == "") {
$("#txt_name").focus().parent().parent().addClass("error"); $("#txt_name").focus().parent().parent().addClass("error");
warn(100, "请输入您的名字"); warn(100, "请输入您的名字");
return; return;
@ -315,10 +434,6 @@
// removed chat // removed chat
for (var i = 0; i < previous_chats.length; i++) { for (var i = 0; i < previous_chats.length; i++) {
var chat = previous_chats[i]; var chat = previous_chats[i];
// ignore the self.
if (self_chat && self_chat.id == chat.id) {
continue;
}
if (chat.player) { if (chat.player) {
chat.player.stop(); chat.player.stop();
@ -342,6 +457,10 @@
data : "", data : "",
dataType : "json", dataType : "json",
complete : function() { complete : function() {
if (!on_publish_stop()) {
return;
}
$("#btn_join").attr("disabled", false); $("#btn_join").attr("disabled", false);
if (complete_pfn) { if (complete_pfn) {
complete_pfn(); complete_pfn();
@ -376,7 +495,7 @@
var chat = {}; var chat = {};
chat.id = -1; chat.id = -1;
chat.username = $("#txt_name").val().trim(); chat.username = $("#txt_name").val();
chat.agent = navigator.userAgent; chat.agent = navigator.userAgent;
chat.url = url; chat.url = url;
chat.vcodec = vcodec; chat.vcodec = vcodec;
@ -414,13 +533,13 @@
$("#btn_join").text("退出会议"); $("#btn_join").text("退出会议");
info("开始推流到服务器"); info("开始推流到服务器。请戴耳机聊天,否则音箱的声音会进入麦克风造成回声。");
srs_publisher.publish(url, vcodec, acodec); srs_publisher.publish(url, vcodec, acodec);
if (realtime_player) { if (realtime_player) {
// directly play the url for the realtime player. // directly play the url for the realtime player.
realtime_player.stop(); realtime_player.stop();
realtime_player.play(url); realtime_player.play(url, 0);
} }
} }
}); });
@ -431,7 +550,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
@ -451,21 +570,21 @@
<div class="alert alert-info fade in" id="txt_log"> <div class="alert alert-info fade in" id="txt_log">
<button type="button" class="close" data-dismiss="alert">×</button> <button type="button" class="close" data-dismiss="alert">×</button>
<strong><span id="txt_log_title">Usage:</span></strong> <strong><span id="txt_log_title">Usage:</span></strong>
<span id="txt_log_msg">输入名字,设置编码参数后,加入会议室</span> <span id="txt_log_msg">输入名字,设点“加入会议”按钮</span>
</div> </div>
<div class="control-group"> <div class="control-group">
<div class="form-inline"> <div class="form-inline">
<input type="text" id="txt_name" class="input-small" placeholder="您的名字..." value=""></input> <button class="btn input-small" id="btn_video_settings">摄像头</button>
<button class="btn input-medium" id="btn_video_settings">视频编码配置</button> <button class="btn input-small" id="btn_audio_settings">麦克风</button>
<button class="btn input-medium" id="btn_audio_settings">音频编码配置</button> <input type="text" id="txt_name" class="input-large" placeholder="请输入您的名字..." value=""></input>
<button class="btn input-medium btn-primary" id="btn_join">加入会议</button> <button class="btn input-small" id="btn_join">加入会议</button>
<input type="text" id="txt_url" class="input-mini hide" value=""></input> <input type="text" id="txt_url" class="input-mini hide" value=""></input>
</div> </div>
</div> </div>
<div class="container"> <table id="lst_chats" class="table">
<div class="row-fluid"> <tr>
<div class="span6"> <td id="td_0">
<div class="accordion-group"> <div class="accordion-group">
<div class="accordion-heading"> <div class="accordion-heading">
<span class="accordion-toggle"> <span class="accordion-toggle">
@ -478,8 +597,8 @@
</div> </div>
</div> </div>
</div> </div>
</div> </td>
<div class="span6"> <td id="td_1">
<div class="accordion-group"> <div class="accordion-group">
<div class="accordion-heading"> <div class="accordion-heading">
<span class="accordion-toggle"> <span class="accordion-toggle">
@ -495,38 +614,33 @@
</div> </div>
</div> </div>
</div> </div>
</div> </td>
</div> <td id="td_2"></td>
</div> </tr>
</table>
<div class="container hide" id="template"> <div class="container hide" id="template">
<div class="accordion-group" id="collapse_main"> <div class="accordion-group" id="collapse_main">
<div class="accordion-heading" title="点击展开或收起,收起后停止播放流,展开时从服务器请求流"> <div class="accordion-heading" title="点击展开或收起,收起后停止播放流,展开时从服务器请求流">
<span id="headerN" class="accordion-toggle" data-toggle="collapse" href="#collapseN"> <span id="headerN" class="accordion-toggle" data-toggle="collapse" href="#collapseN">
<strong>[<a href="#"><span id="user_name">XX</span></a>]</strong> <strong>[<a href="#"><span id="user_name">XX</span></a>]</strong>
<strong>加入时间</strong>[<span id="join_date"></span>] <strong>加入时间</strong>[<span id="join_date"></span>]
<img src="img/tooltip.png"/> <a id="user_player_url" href="#" data-toggle="tooltip" data-placement="top" title="">
播放地址<img src="img/tooltip.png"/>
</a>
</span> </span>
</div> </div>
<div id="collapseM" class="accordion-body collapse"> <div id="collapseM" class="accordion-body collapse in">
<div class="accordion-inner"> <div class="accordion-inner">
<div class="row-fluid"> <div id="chat_player">
<div class="span2"> <div id="chat_player_raw">
</div>
<div class="span8">
<div id="chat_player">
<div id="chat_player_raw">
</div>
</div>
</div>
<div class="span2">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="container" id="lst_chats"> <table class="table">
</div> </table>
<div id="video_modal" class="modal hide fade"> <div id="video_modal" class="modal hide fade">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>

View file

@ -7,7 +7,12 @@
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script> <script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript" src="js/srs.page.js"></script>
<script type="text/javascript" src="js/srs.log.js"></script>
<script type="text/javascript" src="js/srs.player.js"></script>
<script type="text/javascript" src="js/srs.publisher.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
@ -30,7 +35,7 @@
var __active_dar = null; var __active_dar = null;
function select_dar(dar_id, num, den) { function select_dar(dar_id, num, den) {
srs_player.dar(num, den); srs_player.set_dar(num, den);
if (__active_dar) { if (__active_dar) {
__active_dar.removeClass("active"); __active_dar.removeClass("active");
@ -191,13 +196,13 @@
select_dar("#btn_dar_original", 0, 0); select_dar("#btn_dar_original", 0, 0);
}); });
$("#btn_dar_21_9").click(function(){ $("#btn_dar_21_9").click(function(){
select_dar("#btn_dar_21_9", 9, 21); select_dar("#btn_dar_21_9", 21, 9);
}); });
$("#btn_dar_16_9").click(function(){ $("#btn_dar_16_9").click(function(){
select_dar("#btn_dar_16_9", 9, 16); select_dar("#btn_dar_16_9", 16, 9);
}); });
$("#btn_dar_4_3").click(function(){ $("#btn_dar_4_3").click(function(){
select_dar("#btn_dar_4_3", 3, 4); select_dar("#btn_dar_4_3", 4, 3);
}); });
$("#btn_dar_fill").click(function(){ $("#btn_dar_fill").click(function(){
select_dar("#btn_dar_fill", -1, -1); select_dar("#btn_dar_fill", -1, -1);
@ -264,7 +269,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li class="active"><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li class="active"><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>

View file

@ -10,6 +10,7 @@ package
import flash.events.NetStatusEvent; import flash.events.NetStatusEvent;
import flash.events.TimerEvent; import flash.events.TimerEvent;
import flash.external.ExternalInterface; import flash.external.ExternalInterface;
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;
@ -19,6 +20,8 @@ package
import flash.utils.Timer; import flash.utils.Timer;
import flash.utils.setTimeout; import flash.utils.setTimeout;
import flashx.textLayout.formats.Float;
public class srs_player extends Sprite public class srs_player extends Sprite
{ {
// user set id. // user set id.
@ -34,8 +37,8 @@ package
private var user_w:int = 0; private var user_w:int = 0;
private var user_h:int = 0; private var user_h:int = 0;
// user set dar den:num // user set dar den:num
private var user_dar_num:int = 0;
private var user_dar_den:int = 0; private var user_dar_den:int = 0;
private var user_dar_num:int = 0;
// user set fs(fullscreen) refer and percent. // user set fs(fullscreen) refer and percent.
private var user_fs_refer:String = null; private var user_fs_refer:String = null;
private var user_fs_percent:int = 0; private var user_fs_percent:int = 0;
@ -112,7 +115,7 @@ package
flash.external.ExternalInterface.addCallback("__stop", this.js_call_stop); flash.external.ExternalInterface.addCallback("__stop", this.js_call_stop);
flash.external.ExternalInterface.addCallback("__pause", this.js_call_pause); flash.external.ExternalInterface.addCallback("__pause", this.js_call_pause);
flash.external.ExternalInterface.addCallback("__resume", this.js_call_resume); flash.external.ExternalInterface.addCallback("__resume", this.js_call_resume);
flash.external.ExternalInterface.addCallback("__dar", this.js_call_dar); flash.external.ExternalInterface.addCallback("__set_dar", this.js_call_set_dar);
flash.external.ExternalInterface.addCallback("__set_fs", this.js_call_set_fs_size); flash.external.ExternalInterface.addCallback("__set_fs", this.js_call_set_fs_size);
flash.external.ExternalInterface.addCallback("__set_bt", this.js_call_set_bt); flash.external.ExternalInterface.addCallback("__set_bt", this.js_call_set_bt);
@ -218,15 +221,15 @@ package
} }
/** /**
* to set the DAR, for example, DAR=16:9 * to set the DAR, for example, DAR=16:9 where num=16,den=9.
* @param num, for example, 9. * @param num, for example, 16.
* use metadata height if 0. * use metadata width if 0.
* use user specified height if -1. * use user specified width if -1.
* @param den, for example, 16. * @param den, for example, 9.
* use metadata width if 0. * use metadata height if 0.
* use user specified width if -1. * use user specified height if -1.
*/ */
private function js_call_dar(num:int, den:int):void { private function js_call_set_dar(num:int, den:int):void {
user_dar_num = num; user_dar_num = num;
user_dar_den = den; user_dar_den = den;
@ -280,8 +283,9 @@ package
* @param _width, the player width. * @param _width, the player width.
* @param _height, the player height. * @param _height, the player height.
* @param buffer_time, the buffer time in seconds. recommend to >=0.5 * @param buffer_time, the buffer time in seconds. recommend to >=0.5
* @param volume, the volume, 0 is mute, 1 is 100%, 2 is 200%.
*/ */
private function js_call_play(url:String, _width:int, _height:int, buffer_time:Number):void { private function js_call_play(url:String, _width:int, _height:int, buffer_time:Number, volume:Number):void {
this.user_url = url; this.user_url = url;
this.user_w = _width; this.user_w = _width;
this.user_h = _height; this.user_h = _height;
@ -313,6 +317,7 @@ package
} }
media_stream = new NetStream(media_conn); media_stream = new NetStream(media_conn);
media_stream.soundTransform = new SoundTransform(volume);
media_stream.bufferTime = buffer_time; media_stream.bufferTime = buffer_time;
media_stream.client = {}; media_stream.client = {};
media_stream.client.onMetaData = system_on_metadata; media_stream.client.onMetaData = system_on_metadata;
@ -397,21 +402,21 @@ package
var obj:Object = __get_video_size_object(); var obj:Object = __get_video_size_object();
// get the DAR // get the DAR
var num:int = user_dar_num;
var den:int = user_dar_den; var den:int = user_dar_den;
var num:int = user_dar_num;
if (num == 0) {
num = obj.height;
}
if (num == -1) {
num = this.stage.fullScreenHeight;
}
if (den == 0) { if (den == 0) {
den = obj.width; den = obj.height;
} }
if (den == -1) { if (den == -1) {
den = this.stage.fullScreenWidth; den = this.stage.fullScreenHeight;
}
if (num == 0) {
num = obj.width;
}
if (num == -1) {
num = this.stage.fullScreenWidth;
} }
// for refer is screen. // for refer is screen.
@ -431,23 +436,23 @@ package
*/ */
private function __execute_user_set_dar():void { private function __execute_user_set_dar():void {
// get the DAR // get the DAR
var num:int = user_dar_num;
var den:int = user_dar_den; var den:int = user_dar_den;
var num:int = user_dar_num;
var obj:Object = __get_video_size_object(); var obj:Object = __get_video_size_object();
if (num == 0) {
num = obj.height;
}
if (num == -1) {
num = this.user_h;
}
if (den == 0) { if (den == 0) {
den = obj.width; den = obj.height;
} }
if (den == -1) { if (den == -1) {
den = this.user_w; den = this.user_h;
}
if (num == 0) {
num = obj.width;
}
if (num == -1) {
num = this.user_w;
} }
__update_video_size(num, den, this.user_w, this.user_h, this.user_w, this.user_h); __update_video_size(num, den, this.user_w, this.user_h, this.user_w, this.user_h);
@ -463,19 +468,19 @@ package
* @param _sw/_wh the stage size, >= paper size. used to center the player. * @param _sw/_wh the stage size, >= paper size. used to center the player.
*/ */
private function __update_video_size(_num:int, _den:int, _w:int, _h:int, _sw:int, _sh:int):void { private function __update_video_size(_num:int, _den:int, _w:int, _h:int, _sw:int, _sh:int):void {
if (!this.media_video || _num <= 0 || _den <= 0) { if (!this.media_video || _den <= 0 || _num <= 0) {
return; return;
} }
// set DAR. // set DAR.
// calc the height by DAR // calc the height by DAR
var _height:int = _w * _num / _den; var _height:int = _w * _den / _num;
if (_height <= _h) { if (_height <= _h) {
this.media_video.width = _w; this.media_video.width = _w;
this.media_video.height = _height; this.media_video.height = _height;
} else { } else {
// height overflow, calc the width by DAR // height overflow, calc the width by DAR
var _width:int = _h * _den / _num; var _width:int = _h * _num / _den;
this.media_video.width = _width; this.media_video.width = _width;
this.media_video.height = _h; this.media_video.height = _h;

View file

@ -7,7 +7,12 @@
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script> <script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript" src="js/srs.page.js"></script>
<script type="text/javascript" src="js/srs.log.js"></script>
<script type="text/javascript" src="js/srs.player.js"></script>
<script type="text/javascript" src="js/srs.publisher.js"></script>
<script type="text/javascript" src="js/srs.utility.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
@ -18,12 +23,18 @@
var remote_player = null; var remote_player = null;
var realtime_player = null; var realtime_player = null;
var query = parse_query_string();
$(function(){ $(function(){
// get the vhost and port to set the default url. // get the vhost and port to set the default url.
// for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo
// url set to: rtmp://demo:1935/live/livestream // url set to: rtmp://demo:1935/live/livestream
srs_init("#txt_url", null, null); srs_init("#txt_url", null, null);
if (query.agent == "true") {
document.write(navigator.userAgent);
return;
}
$("#btn_video_settings").click(function(){ $("#btn_video_settings").click(function(){
$("#video_modal").modal({show:true}); $("#video_modal").modal({show:true});
}); });
@ -60,7 +71,10 @@
); );
}; };
srs_publisher.on_publisher_error = function(code, desc) { srs_publisher.on_publisher_error = function(code, desc) {
error(code, desc); if (!on_publish_stop()) {
return;
}
error(code, desc + "请重试。");
}; };
srs_publisher.on_publisher_warn = function(code, desc) { srs_publisher.on_publisher_warn = function(code, desc) {
warn(code, desc); warn(code, desc);
@ -70,26 +84,43 @@
update_play_url(); update_play_url();
// if no play specified, donot show the player, for debug the publisher. // if no play specified, donot show the player, for debug the publisher.
var query = parse_query_string();
if (query.no_play != "true") { if (query.no_play != "true") {
// start the normal player with HLS supported. // start the normal player with HLS supported.
remote_player = new SrsPlayer("remote_player", 430, 185); remote_player = new SrsPlayer("remote_player", 430, 185);
remote_player.on_player_ready = function() { remote_player.on_player_ready = function() {
this.set_bt(0.8); this.set_bt(0.8);
this.set_fs("screen", 100);
}; };
remote_player.on_player_metadata = function(metadata) {
this.set_dar(0, 0);
this.set_fs("screen", 100);
}
remote_player.start(); remote_player.start();
// start the realtime player. // start the realtime player.
realtime_player = new SrsPlayer("realtime_player", 430, 185); realtime_player = new SrsPlayer("realtime_player", 430, 185);
realtime_player.on_player_ready = function() { realtime_player.on_player_ready = function() {
this.set_bt(0.8); this.set_bt(0.8);
this.set_fs("screen", 100);
}; };
realtime_player.on_player_metadata = function(metadata) {
this.set_dar(0, 0);
this.set_fs("screen", 100);
}
realtime_player.start(); realtime_player.start();
} }
}); });
function on_publish_stop() {
if (!srs_can_republish()) {
$("#btn_join").attr("disabled", true);
error(0, "您使用的浏览器很弱,请关闭页面后重新打开页面(刷新也不管用)。<br/>推荐使用Chrome/Firefox/Safari/Opera浏览器支持重试");
srs_log_disabled = true;
return false;
}
return true;
}
/** /**
* we generate the transcoded stream url for flash publish donot support HLS * we generate the transcoded stream url for flash publish donot support HLS
* which requires aac, so the publish vhost maybe players for example, we * which requires aac, so the publish vhost maybe players for example, we
@ -101,7 +132,6 @@
function update_play_url() { function update_play_url() {
var url = $("#txt_url").val(); var url = $("#txt_url").val();
var ret = srs_parse_rtmp_url(url); var ret = srs_parse_rtmp_url(url);
var query = parse_query_string();
var remote_url = "rtmp://" + ret.server + ":" + ret.port + "/" + ret.app + "...vhost..." + srs_get_player_publish_vhost(ret.vhost) + "/" + ret.stream; var remote_url = "rtmp://" + ret.server + ":" + ret.port + "/" + ret.app + "...vhost..." + srs_get_player_publish_vhost(ret.vhost) + "/" + ret.stream;
$("#realtime_player_url").attr("href", url).attr("target", "_blank"); $("#realtime_player_url").attr("href", url).attr("target", "_blank");
@ -136,6 +166,10 @@
//$("#remote_player_url").attr("href", "#").attr("target", "_self"); //$("#remote_player_url").attr("href", "#").attr("target", "_self");
//$("#txt_play_hls").text("HLS-m3u8(请发布视频)").attr("href", "#").attr("target", "_self"); //$("#txt_play_hls").text("HLS-m3u8(请发布视频)").attr("href", "#").attr("target", "_self");
//$("#txt_play_jwplayer").text("HLS-JWPlayer(请发布视频)").attr("href", "#").attr("target", "_self"); //$("#txt_play_jwplayer").text("HLS-JWPlayer(请发布视频)").attr("href", "#").attr("target", "_self");
if (!on_publish_stop()) {
return;
}
return; return;
} }
@ -180,7 +214,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>

View file

@ -10,9 +10,14 @@ package
import flash.media.H264Profile; import flash.media.H264Profile;
import flash.media.H264VideoStreamSettings; import flash.media.H264VideoStreamSettings;
import flash.media.Microphone; import flash.media.Microphone;
import flash.media.MicrophoneEnhancedMode;
import flash.media.MicrophoneEnhancedOptions;
import flash.media.SoundCodec;
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.system.Security;
import flash.system.SecurityPanel;
import flash.ui.ContextMenu; import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem; import flash.ui.ContextMenuItem;
import flash.utils.setTimeout; import flash.utils.setTimeout;
@ -45,6 +50,8 @@ package
private const error_camera_get:int = 100; private const error_camera_get:int = 100;
private const error_microphone_get:int = 101; private const error_microphone_get:int = 101;
private const error_camera_muted:int = 102; private const error_camera_muted:int = 102;
private const error_connection_closed:int = 103;
private const error_connection_failed:int = 104;
public function srs_publisher() public function srs_publisher()
{ {
@ -79,6 +86,18 @@ package
this.js_on_publisher_error = flashvars.on_publisher_error; this.js_on_publisher_error = flashvars.on_publisher_error;
this.js_on_publisher_warn = flashvars.on_publisher_warn; this.js_on_publisher_warn = flashvars.on_publisher_warn;
// initialized size.
this.user_w = flashvars.width;
this.user_h = flashvars.height;
// try to get the camera, if muted, alert the security and requires user to open it.
var c:Camera = Camera.getCamera();
if (c.muted) {
Security.showSettings(SecurityPanel.PRIVACY);
}
__show_local_camera(c);
flash.utils.setTimeout(this.system_on_js_ready, 0); flash.utils.setTimeout(this.system_on_js_ready, 0);
} }
@ -135,27 +154,31 @@ package
this.js_call_stop(); this.js_call_stop();
// microphone and camera // microphone and camera
var m:Microphone = Microphone.getMicrophone(acodec.device_code); var microphone:Microphone = null;
if(m == null){ //microphone = Microphone.getEnhancedMicrophone(acodec.device_code);
if (!microphone) {
microphone = Microphone.getMicrophone(acodec.device_code);
}
if(microphone == null){
this.system_error(this.error_microphone_get, "failed to open microphone " + acodec.device_code + "(" + acodec.device_name + ")"); this.system_error(this.error_microphone_get, "failed to open microphone " + acodec.device_code + "(" + acodec.device_name + ")");
return; return;
} }
// ignore muted, for flash will require user to access it. // ignore muted, for flash will require user to access it.
// Remark: the name is the index! // Remark: the name is the index!
var c:Camera = Camera.getCamera(vcodec.device_code); var camera:Camera = Camera.getCamera(vcodec.device_code);
if(c == null){ if(camera == null){
this.system_error(this.error_camera_get, "failed to open camera " + vcodec.device_code + "(" + vcodec.device_name + ")"); this.system_error(this.error_camera_get, "failed to open camera " + vcodec.device_code + "(" + vcodec.device_name + ")");
return; return;
} }
// ignore muted, for flash will require user to access it. // ignore muted, for flash will require user to access it.
// but we still warn user. // but we still warn user.
if(c && c.muted){ if(camera && camera.muted){
this.system_warn(this.error_camera_muted, "Access Denied, camera " + vcodec.device_code + "(" + vcodec.device_name + ") is muted"); this.system_warn(this.error_camera_muted, "Access Denied, camera " + vcodec.device_code + "(" + vcodec.device_name + ") is muted");
} }
this.media_camera = c; this.media_camera = camera;
this.media_microphone = m; this.media_microphone = microphone;
this.media_conn = new NetConnection(); this.media_conn = new NetConnection();
this.media_conn.client = {}; this.media_conn.client = {};
@ -175,6 +198,17 @@ package
contextMenu.customItems = customItems; contextMenu.customItems = customItems;
} }
if (evt.info.code == "NetConnection.Connect.Closed") {
js_call_stop();
system_error(error_connection_closed, "server closed the connection");
return;
}
if (evt.info.code == "NetConnection.Connect.Failed") {
js_call_stop();
system_error(error_connection_failed, "connect to server failed");
return;
}
// TODO: FIXME: failed event. // TODO: FIXME: failed event.
if (evt.info.code != "NetConnection.Connect.Success") { if (evt.info.code != "NetConnection.Connect.Success") {
return; return;
@ -188,30 +222,20 @@ package
// TODO: FIXME: failed event. // TODO: FIXME: failed event.
}); });
__build_video_codec(media_stream, c, vcodec); __build_video_codec(media_stream, camera, vcodec);
__build_audio_codec(media_stream, m, acodec); __build_audio_codec(media_stream, microphone, acodec);
if (media_microphone) { if (media_microphone) {
media_stream.attachAudio(m); media_stream.attachAudio(microphone);
} }
if (media_camera) { if (media_camera) {
media_stream.attachCamera(c); media_stream.attachCamera(camera);
} }
var streamName:String = url.substr(url.lastIndexOf("/")); var streamName:String = url.substr(url.lastIndexOf("/"));
media_stream.publish(streamName); media_stream.publish(streamName);
media_video = new Video(); __show_local_camera(media_camera);
media_video.width = _width;
media_video.height = _height;
media_video.attachCamera(media_camera);
media_video.smoothing = true;
addChild(media_video);
//__draw_black_background(_width, _height);
// lowest layer, for mask to cover it.
setChildIndex(media_video, 0);
}); });
var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/")); var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/"));
@ -222,11 +246,9 @@ package
* function for js to call: to stop the stream. ignore if not publish. * function for js to call: to stop the stream. ignore if not publish.
*/ */
private function js_call_stop():void { private function js_call_stop():void {
if (this.media_video) {
this.removeChild(this.media_video);
this.media_video = null;
}
if (this.media_stream) { if (this.media_stream) {
this.media_stream.attachAudio(null);
this.media_stream.attachCamera(null);
this.media_stream.close(); this.media_stream.close();
this.media_stream = null; this.media_stream = null;
} }
@ -264,6 +286,10 @@ package
// if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that // if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that
// your sound capture device supports, usually 11 kHz. // your sound capture device supports, usually 11 kHz.
m.rate = microRate; m.rate = microRate;
// see: http://www.adobe.com/cn/devnet/flashplayer/articles/acoustic-echo-cancellation.html
m.codec = SoundCodec.SPEEX;
m.framesPerPacket = 1;
} }
private function __build_video_codec(stream:NetStream, c:Camera, vcodec:Object):void { private function __build_video_codec(stream:NetStream, c:Camera, vcodec:Object):void {
if (!c) { if (!c) {
@ -332,9 +358,56 @@ package
// (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth, // (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth,
// pass 0 for quality. // pass 0 for quality.
// winlin: // winlin:
// bandwidth is in bps not kbps. 500*1000 = 500kbps. // bandwidth is in Bps not kbps.
// quality=1 is lowest quality, 100 is highest quality. // quality=1 is lowest quality, 100 is highest quality.
c.setQuality(cameraBitrate * 1000, cameraQuality); c.setQuality(cameraBitrate / 8.0 * 1000, cameraQuality);
}
private function __show_local_camera(c:Camera):void {
if (this.media_video) {
this.media_video.attachCamera(null);
this.removeChild(this.media_video);
this.media_video = null;
}
// show local camera
media_video = new Video();
media_video.attachCamera(c);
media_video.smoothing = true;
addChild(media_video);
// rescale the local camera.
var cw:Number = user_h * c.width / c.height;
if (cw > user_w) {
var ch:Number = user_w * c.height / c.width;
media_video.width = user_w;
media_video.height = ch;
} else {
media_video.width = cw;
media_video.height = user_h;
}
media_video.x = (user_w - media_video.width) / 2;
media_video.y = (user_h - media_video.height) / 2;
__draw_black_background(user_w, user_h);
// lowest layer, for mask to cover it.
setChildIndex(media_video, 0);
}
/**
* draw black background and draw the fullscreen mask.
*/
private function __draw_black_background(_width:int, _height:int):void {
// draw black bg.
this.graphics.beginFill(0x00, 1.0);
this.graphics.drawRect(0, 0, _width, _height);
this.graphics.endFill();
// draw the fs mask.
//this.control_fs_mask.graphics.beginFill(0xff0000, 0);
//this.control_fs_mask.graphics.drawRect(0, 0, _width, _height);
//this.control_fs_mask.graphics.endFill();
} }
} }
} }

View file

@ -7,7 +7,7 @@
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script> <script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/swfobject.js"></script> <script type="text/javascript" src="js/swfobject.js"></script>
<script type="text/javascript" src="js/srs.js"></script> <script type="text/javascript" src="js/srs.page.js"></script>
<style> <style>
body{ body{
padding-top: 55px; padding-top: 55px;
@ -24,7 +24,7 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
<a class="brand" href="index.html">SRS</a> <a id="srs_index" class="brand" href="#">SRS</a>
<div class="nav-collapse collapse"> <div class="nav-collapse collapse">
<ul class="nav"> <ul class="nav">
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>

View file

@ -4,7 +4,9 @@ if [[ ! -d $src_dir ]]; then echo "错误必须在src同目录执行脚本";
cmd="sudo ./objs/nginx/sbin/nginx" cmd="sudo ./objs/nginx/sbin/nginx"
echo "启动NGINXHLS服务$cmd" echo "启动NGINXHLS服务$cmd"
pids=`ps aux|grep nginx|grep process|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGKILL $pid; done if [[ -f ./objs/nginx/logs/nginx.pid ]]; then
pids=`cat ./objs/nginx/logs/nginx.pid`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGTERM $pid; done
fi
sudo ./objs/nginx/sbin/nginx sudo ./objs/nginx/sbin/nginx
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误启动NGINXHLS服务失败"; exit $ret; fi ret=$?; if [[ 0 -ne $ret ]]; then echo "错误启动NGINXHLS服务失败"; exit $ret; fi

View file

@ -31,12 +31,6 @@ bash scripts/_step.start.api.server.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $
# step 8: add server ip to client hosts as demo. # step 8: add server ip to client hosts as demo.
ip=`ifconfig|grep "inet"|grep "addr"|grep "Mask"|grep -v "127.0.0.1"|awk 'NR==1 {print $2}'|awk -F ':' '{print $2}'` ip=`ifconfig|grep "inet"|grep "addr"|grep "Mask"|grep -v "127.0.0.1"|awk 'NR==1 {print $2}'|awk -F ':' '{print $2}'`
echo -e "${GREEN}SRS系统开发环境启动成功${BLACK}" echo -e "${GREEN}SRS系统开发环境启动成功。演示${BLACK}"
echo -e "${BLACK}播放器演示:${BLACK}" echo -e "${RED} http://$ip${BLACK}"
echo -e "${RED} http://$ip/players/srs_player.html?vhost=players${BLACK}" echo -e "${RED} http://$ip:8085/players/index.html${BLACK}"
echo -e "${BLACK}编码器演示:${BLACK}"
echo -e "${RED} http://$ip/players/srs_publisher.html?vhost=players${BLACK}"
echo -e "${BLACK}视频会议演示:${BLACK}"
echo -e "${RED} http://$ip/players/srs_chat.html?vhost=players${BLACK}"
echo -e "${BLACK}服务器测速演示:${BLACK}"
echo -e "${RED} http://$ip/players/srs_bwt.html?vhost=players${BLACK}"

View file

@ -45,3 +45,4 @@ cat<<END
END END
echo -e "${GREEN}演示地址:${BLACK}" echo -e "${GREEN}演示地址:${BLACK}"
echo -e "${RED} http://$ip${BLACK}" echo -e "${RED} http://$ip${BLACK}"
echo -e "${RED} http://$ip:8085${BLACK}"

View file

@ -11,7 +11,9 @@ pids=`ps aux|grep srs|grep "./objs"|grep "srs.19350.conf"|awk '{print $2}'`; for
# step 4(optinal): start nginx for HLS # step 4(optinal): start nginx for HLS
echo "停止NGINXHLS服务" echo "停止NGINXHLS服务"
ps aux|grep nginx|grep process ps aux|grep nginx|grep process
pids=`ps aux|grep nginx|grep process|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGKILL $pid; done if [[ -f ./objs/nginx/logs/nginx.pid ]]; then
pids=`cat ./objs/nginx/logs/nginx.pid`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGTERM $pid; done
fi
# step 5(optinal): start http hooks for srs callback # step 5(optinal): start http hooks for srs callback
echo "停止API服务器" echo "停止API服务器"

View file

@ -24,6 +24,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <srs_core_bandwidth.hpp> #include <srs_core_bandwidth.hpp>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sstream>
using namespace std; using namespace std;
@ -260,21 +261,24 @@ int SrsBandwidth::check_play(
int64_t current_time = srs_get_system_time_ms(); int64_t current_time = srs_get_system_time_ms();
int size = 1024; // TODO: FIXME: magic number int size = 1024; // TODO: FIXME: magic number
char random_data[size]; char random_data[size];
memset(random_data, 0x01, size); memset(random_data, 'A', size);
int interval = 0; int interval = 0;
int data_count = 1;
while ( (srs_get_system_time_ms() - current_time) < duration_ms ) { while ( (srs_get_system_time_ms() - current_time) < duration_ms ) {
st_usleep(interval); st_usleep(interval);
// TODO: FIXME: use shared ptr message. // TODO: FIXME: use shared ptr message.
SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_playing(); SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_playing();
// TODO: FIXME: magic number // TODO: FIXME: magic number
for (int i = 0; i < 100; ++i) { for (int i = 0; i < data_count; ++i) {
char buf[32]; // TODO: FIXME: magic number std::stringstream seq;
sprintf(buf, "%d", i); seq << i;
pkt->data->set(buf, new SrsAmf0String(random_data)); std::string play_data = "SrS band check data from server's playing......";
pkt->data->set(seq.str(), new SrsAmf0String(play_data.c_str()));
} }
data_count += 2;
// TODO: FIXME: get length from the rtmp protocol stack. // TODO: FIXME: get length from the rtmp protocol stack.
play_bytes += pkt->get_payload_length(); play_bytes += pkt->get_payload_length();

View file

@ -2789,7 +2789,7 @@ SrsBandwidthPacket* SrsBandwidthPacket::create_start_play()
SrsBandwidthPacket* SrsBandwidthPacket::create_playing() SrsBandwidthPacket* SrsBandwidthPacket::create_playing()
{ {
SrsBandwidthPacket* pkt = new SrsBandwidthPacket(); SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
return pkt->set_command(SRS_BW_CHECK_STARTING_PLAY); return pkt->set_command(SRS_BW_CHECK_PLAYING);
} }
SrsBandwidthPacket* SrsBandwidthPacket::create_stop_play() SrsBandwidthPacket* SrsBandwidthPacket::create_stop_play()

View file

@ -414,7 +414,17 @@ int SrsRtmpClient::publish(string stream, int stream_id)
} }
} }
return ret; return ret;
}
SrsProtocol *SrsRtmpClient::get_protocol()
{
return protocol;
}
st_netfd_t SrsRtmpClient::get_st_fd()
{
return stfd;
} }
SrsRtmp::SrsRtmp(st_netfd_t client_stfd) SrsRtmp::SrsRtmp(st_netfd_t client_stfd)

View file

@ -131,6 +131,10 @@ public:
virtual int create_stream(int& stream_id); virtual int create_stream(int& stream_id);
virtual int play(std::string stream, int stream_id); virtual int play(std::string stream, int stream_id);
virtual int publish(std::string stream, int stream_id); virtual int publish(std::string stream, int stream_id);
protected:
SrsProtocol* get_protocol();
st_netfd_t get_st_fd();
}; };
/** /**