正在显示
4 个修改的文件
包含
677 行增加
和
323 行删除
@@ -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 © 2013</a></p> | 636 | <p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team © 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> |
-
请 注册 或 登录 后发表评论