mirror of
				https://github.com/ossrs/srs.git
				synced 2025-03-09 15:49:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1122 lines
		
	
	
	
		
			47 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			1122 lines
		
	
	
	
		
			47 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
| #!/usr/bin/python
 | |
| '''
 | |
| The MIT License (MIT)
 | |
| 
 | |
| Copyright (c) 2013-2014 winlin
 | |
| 
 | |
| Permission is hereby granted, free of charge, to any person obtaining a copy of
 | |
| this software and associated documentation files (the "Software"), to deal in
 | |
| the Software without restriction, including without limitation the rights to
 | |
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 | |
| the Software, and to permit persons to whom the Software is furnished to do so,
 | |
| subject to the following conditions:
 | |
| 
 | |
| The above copyright notice and this permission notice shall be included in all
 | |
| copies or substantial portions of the Software.
 | |
| 
 | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 | |
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 | |
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 | |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | |
| '''
 | |
| 
 | |
| """
 | |
| the api-server is a default demo server for srs to call
 | |
| when srs get some event, for example, when client connect
 | |
| to srs, srs can invoke the http api of the api-server
 | |
| """
 | |
| 
 | |
| import sys
 | |
| # reload sys model to enable the getdefaultencoding method.
 | |
| reload(sys)
 | |
| # set the default encoding to utf-8
 | |
| # using exec to set the encoding, to avoid error in IDE.
 | |
| exec("sys.setdefaultencoding('utf-8')")
 | |
| assert sys.getdefaultencoding().lower() == "utf-8"
 | |
| 
 | |
| import os, json, time, datetime, cherrypy, threading
 | |
| 
 | |
| # simple log functions.
 | |
| def trace(msg):
 | |
|     date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 | |
|     print "[%s][trace] %s"%(date, msg)
 | |
| 
 | |
| # enable crossdomain access for js-client
 | |
| # define the following method:
 | |
| #   def OPTIONS(self, *args, **kwargs)
 | |
| #       enable_crossdomain()
 | |
| # invoke this method to enable js to request crossdomain.
 | |
| def enable_crossdomain():
 | |
|     cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"
 | |
|     cherrypy.response.headers["Access-Control-Allow-Methods"] = "GET, POST, HEAD, PUT, DELETE"
 | |
|     # generate allow headers for crossdomain.
 | |
|     allow_headers = ["Cache-Control", "X-Proxy-Authorization", "X-Requested-With", "Content-Type"]
 | |
|     cherrypy.response.headers["Access-Control-Allow-Headers"] = ",".join(allow_headers)
 | |
| 
 | |
| # error codes definition
 | |
| class Error:
 | |
|     # ok, success, completed.
 | |
|     success = 0
 | |
|     # error when parse json
 | |
|     system_parse_json = 100
 | |
|     # request action invalid
 | |
|     request_invalid_action = 200
 | |
|     # cdn node not exists
 | |
|     cdn_node_not_exists = 201
 | |
| 
 | |
| '''
 | |
| handle the clients requests: connect/disconnect vhost/app.
 | |
| '''
 | |
| class RESTClients(object):
 | |
|     exposed = True
 | |
| 
 | |
|     def GET(self):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|         clients = {}
 | |
|         return json.dumps(clients)
 | |
| 
 | |
|     '''
 | |
|     for SRS hook: on_connect/on_close
 | |
|     on_connect:
 | |
|         when client connect to vhost/app, call the hook,
 | |
|         the request in the POST data string is a object encode by json:
 | |
|               {
 | |
|                   "action": "on_connect",
 | |
|                   "client_id": 1985,
 | |
|                   "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
 | |
|                   "pageUrl": "http://www.test.com/live.html"
 | |
|               }
 | |
|     on_close:
 | |
|         when client close/disconnect to vhost/app/stream, call the hook,
 | |
|         the request in the POST data string is a object encode by json:
 | |
|               {
 | |
|                   "action": "on_close",
 | |
|                   "client_id": 1985,
 | |
|                   "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live"
 | |
|               }
 | |
|     if valid, the hook must return HTTP code 200(Stauts OK) and response
 | |
|     an int value specifies the error code(0 corresponding to success):
 | |
|           0
 | |
|     '''
 | |
|     def POST(self):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|         # return the error code in str
 | |
|         code = Error.success
 | |
| 
 | |
|         req = cherrypy.request.body.read()
 | |
