winlin

support publish

@@ -116,11 +116,10 @@ function srs_init(rtmp_url, hls_url, modal_player) { @@ -116,11 +116,10 @@ function srs_init(rtmp_url, hls_url, modal_player) {
116 /** 116 /**
117 * the SrsPlayer object. 117 * the SrsPlayer object.
118 * @param container the html container id. 118 * @param container the html container id.
119 -* @param stream_url the url of stream, rtmp or http.  
120 * @param width a float value specifies the width of player. 119 * @param width a float value specifies the width of player.
121 * @param height a float value specifies the height of player. 120 * @param height a float value specifies the height of player.
122 */ 121 */
123 -function SrsPlayer(container, stream_url, width, height) { 122 +function SrsPlayer(container, width, height) {
124 if (!SrsPlayer.__id) { 123 if (!SrsPlayer.__id) {
125 SrsPlayer.__id = 100; 124 SrsPlayer.__id = 100;
126 } 125 }
@@ -131,12 +130,12 @@ function SrsPlayer(container, stream_url, width, height) { @@ -131,12 +130,12 @@ function SrsPlayer(container, stream_url, width, height) {
131 SrsPlayer.__players.push(this); 130 SrsPlayer.__players.push(this);
132 131
133 this.container = container; 132 this.container = container;
134 - this.stream_url = stream_url;  
135 this.width = width; 133 this.width = width;
136 this.height = height; 134 this.height = height;
137 this.id = SrsPlayer.__id++; 135 this.id = SrsPlayer.__id++;
138 - this.callbackObj = null; 136 + this.stream_url = null;
139 this.buffer_time = 0.8; // default to 0.8 137 this.buffer_time = 0.8; // default to 0.8
  138 + this.callbackObj = null;
140 139
141 // callback set the following values. 140 // callback set the following values.
142 this.meatadata = {}; // for on_player_metadata 141 this.meatadata = {}; // for on_player_metadata
@@ -178,7 +177,12 @@ SrsPlayer.prototype.start = function() { @@ -178,7 +177,12 @@ SrsPlayer.prototype.start = function() {
178 177
179 return this; 178 return this;
180 } 179 }
181 -SrsPlayer.prototype.play = function() { 180 +/**
  181 +* play the stream.
  182 +* @param stream_url the url of stream, rtmp or http.
  183 +*/
  184 +SrsPlayer.prototype.play = function(url) {
  185 + this.stream_url = url;
182 this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time); 186 this.callbackObj.ref.__play(this.stream_url, this.width, this.height, this.buffer_time);
183 } 187 }
184 SrsPlayer.prototype.stop = function() { 188 SrsPlayer.prototype.stop = function() {
@@ -233,7 +237,6 @@ SrsPlayer.prototype.set_bt = function(buffer_time) { @@ -233,7 +237,6 @@ SrsPlayer.prototype.set_bt = function(buffer_time) {
233 this.callbackObj.ref.__set_bt(buffer_time); 237 this.callbackObj.ref.__set_bt(buffer_time);
234 } 238 }
235 SrsPlayer.prototype.on_player_ready = function() { 239 SrsPlayer.prototype.on_player_ready = function() {
236 - this.play();  
237 } 240 }
238 SrsPlayer.prototype.on_player_metadata = function(metadata) { 241 SrsPlayer.prototype.on_player_metadata = function(metadata) {
239 // ignore. 242 // ignore.
@@ -286,4 +289,131 @@ function __srs_on_player_timer(id, time, buffer_length) { @@ -286,4 +289,131 @@ function __srs_on_player_timer(id, time, buffer_length) {
286 ////////////////////////////////////////////////////////////////////////////////// 289 //////////////////////////////////////////////////////////////////////////////////
287 ////////////////////////////////////////////////////////////////////////////////// 290 //////////////////////////////////////////////////////////////////////////////////
288 ////////////////////////////////////////////////////////////////////////////////// 291 //////////////////////////////////////////////////////////////////////////////////
  292 +/**
  293 +* the SrsPublisher object.
  294 +* @param container the html container id.
  295 +* @param width a float value specifies the width of publisher.
  296 +* @param height a float value specifies the height of publisher.
  297 +*/
  298 +function SrsPublisher(container, width, height) {
  299 + if (!SrsPublisher.__id) {
  300 + SrsPublisher.__id = 100;
  301 + }
  302 + if (!SrsPublisher.__publishers) {
  303 + SrsPublisher.__publishers = [];
  304 + }
  305 +
  306 + SrsPublisher.__publishers.push(this);
  307 +
  308 + this.container = container;
  309 + this.width = width;
  310 + this.height = height;
  311 + this.id = SrsPublisher.__id++;
  312 + this.callbackObj = null;
  313 +
  314 + // set the values when publish.
  315 + this.url = null;
  316 + this.vcodec = {};
  317 + this.acodec = {};
  318 +
  319 + // callback set the following values.
  320 + this.cameras = [];
  321 + this.microphones = [];
  322 + this.code = 0;
  323 +
  324 + // error code defines.
  325 + this.error_device_muted = 100;
  326 +}
  327 +/**
  328 +* user can set some callback, then start the publisher.
  329 +* callbacks:
  330 +* on_publisher_ready(cameras, microphones):int, when srs publisher ready, user can publish.
  331 +* on_publisher_error(code):int, when srs publisher error, callback this method.
  332 +*/
  333 +SrsPublisher.prototype.start = function() {
  334 + // embed the flash.
  335 + var flashvars = {};
  336 + flashvars.id = this.id;
  337 + flashvars.on_publisher_ready = "__srs_on_publisher_ready";
  338 + flashvars.on_publisher_error = "__srs_on_publisher_error";
  339 +
  340 + var params = {};
  341 + params.wmode = "opaque";
  342 + params.allowFullScreen = "true";
  343 + params.allowScriptAccess = "always";
  344 +
  345 + var attributes = {};
  346 +
  347 + var self = this;
  348 +
  349 + swfobject.embedSWF(
  350 + "srs_publisher/release/srs_publisher.swf", this.container,
  351 + this.width, this.height,
  352 + "11.1", "js/AdobeFlashPlayerInstall.swf",
  353 + flashvars, params, attributes,
  354 + function(callbackObj){
  355 + self.callbackObj = callbackObj;
  356 + }
  357 + );
  358 +
  359 + return this;
  360 +}
  361 +/**
  362 +* publish stream to server.
  363 +* @param url a string indicates the rtmp url to publish.
  364 +* @param vcodec an object contains the video codec info.
  365 +* @param acodec an object contains the audio codec info.
  366 +*/
  367 +SrsPublisher.prototype.publish = function(url, vcodec, acodec) {
  368 + this.url = url;
  369 + this.vcodec = vcodec;
  370 + this.acodec = acodec;
  371 +
  372 + this.callbackObj.ref.__publish(url, this.width, this.height, vcodec, acodec);
  373 +}
  374 +SrsPublisher.prototype.stop = function() {
  375 + this.callbackObj.ref.__stop();
  376 +}
  377 +/**
  378 +* when publisher ready.
  379 +* @param cameras a string array contains the names of cameras.
  380 +* @param microphones a string array contains the names of microphones.
  381 +*/
  382 +SrsPublisher.prototype.on_publisher_ready = function(cameras, microphones) {
  383 +}
  384 +/**
  385 +* when publisher error.
  386 +* @code the error code.
  387 +*/
  388 +SrsPublisher.prototype.on_publisher_error = function(code) {
  389 + throw new Error("publisher error. code=" + code);
  390 +}
  391 +function __srs_find_publisher(id) {
  392 + for (var i = 0; i < SrsPublisher.__publishers.length; i++) {
  393 + var publisher = SrsPublisher.__publishers[i];
  394 +
  395 + if (publisher.id != id) {
  396 + continue;
  397 + }
  398 +
  399 + return publisher;
  400 + }
  401 +
  402 + throw new Error("publisher not found. id=" + id);
  403 +}
  404 +function __srs_on_publisher_ready(id, cameras, microphones) {
  405 + var publisher = __srs_find_publisher(id);
  406 +
  407 + publisher.cameras = cameras;
  408 + publisher.microphones = microphones;
  409 +
  410 + publisher.on_publisher_ready(cameras, microphones);
  411 +}
  412 +function __srs_on_publisher_error(id, code) {
  413 + var publisher = __srs_find_publisher(id);
  414 +
  415 + publisher.code = code;
  416 +
  417 + publisher.on_publisher_error(code);
  418 +}
289 419
@@ -35,14 +35,13 @@ @@ -35,14 +35,13 @@
35 $("#main_modal").on("show", function(){ 35 $("#main_modal").on("show", function(){
36 $("#div_container").remove(); 36 $("#div_container").remove();
37 37
38 - var obj = $("<div/>");  
39 - $(obj).attr("id", "div_container"); 38 + var div_container = $("<div/>");
  39 + $(div_container).attr("id", "div_container");
  40 + $("#player").append(div_container);
40 41
41 var player = $("<div/>"); 42 var player = $("<div/>");
42 - $(obj).append(player);  
43 - $(obj).attr("id", "player_id");  
44 -  
45 - $("#player").append(obj); 43 + $(player).attr("id", "player_id");
  44 + $(div_container).append(player);
46 45
47 var conf = { 46 var conf = {
48 file: _url, 47 file: _url,
@@ -21,14 +21,13 @@ @@ -21,14 +21,13 @@
21 function osmf_play(url) { 21 function osmf_play(url) {
22 $("#div_container").remove(); 22 $("#div_container").remove();
23 23
24 - var obj = $("<div/>");  
25 - $(obj).attr("id", "div_container"); 24 + var div_container = $("<div/>");
  25 + $(div_container).attr("id", "div_container");
  26 + $("#player").append(div_container);
26 27
27 var player = $("<div/>"); 28 var player = $("<div/>");
28 - $(obj).append(player);  
29 - $(obj).attr("id", "player_id");  
30 -  
31 - $("#player").append(obj); 29 + $(player).attr("id", "player_id");
  30 + $(div_container).append(player);
32 31
33 var flashvars = {}; 32 var flashvars = {};
34 flashvars.src = url; 33 flashvars.src = url;
@@ -80,28 +80,26 @@ @@ -80,28 +80,26 @@
80 80
81 $("#div_container").remove(); 81 $("#div_container").remove();
82 82
83 - var obj = $("<div/>");  
84 - $(obj).attr("id", "div_container"); 83 + var div_container = $("<div/>");
  84 + $(div_container).attr("id", "div_container");
  85 + $("#player").append(div_container);
85 86
86 var player = $("<div/>"); 87 var player = $("<div/>");
87 - $(obj).append(player);  
88 - $(obj).attr("id", "player_id");  
89 -  
90 - $("#player").append(obj); 88 + $(player).attr("id", "player_id");
  89 + $(div_container).append(player);
91 90
92 var url = $("#txt_url").val(); 91 var url = $("#txt_url").val();
93 92
94 - srs_player = new SrsPlayer("player_id", url,  
95 - srs_get_player_width(), srs_get_player_height()); 93 + srs_player = new SrsPlayer("player_id", srs_get_player_width(), srs_get_player_height());
96 srs_player.on_player_ready = function() { 94 srs_player.on_player_ready = function() {
97 select_buffer_time("#btn_bt_0_8", 0.8); 95 select_buffer_time("#btn_bt_0_8", 0.8);
98 - srs_player.play();  
99 - } 96 + srs_player.play(url);
  97 + };
100 srs_player.on_player_metadata = function(metadata) { 98 srs_player.on_player_metadata = function(metadata) {
101 $("#btn_dar_original").text("视频原始比例" + "(" + metadata.width + ":" + metadata.height + ")"); 99 $("#btn_dar_original").text("视频原始比例" + "(" + metadata.width + ":" + metadata.height + ")");
102 select_dar("#btn_dar_original", 0, 0); 100 select_dar("#btn_dar_original", 0, 0);
103 select_fs_size("#btn_fs_size_screen_100", "screen", 100); 101 select_fs_size("#btn_fs_size_screen_100", "screen", 100);
104 - } 102 + };
105 srs_player.on_player_timer = function(time, buffer_length) { 103 srs_player.on_player_timer = function(time, buffer_length) {
106 var buffer = buffer_length / srs_player.buffer_time * 100; 104 var buffer = buffer_length / srs_player.buffer_time * 100;
107 $("#pb_buffer").width(Number(buffer).toFixed(1) + "%"); 105 $("#pb_buffer").width(Number(buffer).toFixed(1) + "%");
@@ -124,7 +122,7 @@ @@ -124,7 +122,7 @@
124 time_str += padding(parseInt(time), 2, '0'); 122 time_str += padding(parseInt(time), 2, '0');
125 // show 123 // show
126 $("#txt_time").val(time_str); 124 $("#txt_time").val(time_str);
127 - } 125 + };
128 srs_player.start(); 126 srs_player.start();
129 }); 127 });
130 128
@@ -218,6 +216,11 @@ @@ -218,6 +216,11 @@
218 select_buffer_time("#btn_bt_30", 30); 216 select_buffer_time("#btn_bt_30", 30);
219 }); 217 });
220 } 218 }
  219 +
  220 + var query = parse_query_string();
  221 + if (query.autostart == "true") {
  222 + $("#main_modal").modal({show:true, keyboard:false});
  223 + }
221 }); 224 });
222 </script> 225 </script>
223 </head> 226 </head>
@@ -258,6 +258,10 @@ package @@ -258,6 +258,10 @@ package
258 * function for js to call: to stop the stream. ignore if not play. 258 * function for js to call: to stop the stream. ignore if not play.
259 */ 259 */
260 private function js_call_stop():void { 260 private function js_call_stop():void {
  261 + if (this.media_video) {
  262 + this.removeChild(this.media_video);
  263 + this.media_video = null;
  264 + }
261 if (this.media_stream) { 265 if (this.media_stream) {
262 this.media_stream.close(); 266 this.media_stream.close();
263 this.media_stream = null; 267 this.media_stream = null;
@@ -266,10 +270,6 @@ package @@ -266,10 +270,6 @@ package
266 this.media_conn.close(); 270 this.media_conn.close();
267 this.media_conn = null; 271 this.media_conn = null;
268 } 272 }
269 - if (this.media_video) {  
270 - this.removeChild(this.media_video);  
271 - this.media_video = null;  
272 - }  
273 } 273 }
274 274
275 /** 275 /**
@@ -14,9 +14,174 @@ @@ -14,9 +14,174 @@
14 } 14 }
15 </style> 15 </style>
16 <script type="text/javascript"> 16 <script type="text/javascript">
  17 + var srs_publisher = null;
  18 + var remote_player = null;
  19 +
17 $(function(){ 20 $(function(){
18 - update_nav(); 21 + // get the vhost and port to set the default url.
  22 + // for example: http://192.168.1.213/players/jwplayer6.html?port=1935&vhost=demo
  23 + // url set to: rtmp://demo:1935/live/livestream
  24 + srs_init("#txt_url", null, null);
  25 +
  26 + $("#btn_video_settings").click(function(){
  27 + $("#video_modal").modal({show:true});
  28 + });
  29 + $("#btn_audio_settings").click(function(){
  30 + $("#audio_modal").modal({show:true});
  31 + });
  32 +
  33 + $("#btn_publish").click(on_user_publish);
  34 +
  35 + update_play_url();
  36 +
  37 + // start the publisher.
  38 + srs_publisher = new SrsPublisher("local_publisher", 430, 185);
  39 + srs_publisher.on_publisher_ready = function(cameras, microphones) {
  40 + $("#sl_cameras").empty();
  41 + for (var i = 0; i < cameras.length; i++) {
  42 + $("#sl_cameras").append("<option value='" + i + "'>" + cameras[i] + "</option");
  43 + }
  44 +
  45 + $("#sl_microphones").empty();
  46 + for (var i = 0; i < microphones.length; i++) {
  47 + $("#sl_microphones").append("<option value='" + i + "'>" + microphones[i] + "</option");
  48 + }
  49 +
  50 + $("#sl_vcodec").empty();
  51 + var vcodecs = ["h264", "vp6"];
  52 + for (var i = 0; i < vcodecs.length; i++) {
  53 + $("#sl_vcodec").append("<option value='" + vcodecs[i] + "'>" + vcodecs[i] + "</option");
  54 + }
  55 +
  56 + $("#sl_profile").empty();
  57 + var profiles = ["baseline", "main"];
  58 + for (var i = 0; i < profiles.length; i++) {
  59 + $("#sl_profile").append("<option value='" + profiles[i] + "'>" + profiles[i] + "</option");
  60 + }
  61 +
  62 + $("#sl_level").empty();
  63 + var levels = ["1", "1b", "1.1", "1.2", "1.3",
  64 + "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
  65 + for (var i = 0; i < levels.length; i++) {
  66 + $("#sl_level").append("<option value='" + levels[i] + "'>" + levels[i] + "</option");
  67 + }
  68 + $("#sl_level option[value='4.1']").attr("selected", true);
  69 +
  70 + $("#sl_gop").empty();
  71 + var gops = ["0.3", "0.5", "1", "2", "3", "4",
  72 + "5", "6", "7", "8", "9", "10", "15", "20"];
  73 + for (var i = 0; i < gops.length; i++) {
  74 + $("#sl_gop").append("<option value='" + gops[i] + "'>" + gops[i] + "秒</option");
  75 + }
  76 + $("#sl_gop option[value='5']").attr("selected", true);
  77 +
  78 + $("#sl_size").empty();
  79 + var sizes = ["176x144", "320x240", "352x240",
  80 + "352x288", "460x240", "640x480", "720x480", "720x576", "800x600",
  81 + "1024x768", "1280x720", "1360x768", "1920x1080"];
  82 + for (i = 0; i < sizes.length; i++) {
  83 + $("#sl_size").append("<option value='" + sizes[i] + "'>" + sizes[i] + "</option");
  84 + }
  85 + $("#sl_size option[value='460x240']").attr("selected", true);
  86 +
  87 + $("#sl_fps").empty();
  88 + var fpses = ["5", "10", "15", "20", "24", "25", "29.97", "30"];
  89 + for (i = 0; i < fpses.length; i++) {
  90 + $("#sl_fps").append("<option value='" + fpses[i] + "'>" + Number(fpses[i]).toFixed(2) + " 帧/秒</option");
  91 + }
  92 + $("#sl_fps option[value='15']").attr("selected", true);
  93 +
  94 + $("#sl_bitrate").empty();
  95 + var bitrates = ["50", "200", "350", "500", "650", "800",
  96 + "950", "1000", "1200", "1500", "1800", "2000", "3000", "5000"];
  97 + for (i = 0; i < bitrates.length; i++) {
  98 + $("#sl_bitrate").append("<option value='" + bitrates[i] + "'>" + bitrates[i] + " kbps</option");
  99 + }
  100 + $("#sl_bitrate option[value='350']").attr("selected", true);
  101 + };
  102 + srs_publisher.on_publisher_error = function(code) {
  103 + if (code == srs_publisher.error_device_muted) {
  104 + error(code, "摄像头和麦克风被禁用,请右键flash播放器启用。");
  105 + } else {
  106 + error(code, "未知系统错误");
  107 + }
  108 + };
  109 + srs_publisher.start();
  110 +
  111 + // start the player.
  112 + remote_player = new SrsPlayer("remote_player", 430, 185);
  113 + remote_player.on_player_ready = function() {
  114 + };
  115 + remote_player.start();
19 }); 116 });
  117 +
  118 + function update_play_url() {
  119 + // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
  120 + var a = document.createElement("a");
  121 + a.href = $("#txt_url").val().replace("rtmp://", "http://");
  122 +
  123 + var url = "http://" + window.location.host;
  124 + url += window.location.pathname.substr(0, window.location.pathname.lastIndexOf("/"));
  125 + url += "/srs_player.html?";
  126 +
  127 + url += "vhost=" + a.hostname;
  128 + url += "&port=" + a.port;
  129 + url += "&app=" + a.pathname.substr(1, a.pathname.lastIndexOf("/") - 1);
  130 + url += "&stream=" + a.pathname.substr(a.pathname.lastIndexOf("/") + 1);
  131 +
  132 + // autostart
  133 + url += "&autostart=true";
  134 +
  135 + $("#txt_play_url").text(url);
  136 + $("#txt_play_url").attr("href", url);
  137 + }
  138 + function on_user_publish() {
  139 + if ($("#btn_publish").text() == "停止发布") {
  140 + srs_publisher.stop();
  141 + $("#btn_publish").text("发布视频");
  142 + return;
  143 + }
  144 +
  145 + $("#btn_publish").text("停止发布");
  146 +
  147 + update_play_url();
  148 +
  149 + var url = $("#txt_url").val();
  150 + var vcodec = {};
  151 + var acodec = {};
  152 +
  153 + acodec.device_code = $("#sl_microphones").val();
  154 + acodec.device_name = $("#sl_microphones").text();
  155 +
  156 + vcodec.device_code = $("#sl_cameras").find("option:selected").val();
  157 + vcodec.device_name = $("#sl_cameras").find("option:selected").text();
  158 +
  159 + vcodec.codec = $("#sl_vcodec").find("option:selected").val();
  160 + vcodec.profile = $("#sl_profile").find("option:selected").val();
  161 + vcodec.level = $("#sl_level").find("option:selected").val();
  162 + vcodec.fps = $("#sl_fps").find("option:selected").val();
  163 + vcodec.gop = $("#sl_gop").find("option:selected").val();
  164 + vcodec.size = $("#sl_size").find("option:selected").val();
  165 + vcodec.bitrate = $("#sl_bitrate").find("option:selected").val();
  166 +
  167 + info("开始推流到服务器");
  168 + srs_publisher.publish(url, vcodec, acodec);
  169 +
  170 + // replay the url.
  171 + remote_player.stop();
  172 + remote_player.play(url);
  173 + }
  174 +
  175 + function info(desc) {
  176 + $("#txt_log").removeClass("alert-error").addClass("alert-info");
  177 + $("#txt_log_title").text("Info:");
  178 + $("#txt_log_msg").text(desc);
  179 + }
  180 + function error(code, desc) {
  181 + $("#txt_log").removeClass("alert-info").addClass("alert-error");
  182 + $("#txt_log_title").text("Error:");
  183 + $("#txt_log_msg").text("code: " + code + ", " + desc);
  184 + }
20 </script> 185 </script>
21 </head> 186 </head>
22 <body> 187 <body>
@@ -38,6 +203,187 @@ @@ -38,6 +203,187 @@
38 </div> 203 </div>
39 </div> 204 </div>
40 <div class="container"> 205 <div class="container">
  206 + <div class="alert alert-info fade in" id="txt_log">
  207 + <button type="button" class="close" data-dismiss="alert">×</button>
  208 + <strong><span id="txt_log_title">Usage:</span></strong>
  209 + <span id="txt_log_msg">输入地址后点击发布按钮</span>
  210 + </div>
  211 + <div class="control-group">
  212 + <div class="form-inline">
  213 + <button class="btn" id="btn_video_settings">视频编码配置</button>
  214 + <button class="btn" id="btn_audio_settings">音频编码配置</button>
  215 + </div>
  216 + </div>
  217 + <div class="control-group">
  218 + <div class="form-inline">
  219 + 发布地址:
  220 + <input type="text" id="txt_url" class="input-xxlarge" value=""></input>
  221 + <button class="btn" id="btn_publish">发布视频</button>
  222 + </div>
  223 + </div>
  224 + <div class="control-group">
  225 + <div class="form-inline">
  226 + 观看地址:
  227 + <a id="txt_play_url" class="input-xxlarge" href="srs_player.html">srs_player.html</a>
  228 + </div>
  229 + </div>
  230 + <div id="video_modal" class="modal hide fade">
  231 + <div class="modal-header">
  232 + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
  233 + <h3>视频编码</h3>
  234 + </div>
  235 + <div class="modal-body">
  236 + <div class="form-horizontal">
  237 + <div class="control-group">
  238 + <label class="control-label" for="sl_cameras">
  239 + 摄像头
  240 + <a id="sl_cameras_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  241 + <img src="img/tooltip.png"/>
  242 + </a>
  243 + </label>
  244 + <div class="controls">
  245 + <select class="span4" id="sl_cameras"></select>
  246 + </div>
  247 + </div>
  248 + <div class="control-group">
  249 + <label class="control-label" for="sl_vcodec">
  250 + Codec
  251 + <a id="sl_cameras_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  252 + <img src="img/tooltip.png"/>
  253 + </a>
  254 + </label>
  255 + <div class="controls">
  256 + <select class="span2" id="sl_vcodec"></select>
  257 + </div>
  258 + </div>
  259 + <div class="control-group">
  260 + <label class="control-label" for="sl_profile">
  261 + Profile
  262 + <a id="sl_profile_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  263 + <img src="img/tooltip.png"/>
  264 + </a>
  265 + </label>
  266 + <div class="controls">
  267 + <select class="span2" id="sl_profile"></select>
  268 + </div>
  269 + </div>
  270 + <div class="control-group">
  271 + <label class="control-label" for="sl_level">
  272 + Level
  273 + <a id="sl_level_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  274 + <img src="img/tooltip.png"/>
  275 + </a>
  276 + </label>
  277 + <div class="controls">
  278 + <select class="span2" id="sl_level"></select>
  279 + </div>
  280 + </div>
  281 + <div class="control-group">
  282 + <label class="control-label" for="sl_gop">
  283 + GOP
  284 + <a id="sl_gop_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  285 + <img src="img/tooltip.png"/>
  286 + </a>
  287 + </label>
  288 + <div class="controls">
  289 + <select class="span2" id="sl_gop"></select>
  290 + </div>
  291 + </div>
  292 + <div class="control-group">
  293 + <label class="control-label" for="sl_size">
  294 + 尺寸
  295 + <a id="sl_size_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  296 + <img src="img/tooltip.png"/>
  297 + </a>
  298 + </label>
  299 + <div class="controls">
  300 + <select class="span2" id="sl_size"></select>
  301 + </div>
  302 + </div>
  303 + <div class="control-group">
  304 + <label class="control-label" for="sl_fps">
  305 + 帧率
  306 + <a id="sl_fps_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  307 + <img src="img/tooltip.png"/>
  308 + </a>
  309 + </label>
  310 + <div class="controls">
  311 + <select class="span2" id="sl_fps"></select>
  312 + </div>
  313 + </div>
  314 + <div class="control-group">
  315 + <label class="control-label" for="sl_bitrate">
  316 + 码率
  317 + <a id="sl_bitrate_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  318 + <img src="img/tooltip.png"/>
  319 + </a>
  320 + </label>
  321 + <div class="controls">
  322 + <select class="span2" id="sl_bitrate"></select>
  323 + </div>
  324 + </div>
  325 + </div>
  326 + </div>
  327 + <div class="modal-footer">
  328 + <button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">设置</button>
  329 + </div>
  330 + </div>
  331 + <div id="audio_modal" class="modal hide fade">
  332 + <div class="modal-header">
  333 + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
  334 + <h3>音频编码</h3>
  335 + </div>
  336 + <div class="modal-body">
  337 + <div class="form-horizontal">
  338 + <div class="control-group">
  339 + <label class="control-label" for="sl_microphones">
  340 + 麦克风
  341 + <a id="worker_id_tips" href="#" data-toggle="tooltip" data-placement="right" title="">
  342 + <img src="img/tooltip.png"/>
  343 + </a>
  344 + </label>
  345 + <div class="controls">
  346 + <select class="span4" id="sl_microphones"></select>
  347 + </div>
  348 + </div>
  349 + </div>
  350 + </div>
  351 + <div class="modal-footer">
  352 + <button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">设置</button>
  353 + </div>
  354 + </div>
  355 + <div class="container">
  356 + <div class="row-fluid">
  357 + <div class="span6">
  358 + <div class="accordion-group">
  359 + <div class="accordion-heading">
  360 + <span class="accordion-toggle" data-toggle="collapse" href="#collapseOne">
  361 + <strong>本地摄像头</strong>
  362 + </span>
  363 + </div>
  364 + <div id="collapseOne" class="accordion-body collapse in">
  365 + <div class="accordion-inner">
  366 + <div id="local_publisher"></div>
  367 + </div>
  368 + </div>
  369 + </div>
  370 + </div>
  371 + <div class="span6">
  372 + <div class="accordion-group">
  373 + <div class="accordion-heading">
  374 + <span class="accordion-toggle" data-toggle="collapse" href="#collapseTwo">
  375 + <strong>远程服务器</strong>
  376 + </span>
  377 + </div>
  378 + <div id="collapseTwo" class="accordion-body collapse in">
  379 + <div class="accordion-inner">
  380 + <div id="remote_player"></div>
  381 + </div>
  382 + </div>
  383 + </div>
  384 + </div>
  385 + </div>
  386 + </div>
41 <hr> 387 <hr>
42 <footer> 388 <footer>
43 <p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team &copy; 2013</a></p> 389 <p><a href="https://github.com/winlinvip/simple-rtmp-server">SRS Team &copy; 2013</a></p>
1 package 1 package
2 { 2 {
3 import flash.display.Sprite; 3 import flash.display.Sprite;
  4 + import flash.display.StageAlign;
  5 + import flash.display.StageScaleMode;
  6 + import flash.events.Event;
  7 + import flash.events.NetStatusEvent;
  8 + import flash.external.ExternalInterface;
  9 + import flash.media.Camera;
  10 + import flash.media.H264Profile;
  11 + import flash.media.H264VideoStreamSettings;
  12 + import flash.media.Microphone;
  13 + import flash.media.Video;
  14 + import flash.net.NetConnection;
  15 + import flash.net.NetStream;
  16 + import flash.ui.ContextMenu;
  17 + import flash.utils.setTimeout;
4 18
5 public class srs_publisher extends Sprite 19 public class srs_publisher extends Sprite
6 { 20 {
  21 + // user set id.
  22 + private var js_id:String = null;
  23 + // user set callback
  24 + private var js_on_publisher_ready:String = null;
  25 + private var js_on_publisher_error:String = null;
  26 +
  27 + // publish param url.
  28 + private var user_url:String = null;
  29 + // play param, user set width and height
  30 + private var user_w:int = 0;
  31 + private var user_h:int = 0;
  32 + private var user_vcodec:Object = {};
  33 + private var user_acodec:Object = {};
  34 +
  35 + // media specified.
  36 + private var media_conn:NetConnection = null;
  37 + private var media_stream:NetStream = null;
  38 + private var media_video:Video = null;
  39 + private var media_camera:Camera = null;
  40 + private var media_microphone:Microphone = null;
  41 +
  42 + // error code.
  43 + private const error_device_muted:int = 100;
  44 +
7 public function srs_publisher() 45 public function srs_publisher()
8 { 46 {
  47 + if (!this.stage) {
  48 + this.addEventListener(Event.ADDED_TO_STAGE, this.system_on_add_to_stage);
  49 + } else {
  50 + this.system_on_add_to_stage(null);
  51 + }
  52 + }
  53 +
  54 + /**
  55 + * system event callback, when this control added to stage.
  56 + * the main function.
  57 + */
  58 + private function system_on_add_to_stage(evt:Event):void {
  59 + this.removeEventListener(Event.ADDED_TO_STAGE, this.system_on_add_to_stage);
  60 +
  61 + this.stage.align = StageAlign.TOP_LEFT;
  62 + this.stage.scaleMode = StageScaleMode.NO_SCALE;
  63 +
  64 + this.contextMenu = new ContextMenu();
  65 + this.contextMenu.hideBuiltInItems();
  66 +
  67 + var flashvars:Object = this.root.loaderInfo.parameters;
  68 +
  69 + if (!flashvars.hasOwnProperty("id")) {
  70 + throw new Error("must specifies the id");
  71 + }
  72 +
  73 + this.js_id = flashvars.id;
  74 + this.js_on_publisher_ready = flashvars.on_publisher_ready;
  75 + this.js_on_publisher_error = flashvars.on_publisher_error;
  76 +
  77 + flash.utils.setTimeout(this.system_on_js_ready, 0);
  78 + }
  79 +
  80 + /**
  81 + * system callack event, when js ready, register callback for js.
  82 + * the actual main function.
  83 + */
  84 + private function system_on_js_ready():void {
  85 + if (!flash.external.ExternalInterface.available) {
  86 + trace("js not ready, try later.");
  87 + flash.utils.setTimeout(this.system_on_js_ready, 100);
  88 + return;
  89 + }
  90 +
  91 + flash.external.ExternalInterface.addCallback("__publish", this.js_call_publish);
  92 + flash.external.ExternalInterface.addCallback("__stop", this.js_call_stop);
  93 +
  94 + var cameras:Array = Camera.names;
  95 + var microphones:Array = Microphone.names;
  96 + trace("retrieve system cameras(" + cameras + ") and microphones(" + microphones + ")");
  97 +
  98 + flash.external.ExternalInterface.call(this.js_on_publisher_ready, this.js_id, cameras, microphones);
  99 + }
  100 +
  101 + /**
  102 + * notify the js an error occur.
  103 + */
  104 + private function system_error(code:int, desc:String):void {
  105 + trace("system error, code=" + code + ", error=" + desc);
  106 + flash.external.ExternalInterface.call(this.js_on_publisher_error, this.js_id, code);
  107 + }
  108 +
  109 + /**
  110 + * publish stream to server.
  111 + * @param url a string indicates the rtmp url to publish.
  112 + * @param _width, the player width.
  113 + * @param _height, the player height.
  114 + * @param vcodec an object contains the video codec info.
  115 + * @param acodec an object contains the audio codec info.
  116 + */
  117 + private function js_call_publish(url:String, _width:int, _height:int, vcodec:Object, acodec:Object):void {
  118 + trace("start to publish to " + url + ", vcodec " + JSON.stringify(vcodec) + ", acodec " + JSON.stringify(acodec));
  119 +
  120 + this.user_url = url;
  121 + this.user_w = _width;
  122 + this.user_h = _height;
  123 + this.user_vcodec = vcodec;
  124 + this.user_acodec = acodec;
  125 +
  126 + this.js_call_stop();
  127 +
  128 + // microphone and camera
  129 + var m:Microphone = Microphone.getMicrophone(acodec.device_code);
  130 + if(m == null){
  131 + trace("failed to open microphone " + acodec.device_code + "(" + acodec.device_name + ")");
  132 + }
  133 + if(m.muted){
  134 + trace("Access Denied, microphone " + acodec.device_code + "(" + acodec.device_name + ") is muted");
  135 + m = null;
  136 + }
  137 +
  138 + // Remark: the name is the index!
  139 + var c:Camera = Camera.getCamera(vcodec.device_code);
  140 + if(c == null){
  141 + trace("failed to open camera " + vcodec.device_code + "(" + vcodec.device_name + ")");
  142 + }
  143 + if(c.muted){
  144 + trace("Access Denied, camera " + vcodec.device_code + "(" + vcodec.device_name + ") is muted");
  145 + c = null;
  146 + }
  147 +
  148 + if (m == null && c == null) {
  149 + system_error(error_device_muted, "failed to publish, for neither camera or microphone is ok.");
  150 + return;
  151 + }
  152 +
  153 + this.media_camera = c;
  154 + this.media_microphone = m;
  155 +
  156 + this.media_conn = new NetConnection();
  157 + this.media_conn.client = {};
  158 + this.media_conn.client.onBWDone = function():void {};
  159 + this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
  160 + trace ("NetConnection: code=" + evt.info.code);
  161 +
  162 + // TODO: FIXME: failed event.
  163 + if (evt.info.code != "NetConnection.Connect.Success") {
  164 + return;
  165 + }
  166 +
  167 + media_stream = new NetStream(media_conn);
  168 + media_stream.client = {};
  169 + media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void {
  170 + trace ("NetStream: code=" + evt.info.code);
  171 +
  172 + // TODO: FIXME: failed event.
  173 + });
  174 +
  175 + __build_video_codec(media_stream, c, vcodec);
  176 + __build_audio_codec(media_stream, m, acodec);
  177 +
  178 + if (media_microphone) {
  179 + media_stream.attachAudio(m);
  180 + }
  181 + if (media_camera) {
  182 + media_stream.attachCamera(c);
  183 + }
  184 +
  185 + var streamName:String = url.substr(url.lastIndexOf("/"));
  186 + media_stream.publish(streamName);
  187 +
  188 + media_video = new Video();
  189 + media_video.width = _width;
  190 + media_video.height = _height;
  191 + media_video.attachCamera(media_camera);
  192 + media_video.smoothing = true;
  193 + addChild(media_video);
  194 +
  195 + //__draw_black_background(_width, _height);
  196 +
  197 + // lowest layer, for mask to cover it.
  198 + setChildIndex(media_video, 0);
  199 + });
  200 +
  201 + var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/"));
  202 + this.media_conn.connect(tcUrl);
  203 + }
  204 +
  205 + /**
  206 + * function for js to call: to stop the stream. ignore if not publish.
  207 + */
  208 + private function js_call_stop():void {
  209 + if (this.media_video) {
  210 + this.removeChild(this.media_video);
  211 + this.media_video = null;
  212 + }
  213 + if (this.media_stream) {
  214 + this.media_stream.close();
  215 + this.media_stream = null;
  216 + }
  217 + if (this.media_conn) {
  218 + this.media_conn.close();
  219 + this.media_conn = null;
  220 + }
  221 + }
  222 +
  223 + private function __build_audio_codec(stream:NetStream, m:Microphone, acodec:Object):void {
  224 + if (!m) {
  225 + return;
  226 + }
  227 +
  228 + // if no microphone, donot set the params.
  229 + if(m == null){
  230 + return;
  231 + }
  232 +
  233 + // use default values.
  234 + var microEncodeQuality:int = 8;
  235 + var microRate:int = 22; // 22 === 22050 Hz
  236 +
  237 + trace("[Publish] audio encoding parameters: "
  238 + + "audio(microphone) encodeQuality=" + microEncodeQuality
  239 + + ", rate=" + microRate + "(22050Hz)"
  240 + );
  241 +
  242 + // The encoded speech quality when using the Speex codec. Possible values are from 0 to 10. The default value is 6. Higher numbers
  243 + // represent higher quality but require more bandwidth, as shown in the following table. The bit rate values that are listed represent
  244 + // net bit rates and do not include packetization overhead.
  245 + m.encodeQuality = microEncodeQuality;
  246 +
  247 + // The rate at which the microphone is capturing sound, in kHz. Acceptable values are 5, 8, 11, 22, and 44. The default value is 8 kHz
  248 + // if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that
  249 + // your sound capture device supports, usually 11 kHz.
  250 + m.rate = microRate;
  251 + }
  252 + private function __build_video_codec(stream:NetStream, c:Camera, vcodec:Object):void {
  253 + if (!c) {
  254 + return;
  255 + }
  256 +
  257 + if(vcodec.codec == "vp6"){
  258 + trace("use VP6, donot use H.264 publish encoding.");
  259 + return;
  260 + }
  261 +
  262 + var x264profile:String = (vcodec.profile == "main") ? H264Profile.MAIN : H264Profile.BASELINE;
  263 + var x264level:String = vcodec.level;
  264 + var cameraFps:Number = Number(vcodec.fps);
  265 + var x264KeyFrameInterval:int = int(vcodec.gop * cameraFps);
  266 + var cameraWidth:int = String(vcodec.size).split("x")[0];
  267 + var cameraHeight:int = String(vcodec.size).split("x")[1];
  268 + var cameraBitrate:int = int(vcodec.bitrate);
  269 +
  270 + // use default values.
  271 + var cameraQuality:int = 85;
  272 +
  273 + trace("[Publish] video h.264(x264) encoding parameters: "
  274 + + "profile=" + x264profile
  275 + + ", level=" + x264level
  276 + + ", keyFrameInterval(gop)=" + x264KeyFrameInterval
  277 + + "; video(camera) width=" + cameraWidth
  278 + + ", height=" + cameraHeight
  279 + + ", fps=" + cameraFps
  280 + + ", bitrate=" + cameraBitrate
  281 + + ", quality=" + cameraQuality
  282 + );
  283 +
  284 + var h264Settings:H264VideoStreamSettings = new H264VideoStreamSettings();
  285 + // we MUST set its values first, then set the NetStream.videoStreamSettings, or it will keep the origin values.
  286 + h264Settings.setProfileLevel(x264profile, x264level);
  287 + stream.videoStreamSettings = h264Settings;
  288 + // the setKeyFrameInterval/setMode/setQuality use the camera settings.
  289 + // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/VideoStreamSettings.html
  290 + // Note This feature will be supported in future releases of Flash Player and AIR, for now, Camera parameters are used.
  291 + //
  292 + //h264Settings.setKeyFrameInterval(4);
  293 + //h264Settings.setMode(800, 600, 15);
  294 + //h264Settings.setQuality(500, 0);
  295 +
  296 + // set the camera and microphone.
  297 +
  298 + // setKeyFrameInterval(keyFrameInterval:int):void
  299 + // keyFrameInterval:int — A value that specifies which video frames are transmitted in full (as keyframes) instead of being
  300 + // interpolated by the video compression algorithm. A value of 1 means that every frame is a keyframe, a value of 3 means
  301 + // that every third frame is a keyframe, and so on. Acceptable values are 1 through 48.
  302 + c.setKeyFrameInterval(x264KeyFrameInterval);
  303 +
  304 + // setMode(width:int, height:int, fps:Number, favorArea:Boolean = true):void
  305 + // width:int — The requested capture width, in pixels. The default value is 160.
  306 + // height:int — The requested capture height, in pixels. The default value is 120.
  307 + // fps:Number — The requested rate at which the camera should capture data, in frames per second. The default value is 15.
  308 + c.setMode(cameraWidth, cameraHeight, cameraFps);
  309 +
  310 + // setQuality(bandwidth:int, quality:int):void
  311 + // bandwidth:int — Specifies the maximum amount of bandwidth that the current outgoing video feed can use, in bytes per second.
  312 + // To specify that the video can use as much bandwidth as needed to maintain the value of quality, pass 0 for bandwidth.
  313 + // The default value is 16384.
  314 + // quality:int — An integer that specifies the required level of picture quality, as determined by the amount of compression
  315 + // being applied to each video frame. Acceptable values range from 1 (lowest quality, maximum compression) to 100
  316 + // (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth,
  317 + // pass 0 for quality.
  318 + // winlin:
  319 + // bandwidth is in bps not kbps. 500*1000 = 500kbps.
  320 + // quality=1 is lowest quality, 100 is highest quality.
  321 + c.setQuality(cameraBitrate * 1000, cameraQuality);
9 } 322 }
10 } 323 }
11 } 324 }