winlin

support chat room, meeting.

@@ -36,7 +36,7 @@ reload(sys) @@ -36,7 +36,7 @@ reload(sys)
36 exec("sys.setdefaultencoding('utf-8')") 36 exec("sys.setdefaultencoding('utf-8')")
37 assert sys.getdefaultencoding().lower() == "utf-8" 37 assert sys.getdefaultencoding().lower() == "utf-8"
38 38
39 -import json, datetime, cherrypy 39 +import os, json, time, datetime, cherrypy, threading
40 40
41 # simple log functions. 41 # simple log functions.
42 def trace(msg): 42 def trace(msg):
@@ -320,6 +320,123 @@ class RESTSessions(object): @@ -320,6 +320,123 @@ class RESTSessions(object):
320 320
321 return code 321 return code
322 322
  323 +global_chat_id = os.getpid();
  324 +'''
  325 +the chat streams, public chat room.
  326 +'''
  327 +class RESTChats(object):
  328 + exposed = True
  329 + global_id = 100
  330 +
  331 + def __init__(self):
  332 + # object fields:
  333 + # id: an int value indicates the id of user.
  334 + # username: a str indicates the user name.
  335 + # url: a str indicates the url of user stream.
  336 + # agent: a str indicates the agent of user.
  337 + # join_date: a number indicates the join timestamp in seconds.
  338 + # join_date_str: a str specifies the formated friendly time.
  339 + # heatbeat: a number indicates the heartbeat timestamp in seconds.
  340 + # vcodec: a dict indicates the video codec info.
  341 + # acodec: a dict indicates the audio codec info.
  342 + self.__chats = [];
  343 + self.__chat_lock = threading.Lock();
  344 +
  345 + # dead time in seconds, if exceed, remove the chat.
  346 + self.__dead_time = 30;
  347 +
  348 + def GET(self):
  349 + enable_crossdomain()
  350 +
  351 + try:
  352 + self.__chat_lock.acquire();
  353 +
  354 + chats = [];
  355 + copy = self.__chats[:];
  356 + for chat in copy:
  357 + if time.time() - chat["heartbeat"] > self.__dead_time:
  358 + self.__chats.remove(chat);
  359 + continue;
  360 +
  361 + chats.append({
  362 + "id": chat["id"],
  363 + "username": chat["username"],
  364 + "url": chat["url"],
  365 + "join_date_str": chat["join_date_str"],
  366 + "heartbeat": chat["heartbeat"],
  367 + });
  368 + finally:
  369 + self.__chat_lock.release();
  370 +
  371 + return json.dumps({"code":0, "data": {"now": time.time(), "chats": chats}})
  372 +
  373 + def POST(self):
  374 + enable_crossdomain()
  375 +
  376 + req = cherrypy.request.body.read()
  377 + chat = json.loads(req)
  378 +
  379 + global global_chat_id;
  380 + chat["id"] = global_chat_id
  381 + global_chat_id += 1
  382 +
  383 + chat["join_date"] = time.time();
  384 + chat["heartbeat"] = time.time();
  385 + chat["join_date_str"] = time.strftime("%Y-%m-%d %H:%M:%S");
  386 +
  387 + try:
  388 + self.__chat_lock.acquire();
  389 +
  390 + self.__chats.append(chat)
  391 + finally:
  392 + self.__chat_lock.release();
  393 +
  394 + trace("create chat success, id=%s"%(chat["id"]))
  395 +
  396 + return json.dumps({"code":0, "data": chat["id"]})
  397 +
  398 + def DELETE(self, id):
  399 + enable_crossdomain()
  400 +
  401 + try:
  402 + self.__chat_lock.acquire();
  403 +
  404 + for chat in self.__chats:
  405 + if str(id) != str(chat["id"]):
  406 + continue
  407 +
  408 + self.__chats.remove(chat)
  409 + trace("delete chat success, id=%s"%(id))
  410 +
  411 + return json.dumps({"code":0, "data": None})
  412 + finally:
  413 + self.__chat_lock.release();
  414 +
  415 + raise cherrypy.HTTPError(405, "Not allowed.")
  416 +
  417 + def PUT(self, id):
  418 + enable_crossdomain()
  419 +
  420 + try:
  421 + self.__chat_lock.acquire();
  422 +
  423 + for chat in self.__chats:
  424 + if str(id) != str(chat["id"]):
  425 + continue
  426 +
  427 + chat["heartbeat"] = time.time();
  428 + trace("heartbeat chat success, id=%s"%(id))
  429 +
  430 + return json.dumps({"code":0, "data": None})
  431 + finally:
  432 + self.__chat_lock.release();
  433 +
  434 + raise cherrypy.HTTPError(405, "Not allowed.")
  435 +
  436 +
  437 + def OPTIONS(self, id=None):
  438 + enable_crossdomain()
  439 +
323 # HTTP RESTful path. 440 # HTTP RESTful path.
324 class Root(object): 441 class Root(object):
325 def __init__(self): 442 def __init__(self):
@@ -335,6 +452,7 @@ class V1(object): @@ -335,6 +452,7 @@ class V1(object):
335 self.clients = RESTClients() 452 self.clients = RESTClients()
336 self.streams = RESTStreams() 453 self.streams = RESTStreams()
337 self.sessions = RESTSessions() 454 self.sessions = RESTSessions()
  455 + self.chats = RESTChats()
338 456
339 ''' 457 '''
340 main code start. 458 main code start.
@@ -28,6 +28,31 @@ function update_nav() { @@ -28,6 +28,31 @@ function update_nav() {
28 } 28 }
29 29
30 /** 30 /**
  31 +* log specified, there must be a log element as:
  32 + <!-- for the log -->
  33 + <div class="alert alert-info fade in" id="txt_log">
  34 + <button type="button" class="close" data-dismiss="alert">×</button>
  35 + <strong><span id="txt_log_title">Usage:</span></strong>
  36 + <span id="txt_log_msg">创建会议室,或者加入会议室</span>
  37 + </div>
  38 +*/
  39 +function info(desc) {
  40 + $("#txt_log").addClass("alert-info").removeClass("alert-error").removeClass("alert-warn");
  41 + $("#txt_log_title").text("Info:");
  42 + $("#txt_log_msg").text(desc);
  43 +}
  44 +function warn(code, desc) {
  45 + $("#txt_log").removeClass("alert-info").removeClass("alert-error").addClass("alert-warn");
  46 + $("#txt_log_title").text("Warn:");
  47 + $("#txt_log_msg").text("code: " + code + ", " + desc);
  48 +}
  49 +function error(code, desc) {
  50 + $("#txt_log").removeClass("alert-info").addClass("alert-error").removeClass("alert-warn");
  51 + $("#txt_log_title").text("Error:");
  52 + $("#txt_log_msg").text("code: " + code + ", " + desc);
  53 +}
  54 +
  55 +/**
31 * parse the query string to object. 56 * parse the query string to object.
32 */ 57 */
33 function parse_query_string(){ 58 function parse_query_string(){
@@ -83,6 +108,23 @@ function build_default_rtmp_url() { @@ -83,6 +108,23 @@ function build_default_rtmp_url() {
83 return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream; 108 return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
84 } 109 }
85 } 110 }
  111 +// for the chat to init the publish url.
  112 +function build_default_publish_rtmp_url() {
  113 + var query = parse_query_string();
  114 +
  115 + var server = (query.server == undefined)? window.location.hostname:query.server;
  116 + var port = (query.port == undefined)? 1935:query.port;
  117 + var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost;
  118 + var app = (query.app == undefined)? "live":query.app;
  119 + var stream = (query.stream == undefined)? "livestream":query.stream;
  120 +
  121 + if (server == vhost || vhost == "") {
  122 + return "rtmp://" + server + ":" + port + "/" + app + "/" + stream;
  123 + } else {
  124 + vhost = srs_get_player_publish_vhost(vhost);
  125 + return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
  126 + }
  127 +}
