diff --git a/trunk/research/api-server/server.py b/trunk/research/api-server/server.py index 617668c45..5e860145f 100755 --- a/trunk/research/api-server/server.py +++ b/trunk/research/api-server/server.py @@ -36,7 +36,7 @@ reload(sys) exec("sys.setdefaultencoding('utf-8')") assert sys.getdefaultencoding().lower() == "utf-8" -import json, datetime, cherrypy +import os, json, time, datetime, cherrypy, threading # simple log functions. def trace(msg): @@ -320,6 +320,123 @@ class RESTSessions(object): return code +global_chat_id = os.getpid(); +''' +the chat streams, public chat room. +''' +class RESTChats(object): + exposed = True + global_id = 100 + + def __init__(self): + # object fields: + # id: an int value indicates the id of user. + # username: a str indicates the user name. + # url: a str indicates the url of user stream. + # agent: a str indicates the agent of user. + # join_date: a number indicates the join timestamp in seconds. + # join_date_str: a str specifies the formated friendly time. + # heatbeat: a number indicates the heartbeat timestamp in seconds. + # vcodec: a dict indicates the video codec info. + # acodec: a dict indicates the audio codec info. + self.__chats = []; + self.__chat_lock = threading.Lock(); + + # dead time in seconds, if exceed, remove the chat. + self.__dead_time = 30; + + def GET(self): + enable_crossdomain() + + try: + self.__chat_lock.acquire(); + + chats = []; + copy = self.__chats[:]; + for chat in copy: + if time.time() - chat["heartbeat"] > self.__dead_time: + self.__chats.remove(chat); + continue; + + chats.append({ + "id": chat["id"], + "username": chat["username"], + "url": chat["url"], + "join_date_str": chat["join_date_str"], + "heartbeat": chat["heartbeat"], + }); + finally: + self.__chat_lock.release(); + + return json.dumps({"code":0, "data": {"now": time.time(), "chats": chats}}) + + def POST(self): + enable_crossdomain() + + req = cherrypy.request.body.read() + chat = json.loads(req) + + global global_chat_id; + chat["id"] = global_chat_id + global_chat_id += 1 + + chat["join_date"] = time.time(); + chat["heartbeat"] = time.time(); + chat["join_date_str"] = time.strftime("%Y-%m-%d %H:%M:%S"); + + try: + self.__chat_lock.acquire(); + + self.__chats.append(chat) + finally: + self.__chat_lock.release(); + + trace("create chat success, id=%s"%(chat["id"])) + + return json.dumps({"code":0, "data": chat["id"]}) + + def DELETE(self, id): + enable_crossdomain() + + try: + self.__chat_lock.acquire(); + + for chat in self.__chats: + if str(id) != str(chat["id"]): + continue + + self.__chats.remove(chat) + trace("delete chat success, id=%s"%(id)) + + return json.dumps({"code":0, "data": None}) + finally: + self.__chat_lock.release(); + + raise cherrypy.HTTPError(405, "Not allowed.") + + def PUT(self, id): + enable_crossdomain() + + try: + self.__chat_lock.acquire(); + + for chat in self.__chats: + if str(id) != str(chat["id"]): + continue + + chat["heartbeat"] = time.time(); + trace("heartbeat chat success, id=%s"%(id)) + + return json.dumps({"code":0, "data": None}) + finally: + self.__chat_lock.release(); + + raise cherrypy.HTTPError(405, "Not allowed.") + + + def OPTIONS(self, id=None): + enable_crossdomain() + # HTTP RESTful path. class Root(object): def __init__(self): @@ -335,6 +452,7 @@ class V1(object): self.clients = RESTClients() self.streams = RESTStreams() self.sessions = RESTSessions() + self.chats = RESTChats() ''' main code start. diff --git a/trunk/research/players/js/srs.js b/trunk/research/players/js/srs.js index c4f7e3c11..a30602694 100755 --- a/trunk/research/players/js/srs.js +++ b/trunk/research/players/js/srs.js @@ -27,6 +27,31 @@ function update_nav() { $("#nav_vlc").attr("href", "vlc.html" + window.location.search); } +/** +* log specified, there must be a log element as: + +
+ + Usage: + 创建会议室,或者加入会议室 +
+*/ +function info(desc) { + $("#txt_log").addClass("alert-info").removeClass("alert-error").removeClass("alert-warn"); + $("#txt_log_title").text("Info:"); + $("#txt_log_msg").text(desc); +} +function warn(code, desc) { + $("#txt_log").removeClass("alert-info").removeClass("alert-error").addClass("alert-warn"); + $("#txt_log_title").text("Warn:"); + $("#txt_log_msg").text("code: " + code + ", " + desc); +} +function error(code, desc) { + $("#txt_log").removeClass("alert-info").addClass("alert-error").removeClass("alert-warn"); + $("#txt_log_title").text("Error:"); + $("#txt_log_msg").text("code: " + code + ", " + desc); +} + /** * parse the query string to object. */ @@ -83,6 +108,23 @@ function build_default_rtmp_url() { return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream; } } +// for the chat to init the publish url. +function build_default_publish_rtmp_url() { + var query = parse_query_string(); + + var server = (query.server == undefined)? window.location.hostname:query.server; + var port = (query.port == undefined)? 1935:query.port; + var vhost = (query.vhost == undefined)? window.location.hostname:query.vhost; + var app = (query.app == undefined)? "live":query.app; + var stream = (query.stream == undefined)? "livestream":query.stream; + + if (server == vhost || vhost == "") { + return "rtmp://" + server + ":" + port + "/" + app + "/" + stream; + } else { + vhost = srs_get_player_publish_vhost(vhost); + return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream; + } +} /** @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; } function srs_get_version_code() { return "1.5"; } // get the default vhost for players. function srs_get_player_vhost() { return "players"; } +// the api server port, for chat room. +function srs_get_api_server_port() { return 8085; } // get the stream published to vhost, // generally we need to transcode the stream to support HLS and filters. // for example, src_vhost is "players", we transcode stream to vhost "players_pub". @@ -188,6 +232,112 @@ function srs_init(rtmp_url, hls_url, modal_player) { $(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px"); } } +// for the chat to init the publish url. +function srs_init_publish(rtmp_url) { + update_nav(); + + if (rtmp_url) { + $(rtmp_url).val(build_default_publish_rtmp_url()); + } +} + +/** +* when publisher ready, init the page elements. +*/ +function srs_publisher_initialize_page( + cameras, microphones, + sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate +) { + $(sl_cameras).empty(); + for (var i = 0; i < cameras.length; i++) { + $(sl_cameras).append("