|         trace("post to clients, 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 str(code)
 | |
| 
 | |
|         action = json_req["action"]
 | |
|         if action == "on_connect":
 | |
|             code = self.__on_connect(json_req)
 | |
|         elif action == "on_close":
 | |
|             code = self.__on_close(json_req)
 | |
|         else:
 | |
|             trace("invalid request action: %s"%(json_req["action"]))
 | |
|             code = Error.request_invalid_action
 | |
| 
 | |
|         return str(code)
 | |
| 
 | |
|     def OPTIONS(self, *args, **kwargs):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|     def __on_connect(self, req):
 | |
|         code = Error.success
 | |
| 
 | |
|         trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, tcUrl=%s, pageUrl=%s"%(
 | |
|             req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["tcUrl"], req["pageUrl"]
 | |
|         ))
 | |
| 
 | |
|         # TODO: process the on_connect event
 | |
| 
 | |
|         return code
 | |
| 
 | |
|     def __on_close(self, req):
 | |
|         code = Error.success
 | |
| 
 | |
|         trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s"%(
 | |
|             req["action"], req["client_id"], req["ip"], req["vhost"], req["app"]
 | |
|         ))
 | |
| 
 | |
|         # TODO: process the on_close event
 | |
| 
 | |
|         return code
 | |
| 
 | |
| '''
 | |
| handle the streams requests: publish/unpublish stream.
 | |
| '''
 | |
| class RESTStreams(object):
 | |
|     exposed = True
 | |
| 
 | |
|     def GET(self):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|         streams = {}
 | |
|         return json.dumps(streams)
 | |
| 
 | |
|     '''
 | |
|     for SRS hook: on_publish/on_unpublish
 | |
|     on_publish:
 | |
|         when client(encoder) publish to vhost/app/stream, call the hook,
 | |
|         the request in the POST data string is a object encode by json:
 | |
|               {
 | |
|                   "action": "on_publish",
 | |
|                   "client_id": 1985,
 | |
|                   "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
 | |
|                   "stream": "livestream"
 | |
|               }
 | |
|     on_unpublish:
 | |
|         when client(encoder) stop publish to vhost/app/stream, call the hook,
 | |
|         the request in the POST data string is a object encode by json:
 | |
|               {
 | |
|                   "action": "on_unpublish",
 | |
|                   "client_id": 1985,
 | |
|                   "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
 | |
|                   "stream": "livestream"
 | |
|               }
 | |
|     if valid, the hook must return HTTP code 200(Stauts OK) and response
 | |
|     an int value specifies the error code(0 corresponding to success):
 | |
|           0
 | |
|     '''
 | |
|     def POST(self):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|         # return the error code in str
 | |
|         code = Error.success
 | |
| 
 | |
|         req = cherrypy.request.body.read()
 | |
|         trace("post to streams, 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 str(code)
 | |
| 
 | |
|         action = json_req["action"]
 | |
|         if action == "on_publish":
 | |
|             code = self.__on_publish(json_req)
 | |
|         elif action == "on_unpublish":
 | |
|             code = self.__on_unpublish(json_req)
 | |
|         else:
 | |
|             trace("invalid request action: %s"%(json_req["action"]))
 | |
|             code = Error.request_invalid_action
 | |
| 
 | |
|         return str(code)
 | |
| 
 | |
|     def OPTIONS(self, *args, **kwargs):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|     def __on_publish(self, req):
 | |
|         code = Error.success
 | |
| 
 | |
|         trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%(
 | |
|             req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"]
 | |
|         ))
 | |
| 
 | |
|         # TODO: process the on_publish event
 | |
| 
 | |
|         return code
 | |
| 
 | |
|     def __on_unpublish(self, req):
 | |
|         code = Error.success
 | |
| 
 | |
|         trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%(
 | |
|             req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"]
 | |
|         ))
 | |
| 
 | |
|         # TODO: process the on_unpublish event
 | |
| 
 | |
|         return code
 | |
| 
 | |
| '''
 | |
| handle the sessions requests: client play/stop stream
 | |
| '''
 | |
| class RESTSessions(object):
 | |
|     exposed = True
 | |
| 
 | |
|     def GET(self):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|         sessions = {}
 | |
|         return json.dumps(sessions)
 | |
| 
 | |
|     '''
 | |
|     for SRS hook: on_play/on_stop
 | |
|     on_play:
 | |
|         when client(encoder) publish to vhost/app/stream, call the hook,
 | |
|         the request in the POST data string is a object encode by json:
 | |
|               {
 | |
|                   "action": "on_play",
 | |
|                   "client_id": 1985,
 | |
|                   "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
 | |
|                   "stream": "livestream"
 | |
|               }
 | |
|     on_stop:
 | |
|         when client(encoder) stop publish to vhost/app/stream, call the hook,
 | |
|         the request in the POST data string is a object encode by json:
 | |
|               {
 | |
|                   "action": "on_stop",
 | |
|                   "client_id": 1985,
 | |
|                   "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
 | |
|                   "stream": "livestream"
 | |
|               }
 | |
|     if valid, the hook must return HTTP code 200(Stauts OK) and response
 | |
|     an int value specifies the error code(0 corresponding to success):
 | |
|           0
 | |
|     '''
 | |
|     def POST(self):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|         # return the error code in str
 | |
|         code = Error.success
 | |
| 
 | |
|         req = cherrypy.request.body.read()
 | |
|         trace("post to sessions, 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 str(code)
 | |
| 
 | |
|         action = json_req["action"]
 | |
|         if action == "on_play":
 | |
|             code = self.__on_play(json_req)
 | |
|         elif action == "on_stop":
 | |
|             code = self.__on_stop(json_req)
 | |
|         else:
 | |
|             trace("invalid request action: %s"%(json_req["action"]))
 | |
|             code = Error.request_invalid_action
 | |
| 
 | |
|         return str(code)
 | |
| 
 | |
|     def OPTIONS(self, *args, **kwargs):
 | |
|         enable_crossdomain()
 | |
| 
 | |
|     def __on_play(self, req):
 | |
|         code = Error.success
 | |
| 
 | |
|         trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%(
 | |
|             req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"]
 | |
|         ))
 | |
| 
 | |
|         # TODO: process the on_play event
 | |
| 
 | |
|         return code
 | |
| 
 | |
|     def __on_stop(self, req):
 | |
|         code = Error.success
 | |
| 
 | |
|         trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%(
 | |
|             req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"]
 | |
|         ))
 | |
| 
 | |
|         # TODO: process the on_stop event
 | |
| 
 | |
|         return code
 | |
| 
 | |
| global_arm_server_id = os.getpid();
 | |
| class ArmServer:
 | |
|     def __init__(self):
 | |
|         global global_arm_server_id
 | |
|         global_arm_server_id += 1
 | |
|         
 | |
|         self.id = str(global_arm_server_id)
 | |
|         self.ip = None
 | |
|         self.device_id = None
 | |
|         
 | |
|         self.public_ip = cherrypy.request.remote.ip
 | |
|         self.heartbeat = time.time()
 | |
|         
 | |
|         self.clients = 0
 | |
|     
 | |
|     def dead(self):
 | |
|         dead_time_seconds = 20
 | |
|         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["device_id"] = self.device_id
 | |
|         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["summaries"] = "http://%s:1985/api/v1/summaries"%(self.ip)
 | |
|         return data
 | |
|         
 | |
| '''
 | |
| the server list
 | |
| '''
 | |
| class RESTServers(object):
 | |
|     exposed = True
 | |
|     
 | |
|     def __init__(self):
 | |
|         self.__nodes = []
 | |
|         
 | |
|         self.__last_update = datetime.datetime.now();
 | |
|         
 | |
|         self.__lock = threading.Lock()
 | |
|         
 | |
|     def __get_node(self, device_id):
 | |
|         for node in self.__nodes:
 | |
|             if node.device_id == device_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 __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.
 | |
|     '''
 | |
|     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})
 | |
|                 
 | |
|             device_id = json_req["device_id"]
 | |
|             node = self.__get_node(device_id)
 | |
|             if node is None:
 | |
|                 node = ArmServer()
 | |
|                 self.__nodes.append(node)
 | |
|                 
 | |
|             node.ip = json_req["ip"]
 | |
|             node.device_id = device_id
 | |
|             node.public_ip = cherrypy.request.remote.ip
 | |
|             node.heartbeat = time.time()
 | |
|             
 | |
|             return json.dumps({"code":Error.success, "data": {"id":node.id}})
 | |
|         finally:
 | |
|             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.
 | |
|     '''
 | |
|     def GET(self, id=None, action="play", stream="live/livestream", index=None, local="false", device_id=None):
 | |
|         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)
 | |
|             
 | |
|             # 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)
 | |
|         finally:
 | |
|             self.__lock.release()
 | |
| 
 | |
|     def DELETE(self, id):
 | |
|         enable_crossdomain()
 | |
|         raise cherrypy.HTTPError(405, "Not allowed.")
 | |
| 
 | |
|     def PUT(self, id):
 | |
|         enable_crossdomain()
 | |
|         raise cherrypy.HTTPError(405, "Not allowed.")
 | |
| 
 | |
