diff --git a/trunk/research/api-server/server.py b/trunk/research/api-server/server.py index 3ec083458..fdd0f0f15 100755 --- a/trunk/research/api-server/server.py +++ b/trunk/research/api-server/server.py @@ -548,6 +548,7 @@ class ArmServer: data["heartbeat"] = self.heartbeat data["heartbeat_h"] = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(self.heartbeat)) data["summaries"] = "http://%s:1985/api/v1/summaries"%(self.ip) + data["console"] = "http://ossrs.net/console/ng_index.html#/summaries?host=%s&port=1985"%(self.ip) return data ''' @@ -579,29 +580,6 @@ class RESTServers(object): if not has_dead_node: break - def __json_dump_nodes(self, peers): - data = [] - for node in peers: - data.append(node.json_dump()) - return data - - def __get_peers_for_play(self, device_id): - peers = [] - for node in self.__nodes: - if node.device_id == device_id: - peers.append(node) - return peers - - def __select_peer(self, peers, device_id): - target = None - for peer in peers: - if target is None or target.clients > peer.clients: - target = peer - if target is None: - return None - target.clients += 1 - return target.ip - ''' post to update server ip. request body: the new raspberry-pi server ip. TODO: FIXME: more info. @@ -637,183 +615,21 @@ class RESTServers(object): self.__lock.release() ''' - id canbe: - pi: the pi demo, raspberry-pi default demo. - device_id: the id of device to get. - action: canbe play or mgmt, play to play the inest stream, mgmt to get api/v1/versions. - stream: the stream to play, for example, live/livestream for http://server:8080/live/livestream.html - meeting: the meeting demo. jump to web meeting if index is None. - device_id: the id of device to get. - local: whether view the local raspberry-pi stream. if "true", redirect to the local(internal) api server. - index: the meeting stream index, dynamic get the streams from root.api.v1.chats.get_url_by_index(index) - gslb: the gslb to get edge ip - device_id: the id of device to get. - ingest: deprecated, alias for pi. + get all servers which report to this api-server. ''' - def GET(self, id=None, action="play", stream="live/livestream", index=None, local="false", device_id=None): + def GET(self): enable_crossdomain() try: self.__lock.acquire() self.__refresh_nodes() - data = self.__json_dump_nodes(self.__nodes) - server_ip = "demo.chnvideo.com" - ip = cherrypy.request.remote.ip - if type is not None: - peers = self.__get_peers_for_play(device_id) - if len(peers) > 0: - server_ip = self.__select_peer(peers, device_id) + data = [] + for node in self.__nodes: + data.append(node.json_dump()) - # demo, srs meeting urls. - if id == "meeting": - if index is None: - url = "http://%s:8085"%(server_ip) - elif local == "true": - url = "http://%s:8085/api/v1/servers?id=%s&index=%s&local=false"%(server_ip, id, index) - else: - rtmp_url = root.api.v1.chats.get_url_by_index(index) - if rtmp_url is None: - return "meeting stream not found" - urls = rtmp_url.replace("...vhost...", "?vhost=").replace("rtmp://", "").split("/") - hls_url = "http://%s:8080/%s/%s.m3u8"%(urls[0].strip(":19350").strip(":1935"), urls[1].split("?")[0], urls[2]) - return self.__generate_hls(hls_url) - # raspberry-pi urls. - elif id == "ingest" or id == "pi": - if action == "play": - url = "http://%s:8080/%s.html"%(server_ip, stream) - elif action == "rtmp": - url = "../../players/srs_player.html?server=%s&vhost=%s&app=%s&stream=%s&autostart=true"%(server_ip, server_ip, stream.split("/")[0], stream.split("/")[1]) - elif action == "hls": - hls_url = "http://%s:8080/%s.m3u8"%(server_ip, stream); - if stream.startswith("http://"): - hls_url = stream; - return self.__generate_hls(hls_url.replace(".m3u8.m3u8", ".m3u8")) - else: - url = "http://%s:8080/api/v1/versions"%(server_ip) - elif id == "gslb": - return json.dumps({"code":Error.success, "data": { - "edge":server_ip, "client":ip, - "peers":self.__json_dump_nodes(peers), - "streams": { - "pi": { - "livestream": { - "sales-pi-hls": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-arm&stream=live/livestream", - "dev-pi-hls": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-dev-arm&stream=live/livestream" - }, - "cztv": { - "sales-pi-hls": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-arm&stream=live/rtmp_cztv01-sd", - "dev-pi-hls": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-dev-arm&stream=live/rtmp_cztv01-sd" - } - }, - "hiwifi": { - "hls": { - "dev-livestream": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-dev-hiwifi&stream=live/livestream", - "sales-livestream": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/livestream" - }, - "rtmp":{ - "dev-livestream": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-dev-hiwifi&stream=live/livestream", - "sales-livestream": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/livestream" - }, - "meiyi": { - "rtmp": { - "avatar": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/avatar", - "MenInBlack3": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/MenInBlack3", - "skyfall": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/skyfall", - "SpiderMan": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/SpiderMan", - "thehobbit": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/thehobbit", - "thorthedarkworld": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/thorthedarkworld", - "transformers": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-sales-hiwifi&stream=live/transformers" - }, - "hls": { - "avatar": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/avatar", - "MenInBlack3": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/MenInBlack3", - "skyfall": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/skyfall", - "SpiderMan": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/SpiderMan", - "thehobbit": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/thehobbit", - "thorthedarkworld": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/thorthedarkworld", - "transformers": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-sales-hiwifi&stream=live/transformers" - } - } - }, - "cubieboard": { - "meiyi": { - "rtmp": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/livestream", - "stream1": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/stream1", - "stream2": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/stream2", - "stream3": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/stream3", - "stream4": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/stream4", - "stream5": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/stream5", - "stream6": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/stream6", - "stream7": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard&stream=live/stream7" - }, - "hls": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/livestream", - "stream1": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/stream1", - "stream2": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/stream2", - "stream3": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/stream3", - "stream4": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/stream4", - "stream5": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/stream5", - "stream6": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/stream6", - "stream7": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard&stream=live/stream7" - } - }, - "meiyi-house": { - "rtmp": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-house&stream=live/livestream" - }, - "hls": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-house&stream=live/livestream" - } - }, - "meiyi-bk": { - "rtmp": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/livestream", - "stream1": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream1", - "stream2": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream2", - "stream3": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream3", - "stream4": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream4", - "stream5": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream5", - "stream6": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream6", - "stream7": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream7" - }, - "hls": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/livestream", - "stream1": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream1", - "stream2": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream2", - "stream3": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream3", - "stream4": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream4", - "stream5": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream5", - "stream6": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream6", - "stream7": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-bk&stream=live/stream7" - } - }, - "meiyi-dev1": { - "rtmp": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-dev1&stream=live/livestream" - }, - "hls": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-dev1&stream=live/livestream" - } - }, - "meiyi-dev2": { - "rtmp": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=rtmp&device_id=chnvideo-meiyi-cubieboard-dev2&stream=live/livestream" - }, - "hls": { - "livesteam": "http://demo.chnvideo.com:8085/api/v1/servers?id=ingest&action=hls&device_id=chnvideo-meiyi-cubieboard-dev2&stream=live/livestream" - } - } - } - } - }}) - # others, default. - else: - return json.dumps(data) - #return "id=%s, action=%s, stream=%s, url=%s, index=%s, local=%s"%(id, action, stream, url, index, local) - raise cherrypy.HTTPRedirect(url) + return json.dumps(data) finally: self.__lock.release() @@ -827,248 +643,6 @@ class RESTServers(object): def OPTIONS(self, *args, **kwargs): enable_crossdomain() - - def __generate_hls(self, hls_url): - return SrsUtility().hls_html(hls_url) - -class SrsUtility: - def hls_html(self, hls_url): - return """ -

