mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
merge upstream
This commit is contained in:
commit
00fb37a831
41 changed files with 2443 additions and 271 deletions
|
@ -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.
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
|
|
|
@ -1,3 +1,28 @@
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* player specified size.
|
||||
*/
|
||||
function srs_get_player_modal() { return 740; }
|
||||
function srs_get_player_width() { return srs_get_player_modal() - 30; }
|
||||
function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
|
||||
|
||||
// to query the swf anti cache.
|
||||
function srs_get_version_code() { return "1.9"; }
|
||||
// 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".
|
||||
// if not equals to the player vhost, return the orignal vhost.
|
||||
function srs_get_player_publish_vhost(src_vhost) { return (src_vhost != srs_get_player_vhost())? src_vhost:(src_vhost + "_pub"); }
|
||||
// for chat, use rtmp only vhost, low latecy, without gop cache.
|
||||
function srs_get_player_chat_vhost(src_vhost) { return (src_vhost != srs_get_player_vhost())? src_vhost:(src_vhost + "_pub_rtmp"); }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -20,12 +45,38 @@ function padding(number, length, prefix) {
|
|||
function update_nav() {
|
||||
$("#nav_srs_player").attr("href", "srs_player.html" + window.location.search);
|
||||
$("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search);
|
||||
$("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search);
|
||||
$("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search);
|
||||
$("#nav_jwplayer6").attr("href", "jwplayer6.html" + window.location.search);
|
||||
$("#nav_osmf").attr("href", "osmf.html" + window.location.search);
|
||||
$("#nav_vlc").attr("href", "vlc.html" + window.location.search);
|
||||
}
|
||||
|
||||
/**
|
||||
* log specified, there must be a log element as:
|
||||
<!-- for the log -->
|
||||
<div class="alert alert-info fade in" id="txt_log">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<strong><span id="txt_log_title">Usage:</span></strong>
|
||||
<span id="txt_log_msg">创建会议室,或者加入会议室</span>
|
||||
</div>
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -82,6 +133,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_chat_vhost(vhost);
|
||||
return "rtmp://" + server + ":" + port + "/" + app + "...vhost..." + vhost + "/" + stream;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@param server the ip of server. default to window.location.hostname
|
||||
|
@ -150,23 +218,6 @@ function srs_parse_rtmp_url(rtmp_url) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* player specified size.
|
||||
*/
|
||||
function srs_get_player_modal() { return 740; }
|
||||
function srs_get_player_width() { return srs_get_player_modal() - 30; }
|
||||
function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
|
||||
|
||||
// to query the swf anti cache.
|
||||
function srs_get_version_code() { return "1.5"; }
|
||||
// get the default vhost for players.
|
||||
function srs_get_player_vhost() { return "players"; }
|
||||
// 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".
|
||||
// if not equals to the player vhost, return the orignal vhost.
|
||||
function srs_get_player_publish_vhost(src_vhost) { return (src_vhost != srs_get_player_vhost())? src_vhost:(src_vhost + "_pub"); }
|
||||
|
||||
/**
|
||||
* initialize the page.
|
||||
* @param rtmp_url the div id contains the rtmp stream url to play
|
||||
|
@ -187,6 +238,190 @@ 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("<option value='" + i + "'>" + cameras[i] + "</option");
|
||||
}
|
||||
// optional: select the first no "virtual" signed.
|
||||
for (var i = 0; i < cameras.length; i++) {
|
||||
if (cameras[i].toLowerCase().indexOf("virtual") == -1) {
|
||||
$(sl_cameras + " option[value='" + i + "']").attr("selected", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$(sl_microphones).empty();
|
||||
for (var i = 0; i < microphones.length; i++) {
|
||||
$(sl_microphones).append("<option value='" + i + "'>" + microphones[i] + "</option");
|
||||
}
|
||||
|
||||
$(sl_vcodec).empty();
|
||||
var vcodecs = ["h264", "vp6"];
|
||||
for (var i = 0; i < vcodecs.length; i++) {
|
||||
$(sl_vcodec).append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");
|
||||
}
|
||||
|
||||
$(sl_profile).empty();
|
||||
var profiles = ["baseline", "main"];
|
||||
for (var i = 0; i < profiles.length; i++) {
|
||||
$(sl_profile).append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");
|
||||
}
|
||||
$(sl_profile + " option[value='main']").attr("selected", true);
|
||||
|
||||
$(sl_level).empty();
|
||||
var levels = ["1", "1b", "1.1", "1.2", "1.3",
|
||||
"2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
|
||||
for (var i = 0; i < levels.length; i++) {
|
||||
$(sl_level).append("<option value='" + levels[i] + "'>" + levels[i] + "</option");
|
||||
}
|
||||
$(sl_level + " option[value='4.1']").attr("selected", true);
|
||||
|
||||
$(sl_gop).empty();
|
||||
var gops = ["0.3", "0.5", "1", "2", "3", "4",
|
||||
"5", "6", "7", "8", "9", "10", "15", "20"];
|
||||
for (var i = 0; i < gops.length; i++) {
|
||||
$(sl_gop).append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");
|
||||
}
|
||||
$(sl_gop + " option[value='10']").attr("selected", true);
|
||||
|
||||
$(sl_size).empty();
|
||||
var sizes = ["176x144", "320x240", "352x240",
|
||||
"352x288", "460x240", "640x480", "720x480", "720x576", "800x600",
|
||||
"1024x768", "1280x720", "1360x768", "1920x1080"];
|
||||
for (i = 0; i < sizes.length; i++) {
|
||||
$(sl_size).append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");
|
||||
}
|
||||
$(sl_size + " option[value='640x480']").attr("selected", true);
|
||||
|
||||
$(sl_fps).empty();
|
||||
var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
|
||||
for (i = 0; i < fpses.length; i++) {
|
||||
$(sl_fps).append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");
|
||||
}
|
||||
$(sl_fps + " option[value='20']").attr("selected", true);
|
||||
|
||||
$(sl_bitrate).empty();
|
||||
var bitrates = ["50", "200", "350", "500", "650", "800",
|
||||
"950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
|
||||
for (i = 0; i < bitrates.length; i++) {
|
||||
$(sl_bitrate).append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");
|
||||
}
|
||||
$(sl_bitrate + " option[value='500']").attr("selected", true);
|
||||
}
|
||||
/**
|
||||
* for chat, use low latecy settings.
|
||||
*/
|
||||
function srs_chat_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("<option value='" + i + "'>" + cameras[i] + "</option");
|
||||
}
|
||||
// optional: select the first no "virtual" signed.
|
||||
for (var i = 0; i < cameras.length; i++) {
|
||||
if (cameras[i].toLowerCase().indexOf("virtual") == -1) {
|
||||
$(sl_cameras + " option[value='" + i + "']").attr("selected", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$(sl_microphones).empty();
|
||||
for (var i = 0; i < microphones.length; i++) {
|
||||
$(sl_microphones).append("<option value='" + i + "'>" + microphones[i] + "</option");
|
||||
}
|
||||
|
||||
$(sl_vcodec).empty();
|
||||
var vcodecs = ["h264", "vp6"];
|
||||
for (var i = 0; i < vcodecs.length; i++) {
|
||||
$(sl_vcodec).append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");
|
||||
}
|
||||
|
||||
$(sl_profile).empty();
|
||||
var profiles = ["baseline", "main"];
|
||||
for (var i = 0; i < profiles.length; i++) {
|
||||
$(sl_profile).append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");
|
||||
}
|
||||
$(sl_profile + " option[value='baseline']").attr("selected", true);
|
||||
|
||||
$(sl_level).empty();
|
||||
var levels = ["1", "1b", "1.1", "1.2", "1.3",
|
||||
"2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
|
||||
for (var i = 0; i < levels.length; i++) {
|
||||
$(sl_level).append("<option value='" + levels[i] + "'>" + levels[i] + "</option");
|
||||
}
|
||||
$(sl_level + " option[value='3.1']").attr("selected", true);
|
||||
|
||||
$(sl_gop).empty();
|
||||
var gops = ["0.3", "0.5", "1", "2", "3", "4",
|
||||
"5", "6", "7", "8", "9", "10", "15", "20"];
|
||||
for (var i = 0; i < gops.length; i++) {
|
||||
$(sl_gop).append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");
|
||||
}
|
||||
$(sl_gop + " option[value='0.5']").attr("selected", true);
|
||||
|
||||
$(sl_size).empty();
|
||||
var sizes = ["176x144", "320x240", "352x240",
|
||||
"352x288", "460x240", "640x480", "720x480", "720x576", "800x600",
|
||||
"1024x768", "1280x720", "1360x768", "1920x1080"];
|
||||
for (i = 0; i < sizes.length; i++) {
|
||||
$(sl_size).append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");
|
||||
}
|
||||
$(sl_size + " option[value='460x240']").attr("selected", true);
|
||||
|
||||
$(sl_fps).empty();
|
||||
var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
|
||||
for (i = 0; i < fpses.length; i++) {
|
||||
$(sl_fps).append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");
|
||||
}
|
||||
$(sl_fps + " option[value='15']").attr("selected", true);
|
||||
|
||||
$(sl_bitrate).empty();
|
||||
var bitrates = ["50", "200", "350", "500", "650", "800",
|
||||
"950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
|
||||
for (i = 0; i < bitrates.length; i++) {
|
||||
$(sl_bitrate).append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");
|
||||
}
|
||||
$(sl_bitrate + " option[value='350']").attr("selected", true);
|
||||
}
|
||||
/**
|
||||
* get the vcodec and acodec.
|
||||
*/
|
||||
function srs_publiser_get_codec(
|
||||
vcodec, acodec,
|
||||
sl_cameras, sl_microphones, sl_vcodec, sl_profile, sl_level, sl_gop, sl_size, sl_fps, sl_bitrate
|
||||
) {
|
||||
acodec.device_code = $(sl_microphones).val();
|
||||
acodec.device_name = $(sl_microphones).text();
|
||||
|
||||
vcodec.device_code = $(sl_cameras).find("option:selected").val();
|
||||
vcodec.device_name = $(sl_cameras).find("option:selected").text();
|
||||
|
||||
vcodec.codec = $(sl_vcodec).find("option:selected").val();
|
||||
vcodec.profile = $(sl_profile).find("option:selected").val();
|
||||
vcodec.level = $(sl_level).find("option:selected").val();
|
||||
vcodec.fps = $(sl_fps).find("option:selected").val();
|
||||
vcodec.gop = $(sl_gop).find("option:selected").val();
|
||||
vcodec.size = $(sl_size).find("option:selected").val();
|
||||
vcodec.bitrate = $(sl_bitrate).find("option:selected").val();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -196,8 +431,10 @@ function srs_init(rtmp_url, hls_url, modal_player) {
|
|||
* @param container the html container id.
|
||||
* @param width a float value specifies the width of player.
|
||||
* @param height a float value specifies the height of player.
|
||||
* @param private_object [optional] an object that used as private object,
|
||||
* for example, the logic chat object which owner this player.
|
||||
*/
|
||||
function SrsPlayer(container, width, height) {
|
||||
function SrsPlayer(container, width, height, private_object) {
|
||||
if (!SrsPlayer.__id) {
|
||||
SrsPlayer.__id = 100;
|
||||
}
|
||||
|
@ -207,6 +444,7 @@ function SrsPlayer(container, width, height) {
|
|||
|
||||
SrsPlayer.__players.push(this);
|
||||
|
||||
this.private_object = private_object;
|
||||
this.container = container;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
@ -222,11 +460,16 @@ function SrsPlayer(container, width, height) {
|
|||
}
|
||||
/**
|
||||
* user can set some callback, then start the player.
|
||||
* @param url the default url.
|
||||
* callbacks:
|
||||
* on_player_ready():int, when srs player ready, user can play.
|
||||
* on_player_metadata(metadata:Object):int, when srs player get metadata.
|
||||
*/
|
||||
SrsPlayer.prototype.start = function() {
|
||||
SrsPlayer.prototype.start = function(url) {
|
||||
if (url) {
|
||||
this.stream_url = url;
|
||||
}
|
||||
|
||||
// embed the flash.
|
||||
var flashvars = {};
|
||||
flashvars.id = this.id;
|
||||
|
@ -261,7 +504,9 @@ SrsPlayer.prototype.start = function() {
|
|||
* @param stream_url the url of stream, rtmp or http.
|
||||
*/
|
||||
SrsPlayer.prototype.play = function(url) {
|
||||
this.stream_url = url;
|
||||
if (url) {
|
||||
this.stream_url = url;
|
||||
}
|
||||
this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time);
|
||||
}
|
||||
SrsPlayer.prototype.stop = function() {
|
||||
|
@ -373,8 +618,10 @@ function __srs_on_player_timer(id, time, buffer_length) {
|
|||
* @param container the html container id.
|
||||
* @param width a float value specifies the width of publisher.
|
||||
* @param height a float value specifies the height of publisher.
|
||||
* @param private_object [optional] an object that used as private object,
|
||||
* for example, the logic chat object which owner this publisher.
|
||||
*/
|
||||
function SrsPublisher(container, width, height) {
|
||||
function SrsPublisher(container, width, height, private_object) {
|
||||
if (!SrsPublisher.__id) {
|
||||
SrsPublisher.__id = 100;
|
||||
}
|
||||
|
@ -384,6 +631,7 @@ function SrsPublisher(container, width, height) {
|
|||
|
||||
SrsPublisher.__publishers.push(this);
|
||||
|
||||
this.private_object = private_object;
|
||||
this.container = container;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li class="active"><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li class="active"><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li class="active"><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
|
|
661
trunk/research/players/srs_chat.html
Executable file
661
trunk/research/players/srs_chat.html
Executable file
|
@ -0,0 +1,661 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SRS</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
|
||||
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="js/swfobject.js"></script>
|
||||
<script type="text/javascript" src="js/srs.js"></script>
|
||||
<style>
|
||||
body{
|
||||
padding-top: 55px;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
var srs_publisher = null;
|
||||
var realtime_player = null;
|
||||
var api_server = null;
|
||||
var self_chat = null;
|
||||
var global_chat_user_id = 200;
|
||||
var previous_chats = [];
|
||||
var no_play = false;
|
||||
|
||||
$(function(){
|
||||
// get the vhost and port to set the default url.
|
||||
// for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo
|
||||
// url set to: rtmp://demo:1935/live/livestream
|
||||
srs_init_publish("#txt_url");
|
||||
|
||||
$("#realtime_player_url").tooltip({
|
||||
title: "右键复制RTMP地址"
|
||||
});
|
||||
|
||||
// if no play specified, donot show the player, for debug the publisher.
|
||||
var query = parse_query_string();
|
||||
if (query.no_play == "true") {
|
||||
no_play = true;
|
||||
}
|
||||
|
||||
$("#btn_video_settings").click(function(){
|
||||
$("#video_modal").modal({show:true});
|
||||
});
|
||||
$("#btn_audio_settings").click(function(){
|
||||
$("#audio_modal").modal({show:true});
|
||||
});
|
||||
$("#btn_join").click(on_user_publish);
|
||||
|
||||
// for publish, we use randome stream name.
|
||||
$("#txt_url").val($("#txt_url").val() + "." + new Date().getTime());
|
||||
|
||||
// start the publisher.
|
||||
srs_publisher = new SrsPublisher("local_publisher", 430, 185);
|
||||
srs_publisher.on_publisher_ready = function(cameras, microphones) {
|
||||
srs_chat_initialize_page(
|
||||
cameras, microphones,
|
||||
"#sl_cameras", "#sl_microphones",
|
||||
"#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
|
||||
"#sl_fps", "#sl_bitrate"
|
||||
);
|
||||
};
|
||||
srs_publisher.on_publisher_error = function(code, desc) {
|
||||
error(code, desc);
|
||||
};
|
||||
srs_publisher.on_publisher_warn = function(code, desc) {
|
||||
warn(code, desc);
|
||||
};
|
||||
srs_publisher.start();
|
||||
|
||||
update_play_url();
|
||||
|
||||
if (!no_play) {
|
||||
// start the realtime player.
|
||||
realtime_player = new SrsPlayer("realtime_player", 430, 185);
|
||||
realtime_player.on_player_ready = function() {
|
||||
this.set_bt(0.5);
|
||||
this.set_fs("screen", 100);
|
||||
};
|
||||
realtime_player.start();
|
||||
}
|
||||
|
||||
api_server = "http://" + query.hostname + ":" + srs_get_api_server_port() + "/api/v1/chats";
|
||||
refresh();
|
||||
});
|
||||
|
||||
function update_play_url() {
|
||||
var url = $("#txt_url").val();
|
||||
|
||||
$("#realtime_player_url").attr("href", url).attr("target", "_blank");
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (!self_chat) {
|
||||
setTimeout(refresh, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type : "GET",
|
||||
async : true,
|
||||
url : api_server,
|
||||
contentType: "text/html",
|
||||
data : "",
|
||||
dataType : "json",
|
||||
complete : function() {
|
||||
},
|
||||
error : function(ret) {
|
||||
setTimeout(refresh, 5000);
|
||||
warn(101, "查询会议室失败:" + JSON.stringify(ret));
|
||||
},
|
||||
success : function(ret) {
|
||||
if(0 != ret["code"]) {
|
||||
warn(102, "查询会议室失败: " + JSON.stringify(ret));
|
||||
setTimeout(refresh, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
var chats = ret["data"]["chats"];
|
||||
var server_time = ret["now"];
|
||||
|
||||
on_get_chats(chats);
|
||||
setTimeout(refresh, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
function on_get_chats(chats) {
|
||||
if (!self_chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get self, check self is valid?
|
||||
var _self_chat = null;
|
||||
for (var i = 0; i < chats.length; i++) {
|
||||
var chat = chats[i];
|
||||
if (self_chat && self_chat.id == chat.id) {
|
||||
_self_chat = chat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// rejoin if invalid.
|
||||
if (!_self_chat) {
|
||||
on_user_exit_chat(function(){
|
||||
on_user_join_chat(function(){
|
||||
info("重新加入会议室成功");
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
render_chat_room(chats);
|
||||
|
||||
if (!self_chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update self heartbeat.
|
||||
var url = api_server + "/" + self_chat.id;
|
||||
|
||||
var chat = {};
|
||||
chat.localtime = new Date().getTime();
|
||||
|
||||
// publish to api server to requires an id.
|
||||
$.ajax({
|
||||
type : "PUT",
|
||||
async : true,
|
||||
url : url,
|
||||
contentType: "text/html",
|
||||
data : JSON.stringify(chat),
|
||||
dataType : "json",
|
||||
complete : function() {
|
||||
},
|
||||
error : function(ret) {
|
||||
warn(105, "更新会议室信息失败:" + JSON.stringify(ret));
|
||||
},
|
||||
success : function(ret) {
|
||||
if(0 != ret["code"]) {
|
||||
warn(106, "更新会议室信息失败:: " + JSON.stringify(ret));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function render_chat_room(chats) {
|
||||
if (!self_chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
// new added chat
|
||||
for (var i = 0; i < chats.length; i++) {
|
||||
var chat = chats[i];
|
||||
// ignore the self.
|
||||
if (self_chat && self_chat.id == chat.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if previous exists, ignore, only add new here.
|
||||
var previous_chat = get_previous_chat_user(previous_chats, chat.id);
|
||||
if (previous_chat) {
|
||||
// update reference.
|
||||
chat.player = previous_chat.player;
|
||||
chat.player.private_object = chat;
|
||||
continue;
|
||||
}
|
||||
|
||||
global_chat_user_id++;
|
||||
|
||||
// username: a str indicates the user name.
|
||||
// url: a str indicates the url of user stream.
|
||||
// join_date: a str indicates the join timestamp in seconds.
|
||||
// join_date_str: friendly formated time.
|
||||
var obj = $("<div/>").html($("#template").html());
|
||||
$(obj).attr("chat_id", chat.id);
|
||||
$(obj).attr("id", "div_" + chat.id); // for specifed chat: $("#div_" + chat_id)
|
||||
$(obj).attr("class", "div_chat"); // for all chats: $(".div_chat")
|
||||
$(obj).find("#chat_player").attr("id", "rp_" + chat.id); // for specifed player: $("#rp_" + chat_id)
|
||||
$(obj).find("#chat_player_raw").attr("id", "rp_raw_" + chat.id); // for specifed player: $("#rp_raw_" + chat_id)
|
||||
$(obj).find("#user_name").text(chat.username);
|
||||
$(obj).find("#join_date").text(chat.join_date_str);
|
||||
$(obj).find("#collapseM").attr("id", "collapse_" + global_chat_user_id);
|
||||
$(obj).find("#headerN").attr("href", "#collapse_" + global_chat_user_id);
|
||||
|
||||
$("#lst_chats").append(obj);
|
||||
|
||||
if (!no_play) {
|
||||
// start the realtime player.
|
||||
var _player = new SrsPlayer("rp_raw_" + chat.id, 600, 300, chat);
|
||||
_player.on_player_ready = function() {
|
||||
this.set_bt(0.5);
|
||||
this.set_fs("screen", 100);
|
||||
};
|
||||
_player.start(chat.url);
|
||||
|
||||
chat.player = _player;
|
||||
} else {
|
||||
chat.player = null;
|
||||
}
|
||||
|
||||
$(obj).find("#collapse_main").on('hidden', function(){
|
||||
var id = $(this).parent().attr("chat_id");
|
||||
var chat = get_previous_chat_user(previous_chats, id);
|
||||
if (!chat || !chat.player) {
|
||||
return;
|
||||
}
|
||||
chat.player.stop();
|
||||
});
|
||||
$(obj).find("#collapse_main").on('shown', function(){
|
||||
var id = $(this).parent().attr("chat_id");
|
||||
var chat = get_previous_chat_user(previous_chats, id);
|
||||
if (!chat || !chat.player) {
|
||||
return;
|
||||
}
|
||||
chat.player.play();
|
||||
});
|
||||
}
|
||||
|
||||
// removed chat
|
||||
for (var i = 0; i < previous_chats.length; i++) {
|
||||
var chat = previous_chats[i];
|
||||
// ignore the self.
|
||||
if (self_chat && self_chat.id == chat.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var new_chat = get_previous_chat_user(chats, chat.id);
|
||||
if (new_chat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chat.player) {
|
||||
chat.player.stop();
|
||||
}
|
||||
$("#div_" + chat.id).remove();
|
||||
}
|
||||
|
||||
previous_chats = chats;
|
||||
}
|
||||
function get_previous_chat_user(arr, id) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var chat = arr[i];
|
||||
if (id == chat.id) {
|
||||
return chat;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function on_user_publish() {
|
||||
if ($("#txt_name").val().trim() == "") {
|
||||
$("#txt_name").focus().parent().parent().addClass("error");
|
||||
warn(100, "请输入您的名字");
|
||||
return;
|
||||
}
|
||||
|
||||
$("#txt_name").parent().parent().removeClass("error");
|
||||
|
||||
// join chat.
|
||||
if (!self_chat) {
|
||||
on_user_join_chat();
|
||||
} else {
|
||||
on_user_exit_chat();
|
||||
}
|
||||
}
|
||||
function on_user_exit_chat(complete_pfn) {
|
||||
srs_publisher.stop();
|
||||
$("#btn_join").text("加入会议");
|
||||
|
||||
if (realtime_player) {
|
||||
realtime_player.stop();
|
||||
}
|
||||
|
||||
if (!self_chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
// removed chat
|
||||
for (var i = 0; i < previous_chats.length; i++) {
|
||||
var chat = previous_chats[i];
|
||||
// ignore the self.
|
||||
if (self_chat && self_chat.id == chat.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chat.player) {
|
||||
chat.player.stop();
|
||||
}
|
||||
$("#div_" + chat.id).remove();
|
||||
}
|
||||
previous_chats = [];
|
||||
|
||||
var url = api_server + "/" + self_chat.id;
|
||||
// whatever, cleanup local chat.
|
||||
self_chat = null;
|
||||
|
||||
$("#btn_join").attr("disabled", true);
|
||||
|
||||
// publish to api server to requires an id.
|
||||
$.ajax({
|
||||
type : "DELETE",
|
||||
async : true,
|
||||
url : url,
|
||||
contentType: "text/html",
|
||||
data : "",
|
||||
dataType : "json",
|
||||
complete : function() {
|
||||
$("#btn_join").attr("disabled", false);
|
||||
if (complete_pfn) {
|
||||
complete_pfn();
|
||||
}
|
||||
},
|
||||
error : function(ret) {
|
||||
warn(103, "退出会议室失败");
|
||||
},
|
||||
success : function(ret) {
|
||||
if(0 != ret["code"]) {
|
||||
warn(104, "退出会议室失败");
|
||||
return;
|
||||
}
|
||||
info("退出会议室成功");
|
||||
}
|
||||
});
|
||||
}
|
||||
function on_user_join_chat(complete_pfn) {
|
||||
if (self_chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = $("#txt_url").val();
|
||||
var vcodec = {};
|
||||
var acodec = {};
|
||||
srs_publiser_get_codec(
|
||||
vcodec, acodec,
|
||||
"#sl_cameras", "#sl_microphones",
|
||||
"#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
|
||||
"#sl_fps", "#sl_bitrate"
|
||||
);
|
||||
|
||||
var chat = {};
|
||||
chat.id = -1;
|
||||
chat.username = $("#txt_name").val().trim();
|
||||
chat.agent = navigator.userAgent;
|
||||
chat.url = url;
|
||||
chat.vcodec = vcodec;
|
||||
chat.acodec = acodec;
|
||||
|
||||
$("#btn_join").attr("disabled", true);
|
||||
|
||||
// publish to api server to requires an id.
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
async : true,
|
||||
url : api_server,
|
||||
contentType: "text/html",
|
||||
data : JSON.stringify(chat),
|
||||
dataType : "json",
|
||||
complete : function() {
|
||||
$("#btn_join").attr("disabled", false);
|
||||
if (complete_pfn) {
|
||||
complete_pfn();
|
||||
}
|
||||
},
|
||||
error : function(ret) {
|
||||
warn(105, "创建会议室失败:" + JSON.stringify(ret));
|
||||
},
|
||||
success : function(ret) {
|
||||
if(0 != ret["code"]) {
|
||||
warn(106, "创建会议室失败: " + JSON.stringify(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
chat.id = ret["data"];
|
||||
|
||||
// success, start publish.
|
||||
self_chat = chat;
|
||||
|
||||
$("#btn_join").text("退出会议");
|
||||
|
||||
info("开始推流到服务器");
|
||||
srs_publisher.publish(url, vcodec, acodec);
|
||||
|
||||
if (realtime_player) {
|
||||
// directly play the url for the realtime player.
|
||||
realtime_player.stop();
|
||||
realtime_player.play(url);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<a class="brand" href="index.html">SRS</a>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li class="active"><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
<li><a id="nav_vlc" href="vlc.html">VLC播放器</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<!-- for the log -->
|
||||
<div class="alert alert-info fade in" id="txt_log">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<strong><span id="txt_log_title">Usage:</span></strong>
|
||||
<span id="txt_log_msg">输入名字,设置编码参数后,加入会议室</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="form-inline">
|
||||
<input type="text" id="txt_name" class="input-small" placeholder="您的名字..." value=""></input>
|
||||
<button class="btn input-medium" id="btn_video_settings">视频编码配置</button>
|
||||
<button class="btn input-medium" id="btn_audio_settings">音频编码配置</button>
|
||||
<button class="btn input-medium btn-primary" id="btn_join">加入会议</button>
|
||||
<input type="text" id="txt_url" class="input-mini hide" value=""></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<span class="accordion-toggle">
|
||||
<strong>[我的] 本地摄像头</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="accordion-body collapse in">
|
||||
<div class="accordion-inner">
|
||||
<div id="local_publisher"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<span class="accordion-toggle">
|
||||
<strong>[我的] 远程服务器流</strong>
|
||||
<a id="realtime_player_url" href="#" data-toggle="tooltip" data-placement="top" title="">
|
||||
播放地址<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="accordion-body collapse in">
|
||||
<div class="accordion-inner">
|
||||
<div id="realtime_player"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container hide" id="template">
|
||||
<div class="accordion-group" id="collapse_main">
|
||||
<div class="accordion-heading" title="点击展开或收起,收起后停止播放流,展开时从服务器请求流">
|
||||
<span id="headerN" class="accordion-toggle" data-toggle="collapse" href="#collapseN">
|
||||
<strong>[<a href="#"><span id="user_name">XX</span></a>]</strong>
|
||||
<strong>加入时间</strong>[<span id="join_date"></span>]
|
||||
<img src="img/tooltip.png"/>
|
||||
</span>
|
||||
</div>
|
||||
<div id="collapseM" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
</div>
|
||||
<div class="span8">
|
||||
<div id="chat_player">
|
||||
<div id="chat_player_raw">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" id="lst_chats">
|
||||
</div>
|
||||
<div id="video_modal" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>视频编码</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_cameras">
|
||||
摄像头
|
||||
<a id="sl_cameras_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span4" id="sl_cameras"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_vcodec">
|
||||
Codec
|
||||
<a id="sl_cameras_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span2" id="sl_vcodec"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_profile">
|
||||
Profile
|
||||
<a id="sl_profile_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span2" id="sl_profile"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_level">
|
||||
Level
|
||||
<a id="sl_level_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span2" id="sl_level"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_gop">
|
||||
GOP
|
||||
<a id="sl_gop_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span2" id="sl_gop"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_size">
|
||||
尺寸
|
||||
<a id="sl_size_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span2" id="sl_size"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_fps">
|
||||
帧率
|
||||
<a id="sl_fps_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span2" id="sl_fps"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_bitrate">
|
||||
码率
|
||||
<a id="sl_bitrate_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span2" id="sl_bitrate"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">设置</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="audio_modal" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>音频编码</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sl_microphones">
|
||||
麦克风
|
||||
<a id="worker_id_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
|
||||
<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select class="span4" id="sl_microphones"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">设置</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<footer>
|
||||
<p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team © 2013</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
srs_player = new SrsPlayer("player_id", srs_get_player_width(), srs_get_player_height());
|
||||
srs_player.on_player_ready = function() {
|
||||
select_buffer_time("#btn_bt_0_8", 0.8);
|
||||
srs_player.play(url);
|
||||
this.play(url);
|
||||
};
|
||||
srs_player.on_player_metadata = function(metadata) {
|
||||
$("#btn_dar_original").text("视频原始比例" + "(" + metadata.width + ":" + metadata.height + ")");
|
||||
|
@ -100,7 +100,7 @@
|
|||
select_fs_size("#btn_fs_size_screen_100", "screen", 100);
|
||||
};
|
||||
srs_player.on_player_timer = function(time, buffer_length) {
|
||||
var buffer = buffer_length / srs_player.buffer_time * 100;
|
||||
var buffer = buffer_length / this.buffer_time * 100;
|
||||
$("#pb_buffer").width(Number(buffer).toFixed(1) + "%");
|
||||
|
||||
$("#pb_buffer_bg").attr("title",
|
||||
|
@ -269,6 +269,7 @@
|
|||
<ul class="nav">
|
||||
<li class="active"><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
|
@ -316,7 +317,7 @@
|
|||
</div>
|
||||
<div id="collapse10" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<a href="#" id="srs_publish_hls">http://demo.srs.com/live/livestream.m3u8</a> <br/>
|
||||
<a href="#" id="srs_publish_hls" target="_blank">http://demo.srs.com/live/livestream.m3u8</a> <br/>
|
||||
<span>对用户的流进行HLS切片(若编码为非H264/AAC,HLS流会自动禁用)。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -342,7 +343,7 @@
|
|||
</div>
|
||||
<div id="collapse11" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<a href="#" id="srs_publish_ld_hls">http://demo.srs.com/live/livestream_ld.m3u8</a> <br/>
|
||||
<a href="#" id="srs_publish_ld_hls" target="_blank">http://demo.srs.com/live/livestream_ld.m3u8</a> <br/>
|
||||
<span>对转码配置LD流进行HLS切片。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -368,7 +369,7 @@
|
|||
</div>
|
||||
<div id="collapse12" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<a href="#" id="srs_publish_sd_hls">http://demo.srs.com/live/livestream_sd.m3u8</a> <br/>
|
||||
<a href="#" id="srs_publish_sd_hls" target="_blank">http://demo.srs.com/live/livestream_sd.m3u8</a> <br/>
|
||||
<span>对转码配置SD流进行HLS切片。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -394,7 +395,7 @@
|
|||
</div>
|
||||
<div id="collapse13" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<a href="#" id="srs_publish_fw_hls">http://demo.srs.com/forward/live/livestream.m3u8</a> <br/>
|
||||
<a href="#" id="srs_publish_fw_hls" target="_blank">http://demo.srs.com/forward/live/livestream.m3u8</a> <br/>
|
||||
<span>对转发原始流进行HLS切片(若编码为非H264/AAC,HLS流会自动禁用)。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -420,7 +421,7 @@
|
|||
</div>
|
||||
<div id="collapse14" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<a href="#" id="srs_publish_fw_ld_hls">http://demo.srs.com/forward/live/livestream_ld.m3u8</a> <br/>
|
||||
<a href="#" id="srs_publish_fw_ld_hls" target="_blank">http://demo.srs.com/forward/live/livestream_ld.m3u8</a> <br/>
|
||||
<span>对转发转码配置LD流进行HLS切片,所有转发的流会自动支持HLS。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -446,7 +447,7 @@
|
|||
</div>
|
||||
<div id="collapse15" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<a href="#" id="srs_publish_fw_sd_hls">http://demo.srs.com/forward/live/livestream_sd.m3u8</a> <br/>
|
||||
<a href="#" id="srs_publish_fw_sd_hls" target="_blank">http://demo.srs.com/forward/live/livestream_sd.m3u8</a> <br/>
|
||||
<span>对转发转码配置SD流进行HLS切片,所有转发的流会自动支持HLS。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,6 +37,12 @@
|
|||
$("#low_latecy_tips").tooltip({
|
||||
title: "服务器不转码直接转发FLASH编码器的流,所以延迟比支持HLS的流要低很多"
|
||||
});
|
||||
$("#realtime_player_url").tooltip({
|
||||
title: "右键复制RTMP地址"
|
||||
});
|
||||
$("#remote_player_url").tooltip({
|
||||
title: "右键复制RTMP地址"
|
||||
});
|
||||
|
||||
$("#btn_publish").click(on_user_publish);
|
||||
|
||||
|
@ -46,74 +52,12 @@
|
|||
// start the publisher.
|
||||
srs_publisher = new SrsPublisher("local_publisher", 430, 185);
|
||||
srs_publisher.on_publisher_ready = function(cameras, microphones) {
|
||||
$("#sl_cameras").empty();
|
||||
for (var i = 0; i < cameras.length; i++) {
|
||||
$("#sl_cameras").append("<option value='" + i + "'>" + cameras[i] + "</option");
|
||||
}
|
||||
// optional: select the first no "virtual" signed.
|
||||
for (var i = 0; i < cameras.length; i++) {
|
||||
if (cameras[i].toLowerCase().indexOf("virtual") == -1) {
|
||||
$("#sl_cameras option[value='" + i + "']").attr("selected", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$("#sl_microphones").empty();
|
||||
for (var i = 0; i < microphones.length; i++) {
|
||||
$("#sl_microphones").append("<option value='" + i + "'>" + microphones[i] + "</option");
|
||||
}
|
||||
|
||||
$("#sl_vcodec").empty();
|
||||
var vcodecs = ["h264", "vp6"];
|
||||
for (var i = 0; i < vcodecs.length; i++) {
|
||||
$("#sl_vcodec").append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");
|
||||
}
|
||||
|
||||
$("#sl_profile").empty();
|
||||
var profiles = ["baseline", "main"];
|
||||
for (var i = 0; i < profiles.length; i++) {
|
||||
$("#sl_profile").append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");
|
||||
}
|
||||
|
||||
$("#sl_level").empty();
|
||||
var levels = ["1", "1b", "1.1", "1.2", "1.3",
|
||||
"2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
|
||||
for (var i = 0; i < levels.length; i++) {
|
||||
$("#sl_level").append("<option value='" + levels[i] + "'>" + levels[i] + "</option");
|
||||
}
|
||||
$("#sl_level option[value='4.1']").attr("selected", true);
|
||||
|
||||
$("#sl_gop").empty();
|
||||
var gops = ["0.3", "0.5", "1", "2", "3", "4",
|
||||
"5", "6", "7", "8", "9", "10", "15", "20"];
|
||||
for (var i = 0; i < gops.length; i++) {
|
||||
$("#sl_gop").append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");
|
||||
}
|
||||
$("#sl_gop option[value='5']").attr("selected", true);
|
||||
|
||||
$("#sl_size").empty();
|
||||
var sizes = ["176x144", "320x240", "352x240",
|
||||
"352x288", "460x240", "640x480", "720x480", "720x576", "800x600",
|
||||
"1024x768", "1280x720", "1360x768", "1920x1080"];
|
||||
for (i = 0; i < sizes.length; i++) {
|
||||
$("#sl_size").append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");
|
||||
}
|
||||
$("#sl_size option[value='460x240']").attr("selected", true);
|
||||
|
||||
$("#sl_fps").empty();
|
||||
var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
|
||||
for (i = 0; i < fpses.length; i++) {
|
||||
$("#sl_fps").append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");
|
||||
}
|
||||
$("#sl_fps option[value='15']").attr("selected", true);
|
||||
|
||||
$("#sl_bitrate").empty();
|
||||
var bitrates = ["50", "200", "350", "500", "650", "800",
|
||||
"950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
|
||||
for (i = 0; i < bitrates.length; i++) {
|
||||
$("#sl_bitrate").append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");
|
||||
}
|
||||
$("#sl_bitrate option[value='350']").attr("selected", true);
|
||||
srs_publisher_initialize_page(
|
||||
cameras, microphones,
|
||||
"#sl_cameras", "#sl_microphones",
|
||||
"#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
|
||||
"#sl_fps", "#sl_bitrate"
|
||||
);
|
||||
};
|
||||
srs_publisher.on_publisher_error = function(code, desc) {
|
||||
error(code, desc);
|
||||
|
@ -123,22 +67,24 @@
|
|||
};
|
||||
srs_publisher.start();
|
||||
|
||||
update_play_url();
|
||||
|
||||
// if no play specified, donot show the player, for debug the publisher.
|
||||
var query = parse_query_string();
|
||||
if (query.no_play != "true") {
|
||||
// start the normal player with HLS supported.
|
||||
remote_player = new SrsPlayer("remote_player", 430, 185);
|
||||
remote_player.on_player_ready = function() {
|
||||
remote_player.set_bt(0.8);
|
||||
remote_player.set_fs("screen", 100);
|
||||
this.set_bt(0.8);
|
||||
this.set_fs("screen", 100);
|
||||
};
|
||||
remote_player.start();
|
||||
|
||||
// start the realtime player.
|
||||
realtime_player = new SrsPlayer("realtime_player", 430, 185);
|
||||
realtime_player.on_player_ready = function() {
|
||||
realtime_player.set_bt(0.8);
|
||||
realtime_player.set_fs("screen", 100);
|
||||
this.set_bt(0.8);
|
||||
this.set_fs("screen", 100);
|
||||
};
|
||||
realtime_player.start();
|
||||
}
|
||||
|
@ -157,6 +103,10 @@
|
|||
var ret = srs_parse_rtmp_url(url);
|
||||
var query = parse_query_string();
|
||||
|
||||
var remote_url = "rtmp://" + ret.server + ":" + ret.port + "/" + ret.app + "...vhost..." + srs_get_player_publish_vhost(ret.vhost) + "/" + ret.stream;
|
||||
$("#realtime_player_url").attr("href", url).attr("target", "_blank");
|
||||
$("#remote_player_url").attr("href", remote_url).attr("target", "_blank");
|
||||
|
||||
var srs_player_url = "http://" + query.host + query.dir + "/srs_player.html?";
|
||||
srs_player_url += "vhost=" + srs_get_player_publish_vhost(ret.vhost) + "&port=" + ret.port + "&app=" + ret.app + "&stream=" + ret.stream;
|
||||
srs_player_url += "&autostart=true";
|
||||
|
@ -180,10 +130,12 @@
|
|||
if ($("#btn_publish").text() == "停止发布") {
|
||||
srs_publisher.stop();
|
||||
$("#btn_publish").text("发布视频");
|
||||
$("#txt_play_realtime").text("RTMP低延时(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
$("#txt_play_url").text("RTMP已转码(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
$("#txt_play_hls").text("HLS-m3u8(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
$("#txt_play_jwplayer").text("HLS-JWPlayer(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
//$("#txt_play_realtime").text("RTMP低延时(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
//$("#txt_play_realtime").attr("href", "#").attr("target", "_self");
|
||||
//$("#txt_play_url").text("RTMP已转码(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
//$("#remote_player_url").attr("href", "#").attr("target", "_self");
|
||||
//$("#txt_play_hls").text("HLS-m3u8(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
//$("#txt_play_jwplayer").text("HLS-JWPlayer(请发布视频)").attr("href", "#").attr("target", "_self");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -194,20 +146,12 @@
|
|||
var url = $("#txt_url").val();
|
||||
var vcodec = {};
|
||||
var acodec = {};
|
||||
|
||||
acodec.device_code = $("#sl_microphones").val();
|
||||
acodec.device_name = $("#sl_microphones").text();
|
||||
|
||||
vcodec.device_code = $("#sl_cameras").find("option:selected").val();
|
||||
vcodec.device_name = $("#sl_cameras").find("option:selected").text();
|
||||
|
||||
vcodec.codec = $("#sl_vcodec").find("option:selected").val();
|
||||
vcodec.profile = $("#sl_profile").find("option:selected").val();
|
||||
vcodec.level = $("#sl_level").find("option:selected").val();
|
||||
vcodec.fps = $("#sl_fps").find("option:selected").val();
|
||||
vcodec.gop = $("#sl_gop").find("option:selected").val();
|
||||
vcodec.size = $("#sl_size").find("option:selected").val();
|
||||
vcodec.bitrate = $("#sl_bitrate").find("option:selected").val();
|
||||
srs_publiser_get_codec(
|
||||
vcodec, acodec,
|
||||
"#sl_cameras", "#sl_microphones",
|
||||
"#sl_vcodec", "#sl_profile", "#sl_level", "#sl_gop", "#sl_size",
|
||||
"#sl_fps", "#sl_bitrate"
|
||||
);
|
||||
|
||||
info("开始推流到服务器");
|
||||
srs_publisher.publish(url, vcodec, acodec);
|
||||
|
@ -230,22 +174,6 @@
|
|||
remote_player.play(pub_url);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -257,6 +185,7 @@
|
|||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li class="active"><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
|
@ -424,11 +353,11 @@
|
|||
<div class="span6">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<span class="accordion-toggle" data-toggle="collapse" href="#collapse1">
|
||||
<span class="accordion-toggle">
|
||||
<strong>本地摄像头</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div id="collapse1" class="accordion-body collapse in">
|
||||
<div class="accordion-body collapse in">
|
||||
<div class="accordion-inner">
|
||||
<div id="local_publisher"></div>
|
||||
</div>
|
||||
|
@ -438,14 +367,17 @@
|
|||
<div class="span6">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<span class="accordion-toggle" data-toggle="collapse" href="#collapse2">
|
||||
<span class="accordion-toggle">
|
||||
<strong>远程服务器</strong>
|
||||
<a id="remote_tips" href="#" data-toggle="tooltip" data-placement="top" title="">
|
||||
黑屏<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
<a id="remote_player_url" href="#" data-toggle="tooltip" data-placement="top" title="">
|
||||
播放地址<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div id="collapse2" class="accordion-body collapse in">
|
||||
<div class="accordion-body collapse in">
|
||||
<div class="accordion-inner">
|
||||
<div id="remote_player"></div>
|
||||
</div>
|
||||
|
@ -459,14 +391,17 @@
|
|||
<div class="span6">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<span class="accordion-toggle" data-toggle="collapse" href="#collapse3">
|
||||
<span class="accordion-toggle">
|
||||
<strong>远程服务器</strong>
|
||||
<a id="low_latecy_tips" href="#" data-toggle="tooltip" data-placement="top" title="">
|
||||
低延时<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
<a id="realtime_player_url" href="#" data-toggle="tooltip" data-placement="top" title="">
|
||||
播放地址<img src="img/tooltip.png"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div id="collapse3" class="accordion-body collapse in">
|
||||
<div class="accordion-body collapse in">
|
||||
<div class="accordion-inner">
|
||||
<div id="realtime_player"></div>
|
||||
</div>
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">SRS播放器</a></li>
|
||||
<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>
|
||||
<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>
|
||||
<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>
|
||||
<li><a id="nav_jwplayer6" href="jwplayer6.html">JWPlayer6播放器</a></li>
|
||||
<li><a id="nav_osmf" href="osmf.html">AdobeOSMF播放器</a></li>
|
||||
|
@ -39,7 +40,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<iframe id="main_frame" width="100%" height="800" frameBorder="0"></iframe>
|
||||
<iframe id="main_frame" width="100%" height="600" frameBorder="0"></iframe>
|
||||
</div>
|
||||
<div class="container">
|
||||
<hr>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue