正在显示
1 个修改的文件
包含
187 行增加
和
5 行删除
| @@ -63,6 +63,8 @@ class Error: | @@ -63,6 +63,8 @@ class Error: | ||
| 63 | system_parse_json = 100 | 63 | system_parse_json = 100 |
| 64 | # request action invalid | 64 | # request action invalid |
| 65 | request_invalid_action = 200 | 65 | request_invalid_action = 200 |
| 66 | + # cdn node not exists | ||
| 67 | + cdn_node_not_exists = 201 | ||
| 66 | 68 | ||
| 67 | ''' | 69 | ''' |
| 68 | handle the clients requests: connect/disconnect vhost/app. | 70 | handle the clients requests: connect/disconnect vhost/app. |
| @@ -403,12 +405,186 @@ class RESTServers(object): | @@ -403,12 +405,186 @@ class RESTServers(object): | ||
| 403 | enable_crossdomain() | 405 | enable_crossdomain() |
| 404 | 406 | ||
| 405 | def __generate_hls(self, hls_url): | 407 | def __generate_hls(self, hls_url): |
| 408 | + return SrsUtility().hls_html(hls_url) | ||
| 409 | + | ||
| 410 | +class SrsUtility: | ||
| 411 | + def hls_html(self, hls_url): | ||
| 406 | return """ | 412 | return """ |
| 413 | +<h1>%s</h1> | ||
| 407 | <video width="640" height="360" | 414 | <video width="640" height="360" |
| 408 | autoplay controls autobuffer | 415 | autoplay controls autobuffer |
| 409 | src="%s" | 416 | src="%s" |
| 410 | type="application/vnd.apple.mpegurl"> | 417 | type="application/vnd.apple.mpegurl"> |
| 411 | -</video>"""%(hls_url); | 418 | +</video>"""%(hls_url, hls_url); |
| 419 | + | ||
| 420 | +global_cdn_id = os.getpid(); | ||
| 421 | +class CdnNode: | ||
| 422 | + def __init__(self): | ||
| 423 | + global global_cdn_id | ||
| 424 | + global_cdn_id += 1 | ||
| 425 | + | ||
| 426 | + self.id = str(global_cdn_id) | ||
| 427 | + self.ip = None | ||
| 428 | + self.os = None | ||
| 429 | + self.srs_status = None | ||
| 430 | + | ||
| 431 | + self.public_ip = cherrypy.request.remote.ip | ||
| 432 | + self.heartbeat = time.time() | ||
| 433 | + | ||
| 434 | + def dead(self): | ||
| 435 | + dead_time_seconds = 10 | ||
| 436 | + if time.time() - self.heartbeat > dead_time_seconds: | ||
| 437 | + return True | ||
| 438 | + return False | ||
| 439 | + | ||
| 440 | + def json_dump(self): | ||
| 441 | + data = {} | ||
| 442 | + data["id"] = self.id | ||
| 443 | + data["ip"] = self.ip | ||
| 444 | + data["os"] = self.os | ||
| 445 | + data["srs_status"] = self.srs_status | ||
| 446 | + data["public_ip"] = self.public_ip | ||
| 447 | + data["heartbeat"] = self.heartbeat | ||
| 448 | + data["heartbeat_h"] = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(self.heartbeat)) | ||
| 449 | + return data | ||
| 450 | + | ||
| 451 | +''' | ||
| 452 | +the cdn nodes list | ||
| 453 | +''' | ||
| 454 | +class RESTNodes(object): | ||
| 455 | + exposed = True | ||
| 456 | + | ||
| 457 | + def __init__(self): | ||
| 458 | + self.__nodes = [] | ||
| 459 | + | ||
| 460 | + def __get_node(self, id): | ||
| 461 | + for node in self.__nodes: | ||
| 462 | + if node.id == id: | ||
| 463 | + return node | ||
| 464 | + return None | ||
| 465 | + | ||
| 466 | + def __refresh_nodes(self): | ||
| 467 | + has_dead_node = False | ||
| 468 | + while True: | ||
| 469 | + for node in self.__nodes: | ||
| 470 | + if node.dead(): | ||
| 471 | + self.__nodes.remove(node) | ||
| 472 | + has_dead_node = True | ||
| 473 | + if not has_dead_node: | ||
| 474 | + break | ||
| 475 | + | ||
| 476 | + def __get_peers(self, target_node): | ||
| 477 | + peers = [] | ||
| 478 | + for node in self.__nodes: | ||
| 479 | + if node.id == target_node.id: | ||
| 480 | + continue | ||
| 481 | + if node.public_ip == target_node.public_ip: | ||
| 482 | + peers.append(node) | ||
| 483 | + return peers | ||
| 484 | + | ||
| 485 | + def __get_peers_for_play(self, ip): | ||
| 486 | + peers = [] | ||
| 487 | + for node in self.__nodes: | ||
| 488 | + if node.public_ip == ip and node.srs_status == "running": | ||
| 489 | + peers.append(node) | ||
| 490 | + return peers | ||
| 491 | + | ||
| 492 | + def __json_dump_nodes(self, peers): | ||
| 493 | + data = [] | ||
| 494 | + for node in peers: | ||
| 495 | + data.append(node.json_dump()) | ||
| 496 | + return data | ||
| 497 | + | ||
| 498 | + def GET(self, type=None, format=None, origin=None, vhost=None, port=None, stream=None): | ||
| 499 | + enable_crossdomain() | ||
| 500 | + | ||
| 501 | + self.__refresh_nodes() | ||
| 502 | + data = self.__json_dump_nodes(self.__nodes) | ||
| 503 | + | ||
| 504 | + ip = cherrypy.request.remote.ip | ||
| 505 | + if type is not None: | ||
| 506 | + server = origin | ||
| 507 | + peers = self.__get_peers_for_play(ip) | ||
| 508 | + if len(peers) > 0: | ||
| 509 | + server = peers[0].ip | ||
| 510 | + if type == "hls": | ||
| 511 | + hls_url = "http://%s:%s/%s.m3u8"%(server, port, stream) | ||
| 512 | + hls_url = hls_url.replace(".m3u8.m3u8", ".m3u8") | ||
| 513 | + if format == "html": | ||
| 514 | + return SrsUtility().hls_html(hls_url) | ||
| 515 | + else: | ||
| 516 | + #return hls_url | ||
| 517 | + raise cherrypy.HTTPRedirect(hls_url) | ||
| 518 | + elif type == "rtmp": | ||
| 519 | + rtmp_url = "rtmp://%s:%s/%s?vhost=%s/%s"%(server, port, stream.split("/")[0], vhost, stream.split("/")[1]) | ||
| 520 | + if format == "html": | ||
| 521 | + html = "%s?server=%s&port=%s&vhost=%s&app=%s&stream=%s&autostart=true"%( | ||
| 522 | + "http://demo.chnvideo.com:8085/srs/trunk/research/players/srs_player.html", | ||
| 523 | + server, port, vhost, stream.split("/")[0], stream.split("/")[1]) | ||
| 524 | + #return html | ||
| 525 | + raise cherrypy.HTTPRedirect(html) | ||
| 526 | + return rtmp_url | ||
| 527 | + | ||
| 528 | + return json.dumps({"code":Error.success, "data": data}) | ||
| 529 | + | ||
| 530 | + def PUT(self): | ||
| 531 | + enable_crossdomain() | ||
| 532 | + | ||
| 533 | + req = cherrypy.request.body.read() | ||
| 534 | + trace("put to nodes, req=%s"%(req)) | ||
| 535 | + try: | ||
| 536 | + json_req = json.loads(req) | ||
| 537 | + except Exception, ex: | ||
| 538 | + code = Error.system_parse_json | ||
| 539 | + trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) | ||
| 540 | + return json.dumps({"code":code, "data": None}) | ||
| 541 | + | ||
| 542 | + id = str(json_req["id"]) | ||
| 543 | + node = self.__get_node(id) | ||
| 544 | + if node is None: | ||
| 545 | + code = Error.cdn_node_not_exists | ||
| 546 | + trace("cdn node not exists, req=%s, id=%s, code=%s"%(req, id, code)) | ||
| 547 | + return json.dumps({"code":code, "data": None}) | ||
| 548 | + | ||
| 549 | + node.heartbeat = time.time() | ||
| 550 | + node.srs_status = str(json_req["srs_status"]) | ||
| 551 | + | ||
| 552 | + self.__refresh_nodes() | ||
| 553 | + peers = self.__get_peers(node) | ||
| 554 | + peers_data = self.__json_dump_nodes(peers) | ||
| 555 | + | ||
| 556 | + res = json.dumps({"code":Error.success, "data": {"id":node.id, "peers":peers_data}}) | ||
| 557 | + trace(res) | ||
| 558 | + return res | ||
| 559 | + | ||
| 560 | + def POST(self): | ||
| 561 | + enable_crossdomain() | ||
| 562 | + | ||
| 563 | + req = cherrypy.request.body.read() | ||
| 564 | + trace("post to nodes, req=%s"%(req)) | ||
| 565 | + try: | ||
| 566 | + json_req = json.loads(req) | ||
| 567 | + except Exception, ex: | ||
| 568 | + code = Error.system_parse_json | ||
| 569 | + trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) | ||
| 570 | + return json.dumps({"code":code, "data": None}) | ||
| 571 | + | ||
| 572 | + node = CdnNode() | ||
| 573 | + node.ip = str(json_req["ip"]); | ||
| 574 | + node.os = str(json_req["os"]); | ||
| 575 | + node.srs_status = str(json_req["srs_status"]) | ||
| 576 | + self.__nodes.append(node) | ||
| 577 | + | ||
| 578 | + self.__refresh_nodes() | ||
| 579 | + peers = self.__get_peers(node) | ||
| 580 | + peers_data = self.__json_dump_nodes(peers) | ||
| 581 | + | ||
| 582 | + res = json.dumps({"code":Error.success, "data": {"id":node.id, "peers":peers_data}}) | ||
| 583 | + trace(res) | ||
| 584 | + return res | ||
| 585 | + | ||
| 586 | + def OPTIONS(self, *args, **kwargs): | ||
| 587 | + enable_crossdomain() | ||
| 412 | 588 | ||
| 413 | global_chat_id = os.getpid(); | 589 | global_chat_id = os.getpid(); |
| 414 | ''' | 590 | ''' |
| @@ -572,6 +748,7 @@ class V1(object): | @@ -572,6 +748,7 @@ class V1(object): | ||
| 572 | self.sessions = RESTSessions() | 748 | self.sessions = RESTSessions() |
| 573 | self.chats = RESTChats() | 749 | self.chats = RESTChats() |
| 574 | self.servers = RESTServers() | 750 | self.servers = RESTServers() |
| 751 | + self.nodes = RESTNodes() | ||
| 575 | def GET(self): | 752 | def GET(self): |
| 576 | enable_crossdomain(); | 753 | enable_crossdomain(); |
| 577 | return json.dumps({"code":Error.success, "urls":{ | 754 | return json.dumps({"code":Error.success, "urls":{ |
| @@ -579,15 +756,20 @@ class V1(object): | @@ -579,15 +756,20 @@ class V1(object): | ||
| 579 | "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.", | 756 | "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.", |
| 580 | "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream", | 757 | "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream", |
| 581 | "chats": "for srs demo meeting, the chat streams, public chat room.", | 758 | "chats": "for srs demo meeting, the chat streams, public chat room.", |
| 759 | + "nodes": { | ||
| 760 | + "summary": "for srs cdn node", | ||
| 761 | + "POST ip=node_ip&os=node_os": "register a new node", | ||
| 762 | + "GET": "get the active edge nodes", | ||
| 763 | + "GET type=hls&format=html&origin=demo.chnvideo.com&port=8080&stream=live/livestream": "get the play url, html for hls", | ||
| 764 | + "GET type=rtmp&format=html&origin=demo.chnvideo.com&vhost=demo.srs.com&port=1935&stream=live/livestream": "get the play url, for rtmp" | ||
| 765 | + }, | ||
| 582 | "servers": { | 766 | "servers": { |
| 583 | "summary": "for srs raspberry-pi and meeting demo", | 767 | "summary": "for srs raspberry-pi and meeting demo", |
| 584 | "GET": "get the current raspberry-pi servers info", | 768 | "GET": "get the current raspberry-pi servers info", |
| 585 | - "POST": { | ||
| 586 | - "body": "the new raspberry-pi server ip." | ||
| 587 | - }, | 769 | + "POST body=ip": "the new raspberry-pi server ip.", |
| 588 | "GET id=ingest&action=play&stream=live/livestream": "play the ingest HLS stream on raspberry-pi", | 770 | "GET id=ingest&action=play&stream=live/livestream": "play the ingest HLS stream on raspberry-pi", |
| 589 | "GET id=ingest&action=rtmp&stream=live/livestream": "play the ingest RTMP stream on raspberry-pi", | 771 | "GET id=ingest&action=rtmp&stream=live/livestream": "play the ingest RTMP stream on raspberry-pi", |
| 590 | - "GET id=ingest&action=hls&stream=live/livestream.m3u8": "play the ingest HLS stream on raspberry-pi", | 772 | + "GET id=ingest&action=hls&stream=live/livestream": "play the ingest HLS stream on raspberry-pi", |
| 591 | "GET id=ingest&action=mgmt": "open the HTTP api url of raspberry-pi", | 773 | "GET id=ingest&action=mgmt": "open the HTTP api url of raspberry-pi", |
| 592 | "GET id=meeting": "redirect to local raspberry-pi meeting url(local ignored)", | 774 | "GET id=meeting": "redirect to local raspberry-pi meeting url(local ignored)", |
| 593 | "GET id=meeting&local=false&index=0": "play the first(index=0) meeting HLS stream on demo.chnvideo.com(not local)", | 775 | "GET id=meeting&local=false&index=0": "play the first(index=0) meeting HLS stream on demo.chnvideo.com(not local)", |
-
请 注册 或 登录 后发表评论