%s

-"""%(hls_url, hls_url); - -global_cdn_id = os.getpid(); -class CdnNode: - def __init__(self): - global global_cdn_id - global_cdn_id += 1 - - self.id = str(global_cdn_id) - self.ip = None - self.origin = None - self.os = None - self.srs_status = None - - self.public_ip = cherrypy.request.remote.ip - self.heartbeat = time.time() - - self.clients = 0 - - def dead(self): - dead_time_seconds = 10 - if time.time() - self.heartbeat > dead_time_seconds: - return True - return False - - def json_dump(self): - data = {} - data["id"] = self.id - data["ip"] = self.ip - data["origin"] = self.origin - data["os"] = self.os - data["srs_status"] = self.srs_status - data["public_ip"] = self.public_ip - data["heartbeat"] = self.heartbeat - data["heartbeat_h"] = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(self.heartbeat)) - data["clients"] = self.clients - data["summaries"] = "http://%s:1985/api/v1/summaries"%(self.ip) - return data - -''' -the cdn nodes list -''' -class RESTNodes(object): - exposed = True - - def __init__(self): - self.__nodes = [] - # @remark, if there is shared data, such as the self.__nodes, - # we must use lock for cherrypy, or the cpu of cherrypy will high - # and performance suffer. - self.__lock = threading.Lock() - - def __get_node(self, id): - for node in self.__nodes: - if node.id == id: - return node - return None - - def __refresh_nodes(self): - while len(self.__nodes) > 0: - has_dead_node = False - for node in self.__nodes: - if node.dead(): - self.__nodes.remove(node) - has_dead_node = True - if not has_dead_node: - break - - def __get_peers(self, target_node): - peers = [] - for node in self.__nodes: - if str(node.id).strip() == str(target_node.id).strip(): - continue - if node.public_ip == target_node.public_ip and node.srs_status == "running" and node.origin != target_node.ip: - peers.append(node) - return peers - - def __get_peers_for_play(self, ip): - peers = [] - for node in self.__nodes: - if node.public_ip == ip and node.srs_status == "running": - peers.append(node) - return peers - - def __json_dump_nodes(self, peers): - data = [] - for node in peers: - data.append(node.json_dump()) - return data - - def __select_peer(self, peers, ip): - target = None - for peer in peers: - if target is None or target.clients > peer.clients: - target = peer - if target is None: - return None - target.clients += 1 - return target.ip - - def GET(self, type=None, format=None, origin=None, vhost=None, port=None, stream=None, node_id=None): - enable_crossdomain() - - try: - self.__lock.acquire() - - self.__refresh_nodes() - data = self.__json_dump_nodes(self.__nodes) - - ip = cherrypy.request.remote.ip - if type is not None: - server = origin - peers = self.__get_peers_for_play(ip) - if len(peers) > 0: - server = self.__select_peer(peers, ip) - if type == "hls": - hls_url = "http://%s:%s/%s.m3u8"%(server, port, stream) - hls_url = hls_url.replace(".m3u8.m3u8", ".m3u8") - if format == "html": - return SrsUtility().hls_html(hls_url) - else: - #return hls_url - raise cherrypy.HTTPRedirect(hls_url) - elif type == "rtmp": - rtmp_url = "rtmp://%s:%s/%s?vhost=%s/%s"%(server, port, stream.split("/")[0], vhost, stream.split("/")[1]) - if format == "html": - html = "%s?server=%s&port=%s&vhost=%s&app=%s&stream=%s&autostart=true"%( - "http://demo.chnvideo.com:8085/srs/trunk/research/players/srs_player.html", - server, port, vhost, stream.split("/")[0], stream.split("/")[1]) - #return html - raise cherrypy.HTTPRedirect(html) - return rtmp_url - elif type == "gslb": - return json.dumps({"code":Error.success, "data": { - "edge":server, "client":ip, - "peers":self.__json_dump_nodes(peers), - "streams": { - "cztv": { - "hls": "http://demo.chnvideo.com:8085/api/v1/nodes?type=hls&format=html&origin=demo.chnvideo.com&port=8080&stream=live/rtmp_cztv01-sd", - "rtmp": "http://demo.chnvideo.com:8085/api/v1/nodes?type=rtmp&format=html&origin=demo.chnvideo.com&vhost=android&port=1935&stream=live/rtmp_cztv01-sd" - }, - "livestream": { - "hls": "http://demo.chnvideo.com:8085/api/v1/nodes?type=hls&format=html&origin=demo.chnvideo.com&port=8080&stream=live/livestream", - "rtmp": "http://demo.chnvideo.com:8085/api/v1/nodes?type=rtmp&format=html&origin=demo.chnvideo.com&vhost=demo.srs.com&port=1935&stream=live/livestream" - }, - "apk": "http://demo.chnvideo.com/android.srs.apk" - } - }}) - - return json.dumps({"code":Error.success, "data": data}) - finally: - self.__lock.release() - - def PUT(self): - enable_crossdomain() - - try: - self.__lock.acquire() - - req = cherrypy.request.body.read() - trace("put to nodes, req=%s"%(req)) - try: - json_req = json.loads(req) - except Exception, ex: - code = Error.system_parse_json - trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) - return json.dumps({"code":code, "data": None}) - - id = str(json_req["id"]) - node = self.__get_node(id) - if node is None: - code = Error.cdn_node_not_exists - trace("cdn node not exists, req=%s, id=%s, code=%s"%(req, id, code)) - return json.dumps({"code":code, "data": None}) - - node.heartbeat = time.time() - node.srs_status = str(json_req["srs_status"]) - node.ip = str(json_req["ip"]) - if "origin" in json_req: - node.origin = str(json_req["origin"]); - node.public_ip = cherrypy.request.remote.ip - # reset if restart. - if node.srs_status != "running": - node.clients = 0 - - self.__refresh_nodes() - peers = self.__get_peers(node) - peers_data = self.__json_dump_nodes(peers) - - res = json.dumps({"code":Error.success, "data": {"id":node.id, "peers":peers_data}}) - trace(res) - return res - finally: - self.__lock.release() - - def POST(self): - enable_crossdomain() - - try: - self.__lock.acquire() - - req = cherrypy.request.body.read() - trace("post to nodes, req=%s"%(req)) - try: - json_req = json.loads(req) - except Exception, ex: - code = Error.system_parse_json - trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) - return json.dumps({"code":code, "data": None}) - - node = CdnNode() - node.ip = str(json_req["ip"]); - node.os = str(json_req["os"]); - if "origin" in json_req: - node.origin = str(json_req["origin"]); - node.srs_status = str(json_req["srs_status"]) - self.__nodes.append(node) - - self.__refresh_nodes() - peers = self.__get_peers(node) - peers_data = self.__json_dump_nodes(peers) - - res = json.dumps({"code":Error.success, "data": {"id":node.id, "peers":peers_data}}) - trace(res) - return res - finally: - self.__lock.release() - - def OPTIONS(self, *args, **kwargs): - enable_crossdomain() global_chat_id = os.getpid(); ''' @@ -1235,7 +809,6 @@ class V1(object): self.proxy = RESTProxy() self.chats = RESTChats() self.servers = RESTServers() - self.nodes = RESTNodes() def GET(self): enable_crossdomain(); return json.dumps({"code":Error.success, "urls":{ @@ -1244,26 +817,10 @@ class V1(object): "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream", "dvrs": "for srs http callback, to handle the dvr requests: dvr stream.", "chats": "for srs demo meeting, the chat streams, public chat room.", - "nodes": { - "summary": "for srs cdn node", - "POST ip=node_ip&os=node_os": "register a new node", - "GET": "get the active edge nodes", - "GET type=gslb&origin=demo.chnvideo.com": "get the gslb edge ip", - "GET type=hls&format=html&origin=demo.chnvideo.com&port=8080&stream=live/livestream": "get the play url, html for hls", - "GET type=rtmp&format=html&origin=demo.chnvideo.com&vhost=demo.srs.com&port=1935&stream=live/livestream": "get the play url, for rtmp" - }, "servers": { "summary": "for srs raspberry-pi and meeting demo", "GET": "get the current raspberry-pi servers info", - "GET id=gslb&device_id=chnvideo-sales-arm": "get the gslb edge ip", - "POST ip=node_ip&device_id=device_id": "the new raspberry-pi server info.", - "GET id=ingest&action=play&stream=live/livestream": "play the ingest HLS stream on raspberry-pi", - "GET id=ingest&action=rtmp&stream=live/livestream": "play the ingest RTMP stream on raspberry-pi", - "GET id=ingest&action=hls&stream=live/livestream": "play the ingest HLS stream on raspberry-pi", - "GET id=ingest&action=mgmt": "open the HTTP api url of raspberry-pi", - "GET id=meeting": "redirect to local raspberry-pi meeting url(local ignored)", - "GET id=meeting&local=false&index=0": "play the first(index=0) meeting HLS stream on demo.chnvideo.com(not local)", - "GET id=meeting&local=true&index=0": "play the first(index=0) meeting HLS stream on local server(local x86/x64 server), warn: raspberry-pi donot support HLS meeting." + "POST ip=node_ip&device_id=device_id": "the new raspberry-pi server info." } }}); def OPTIONS(self, *args, **kwargs):