wenjiegit

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

@@ -6,8 +6,8 @@ srs is a simple, high-performance, running in single process, origin live server @@ -6,8 +6,8 @@ srs is a simple, high-performance, running in single process, origin live server
6 srs supports vhost, rtmp, HLS, transcoding, forward, http hooks. <br/> 6 srs supports vhost, rtmp, HLS, transcoding, forward, http hooks. <br/>
7 blog: [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/> 7 blog: [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/>
8 see also: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server) <br/> 8 see also: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server) <br/>
9 -see also: [http://winlinvip.github.io/simple-rtmp-server](http://winlinvip.github.io/simple-rtmp-server)  
10 -TencentQQ: http://url.cn/WAHICw (Group: 212189142) 9 +see also: [http://winlinvip.github.io/simple-rtmp-server](http://winlinvip.github.io/simple-rtmp-server) <br/>
  10 +TencentQQ: [http://url.cn/WAHICw](http://url.cn/WAHICw) (Group: 212189142)
11 11
12 ### Contributors 12 ### Contributors
13 winlin([winterserver](#)): [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/> 13 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 @@ -253,7 +253,8 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw
253 </pre> 253 </pre>
254 254
255 ### Releases 255 ### Releases
256 -* 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/> 256 +* 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/>
  257 +* 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/>
257 * 2013-12-03, [release v0.7](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.7), support live stream transcoding. 17605 lines.<br/> 258 * 2013-12-03, [release v0.7](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.7), support live stream transcoding. 17605 lines.<br/>
258 * 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/> 259 * 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/>
259 * 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/> 260 * 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 @@ -264,6 +265,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw
264 * 2013-10-17, created.<br/> 265 * 2013-10-17, created.<br/>
265 266
266 ### Compare 267 ### Compare
  268 +* srs v0.9: 20926 lines. player/encoder/chat demos. bandwidth test for encoder/CDN.<br/>
267 * srs v0.8: 19186 lines. implements http hooks refer to [nginx-rtmp](https://github.com/arut/nginx-rtmp-module). <br/> 269 * srs v0.8: 19186 lines. implements http hooks refer to [nginx-rtmp](https://github.com/arut/nginx-rtmp-module). <br/>
268 * srs v0.7: 17605 lines. implements transcoding(FFMPEG) feature refer to [wowza](http://www.wowza.com). <br/> 270 * srs v0.7: 17605 lines. implements transcoding(FFMPEG) feature refer to [wowza](http://www.wowza.com). <br/>
269 * srs v0.6: 16094 lines. important feature forward for CDN. <br/> 271 * 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 @@ -276,6 +278,8 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw
276 * nginx v1.5.0: 139524 lines <br/> 278 * nginx v1.5.0: 139524 lines <br/>
277 279
278 ### History 280 ### History
  281 +* v0.9, 2013-12-25, [v0.9](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.9) released. 20926 lines.
  282 +* v0.9, 2013-12-25, fix the bitrate bug(in Bps), use enhanced microphone.
279 * v0.9, 2013-12-22, demo video meeting or chat(srs+cherrypy+jquery+bootstrap). 283 * v0.9, 2013-12-22, demo video meeting or chat(srs+cherrypy+jquery+bootstrap).
280 * v0.9, 2013-12-22, merge from wenjie, support banwidth test. 284 * v0.9, 2013-12-22, merge from wenjie, support banwidth test.
281 * v0.9, 2013-12-22, merge from wenjie: support set chunk size at vhost level 285 * v0.9, 2013-12-22, merge from wenjie: support set chunk size at vhost level
@@ -261,11 +261,8 @@ if [ $SRS_HLS = YES ]; then @@ -261,11 +261,8 @@ if [ $SRS_HLS = YES ]; then
261 ln -sf `pwd`/research/players/crossdomain.xml ${SRS_OBJS}/nginx/html/crossdomain.xml 261 ln -sf `pwd`/research/players/crossdomain.xml ${SRS_OBJS}/nginx/html/crossdomain.xml
262 262
263 # override the default index. 263 # override the default index.
264 - cat <<END > ${SRS_OBJS}/nginx/html/index.html  
265 - <script type="text/javascript">  
266 - window.location.href = "players/index.html";  
267 - </script>  
268 -END 264 + rm -f ${SRS_OBJS}/nginx/html/index.html &&
  265 + ln -sf `pwd`/research/players/nginx_index.html ${SRS_OBJS}/nginx/html/index.html
269 fi 266 fi
270 267
271 if [ $SRS_HLS = YES ]; then 268 if [ $SRS_HLS = YES ]; then
@@ -300,6 +297,12 @@ else @@ -300,6 +297,12 @@ else
300 echo "#undef SRS_HTTP" >> $SRS_AUTO_HEADERS_H 297 echo "#undef SRS_HTTP" >> $SRS_AUTO_HEADERS_H
301 fi 298 fi
302 299
  300 +echo "link players to cherrypy static-dir"
  301 +rm -f research/api-server/static-dir/players &&
  302 +ln -sf `pwd`/research/players research/api-server/static-dir/players &&
  303 +rm -f research/api-server/static-dir/crossdomain.xml &&
  304 +ln -sf `pwd`/research/players/crossdomain.xml research/api-server/static-dir/crossdomain.xml
  305 +
303 ##################################################################################### 306 #####################################################################################
304 # openssl, for rtmp complex handshake 307 # openssl, for rtmp complex handshake
305 ##################################################################################### 308 #####################################################################################
@@ -343,7 +343,7 @@ class RESTChats(object): @@ -343,7 +343,7 @@ class RESTChats(object):
343 self.__chat_lock = threading.Lock(); 343 self.__chat_lock = threading.Lock();
344 344
345 # dead time in seconds, if exceed, remove the chat. 345 # dead time in seconds, if exceed, remove the chat.
346 - self.__dead_time = 30; 346 + self.__dead_time = 15;
347 347
348 def GET(self): 348 def GET(self):
349 enable_crossdomain() 349 enable_crossdomain()
@@ -474,7 +474,8 @@ if len(sys.argv) <= 1: @@ -474,7 +474,8 @@ if len(sys.argv) <= 1:
474 474
475 # parse port from user options. 475 # parse port from user options.
476 port = int(sys.argv[1]) 476 port = int(sys.argv[1])
477 -trace("api server listen at port: %s"%(port)) 477 +static_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "static-dir"))
  478 +trace("api server listen at port: %s, static_dir: %s"%(port, static_dir))
478 479
479 # cherrypy config. 480 # cherrypy config.
480 conf = { 481 conf = {
@@ -483,9 +484,12 @@ conf = { @@ -483,9 +484,12 @@ conf = {
483 'server.socket_host': '0.0.0.0', 484 'server.socket_host': '0.0.0.0',
484 'server.socket_port': port, 485 'server.socket_port': port,
485 'tools.encode.on': True, 486 'tools.encode.on': True,
  487 + 'tools.staticdir.on': True,
486 'tools.encode.encoding': "utf-8" 488 'tools.encode.encoding': "utf-8"
487 }, 489 },
488 '/': { 490 '/': {
  491 + 'tools.staticdir.dir': static_dir,
  492 + 'tools.staticdir.index': "index.html",
489 # for cherrypy RESTful api support 493 # for cherrypy RESTful api support
490 'request.dispatch': cherrypy.dispatch.MethodDispatcher() 494 'request.dispatch': cherrypy.dispatch.MethodDispatcher()
491 } 495 }
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <title>SRS</title>
  5 + <meta charset="utf-8">
  6 + <link rel="stylesheet" type="text/css" href="players/css/bootstrap.min.css"/>
  7 + <script type="text/javascript" src="players/js/jquery-1.10.2.min.js"></script>
  8 + <script type="text/javascript" src="players/js/bootstrap.min.js"></script>
  9 + <script type="text/javascript" src="players/js/swfobject.js"></script>
  10 + <script type="text/javascript" src="players/js/srs.page.js"></script>
  11 + <style>
  12 + body{
  13 + padding-top: 55px;
  14 + }
  15 + </style>
  16 + <script type="text/javascript">
  17 + $(function(){
  18 + update_nav();
  19 +
  20 + // direct to the default vhost for players.
  21 + window.location.href = "players/index.html" + window.location.search;
  22 + });
  23 + </script>
  24 +</head>
  25 +<body>
  26 +<div class="navbar navbar-fixed-top">
  27 + <div class="navbar-inner">
  28 + <div class="container">
  29 + <a class="brand" href="index.html">SRS</a>
  30 + <div class="nav-collapse collapse">
  31 + <ul class="nav">
  32 + <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
  33 + <li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
  34 + <li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
  35 + <li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
  36 + <li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
  37 + <li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
  38 + <li><a id="nav_vlc" href="vlc.html">VLC播放器</a></li>
  39 + </ul>
  40 + </div>
  41 + </div>
  42 + </div>
  43 +</div>
  44 +<div class="container">
  45 + <hr>
  46 + <footer>
  47 + <p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team &copy; 2013</a></p>
  48 + </footer>
  49 +</div>
  50 +</body>
@@ -7,7 +7,8 @@ @@ -7,7 +7,8 @@
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 <script type="text/javascript" src="js/swfobject.js"></script> 9 <script type="text/javascript" src="js/swfobject.js"></script>
10 - <script type="text/javascript" src="js/srs.js"></script> 10 + <script type="text/javascript" src="js/srs.page.js"></script>
  11 + <script type="text/javascript" src="js/srs.utility.js"></script>
11 <style> 12 <style>
12 body{ 13 body{
13 padding-top: 55px; 14 padding-top: 55px;
@@ -17,8 +18,19 @@ @@ -17,8 +18,19 @@
17 $(function(){ 18 $(function(){
18 update_nav(); 19 update_nav();
19 20
20 - // direct to the default vhost for players.  
21 - window.location.href = "srs_player.html?vhost=" + srs_get_player_vhost(); 21 + var query = parse_query_string();
  22 + var url = "srs_chat.html?vhost=" + srs_get_player_vhost();
  23 +
  24 + for (var key in query.user_query) {
  25 + if (key == "vhost") {
  26 + continue;
  27 + }
  28 + url += "&" + key + "=" + query[key];
  29 + }
  30 +
  31 + setTimeout(function(){
  32 + window.location.href = url;
  33 + }, 100);
22 }); 34 });
23 </script> 35 </script>
24 </head> 36 </head>
@@ -26,7 +38,7 @@ @@ -26,7 +38,7 @@
26 <div class="navbar navbar-fixed-top"> 38 <div class="navbar navbar-fixed-top">
27 <div class="navbar-inner"> 39 <div class="navbar-inner">
28 <div class="container"> 40 <div class="container">
29 - <a class="brand" href="index.html">SRS</a> 41 + <a id="srs_index" class="brand" href="#">SRS</a>
30 <div class="nav-collapse collapse"> 42 <div class="nav-collapse collapse">
31 <ul class="nav"> 43 <ul class="nav">
32 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 44 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
  1 +/*
  2 + json2.js
  3 + 2013-05-26
  4 +
  5 + Public Domain.
  6 +
  7 + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  8 +
  9 + See http://www.JSON.org/js.html
  10 +
  11 +
  12 + This code should be minified before deployment.
  13 + See http://javascript.crockford.com/jsmin.html
  14 +
  15 + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
  16 + NOT CONTROL.
  17 +
  18 +
  19 + This file creates a global JSON object containing two methods: stringify
  20 + and parse.
  21 +
  22 + JSON.stringify(value, replacer, space)
  23 + value any JavaScript value, usually an object or array.
  24 +
  25 + replacer an optional parameter that determines how object
  26 + values are stringified for objects. It can be a
  27 + function or an array of strings.
  28 +
  29 + space an optional parameter that specifies the indentation
  30 + of nested structures. If it is omitted, the text will
  31 + be packed without extra whitespace. If it is a number,
  32 + it will specify the number of spaces to indent at each
  33 + level. If it is a string (such as '\t' or '&nbsp;'),
  34 + it contains the characters used to indent at each level.
  35 +
  36 + This method produces a JSON text from a JavaScript value.
  37 +
  38 + When an object value is found, if the object contains a toJSON
  39 + method, its toJSON method will be called and the result will be
  40 + stringified. A toJSON method does not serialize: it returns the
  41 + value represented by the name/value pair that should be serialized,
  42 + or undefined if nothing should be serialized. The toJSON method
  43 + will be passed the key associated with the value, and this will be
  44 + bound to the value
  45 +
  46 + For example, this would serialize Dates as ISO strings.
  47 +
  48 + Date.prototype.toJSON = function (key) {
  49 + function f(n) {
  50 + // Format integers to have at least two digits.
  51 + return n < 10 ? '0' + n : n;
  52 + }
  53 +
  54 + return this.getUTCFullYear() + '-' +
  55 + f(this.getUTCMonth() + 1) + '-' +
  56 + f(this.getUTCDate()) + 'T' +
  57 + f(this.getUTCHours()) + ':' +
  58 + f(this.getUTCMinutes()) + ':' +
  59 + f(this.getUTCSeconds()) + 'Z';
  60 + };
  61 +
  62 + You can provide an optional replacer method. It will be passed the
  63 + key and value of each member, with this bound to the containing
  64 + object. The value that is returned from your method will be
  65 + serialized. If your method returns undefined, then the member will
  66 + be excluded from the serialization.
  67 +
  68 + If the replacer parameter is an array of strings, then it will be
  69 + used to select the members to be serialized. It filters the results
  70 + such that only members with keys listed in the replacer array are
  71 + stringified.
  72 +
  73 + Values that do not have JSON representations, such as undefined or
  74 + functions, will not be serialized. Such values in objects will be
  75 + dropped; in arrays they will be replaced with null. You can use
  76 + a replacer function to replace those with JSON values.
  77 + JSON.stringify(undefined) returns undefined.
  78 +
  79 + The optional space parameter produces a stringification of the
  80 + value that is filled with line breaks and indentation to make it
  81 + easier to read.
  82 +
  83 + If the space parameter is a non-empty string, then that string will
  84 + be used for indentation. If the space parameter is a number, then
  85 + the indentation will be that many spaces.
  86 +
  87 + Example:
  88 +
  89 + text = JSON.stringify(['e', {pluribus: 'unum'}]);
  90 + // text is '["e",{"pluribus":"unum"}]'
  91 +
  92 +
  93 + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
  94 + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
  95 +
  96 + text = JSON.stringify([new Date()], function (key, value) {
  97 + return this[key] instanceof Date ?
  98 + 'Date(' + this[key] + ')' : value;
  99 + });
  100 + // text is '["Date(---current time---)"]'
  101 +
  102 +
  103 + JSON.parse(text, reviver)
  104 + This method parses a JSON text to produce an object or array.
  105 + It can throw a SyntaxError exception.
  106 +
  107 + The optional reviver parameter is a function that can filter and
  108 + transform the results. It receives each of the keys and values,
  109 + and its return value is used instead of the original value.
  110 + If it returns what it received, then the structure is not modified.
  111 + If it returns undefined then the member is deleted.
  112 +
  113 + Example:
  114 +
  115 + // Parse the text. Values that look like ISO date strings will
  116 + // be converted to Date objects.
  117 +
  118 + myData = JSON.parse(text, function (key, value) {
  119 + var a;
  120 + if (typeof value === 'string') {
  121 + a =
  122 +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  123 + if (a) {
  124 + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
  125 + +a[5], +a[6]));
  126 + }
  127 + }
  128 + return value;
  129 + });
  130 +
  131 + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
  132 + var d;
  133 + if (typeof value === 'string' &&
  134 + value.slice(0, 5) === 'Date(' &&
  135 + value.slice(-1) === ')') {
  136 + d = new Date(value.slice(5, -1));
  137 + if (d) {
  138 + return d;
  139 + }
  140 + }
  141 + return value;
  142 + });
  143 +
  144 +
  145 + This is a reference implementation. You are free to copy, modify, or
  146 + redistribute.
  147 +*/
  148 +
  149 +/*jslint evil: true, regexp: true */
  150 +
  151 +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
  152 + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
  153 + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
  154 + lastIndex, length, parse, prototype, push, replace, slice, stringify,
  155 + test, toJSON, toString, valueOf
  156 +*/
  157 +
  158 +
  159 +// Create a JSON object only if one does not already exist. We create the
  160 +// methods in a closure to avoid creating global variables.
  161 +
  162 +if (typeof JSON !== 'object') {
  163 + JSON = {};
  164 +}
  165 +
  166 +(function () {
  167 + 'use strict';
  168 +
  169 + function f(n) {
  170 + // Format integers to have at least two digits.
  171 + return n < 10 ? '0' + n : n;
  172 + }
  173 +
  174 + if (typeof Date.prototype.toJSON !== 'function') {
  175 +
  176 + Date.prototype.toJSON = function () {
  177 +
  178 + return isFinite(this.valueOf())
  179 + ? this.getUTCFullYear() + '-' +
  180 + f(this.getUTCMonth() + 1) + '-' +
  181 + f(this.getUTCDate()) + 'T' +
  182 + f(this.getUTCHours()) + ':' +
  183 + f(this.getUTCMinutes()) + ':' +
  184 + f(this.getUTCSeconds()) + 'Z'
  185 + : null;
  186 + };
  187 +
  188 + String.prototype.toJSON =
  189 + Number.prototype.toJSON =
  190 + Boolean.prototype.toJSON = function () {
  191 + return this.valueOf();
  192 + };
  193 + }
  194 +
  195 + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  196 + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  197 + gap,
  198 + indent,
  199 + meta = { // table of character substitutions
  200 + '\b': '\\b',
  201 + '\t': '\\t',
  202 + '\n': '\\n',
  203 + '\f': '\\f',
  204 + '\r': '\\r',
  205 + '"' : '\\"',
  206 + '\\': '\\\\'
  207 + },
  208 + rep;
  209 +
  210 +
  211 + function quote(string) {
  212 +
  213 +// If the string contains no control characters, no quote characters, and no
  214 +// backslash characters, then we can safely slap some quotes around it.
  215 +// Otherwise we must also replace the offending characters with safe escape
  216 +// sequences.
  217 +
  218 + escapable.lastIndex = 0;
  219 + return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
  220 + var c = meta[a];
  221 + return typeof c === 'string'
  222 + ? c
  223 + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  224 + }) + '"' : '"' + string + '"';
  225 + }
  226 +
  227 +
  228 + function str(key, holder) {
  229 +
  230 +// Produce a string from holder[key].
  231 +
  232 + var i, // The loop counter.
  233 + k, // The member key.
  234 + v, // The member value.
  235 + length,
  236 + mind = gap,
  237 + partial,
  238 + value = holder[key];
  239 +
  240 +// If the value has a toJSON method, call it to obtain a replacement value.
  241 +
  242 + if (value && typeof value === 'object' &&
  243 + typeof value.toJSON === 'function') {
  244 + value = value.toJSON(key);
  245 + }
  246 +
  247 +// If we were called with a replacer function, then call the replacer to
  248 +// obtain a replacement value.
  249 +
  250 + if (typeof rep === 'function') {
  251 + value = rep.call(holder, key, value);
  252 + }
  253 +
  254 +// What happens next depends on the value's type.
  255 +
  256 + switch (typeof value) {
  257 + case 'string':
  258 + return quote(value);
  259 +
  260 + case 'number':
  261 +
  262 +// JSON numbers must be finite. Encode non-finite numbers as null.
  263 +
  264 + return isFinite(value) ? String(value) : 'null';
  265 +
  266 + case 'boolean':
  267 + case 'null':
  268 +
  269 +// If the value is a boolean or null, convert it to a string. Note:
  270 +// typeof null does not produce 'null'. The case is included here in
  271 +// the remote chance that this gets fixed someday.
  272 +
  273 + return String(value);
  274 +
  275 +// If the type is 'object', we might be dealing with an object or an array or
  276 +// null.
  277 +
  278 + case 'object':
  279 +
  280 +// Due to a specification blunder in ECMAScript, typeof null is 'object',
  281 +// so watch out for that case.
  282 +
  283 + if (!value) {
  284 + return 'null';
  285 + }
  286 +
  287 +// Make an array to hold the partial results of stringifying this object value.
  288 +
  289 + gap += indent;
  290 + partial = [];
  291 +
  292 +// Is the value an array?
  293 +
  294 + if (Object.prototype.toString.apply(value) === '[object Array]') {
  295 +
  296 +// The value is an array. Stringify every element. Use null as a placeholder
  297 +// for non-JSON values.
  298 +
  299 + length = value.length;
  300 + for (i = 0; i < length; i += 1) {
  301 + partial[i] = str(i, value) || 'null';
  302 + }
  303 +
  304 +// Join all of the elements together, separated with commas, and wrap them in
  305 +// brackets.
  306 +
  307 + v = partial.length === 0
  308 + ? '[]'
  309 + : gap
  310 + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
  311 + : '[' + partial.join(',') + ']';
  312 + gap = mind;
  313 + return v;
  314 + }
  315 +
  316 +// If the replacer is an array, use it to select the members to be stringified.
  317 +
  318 + if (rep && typeof rep === 'object') {
  319 + length = rep.length;
  320 + for (i = 0; i < length; i += 1) {
  321 + if (typeof rep[i] === 'string') {
  322 + k = rep[i];
  323 + v = str(k, value);
  324 + if (v) {
  325 + partial.push(quote(k) + (gap ? ': ' : ':') + v);
  326 + }
  327 + }
  328 + }
  329 + } else {
  330 +
  331 +// Otherwise, iterate through all of the keys in the object.
  332 +
  333 + for (k in value) {
  334 + if (Object.prototype.hasOwnProperty.call(value, k)) {
  335 + v = str(k, value);
  336 + if (v) {
  337 + partial.push(quote(k) + (gap ? ': ' : ':') + v);
  338 + }
  339 + }
  340 + }
  341 + }
  342 +
  343 +// Join all of the member texts together, separated with commas,
  344 +// and wrap them in braces.
  345 +
  346 + v = partial.length === 0
  347 + ? '{}'
  348 + : gap
  349 + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
  350 + : '{' + partial.join(',') + '}';
  351 + gap = mind;
  352 + return v;
  353 + }
  354 + }
  355 +
  356 +// If the JSON object does not yet have a stringify method, give it one.
  357 +
  358 + if (typeof JSON.stringify !== 'function') {
  359 + JSON.stringify = function (value, replacer, space) {
  360 +
  361 +// The stringify method takes a value and an optional replacer, and an optional
  362 +// space parameter, and returns a JSON text. The replacer can be a function
  363 +// that can replace values, or an array of strings that will select the keys.
  364 +// A default replacer method can be provided. Use of the space parameter can
  365 +// produce text that is more easily readable.
  366 +
  367 + var i;
  368 + gap = '';
  369 + indent = '';
  370 +
  371 +// If the space parameter is a number, make an indent string containing that
  372 +// many spaces.
  373 +
  374 + if (typeof space === 'number') {
  375 + for (i = 0; i < space; i += 1) {
  376 + indent += ' ';
  377 + }
  378 +
  379 +// If the space parameter is a string, it will be used as the indent string.
  380 +
  381 + } else if (typeof space === 'string') {
  382 + indent = space;
  383 + }
  384 +
  385 +// If there is a replacer, it must be a function or an array.
  386 +// Otherwise, throw an error.
  387 +
  388 + rep = replacer;
  389 + if (replacer && typeof replacer !== 'function' &&
  390 + (typeof replacer !== 'object' ||
  391 + typeof replacer.length !== 'number')) {
  392 + throw new Error('JSON.stringify');
  393 + }
  394 +
  395 +// Make a fake root object containing our value under the key of ''.
  396 +// Return the result of stringifying the value.
  397 +
  398 + return str('', {'': value});
  399 + };
  400 + }
  401 +
  402 +
  403 +// If the JSON object does not yet have a parse method, give it one.
  404 +
  405 + if (typeof JSON.parse !== 'function') {
  406 + JSON.parse = function (text, reviver) {
  407 +
  408 +// The parse method takes a text and an optional reviver function, and returns
  409 +// a JavaScript value if the text is a valid JSON text.
  410 +
  411 + var j;
  412 +
  413 + function walk(holder, key) {
  414 +
  415 +// The walk method is used to recursively walk the resulting structure so
  416 +// that modifications can be made.
  417 +
  418 + var k, v, value = holder[key];
  419 + if (value && typeof value === 'object') {
  420 + for (k in value) {
  421 + if (Object.prototype.hasOwnProperty.call(value, k)) {
  422 + v = walk(value, k);
  423 + if (v !== undefined) {
  424 + value[k] = v;
  425 + } else {
  426 + delete value[k];
  427 + }
  428 + }
  429 + }
  430 + }
  431 + return reviver.call(holder, key, value);
  432 + }
  433 +
  434 +
  435 +// Parsing happens in four stages. In the first stage, we replace certain
  436 +// Unicode characters with escape sequences. JavaScript handles many characters
  437 +// incorrectly, either silently deleting them, or treating them as line endings.
  438 +
  439 + text = String(text);
  440 + cx.lastIndex = 0;
  441 + if (cx.test(text)) {
  442 + text = text.replace(cx, function (a) {
  443 + return '\\u' +
  444 + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  445 + });
  446 + }
  447 +
  448 +// In the second stage, we run the text against regular expressions that look
  449 +// for non-JSON patterns. We are especially concerned with '()' and 'new'
  450 +// because they can cause invocation, and '=' because it can cause mutation.
  451 +// But just to be safe, we want to reject all unexpected forms.
  452 +
  453 +// We split the second stage into 4 regexp operations in order to work around
  454 +// crippling inefficiencies in IE's and Safari's regexp engines. First we
  455 +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
  456 +// replace all simple value tokens with ']' characters. Third, we delete all
  457 +// open brackets that follow a colon or comma or that begin the text. Finally,
  458 +// we look to see that the remaining characters are only whitespace or ']' or
  459 +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
  460 +
  461 + if (/^[\],:{}\s]*$/
  462 + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
  463 + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
  464 + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  465 +
  466 +// In the third stage we use the eval function to compile the text into a
  467 +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
  468 +// in JavaScript: it can begin a block or an object literal. We wrap the text
  469 +// in parens to eliminate the ambiguity.
  470 +
  471 + j = eval('(' + text + ')');
  472 +
  473 +// In the optional fourth stage, we recursively walk the new structure, passing
  474 +// each name/value pair to a reviver function for possible transformation.
  475 +
  476 + return typeof reviver === 'function'
  477 + ? walk({'': j}, '')
  478 + : j;
  479 + }
  480 +
  481 +// If the text is not JSON parseable, then a SyntaxError is thrown.
  482 +
  483 + throw new SyntaxError('JSON.parse');
  484 + };
  485 + }
  486 +}());
  1 +/**
  2 +* log specified, there must be a log element as:
  3 + <!-- for the log -->
  4 + <div class="alert alert-info fade in" id="txt_log">
  5 + <button type="button" class="close" data-dismiss="alert">×</button>
  6 + <strong><span id="txt_log_title">标题:</span></strong>
  7 + <span id="txt_log_msg">日志内容</span>
  8 + </div>
  9 +*/
  10 +var srs_log_disabled = false;
  11 +function info(desc) {
  12 + if (srs_log_disabled) {
  13 + return;
  14 + }
  15 +
  16 + $("#txt_log").addClass("alert-info").removeClass("alert-error").removeClass("alert-warn");
  17 + $("#txt_log_title").text("Info:");
  18 + $("#txt_log_msg").html(desc);
  19 +}
  20 +function warn(code, desc) {
  21 + if (srs_log_disabled) {
  22 + return;
  23 + }
  24 +
  25 + $("#txt_log").removeClass("alert-info").removeClass("alert-error").addClass("alert-warn");
  26 + $("#txt_log_title").text("Warn:");
  27 + $("#txt_log_msg").html("code: " + code + ", " + desc);
  28 +}
  29 +function error(code, desc) {
  30 + if (srs_log_disabled) {
  31 + return;
  32 + }
  33 +
  34 + $("#txt_log").removeClass("alert-info").addClass("alert-error").removeClass("alert-warn");
  35 + $("#txt_log_title").text("Error:");
  36 + $("#txt_log_msg").html("code: " + code + ", " + desc);
  37 +}
  1 +//////////////////////////////////////////////////////////////////////////////////
  2 +//////////////////////////////////////////////////////////////////////////////////
  3 +//////////////////////////////////////////////////////////////////////////////////
  4 +
  5 +/**
  6 +* player specified size.
  7 +*/
  8 +function srs_get_player_modal() { return 740; }
  9 +function srs_get_player_width() { return srs_get_player_modal() - 30; }
  10 +function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
  11 +
  12 +// to query the swf anti cache.
  13 +function srs_get_version_code() { return "1.17"; }
  14 +// get the default vhost for players.
  15 +function srs_get_player_vhost() { return "players"; }
  16 +// the api server port, for chat room.
  17 +function srs_get_api_server_port() { return 8085; }
  18 +// get the stream published to vhost,
  19 +// generally we need to transcode the stream to support HLS and filters.
  20 +// for example, src_vhost is "players", we transcode stream to vhost "players_pub".
  21 +// if not equals to the player vhost, return the orignal vhost.
  22 +function srs_get_player_publish_vhost(src_vhost) { return (src_vhost != srs_get_player_vhost())? src_vhost:(src_vhost + "_pub"); }
  23 +// for chat, use rtmp only vhost, low latecy, without gop cache.
  24 +function srs_get_player_chat_vhost(src_vhost) { return (src_vhost != srs_get_player_vhost())? src_vhost:(src_vhost + "_pub_rtmp"); }
  25 +
  26 +//////////////////////////////////////////////////////////////////////////////////
  27 +//////////////////////////////////////////////////////////////////////////////////
  28 +//////////////////////////////////////////////////////////////////////////////////
  29 +
  30 +/**
  31 +* update the navigator, add same query string.
  32 +*/
  33 +function update_nav() {
  34 + $("#srs_index").attr("href", "index.html" + window.location.search);
  35 + $("#nav_srs_player").attr("href", "srs_player.html" + window.location.search);
  36 + $("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search);
  37 + $("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search);
  38 + $("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search);
  39 + $("#nav_jwplayer6").attr("href", "jwplayer6.html" + window.location.search);
  40 + $("#nav_osmf").attr("href", "osmf.html" + window.location.search);
  41 + $("#nav_vlc").attr("href", "vlc.html" + window.location.search);
  42 +}
  43 +
  44 +/**
  45 +@param server the ip of server. default to window.location.hostname
  46 +@param vhost the vhost of rtmp. default to window.location.hostname
  47 +@param port the port of rtmp. default to 1935
  48 +@param app the app of rtmp. default to live.
  49 +@param stream the stream of rtmp. default to livestream.
  50 +*/
  51 +function build_default_rtmp_url() {
  52 + var query = parse_query_string();
  53 +
  54 + var server = (query.server == undefined)? window.location.hostname:query.server;
  55 + var port = (query.port == undefined)? 1935:query.port;
  56 + var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost;
  57 + var app = (query.app == undefined)? "live":query.app;
  58 + var stream = (query.stream == undefined)? "livestream":query.stream;
  59 +
  60 + if (server == vhost || vhost == "") {
  61 + return "rtmp://" + server + ":" + port + "/" + app + "/" + stream;
  62 + } else {
  63 + return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
  64 + }
  65 +}
  66 +// for the chat to init the publish url.
  67 +function build_default_publish_rtmp_url() {
  68 + var query = parse_query_string();
  69 +
  70 + var server = (query.server == undefined)? window.location.hostname:query.server;
  71 + var port = (query.port == undefined)? 1935:query.port;
  72 + var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost;
  73 + var app = (query.app == undefined)? "live":query.app;
  74 + var stream = (query.stream == undefined)? "livestream":query.stream;
  75 +
  76 + if (server == vhost || vhost == "") {
  77 + return "rtmp://" + server + ":" + port + "/" + app + "/" + stream;
  78 + } else {
  79 + vhost = srs_get_player_chat_vhost(vhost);
  80 + return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
  81 + }
  82 +}
  83 +
  84 +/**
  85 +@param server the ip of server. default to window.location.hostname
  86 +@param vhost the vhost of hls. default to window.location.hostname
  87 +@param hls_vhost the vhost of hls. override the server if specified.
  88 +@param hls_port the port of hls. default to window.location.port
  89 +@param app the app of hls. default to live.
  90 +@param stream the stream of hls. default to livestream.
  91 +*/
  92 +function build_default_hls_url() {
  93 + var query = parse_query_string();
  94 +
  95 + // for http, use hls_vhost to override server if specified.
  96 + var server = window.location.hostname;
  97 + if (query.server != undefined) {
  98 + server = query.server;
  99 + } else if (query.hls_vhost != undefined) {
  100 + server = query.hls_vhost;
  101 + }
  102 +
  103 + var port = (query.hls_port == undefined)? window.location.port:query.hls_port;
  104 + var app = (query.app == undefined)? "live":query.app;
  105 + var stream = (query.stream == undefined)? "livestream":query.stream;
  106 +
  107 + if (port == "" || port == null || port == undefined) {
  108 + port = 80;
  109 + }
  110 +
  111 + return "http://" + server + ":" + port + "/" + app + "/" + stream + ".m3u8";
  112 +}
  113 +
  114 +/**
  115 +* initialize the page.
  116 +* @param rtmp_url the div id contains the rtmp stream url to play
  117 +* @param hls_url the div id contains the hls stream url to play
  118 +* @param modal_player the div id contains the modal player
  119 +*/
  120 +function srs_init(rtmp_url, hls_url, modal_player) {
  121 + update_nav();
  122 +
  123 + if (rtmp_url) {
  124 + $(rtmp_url).val(build_default_rtmp_url());
  125 + }
  126 + if (hls_url) {
  127 + $(hls_url).val(build_default_hls_url());
  128 + }
  129 + if (modal_player) {
  130 + $(modal_player).width(srs_get_player_modal() + "px");
  131 + $(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px");
  132 + }
  133 +}
  134 +// for the chat to init the publish url.
  135 +function srs_init_publish(rtmp_url) {
  136 + update_nav();
  137 +
  138 + if (rtmp_url) {
  139 + $(rtmp_url).val(build_default_publish_rtmp_url());
  140 + }
  141 +}
  142 +
  143 +// check whether can republish
  144 +function srs_can_republish() {
  145 + var browser = get_browser_agents();
  146 +
  147 + if (browser.Chrome || browser.Firefox) {
  148 + return true;
  149 + }
  150 +
  151 + if (browser.MSIE || browser.QQBrowser) {
  152 + return false;
  153 + }
  154 +
  155 + return false;
  156 +}
  157 +
  158 +// without default values set.
  159 +function srs_initialize_codec_page(
  160 + cameras, microphones,
  161 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  162 +) {
  163 + $(sl_cameras).empty();
  164 + for (var i = 0; i < cameras.length; i++) {
  165 + $(sl_cameras).append("<option value='" + i + "'>" + cameras[i] + "</option");
  166 + }
  167 + // optional: select the except matches
  168 + matchs = ["virtual"];
  169 + for (var i = 0; i < cameras.length; i++) {
  170 + for (var j = 0; j < matchs.length; j++) {
  171 + if (cameras[i].toLowerCase().indexOf(matchs[j]) == -1) {
  172 + $(sl_cameras + " option[value='" + i + "']").attr("selected", true);
  173 + break;
  174 + }
  175 + }
  176 + if (j < matchs.length) {
  177 + break;
  178 + }
  179 + }
  180 + // optional: select the first matched.
  181 + matchs = ["truevision", "integrated"];
  182 + for (var i = 0; i < cameras.length; i++) {
  183 + for (var j = 0; j < matchs.length; j++) {
  184 + if (cameras[i].toLowerCase().indexOf(matchs[j]) >= 0) {
  185 + $(sl_cameras + " option[value='" + i + "']").attr("selected", true);
  186 + break;
  187 + }
  188 + }
  189 + if (j < matchs.length) {
  190 + break;
  191 + }
  192 + }
  193 +
  194 + $(sl_microphones).empty();
  195 + for (var i = 0; i < microphones.length; i++) {
  196 + $(sl_microphones).append("<option value='" + i + "'>" + microphones[i] + "</option");
  197 + }
  198 + // optional: select the except matches
  199 + matchs = ["default"];
  200 + for (var i = 0; i < microphones.length; i++) {
  201 + for (var j = 0; j < matchs.length; j++) {
  202 + if (microphones[i].toLowerCase().indexOf(matchs[j]) == -1) {
  203 + $(sl_microphones + " option[value='" + i + "']").attr("selected", true);
  204 + break;
  205 + }
  206 + }
  207 + if (j < matchs.length) {
  208 + break;
  209 + }
  210 + }
  211 + // optional: select the first matched.
  212 + matchs = ["realtek", "内置式麦克风"];
  213 + for (var i = 0; i < microphones.length; i++) {
  214 + for (var j = 0; j < matchs.length; j++) {
  215 + if (microphones[i].toLowerCase().indexOf(matchs[j]) >= 0) {
  216 + $(sl_microphones + " option[value='" + i + "']").attr("selected", true);
  217 + break;
  218 + }
  219 + }
  220 + if (j < matchs.length) {
  221 + break;
  222 + }
  223 + }
  224 +
  225 + $(sl_vcodec).empty();
  226 + var vcodecs = ["h264", "vp6"];
  227 + vcodecs = ["h264"]; // h264 only.
  228 + for (var i = 0; i < vcodecs.length; i++) {
  229 + $(sl_vcodec).append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");
  230 + }
  231 +
  232 + $(sl_profile).empty();
  233 + var profiles = ["baseline", "main"];
  234 + for (var i = 0; i < profiles.length; i++) {
  235 + $(sl_profile).append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");
  236 + }
  237 +
  238 + $(sl_level).empty();
  239 + var levels = ["1", "1b", "1.1", "1.2", "1.3",
  240 + "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
  241 + for (var i = 0; i < levels.length; i++) {
  242 + $(sl_level).append("<option value='" + levels[i] + "'>" + levels[i] + "</option");
  243 + }
  244 +
  245 + $(sl_gop).empty();
  246 + var gops = ["0.3", "0.5", "1", "2", "3", "4",
  247 + "5", "6", "7", "8", "9", "10", "15", "20"];
  248 + for (var i = 0; i < gops.length; i++) {
  249 + $(sl_gop).append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");
  250 + }
  251 +
  252 + $(sl_size).empty();
  253 + var sizes = ["176x144", "320x240", "352x240",
  254 + "352x288", "480x360", "640x480", "720x480", "720x576", "800x600",
  255 + "1024x768", "1280x720", "1360x768", "1920x1080"];
  256 + for (i = 0; i < sizes.length; i++) {
  257 + $(sl_size).append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");
  258 + }
  259 +
  260 + $(sl_fps).empty();
  261 + var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
  262 + for (i = 0; i < fpses.length; i++) {
  263 + $(sl_fps).append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");
  264 + }
  265 +
  266 + $(sl_bitrate).empty();
  267 + var bitrates = ["50", "200", "350", "500", "650", "800",
  268 + "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
  269 + for (i = 0; i < bitrates.length; i++) {
  270 + $(sl_bitrate).append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");
  271 + }
  272 +}
  273 +/**
  274 +* when publisher ready, init the page elements.
  275 +*/
  276 +function srs_publisher_initialize_page(
  277 + cameras, microphones,
  278 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  279 +) {
  280 + srs_initialize_codec_page(
  281 + cameras, microphones,
  282 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  283 + );
  284 +
  285 + //var profiles = ["baseline", "main"];
  286 + $(sl_profile + " option[value='main']").attr("selected", true);
  287 +
  288 + //var levels = ["1", "1b", "1.1", "1.2", "1.3",
  289 + // "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
  290 + $(sl_level + " option[value='4.1']").attr("selected", true);
  291 +
  292 + //var gops = ["0.3", "0.5", "1", "2", "3", "4",
  293 + // "5", "6", "7", "8", "9", "10", "15", "20"];
  294 + $(sl_gop + " option[value='10']").attr("selected", true);
  295 +
  296 + //var sizes = ["176x144", "320x240", "352x240",
  297 + // "352x288", "480x360", "640x480", "720x480", "720x576", "800x600",
  298 + // "1024x768", "1280x720", "1360x768", "1920x1080"];
  299 + $(sl_size + " option[value='640x480']").attr("selected", true);
  300 +
  301 + //var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
  302 + $(sl_fps + " option[value='20']").attr("selected", true);
  303 +
  304 + //var bitrates = ["50", "200", "350", "500", "650", "800",
  305 + // "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
  306 + $(sl_bitrate + " option[value='500']").attr("selected", true);
  307 +}
  308 +/**
  309 +* for chat, use low latecy settings.
  310 +*/
  311 +function srs_chat_initialize_page(
  312 + cameras, microphones,
  313 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  314 +) {
  315 + srs_initialize_codec_page(
  316 + cameras, microphones,
  317 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  318 + );
  319 +
  320 + //var profiles = ["baseline", "main"];
  321 + $(sl_profile + " option[value='baseline']").attr("selected", true);
  322 +
  323 + //var levels = ["1", "1b", "1.1", "1.2", "1.3",
  324 + // "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
  325 + $(sl_level + " option[value='3.1']").attr("selected", true);
  326 +
  327 + //var gops = ["0.3", "0.5", "1", "2", "3", "4",
  328 + // "5", "6", "7", "8", "9", "10", "15", "20"];
  329 + $(sl_gop + " option[value='2']").attr("selected", true);
  330 +
  331 + //var sizes = ["176x144", "320x240", "352x240",
  332 + // "352x288", "480x360", "640x480", "720x480", "720x576", "800x600",
  333 + // "1024x768", "1280x720", "1360x768", "1920x1080"];
  334 + $(sl_size + " option[value='480x360']").attr("selected", true);
  335 +
  336 + //var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
  337 + $(sl_fps + " option[value='15']").attr("selected", true);
  338 +
  339 + //var bitrates = ["50", "200", "350", "500", "650", "800",
  340 + // "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
  341 + $(sl_bitrate + " option[value='350']").attr("selected", true);
  342 +}
  343 +/**
  344 +* get the vcodec and acodec.
  345 +*/
  346 +function srs_publiser_get_codec(
  347 + vcodec, acodec,
  348 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  349 +) {
  350 + acodec.device_code = $(sl_microphones).val();
  351 + acodec.device_name = $(sl_microphones).text();
  352 +
  353 + vcodec.device_code = $(sl_cameras).find("option:selected").val();
  354 + vcodec.device_name = $(sl_cameras).find("option:selected").text();
  355 +
  356 + vcodec.codec = $(sl_vcodec).find("option:selected").val();
  357 + vcodec.profile = $(sl_profile).find("option:selected").val();
  358 + vcodec.level = $(sl_level).find("option:selected").val();
  359 + vcodec.fps = $(sl_fps).find("option:selected").val();
  360 + vcodec.gop = $(sl_gop).find("option:selected").val();
  361 + vcodec.size = $(sl_size).find("option:selected").val();
  362 + vcodec.bitrate = $(sl_bitrate).find("option:selected").val();
  363 +}
  1 +/**
  2 +* the SrsPlayer object.
  3 +* @param container the html container id.
  4 +* @param width a float value specifies the width of player.
  5 +* @param height a float value specifies the height of player.
  6 +* @param private_object [optional] an object that used as private object,
  7 +* for example, the logic chat object which owner this player.
  8 +*/
  9 +function SrsPlayer(container, width, height, private_object) {
  10 + if (!SrsPlayer.__id) {
  11 + SrsPlayer.__id = 100;
  12 + }
  13 + if (!SrsPlayer.__players) {
  14 + SrsPlayer.__players = [];
  15 + }
  16 +
  17 + SrsPlayer.__players.push(this);
  18 +
  19 + this.private_object = private_object;
  20 + this.container = container;
  21 + this.width = width;
  22 + this.height = height;
  23 + this.id = SrsPlayer.__id++;
  24 + this.stream_url = null;
  25 + this.buffer_time = 0.8; // default to 0.8
  26 + this.volume = 1.0; // default to 100%
  27 + this.callbackObj = null;
  28 +
  29 + // callback set the following values.
  30 + this.meatadata = {}; // for on_player_metadata
  31 + this.time = 0; // current stream time.
  32 + this.buffer_length = 0; // current stream buffer length.
  33 +}
  34 +/**
  35 +* user can set some callback, then start the player.
  36 +* @param url the default url.
  37 +* callbacks:
  38 +* on_player_ready():int, when srs player ready, user can play.
  39 +* on_player_metadata(metadata:Object):int, when srs player get metadata.
  40 +*/
  41 +SrsPlayer.prototype.start = function(url) {
  42 + if (url) {
  43 + this.stream_url = url;
  44 + }
  45 +
  46 + // embed the flash.
  47 + var flashvars = {};
  48 + flashvars.id = this.id;
  49 + flashvars.on_player_ready = "__srs_on_player_ready";
  50 + flashvars.on_player_metadata = "__srs_on_player_metadata";
  51 + flashvars.on_player_timer = "__srs_on_player_timer";
  52 +
  53 + var params = {};
  54 + params.wmode = "opaque";
  55 + params.allowFullScreen = "true";
  56 + params.allowScriptAccess = "always";
  57 +
  58 + var attributes = {};
  59 +
  60 + var self = this;
  61 +
  62 + swfobject.embedSWF(
  63 + "srs_player/release/srs_player.swf?_version="+srs_get_version_code(),
  64 + this.container,
  65 + this.width, this.height,
  66 + "11.1", "js/AdobeFlashPlayerInstall.swf",
  67 + flashvars, params, attributes,
  68 + function(callbackObj){
  69 + self.callbackObj = callbackObj;
  70 + }
  71 + );
  72 +
  73 + return this;
  74 +}
  75 +/**
  76 +* play the stream.
  77 +* @param stream_url the url of stream, rtmp or http.
  78 +* @param volume the volume, 0 is mute, 1 is 100%, 2 is 200%.
  79 +*/
  80 +SrsPlayer.prototype.play = function(url, volume) {
  81 + this.stop();
  82 + SrsPlayer.__players.push(this);
  83 +
  84 + if (url) {
  85 + this.stream_url = url;
  86 + }
  87 +
  88 + // volume maybe 0, so never use if(volume) to check its value.
  89 + if (volume != null && volume != undefined) {
  90 + this.volume = volume;
  91 + }
  92 +
  93 + this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time, this.volume);
  94 +}
  95 +SrsPlayer.prototype.stop = function() {
  96 + for (var i = 0; i < SrsPlayer.__players.length; i++) {
  97 + var player = SrsPlayer.__players[i];
  98 +
  99 + if (player.id != this.id) {
  100 + continue;
  101 + }
  102 +
  103 + SrsPlayer.__players.splice(i, 1);
  104 + break;
  105 + }
  106 +
  107 + this.callbackObj.ref.__stop();
  108 +}
  109 +SrsPlayer.prototype.pause = function() {
  110 + this.callbackObj.ref.__pause();
  111 +}
  112 +SrsPlayer.prototype.resume = function() {
  113 + this.callbackObj.ref.__resume();
  114 +}
  115 +/**
  116 +* to set the DAR, for example, DAR=16:9 where num=16,den=9.
  117 +* @param num, for example, 16.
  118 +* use metadata width if 0.
  119 +* use user specified width if -1.
  120 +* @param den, for example, 9.
  121 +* use metadata height if 0.
  122 +* use user specified height if -1.
  123 +*/
  124 +SrsPlayer.prototype.set_dar = function(num, den) {
  125 + this.callbackObj.ref.__set_dar(num, den);
  126 +}
  127 +/**
  128 +* set the fullscreen size data.
  129 +* @refer the refer fullscreen mode. it can be:
  130 +* video: use video orignal size.
  131 +* screen: use screen size to rescale video.
  132 +* @param percent, the rescale percent, where
  133 +* 100 means 100%.
  134 +*/
  135 +SrsPlayer.prototype.set_fs = function(refer, percent) {
  136 + this.callbackObj.ref.__set_fs(refer, percent);
  137 +}
  138 +/**
  139 +* set the stream buffer time in seconds.
  140 +* @buffer_time the buffer time in seconds.
  141 +*/
  142 +SrsPlayer.prototype.set_bt = function(buffer_time) {
  143 + this.buffer_time = buffer_time;
  144 + this.callbackObj.ref.__set_bt(buffer_time);
  145 +}
  146 +SrsPlayer.prototype.on_player_ready = function() {
  147 +}
  148 +SrsPlayer.prototype.on_player_metadata = function(metadata) {
  149 + // ignore.
  150 +}
  151 +SrsPlayer.prototype.on_player_timer = function(time, buffer_length) {
  152 + // ignore.
  153 +}
  154 +function __srs_find_player(id) {
  155 + for (var i = 0; i < SrsPlayer.__players.length; i++) {
  156 + var player = SrsPlayer.__players[i];
  157 +
  158 + if (player.id != id) {
  159 + continue;
  160 + }
  161 +
  162 + return player;
  163 + }
  164 +
  165 + throw new Error("player not found. id=" + id);
  166 +}
  167 +function __srs_on_player_ready(id) {
  168 + var player = __srs_find_player(id);
  169 + player.on_player_ready();
  170 +}
  171 +function __srs_on_player_metadata(id, metadata) {
  172 + var player = __srs_find_player(id);
  173 +
  174 + // user may override the on_player_metadata,
  175 + // so set the data before invoke it.
  176 + player.metadata = metadata;
  177 +
  178 + player.on_player_metadata(metadata);
  179 +}
  180 +function __srs_on_player_timer(id, time, buffer_length) {
  181 + var player = __srs_find_player(id);
  182 +
  183 + buffer_length = Math.max(0, buffer_length);
  184 + buffer_length = Math.min(player.buffer_time, buffer_length);
  185 +
  186 + time = Math.max(0, time);
  187 +
  188 + // user may override the on_player_timer,
  189 + // so set the data before invoke it.
  190 + player.time = time;
  191 + player.buffer_length = buffer_length;
  192 +
  193 + player.on_player_timer(time, buffer_length);
  194 +}
  1 +/**
  2 +* the SrsPublisher object.
  3 +* @param container the html container id.
  4 +* @param width a float value specifies the width of publisher.
  5 +* @param height a float value specifies the height of publisher.
  6 +* @param private_object [optional] an object that used as private object,
  7 +* for example, the logic chat object which owner this publisher.
  8 +*/
  9 +function SrsPublisher(container, width, height, private_object) {
  10 + if (!SrsPublisher.__id) {
  11 + SrsPublisher.__id = 100;
  12 + }
  13 + if (!SrsPublisher.__publishers) {
  14 + SrsPublisher.__publishers = [];
  15 + }
  16 +
  17 + SrsPublisher.__publishers.push(this);
  18 +
  19 + this.private_object = private_object;
  20 + this.container = container;
  21 + this.width = width;
  22 + this.height = height;
  23 + this.id = SrsPublisher.__id++;
  24 + this.callbackObj = null;
  25 +
  26 + // set the values when publish.
  27 + this.url = null;
  28 + this.vcodec = {};
  29 + this.acodec = {};
  30 +
  31 + // callback set the following values.
  32 + this.cameras = [];
  33 + this.microphones = [];
  34 + this.code = 0;
  35 +
  36 + // error code defines.
  37 + this.errors = {
  38 + "100": "无法获取指定的摄像头。", //error_camera_get
  39 + "101": "无法获取指定的麦克风。", //error_microphone_get
  40 + "102": "摄像头为禁用状态,推流时请允许flash访问摄像头。", //error_camera_muted
  41 + "103": "服务器关闭了连接。", //error_connection_closed
  42 + "104": "服务器连接失败。", //error_connection_failed
  43 + "199": "未知错误。"
  44 + };
  45 +}
  46 +/**
  47 +* user can set some callback, then start the publisher.
  48 +* callbacks:
  49 +* on_publisher_ready(cameras, microphones):int, when srs publisher ready, user can publish.
  50 +* on_publisher_error(code, desc):int, when srs publisher error, callback this method.
  51 +* on_publisher_warn(code, desc):int, when srs publisher warn, callback this method.
  52 +*/
  53 +SrsPublisher.prototype.start = function() {
  54 + // embed the flash.
  55 + var flashvars = {};
  56 + flashvars.id = this.id;
  57 + flashvars.width = this.width;
  58 + flashvars.height = this.height;
  59 + flashvars.on_publisher_ready = "__srs_on_publisher_ready";
  60 + flashvars.on_publisher_error = "__srs_on_publisher_error";
  61 + flashvars.on_publisher_warn = "__srs_on_publisher_warn";
  62 +
  63 + var params = {};
  64 + params.wmode = "opaque";
  65 + params.allowFullScreen = "true";
  66 + params.allowScriptAccess = "always";
  67 +
  68 + var attributes = {};
  69 +
  70 + var self = this;
  71 +
  72 + swfobject.embedSWF(
  73 + "srs_publisher/release/srs_publisher.swf?_version="+srs_get_version_code(),
  74 + this.container,
  75 + this.width, this.height,
  76 + "11.1", "js/AdobeFlashPlayerInstall.swf",
  77 + flashvars, params, attributes,
  78 + function(callbackObj){
  79 + self.callbackObj = callbackObj;
  80 + }
  81 + );
  82 +
  83 + return this;
  84 +}
  85 +/**
  86 +* publish stream to server.
  87 +* @param url a string indicates the rtmp url to publish.
  88 +* @param vcodec an object contains the video codec info.
  89 +* @param acodec an object contains the audio codec info.
  90 +*/
  91 +SrsPublisher.prototype.publish = function(url, vcodec, acodec) {
  92 + this.stop();
  93 + SrsPublisher.__publishers.push(this);
  94 +
  95 + if (url) {
  96 + this.url = url;
  97 + }
  98 + if (vcodec) {
  99 + this.vcodec = vcodec;
  100 + }
  101 + if (acodec) {
  102 + this.acodec = acodec;
  103 + }
  104 +
  105 + this.callbackObj.ref.__publish(this.url, this.width, this.height, this.vcodec, this.acodec);
  106 +}
  107 +SrsPublisher.prototype.stop = function() {
  108 + for (var i = 0; i < SrsPublisher.__publishers.length; i++) {
  109 + var player = SrsPublisher.__publishers[i];
  110 +
  111 + if (player.id != this.id) {
  112 + continue;
  113 + }
  114 +
  115 + SrsPublisher.__publishers.splice(i, 1);
  116 + break;
  117 + }
  118 +
  119 + this.callbackObj.ref.__stop();
  120 +}
  121 +/**
  122 +* when publisher ready.
  123 +* @param cameras a string array contains the names of cameras.
  124 +* @param microphones a string array contains the names of microphones.
  125 +*/
  126 +SrsPublisher.prototype.on_publisher_ready = function(cameras, microphones) {
  127 +}
  128 +/**
  129 +* when publisher error.
  130 +* @code the error code.
  131 +* @desc the error desc message.
  132 +*/
  133 +SrsPublisher.prototype.on_publisher_error = function(code, desc) {
  134 + throw new Error("publisher error. code=" + code + ", desc=" + desc);
  135 +}
  136 +SrsPublisher.prototype.on_publisher_warn = function(code, desc) {
  137 + throw new Error("publisher warn. code=" + code + ", desc=" + desc);
  138 +}
  139 +function __srs_find_publisher(id) {
  140 + for (var i = 0; i < SrsPublisher.__publishers.length; i++) {
  141 + var publisher = SrsPublisher.__publishers[i];
  142 +
  143 + if (publisher.id != id) {
  144 + continue;
  145 + }
  146 +
  147 + return publisher;
  148 + }
  149 +
  150 + throw new Error("publisher not found. id=" + id);
  151 +}
  152 +function __srs_on_publisher_ready(id, cameras, microphones) {
  153 + var publisher = __srs_find_publisher(id);
  154 +
  155 + publisher.cameras = cameras;
  156 + publisher.microphones = microphones;
  157 +
  158 + publisher.on_publisher_ready(cameras, microphones);
  159 +}
  160 +function __srs_on_publisher_error(id, code) {
  161 + var publisher = __srs_find_publisher(id);
  162 +
  163 + publisher.code = code;
  164 +
  165 + publisher.on_publisher_error(code, publisher.errors[""+code]);
  166 +}
  167 +function __srs_on_publisher_warn(id, code) {
  168 + var publisher = __srs_find_publisher(id);
  169 +
  170 + publisher.code = code;
  171 +
  172 + publisher.on_publisher_warn(code, publisher.errors[""+code]);
  173 +}
  1 +/**
  2 +* padding the output.
  3 +* padding(3, 5, '0') is 00003
  4 +* padding(3, 5, 'x') is xxxx3
  5 +* @see http://blog.csdn.net/win_lin/article/details/12065413
  6 +*/
  7 +function padding(number, length, prefix) {
  8 + if(String(number).length >= length){
  9 + return String(number);
  10 + }
  11 + return padding(prefix+number, length, prefix);
  12 +}
  13 +
  14 +/**
  15 +* parse the query string to object.
  16 +*/
  17 +function parse_query_string(){
  18 + var obj = {};
  19 +
  20 + // add the uri object.
  21 + // parse the host(hostname:http_port), pathname(dir/filename)
  22 + obj.host = window.location.host;
  23 + obj.hostname = window.location.hostname;
  24 + obj.http_port = (window.location.port == "")? 80:window.location.port;
  25 + obj.pathname = window.location.pathname;
  26 + if (obj.pathname.lastIndexOf("/") <= 0) {
  27 + obj.dir = "/";
  28 + obj.filename = "";
  29 + } else {
  30 + obj.dir = obj.pathname.substr(0, obj.pathname.lastIndexOf("/"));
  31 + obj.filename = obj.pathname.substr(obj.pathname.lastIndexOf("/"));
  32 + }
  33 +
  34 + // pure user query object.
  35 + obj.user_query = {};
  36 +
  37 + // parse the query string.
  38 + var query_string = String(window.location.search).replace(" ", "").split("?")[1];
  39 + if(query_string == undefined){
  40 + return obj;
  41 + }
  42 +
  43 + var queries = query_string.split("&");
  44 + $(queries).each(function(){
  45 + var query = this.split("=");
  46 + obj[query[0]] = query[1];
  47 + obj.user_query[query[0]] = query[1];
  48 + });
  49 +
  50 + return obj;
  51 +}
  52 +
  53 +/**
  54 +* parse the rtmp url,
  55 +* for example: rtmp://demo.srs.com:1935/live...vhost...players/livestream
  56 +* @return object {server, port, vhost, app, stream}
  57 +*/
  58 +function srs_parse_rtmp_url(rtmp_url) {
  59 + // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
  60 + var a = document.createElement("a");
  61 + a.href = rtmp_url.replace("rtmp://", "http://");
  62 +
  63 + var vhost = a.hostname;
  64 + var port = (a.port == "")? "1935":a.port;
  65 + var app = a.pathname.substr(1, a.pathname.lastIndexOf("/") - 1);
  66 + var stream = a.pathname.substr(a.pathname.lastIndexOf("/") + 1);
  67 +
  68 + // parse the vhost in the params of app, that srs supports.
  69 + app = app.replace("...vhost...", "?vhost=");
  70 + if (app.indexOf("?") >= 0) {
  71 + var params = app.substr(app.indexOf("?"));
  72 + app = app.substr(0, app.indexOf("?"));
  73 +
  74 + if (params.indexOf("vhost=") > 0) {
  75 + vhost = params.substr(params.indexOf("vhost=") + "vhost=".length);
  76 + if (vhost.indexOf("&") > 0) {
  77 + vhost = vhost.substr(0, vhost.indexOf("&"));
  78 + }
  79 + }
  80 + }
  81 +
  82 + var ret = {
  83 + server: a.hostname, port: port,
  84 + vhost: vhost, app: app, stream: stream
  85 + };
  86 +
  87 + return ret;
  88 +}
  89 +
  90 +/**
  91 +* get the agent.
  92 +* @return an object specifies some browser.
  93 +* for example, get_browser_agents().MSIE
  94 +*/
  95 +function get_browser_agents() {
  96 + var agent = navigator.userAgent;
  97 +
  98 + /**
  99 + WindowsPC platform, Win7:
  100 + chrome 31.0.1650.63:
  101 + Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
  102 + (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
  103 + firefox 23.0.1:
  104 + Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101
  105 + Firefox/23.0
  106 + safari 5.1.7(7534.57.2):
  107 + Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2
  108 + (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2
  109 + opera 15.0.1147.153:
  110 + Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
  111 + (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
  112 + OPR/15.0.1147.153
  113 + 360 6.2.1.272:
  114 + Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
  115 + Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
  116 + .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
  117 + .NET4.0E)
  118 + IE 10.0.9200.16750(update: 10.0.12):
  119 + Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
  120 + Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
  121 + .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
  122 + .NET4.0E)
  123 + */
  124 +
  125 + return {
  126 + // platform
  127 + Android: agent.indexOf("Android") != -1,
  128 + Windows: agent.indexOf("Windows") != -1,
  129 + iPhone: agent.indexOf("iPhone") != -1,
  130 + // Windows Browsers
  131 + Chrome: agent.indexOf("Chrome") != -1,
  132 + Firefox: agent.indexOf("Firefox") != -1,
  133 + QQBrowser: agent.indexOf("QQBrowser") != -1,
  134 + MSIE: agent.indexOf("MSIE") != -1,
  135 + // Android Browsers
  136 + Opera: agent.indexOf("Presto") != -1,
  137 + MQQBrowser: agent.indexOf("MQQBrowser") != -1
  138 + };
  139 +}
@@ -6,7 +6,11 @@ @@ -6,7 +6,11 @@
6 <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/> 6 <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 - <script type="text/javascript" src="js/srs.js"></script> 9 + <script type="text/javascript" src="js/srs.page.js"></script>
  10 + <script type="text/javascript" src="js/srs.log.js"></script>
  11 + <script type="text/javascript" src="js/srs.player.js"></script>
  12 + <script type="text/javascript" src="js/srs.publisher.js"></script>
  13 + <script type="text/javascript" src="js/srs.utility.js"></script>
10 <style> 14 <style>
11 body{ 15 body{
12 padding-top: 55px; 16 padding-top: 55px;
@@ -78,7 +82,7 @@ @@ -78,7 +82,7 @@
78 <div class="navbar navbar-fixed-top"> 82 <div class="navbar navbar-fixed-top">
79 <div class="navbar-inner"> 83 <div class="navbar-inner">
80 <div class="container"> 84 <div class="container">
81 - <a class="brand" href="index.html">SRS</a> 85 + <a id="srs_index" class="brand" href="#">SRS</a>
82 <div class="nav-collapse collapse"> 86 <div class="nav-collapse collapse">
83 <ul class="nav"> 87 <ul class="nav">
84 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 88 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <title>SRS</title>
  5 + <meta charset="utf-8">
  6 + <script type="text/javascript" src="players/js/jquery-1.10.2.min.js"></script>
  7 + <script type="text/javascript" src="players/js/srs.page.js"></script>
  8 + <script type="text/javascript" src="players/js/srs.utility.js"></script>
  9 +</head>
  10 +<body>
  11 + <script type="text/javascript">
  12 + var query = parse_query_string();
  13 + var url = window.location.protocol + "//" + query.hostname + ":" + srs_get_api_server_port() + "/index.html" + window.location.search;
  14 + document.write("请确认api-server已经开启,跳转到api-server的页面(解决IE跨域问题)<br/>");
  15 + document.write("正在跳转,若您的浏览器没有跳转,请点击: <a href='" + url + "'>" + url + "</a>");
  16 +
  17 + setTimeout(function(){
  18 + window.location.href = url;
  19 + }, 3000);
  20 + </script>
  21 +</body>
@@ -7,7 +7,11 @@ @@ -7,7 +7,11 @@
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 <script type="text/javascript" src="js/swfobject.js"></script> 9 <script type="text/javascript" src="js/swfobject.js"></script>
10 - <script type="text/javascript" src="js/srs.js"></script> 10 + <script type="text/javascript" src="js/srs.page.js"></script>
  11 + <script type="text/javascript" src="js/srs.log.js"></script>
  12 + <script type="text/javascript" src="js/srs.player.js"></script>
  13 + <script type="text/javascript" src="js/srs.publisher.js"></script>
  14 + <script type="text/javascript" src="js/srs.utility.js"></script>
11 <style> 15 <style>
12 body{ 16 body{
13 padding-top: 55px; 17 padding-top: 55px;
@@ -74,7 +78,7 @@ @@ -74,7 +78,7 @@
74 <div class="navbar navbar-fixed-top"> 78 <div class="navbar navbar-fixed-top">
75 <div class="navbar-inner"> 79 <div class="navbar-inner">
76 <div class="container"> 80 <div class="container">
77 - <a class="brand" href="index.html">SRS</a> 81 + <a id="srs_index" class="brand" href="#">SRS</a>
78 <div class="nav-collapse collapse"> 82 <div class="nav-collapse collapse">
79 <ul class="nav"> 83 <ul class="nav">
80 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 84 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
@@ -7,7 +7,13 @@ @@ -7,7 +7,13 @@
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 <script type="text/javascript" src="js/swfobject.js"></script> 9 <script type="text/javascript" src="js/swfobject.js"></script>
10 - <script type="text/javascript" src="js/srs.js"></script> 10 + <script type="text/javascript" src="js/srs.page.js"></script>
  11 + <script type="text/javascript" src="js/srs.log.js"></script>
  12 + <script type="text/javascript" src="js/srs.player.js"></script>
  13 + <script type="text/javascript" src="js/srs.publisher.js"></script>
  14 + <script type="text/javascript" src="js/srs.utility.js"></script>
  15 + <script type="text/javascript" src="js/srs.utility.js"></script>
  16 + <script type="text/javascript" src="js/srs.bandwidth.js"></script>
11 <style> 17 <style>
12 body{ 18 body{
13 padding-top: 55px; 19 padding-top: 55px;
@@ -119,7 +125,7 @@ @@ -119,7 +125,7 @@
119 <div class="navbar navbar-fixed-top"> 125 <div class="navbar navbar-fixed-top">
120 <div class="navbar-inner"> 126 <div class="navbar-inner">
121 <div class="container"> 127 <div class="container">
122 - <a class="brand" href="index.html">SRS</a> 128 + <a id="srs_index" class="brand" href="#">SRS</a>
123 <div class="nav-collapse collapse"> 129 <div class="nav-collapse collapse">
124 <ul class="nav"> 130 <ul class="nav">
125 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 131 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
@@ -7,11 +7,19 @@ @@ -7,11 +7,19 @@
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 <script type="text/javascript" src="js/swfobject.js"></script> 9 <script type="text/javascript" src="js/swfobject.js"></script>
10 - <script type="text/javascript" src="js/srs.js"></script> 10 + <script type="text/javascript" src="js/json2.js"></script>
  11 + <script type="text/javascript" src="js/srs.page.js"></script>
  12 + <script type="text/javascript" src="js/srs.log.js"></script>
  13 + <script type="text/javascript" src="js/srs.player.js"></script>
  14 + <script type="text/javascript" src="js/srs.publisher.js"></script>
  15 + <script type="text/javascript" src="js/srs.utility.js"></script>
11 <style> 16 <style>
12 body{ 17 body{
13 padding-top: 55px; 18 padding-top: 55px;
14 } 19 }
  20 + .accordion-group {
  21 + width: 310px;
  22 + }
15 </style> 23 </style>
16 <script type="text/javascript"> 24 <script type="text/javascript">
17 var srs_publisher = null; 25 var srs_publisher = null;
@@ -22,18 +30,36 @@ @@ -22,18 +30,36 @@
22 var previous_chats = []; 30 var previous_chats = [];
23 var no_play = false; 31 var no_play = false;
24 32
  33 + var query = parse_query_string();
25 $(function(){ 34 $(function(){
26 // get the vhost and port to set the default url. 35 // get the vhost and port to set the default url.
27 // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo 36 // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo
28 // url set to: rtmp://demo:1935/live/livestream 37 // url set to: rtmp://demo:1935/live/livestream
29 srs_init_publish("#txt_url"); 38 srs_init_publish("#txt_url");
30 39
  40 + // support 5x3+1 users
  41 + for (var i = 0; i < 5; i++) {
  42 + var tr = $("<tr></tr>").hide();
  43 + $("#lst_chats").append(tr);
  44 +
  45 + for (var j = 0; j < 3; j++) {
  46 + tr.append($("<td></td>").attr("id", "td_" + ((i+1) * 8 + j)));
  47 + }
  48 + }
  49 + // remove border of row.
  50 + $("#lst_chats").find("td").css("border", "none").css("padding", "2px")
  51 + .css("padding-left", "0px").css("width", "327px");
  52 +
  53 + if (query.agent == "true") {
  54 + document.write(navigator.userAgent);
  55 + return;
  56 + }
  57 +
31 $("#realtime_player_url").tooltip({ 58 $("#realtime_player_url").tooltip({
32 title: "右键复制RTMP地址" 59 title: "右键复制RTMP地址"
33 }); 60 });
34 61
35 // if no play specified, donot show the player, for debug the publisher. 62 // if no play specified, donot show the player, for debug the publisher.
36 - var query = parse_query_string();  
37 if (query.no_play == "true") { 63 if (query.no_play == "true") {
38 no_play = true; 64 no_play = true;
39 } 65 }
@@ -50,7 +76,7 @@ @@ -50,7 +76,7 @@
50 $("#txt_url").val($("#txt_url").val() + "." + new Date().getTime()); 76 $("#txt_url").val($("#txt_url").val() + "." + new Date().getTime());
51 77
52 // start the publisher. 78 // start the publisher.
53 - srs_publisher = new SrsPublisher("local_publisher", 430, 185); 79 + srs_publisher = new SrsPublisher("local_publisher", 280, 180);
54 srs_publisher.on_publisher_ready = function(cameras, microphones) { 80 srs_publisher.on_publisher_ready = function(cameras, microphones) {
55 srs_chat_initialize_page( 81 srs_chat_initialize_page(
56 cameras, microphones, 82 cameras, microphones,
@@ -60,7 +86,11 @@ @@ -60,7 +86,11 @@
60 ); 86 );
61 }; 87 };
62 srs_publisher.on_publisher_error = function(code, desc) { 88 srs_publisher.on_publisher_error = function(code, desc) {
63 - error(code, desc); 89 + if (!on_publish_stop()) {
  90 + return;
  91 + }
  92 +
  93 + error(code, desc + "请重试。");
64 }; 94 };
65 srs_publisher.on_publisher_warn = function(code, desc) { 95 srs_publisher.on_publisher_warn = function(code, desc) {
66 warn(code, desc); 96 warn(code, desc);
@@ -71,18 +101,37 @@ @@ -71,18 +101,37 @@
71 101
72 if (!no_play) { 102 if (!no_play) {
73 // start the realtime player. 103 // start the realtime player.
74 - realtime_player = new SrsPlayer("realtime_player", 430, 185); 104 + realtime_player = new SrsPlayer("realtime_player", 280, 180);
75 realtime_player.on_player_ready = function() { 105 realtime_player.on_player_ready = function() {
76 this.set_bt(0.5); 106 this.set_bt(0.5);
77 - this.set_fs("screen", 100);  
78 }; 107 };
  108 + realtime_player.on_player_metadata = function(metadata) {
  109 + this.set_dar(0, 0);
  110 + this.set_fs("screen", 100);
  111 +
  112 + info("推流到服务器成功。请戴耳机聊天,否则音箱的声音会进入麦克风造成回声。");
  113 + }
79 realtime_player.start(); 114 realtime_player.start();
80 } 115 }
81 116
  117 + $("#txt_name").focus();
  118 +
82 api_server = "http://" + query.hostname + ":" + srs_get_api_server_port() + "/api/v1/chats"; 119 api_server = "http://" + query.hostname + ":" + srs_get_api_server_port() + "/api/v1/chats";
83 refresh(); 120 refresh();
84 }); 121 });
85 122
  123 + function on_publish_stop() {
  124 + if (!srs_can_republish()) {
  125 + $("#btn_join").attr("disabled", true);
  126 + error(0, "您使用的浏览器很弱,请关闭页面后重新打开页面(刷新也不管用)。<br/>推荐使用Chrome/Firefox/Safari/Opera浏览器,支持重试");
  127 +
  128 + srs_log_disabled = true;
  129 + return false;
  130 + }
  131 +
  132 + return true;
  133 + }
  134 +
86 function update_play_url() { 135 function update_play_url() {
87 var url = $("#txt_url").val(); 136 var url = $("#txt_url").val();
88 137
@@ -180,86 +229,44 @@ @@ -180,86 +229,44 @@
180 } 229 }
181 }); 230 });
182 } 231 }
183 - function render_chat_room(chats) { 232 + function render_chat_room(original_chats) {
184 if (!self_chat) { 233 if (!self_chat) {
185 return; 234 return;
186 } 235 }
187 236
188 - // new added chat  
189 - for (var i = 0; i < chats.length; i++) {  
190 - var chat = chats[i]; 237 + var chats = [];
  238 + for (var i = 0; i < original_chats.length; i++) {
  239 + var chat = original_chats[i];
  240 +
191 // ignore the self. 241 // ignore the self.
192 if (self_chat && self_chat.id == chat.id) { 242 if (self_chat && self_chat.id == chat.id) {
193 continue; 243 continue;
194 } 244 }
195 245
  246 + chats.push(chat);
  247 + }
  248 +
  249 + // new added chat
  250 + for (var i = 0; i < chats.length; i++) {
  251 + var chat = chats[i];
  252 +
196 // if previous exists, ignore, only add new here. 253 // if previous exists, ignore, only add new here.
197 var previous_chat = get_previous_chat_user(previous_chats, chat.id); 254 var previous_chat = get_previous_chat_user(previous_chats, chat.id);
198 if (previous_chat) { 255 if (previous_chat) {
199 // update reference. 256 // update reference.
  257 + chat.ui = previous_chat.ui;
  258 + chat.parent = previous_chat.parent;
200 chat.player = previous_chat.player; 259 chat.player = previous_chat.player;
201 - chat.player.private_object = chat; 260 + if (chat.player) {
  261 + chat.player.private_object = chat;
  262 + }
202 continue; 263 continue;
203 } 264 }
204 -  
205 - global_chat_user_id++;  
206 -  
207 - // username: a str indicates the user name.  
208 - // url: a str indicates the url of user stream.  
209 - // join_date: a str indicates the join timestamp in seconds.  
210 - // join_date_str: friendly formated time.  
211 - var obj = $("<div/>").html($("#template").html());  
212 - $(obj).attr("chat_id", chat.id);  
213 - $(obj).attr("id", "div_" + chat.id); // for specifed chat: $("#div_" + chat_id)  
214 - $(obj).attr("class", "div_chat"); // for all chats: $(".div_chat")  
215 - $(obj).find("#chat_player").attr("id", "rp_" + chat.id); // for specifed player: $("#rp_" + chat_id)  
216 - $(obj).find("#chat_player_raw").attr("id", "rp_raw_" + chat.id); // for specifed player: $("#rp_raw_" + chat_id)  
217 - $(obj).find("#user_name").text(chat.username);  
218 - $(obj).find("#join_date").text(chat.join_date_str);  
219 - $(obj).find("#collapseM").attr("id", "collapse_" + global_chat_user_id);  
220 - $(obj).find("#headerN").attr("href", "#collapse_" + global_chat_user_id);  
221 -  
222 - $("#lst_chats").append(obj);  
223 -  
224 - if (!no_play) {  
225 - // start the realtime player.  
226 - var _player = new SrsPlayer("rp_raw_" + chat.id, 600, 300, chat);  
227 - _player.on_player_ready = function() {  
228 - this.set_bt(0.5);  
229 - this.set_fs("screen", 100);  
230 - };  
231 - _player.start(chat.url);  
232 -  
233 - chat.player = _player;  
234 - } else {  
235 - chat.player = null;  
236 - }  
237 -  
238 - $(obj).find("#collapse_main").on('hidden', function(){  
239 - var id = $(this).parent().attr("chat_id");  
240 - var chat = get_previous_chat_user(previous_chats, id);  
241 - if (!chat || !chat.player) {  
242 - return;  
243 - }  
244 - chat.player.stop();  
245 - });  
246 - $(obj).find("#collapse_main").on('shown', function(){  
247 - var id = $(this).parent().attr("chat_id");  
248 - var chat = get_previous_chat_user(previous_chats, id);  
249 - if (!chat || !chat.player) {  
250 - return;  
251 - }  
252 - chat.player.play();  
253 - });  
254 } 265 }
255 266
256 // removed chat 267 // removed chat
257 for (var i = 0; i < previous_chats.length; i++) { 268 for (var i = 0; i < previous_chats.length; i++) {
258 var chat = previous_chats[i]; 269 var chat = previous_chats[i];
259 - // ignore the self.  
260 - if (self_chat && self_chat.id == chat.id) {  
261 - continue;  
262 - }  
263 270
264 var new_chat = get_previous_chat_user(chats, chat.id); 271 var new_chat = get_previous_chat_user(chats, chat.id);
265 if (new_chat) { 272 if (new_chat) {
@@ -272,6 +279,118 @@ @@ -272,6 +279,118 @@
272 $("#div_" + chat.id).remove(); 279 $("#div_" + chat.id).remove();
273 } 280 }
274 281
  282 + // hide empty rows.
  283 + $("#lst_chats").find("tr").each(function(){
  284 + var empty = true;
  285 +
  286 + $(this).find("td").each(function(){
  287 + if ($(this).html() != "") {
  288 + empty = false;
  289 + return false; // abort each
  290 + }
  291 + return true;
  292 + });
  293 +
  294 + if (empty) {
  295 + $(this).hide();
  296 + }
  297 + });
  298 +
  299 + // render the chat
  300 + for (var i = 0; i < chats.length; i++) {
  301 + var chat = chats[i];
  302 +
  303 + // if redered, ignore.
  304 + if (chat.parent) {
  305 + continue;
  306 + }
  307 +
  308 + // generate the ui of chat
  309 + if (!chat.ui) {
  310 + global_chat_user_id++;
  311 +
  312 + // username: a str indicates the user name.
  313 + // url: a str indicates the url of user stream.
  314 + // join_date: a str indicates the join timestamp in seconds.
  315 + // join_date_str: friendly formated time.
  316 + var obj = $("<div/>").html($("#template").html());
  317 + if (true) {
  318 + // save current ui object to the chat.
  319 + chat.ui = obj;
  320 +
  321 + $(obj).attr("chat_id", chat.id);
  322 + $(obj).attr("id", "div_" + chat.id); // for specifed chat: $("#div_" + chat_id)
  323 + $(obj).attr("class", "div_chat"); // for all chats: $(".div_chat")
  324 + $(obj).find("#chat_player").attr("id", "rp_" + chat.id); // for specifed player: $("#rp_" + chat_id)
  325 + $(obj).find("#chat_player_raw").attr("id", "rp_raw_" + chat.id); // for specifed player: $("#rp_raw_" + chat_id)
  326 + $(obj).find("#user_name").text(chat.username);
  327 + $(obj).find("#user_player_url").attr("href", chat.url);
  328 + $(obj).find("#join_date").text(chat.join_date_str.split(" ")[1]);
  329 + $(obj).find("#collapseM").attr("id", "collapse_" + global_chat_user_id);
  330 + $(obj).find("#headerN").attr("href", "#collapse_" + global_chat_user_id);
  331 + }
  332 + }
  333 +
  334 + // find a idle td to render the chat.
  335 + var parent = null;
  336 + $("#lst_chats").find("td").each(function(){
  337 + if ($(this).html() != "") {
  338 + return true;
  339 + }
  340 +
  341 + parent = $(this);
  342 + return false; // abort each
  343 + });
  344 +
  345 + if (!parent) {
  346 + warn("没有可用的位置展示流。");
  347 + break;
  348 + }
  349 + $(parent).append(chat.ui);
  350 + $(parent).parent().show();
  351 +
  352 + // when ui elements appent to the page,
  353 + // create the player, or flash will failed.
  354 + if (!chat.parent) {
  355 + chat.parent = $(parent);
  356 +
  357 + if (!no_play) {
  358 + // start the realtime player.
  359 + var _player = new SrsPlayer("rp_raw_" + chat.id, 240, 180, chat);
  360 + _player.on_player_ready = function() {
  361 + this.set_bt(0.5);
  362 + this.play();
  363 + };
  364 + _player.on_player_metadata = function(metadata) {
  365 + this.set_dar(0, 0);
  366 + this.set_fs("screen", 100);
  367 + }
  368 + _player.start(chat.url);
  369 +
  370 + chat.player = _player;
  371 + } else {
  372 + chat.player = null;
  373 + }
  374 +
  375 + $(obj).find("#collapse_main").on('hidden', function(){
  376 + var id = $(this).parent().attr("chat_id");
  377 + var chat = get_previous_chat_user(previous_chats, id);
  378 + if (!chat || !chat.player) {
  379 + return;
  380 + }
  381 + chat.player.stop();
  382 + });
  383 + $(obj).find("#collapse_main").on('shown', function(){
  384 + var id = $(this).parent().attr("chat_id");
  385 + var chat = get_previous_chat_user(previous_chats, id);
  386 + if (!chat || !chat.player) {
  387 + return;
  388 + }
  389 + chat.player.play();
  390 + });
  391 + }
  392 + }
  393 +
275 previous_chats = chats; 394 previous_chats = chats;
276 } 395 }
277 function get_previous_chat_user(arr, id) { 396 function get_previous_chat_user(arr, id) {
@@ -285,7 +404,7 @@ @@ -285,7 +404,7 @@
285 } 404 }
286 405
287 function on_user_publish() { 406 function on_user_publish() {
288 - if ($("#txt_name").val().trim() == "") { 407 + if ($("#txt_name").val() == "") {
289 $("#txt_name").focus().parent().parent().addClass("error"); 408 $("#txt_name").focus().parent().parent().addClass("error");
290 warn(100, "请输入您的名字"); 409 warn(100, "请输入您的名字");
291 return; 410 return;
@@ -315,10 +434,6 @@ @@ -315,10 +434,6 @@
315 // removed chat 434 // removed chat
316 for (var i = 0; i < previous_chats.length; i++) { 435 for (var i = 0; i < previous_chats.length; i++) {
317 var chat = previous_chats[i]; 436 var chat = previous_chats[i];
318 - // ignore the self.  
319 - if (self_chat && self_chat.id == chat.id) {  
320 - continue;  
321 - }  
322 437
323 if (chat.player) { 438 if (chat.player) {
324 chat.player.stop(); 439 chat.player.stop();
@@ -342,6 +457,10 @@ @@ -342,6 +457,10 @@
342 data : "", 457 data : "",
343 dataType : "json", 458 dataType : "json",
344 complete : function() { 459 complete : function() {
  460 + if (!on_publish_stop()) {
  461 + return;
  462 + }
  463 +
345 $("#btn_join").attr("disabled", false); 464 $("#btn_join").attr("disabled", false);
346 if (complete_pfn) { 465 if (complete_pfn) {
347 complete_pfn(); 466 complete_pfn();
@@ -376,7 +495,7 @@ @@ -376,7 +495,7 @@
376 495
377 var chat = {}; 496 var chat = {};
378 chat.id = -1; 497 chat.id = -1;
379 - chat.username = $("#txt_name").val().trim(); 498 + chat.username = $("#txt_name").val();
380 chat.agent = navigator.userAgent; 499 chat.agent = navigator.userAgent;
381 chat.url = url; 500 chat.url = url;
382 chat.vcodec = vcodec; 501 chat.vcodec = vcodec;
@@ -414,13 +533,13 @@ @@ -414,13 +533,13 @@
414 533
415 $("#btn_join").text("退出会议"); 534 $("#btn_join").text("退出会议");
416 535
417 - info("开始推流到服务器"); 536 + info("开始推流到服务器。请戴耳机聊天,否则音箱的声音会进入麦克风造成回声。");
418 srs_publisher.publish(url, vcodec, acodec); 537 srs_publisher.publish(url, vcodec, acodec);
419 538
420 if (realtime_player) { 539 if (realtime_player) {
421 // directly play the url for the realtime player. 540 // directly play the url for the realtime player.
422 realtime_player.stop(); 541 realtime_player.stop();
423 - realtime_player.play(url); 542 + realtime_player.play(url, 0);
424 } 543 }
425 } 544 }
426 }); 545 });
@@ -431,7 +550,7 @@ @@ -431,7 +550,7 @@
431 <div class="navbar navbar-fixed-top"> 550 <div class="navbar navbar-fixed-top">
432 <div class="navbar-inner"> 551 <div class="navbar-inner">
433 <div class="container"> 552 <div class="container">
434 - <a class="brand" href="index.html">SRS</a> 553 + <a id="srs_index" class="brand" href="#">SRS</a>
435 <div class="nav-collapse collapse"> 554 <div class="nav-collapse collapse">
436 <ul class="nav"> 555 <ul class="nav">
437 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 556 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
@@ -451,21 +570,21 @@ @@ -451,21 +570,21 @@
451 <div class="alert alert-info fade in" id="txt_log"> 570 <div class="alert alert-info fade in" id="txt_log">
452 <button type="button" class="close" data-dismiss="alert">×</button> 571 <button type="button" class="close" data-dismiss="alert">×</button>
453 <strong><span id="txt_log_title">Usage:</span></strong> 572 <strong><span id="txt_log_title">Usage:</span></strong>
454 - <span id="txt_log_msg">输入名字,设置编码参数后,加入会议室</span> 573 + <span id="txt_log_msg">输入名字,设点“加入会议”按钮</span>
455 </div> 574 </div>
456 575
457 <div class="control-group"> 576 <div class="control-group">
458 <div class="form-inline"> 577 <div class="form-inline">
459 - <input type="text" id="txt_name" class="input-small" placeholder="您的名字..." value=""></input>  
460 - <button class="btn input-medium" id="btn_video_settings">视频编码配置</button>  
461 - <button class="btn input-medium" id="btn_audio_settings">音频编码配置</button>  
462 - <button class="btn input-medium btn-primary" id="btn_join">加入会议</button> 578 + <button class="btn input-small" id="btn_video_settings">摄像头</button>
  579 + <button class="btn input-small" id="btn_audio_settings">麦克风</button>
  580 + <input type="text" id="txt_name" class="input-large" placeholder="请输入您的名字..." value=""></input>
  581 + <button class="btn input-small" id="btn_join">加入会议</button>
463 <input type="text" id="txt_url" class="input-mini hide" value=""></input> 582 <input type="text" id="txt_url" class="input-mini hide" value=""></input>
464 </div> 583 </div>
465 </div> 584 </div>
466 - <div class="container">  
467 - <div class="row-fluid">  
468 - <div class="span6"> 585 + <table id="lst_chats" class="table">
  586 + <tr>
  587 + <td id="td_0">
469 <div class="accordion-group"> 588 <div class="accordion-group">
470 <div class="accordion-heading"> 589 <div class="accordion-heading">
471 <span class="accordion-toggle"> 590 <span class="accordion-toggle">
@@ -478,8 +597,8 @@ @@ -478,8 +597,8 @@
478 </div> 597 </div>
479 </div> 598 </div>
480 </div> 599 </div>
481 - </div>  
482 - <div class="span6"> 600 + </td>
  601 + <td id="td_1">
483 <div class="accordion-group"> 602 <div class="accordion-group">
484 <div class="accordion-heading"> 603 <div class="accordion-heading">
485 <span class="accordion-toggle"> 604 <span class="accordion-toggle">
@@ -495,38 +614,33 @@ @@ -495,38 +614,33 @@
495 </div> 614 </div>
496 </div> 615 </div>
497 </div> 616 </div>
498 - </div>  
499 - </div>  
500 - </div> 617 + </td>
  618 + <td id="td_2"></td>
  619 + </tr>
  620 + </table>
501 <div class="container hide" id="template"> 621 <div class="container hide" id="template">
502 <div class="accordion-group" id="collapse_main"> 622 <div class="accordion-group" id="collapse_main">
503 <div class="accordion-heading" title="点击展开或收起,收起后停止播放流,展开时从服务器请求流"> 623 <div class="accordion-heading" title="点击展开或收起,收起后停止播放流,展开时从服务器请求流">
504 <span id="headerN" class="accordion-toggle" data-toggle="collapse" href="#collapseN"> 624 <span id="headerN" class="accordion-toggle" data-toggle="collapse" href="#collapseN">
505 <strong>[<a href="#"><span id="user_name">XX</span></a>]</strong> 625 <strong>[<a href="#"><span id="user_name">XX</span></a>]</strong>
506 <strong>加入时间</strong>[<span id="join_date"></span>] 626 <strong>加入时间</strong>[<span id="join_date"></span>]
507 - <img src="img/tooltip.png"/> 627 + <a id="user_player_url" href="#" data-toggle="tooltip" data-placement="top" title="">
  628 + 播放地址<img src="img/tooltip.png"/>
  629 + </a>
508 </span> 630 </span>
509 </div> 631 </div>
510 - <div id="collapseM" class="accordion-body collapse"> 632 + <div id="collapseM" class="accordion-body collapse in">
511 <div class="accordion-inner"> 633 <div class="accordion-inner">
512 - <div class="row-fluid">  
513 - <div class="span2">  
514 - </div>  
515 - <div class="span8">  
516 - <div id="chat_player">  
517 - <div id="chat_player_raw">  
518 - </div>  
519 - </div>  
520 - </div>  
521 - <div class="span2"> 634 + <div id="chat_player">
  635 + <div id="chat_player_raw">
522 </div> 636 </div>
523 </div> 637 </div>
524 </div> 638 </div>
525 </div> 639 </div>
526 </div> 640 </div>
527 </div> 641 </div>
528 - <div class="container" id="lst_chats">  
529 - </div> 642 + <table class="table">
  643 + </table>
530 <div id="video_modal" class="modal hide fade"> 644 <div id="video_modal" class="modal hide fade">
531 <div class="modal-header"> 645 <div class="modal-header">
532 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> 646 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
@@ -7,7 +7,12 @@ @@ -7,7 +7,12 @@
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 <script type="text/javascript" src="js/swfobject.js"></script> 9 <script type="text/javascript" src="js/swfobject.js"></script>
10 - <script type="text/javascript" src="js/srs.js"></script> 10 + <script type="text/javascript" src="js/json2.js"></script>
  11 + <script type="text/javascript" src="js/srs.page.js"></script>
  12 + <script type="text/javascript" src="js/srs.log.js"></script>
  13 + <script type="text/javascript" src="js/srs.player.js"></script>
  14 + <script type="text/javascript" src="js/srs.publisher.js"></script>
  15 + <script type="text/javascript" src="js/srs.utility.js"></script>
11 <style> 16 <style>
12 body{ 17 body{
13 padding-top: 55px; 18 padding-top: 55px;
@@ -30,7 +35,7 @@ @@ -30,7 +35,7 @@
30 35
31 var __active_dar = null; 36 var __active_dar = null;
32 function select_dar(dar_id, num, den) { 37 function select_dar(dar_id, num, den) {
33 - srs_player.dar(num, den); 38 + srs_player.set_dar(num, den);
34 39
35 if (__active_dar) { 40 if (__active_dar) {
36 __active_dar.removeClass("active"); 41 __active_dar.removeClass("active");
@@ -191,13 +196,13 @@ @@ -191,13 +196,13 @@
191 select_dar("#btn_dar_original", 0, 0); 196 select_dar("#btn_dar_original", 0, 0);
192 }); 197 });
193 $("#btn_dar_21_9").click(function(){ 198 $("#btn_dar_21_9").click(function(){
194 - select_dar("#btn_dar_21_9", 9, 21); 199 + select_dar("#btn_dar_21_9", 21, 9);
195 }); 200 });
196 $("#btn_dar_16_9").click(function(){ 201 $("#btn_dar_16_9").click(function(){
197 - select_dar("#btn_dar_16_9", 9, 16); 202 + select_dar("#btn_dar_16_9", 16, 9);
198 }); 203 });
199 $("#btn_dar_4_3").click(function(){ 204 $("#btn_dar_4_3").click(function(){
200 - select_dar("#btn_dar_4_3", 3, 4); 205 + select_dar("#btn_dar_4_3", 4, 3);
201 }); 206 });
202 $("#btn_dar_fill").click(function(){ 207 $("#btn_dar_fill").click(function(){
203 select_dar("#btn_dar_fill", -1, -1); 208 select_dar("#btn_dar_fill", -1, -1);
@@ -264,7 +269,7 @@ @@ -264,7 +269,7 @@
264 <div class="navbar navbar-fixed-top"> 269 <div class="navbar navbar-fixed-top">
265 <div class="navbar-inner"> 270 <div class="navbar-inner">
266 <div class="container"> 271 <div class="container">
267 - <a class="brand" href="index.html">SRS</a> 272 + <a id="srs_index" class="brand" href="#">SRS</a>
268 <div class="nav-collapse collapse"> 273 <div class="nav-collapse collapse">
269 <ul class="nav"> 274 <ul class="nav">
270 <li class="active"><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 275 <li class="active"><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
@@ -10,6 +10,7 @@ package @@ -10,6 +10,7 @@ package
10 import flash.events.NetStatusEvent; 10 import flash.events.NetStatusEvent;
11 import flash.events.TimerEvent; 11 import flash.events.TimerEvent;
12 import flash.external.ExternalInterface; 12 import flash.external.ExternalInterface;
  13 + import flash.media.SoundTransform;
13 import flash.media.Video; 14 import flash.media.Video;
14 import flash.net.NetConnection; 15 import flash.net.NetConnection;
15 import flash.net.NetStream; 16 import flash.net.NetStream;
@@ -19,6 +20,8 @@ package @@ -19,6 +20,8 @@ package
19 import flash.utils.Timer; 20 import flash.utils.Timer;
20 import flash.utils.setTimeout; 21 import flash.utils.setTimeout;
21 22
  23 + import flashx.textLayout.formats.Float;
  24 +
22 public class srs_player extends Sprite 25 public class srs_player extends Sprite
23 { 26 {
24 // user set id. 27 // user set id.
@@ -34,8 +37,8 @@ package @@ -34,8 +37,8 @@ package
34 private var user_w:int = 0; 37 private var user_w:int = 0;
35 private var user_h:int = 0; 38 private var user_h:int = 0;
36 // user set dar den:num 39 // user set dar den:num
37 - private var user_dar_num:int = 0;  
38 private var user_dar_den:int = 0; 40 private var user_dar_den:int = 0;
  41 + private var user_dar_num:int = 0;
39 // user set fs(fullscreen) refer and percent. 42 // user set fs(fullscreen) refer and percent.
40 private var user_fs_refer:String = null; 43 private var user_fs_refer:String = null;
41 private var user_fs_percent:int = 0; 44 private var user_fs_percent:int = 0;
@@ -112,7 +115,7 @@ package @@ -112,7 +115,7 @@ package
112 flash.external.ExternalInterface.addCallback("__stop", this.js_call_stop); 115 flash.external.ExternalInterface.addCallback("__stop", this.js_call_stop);
113 flash.external.ExternalInterface.addCallback("__pause", this.js_call_pause); 116 flash.external.ExternalInterface.addCallback("__pause", this.js_call_pause);
114 flash.external.ExternalInterface.addCallback("__resume", this.js_call_resume); 117 flash.external.ExternalInterface.addCallback("__resume", this.js_call_resume);
115 - flash.external.ExternalInterface.addCallback("__dar", this.js_call_dar); 118 + flash.external.ExternalInterface.addCallback("__set_dar", this.js_call_set_dar);
116 flash.external.ExternalInterface.addCallback("__set_fs", this.js_call_set_fs_size); 119 flash.external.ExternalInterface.addCallback("__set_fs", this.js_call_set_fs_size);
117 flash.external.ExternalInterface.addCallback("__set_bt", this.js_call_set_bt); 120 flash.external.ExternalInterface.addCallback("__set_bt", this.js_call_set_bt);
118 121
@@ -218,15 +221,15 @@ package @@ -218,15 +221,15 @@ package
218 } 221 }
219 222
220 /** 223 /**
221 - * to set the DAR, for example, DAR=16:9  
222 - * @param num, for example, 9.  
223 - * use metadata height if 0.  
224 - * use user specified height if -1.  
225 - * @param den, for example, 16.  
226 - * use metadata width if 0.  
227 - * use user specified width if -1. 224 + * to set the DAR, for example, DAR=16:9 where num=16,den=9.
  225 + * @param num, for example, 16.
  226 + * use metadata width if 0.
  227 + * use user specified width if -1.
  228 + * @param den, for example, 9.
  229 + * use metadata height if 0.
  230 + * use user specified height if -1.
228 */ 231 */
229 - private function js_call_dar(num:int, den:int):void { 232 + private function js_call_set_dar(num:int, den:int):void {
230 user_dar_num = num; 233 user_dar_num = num;
231 user_dar_den = den; 234 user_dar_den = den;
232 235
@@ -280,8 +283,9 @@ package @@ -280,8 +283,9 @@ package
280 * @param _width, the player width. 283 * @param _width, the player width.
281 * @param _height, the player height. 284 * @param _height, the player height.
282 * @param buffer_time, the buffer time in seconds. recommend to >=0.5 285 * @param buffer_time, the buffer time in seconds. recommend to >=0.5
  286 + * @param volume, the volume, 0 is mute, 1 is 100%, 2 is 200%.
283 */ 287 */
284 - private function js_call_play(url:String, _width:int, _height:int, buffer_time:Number):void { 288 + private function js_call_play(url:String, _width:int, _height:int, buffer_time:Number, volume:Number):void {
285 this.user_url = url; 289 this.user_url = url;
286 this.user_w = _width; 290 this.user_w = _width;
287 this.user_h = _height; 291 this.user_h = _height;
@@ -313,6 +317,7 @@ package @@ -313,6 +317,7 @@ package
313 } 317 }
314 318
315 media_stream = new NetStream(media_conn); 319 media_stream = new NetStream(media_conn);
  320 + media_stream.soundTransform = new SoundTransform(volume);
316 media_stream.bufferTime = buffer_time; 321 media_stream.bufferTime = buffer_time;
317 media_stream.client = {}; 322 media_stream.client = {};
318 media_stream.client.onMetaData = system_on_metadata; 323 media_stream.client.onMetaData = system_on_metadata;
@@ -397,21 +402,21 @@ package @@ -397,21 +402,21 @@ package
397 var obj:Object = __get_video_size_object(); 402 var obj:Object = __get_video_size_object();
398 403
399 // get the DAR 404 // get the DAR
400 - var num:int = user_dar_num;  
401 var den:int = user_dar_den; 405 var den:int = user_dar_den;
  406 + var num:int = user_dar_num;
402 407
403 - if (num == 0) {  
404 - num = obj.height; 408 + if (den == 0) {
  409 + den = obj.height;
405 } 410 }
406 - if (num == -1) {  
407 - num = this.stage.fullScreenHeight; 411 + if (den == -1) {
  412 + den = this.stage.fullScreenHeight;
408 } 413 }
409 414
410 - if (den == 0) {  
411 - den = obj.width; 415 + if (num == 0) {
  416 + num = obj.width;
412 } 417 }
413 - if (den == -1) {  
414 - den = this.stage.fullScreenWidth; 418 + if (num == -1) {
  419 + num = this.stage.fullScreenWidth;
415 } 420 }
416 421
417 // for refer is screen. 422 // for refer is screen.
@@ -431,23 +436,23 @@ package @@ -431,23 +436,23 @@ package
431 */ 436 */
432 private function __execute_user_set_dar():void { 437 private function __execute_user_set_dar():void {
433 // get the DAR 438 // get the DAR
434 - var num:int = user_dar_num;  
435 var den:int = user_dar_den; 439 var den:int = user_dar_den;
  440 + var num:int = user_dar_num;
436 441
437 var obj:Object = __get_video_size_object(); 442 var obj:Object = __get_video_size_object();
438 443
439 - if (num == 0) {  
440 - num = obj.height; 444 + if (den == 0) {
  445 + den = obj.height;
441 } 446 }
442 - if (num == -1) {  
443 - num = this.user_h; 447 + if (den == -1) {
  448 + den = this.user_h;
444 } 449 }
445 450
446 - if (den == 0) {  
447 - den = obj.width; 451 + if (num == 0) {
  452 + num = obj.width;
448 } 453 }
449 - if (den == -1) {  
450 - den = this.user_w; 454 + if (num == -1) {
  455 + num = this.user_w;
451 } 456 }
452 457
453 __update_video_size(num, den, this.user_w, this.user_h, this.user_w, this.user_h); 458 __update_video_size(num, den, this.user_w, this.user_h, this.user_w, this.user_h);
@@ -463,19 +468,19 @@ package @@ -463,19 +468,19 @@ package
463 * @param _sw/_wh the stage size, >= paper size. used to center the player. 468 * @param _sw/_wh the stage size, >= paper size. used to center the player.
464 */ 469 */
465 private function __update_video_size(_num:int, _den:int, _w:int, _h:int, _sw:int, _sh:int):void { 470 private function __update_video_size(_num:int, _den:int, _w:int, _h:int, _sw:int, _sh:int):void {
466 - if (!this.media_video || _num <= 0 || _den <= 0) { 471 + if (!this.media_video || _den <= 0 || _num <= 0) {
467 return; 472 return;
468 } 473 }
469 474
470 // set DAR. 475 // set DAR.
471 // calc the height by DAR 476 // calc the height by DAR
472 - var _height:int = _w * _num / _den; 477 + var _height:int = _w * _den / _num;
473 if (_height <= _h) { 478 if (_height <= _h) {
474 this.media_video.width = _w; 479 this.media_video.width = _w;
475 this.media_video.height = _height; 480 this.media_video.height = _height;
476 } else { 481 } else {
477 // height overflow, calc the width by DAR 482 // height overflow, calc the width by DAR
478 - var _width:int = _h * _den / _num; 483 + var _width:int = _h * _num / _den;
479 484
480 this.media_video.width = _width; 485 this.media_video.width = _width;
481 this.media_video.height = _h; 486 this.media_video.height = _h;
@@ -7,7 +7,12 @@ @@ -7,7 +7,12 @@
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 <script type="text/javascript" src="js/swfobject.js"></script> 9 <script type="text/javascript" src="js/swfobject.js"></script>
10 - <script type="text/javascript" src="js/srs.js"></script> 10 + <script type="text/javascript" src="js/json2.js"></script>
  11 + <script type="text/javascript" src="js/srs.page.js"></script>
  12 + <script type="text/javascript" src="js/srs.log.js"></script>
  13 + <script type="text/javascript" src="js/srs.player.js"></script>
  14 + <script type="text/javascript" src="js/srs.publisher.js"></script>
  15 + <script type="text/javascript" src="js/srs.utility.js"></script>
11 <style> 16 <style>
12 body{ 17 body{
13 padding-top: 55px; 18 padding-top: 55px;
@@ -18,12 +23,18 @@ @@ -18,12 +23,18 @@
18 var remote_player = null; 23 var remote_player = null;
19 var realtime_player = null; 24 var realtime_player = null;
20 25
  26 + var query = parse_query_string();
21 $(function(){ 27 $(function(){
22 // get the vhost and port to set the default url. 28 // get the vhost and port to set the default url.
23 // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo 29 // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo
24 // url set to: rtmp://demo:1935/live/livestream 30 // url set to: rtmp://demo:1935/live/livestream
25 srs_init("#txt_url", null, null); 31 srs_init("#txt_url", null, null);
26 32
  33 + if (query.agent == "true") {
  34 + document.write(navigator.userAgent);
  35 + return;
  36 + }
  37 +
27 $("#btn_video_settings").click(function(){ 38 $("#btn_video_settings").click(function(){
28 $("#video_modal").modal({show:true}); 39 $("#video_modal").modal({show:true});
29 }); 40 });
@@ -60,7 +71,10 @@ @@ -60,7 +71,10 @@
60 ); 71 );
61 }; 72 };
62 srs_publisher.on_publisher_error = function(code, desc) { 73 srs_publisher.on_publisher_error = function(code, desc) {
63 - error(code, desc); 74 + if (!on_publish_stop()) {
  75 + return;
  76 + }
  77 + error(code, desc + "请重试。");
64 }; 78 };
65 srs_publisher.on_publisher_warn = function(code, desc) { 79 srs_publisher.on_publisher_warn = function(code, desc) {
66 warn(code, desc); 80 warn(code, desc);
@@ -70,26 +84,43 @@ @@ -70,26 +84,43 @@
70 update_play_url(); 84 update_play_url();
71 85
72 // if no play specified, donot show the player, for debug the publisher. 86 // if no play specified, donot show the player, for debug the publisher.
73 - var query = parse_query_string();  
74 if (query.no_play != "true") { 87 if (query.no_play != "true") {
75 // start the normal player with HLS supported. 88 // start the normal player with HLS supported.
76 remote_player = new SrsPlayer("remote_player", 430, 185); 89 remote_player = new SrsPlayer("remote_player", 430, 185);
77 remote_player.on_player_ready = function() { 90 remote_player.on_player_ready = function() {
78 this.set_bt(0.8); 91 this.set_bt(0.8);
79 - this.set_fs("screen", 100);  
80 }; 92 };
  93 + remote_player.on_player_metadata = function(metadata) {
  94 + this.set_dar(0, 0);
  95 + this.set_fs("screen", 100);
  96 + }
81 remote_player.start(); 97 remote_player.start();
82 98
83 // start the realtime player. 99 // start the realtime player.
84 realtime_player = new SrsPlayer("realtime_player", 430, 185); 100 realtime_player = new SrsPlayer("realtime_player", 430, 185);
85 realtime_player.on_player_ready = function() { 101 realtime_player.on_player_ready = function() {
86 this.set_bt(0.8); 102 this.set_bt(0.8);
87 - this.set_fs("screen", 100);  
88 }; 103 };
  104 + realtime_player.on_player_metadata = function(metadata) {
  105 + this.set_dar(0, 0);
  106 + this.set_fs("screen", 100);
  107 + }
89 realtime_player.start(); 108 realtime_player.start();
90 } 109 }
91 }); 110 });
92 111
  112 + function on_publish_stop() {
  113 + if (!srs_can_republish()) {
  114 + $("#btn_join").attr("disabled", true);
  115 + error(0, "您使用的浏览器很弱,请关闭页面后重新打开页面(刷新也不管用)。<br/>推荐使用Chrome/Firefox/Safari/Opera浏览器,支持重试");
  116 +
  117 + srs_log_disabled = true;
  118 + return false;
  119 + }
  120 +
  121 + return true;
  122 + }
  123 +
93 /** 124 /**
94 * we generate the transcoded stream url for flash publish donot support HLS 125 * we generate the transcoded stream url for flash publish donot support HLS
95 * which requires aac, so the publish vhost maybe players for example, we 126 * which requires aac, so the publish vhost maybe players for example, we
@@ -101,7 +132,6 @@ @@ -101,7 +132,6 @@
101 function update_play_url() { 132 function update_play_url() {
102 var url = $("#txt_url").val(); 133 var url = $("#txt_url").val();
103 var ret = srs_parse_rtmp_url(url); 134 var ret = srs_parse_rtmp_url(url);
104 - var query = parse_query_string();  
105 135
106 var remote_url = "rtmp://" + ret.server + ":" + ret.port + "/" + ret.app + "...vhost..." + srs_get_player_publish_vhost(ret.vhost) + "/" + ret.stream; 136 var remote_url = "rtmp://" + ret.server + ":" + ret.port + "/" + ret.app + "...vhost..." + srs_get_player_publish_vhost(ret.vhost) + "/" + ret.stream;
107 $("#realtime_player_url").attr("href", url).attr("target", "_blank"); 137 $("#realtime_player_url").attr("href", url).attr("target", "_blank");
@@ -136,6 +166,10 @@ @@ -136,6 +166,10 @@
136 //$("#remote_player_url").attr("href", "#").attr("target", "_self"); 166 //$("#remote_player_url").attr("href", "#").attr("target", "_self");
137 //$("#txt_play_hls").text("HLS-m3u8(请发布视频)").attr("href", "#").attr("target", "_self"); 167 //$("#txt_play_hls").text("HLS-m3u8(请发布视频)").attr("href", "#").attr("target", "_self");
138 //$("#txt_play_jwplayer").text("HLS-JWPlayer(请发布视频)").attr("href", "#").attr("target", "_self"); 168 //$("#txt_play_jwplayer").text("HLS-JWPlayer(请发布视频)").attr("href", "#").attr("target", "_self");
  169 +
  170 + if (!on_publish_stop()) {
  171 + return;
  172 + }
139 return; 173 return;
140 } 174 }
141 175
@@ -180,7 +214,7 @@ @@ -180,7 +214,7 @@
180 <div class="navbar navbar-fixed-top"> 214 <div class="navbar navbar-fixed-top">
181 <div class="navbar-inner"> 215 <div class="navbar-inner">
182 <div class="container"> 216 <div class="container">
183 - <a class="brand" href="index.html">SRS</a> 217 + <a id="srs_index" class="brand" href="#">SRS</a>
184 <div class="nav-collapse collapse"> 218 <div class="nav-collapse collapse">
185 <ul class="nav"> 219 <ul class="nav">
186 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 220 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
@@ -10,9 +10,14 @@ package @@ -10,9 +10,14 @@ package
10 import flash.media.H264Profile; 10 import flash.media.H264Profile;
11 import flash.media.H264VideoStreamSettings; 11 import flash.media.H264VideoStreamSettings;
12 import flash.media.Microphone; 12 import flash.media.Microphone;
  13 + import flash.media.MicrophoneEnhancedMode;
  14 + import flash.media.MicrophoneEnhancedOptions;
  15 + import flash.media.SoundCodec;
13 import flash.media.Video; 16 import flash.media.Video;
14 import flash.net.NetConnection; 17 import flash.net.NetConnection;
15 import flash.net.NetStream; 18 import flash.net.NetStream;
  19 + import flash.system.Security;
  20 + import flash.system.SecurityPanel;
16 import flash.ui.ContextMenu; 21 import flash.ui.ContextMenu;
17 import flash.ui.ContextMenuItem; 22 import flash.ui.ContextMenuItem;
18 import flash.utils.setTimeout; 23 import flash.utils.setTimeout;
@@ -45,6 +50,8 @@ package @@ -45,6 +50,8 @@ package
45 private const error_camera_get:int = 100; 50 private const error_camera_get:int = 100;
46 private const error_microphone_get:int = 101; 51 private const error_microphone_get:int = 101;
47 private const error_camera_muted:int = 102; 52 private const error_camera_muted:int = 102;
  53 + private const error_connection_closed:int = 103;
  54 + private const error_connection_failed:int = 104;
48 55
49 public function srs_publisher() 56 public function srs_publisher()
50 { 57 {
@@ -79,6 +86,18 @@ package @@ -79,6 +86,18 @@ package
79 this.js_on_publisher_error = flashvars.on_publisher_error; 86 this.js_on_publisher_error = flashvars.on_publisher_error;
80 this.js_on_publisher_warn = flashvars.on_publisher_warn; 87 this.js_on_publisher_warn = flashvars.on_publisher_warn;
81 88
  89 + // initialized size.
  90 + this.user_w = flashvars.width;
  91 + this.user_h = flashvars.height;
  92 +
  93 + // try to get the camera, if muted, alert the security and requires user to open it.
  94 + var c:Camera = Camera.getCamera();
  95 + if (c.muted) {
  96 + Security.showSettings(SecurityPanel.PRIVACY);
  97 + }
  98 +
  99 + __show_local_camera(c);
  100 +
82 flash.utils.setTimeout(this.system_on_js_ready, 0); 101 flash.utils.setTimeout(this.system_on_js_ready, 0);
83 } 102 }
84 103
@@ -135,27 +154,31 @@ package @@ -135,27 +154,31 @@ package
135 this.js_call_stop(); 154 this.js_call_stop();
136 155
137 // microphone and camera 156 // microphone and camera
138 - var m:Microphone = Microphone.getMicrophone(acodec.device_code);  
139 - if(m == null){ 157 + var microphone:Microphone = null;
  158 + //microphone = Microphone.getEnhancedMicrophone(acodec.device_code);
  159 + if (!microphone) {
  160 + microphone = Microphone.getMicrophone(acodec.device_code);
  161 + }
  162 + if(microphone == null){
140 this.system_error(this.error_microphone_get, "failed to open microphone " + acodec.device_code + "(" + acodec.device_name + ")"); 163 this.system_error(this.error_microphone_get, "failed to open microphone " + acodec.device_code + "(" + acodec.device_name + ")");
141 return; 164 return;
142 } 165 }
143 // ignore muted, for flash will require user to access it. 166 // ignore muted, for flash will require user to access it.
144 167
145 // Remark: the name is the index! 168 // Remark: the name is the index!
146 - var c:Camera = Camera.getCamera(vcodec.device_code);  
147 - if(c == null){ 169 + var camera:Camera = Camera.getCamera(vcodec.device_code);
  170 + if(camera == null){
148 this.system_error(this.error_camera_get, "failed to open camera " + vcodec.device_code + "(" + vcodec.device_name + ")"); 171 this.system_error(this.error_camera_get, "failed to open camera " + vcodec.device_code + "(" + vcodec.device_name + ")");
149 return; 172 return;
150 } 173 }
151 // ignore muted, for flash will require user to access it. 174 // ignore muted, for flash will require user to access it.
152 // but we still warn user. 175 // but we still warn user.
153 - if(c && c.muted){ 176 + if(camera && camera.muted){
154 this.system_warn(this.error_camera_muted, "Access Denied, camera " + vcodec.device_code + "(" + vcodec.device_name + ") is muted"); 177 this.system_warn(this.error_camera_muted, "Access Denied, camera " + vcodec.device_code + "(" + vcodec.device_name + ") is muted");
155 } 178 }
156 179
157 - this.media_camera = c;  
158 - this.media_microphone = m; 180 + this.media_camera = camera;
  181 + this.media_microphone = microphone;
159 182
160 this.media_conn = new NetConnection(); 183 this.media_conn = new NetConnection();
161 this.media_conn.client = {}; 184 this.media_conn.client = {};
@@ -174,6 +197,17 @@ package @@ -174,6 +197,17 @@ package
174 } 197 }
175 contextMenu.customItems = customItems; 198 contextMenu.customItems = customItems;
176 } 199 }
  200 +
  201 + if (evt.info.code == "NetConnection.Connect.Closed") {
  202 + js_call_stop();
  203 + system_error(error_connection_closed, "server closed the connection");
  204 + return;
  205 + }
  206 + if (evt.info.code == "NetConnection.Connect.Failed") {
  207 + js_call_stop();
  208 + system_error(error_connection_failed, "connect to server failed");
  209 + return;
  210 + }
177 211
178 // TODO: FIXME: failed event. 212 // TODO: FIXME: failed event.
179 if (evt.info.code != "NetConnection.Connect.Success") { 213 if (evt.info.code != "NetConnection.Connect.Success") {
@@ -188,30 +222,20 @@ package @@ -188,30 +222,20 @@ package
188 // TODO: FIXME: failed event. 222 // TODO: FIXME: failed event.
189 }); 223 });
190 224
191 - __build_video_codec(media_stream, c, vcodec);  
192 - __build_audio_codec(media_stream, m, acodec); 225 + __build_video_codec(media_stream, camera, vcodec);
  226 + __build_audio_codec(media_stream, microphone, acodec);
193 227
194 if (media_microphone) { 228 if (media_microphone) {
195 - media_stream.attachAudio(m); 229 + media_stream.attachAudio(microphone);
196 } 230 }
197 if (media_camera) { 231 if (media_camera) {
198 - media_stream.attachCamera(c); 232 + media_stream.attachCamera(camera);
199 } 233 }
200 234
201 var streamName:String = url.substr(url.lastIndexOf("/")); 235 var streamName:String = url.substr(url.lastIndexOf("/"));
202 media_stream.publish(streamName); 236 media_stream.publish(streamName);
203 237
204 - media_video = new Video();  
205 - media_video.width = _width;  
206 - media_video.height = _height;  
207 - media_video.attachCamera(media_camera);  
208 - media_video.smoothing = true;  
209 - addChild(media_video);  
210 -  
211 - //__draw_black_background(_width, _height);  
212 -  
213 - // lowest layer, for mask to cover it.  
214 - setChildIndex(media_video, 0); 238 + __show_local_camera(media_camera);
215 }); 239 });
216 240
217 var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/")); 241 var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/"));
@@ -222,11 +246,9 @@ package @@ -222,11 +246,9 @@ package
222 * function for js to call: to stop the stream. ignore if not publish. 246 * function for js to call: to stop the stream. ignore if not publish.
223 */ 247 */
224 private function js_call_stop():void { 248 private function js_call_stop():void {
225 - if (this.media_video) {  
226 - this.removeChild(this.media_video);  
227 - this.media_video = null;  
228 - }  
229 if (this.media_stream) { 249 if (this.media_stream) {
  250 + this.media_stream.attachAudio(null);
  251 + this.media_stream.attachCamera(null);
230 this.media_stream.close(); 252 this.media_stream.close();
231 this.media_stream = null; 253 this.media_stream = null;
232 } 254 }
@@ -264,6 +286,10 @@ package @@ -264,6 +286,10 @@ package
264 // if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that 286 // if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that
265 // your sound capture device supports, usually 11 kHz. 287 // your sound capture device supports, usually 11 kHz.
266 m.rate = microRate; 288 m.rate = microRate;
  289 +
  290 + // see: http://www.adobe.com/cn/devnet/flashplayer/articles/acoustic-echo-cancellation.html
  291 + m.codec = SoundCodec.SPEEX;
  292 + m.framesPerPacket = 1;
267 } 293 }
268 private function __build_video_codec(stream:NetStream, c:Camera, vcodec:Object):void { 294 private function __build_video_codec(stream:NetStream, c:Camera, vcodec:Object):void {
269 if (!c) { 295 if (!c) {
@@ -332,9 +358,56 @@ package @@ -332,9 +358,56 @@ package
332 // (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth, 358 // (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth,
333 // pass 0 for quality. 359 // pass 0 for quality.
334 // winlin: 360 // winlin:
335 - // bandwidth is in bps not kbps. 500*1000 = 500kbps. 361 + // bandwidth is in Bps not kbps.
336 // quality=1 is lowest quality, 100 is highest quality. 362 // quality=1 is lowest quality, 100 is highest quality.
337 - c.setQuality(cameraBitrate * 1000, cameraQuality); 363 + c.setQuality(cameraBitrate / 8.0 * 1000, cameraQuality);
  364 + }
  365 +
  366 + private function __show_local_camera(c:Camera):void {
  367 + if (this.media_video) {
  368 + this.media_video.attachCamera(null);
  369 + this.removeChild(this.media_video);
  370 + this.media_video = null;
  371 + }
  372 +
  373 + // show local camera
  374 + media_video = new Video();
  375 + media_video.attachCamera(c);
  376 + media_video.smoothing = true;
  377 + addChild(media_video);
  378 +
  379 + // rescale the local camera.
  380 + var cw:Number = user_h * c.width / c.height;
  381 + if (cw > user_w) {
  382 + var ch:Number = user_w * c.height / c.width;
  383 + media_video.width = user_w;
  384 + media_video.height = ch;
  385 + } else {
  386 + media_video.width = cw;
  387 + media_video.height = user_h;
  388 + }
  389 + media_video.x = (user_w - media_video.width) / 2;
  390 + media_video.y = (user_h - media_video.height) / 2;
  391 +
  392 + __draw_black_background(user_w, user_h);
  393 +
  394 + // lowest layer, for mask to cover it.
  395 + setChildIndex(media_video, 0);
  396 + }
  397 +
  398 + /**
  399 + * draw black background and draw the fullscreen mask.
  400 + */
  401 + private function __draw_black_background(_width:int, _height:int):void {
  402 + // draw black bg.
  403 + this.graphics.beginFill(0x00, 1.0);
  404 + this.graphics.drawRect(0, 0, _width, _height);
  405 + this.graphics.endFill();
  406 +
  407 + // draw the fs mask.
  408 + //this.control_fs_mask.graphics.beginFill(0xff0000, 0);
  409 + //this.control_fs_mask.graphics.drawRect(0, 0, _width, _height);
  410 + //this.control_fs_mask.graphics.endFill();
338 } 411 }
339 } 412 }
340 } 413 }
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> 7 <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
8 <script type="text/javascript" src="js/bootstrap.min.js"></script> 8 <script type="text/javascript" src="js/bootstrap.min.js"></script>
9 <script type="text/javascript" src="js/swfobject.js"></script> 9 <script type="text/javascript" src="js/swfobject.js"></script>
10 - <script type="text/javascript" src="js/srs.js"></script> 10 + <script type="text/javascript" src="js/srs.page.js"></script>
11 <style> 11 <style>
12 body{ 12 body{
13 padding-top: 55px; 13 padding-top: 55px;
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 <div class="navbar navbar-fixed-top"> 24 <div class="navbar navbar-fixed-top">
25 <div class="navbar-inner"> 25 <div class="navbar-inner">
26 <div class="container"> 26 <div class="container">
27 - <a class="brand" href="index.html">SRS</a> 27 + <a id="srs_index" class="brand" href="#">SRS</a>
28 <div class="nav-collapse collapse"> 28 <div class="nav-collapse collapse">
29 <ul class="nav"> 29 <ul class="nav">
30 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li> 30 <li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
@@ -4,7 +4,9 @@ if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; @@ -4,7 +4,9 @@ if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本";
4 4
5 cmd="sudo ./objs/nginx/sbin/nginx" 5 cmd="sudo ./objs/nginx/sbin/nginx"
6 echo "启动NGINX(HLS服务):$cmd" 6 echo "启动NGINX(HLS服务):$cmd"
7 -pids=`ps aux|grep nginx|grep process|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGKILL $pid; done 7 +if [[ -f ./objs/nginx/logs/nginx.pid ]]; then
  8 + pids=`cat ./objs/nginx/logs/nginx.pid`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGTERM $pid; done
  9 +fi
8 sudo ./objs/nginx/sbin/nginx 10 sudo ./objs/nginx/sbin/nginx
9 ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动NGINX(HLS服务)失败"; exit $ret; fi 11 ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动NGINX(HLS服务)失败"; exit $ret; fi
10 12
@@ -31,12 +31,6 @@ bash scripts/_step.start.api.server.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ @@ -31,12 +31,6 @@ bash scripts/_step.start.api.server.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $
31 31
32 # step 8: add server ip to client hosts as demo. 32 # step 8: add server ip to client hosts as demo.
33 ip=`ifconfig|grep "inet"|grep "addr"|grep "Mask"|grep -v "127.0.0.1"|awk 'NR==1 {print $2}'|awk -F ':' '{print $2}'` 33 ip=`ifconfig|grep "inet"|grep "addr"|grep "Mask"|grep -v "127.0.0.1"|awk 'NR==1 {print $2}'|awk -F ':' '{print $2}'`
34 -echo -e "${GREEN}SRS系统开发环境启动成功${BLACK}"  
35 -echo -e "${BLACK}播放器演示:${BLACK}"  
36 -echo -e "${RED} http://$ip/players/srs_player.html?vhost=players${BLACK}"  
37 -echo -e "${BLACK}编码器演示:${BLACK}"  
38 -echo -e "${RED} http://$ip/players/srs_publisher.html?vhost=players${BLACK}"  
39 -echo -e "${BLACK}视频会议演示:${BLACK}"  
40 -echo -e "${RED} http://$ip/players/srs_chat.html?vhost=players${BLACK}"  
41 -echo -e "${BLACK}服务器测速演示:${BLACK}"  
42 -echo -e "${RED} http://$ip/players/srs_bwt.html?vhost=players${BLACK}" 34 +echo -e "${GREEN}SRS系统开发环境启动成功。演示:${BLACK}"
  35 +echo -e "${RED} http://$ip${BLACK}"
  36 +echo -e "${RED} http://$ip:8085/players/index.html${BLACK}"
@@ -45,3 +45,4 @@ cat<<END @@ -45,3 +45,4 @@ cat<<END
45 END 45 END
46 echo -e "${GREEN}演示地址:${BLACK}" 46 echo -e "${GREEN}演示地址:${BLACK}"
47 echo -e "${RED} http://$ip${BLACK}" 47 echo -e "${RED} http://$ip${BLACK}"
  48 +echo -e "${RED} http://$ip:8085${BLACK}"
@@ -11,7 +11,9 @@ pids=`ps aux|grep srs|grep "./objs"|grep "srs.19350.conf"|awk '{print $2}'`; for @@ -11,7 +11,9 @@ pids=`ps aux|grep srs|grep "./objs"|grep "srs.19350.conf"|awk '{print $2}'`; for
11 # step 4(optinal): start nginx for HLS 11 # step 4(optinal): start nginx for HLS
12 echo "停止NGINX(HLS服务)" 12 echo "停止NGINX(HLS服务)"
13 ps aux|grep nginx|grep process 13 ps aux|grep nginx|grep process
14 -pids=`ps aux|grep nginx|grep process|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGKILL $pid; done 14 +if [[ -f ./objs/nginx/logs/nginx.pid ]]; then
  15 + pids=`cat ./objs/nginx/logs/nginx.pid`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGTERM $pid; done
  16 +fi
15 17
16 # step 5(optinal): start http hooks for srs callback 18 # step 5(optinal): start http hooks for srs callback
17 echo "停止API服务器" 19 echo "停止API服务器"
@@ -24,6 +24,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -24,6 +24,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include <srs_core_bandwidth.hpp> 24 #include <srs_core_bandwidth.hpp>
25 25
26 #include <arpa/inet.h> 26 #include <arpa/inet.h>
  27 +#include <sstream>
27 28
28 using namespace std; 29 using namespace std;
29 30
@@ -260,21 +261,24 @@ int SrsBandwidth::check_play( @@ -260,21 +261,24 @@ int SrsBandwidth::check_play(
260 int64_t current_time = srs_get_system_time_ms(); 261 int64_t current_time = srs_get_system_time_ms();
261 int size = 1024; // TODO: FIXME: magic number 262 int size = 1024; // TODO: FIXME: magic number
262 char random_data[size]; 263 char random_data[size];
263 - memset(random_data, 0x01, size); 264 + memset(random_data, 'A', size);
264 265
265 int interval = 0; 266 int interval = 0;
  267 + int data_count = 1;
266 while ( (srs_get_system_time_ms() - current_time) < duration_ms ) { 268 while ( (srs_get_system_time_ms() - current_time) < duration_ms ) {
267 st_usleep(interval); 269 st_usleep(interval);
268 270
269 // TODO: FIXME: use shared ptr message. 271 // TODO: FIXME: use shared ptr message.
270 SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_playing(); 272 SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_playing();
271 273
272 - // TODO: FIXME: magic number  
273 - for (int i = 0; i < 100; ++i) {  
274 - char buf[32]; // TODO: FIXME: magic number  
275 - sprintf(buf, "%d", i);  
276 - pkt->data->set(buf, new SrsAmf0String(random_data)); 274 + // TODO: FIXME: magic number
  275 + for (int i = 0; i < data_count; ++i) {
  276 + std::stringstream seq;
  277 + seq << i;
  278 + std::string play_data = "SrS band check data from server's playing......";
  279 + pkt->data->set(seq.str(), new SrsAmf0String(play_data.c_str()));
277 } 280 }
  281 + data_count += 2;
278 282
279 // TODO: FIXME: get length from the rtmp protocol stack. 283 // TODO: FIXME: get length from the rtmp protocol stack.
280 play_bytes += pkt->get_payload_length(); 284 play_bytes += pkt->get_payload_length();
@@ -2789,7 +2789,7 @@ SrsBandwidthPacket* SrsBandwidthPacket::create_start_play() @@ -2789,7 +2789,7 @@ SrsBandwidthPacket* SrsBandwidthPacket::create_start_play()
2789 SrsBandwidthPacket* SrsBandwidthPacket::create_playing() 2789 SrsBandwidthPacket* SrsBandwidthPacket::create_playing()
2790 { 2790 {
2791 SrsBandwidthPacket* pkt = new SrsBandwidthPacket(); 2791 SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
2792 - return pkt->set_command(SRS_BW_CHECK_STARTING_PLAY); 2792 + return pkt->set_command(SRS_BW_CHECK_PLAYING);
2793 } 2793 }
2794 2794
2795 SrsBandwidthPacket* SrsBandwidthPacket::create_stop_play() 2795 SrsBandwidthPacket* SrsBandwidthPacket::create_stop_play()
@@ -414,7 +414,17 @@ int SrsRtmpClient::publish(string stream, int stream_id) @@ -414,7 +414,17 @@ int SrsRtmpClient::publish(string stream, int stream_id)
414 } 414 }
415 } 415 }
416 416
417 - return ret; 417 + return ret;
  418 +}
  419 +
  420 +SrsProtocol *SrsRtmpClient::get_protocol()
  421 +{
  422 + return protocol;
  423 +}
  424 +
  425 +st_netfd_t SrsRtmpClient::get_st_fd()
  426 +{
  427 + return stfd;
418 } 428 }
419 429
420 SrsRtmp::SrsRtmp(st_netfd_t client_stfd) 430 SrsRtmp::SrsRtmp(st_netfd_t client_stfd)
@@ -131,6 +131,10 @@ public: @@ -131,6 +131,10 @@ public:
131 virtual int create_stream(int& stream_id); 131 virtual int create_stream(int& stream_id);
132 virtual int play(std::string stream, int stream_id); 132 virtual int play(std::string stream, int stream_id);
133 virtual int publish(std::string stream, int stream_id); 133 virtual int publish(std::string stream, int stream_id);
  134 +
  135 +protected:
  136 + SrsProtocol* get_protocol();
  137 + st_netfd_t get_st_fd();
134 }; 138 };
135 139
136 /** 140 /**