正在显示
4 个修改的文件
包含
692 行增加
和
338 行删除
| @@ -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) { |
| 265 | - this.stream_url = url; | 420 | + if (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,99 +15,45 @@ | @@ -15,99 +15,45 @@ | ||
| 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); | ||
| 36 | - | 43 | + $("#btn_join").click(on_user_publish); |
| 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()); |
| 39 | 47 | ||
| 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 | - } | ||
| 54 | - | ||
| 55 | - $("#sl_microphones").empty(); | ||
| 56 | - for (var i = 0; i < microphones.length; i++) { | ||
| 57 | - $("#sl_microphones").append("<option value='" + i + "'>" + microphones[i] + "</option"); | ||
| 58 | - } | ||
| 59 | - | ||
| 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 | - } | ||
| 65 | - | ||
| 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"); | ||
| 70 | - } | ||
| 71 | - | ||
| 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"); | ||
| 77 | - } | ||
| 78 | - $("#sl_level option[value='4.1']").attr("selected", true); | ||
| 79 | - | ||
| 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"); | ||
| 85 | - } | ||
| 86 | - $("#sl_gop option[value='5']").attr("selected", true); | ||
| 87 | - | ||
| 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"); | ||
| 94 | - } | ||
| 95 | - $("#sl_size option[value='460x240']").attr("selected", true); | ||
| 96 | - | ||
| 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"); | ||
| 101 | - } | ||
| 102 | - $("#sl_fps option[value='15']").attr("selected", true); | ||
| 103 | - | ||
| 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"); | ||
| 109 | - } | ||
| 110 | - $("#sl_bitrate option[value='350']").attr("selected", true); | 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 | + ); | ||
| 111 | }; | 57 | }; |
| 112 | srs_publisher.on_publisher_error = function(code, desc) { | 58 | srs_publisher.on_publisher_error = function(code, desc) { |
| 113 | error(code, desc); | 59 | error(code, desc); |
| @@ -117,20 +63,7 @@ | @@ -117,20 +63,7 @@ | ||
| 117 | }; | 63 | }; |
| 118 | srs_publisher.start(); | 64 | srs_publisher.start(); |
| 119 | 65 | ||
| 120 | - //wizard = $("#some-wizard").wizard({}); | ||
| 121 | - //wizard.show(); | ||
| 122 | - | ||
| 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(); | ||
| 133 | - | 66 | + if (!no_play) { |
| 134 | // start the realtime player. | 67 | // start the realtime player. |
| 135 | realtime_player = new SrsPlayer("realtime_player", 430, 185); | 68 | realtime_player = new SrsPlayer("realtime_player", 430, 185); |
| 136 | realtime_player.on_player_ready = function() { | 69 | realtime_player.on_player_ready = function() { |
| @@ -139,101 +72,340 @@ | @@ -139,101 +72,340 @@ | ||
| 139 | }; | 72 | }; |
| 140 | realtime_player.start(); | 73 | realtime_player.start(); |
| 141 | } | 74 | } |
| 75 | + | ||
| 76 | + api_server = "http://" + query.hostname + ":" + srs_get_api_server_port() + "/api/v1/chats"; | ||
| 77 | + refresh(); | ||
| 142 | }); | 78 | }); |
| 143 | 79 | ||
| 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(); | ||
| 148 | - | ||
| 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"; | ||
| 152 | - | ||
| 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"; | ||
| 156 | - | ||
| 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"; | ||
| 160 | - | ||
| 161 | - var hls_url = "http://" + ret.server + ":" + query.http_port + "/" + ret.app + "/" + ret.stream + ".m3u8"; | 80 | + function refresh() { |
| 81 | + if (!self_chat) { | ||
| 82 | + setTimeout(refresh, 1000); | ||
| 83 | + return; | ||
| 84 | + } | ||
| 162 | 85 | ||
| 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"); | 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; | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + var chats = ret["data"]["chats"]; | ||
| 107 | + var server_time = ret["now"]; | ||
| 108 | + | ||
| 109 | + on_get_chats(chats); | ||
| 110 | + setTimeout(refresh, 5000); | ||
| 111 | + } | ||
| 112 | + }); | ||
| 167 | } | 113 | } |
| 168 | - function on_user_publish() { | ||
| 169 | - if ($("#btn_publish").text() == "停止发布") { | ||
| 170 | - 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"); | 114 | + function on_get_chats(chats) { |
| 115 | + if (!self_chat) { | ||
| 116 | + return; | ||
| 117 | + } | ||
| 118 | + | ||
| 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 | + }); | ||
| 176 | return; | 135 | return; |
| 177 | } | 136 | } |
| 178 | 137 | ||
| 179 | - $("#btn_publish").text("停止发布"); | 138 | + render_chat_room(chats); |
| 180 | 139 | ||
| 181 | - update_play_url(); | 140 | + if (!self_chat) { |
| 141 | + return; | ||
| 142 | + } | ||
| 182 | 143 | ||
| 183 | - var url = $("#txt_url").val(); | ||
| 184 | - var vcodec = {}; | ||
| 185 | - var acodec = {}; | 144 | + // update self heartbeat. |
| 145 | + var url = api_server + "/" + self_chat.id; | ||
| 186 | 146 | ||
| 187 | - acodec.device_code = $("#sl_microphones").val(); | ||
| 188 | - acodec.device_name = $("#sl_microphones").text(); | 147 | + var chat = {}; |
| 148 | + chat.localtime = new Date().getTime(); | ||
| 189 | 149 | ||
| 190 | - vcodec.device_code = $("#sl_cameras").find("option:selected").val(); | ||
| 191 | - vcodec.device_name = $("#sl_cameras").find("option:selected").text(); | 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; | ||
| 174 | + } | ||
| 192 | 175 | ||
| 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(); | 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 | + } | ||
| 183 | + | ||
| 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 | + } | ||
| 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); | ||
| 200 | 209 | ||
| 201 | - info("开始推流到服务器"); | ||
| 202 | - srs_publisher.publish(url, vcodec, acodec); | 210 | + if (!no_play) { |
| 211 | + // start the realtime player. | ||
| 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); | ||
| 216 | + }; | ||
| 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; | ||
| 229 | + } | ||
| 230 | + chat.player.stop(); | ||
| 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 | + } | ||
| 203 | 241 | ||
| 204 | - if (realtime_player) { | ||
| 205 | - // directly play the url for the realtime player. | ||
| 206 | - realtime_player.stop(); | ||
| 207 | - realtime_player.play(url); | 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 | + } | ||
| 254 | + | ||
| 255 | + if (chat.player) { | ||
| 256 | + chat.player.stop(); | ||
| 257 | + } | ||
| 258 | + $("#div_" + chat.id).remove(); | ||
| 208 | } | 259 | } |
| 209 | 260 | ||
| 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); | 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 | + } | ||
| 220 | } | 269 | } |
| 270 | + return null; | ||
| 221 | } | 271 | } |
| 222 | 272 | ||
| 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); | 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 | + } | ||
| 279 | + | ||
| 280 | + $("#txt_name").parent().parent().removeClass("error"); | ||
| 281 | + | ||
| 282 | + // join chat. | ||
| 283 | + if (!self_chat) { | ||
| 284 | + on_user_join_chat(); | ||
| 285 | + } else { | ||
| 286 | + on_user_exit_chat(); | ||
| 287 | + } | ||
| 227 | } | 288 | } |
| 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); | 289 | + function on_user_exit_chat(complete_pfn) { |
| 290 | + srs_publisher.stop(); | ||
| 291 | + $("#btn_join").text("加入会议"); | ||
| 292 | + | ||
| 293 | + if (!self_chat) { | ||
| 294 | + return; | ||
| 295 | + } | ||
| 296 | + | ||
| 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 | + } | ||
| 304 | + | ||
| 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 | + }); | ||
| 232 | } | 343 | } |
| 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); | 344 | + function on_user_join_chat(complete_pfn) { |
| 345 | + if (self_chat) { | ||
| 346 | + return; | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + var url = $("#txt_url").val(); | ||
| 350 | + var vcodec = {}; | ||
| 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 | + } | ||
| 391 | + | ||
| 392 | + chat.id = ret["data"]; | ||
| 393 | + | ||
| 394 | + // success, start publish. | ||
| 395 | + self_chat = chat; | ||
| 396 | + | ||
| 397 | + $("#btn_join").text("退出会议"); | ||
| 398 | + | ||
| 399 | + info("开始推流到服务器"); | ||
| 400 | + srs_publisher.publish(url, vcodec, acodec); | ||
| 401 | + | ||
| 402 | + if (realtime_player) { | ||
| 403 | + // directly play the url for the realtime player. | ||
| 404 | + realtime_player.stop(); | ||
| 405 | + realtime_player.play(url); | ||
| 406 | + } | ||
| 407 | + } | ||
| 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> | ||
| 460 | + </div> | ||
| 461 | + </div> | ||
| 462 | + </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> | ||
| 276 | </div> | 478 | </div> |
| 277 | </div> | 479 | </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> | 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> | ||
| 285 | </div> | 505 | </div> |
| 286 | </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> |
-
请 注册 或 登录 后发表评论