|     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 """
 | |
| <h1>%s</h1>
 | |
| <video width="640" height="360"
 | |
|         autoplay controls autobuffer 
 | |
|         src="%s"
 | |
|         type="application/vnd.apple.mpegurl">
 | |
| </video>"""%(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();
 | |
| '''
 | |
| 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 = 15;
 | |
|     
 | |
|     '''
 | |
|     get the rtmp url of chat object. None if overflow.
 | |
|     '''
 | |
|     def get_url_by_index(self, index):
 | |
|         index = int(index)
 | |
|         if index is None or index >= len(self.__chats):
 | |
|             return None;
 | |
|         return self.__chats[index]["url"];
 | |
| 
 | |
|     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, *args, **kwargs):
 | |
|         enable_crossdomain()
 | |
| 
 | |
| # HTTP RESTful path.
 | |
| class Root(object):
 | |
|     exposed = True
 | |
| 
 | |
|     def __init__(self):
 | |
|         self.api = Api()
 | |
|     def GET(self):
 | |
|         enable_crossdomain();
 | |
|         return json.dumps({"code":Error.success, "urls":{"api":"the api root"}})
 | |
|     def OPTIONS(self, *args, **kwargs):
 | |
|         enable_crossdomain();
 | |
| # HTTP RESTful path.
 | |
| class Api(object):
 | |
|     exposed = True
 | |
| 
 | |
|     def __init__(self):
 | |
|         self.v1 = V1()
 | |
|     def GET(self):
 | |
|         enable_crossdomain();
 | |
|         return json.dumps({"code":Error.success, 
 | |
|             "urls": {
 | |
|                 "v1": "the api version 1.0"
 | |
|             }
 | |
|         });
 | |
|     def OPTIONS(self, *args, **kwargs):
 | |
|         enable_crossdomain();
 | |
| # HTTP RESTful path. to access as:
 | |
| #   http://127.0.0.1:8085/api/v1/clients
 | |
| class V1(object):
 | |
|     exposed = True
 | |
| 
 | |
|     def __init__(self):
 | |
|         self.clients = RESTClients()
 | |
|         self.streams = RESTStreams()
 | |
|         self.sessions = RESTSessions()
 | |
|         self.chats = RESTChats()
 | |
|         self.servers = RESTServers()
 | |
|         self.nodes = RESTNodes()
 | |
|     def GET(self):
 | |
|         enable_crossdomain();
 | |
|         return json.dumps({"code":Error.success, "urls":{
 | |
|             "clients": "for srs http callback, to handle the clients requests: connect/disconnect vhost/app.", 
 | |
|             "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.",
 | |
|             "sessions": "for srs http callback, to handle the sessions requests: client play/stop 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."
 | |
|             }
 | |
|         }});
 | |
|     def OPTIONS(self, *args, **kwargs):
 | |
|         enable_crossdomain();
 | |
| 
 | |
| '''
 | |
| main code start.
 | |
| '''
 | |
| # donot support use this module as library.
 | |
| if __name__ != "__main__":
 | |
|     raise Exception("embed not support")
 | |
| 
 | |
| # check the user options
 | |
| if len(sys.argv) <= 1:
 | |
|     print "SRS api callback server, Copyright (c) 2013-2014 winlin"
 | |
|     print "Usage: python %s <port>"%(sys.argv[0])
 | |
|     print "    port: the port to listen at."
 | |
|     print "For example:"
 | |
|     print "    python %s 8085"%(sys.argv[0])
 | |
|     print ""
 | |
|     print "See also: https://github.com/winlinvip/simple-rtmp-server"
 | |
|     sys.exit(1)
 | |
| 
 | |
| # parse port from user options.
 | |
| port = int(sys.argv[1])
 | |
| static_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "static-dir"))
 | |
| trace("api server listen at port: %s, static_dir: %s"%(port, static_dir))
 | |
| 
 | |
| # cherrypy config.
 | |
| conf = {
 | |
|     'global': {
 | |
|         'server.shutdown_timeout': 1,
 | |
|         'server.socket_host': '0.0.0.0',
 | |
|         'server.socket_port': port,
 | |
|         'tools.encode.on': True,
 | |
|         'tools.staticdir.on': True,
 | |
|         'tools.encode.encoding': "utf-8",
 | |
|         #'server.thread_pool': 2, # single thread server.
 | |
|     },
 | |
|     '/': {
 | |
|         'tools.staticdir.dir': static_dir,
 | |
|         'tools.staticdir.index': "index.html",
 | |
|         # for cherrypy RESTful api support
 | |
|         'request.dispatch': cherrypy.dispatch.MethodDispatcher()
 | |
|     }
 | |
| }
 | |
| 
 | |
| # start cherrypy web engine
 | |
| trace("start cherrypy server")
 | |
| root = Root()
 | |
| cherrypy.quickstart(root, '/', conf)
 | |
| 
 |