86 128
87 /** 129 /**
88 @param server the ip of server. default to window.location.hostname 130 @param server the ip of server. default to window.location.hostname
@@ -162,6 +204,8 @@ function srs_get_player_height() { return srs_get_player_width() * 9 / 19; } @@ -162,6 +204,8 @@ function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
162 function srs_get_version_code() { return "1.5"; } 204 function srs_get_version_code() { return "1.5"; }
163 // get the default vhost for players. 205 // get the default vhost for players.
164 function srs_get_player_vhost() { return "players"; } 206 function srs_get_player_vhost() { return "players"; }
  207 +// the api server port, for chat room.
  208 +function srs_get_api_server_port() { return 8085; }
165 // get the stream published to vhost, 209 // get the stream published to vhost,
166 // generally we need to transcode the stream to support HLS and filters. 210 // generally we need to transcode the stream to support HLS and filters.
167 // for example, src_vhost is "players", we transcode stream to vhost "players_pub". 211 // for example, src_vhost is "players", we transcode stream to vhost "players_pub".
@@ -188,6 +232,112 @@ function srs_init(rtmp_url, hls_url, modal_player) { @@ -188,6 +232,112 @@ function srs_init(rtmp_url, hls_url, modal_player) {
188 $(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px"); 232 $(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px");
189 } 233 }
190 } 234 }
  235 +// for the chat to init the publish url.
  236 +function srs_init_publish(rtmp_url) {
  237 + update_nav();
  238 +
  239 + if (rtmp_url) {
  240 + $(rtmp_url).val(build_default_publish_rtmp_url());
  241 + }
  242 +}
  243 +
  244 +/**
  245 +* when publisher ready, init the page elements.
  246 +*/
  247 +function srs_publisher_initialize_page(
  248 + cameras, microphones,
  249 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  250 +) {
  251 + $(sl_cameras).empty();
  252 + for (var i = 0; i < cameras.length; i++) {
  253 + $(sl_cameras).append("<option value='" + i + "'>" + cameras[i] + "</option");
  254 + }
  255 + // optional: select the first no "virtual" signed.
  256 + for (var i = 0; i < cameras.length; i++) {
  257 + if (cameras[i].toLowerCase().indexOf("virtual") == -1) {
  258 + $(sl_cameras + " option[value='" + i + "']").attr("selected", true);
  259 + break;
  260 + }
  261 + }
  262 +
  263 + $(sl_microphones).empty();
  264 + for (var i = 0; i < microphones.length; i++) {
  265 + $(sl_microphones).append("<option value='" + i + "'>" + microphones[i] + "</option");
  266 + }
  267 +
  268 + $(sl_vcodec).empty();
  269 + var vcodecs = ["h264", "vp6"];
  270 + for (var i = 0; i < vcodecs.length; i++) {
  271 + $(sl_vcodec).append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");
  272 + }
  273 +
  274 + $(sl_profile).empty();
  275 + var profiles = ["baseline", "main"];
  276 + for (var i = 0; i < profiles.length; i++) {
  277 + $(sl_profile).append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");
  278 + }
  279 +
  280 + $(sl_level).empty();
  281 + var levels = ["1", "1b", "1.1", "1.2", "1.3",
  282 + "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
  283 + for (var i = 0; i < levels.length; i++) {
  284 + $(sl_level).append("<option value='" + levels[i] + "'>" + levels[i] + "</option");
  285 + }
  286 + $(sl_level + " option[value='4.1']").attr("selected", true);
  287 +
  288 + $(sl_gop).empty();
  289 + var gops = ["0.3", "0.5", "1", "2", "3", "4",
  290 + "5", "6", "7", "8", "9", "10", "15", "20"];
  291 + for (var i = 0; i < gops.length; i++) {
  292 + $(sl_gop).append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");
  293 + }
  294 + $(sl_gop + " option[value='5']").attr("selected", true);
  295 +
  296 + $(sl_size).empty();
  297 + var sizes = ["176x144", "320x240", "352x240",
  298 + "352x288", "460x240", "640x480", "720x480", "720x576", "800x600",
  299 + "1024x768", "1280x720", "1360x768", "1920x1080"];
  300 + for (i = 0; i < sizes.length; i++) {
  301 + $(sl_size).append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");
  302 + }
  303 + $(sl_size + " option[value='460x240']").attr("selected", true);
  304 +
  305 + $(sl_fps).empty();
  306 + var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
  307 + for (i = 0; i < fpses.length; i++) {
  308 + $(sl_fps).append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");
  309 + }
  310 + $(sl_fps + " option[value='15']").attr("selected", true);
  311 +
  312 + $(sl_bitrate).empty();
  313 + var bitrates = ["50", "200", "350", "500", "650", "800",
  314 + "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
  315 + for (i = 0; i < bitrates.length; i++) {
  316 + $(sl_bitrate).append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");
  317 + }
  318 + $(sl_bitrate + " option[value='350']").attr("selected", true);
  319 +}
  320 +/**
  321 +* get the vcodec and acodec.
  322 +*/
  323 +function srs_publiser_get_codec(
  324 + vcodec, acodec,
  325 + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
  326 +) {
  327 + acodec.device_code = $(sl_microphones).val();
  328 + acodec.device_name = $(sl_microphones).text();
  329 +
  330 + vcodec.device_code = $(sl_cameras).find("option:selected").val();
  331 + vcodec.device_name = $(sl_cameras).find("option:selected").text();
  332 +
  333 + vcodec.codec = $(sl_vcodec).find("option:selected").val();
  334 + vcodec.profile = $(sl_profile).find("option:selected").val();
  335 + vcodec.level = $(sl_level).find("option:selected").val();
  336 + vcodec.fps = $(sl_fps).find("option:selected").val();
  337 + vcodec.gop = $(sl_gop).find("option:selected").val();
  338 + vcodec.size = $(sl_size).find("option:selected").val();
  339 + vcodec.bitrate = $(sl_bitrate).find("option:selected").val();
  340 +}
191 341
192 ////////////////////////////////////////////////////////////////////////////////// 342 //////////////////////////////////////////////////////////////////////////////////
193 ////////////////////////////////////////////////////////////////////////////////// 343 //////////////////////////////////////////////////////////////////////////////////
@@ -223,11 +373,16 @@ function SrsPlayer(container, width, height) { @@ -223,11 +373,16 @@ function SrsPlayer(container, width, height) {
223 } 373 }
224 /** 374 /**
225 * user can set some callback, then start the player. 375 * user can set some callback, then start the player.
  376 +* @param url the default url.
226 * callbacks: 377 * callbacks:
227 * on_player_ready():int, when srs player ready, user can play. 378 * on_player_ready():int, when srs player ready, user can play.
228 * on_player_metadata(metadata:Object):int, when srs player get metadata. 379 * on_player_metadata(metadata:Object):int, when srs player get metadata.
229 */ 380 */
230 -SrsPlayer.prototype.start = function() { 381 +SrsPlayer.prototype.start = function(url) {
  382 + if (url) {
  383 + this.stream_url = url;
  384 + }
  385 +
231 // embed the flash. 386 // embed the flash.
232 var flashvars = {}; 387 var flashvars = {};
233 flashvars.id = this.id; 388 flashvars.id = this.id;
@@ -262,7 +417,9 @@ SrsPlayer.prototype.start = function() { @@ -262,7 +417,9 @@ SrsPlayer.prototype.start = function() {
262 * @param stream_url the url of stream, rtmp or http. 417 * @param stream_url the url of stream, rtmp or http.
263 */ 418 */
264 SrsPlayer.prototype.play = function(url) { 419 SrsPlayer.prototype.play = function(url) {
  420 + if (url) {
265 this.stream_url = url; 421 this.stream_url = url;
  422 + }
266 this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time); 423 this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time);
267 } 424 }
268 SrsPlayer.prototype.stop = function() { 425 SrsPlayer.prototype.stop = function() {
@@ -15,24 +15,32 @@ @@ -15,24 +15,32 @@
15 </style> 15 </style>
16 <script type="text/javascript"> 16 <script type="text/javascript">
17 var srs_publisher = null; 17 var srs_publisher = null;
18 - var remote_player = null;  
19 var realtime_player = null; 18 var realtime_player = null;
20 - var wizard = null; 19 + var api_server = null;
  20 + var self_chat = null;
  21 + var global_chat_user_id = 200;
  22 + var previous_chats = [];
  23 + var no_play = false;
21 24
22 $(function(){ 25 $(function(){
23 // get the vhost and port to set the default url. 26 // get the vhost and port to set the default url.
24 // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo 27 // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo
25 // url set to: rtmp://demo:1935/live/livestream 28 // url set to: rtmp://demo:1935/live/livestream
26 - srs_init("#txt_url", null, null); 29 + srs_init_publish("#txt_url");
27 30
28 - $("#btn_create_chat").click(function(){ 31 + // if no play specified, donot show the player, for debug the publisher.
  32 + var query = parse_query_string();
  33 + if (query.no_play == "true") {
  34 + no_play = true;
  35 + }
  36 +
  37 + $("#btn_video_settings").click(function(){
29 $("#video_modal").modal({show:true}); 38 $("#video_modal").modal({show:true});
30 }); 39 });
31 - $("#btn_join_chat").click(function(){ 40 + $("#btn_audio_settings").click(function(){
32 $("#audio_modal").modal({show:true}); 41 $("#audio_modal").modal({show:true});
33 }); 42 });
34 -  
35 - $("#btn_publish").click(on_user_publish); 43 + $("#btn_join").click(on_user_publish);
36 44
37 // for publish, we use randome stream name. 45 // for publish, we use randome stream name.
38 $("#txt_url").val($("#txt_url").val() + "." + new Date().getTime()); 46 $("#txt_url").val($("#txt_url").val() + "." + new Date().getTime());
@@ -40,163 +48,353 @@ @@ -40,163 +48,353 @@
40 // start the publisher. 48 // start the publisher.
41 srs_publisher = new SrsPublisher("local_publisher", 430, 185); 49 srs_publisher = new SrsPublisher("local_publisher", 430, 185);
42 srs_publisher.on_publisher_ready = function(cameras, microphones) { 50 srs_publisher.on_publisher_ready = function(cameras, microphones) {
43 - $("#sl_cameras").empty();  
44 - for (var i = 0; i < cameras.length; i++) {  
45 - $("#sl_cameras").append("<option value='" + i + "'>" + cameras[i] + "</option");  
46 - }  
47 - // optional: select the first no "virtual" signed.  
48 - for (var i = 0; i < cameras.length; i++) {  
49 - if (cameras[i].toLowerCase().indexOf("virtual") == -1) {  
50 - $("#sl_cameras option[value='" + i + "']").attr("selected", true);  
51 - break;  
52 - }  
53 - } 51 + srs_publisher_initialize_page(
  52 + cameras, microphones,
  53 + "#sl_cameras", "#sl_microphones",
  54 + "#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
  55 + "#sl_fps", "#sl_bitrate"
  56 + );
  57 + };
  58 + srs_publisher.on_publisher_error = function(code, desc) {
  59 + error(code, desc);
  60 + };
  61 + srs_publisher.on_publisher_warn = function(code, desc) {
  62 + warn(code, desc);
  63 + };
  64 + srs_publisher.start();
54 65
55 - $("#sl_microphones").empty();  
56 - for (var i = 0; i < microphones.length; i++) {  
57 - $("#sl_microphones").append("<option value='" + i + "'>" + microphones[i] + "</option"); 66 + if (!no_play) {
  67 + // start the realtime player.
  68 + realtime_player = new SrsPlayer("realtime_player", 430, 185);
  69 + realtime_player.on_player_ready = function() {
  70 + realtime_player.set_bt(0.8);
  71 + realtime_player.set_fs("screen", 100);
  72 + };
  73 + realtime_player.start();
58 } 74 }
59 75
60 - $("#sl_vcodec").empty();  
61 - var vcodecs = ["h264", "vp6"];  
62 - for (var i = 0; i < vcodecs.length; i++) {  
63 - $("#sl_vcodec").append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");  
64 - } 76 + api_server = "http://" + query.hostname + ":" + srs_get_api_server_port() + "/api/v1/chats";
  77 + refresh();
  78 + });
65 79
66 - $("#sl_profile").empty();  
67 - var profiles = ["baseline", "main"];  
68 - for (var i = 0; i < profiles.length; i++) {  
69 - $("#sl_profile").append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option"); 80 + function refresh() {
  81 + if (!self_chat) {
  82 + setTimeout(refresh, 1000);
  83 + return;
70 } 84 }
71 85
72 - $("#sl_level").empty();  
73 - var levels = ["1", "1b", "1.1", "1.2", "1.3",  
74 - "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];  
75 - for (var i = 0; i < levels.length; i++) {  
76 - $("#sl_level").append("<option value='" + levels[i] + "'>" + levels[i] + "</option"); 86 + $.ajax({
  87 + type : "GET",
  88 + async : true,
  89 + url : api_server,
  90 + contentType: "text/html",
  91 + data : "",
  92 + dataType : "json",
  93 + complete : function() {
  94 + },
  95 + error : function(ret) {
  96 + setTimeout(refresh, 5000);
  97 + warn(101, "查询会议室失败:" + JSON.stringify(ret));
  98 + },
  99 + success : function(ret) {
  100 + if(0 != ret["code"]) {
  101 + warn(102, "查询会议室失败: " + JSON.stringify(ret));
  102 + setTimeout(refresh, 5000);
  103 + return;
77 } 104 }
78 - $("#sl_level option[value='4.1']").attr("selected", true);  
79 105
80 - $("#sl_gop").empty();  
81 - var gops = ["0.3", "0.5", "1", "2", "3", "4",  
82 - "5", "6", "7", "8", "9", "10", "15", "20"];  
83 - for (var i = 0; i < gops.length; i++) {  
84 - $("#sl_gop").append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option"); 106 + var chats = ret["data"]["chats"];
  107 + var server_time = ret["now"];
  108 +
  109 + on_get_chats(chats);
  110 + setTimeout(refresh, 5000);
  111 + }
  112 + });
  113 + }
  114 + function on_get_chats(chats) {
  115 + if (!self_chat) {
  116 + return;
85 } 117 }
86 - $("#sl_gop option[value='5']").attr("selected", true);  
87 118
88 - $("#sl_size").empty();  
89 - var sizes = ["176x144", "320x240", "352x240",  
90 - "352x288", "460x240", "640x480", "720x480", "720x576", "800x600",  
91 - "1024x768", "1280x720", "1360x768", "1920x1080"];  
92 - for (i = 0; i < sizes.length; i++) {  
93 - $("#sl_size").append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option"); 119 + // get self, check self is valid?
  120 + var _self_chat = null;
  121 + for (var i = 0; i < chats.length; i++) {
  122 + var chat = chats[i];
  123 + if (self_chat && self_chat.id == chat.id) {
  124 + _self_chat = chat;
  125 + break;
  126 + }
  127 + }
  128 + // rejoin if invalid.
  129 + if (!_self_chat) {
  130 + on_user_exit_chat(function(){
  131 + on_user_join_chat(function(){
  132 + info("重新加入会议室成功");
  133 + });
  134 + });
  135 + return;
94 } 136 }
95 - $("#sl_size option[value='460x240']").attr("selected", true);  
96 137
97 - $("#sl_fps").empty();  
98 - var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];  
99 - for (i = 0; i < fpses.length; i++) {  
100 - $("#sl_fps").append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option"); 138 + render_chat_room(chats);
  139 +
  140 + if (!self_chat) {
  141 + return;
101 } 142 }
102 - $("#sl_fps option[value='15']").attr("selected", true);  
103 143
104 - $("#sl_bitrate").empty();  
105 - var bitrates = ["50", "200", "350", "500", "650", "800",  
106 - "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];  
107 - for (i = 0; i < bitrates.length; i++) {  
108 - $("#sl_bitrate").append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option"); 144 + // update self heartbeat.
  145 + var url = api_server + "/" + self_chat.id;
  146 +
  147 + var chat = {};
  148 + chat.localtime = new Date().getTime();
  149 +
  150 + // publish to api server to requires an id.
  151 + $.ajax({
  152 + type : "PUT",
  153 + async : true,
  154 + url : url,
  155 + contentType: "text/html",
  156 + data : JSON.stringify(chat),
  157 + dataType : "json",
  158 + complete : function() {
  159 + },
  160 + error : function(ret) {
  161 + warn(105, "更新会议室信息失败:" + JSON.stringify(ret));
  162 + },
  163 + success : function(ret) {
  164 + if(0 != ret["code"]) {
  165 + warn(106, "更新会议室信息失败:: " + JSON.stringify(ret));
  166 + return;
  167 + }
  168 + }
  169 + });
  170 + }
  171 + function render_chat_room(chats) {
  172 + if (!self_chat) {
  173 + return;
109 } 174 }
110 - $("#sl_bitrate option[value='350']").attr("selected", true);  
111 - };  
112 - srs_publisher.on_publisher_error = function(code, desc) {  
113 - error(code, desc);  
114 - };  
115 - srs_publisher.on_publisher_warn = function(code, desc) {  
116 - warn(code, desc);  
117 - };  
118 - srs_publisher.start();  
119 175
120 - //wizard = $("#some-wizard").wizard({});  
121 - //wizard.show(); 176 + // new added chat
  177 + for (var i = 0; i < chats.length; i++) {
  178 + var chat = chats[i];
  179 + // ignore the self.
  180 + if (self_chat && self_chat.id == chat.id) {
  181 + continue;
  182 + }
122 183
123 - // if no play specified, donot show the player, for debug the publisher.  
124 - var query = parse_query_string();  
125 - if (query.no_play != "true") {  
126 - // start the normal player with HLS supported.  
127 - remote_player = new SrsPlayer("remote_player", 430, 185);  
128 - remote_player.on_player_ready = function() {  
129 - remote_player.set_bt(0.8);  
130 - remote_player.set_fs("screen", 100);  
131 - };  
132 - remote_player.start(); 184 + // if previous exists, ignore, only add new here.
  185 + var previous_chat = get_previous_chat_user(previous_chats, chat.id);
  186 + if (previous_chat) {
  187 + chat.player = previous_chat.player;
  188 + continue;
  189 + }
133 190
  191 + global_chat_user_id++;
  192 +
  193 + // username: a str indicates the user name.
  194 + // url: a str indicates the url of user stream.
  195 + // join_date: a str indicates the join timestamp in seconds.
  196 + // join_date_str: friendly formated time.
  197 + var obj = $("<div/>").html($("#template").html());
  198 + $(obj).attr("chat_id", chat.id);
  199 + $(obj).attr("id", "div_" + chat.id); // for specifed chat: $("#div_" + chat_id)
  200 + $(obj).attr("class", "div_chat"); // for all chats: $(".div_chat")
  201 + $(obj).find("#realtime_player").attr("id", "rp_" + chat.id); // for specifed player: $("#rp_" + chat_id)
  202 + $(obj).find("#realtime_player_raw").attr("id", "rp_raw_" + chat.id); // for specifed player: $("#rp_raw_" + chat_id)
  203 + $(obj).find("#user_name").text(chat.username);
  204 + $(obj).find("#join_date").text(chat.join_date_str);
  205 + $(obj).find("#collapseM").attr("id", "collapse_" + global_chat_user_id);
  206 + $(obj).find("#headerN").attr("href", "#collapse_" + global_chat_user_id);
  207 +
  208 + $("#lst_chats").append(obj);
  209 +
  210 + if (!no_play) {
134 // start the realtime player. 211 // start the realtime player.
135 - realtime_player = new SrsPlayer("realtime_player", 430, 185);  
136 - realtime_player.on_player_ready = function() {  
137 - realtime_player.set_bt(0.8);  
138 - realtime_player.set_fs("screen", 100); 212 + var _player = new SrsPlayer("rp_raw_" + chat.id, 600, 300);
  213 + _player.on_player_ready = function() {
  214 + this.set_bt(0.8);
  215 + this.set_fs("screen", 100);
139 }; 216 };
140 - realtime_player.start(); 217 + _player.start(chat.url);
  218 +
  219 + chat.player = _player;
  220 + } else {
  221 + chat.player = null;
  222 + }
  223 +
  224 + $(obj).find("#collapse_main").on('hidden', function(){
  225 + var id = $(this).parent().attr("chat_id");
  226 + var chat = get_previous_chat_user(previous_chats, id);
  227 + if (!chat || !chat.player) {
  228 + return;
141 } 229 }
  230 + chat.player.stop();
142 }); 231 });
  232 + $(obj).find("#collapse_main").on('shown', function(){
  233 + var id = $(this).parent().attr("chat_id");
  234 + var chat = get_previous_chat_user(previous_chats, id);
  235 + if (!chat || !chat.player) {
  236 + return;
  237 + }
  238 + chat.player.play();
  239 + });
  240 + }
143 241
144 - function update_play_url() {  
145 - var url = $("#txt_url").val();  
146 - var ret = srs_parse_rtmp_url(url);  
147 - var query = parse_query_string(); 242 + // removed chat
  243 + for (var i = 0; i < previous_chats.length; i++) {
  244 + var chat = previous_chats[i];
  245 + // ignore the self.
  246 + if (self_chat && self_chat.id == chat.id) {
  247 + continue;
  248 + }
  249 +
  250 + var new_chat = get_previous_chat_user(chats, chat.id);
  251 + if (new_chat) {
  252 + continue;
  253 + }
148 254
149 - var srs_player_url = "http://" + query.host + query.dir + "/srs_player.html?";  
150 - srs_player_url += "vhost=" + srs_get_player_publish_vhost(ret.vhost) + "&port=" + ret.port + "&app=" + ret.app + "&stream=" + ret.stream;  
151 - srs_player_url += "&autostart=true"; 255 + if (chat.player) {
  256 + chat.player.stop();
  257 + }
  258 + $("#div_" + chat.id).remove();
  259 + }
152 260
153 - var srs_player_rt_url = "http://" + query.host + query.dir + "/srs_player.html?";  
154 - srs_player_rt_url += "vhost=" + ret.vhost + "&port=" + ret.port + "&app=" + ret.app + "&stream=" + ret.stream;  
155 - srs_player_rt_url += "&autostart=true"; 261 + previous_chats = chats;
  262 + }
  263 + function get_previous_chat_user(arr, id) {
  264 + for (var i = 0; i < arr.length; i++) {
  265 + var chat = arr[i];
  266 + if (id == chat.id) {
  267 + return chat;
  268 + }
  269 + }
  270 + return null;
  271 + }
156 272
157 - var jwplayer_url = "http://" + query.host + query.dir + "/jwplayer6.html?";  
158 - jwplayer_url += "vhost=" + srs_get_player_publish_vhost(ret.vhost) + "&port=" + ret.port + "&app=" + ret.app + "&stream=" + ret.stream;  
159 - jwplayer_url += "&hls_autostart=true"; 273 + function on_user_publish() {
  274 + if ($("#txt_name").val().trim() == "") {
  275 + $("#txt_name").focus().parent().parent().addClass("error");
  276 + warn(100, "请输入您的名字");
  277 + return;
  278 + }
160 279
161 - var hls_url = "http://" + ret.server + ":" + query.http_port + "/" + ret.app + "/" + ret.stream + ".m3u8"; 280 + $("#txt_name").parent().parent().removeClass("error");
162 281
163 - $("#txt_play_realtime").text("RTMP低延时(点击打开)").attr("href", srs_player_rt_url).attr("target", "_blank");  
164 - $("#txt_play_url").text("RTMP已转码(点击打开)").attr("href", srs_player_url).attr("target", "_blank");  
165 - $("#txt_play_hls").text("HLS-m3u8(点击打开或右键复制)").attr("href", hls_url).attr("target", "_blank");  
166 - $("#txt_play_jwplayer").text("HLS-JWPlayer(点击打开)").attr("href", jwplayer_url).attr("target", "_blank"); 282 + // join chat.
  283 + if (!self_chat) {
  284 + on_user_join_chat();
  285 + } else {
  286 + on_user_exit_chat();
167 } 287 }
168 - function on_user_publish() {  
169 - if ($("#btn_publish").text() == "停止发布") { 288 + }
  289 + function on_user_exit_chat(complete_pfn) {
170 srs_publisher.stop(); 290 srs_publisher.stop();
171 - $("#btn_publish").text("发布视频");  
172 - $("#txt_play_realtime").text("RTMP低延时(请发布视频)").attr("href", "#").attr("target", "_self");  
173 - $("#txt_play_url").text("RTMP已转码(请发布视频)").attr("href", "#").attr("target", "_self");  
174 - $("#txt_play_hls").text("HLS-m3u8(请发布视频)").attr("href", "#").attr("target", "_self");  
175 - $("#txt_play_jwplayer").text("HLS-JWPlayer(请发布视频)").attr("href", "#").attr("target", "_self"); 291 + $("#btn_join").text("加入会议");
  292 +
  293 + if (!self_chat) {
176 return; 294 return;
177 } 295 }
178 296
179 - $("#btn_publish").text("停止发布"); 297 + // removed chat
  298 + for (var i = 0; i < previous_chats.length; i++) {
  299 + var chat = previous_chats[i];
  300 + // ignore the self.
  301 + if (self_chat && self_chat.id == chat.id) {
  302 + continue;
  303 + }
180 304
181 - update_play_url(); 305 + if (chat.player) {
  306 + chat.player.stop();
  307 + }
  308 + $("#div_" + chat.id).remove();
  309 + }
  310 + previous_chats = [];
  311 +
  312 + var url = api_server + "/" + self_chat.id;
  313 + // whatever, cleanup local chat.
  314 + self_chat = null;
  315 +
  316 + $("#btn_join").attr("disabled", true);
  317 +
  318 + // publish to api server to requires an id.
  319 + $.ajax({
  320 + type : "DELETE",
  321 + async : true,
  322 + url : url,
  323 + contentType: "text/html",
  324 + data : "",
  325 + dataType : "json",
  326 + complete : function() {
  327 + $("#btn_join").attr("disabled", false);
  328 + if (complete_pfn) {
  329 + complete_pfn();
  330 + }
  331 + },
  332 + error : function(ret) {
  333 + warn(103, "退出会议室失败");
  334 + },
  335 + success : function(ret) {
  336 + if(0 != ret["code"]) {
  337 + warn(104, "退出会议室失败");
  338 + return;
  339 + }
  340 + info("退出会议室成功");
  341 + }
  342 + });
  343 + }
  344 + function on_user_join_chat(complete_pfn) {
  345 + if (self_chat) {
  346 + return;
  347 + }
182 348
183 var url = $("#txt_url").val(); 349 var url = $("#txt_url").val();
184 var vcodec = {}; 350 var vcodec = {};
185 var acodec = {}; 351 var acodec = {};
  352 + srs_publiser_get_codec(
  353 + vcodec, acodec,
  354 + "#sl_cameras", "#sl_microphones",
  355 + "#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
  356 + "#sl_fps", "#sl_bitrate"
  357 + );
  358 +
  359 + var chat = {};
  360 + chat.id = -1;
  361 + chat.username = $("#txt_name").val().trim();
  362 + chat.agent = navigator.userAgent;
  363 + chat.url = url;
  364 + chat.vcodec = vcodec;
  365 + chat.acodec = acodec;
  366 +
  367 + $("#btn_join").attr("disabled", true);
  368 +
  369 + // publish to api server to requires an id.
  370 + $.ajax({
  371 + type : "POST",
  372 + async : true,
  373 + url : api_server,
  374 + contentType: "text/html",
  375 + data : JSON.stringify(chat),
  376 + dataType : "json",
  377 + complete : function() {
  378 + $("#btn_join").attr("disabled", false);
  379 + if (complete_pfn) {
  380 + complete_pfn();
  381 + }
  382 + },
  383 + error : function(ret) {
  384 + warn(105, "创建会议室失败:" + JSON.stringify(ret));
  385 + },
  386 + success : function(ret) {
  387 + if(0 != ret["code"]) {
  388 + warn(106, "创建会议室失败: " + JSON.stringify(ret));
  389 + return;
  390 + }
186 391
187 - acodec.device_code = $("#sl_microphones").val();  
188 - acodec.device_name = $("#sl_microphones").text(); 392 + chat.id = ret["data"];
189 393
190 - vcodec.device_code = $("#sl_cameras").find("option:selected").val();  
191 - vcodec.device_name = $("#sl_cameras").find("option:selected").text(); 394 + // success, start publish.
  395 + self_chat = chat;
192 396
193 - vcodec.codec = $("#sl_vcodec").find("option:selected").val();  
194 - vcodec.profile = $("#sl_profile").find("option:selected").val();  
195 - vcodec.level = $("#sl_level").find("option:selected").val();  
196 - vcodec.fps = $("#sl_fps").find("option:selected").val();  
197 - vcodec.gop = $("#sl_gop").find("option:selected").val();  
198 - vcodec.size = $("#sl_size").find("option:selected").val();  
199 - vcodec.bitrate = $("#sl_bitrate").find("option:selected").val(); 397 + $("#btn_join").text("退出会议");
200 398
201 info("开始推流到服务器"); 399 info("开始推流到服务器");
202 srs_publisher.publish(url, vcodec, acodec); 400 srs_publisher.publish(url, vcodec, acodec);
@@ -206,34 +404,8 @@ @@ -206,34 +404,8 @@
206 realtime_player.stop(); 404 realtime_player.stop();
207 realtime_player.play(url); 405 realtime_player.play(url);
208 } 406 }
209 -  
210 - if (remote_player) {  
211 - // the normal player should play the transcoded stream in another vhost.  
212 - // for example, publish stream to vhost players,  
213 - // the realtime player play the vhost players, which may donot support HLS,  
214 - // the normal player play the vhost players_pub, which transcoded to h264/aac with HLS.  
215 - var ret = srs_parse_rtmp_url(url);  
216 - var pub_url = "rtmp://" + ret.server + ":" + ret.port + "/" + ret.app;  
217 - pub_url += "?vhost=" + srs_get_player_publish_vhost(ret.vhost) + "/" + ret.stream;  
218 - remote_player.stop();  
219 - remote_player.play(pub_url);  
220 - }  
221 } 407 }
222 -  
223 - function info(desc) {  
224 - $("#txt_log").addClass("alert-info").removeClass("alert-error").removeClass("alert-warn");  
225 - $("#txt_log_title").text("Info:");  
226 - $("#txt_log_msg").text(desc);  
227 - }  
228 - function warn(code, desc) {  
229 - $("#txt_log").removeClass("alert-info").removeClass("alert-error").addClass("alert-warn");  
230 - $("#txt_log_title").text("Warn:");  
231 - $("#txt_log_msg").text("code: " + code + ", " + desc);  
232 - }  
233 - function error(code, desc) {  
234 - $("#txt_log").removeClass("alert-info").addClass("alert-error").removeClass("alert-warn");  
235 - $("#txt_log_title").text("Error:");  
236 - $("#txt_log_msg").text("code: " + code + ", " + desc); 408 + });
237 } 409 }
238 </script> 410 </script>
239 </head> 411 </head>
@@ -257,33 +429,83 @@ @@ -257,33 +429,83 @@
257 </div> 429 </div>
258 </div> 430 </div>
259 <div class="container"> 431 <div class="container">
  432 + <!-- for the log -->
260 <div class="alert alert-info fade in" id="txt_log"> 433 <div class="alert alert-info fade in" id="txt_log">
261 <button type="button" class="close" data-dismiss="alert">×</button> 434 <button type="button" class="close" data-dismiss="alert">×</button>
262 <strong><span id="txt_log_title">Usage:</span></strong> 435 <strong><span id="txt_log_title">Usage:</span></strong>
263 - <span id="txt_log_msg">创建会议室,或者加入会议室</span> 436 + <span id="txt_log_msg">输入名字,设置编码参数后,加入会议室</span>
264 </div> 437 </div>
  438 +
265 <div class="control-group"> 439 <div class="control-group">
266 <div class="form-inline"> 440 <div class="form-inline">
267 - <button class="btn" id="btn_create_chat">创建会议室</button>  
268 - <button class="btn" id="btn_join_chat">加入会议室</button> 441 + <input type="text" id="txt_name" class="input-small" placeholder="您的名字..." value=""></input>
  442 + <button class="btn input-medium" id="btn_video_settings">视频编码配置</button>
  443 + <button class="btn input-medium" id="btn_audio_settings">音频编码配置</button>
  444 + <button class="btn input-medium btn-primary" id="btn_join">加入会议</button>
  445 + <input type="text" id="txt_url" class="input-mini hide" value=""></input>
269 </div> 446 </div>
270 </div> 447 </div>
271 - <div class="control-group">  
272 - <div class="form-inline">  
273 - 发布地址:  
274 - <input type="text" id="txt_url" class="input-xxlarge" value=""></input>  
275 - <button class="btn btn-primary" id="btn_publish">发布视频</button> 448 + <div class="container">
  449 + <div class="row-fluid">
  450 + <div class="span6">
  451 + <div class="accordion-group">
  452 + <div class="accordion-heading">
  453 + <span class="accordion-toggle" data-toggle="collapse" href="#collapseN">
  454 + <strong>[我的] 本地摄像头</strong>
  455 + </span>
  456 + </div>
  457 + <div id="collapseM" class="accordion-body collapse in">
  458 + <div class="accordion-inner">
  459 + <div id="local_publisher"></div>
276 </div> 460 </div>
277 </div> 461 </div>
278 - <div class="control-group">  
279 - <div class="form-inline">  
280 - 播放地址  
281 - 1.<a id="txt_play_realtime" class="input-xxlarge" href="#">RTMP低延时(请发布视频)</a>  
282 - 2.<a id="txt_play_url" class="input-xxlarge" href="#">RTMP已转码(请发布视频)</a>  
283 - 3.<a id="txt_play_hls" class="input-xxlarge" href="#">HLS-m3u8(请发布视频)</a>  
284 - 4.<a id="txt_play_jwplayer" class="input-xxlarge" href="#">HLS-JWPlayer(请发布视频)</a>  
285 </div> 462 </div>
286 </div> 463 </div>
  464 + <div class="span6">
  465 + <div class="accordion-group">
  466 + <div class="accordion-heading">
  467 + <span class="accordion-toggle" data-toggle="collapse" href="#collapseN">
  468 + <strong>[我的] 远程服务器流</strong>
  469 + </span>
  470 + </div>
  471 + <div id="collapseM" class="accordion-body collapse in">
  472 + <div class="accordion-inner">
  473 + <div id="realtime_player"></div>
  474 + </div>
  475 + </div>
  476 + </div>
  477 + </div>
  478 + </div>
  479 + </div>
  480 + <div class="container hide" id="template">
  481 + <div class="accordion-group" id="collapse_main">
  482 + <div class="accordion-heading" title="点击展开或收起,收起后停止播放流,展开时从服务器请求流">
  483 + <span id="headerN" class="accordion-toggle" data-toggle="collapse" href="#collapseN">
  484 + <strong>[<a href="#"><span id="user_name">XX</span></a>]</strong>
  485 + <strong>加入时间</strong>[<span id="join_date"></span>]
  486 + <img src="img/tooltip.png"/>
  487 + </span>
  488 + </div>
  489 + <div id="collapseM" class="accordion-body collapse">
  490 + <div class="accordion-inner">
  491 + <div class="row-fluid">
  492 + <div class="span2">
  493 + </div>
  494 + <div class="span8">
  495 + <div id="realtime_player">
  496 + <div id="realtime_player_raw">
  497 + </div>
  498 + </div>
  499 + </div>
  500 + <div class="span2">
  501 + </div>
  502 + </div>
  503 + </div>
  504 + </div>
  505 + </div>
  506 + </div>
  507 + <div class="container" id="lst_chats">
  508 + </div>
287 <div id="video_modal" class="modal hide fade"> 509 <div id="video_modal" class="modal hide fade">
288 <div class="modal-header"> 510 <div class="modal-header">
289 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> 511 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
@@ -409,64 +631,7 @@ @@ -409,64 +631,7 @@
409 <button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">设置</button> 631 <button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">设置</button>
410 </div> 632 </div>
411 </div> 633 </div>
412 - <div class="container">  
413 - <div class="row-fluid">  
414 - <div class="span6">  
415 - <div class="accordion-group">  
416 - <div class="accordion-heading">  
417 - <span class="accordion-toggle" data-toggle="collapse" href="#collapse1">  
418 - <strong>本地摄像头</strong>  
419 - </span>  
420 - </div>  
421 - <div id="collapse1" class="accordion-body collapse in">  
422 - <div class="accordion-inner">  
423 - <div id="local_publisher"></div>  
424 - </div>  
425 - </div>  
426 - </div>  
427 - </div>  
428 - <div class="span6">  
429 - <div class="accordion-group">  
430 - <div class="accordion-heading">  
431 - <span class="accordion-toggle" data-toggle="collapse" href="#collapse2">  
432 - <strong>远程服务器</strong>  
433 - <a id="remote_tips" href="#" data-toggle="tooltip" data-placement="top" title="">  
434 - 黑屏<img src="img/tooltip.png"/>  
435 - </a>  
436 - </span>  
437 - </div>  
438 - <div id="collapse2" class="accordion-body collapse in">  
439 - <div class="accordion-inner">  
440 - <div id="remote_player"></div>  
441 - </div>  
442 - </div>  
443 - </div>  
444 - </div>  
445 - </div>  
446 - </div>  
447 - <div class="container">  
448 - <div class="row-fluid">  
449 - <div class="span6">  
450 - <div class="accordion-group">  
451 - <div class="accordion-heading">  
452 - <span class="accordion-toggle" data-toggle="collapse" href="#collapse3">  
453 - <strong>远程服务器</strong>  
454 - <a id="low_latecy_tips" href="#" data-toggle="tooltip" data-placement="top" title="">  
455 - 低延时<img src="img/tooltip.png"/>  
456 - </a>  
457 - </span>  
458 - </div>  
459 - <div id="collapse3" class="accordion-body collapse in">  
460 - <div class="accordion-inner">  
461 - <div id="realtime_player"></div>  
462 - </div>  
463 - </div>  
464 - </div>  
465 - </div>  
466 - <div class="span6">  
467 - </div>  
468 - </div>  
469 - </div> 634 + <hr/>
470 <footer> 635 <footer>
471 <p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team &copy; 2013</a></p> 636 <p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team &copy; 2013</a></p>
472 </footer> 637 </footer>
@@ -46,74 +46,12 @@ @@ -46,74 +46,12 @@
46 // start the publisher. 46 // start the publisher.
47 srs_publisher = new SrsPublisher("local_publisher", 430, 185); 47 srs_publisher = new SrsPublisher("local_publisher", 430, 185);
48 srs_publisher.on_publisher_ready = function(cameras, microphones) { 48 srs_publisher.on_publisher_ready = function(cameras, microphones) {
49 - $("#sl_cameras").empty();  
50 - for (var i = 0; i < cameras.length; i++) {  
51 - $("#sl_cameras").append("<option value='" + i + "'>" + cameras[i] + "</option");  
52 - }  
53 - // optional: select the first no "virtual" signed.  
54 - for (var i = 0; i < cameras.length; i++) {  
55 - if (cameras[i].toLowerCase().indexOf("virtual") == -1) {  
56 - $("#sl_cameras option[value='" + i + "']").attr("selected", true);  
57 - break;  
58 - }  
59 - }  
60 -  
61 - $("#sl_microphones").empty();  
62 - for (var i = 0; i < microphones.length; i++) {  
63 - $("#sl_microphones").append("<option value='" + i + "'>" + microphones[i] + "</option");  
64 - }  
65 -  
66 - $("#sl_vcodec").empty();  
67 - var vcodecs = ["h264", "vp6"];  
68 - for (var i = 0; i < vcodecs.length; i++) {  
69 - $("#sl_vcodec").append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");  
70 - }  
71 -  
72 - $("#sl_profile").empty();  
73 - var profiles = ["baseline", "main"];  
74 - for (var i = 0; i < profiles.length; i++) {  
75 - $("#sl_profile").append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");  
76 - }  
77 -  
78 - $("#sl_level").empty();  
79 - var levels = ["1", "1b", "1.1", "1.2", "1.3",  
80 - "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];  
81 - for (var i = 0; i < levels.length; i++) {  
82 - $("#sl_level").append("<option value='" + levels[i] + "'>" + levels[i] + "</option");  
83 - }  
84 - $("#sl_level option[value='4.1']").attr("selected", true);  
85 -  
86 - $("#sl_gop").empty();  
87 - var gops = ["0.3", "0.5", "1", "2", "3", "4",  
88 - "5", "6", "7", "8", "9", "10", "15", "20"];  
89 - for (var i = 0; i < gops.length; i++) {  
90 - $("#sl_gop").append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");  
91 - }  
92 - $("#sl_gop option[value='5']").attr("selected", true);  
93 -  
94 - $("#sl_size").empty();  
95 - var sizes = ["176x144", "320x240", "352x240",  
96 - "352x288", "460x240", "640x480", "720x480", "720x576", "800x600",  
97 - "1024x768", "1280x720", "1360x768", "1920x1080"];  
98 - for (i = 0; i < sizes.length; i++) {  
99 - $("#sl_size").append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");  
100 - }  
101 - $("#sl_size option[value='460x240']").attr("selected", true);  
102 -  
103 - $("#sl_fps").empty();  
104 - var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];  
105 - for (i = 0; i < fpses.length; i++) {  
106 - $("#sl_fps").append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");  
107 - }  
108 - $("#sl_fps option[value='15']").attr("selected", true);  
109 -  
110 - $("#sl_bitrate").empty();  
111 - var bitrates = ["50", "200", "350", "500", "650", "800",  
112 - "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];  
113 - for (i = 0; i < bitrates.length; i++) {  
114 - $("#sl_bitrate").append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");  
115 - }  
116 - $("#sl_bitrate option[value='350']").attr("selected", true); 49 + srs_publisher_initialize_page(
  50 + cameras, microphones,
  51 + "#sl_cameras", "#sl_microphones",
  52 + "#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
  53 + "#sl_fps", "#sl_bitrate"
  54 + );
117 }; 55 };
118 srs_publisher.on_publisher_error = function(code, desc) { 56 srs_publisher.on_publisher_error = function(code, desc) {
119 error(code, desc); 57 error(code, desc);
@@ -194,20 +132,12 @@ @@ -194,20 +132,12 @@
194 var url = $("#txt_url").val(); 132 var url = $("#txt_url").val();
195 var vcodec = {}; 133 var vcodec = {};
196 var acodec = {}; 134 var acodec = {};
197 -  
198 - acodec.device_code = $("#sl_microphones").val();  
199 - acodec.device_name = $("#sl_microphones").text();  
200 -  
201 - vcodec.device_code = $("#sl_cameras").find("option:selected").val();  
202 - vcodec.device_name = $("#sl_cameras").find("option:selected").text();  
203 -  
204 - vcodec.codec = $("#sl_vcodec").find("option:selected").val();  
205 - vcodec.profile = $("#sl_profile").find("option:selected").val();  
206 - vcodec.level = $("#sl_level").find("option:selected").val();  
207 - vcodec.fps = $("#sl_fps").find("option:selected").val();  
208 - vcodec.gop = $("#sl_gop").find("option:selected").val();  
209 - vcodec.size = $("#sl_size").find("option:selected").val();  
210 - vcodec.bitrate = $("#sl_bitrate").find("option:selected").val(); 135 + srs_publiser_get_codec(
  136 + vcodec, acodec,
  137 + "#sl_cameras", "#sl_microphones",
  138 + "#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
  139 + "#sl_fps", "#sl_bitrate"
  140 + );
211 141
212 info("开始推流到服务器"); 142 info("开始推流到服务器");
213 srs_publisher.publish(url, vcodec, acodec); 143 srs_publisher.publish(url, vcodec, acodec);
@@ -230,22 +160,6 @@ @@ -230,22 +160,6 @@
230 remote_player.play(pub_url); 160 remote_player.play(pub_url);
231 } 161 }
232 } 162 }
233 -  
234 - function info(desc) {  
235 - $("#txt_log").addClass("alert-info").removeClass("alert-error").removeClass("alert-warn");  
236 - $("#txt_log_title").text("Info:");  
237 - $("#txt_log_msg").text(desc);  
238 - }  
239 - function warn(code, desc) {  
240 - $("#txt_log").removeClass("alert-info").removeClass("alert-error").addClass("alert-warn");  
241 - $("#txt_log_title").text("Warn:");  
242 - $("#txt_log_msg").text("code: " + code + ", " + desc);  
243 - }  
244 - function error(code, desc) {  
245 - $("#txt_log").removeClass("alert-info").addClass("alert-error").removeClass("alert-warn");  
246 - $("#txt_log_title").text("Error:");  
247 - $("#txt_log_msg").text("code: " + code + ", " + desc);  
248 - }  
249 </script> 163 </script>
250 </head> 164 </head>
251 <body> 165 <body>