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
136
README.md
136
README.md
|
@ -9,21 +9,41 @@ see also: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/w
|
|||
see also: [http://winlinvip.github.io/simple-rtmp-server](http://winlinvip.github.io/simple-rtmp-server)
|
||||
|
||||
### Contributors
|
||||
winlin(winterserver): [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin)
|
||||
winlin([winterserver](#)): [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) <br/>
|
||||
wenjie([wenjiegit](https://github.com/wenjiegit/simple-rtmp-server)): [http://blog.chinaunix.net/uid/25006789.html](http://blog.chinaunix.net/uid/25006789.html) <br/>
|
||||
who is the contributors: <br/>
|
||||
1. contribute important features to srs. <br/>
|
||||
2. the name of all contributors will send in the response of NetConnection.connect and metadata.
|
||||
|
||||
### Usage
|
||||
### Usage(simple)
|
||||
<strong>step -1:</strong> get srs<br/>
|
||||
<pre>
|
||||
git clone https://github.com/winlinvip/simple-rtmp-server &&
|
||||
cd simple-rtmp-server/trunk
|
||||
</pre>
|
||||
<strong>step 0:</strong> build srs system.<br/>
|
||||
<pre>
|
||||
bash scripts/build.sh
|
||||
</pre>
|
||||
<strong>step 1:</strong> start srs all demo features.<br/>
|
||||
<pre>
|
||||
bash scripts/run.sh
|
||||
</pre>
|
||||
<strong>step 2:</strong> srs live show: [http://your-server-ip](http://your-server-ip) <br/>
|
||||
<strong>step 3:</strong> stop srs demo<br/>
|
||||
<pre>
|
||||
bash scripts/stop.sh
|
||||
</pre>
|
||||
|
||||
### Usage(detail)
|
||||
<strong>step 0:</strong> get srs <br/>
|
||||
<pre>
|
||||
git clone https://github.com/winlinvip/simple-rtmp-server &&
|
||||
cd simple-rtmp-server/trunk
|
||||
</pre>
|
||||
<strong>step 1:</strong> build srs <br/>
|
||||
<pre>
|
||||
tar xf simple-rtmp-server-*.*.tar.gz
|
||||
cd simple-rtmp-server-*.*/trunk
|
||||
./configure --with-ssl --with-hls --with-ffmpeg --with-http
|
||||
make
|
||||
</pre>
|
||||
or get the latest code:<br/>
|
||||
<pre>
|
||||
git clone https://github.com/winlinvip/simple-rtmp-server
|
||||
cd simple-rtmp-server/trunk
|
||||
./configure --with-ssl --with-hls --with-ffmpeg --with-http
|
||||
./configure --with-ssl --with-hls --with-ffmpeg --with-http && make
|
||||
</pre>
|
||||
<strong>step 2:</strong> start srs <br/>
|
||||
<pre>
|
||||
|
@ -41,11 +61,11 @@ sudo ./objs/nginx/sbin/nginx
|
|||
<pre>
|
||||
python ./research/api-server/server.py 8085
|
||||
</pre>
|
||||
<strong>step 6:</strong> publish live stream <br/>
|
||||
<strong>step 6:</strong> publish demo live stream <br/>
|
||||
<pre>
|
||||
FMS URL: rtmp://127.0.0.1/live
|
||||
FMS URL: rtmp://127.0.0.1/live?vhost=demo.srs.com
|
||||
Stream: livestream
|
||||
For example, use ffmpeg to publish:
|
||||
FFMPEG to publish the default demo stream:
|
||||
for((;;)); do \
|
||||
./objs/ffmpeg/bin/ffmpeg -re -i ./doc/source.200kbps.768x320.flv \
|
||||
-vcodec copy -acodec copy \
|
||||
|
@ -53,7 +73,19 @@ For example, use ffmpeg to publish:
|
|||
sleep 1; \
|
||||
done
|
||||
</pre>
|
||||
<strong>step 7:</strong> add server ip to client hosts as demo. <br/>
|
||||
<strong>step 7:</strong> publish players live stream <br/>
|
||||
<pre>
|
||||
FMS URL: rtmp://127.0.0.1/live?vhost=players
|
||||
Stream: livestream
|
||||
FFMPEG to publish the players demo stream:
|
||||
for((;;)); do \
|
||||
./objs/ffmpeg/bin/ffmpeg -re -i ./doc/source.200kbps.768x320.flv \
|
||||
-vcodec copy -acodec copy \
|
||||
-f flv -y rtmp://127.0.0.1/live?vhost=players/livestream; \
|
||||
sleep 1; \
|
||||
done
|
||||
</pre>
|
||||
<strong>step 8:</strong> add server ip to client hosts as demo. <br/>
|
||||
<pre>
|
||||
# edit the folowing file:
|
||||
# linux: /etc/hosts
|
||||
|
@ -61,35 +93,35 @@ For example, use ffmpeg to publish:
|
|||
# where server ip is 192.168.2.111
|
||||
192.168.2.111 demo.srs.com
|
||||
</pre>
|
||||
<strong>step 8:</strong> play live stream. <br/>
|
||||
<strong>step 9:</strong> play live stream. <br/>
|
||||
<pre>
|
||||
players: [http://demo.srs.com/players](http://demo.srs.com/players)
|
||||
players: http://demo.srs.com/players
|
||||
rtmp url: rtmp://demo.srs.com/live/livestream
|
||||
m3u8 url: http://demo.srs.com/live/livestream.m3u8
|
||||
for android: http://demo.srs.com/live/livestream.html
|
||||
</pre>
|
||||
<strong>step 9(optinal):</strong> play live stream auto transcoded<br/>
|
||||
<strong>step 10(optinal):</strong> play live stream auto transcoded<br/>
|
||||
<pre>
|
||||
rtmp url: rtmp://demo.srs.com/live/livestream_ld
|
||||
m3u8 url: http://demo.srs.com/live/livestream_ld.m3u8
|
||||
for android: [http://demo.srs.com/live/livestream_ld.html](http://demo.srs.com/live/livestream_ld.html)
|
||||
for android: http://demo.srs.com/live/livestream_ld.html
|
||||
rtmp url: rtmp://demo.srs.com/live/livestream_sd
|
||||
m3u8 url: http://demo.srs.com/live/livestream_sd.m3u8
|
||||
for android: [http://demo.srs.com/live/livestream_sd.html](http://demo.srs.com/live/livestream_sd.html)
|
||||
for android: http://demo.srs.com/live/livestream_sd.html
|
||||
</pre>
|
||||
<strong>step 10(optinal):</strong> play live stream auto forwarded, the hls dir change to /forward<br/>
|
||||
<strong>step 11(optinal):</strong> play live stream auto forwarded, the hls dir change to /forward<br/>
|
||||
<pre>
|
||||
rtmp url: rtmp://demo.srs.com:19350/live/livestream
|
||||
m3u8 url: http://demo.srs.com/forward/live/livestream.m3u8
|
||||
for android: [http://demo.srs.com/forward/live/livestream.html](http://demo.srs.com/forward/live/livestream.html)
|
||||
for android: http://demo.srs.com/forward/live/livestream.html
|
||||
rtmp url: rtmp://demo.srs.com:19350/live/livestream_ld
|
||||
m3u8 url: http://demo.srs.com/forward/live/livestream_ld.m3u8
|
||||
for android: [http://demo.srs.com/forward/live/livestream_ld.html](http://demo.srs.com/forward/live/livestream_ld.html)
|
||||
for android: http://demo.srs.com/forward/live/livestream_ld.html
|
||||
rtmp url: rtmp://demo.srs.com:19350/live/livestream_sd
|
||||
m3u8 url: http://demo.srs.com/forward/live/livestream_sd.m3u8
|
||||
for android: [http://demo.srs.com/forward/live/livestream_sd.html](http://demo.srs.com/forward/live/livestream_sd.html)
|
||||
for android: http://demo.srs.com/forward/live/livestream_sd.html
|
||||
</pre>
|
||||
<strong>step 11(optinal):</strong> modify the config and reload it (all features support reload)<br/>
|
||||
<strong>step 12(optinal):</strong> modify the config and reload it (all features support reload)<br/>
|
||||
<pre>
|
||||
killall -1 srs
|
||||
</pre>
|
||||
|
@ -125,8 +157,36 @@ Stream Architecture:
|
|||
| Flash, | +-> Fowarder ---------+-> RTMP Server |
|
||||
| XSPLIT, | +-> Transcoder -------+-> RTMP Server |
|
||||
| ...) | +-> DVR --------------+-> FILE |
|
||||
| | +-> BandwidthTest-----+-> Flash/StLoad |
|
||||
+-----------+-------------------------+----------------+
|
||||
</pre>
|
||||
Bandwidth Test Workflow:
|
||||
<pre>
|
||||
+------------+ +----------+
|
||||
| Client | | Server |
|
||||
+-----+------+ +-----+----+
|
||||
| |
|
||||
| connect vhost-------------> |
|
||||
| <-----------result(success) |
|
||||
| |
|
||||
| <----------call(start play) |
|
||||
| result(playing)----------> |
|
||||
| <-------------data(playing) |
|
||||
| <-----------call(stop play) |
|
||||
| result(stopped)----------> |
|
||||
| |
|
||||
| <-------call(start publish) |
|
||||
| result(publishing)-------> |
|
||||
| data(publishing)---------> |
|
||||
| <--------call(stop publish) |
|
||||
| result(stopped)(1)-------> |
|
||||
| |
|
||||
| <--------------------report |
|
||||
| final(2)-----------------> |
|
||||
| <END> |
|
||||
|
||||
@see: class SrsBandwidth comments.
|
||||
</pre>
|
||||
|
||||
### System Requirements
|
||||
Supported operating systems and hardware:
|
||||
|
@ -154,15 +214,17 @@ Supported operating systems and hardware:
|
|||
18. support ffmpeg filters(logo/overlay/crop), x264 params.<br/>
|
||||
19. support audio transcode only, speex/mp3 to aac<br/>
|
||||
20. support http callback api hooks(for authentication and injection).<br/>
|
||||
21. [plan] support network based cli and json result.<br/>
|
||||
22. [plan] support bandwidth test api and flash client.<br/>
|
||||
23. [plan] support adobe flash refer/token/swf verification.<br/>
|
||||
24. [plan] support adobe amf3 codec.<br/>
|
||||
25. [plan] support dvr(record live to vod file)<br/>
|
||||
26. [plan] support FMS edge protocol<br/>
|
||||
27. [plan] support encryption: RTMPE/RTMPS, HLS DRM<br/>
|
||||
28. [plan] support RTMPT, http to tranverse firewalls<br/>
|
||||
29. [plan] support file source, transcoding file to live stream<br/>
|
||||
21. support bandwidth test api and flash client.<br/>
|
||||
22. player, publisher(encoder), and demo pages(jquery+bootstrap). <br/>
|
||||
23. demo video meeting or chat(srs+cherrypy+jquery+bootstrap). <br/>
|
||||
24. [plan] support network based cli and json result.<br/>
|
||||
25. [plan] support adobe flash refer/token/swf verification.<br/>
|
||||
26. [plan] support adobe amf3 codec.<br/>
|
||||
27. [plan] support dvr(record live to vod file)<br/>
|
||||
28. [plan] support FMS edge protocol<br/>
|
||||
29. [plan] support encryption: RTMPE/RTMPS, HLS DRM<br/>
|
||||
30. [plan] support RTMPT, http to tranverse firewalls<br/>
|
||||
31. [plan] support file source, transcoding file to live stream<br/>
|
||||
|
||||
### Performance
|
||||
1. 300 connections, 150Mbps, 500kbps, CPU 18.8%, 5956KB.
|
||||
|
@ -213,6 +275,10 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw
|
|||
* nginx v1.5.0: 139524 lines <br/>
|
||||
|
||||
### History
|
||||
* v0.9, 2013-12-22, demo video meeting or chat(srs+cherrypy+jquery+bootstrap).
|
||||
* v0.9, 2013-12-22, merge from wenjie, support banwidth test.
|
||||
* v0.9, 2013-12-22, merge from wenjie: support set chunk size at vhost level
|
||||
* v0.9, 2013-12-21, add [players](http://demo.srs.com/players) for play and publish.
|
||||
* v0.9, 2013-12-15, ensure the HLS(ts) is continous when republish stream.
|
||||
* v0.9, 2013-12-15, fix the hls reload bug, feed it the sequence header.
|
||||
* v0.9, 2013-12-15, refine protocol, use int64_t timestamp for ts and jitter.
|
||||
|
|
BIN
trunk/3rdparty/bootstrap.2.3.2.zip
vendored
Normal file
BIN
trunk/3rdparty/bootstrap.2.3.2.zip
vendored
Normal file
Binary file not shown.
BIN
trunk/3rdparty/jquery-1.10.2.zip
vendored
Normal file
BIN
trunk/3rdparty/jquery-1.10.2.zip
vendored
Normal file
Binary file not shown.
|
@ -28,10 +28,14 @@ vhost __defaultVhost__ {
|
|||
# for which cannot identify the required vhost.
|
||||
# for default demo.
|
||||
vhost demo.srs.com {
|
||||
chunk_size 4096;
|
||||
enabled on;
|
||||
gop_cache on;
|
||||
queue_length 30;
|
||||
forward 127.0.0.1:19350;
|
||||
bandcheck {
|
||||
enabled off;
|
||||
}
|
||||
hls {
|
||||
enabled on;
|
||||
hls_path ./objs/nginx/html;
|
||||
|
@ -140,6 +144,14 @@ vhost players_pub {
|
|||
hls_window 30;
|
||||
}
|
||||
}
|
||||
# rtmp only, no hls, for chat(low latecy)
|
||||
vhost players_pub_rtmp {
|
||||
gop_cache off;
|
||||
hls {
|
||||
enabled off;
|
||||
}
|
||||
}
|
||||
|
||||
# for development
|
||||
vhost dev {
|
||||
chunk_size 65000;
|
||||
|
@ -201,16 +213,38 @@ vhost dev {
|
|||
}
|
||||
}
|
||||
|
||||
# vhost for bandwidth check
|
||||
# generally, the bandcheck vhost must be: bandcheck.srs.com,
|
||||
# or need to modify the vhost of client.
|
||||
vhost bandcheck.srs.com {
|
||||
chunk_size 65000;
|
||||
enabled on;
|
||||
# vhost for band width check
|
||||
bandcheck{
|
||||
enabled on;
|
||||
key test kate;
|
||||
interval 5;
|
||||
limit_kbps 4000;
|
||||
}
|
||||
enabled on;
|
||||
chunk_size 65000;
|
||||
# bandwidth check config.
|
||||
bandcheck {
|
||||
# whether support bandwidth check,
|
||||
# default: off.
|
||||
enabled on;
|
||||
# the key for server to valid,
|
||||
# if invalid key, server disconnect and abort the bandwidth check.
|
||||
key 35c9b402c12a7246868752e2878f7e0e;
|
||||
# the interval in seconds for bandwidth check,
|
||||
# server donot allow new test request.
|
||||
# default: 30
|
||||
interval 30;
|
||||
# the max available check bandwidth in kbps.
|
||||
# to avoid attack of bandwidth check.
|
||||
# default: 1000
|
||||
limit_kbps 4000;
|
||||
}
|
||||
}
|
||||
|
||||
# set the chunk size of vhost.
|
||||
vhost chunksize.vhost.com {
|
||||
# the default chunk size is 128, max is 65536,
|
||||
# some client does not support chunk size change,
|
||||
# vhost chunk size will override the global value.
|
||||
# default: global chunk size.
|
||||
chunk_size 128;
|
||||
}
|
||||
|
||||
# the http hook callback vhost, srs will invoke the hooks for specified events.
|
||||
|
@ -304,6 +338,7 @@ vhost hooks.callback.vhost.com {
|
|||
on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions;
|
||||
}
|
||||
}
|
||||
|
||||
# the mirror filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#Filtering-Introduction
|
||||
vhost mirror.transcode.vhost.com {
|
||||
transcode {
|
||||
|
@ -683,6 +718,7 @@ vhost stream.transcode.vhost.com {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
# the vhost which forward publish streams.
|
||||
vhost same.vhost.forward.vhost.com {
|
||||
# forward all publish stream to the specified server.
|
||||
|
@ -698,6 +734,7 @@ vhost same.vhost.forward.vhost.com {
|
|||
vhost change.vhost.forward.vhost.com {
|
||||
forward 127.0.0.1:1936?vhost=forward2.vhost.com 127.0.0.1:1937?vhost=forward3.vhost.com;
|
||||
}
|
||||
|
||||
# the vhost disabled.
|
||||
vhost removed.vhost.com {
|
||||
# whether the vhost is enabled.
|
||||
|
@ -705,6 +742,7 @@ vhost removed.vhost.com {
|
|||
# default: on
|
||||
enabled off;
|
||||
}
|
||||
|
||||
# the vhost with hls specified.
|
||||
vhost with-hls.vhost.com {
|
||||
hls {
|
||||
|
@ -741,6 +779,7 @@ vhost no-hls.vhost.com {
|
|||
enabled off;
|
||||
}
|
||||
}
|
||||
|
||||
# the vhost for min delay, donot cache any stream.
|
||||
vhost min.delay.com {
|
||||
# whether cache the last gop.
|
||||
|
@ -758,6 +797,7 @@ vhost min.delay.com {
|
|||
# default: 30
|
||||
queue_length 10;
|
||||
}
|
||||
|
||||
# the vhost for antisuck.
|
||||
vhost refer.anti_suck.com {
|
||||
# the common refer for play and publish.
|
||||
|
@ -776,6 +816,7 @@ vhost refer.anti_suck.com {
|
|||
# default: not specified.
|
||||
refer_play github.com github.io;
|
||||
}
|
||||
|
||||
# config for the pithy print,
|
||||
# which always print constant message specified by interval,
|
||||
# whatever the clients in concurrency.
|
||||
|
|
2
trunk/configure
vendored
2
trunk/configure
vendored
|
@ -116,7 +116,7 @@ MODULE_FILES=("srs_core" "srs_core_log" "srs_core_server"
|
|||
"srs_core_handshake" "srs_core_pithy_print"
|
||||
"srs_core_config" "srs_core_refer" "srs_core_reload"
|
||||
"srs_core_hls" "srs_core_forward" "srs_core_encoder"
|
||||
"srs_core_http" "srs_core_thread")
|
||||
"srs_core_http" "srs_core_thread" "srs_core_bandwidth")
|
||||
MODULE_DIR="src/core" . auto/modules.sh
|
||||
CORE_OBJS="${MODULE_OBJS[@]}"
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
7
trunk/scripts/_ffmpeg.demo.sh
Executable file
7
trunk/scripts/_ffmpeg.demo.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
for((;;)); do \
|
||||
./objs/ffmpeg/bin/ffmpeg -re -i ./doc/source.200kbps.768x320.flv \
|
||||
-vcodec copy -acodec copy \
|
||||
-f flv -y rtmp://127.0.0.1/live?vhost=demo.srs.com/livestream; \
|
||||
sleep 1; \
|
||||
done
|
7
trunk/scripts/_ffmpeg.players.sh
Executable file
7
trunk/scripts/_ffmpeg.players.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
for((;;)); do \
|
||||
./objs/ffmpeg/bin/ffmpeg -re -i ./doc/source.200kbps.768x320.flv \
|
||||
-vcodec copy -acodec copy \
|
||||
-f flv -y rtmp://127.0.0.1/live?vhost=players/livestream; \
|
||||
sleep 1; \
|
||||
done
|
10
trunk/scripts/_step.build.sh
Executable file
10
trunk/scripts/_step.build.sh
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
echo "编译SRS"
|
||||
./configure --with-ssl --with-hls --with-ffmpeg --with-http && make
|
||||
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:编译SRS失败"; exit $ret; fi
|
||||
|
||||
echo "编译SRS成功"
|
||||
exit 0
|
12
trunk/scripts/_step.start.api.server.sh
Executable file
12
trunk/scripts/_step.start.api.server.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
cmd="nohup python ./research/api-server/server.py 8085 >./objs/logs/api-server.log 2>&1 &"
|
||||
echo "启动API服务器:$cmd"
|
||||
pids=`ps aux|grep python|grep research|grep "api-server"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
nohup python ./research/api-server/server.py 8085 >./objs/logs/api-server.log 2>&1 &
|
||||
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动API服务器失败"; exit $ret; fi
|
||||
|
||||
echo "启动API服务器成功"
|
||||
exit 0
|
12
trunk/scripts/_step.start.ffmpeg.demo.sh
Executable file
12
trunk/scripts/_step.start.ffmpeg.demo.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
cmd="nohup bash ./scripts/_ffmpeg.demo.sh >./objs/logs/ffmpeg-demo.log 2>&1 &"
|
||||
echo "启动FFMPEG推送demo流(播放器上12路演示):$cmd"
|
||||
pids=`ps aux|grep scripts|grep "/_ffmpeg.demo.sh"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
nohup bash ./scripts/_ffmpeg.demo.sh >./objs/logs/ffmpeg-demo.log 2>&1 &
|
||||
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动FFMPEG推送demo流(播放器上12路演示)失败"; exit $ret; fi
|
||||
|
||||
echo "启动FFMPEG推送demo流(播放器上12路演示)成功"
|
||||
exit 0
|
12
trunk/scripts/_step.start.ffmpeg.players.sh
Executable file
12
trunk/scripts/_step.start.ffmpeg.players.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
cmd="nohup bash ./scripts/_ffmpeg.players.sh >./objs/logs/ffmpeg-players.log 2>&1 &"
|
||||
echo "启动FFMPEG推送players流(播放器上演示用):$cmd"
|
||||
pids=`ps aux|grep scripts|grep "/_ffmpeg.players.sh"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
nohup bash ./scripts/_ffmpeg.players.sh >./objs/logs/ffmpeg-players.log 2>&1 &
|
||||
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动FFMPEG推送players流(播放器上演示用)失败"; exit $ret; fi
|
||||
|
||||
echo "启动FFMPEG推送players流(播放器上演示用)成功"
|
||||
exit 0
|
12
trunk/scripts/_step.start.nginx.sh
Executable file
12
trunk/scripts/_step.start.nginx.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
cmd="sudo ./objs/nginx/sbin/nginx"
|
||||
echo "启动NGINX(HLS服务):$cmd"
|
||||
pids=`ps aux|grep nginx|grep process|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGKILL $pid; done
|
||||
sudo ./objs/nginx/sbin/nginx
|
||||
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动NGINX(HLS服务)失败"; exit $ret; fi
|
||||
|
||||
echo "启动NGINX(HLS服务)成功"
|
||||
exit 0
|
12
trunk/scripts/_step.start.srs.19350.sh
Executable file
12
trunk/scripts/_step.start.srs.19350.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
cmd="nohup ./objs/srs -c conf/srs.19350.conf > ./objs/logs/srs.19350.log 2>&1 &"
|
||||
echo "启动SRS转发服务器:$cmd"
|
||||
pids=`ps aux|grep srs|grep "./objs"|grep "srs.19350.conf"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
nohup ./objs/srs -c conf/srs.19350.conf > ./objs/logs/srs.19350.log 2>&1 &
|
||||
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动SRS转发服务器失败"; exit $ret; fi
|
||||
|
||||
echo "启动SRS转发服务器成功"
|
||||
exit 0
|
12
trunk/scripts/_step.start.srs.sh
Executable file
12
trunk/scripts/_step.start.srs.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
cmd="nohup ./objs/srs -c conf/srs.conf >./objs/logs/srs.log 2>&1 &"
|
||||
echo "启动SRS服务器:$cmd"
|
||||
pids=`ps aux|grep srs|grep "./objs"|grep "srs.conf"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
nohup ./objs/srs -c conf/srs.conf >./objs/logs/srs.log 2>&1 &
|
||||
ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动SRS失败"; exit $ret; fi
|
||||
|
||||
echo "启动SRS服务器成功"
|
||||
exit 0
|
8
trunk/scripts/build.sh
Executable file
8
trunk/scripts/build.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
# step 1: build srs
|
||||
bash scripts/_step.build.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
echo "编译SRS成功"
|
42
trunk/scripts/dev.sh
Executable file
42
trunk/scripts/dev.sh
Executable file
|
@ -0,0 +1,42 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
# linux shell color support.
|
||||
RED="\\e[31m"
|
||||
GREEN="\\e[32m"
|
||||
YELLOW="\\e[33m"
|
||||
BLACK="\\e[0m"
|
||||
|
||||
# step 1: build srs
|
||||
#bash scripts/_step.build.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 2: start srs
|
||||
bash scripts/_step.start.srs.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 3(optinal): start srs listen at 19350 to forward to
|
||||
#bash scripts/_step.start.srs.19350.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 4(optinal): start nginx for HLS
|
||||
bash scripts/_step.start.nginx.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 5(optinal): start http hooks for srs callback
|
||||
bash scripts/_step.start.api.server.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 6: publish demo live stream
|
||||
#bash scripts/_step.start.ffmpeg.demo.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 7: publish players live stream
|
||||
#bash scripts/_step.start.ffmpeg.players.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 8: add server ip to client hosts as demo.
|
||||
ip=`ifconfig|grep "inet"|grep "addr"|grep "Mask"|grep -v "127.0.0.1"|awk 'NR==1 {print $2}'|awk -F ':' '{print $2}'`
|
||||
echo -e "${GREEN}SRS系统开发环境启动成功${BLACK}"
|
||||
echo -e "${BLACK}播放器演示:${BLACK}"
|
||||
echo -e "${RED} http://$ip/players/srs_player.html?vhost=players${BLACK}"
|
||||
echo -e "${BLACK}编码器演示:${BLACK}"
|
||||
echo -e "${RED} http://$ip/players/srs_publisher.html?vhost=players${BLACK}"
|
||||
echo -e "${BLACK}视频会议演示:${BLACK}"
|
||||
echo -e "${RED} http://$ip/players/srs_chat.html?vhost=players${BLACK}"
|
||||
echo -e "${BLACK}服务器测速演示:${BLACK}"
|
||||
echo -e "${RED} http://$ip/players/srs_bwt.html?vhost=players${BLACK}"
|
45
trunk/scripts/run.sh
Executable file
45
trunk/scripts/run.sh
Executable file
|
@ -0,0 +1,45 @@
|
|||
#!/bin/bash
|
||||
src_dir='src'
|
||||
if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi
|
||||
|
||||
# linux shell color support.
|
||||
RED="\\e[31m"
|
||||
GREEN="\\e[32m"
|
||||
YELLOW="\\e[33m"
|
||||
BLACK="\\e[0m"
|
||||
|
||||
# step 1: build srs
|
||||
#bash scripts/_step.build.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 2: start srs
|
||||
bash scripts/_step.start.srs.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 3(optinal): start srs listen at 19350 to forward to
|
||||
bash scripts/_step.start.srs.19350.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 4(optinal): start nginx for HLS
|
||||
bash scripts/_step.start.nginx.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 5(optinal): start http hooks for srs callback
|
||||
bash scripts/_step.start.api.server.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 6: publish demo live stream
|
||||
bash scripts/_step.start.ffmpeg.demo.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 7: publish players live stream
|
||||
bash scripts/_step.start.ffmpeg.players.sh; ret=$?; if [[ 0 -ne $ret ]]; then exit $ret; fi
|
||||
|
||||
# step 8: add server ip to client hosts as demo.
|
||||
ip=`ifconfig|grep "inet"|grep "addr"|grep "Mask"|grep -v "127.0.0.1"|awk 'NR==1 {print $2}'|awk -F ':' '{print $2}'`
|
||||
cat<<END
|
||||
默认的12路流演示:
|
||||
http://$ip/players
|
||||
默认的播放器流演示:
|
||||
http://$ip/players/srs_player.html?vhost=players
|
||||
推流(主播)应用演示:
|
||||
http://$ip/players/srs_publisher.html?vhost=players
|
||||
视频会议(聊天室)应用演示:
|
||||
http://$ip/players/srs_chat.html?vhost=players
|
||||
END
|
||||
echo -e "${GREEN}演示地址:${BLACK}"
|
||||
echo -e "${RED} http://$ip${BLACK}"
|
31
trunk/scripts/stop.sh
Executable file
31
trunk/scripts/stop.sh
Executable file
|
@ -0,0 +1,31 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "停止SRS服务器"
|
||||
ps aux|grep srs|grep "./objs"|grep "srs.conf"
|
||||
pids=`ps aux|grep srs|grep "./objs"|grep "srs.conf"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
|
||||
echo "停止SRS转发服务器"
|
||||
ps aux|grep srs|grep "./objs"|grep "srs.19350.conf"
|
||||
pids=`ps aux|grep srs|grep "./objs"|grep "srs.19350.conf"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
|
||||
# step 4(optinal): start nginx for HLS
|
||||
echo "停止NGINX(HLS服务)"
|
||||
ps aux|grep nginx|grep process
|
||||
pids=`ps aux|grep nginx|grep process|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; sudo kill -s SIGKILL $pid; done
|
||||
|
||||
# step 5(optinal): start http hooks for srs callback
|
||||
echo "停止API服务器"
|
||||
ps aux|grep python|grep research|grep "api-server"
|
||||
pids=`ps aux|grep python|grep research|grep "api-server"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
|
||||
# step 6: publish demo live stream
|
||||
echo "停止FFMPEG推送demo流(播放器上12路演示)"
|
||||
ps aux|grep scripts|grep "ffmpeg.demo.sh"
|
||||
pids=`ps aux|grep scripts|grep "/ffmpeg.demo.sh"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
|
||||
# step 7: publish players live stream
|
||||
echo "停止FFMPEG推送players流(播放器上演示用)"
|
||||
ps aux|grep scripts|grep "ffmpeg.players.sh"
|
||||
pids=`ps aux|grep scripts|grep "/ffmpeg.players.sh"|awk '{print $2}'`; for pid in $pids; do echo "结束现有进程:$pid"; kill -s SIGKILL $pid; done
|
||||
|
||||
echo "SRS系统服务均已停止"
|
|
@ -79,7 +79,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#define RTMP_SIG_SRS_WEB "http://blog.csdn.net/win_lin"
|
||||
#define RTMP_SIG_SRS_EMAIL "winterserver@126.com"
|
||||
#define RTMP_SIG_SRS_LICENSE "The MIT License (MIT)"
|
||||
#define RTMP_SIG_SRS_COPYRIGHT "Copyright (c) 2013 winlin,wenjiegit"
|
||||
#define RTMP_SIG_SRS_COPYRIGHT "Copyright (c) 2013 winlin"
|
||||
#define RTMP_SIG_SRS_CONTRIBUTOR "winlin,wenjiegit"
|
||||
|
||||
// compare
|
||||
|
@ -108,4 +108,15 @@ extern void srs_vhost_resolve(std::string& vhost, std::string& app);
|
|||
// close the netfd, and close the underlayer fd.
|
||||
extern void srs_close_stfd(st_netfd_t& stfd);
|
||||
|
||||
/**
|
||||
* disable copy constructor of class
|
||||
*/
|
||||
#define disable_default_copy(className)\
|
||||
private:\
|
||||
/** \
|
||||
* disable the copy constructor and operator=, donot allow directly copy. \
|
||||
*/ \
|
||||
className(const className&); \
|
||||
className& operator= (const className&)
|
||||
|
||||
#endif
|
||||
|
|
450
trunk/src/core/srs_core_bandwidth.cpp
Normal file
450
trunk/src/core/srs_core_bandwidth.cpp
Normal file
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 wenjiegit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <srs_core_bandwidth.hpp>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#include <srs_core_rtmp.hpp>
|
||||
#include <srs_core_error.hpp>
|
||||
#include <srs_core_amf0.hpp>
|
||||
#include <srs_core_protocol.hpp>
|
||||
#include <srs_core_config.hpp>
|
||||
#include <srs_core_autofree.hpp>
|
||||
|
||||
SrsBandwidth::SrsBandwidth()
|
||||
{
|
||||
}
|
||||
|
||||
SrsBandwidth::~SrsBandwidth()
|
||||
{
|
||||
}
|
||||
|
||||
int SrsBandwidth::bandwidth_test(SrsRequest* _req, st_netfd_t stfd, SrsRtmp* _rtmp)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
rtmp = _rtmp;
|
||||
req = _req;
|
||||
|
||||
if (!config->get_bw_check_enabled(req->vhost)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// validate the bandwidth check key
|
||||
std::string key = "key=" + config->get_bw_check_key(req->vhost);
|
||||
if (req->tcUrl.find(key) == std::string::npos) {
|
||||
ret = ERROR_SYSTEM_BANDWIDTH_KEY;
|
||||
srs_error("check the vhost=%s %s failed, tcUrl=%s, ret=%d",
|
||||
req->vhost.c_str(), key.c_str(), req->tcUrl.c_str(), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// shared global last check time,
|
||||
// to avoid attach by bandwidth check,
|
||||
// if client request check in the window(specifeid by interval),
|
||||
// directly reject the request.
|
||||
static int64_t last_check_time = 0;
|
||||
int interval_ms = config->get_bw_check_interval_ms(req->vhost);
|
||||
|
||||
int64_t time_now = srs_get_system_time_ms();
|
||||
// reject the connection in the interval window.
|
||||
if (last_check_time > 0 && time_now - last_check_time < interval_ms) {
|
||||
ret = ERROR_SYSTEM_BANDWIDTH_DENIED;
|
||||
srs_trace("bandcheck denied, "
|
||||
"last_check=%"PRId64", now=%"PRId64", interval=%d",
|
||||
last_check_time, time_now, interval_ms);
|
||||
|
||||
rtmp->response_connect_reject(req, "bandcheck rejected");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// accept and do bandwidth check.
|
||||
last_check_time = time_now;
|
||||
|
||||
char* local_ip = 0;
|
||||
if ((ret = get_local_ip(stfd, local_ip)) != ERROR_SUCCESS) {
|
||||
srs_error("get local ip failed. ret = %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = rtmp->response_connect_app(req, local_ip)) != ERROR_SUCCESS) {
|
||||
srs_error("response connect app failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return do_bandwidth_check();
|
||||
}
|
||||
|
||||
int SrsBandwidth::get_local_ip(st_netfd_t stfd, char *&local_ip)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
int fd = st_netfd_fileno(stfd);
|
||||
|
||||
// discovery client information
|
||||
sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (getsockname(fd, (sockaddr*)&addr, &addrlen) == -1) {
|
||||
ret = ERROR_SOCKET_GET_LOCAL_IP;
|
||||
srs_error("discovery local ip information failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("get local ip success.");
|
||||
|
||||
// ip v4 or v6
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
if ((inet_ntop(addr.sin_family, &addr.sin_addr, buf, sizeof(buf))) == NULL) {
|
||||
ret = ERROR_SOCKET_GET_LOCAL_IP;
|
||||
srs_error("convert local ip information failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
local_ip = new char[strlen(buf) + 1];
|
||||
strcpy(local_ip, buf);
|
||||
|
||||
srs_verbose("get local ip of client ip=%s, fd=%d", buf, fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsBandwidth::do_bandwidth_check()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsProtocol* protocol = rtmp->get_protocol();
|
||||
|
||||
int play_duration_ms = 3000;
|
||||
int play_interval_ms = 0;
|
||||
int play_actual_duration_ms = 0;
|
||||
int play_bytes = 0;
|
||||
|
||||
int publish_duration_ms = 3000;
|
||||
int publish_interval_ms = 0;
|
||||
int publish_actual_duration_ms = 0;
|
||||
int publish_bytes = 0;
|
||||
|
||||
int limit_kbps = config->get_bw_check_limit_kbps(req->vhost);
|
||||
|
||||
int64_t start_time = srs_get_system_time_ms();
|
||||
|
||||
ret = check_play(play_duration_ms,
|
||||
play_interval_ms, play_actual_duration_ms, play_bytes, limit_kbps);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
srs_error("band width play check failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = check_publish(publish_duration_ms,
|
||||
publish_interval_ms, publish_actual_duration_ms, publish_bytes, limit_kbps);
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
srs_error("band width publish check failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t end_time = srs_get_system_time_ms();
|
||||
int play_kbps = play_bytes * 8 / play_actual_duration_ms;
|
||||
int publish_kbps = publish_bytes * 8 / publish_actual_duration_ms;
|
||||
|
||||
srs_trace("bandwidth check finished. start=%"PRId64"ms, end=%"PRId64"ms, "
|
||||
"duartion=%dms, play=%dkbps, publish=%dkbps, tcUrl=%s, ret=%#x",
|
||||
start_time, end_time, (int)(end_time - start_time), play_kbps, publish_kbps,
|
||||
req->tcUrl.c_str(), ret);
|
||||
|
||||
// send finished msg
|
||||
SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_finish();
|
||||
pkt->data->set("code", new SrsAmf0Number(ERROR_SUCCESS));
|
||||
pkt->data->set("start_time", new SrsAmf0Number(start_time));
|
||||
pkt->data->set("end_time", new SrsAmf0Number(end_time));
|
||||
pkt->data->set("play_kbps", new SrsAmf0Number(play_kbps));
|
||||
pkt->data->set("publish_kbps", new SrsAmf0Number(publish_kbps));
|
||||
pkt->data->set("play_bytes", new SrsAmf0Number(play_bytes));
|
||||
pkt->data->set("play_time", new SrsAmf0Number(play_actual_duration_ms));
|
||||
pkt->data->set("publish_bytes", new SrsAmf0Number(publish_bytes));
|
||||
pkt->data->set("publish_time", new SrsAmf0Number(publish_actual_duration_ms));
|
||||
|
||||
SrsCommonMessage* msg = (new SrsCommonMessage())->set_packet(pkt, 0);
|
||||
if ((ret = rtmp->send_message(msg)) != ERROR_SUCCESS) {
|
||||
srs_error("send bandwidth check finish message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// if flash, we notice the result, and expect a final packet.
|
||||
while (true) {
|
||||
SrsCommonMessage* msg = NULL;
|
||||
SrsBandwidthPacket* pkt = NULL;
|
||||
if ((ret = srs_rtmp_expect_message<SrsBandwidthPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
|
||||
// info level to ignore and return success.
|
||||
srs_info("expect final message failed. ret=%d", ret);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
SrsAutoFree(SrsCommonMessage, msg, false);
|
||||
srs_info("get final message success.");
|
||||
|
||||
if (pkt->is_flash_final()) {
|
||||
srs_info("BW check recv flash final response.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
srs_info("BW check finished.");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsBandwidth::check_play(
|
||||
int duration_ms, int interval_ms, int& actual_duration_ms,
|
||||
int& play_bytes, int max_play_kbps)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsProtocol* protocol = rtmp->get_protocol();
|
||||
|
||||
if (true) {
|
||||
// send start play command to client
|
||||
SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_start_play();
|
||||
|
||||
pkt->data->set("duration_ms", new SrsAmf0Number(duration_ms));
|
||||
pkt->data->set("interval_ms", new SrsAmf0Number(interval_ms));
|
||||
|
||||
SrsCommonMessage* msg = (new SrsCommonMessage())->set_packet(pkt, 0);
|
||||
if ((ret = rtmp->send_message(msg)) != ERROR_SUCCESS) {
|
||||
srs_error("send bandwidth check start play message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_info("BW check begin.");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// recv client's starting play response
|
||||
SrsCommonMessage* msg = NULL;
|
||||
SrsBandwidthPacket* pkt = NULL;
|
||||
if ((ret = srs_rtmp_expect_message<SrsBandwidthPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
|
||||
srs_error("expect bandwidth message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
SrsAutoFree(SrsCommonMessage, msg, false);
|
||||
srs_info("get bandwidth message succes.");
|
||||
|
||||
if (pkt->is_starting_play()) {
|
||||
srs_info("BW check recv play begin response.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// send play data to client
|
||||
int64_t current_time = srs_get_system_time_ms();
|
||||
int size = 1024; // TODO: FIXME: magic number
|
||||
char random_data[size];
|
||||
memset(random_data, 0x01, size);
|
||||
|
||||
int interval = 0;
|
||||
while ( (srs_get_system_time_ms() - current_time) < duration_ms ) {
|
||||
st_usleep(interval);
|
||||
|
||||
// TODO: FIXME: use shared ptr message.
|
||||
SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_playing();
|
||||
|
||||
// TODO: FIXME: magic number
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
char buf[32]; // TODO: FIXME: magic number
|
||||
sprintf(buf, "%d", i);
|
||||
pkt->data->set(buf, new SrsAmf0String(random_data));
|
||||
}
|
||||
|
||||
// TODO: FIXME: get length from the rtmp protocol stack.
|
||||
play_bytes += pkt->get_payload_length();
|
||||
|
||||
SrsCommonMessage* msg = (new SrsCommonMessage())->set_packet(pkt, 0);
|
||||
if ((ret = rtmp->send_message(msg)) != ERROR_SUCCESS) {
|
||||
srs_error("send bandwidth check play messages failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// sleep while current kbps <= max_play_kbps
|
||||
int kbps = 0;
|
||||
while (true) {
|
||||
if(srs_get_system_time_ms() - current_time != 0)
|
||||
kbps = play_bytes * 8 / (srs_get_system_time_ms() - current_time);
|
||||
|
||||
if (kbps > max_play_kbps) {
|
||||
st_usleep(500);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
actual_duration_ms = srs_get_system_time_ms() - current_time;
|
||||
srs_info("BW check send play bytes over.");
|
||||
|
||||
if (true) {
|
||||
// notify client to stop play
|
||||
SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_stop_play();
|
||||
pkt->data->set("duration_ms", new SrsAmf0Number(duration_ms));
|
||||
pkt->data->set("interval_ms", new SrsAmf0Number(interval_ms));
|
||||
pkt->data->set("duration_delta", new SrsAmf0Number(actual_duration_ms));
|
||||
pkt->data->set("bytes_delta", new SrsAmf0Number(play_bytes));
|
||||
|
||||
SrsCommonMessage* msg = (new SrsCommonMessage())->set_packet(pkt, 0);
|
||||
if ((ret = rtmp->send_message(msg)) != ERROR_SUCCESS) {
|
||||
srs_error("send bandwidth check stop play message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_info("BW check stop play bytes.");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// recv client's stop play response.
|
||||
SrsCommonMessage* msg = NULL;
|
||||
SrsBandwidthPacket* pkt = NULL;
|
||||
if ((ret = srs_rtmp_expect_message<SrsBandwidthPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
|
||||
srs_error("expect bandwidth message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
SrsAutoFree(SrsCommonMessage, msg, false);
|
||||
srs_info("get bandwidth message succes.");
|
||||
|
||||
if (pkt->is_stopped_play()) {
|
||||
srs_info("BW check recv stop play response.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsBandwidth::check_publish(
|
||||
int duration_ms, int interval_ms, int& actual_duration_ms,
|
||||
int& publish_bytes, int max_pub_kbps)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsProtocol* protocol = rtmp->get_protocol();
|
||||
|
||||
if (true) {
|
||||
// notify client to start publish
|
||||
SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_start_publish();
|
||||
|
||||
pkt->data->set("duration_ms", new SrsAmf0Number(duration_ms));
|
||||
pkt->data->set("interval_ms", new SrsAmf0Number(interval_ms));
|
||||
|
||||
SrsCommonMessage* msg = (new SrsCommonMessage())->set_packet(pkt, 0);
|
||||
if ((ret = rtmp->send_message(msg)) != ERROR_SUCCESS) {
|
||||
srs_error("send bandwidth check start publish message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_info("BW check publish begin.");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// read client's notification of starting publish
|
||||
SrsCommonMessage* msg = NULL;
|
||||
SrsBandwidthPacket* pkt = NULL;
|
||||
if ((ret = srs_rtmp_expect_message<SrsBandwidthPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
|
||||
srs_error("expect bandwidth message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
SrsAutoFree(SrsCommonMessage, msg, false);
|
||||
srs_info("get bandwidth message succes.");
|
||||
|
||||
if (pkt->is_starting_publish()) {
|
||||
srs_info("BW check recv publish begin response.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// recv publish msgs until @duration_ms ms
|
||||
int64_t current_time = srs_get_system_time_ms();
|
||||
while ( (srs_get_system_time_ms() - current_time) < duration_ms ) {
|
||||
st_usleep(0);
|
||||
|
||||
SrsCommonMessage* msg = NULL;
|
||||
if ((ret = rtmp->recv_message(&msg)) != ERROR_SUCCESS) {
|
||||
srs_error("recv message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
SrsAutoFree(SrsCommonMessage, msg, false);
|
||||
|
||||
// TODO: FIXME.
|
||||
publish_bytes += msg->header.payload_length;
|
||||
|
||||
int kbps = 0;
|
||||
while (true) {
|
||||
if(srs_get_system_time_ms() - current_time != 0)
|
||||
kbps = publish_bytes * 8 / (srs_get_system_time_ms() - current_time);
|
||||
|
||||
if (kbps > max_pub_kbps) {
|
||||
st_usleep(500);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
actual_duration_ms = srs_get_system_time_ms() - current_time;
|
||||
srs_info("BW check recv publish data over.");
|
||||
|
||||
if (true) {
|
||||
// notify client to stop publish
|
||||
SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_stop_publish();
|
||||
pkt->data->set("duration_ms", new SrsAmf0Number(duration_ms));
|
||||
pkt->data->set("interval_ms", new SrsAmf0Number(interval_ms));
|
||||
pkt->data->set("duration_delta", new SrsAmf0Number(actual_duration_ms));
|
||||
pkt->data->set("bytes_delta", new SrsAmf0Number(publish_bytes));
|
||||
|
||||
SrsCommonMessage* msg = (new SrsCommonMessage())->set_packet(pkt, 0);
|
||||
if ((ret = rtmp->send_message(msg)) != ERROR_SUCCESS) {
|
||||
srs_error("send bandwidth check stop publish message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_info("BW check stop publish bytes.");
|
||||
}
|
||||
|
||||
// expect client to stop publish
|
||||
// if flash client, we never expect the client stop publish bytes,
|
||||
// for the flash send call packet to test publish bandwidth,
|
||||
// there are many many packets in the queue.
|
||||
// we just ignore the packet and send the bandwidth test data.
|
||||
// TODO: FIXME: check whether flash client.
|
||||
while (false) {
|
||||
// recv client's stop publish response.
|
||||
SrsCommonMessage* msg = NULL;
|
||||
SrsBandwidthPacket* pkt = NULL;
|
||||
if ((ret = srs_rtmp_expect_message<SrsBandwidthPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
|
||||
srs_error("expect bandwidth message failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
SrsAutoFree(SrsCommonMessage, msg, false);
|
||||
srs_info("get bandwidth message succes.");
|
||||
|
||||
if (pkt->is_stopped_publish()) {
|
||||
srs_info("BW check recv stop publish response.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
94
trunk/src/core/srs_core_bandwidth.hpp
Normal file
94
trunk/src/core/srs_core_bandwidth.hpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 wenjiegit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SRS_CORE_BANDWIDTH_HPP
|
||||
#define SRS_CORE_BANDWIDTH_HPP
|
||||
|
||||
/*
|
||||
#include <srs_core_bandwidth.hpp>
|
||||
*/
|
||||
#include <srs_core.hpp>
|
||||
|
||||
class SrsRequest;
|
||||
class SrsRtmp;
|
||||
|
||||
/**
|
||||
* bandwidth test agent which provides the interfaces for bandwidth check.
|
||||
* 1. if vhost disabled bandwidth check, ignore.
|
||||
* 2. otherwise, check the key, error if verify failed.
|
||||
* 3. check the interval limit, error if bandwidth in the interval window.
|
||||
* 4. check the bandwidth under the max kbps.
|
||||
* 5. send the bandwidth data to client.
|
||||
* bandwidth workflow:
|
||||
* +------------+ +----------+
|
||||
* | Client | | Server |
|
||||
* +-----+------+ +-----+----+
|
||||
* | |
|
||||
* | connect vhost------> | if vhost enable bandwidth,
|
||||
* | <-----result(success) | do bandwidth check.
|
||||
* | |
|
||||
* | <----call(start play) | onSrsBandCheckStartPlayBytes
|
||||
* | result(playing)-----> | onSrsBandCheckStartingPlayBytes
|
||||
* | <-------data(playing) | onSrsBandCheckStartingPlayBytes
|
||||
* | <-----call(stop play) | onSrsBandCheckStopPlayBytes
|
||||
* | result(stopped)-----> | onSrsBandCheckStoppedPlayBytes
|
||||
* | |
|
||||
* | <-call(start publish) | onSrsBandCheckStartPublishBytes
|
||||
* | result(publishing)--> | onSrsBandCheckStartingPublishBytes
|
||||
* | data(publishing)----> | onSrsBandCheckStartingPublishBytes
|
||||
* | <--call(stop publish) | onSrsBandCheckStopPublishBytes
|
||||
* | result(stopped)(1)--> | onSrsBandCheckStoppedPublishBytes
|
||||
* | |
|
||||
* | <--------------report |
|
||||
* | final(2)------------> | finalClientPacket
|
||||
* | <END> |
|
||||
*
|
||||
* 1. when flash client, server ignore the publish stopped result,
|
||||
* and directly send the report to flash client.
|
||||
* 2. flash client only. when got report, flash client should send out
|
||||
* a final packet and close the connection immediately.
|
||||
*/
|
||||
class SrsBandwidth
|
||||
{
|
||||
private:
|
||||
SrsRequest* req;
|
||||
SrsRtmp* rtmp;
|
||||
public:
|
||||
SrsBandwidth();
|
||||
virtual ~SrsBandwidth();
|
||||
public:
|
||||
/**
|
||||
* do the bandwidth test.
|
||||
*/
|
||||
virtual int bandwidth_test(SrsRequest* _req, st_netfd_t stfd, SrsRtmp* _rtmp);
|
||||
private:
|
||||
virtual int get_local_ip(st_netfd_t stfd, char *&local_ip);
|
||||
/**
|
||||
* used to process band width check from client.
|
||||
*/
|
||||
virtual int do_bandwidth_check();
|
||||
virtual int check_play(int duration_ms, int interval_ms, int& actual_duration_ms, int& play_bytes, int max_play_kbps);
|
||||
virtual int check_publish(int duration_ms, int interval_ms, int& actual_duration_ms, int& publish_bytes, int max_pub_kbps);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -41,6 +41,7 @@ using namespace std;
|
|||
#include <srs_core_refer.hpp>
|
||||
#include <srs_core_hls.hpp>
|
||||
#include <srs_core_http.hpp>
|
||||
#include <srs_core_bandwidth.hpp>
|
||||
|
||||
#define SRS_PULSE_TIMEOUT_MS 100
|
||||
#define SRS_SEND_TIMEOUT_US 5000000L
|
||||
|
@ -58,6 +59,7 @@ SrsClient::SrsClient(SrsServer* srs_server, st_netfd_t client_stfd)
|
|||
#ifdef SRS_HTTP
|
||||
http_hooks = new SrsHttpHooks();
|
||||
#endif
|
||||
bandwidth = new SrsBandwidth();
|
||||
|
||||
config->subscribe(this);
|
||||
}
|
||||
|
@ -74,6 +76,7 @@ SrsClient::~SrsClient()
|
|||
#ifdef SRS_HTTP
|
||||
srs_freep(http_hooks);
|
||||
#endif
|
||||
srs_freep(bandwidth);
|
||||
}
|
||||
|
||||
// TODO: return detail message when error for client.
|
||||
|
@ -154,38 +157,11 @@ int SrsClient::service_cycle()
|
|||
}
|
||||
srs_verbose("set peer bandwidth success");
|
||||
|
||||
if (config->get_bw_check_enabled(req->vhost, req->bw_key)) {
|
||||
static int64_t last_check_time_ms = srs_get_system_time_ms();
|
||||
int64_t interval_ms = 0;
|
||||
int limit_kbps = 0;
|
||||
|
||||
config->get_bw_check_settings(req->vhost, interval_ms, limit_kbps);
|
||||
|
||||
if((srs_get_system_time_ms() - last_check_time_ms) < interval_ms
|
||||
&& last_check_time_ms != srs_get_system_time_ms())
|
||||
{
|
||||
srs_trace("bandcheck interval less than limted interval. last time=%lld, current time=%lld"
|
||||
, last_check_time_ms, srs_get_system_time_ms());
|
||||
|
||||
return rtmp->response_connect_reject(req, "your bandcheck frequency is too high!");
|
||||
} else {
|
||||
last_check_time_ms = srs_get_system_time_ms(); // update last check time
|
||||
char* local_ip = 0;
|
||||
|
||||
if ((ret = get_local_ip(local_ip)) != ERROR_SUCCESS) {
|
||||
srs_error("get local ip failed. ret = %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = rtmp->response_connect_app(req, local_ip)) != ERROR_SUCCESS) {
|
||||
srs_error("response connect app failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return rtmp->start_bandwidth_check(limit_kbps);
|
||||
}
|
||||
}
|
||||
|
||||
// do bandwidth test if connect to the vhost which is for bandwidth check.
|
||||
if (config->get_bw_check_enabled(req->vhost)) {
|
||||
return bandwidth->bandwidth_test(req, stfd, rtmp);
|
||||
}
|
||||
|
||||
if ((ret = rtmp->response_connect_app(req)) != ERROR_SUCCESS) {
|
||||
srs_error("response connect app failed. ret=%d", ret);
|
||||
return ret;
|
||||
|
|
|
@ -44,6 +44,7 @@ class SrsCommonMessage;
|
|||
#ifdef SRS_HTTP
|
||||
class SrsHttpHooks;
|
||||
#endif
|
||||
class SrsBandwidth;
|
||||
|
||||
/**
|
||||
* the client provides the main logic control for RTMP clients.
|
||||
|
@ -59,6 +60,7 @@ private:
|
|||
#ifdef SRS_HTTP
|
||||
SrsHttpHooks* http_hooks;
|
||||
#endif
|
||||
SrsBandwidth* bandwidth;
|
||||
public:
|
||||
SrsClient(SrsServer* srs_server, st_netfd_t client_stfd);
|
||||
virtual ~SrsClient();
|
||||
|
|
|
@ -1443,30 +1443,27 @@ SrsConfDirective* SrsConfig::get_listen()
|
|||
return root->get("listen");
|
||||
}
|
||||
|
||||
int SrsConfig::get_chunk_size()
|
||||
{
|
||||
SrsConfDirective* conf = root->get("chunk_size");
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
return ::atoi(conf->arg0().c_str());
|
||||
}
|
||||
|
||||
int SrsConfig::get_chunk_size(const std::string &vhost)
|
||||
{
|
||||
SrsConfDirective* conf = get_vhost(vhost);
|
||||
|
||||
if (!conf) {
|
||||
return get_chunk_size();
|
||||
return SRS_CONF_DEFAULT_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
SrsConfDirective* conf_vhost = conf->get("chunk_size");
|
||||
if (!conf_vhost) {
|
||||
return get_chunk_size();
|
||||
conf = conf->get("chunk_size");
|
||||
if (!conf) {
|
||||
// vhost does not specify the chunk size,
|
||||
// use the global instead.
|
||||
conf = root->get("chunk_size");
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
return ::atoi(conf->arg0().c_str());
|
||||
}
|
||||
|
||||
return ::atoi(conf_vhost->arg0().c_str());
|
||||
return ::atoi(conf->arg0().c_str());
|
||||
}
|
||||
|
||||
int SrsConfig::get_pithy_print_publish()
|
||||
|
@ -1568,6 +1565,90 @@ void SrsConfig::get_bw_check_settings(const std::string &vhost, int64_t &interva
|
|||
}
|
||||
}
|
||||
|
||||
bool SrsConfig::get_bw_check_enabled(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* conf = get_vhost(vhost);
|
||||
|
||||
if (!conf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
conf = conf->get("bandcheck");
|
||||
if (!conf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
conf = conf->get("enabled");
|
||||
if (!conf || conf->arg0() != "on") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string SrsConfig::get_bw_check_key(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* conf = get_vhost(vhost);
|
||||
|
||||
if (!conf) {
|
||||
return "";
|
||||
}
|
||||
|
||||
conf = conf->get("bandcheck");
|
||||
if (!conf) {
|
||||
return "";
|
||||
}
|
||||
|
||||
conf = conf->get("key");
|
||||
if (!conf) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return conf->arg0();
|
||||
}
|
||||
|
||||
int SrsConfig::get_bw_check_interval_ms(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* conf = get_vhost(vhost);
|
||||
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL;
|
||||
}
|
||||
|
||||
conf = conf->get("bandcheck");
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL;
|
||||
}
|
||||
|
||||
conf = conf->get("interval_ms");
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL;
|
||||
}
|
||||
|
||||
return ::atoi(conf->arg0().c_str()) * 1000;
|
||||
}
|
||||
|
||||
int SrsConfig::get_bw_check_limit_kbps(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* conf = get_vhost(vhost);
|
||||
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS;
|
||||
}
|
||||
|
||||
conf = conf->get("bandcheck");
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS;
|
||||
}
|
||||
|
||||
conf = conf->get("limit_kbps");
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS;
|
||||
}
|
||||
|
||||
return ::atoi(conf->arg0().c_str());
|
||||
}
|
||||
|
||||
int SrsConfig::get_pithy_print_encoder()
|
||||
{
|
||||
SrsConfDirective* pithy = root->get("encoder");
|
||||
|
|
|
@ -53,6 +53,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#define SRS_CONF_DEFAULT_QUEUE_LENGTH 30
|
||||
// in seconds, the paused queue length.
|
||||
#define SRS_CONF_DEFAULT_PAUSED_LENGTH 10
|
||||
// the interval in seconds for bandwidth check
|
||||
#define SRS_CONF_DEFAULT_BANDWIDTH_INTERVAL 30
|
||||
// the interval in seconds for bandwidth check
|
||||
#define SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS 1000
|
||||
|
||||
#define SRS_CONF_DEFAULT_CHUNK_SIZE 4096
|
||||
|
||||
|
@ -164,15 +168,16 @@ public:
|
|||
virtual SrsConfDirective* get_refer_play(std::string vhost);
|
||||
virtual SrsConfDirective* get_refer_publish(std::string vhost);
|
||||
virtual SrsConfDirective* get_listen();
|
||||
virtual int get_chunk_size();
|
||||
virtual int get_chunk_size(const std::string& vhost);
|
||||
virtual int get_pithy_print_publish();
|
||||
virtual int get_pithy_print_forwarder();
|
||||
virtual int get_pithy_print_encoder();
|
||||
virtual int get_pithy_print_hls();
|
||||
virtual int get_pithy_print_play();
|
||||
virtual bool get_bw_check_enabled(const std::string &vhost, const std::string &key);
|
||||
virtual void get_bw_check_settings(const std::string &vhost, int64_t &interval_ms, int &limit_kbps);
|
||||
virtual bool get_bw_check_enabled(const std::string& vhost);
|
||||
virtual std::string get_bw_check_key(const std::string& vhost);
|
||||
virtual int get_bw_check_interval_ms(const std::string& vhost);
|
||||
virtual int get_bw_check_limit_kbps(const std::string& vhost);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -87,6 +87,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#define ERROR_SYSTEM_IP_INVALID 411
|
||||
#define ERROR_SYSTEM_FORWARD_LOOP 412
|
||||
#define ERROR_SYSTEM_WAITPID 413
|
||||
#define ERROR_SYSTEM_BANDWIDTH_KEY 414
|
||||
#define ERROR_SYSTEM_BANDWIDTH_DENIED 415
|
||||
|
||||
// see librtmp.
|
||||
// failed when open ssl create the dh
|
||||
|
|
|
@ -32,6 +32,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#include <srs_core_stream.hpp>
|
||||
#include <srs_core_autofree.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/****************************************************************************
|
||||
*****************************************************************************
|
||||
****************************************************************************/
|
||||
|
@ -209,6 +211,35 @@ messages.
|
|||
#define RTMP_AMF0_DATA_SET_DATAFRAME "@setDataFrame"
|
||||
#define RTMP_AMF0_DATA_ON_METADATA "onMetaData"
|
||||
|
||||
/**
|
||||
* band width check method name, which will be invoked by client.
|
||||
* band width check mothods use SrsBandwidthPacket as its internal packet type,
|
||||
* so ensure you set command name when you use it.
|
||||
*/
|
||||
// server play control
|
||||
#define SRS_BW_CHECK_START_PLAY "onSrsBandCheckStartPlayBytes"
|
||||
#define SRS_BW_CHECK_STARTING_PLAY "onSrsBandCheckStartingPlayBytes"
|
||||
#define SRS_BW_CHECK_STOP_PLAY "onSrsBandCheckStopPlayBytes"
|
||||
#define SRS_BW_CHECK_STOPPED_PLAY "onSrsBandCheckStoppedPlayBytes"
|
||||
|
||||
// server publish control
|
||||
#define SRS_BW_CHECK_START_PUBLISH "onSrsBandCheckStartPublishBytes"
|
||||
#define SRS_BW_CHECK_STARTING_PUBLISH "onSrsBandCheckStartingPublishBytes"
|
||||
#define SRS_BW_CHECK_STOP_PUBLISH "onSrsBandCheckStopPublishBytes"
|
||||
#define SRS_BW_CHECK_STOPPED_PUBLISH "onSrsBandCheckStoppedPublishBytes"
|
||||
|
||||
// EOF control.
|
||||
#define SRS_BW_CHECK_FINISHED "onSrsBandCheckFinished"
|
||||
// for flash, it will sendout a final call,
|
||||
// used to confirm got the report.
|
||||
// actually, client send out this packet and close the connection,
|
||||
// so server may cannot got this packet, ignore is ok.
|
||||
#define SRS_BW_CHECK_FLASH_FINAL "finalClientPacket"
|
||||
|
||||
// client only
|
||||
#define SRS_BW_CHECK_PLAYING "onSrsBandCheckPlaying"
|
||||
#define SRS_BW_CHECK_PUBLISHING "onSrsBandCheckPublishing"
|
||||
|
||||
/****************************************************************************
|
||||
*****************************************************************************
|
||||
****************************************************************************/
|
||||
|
@ -284,7 +315,7 @@ SrsProtocol::~SrsProtocol()
|
|||
srs_freep(skt);
|
||||
}
|
||||
|
||||
std::string SrsProtocol::get_request_name(double transcationId)
|
||||
string SrsProtocol::get_request_name(double transcationId)
|
||||
{
|
||||
if (requests.find(transcationId) == requests.end()) {
|
||||
return "";
|
||||
|
@ -1324,19 +1355,21 @@ int SrsCommonMessage::decode_packet(SrsProtocol* protocol)
|
|||
srs_info("decode the AMF0/AMF3 data(onMetaData message).");
|
||||
packet = new SrsOnMetaDataPacket();
|
||||
return packet->decode(stream);
|
||||
} else if( command == SRS_BW_CHECK_FINISHED
|
||||
|| command == SRS_BW_CHECK_PLAYING
|
||||
|| command == SRS_BW_CHECK_PUBLISHING
|
||||
|| command == SRS_BW_CHECK_STARTING_PLAY
|
||||
|| command == SRS_BW_CHECK_STARTING_PUBLISH
|
||||
|| command == SRS_BW_CHECK_START_PLAY
|
||||
|| command == SRS_BW_CHECK_START_PUBLISH
|
||||
|| command == SRS_BW_CHECK_STOPPED_PLAY
|
||||
|| command == SRS_BW_CHECK_STOP_PLAY
|
||||
|| command == SRS_BW_CHECK_STOP_PUBLISH)
|
||||
} else if(command == SRS_BW_CHECK_FINISHED
|
||||
|| command == SRS_BW_CHECK_PLAYING
|
||||
|| command == SRS_BW_CHECK_PUBLISHING
|
||||
|| command == SRS_BW_CHECK_STARTING_PLAY
|
||||
|| command == SRS_BW_CHECK_STARTING_PUBLISH
|
||||
|| command == SRS_BW_CHECK_START_PLAY
|
||||
|| command == SRS_BW_CHECK_START_PUBLISH
|
||||
|| command == SRS_BW_CHECK_STOPPED_PLAY
|
||||
|| command == SRS_BW_CHECK_STOP_PLAY
|
||||
|| command == SRS_BW_CHECK_STOP_PUBLISH
|
||||
|| command == SRS_BW_CHECK_STOPPED_PUBLISH
|
||||
|| command == SRS_BW_CHECK_FLASH_FINAL)
|
||||
{
|
||||
srs_info("decode the AMF0/AMF3 band width check message.");
|
||||
packet = new SrsOnStatusCallPacket();
|
||||
packet = new SrsBandwidthPacket();
|
||||
return packet->decode(stream);
|
||||
}
|
||||
|
||||
|
@ -1390,7 +1423,7 @@ int SrsCommonMessage::get_perfer_cid()
|
|||
return packet->get_perfer_cid();
|
||||
}
|
||||
|
||||
void SrsCommonMessage::set_packet(SrsPacket* pkt, int stream_id)
|
||||
SrsCommonMessage* SrsCommonMessage::set_packet(SrsPacket* pkt, int stream_id)
|
||||
{
|
||||
srs_freep(packet);
|
||||
|
||||
|
@ -1399,6 +1432,8 @@ void SrsCommonMessage::set_packet(SrsPacket* pkt, int stream_id)
|
|||
header.message_type = packet->get_message_type();
|
||||
header.payload_length = packet->get_payload_length();
|
||||
header.stream_id = stream_id;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
int SrsCommonMessage::encode_packet()
|
||||
|
@ -1803,10 +1838,14 @@ int SrsConnectAppResPacket::get_message_type()
|
|||
int SrsConnectAppResPacket::get_size()
|
||||
{
|
||||
int size = srs_amf0_get_string_size(command_name) + srs_amf0_get_number_size();
|
||||
if(props->size() > 0)
|
||||
|
||||
if (props->size() > 0) {
|
||||
size += srs_amf0_get_object_size(props);
|
||||
if(info->size() > 0)
|
||||
}
|
||||
|
||||
if (info->size() > 0) {
|
||||
size += srs_amf0_get_object_size(info);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
@ -1827,7 +1866,7 @@ int SrsConnectAppResPacket::encode_packet(SrsStream* stream)
|
|||
}
|
||||
srs_verbose("encode transaction_id success.");
|
||||
|
||||
if(props->size() > 0){
|
||||
if (props->size() > 0) {
|
||||
if ((ret = srs_amf0_write_object(stream, props)) != ERROR_SUCCESS) {
|
||||
srs_error("encode props failed. ret=%d", ret);
|
||||
return ret;
|
||||
|
@ -1836,7 +1875,7 @@ int SrsConnectAppResPacket::encode_packet(SrsStream* stream)
|
|||
|
||||
srs_verbose("encode props success.");
|
||||
|
||||
if(info->size() > 0){
|
||||
if (info->size() > 0) {
|
||||
if ((ret = srs_amf0_write_object(stream, info)) != ERROR_SUCCESS) {
|
||||
srs_error("encode info failed. ret=%d", ret);
|
||||
return ret;
|
||||
|
@ -2651,6 +2690,163 @@ int SrsOnStatusCallPacket::encode_packet(SrsStream* stream)
|
|||
return ret;
|
||||
}
|
||||
|
||||
SrsBandwidthPacket::SrsBandwidthPacket()
|
||||
{
|
||||
command_name = RTMP_AMF0_COMMAND_ON_STATUS;
|
||||
transaction_id = 0;
|
||||
args = new SrsAmf0Null();
|
||||
data = new SrsAmf0Object();
|
||||
}
|
||||
|
||||
SrsBandwidthPacket::~SrsBandwidthPacket()
|
||||
{
|
||||
srs_freep(args);
|
||||
srs_freep(data);
|
||||
}
|
||||
|
||||
int SrsBandwidthPacket::get_perfer_cid()
|
||||
{
|
||||
return RTMP_CID_OverStream;
|
||||
}
|
||||
|
||||
int SrsBandwidthPacket::get_message_type()
|
||||
{
|
||||
return RTMP_MSG_AMF0CommandMessage;
|
||||
}
|
||||
|
||||
int SrsBandwidthPacket::get_size()
|
||||
{
|
||||
return srs_amf0_get_string_size(command_name) + srs_amf0_get_number_size()
|
||||
+ srs_amf0_get_null_size() + srs_amf0_get_object_size(data);
|
||||
}
|
||||
|
||||
int SrsBandwidthPacket::encode_packet(SrsStream* stream)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = srs_amf0_write_string(stream, command_name)) != ERROR_SUCCESS) {
|
||||
srs_error("encode command_name failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("encode command_name success.");
|
||||
|
||||
if ((ret = srs_amf0_write_number(stream, transaction_id)) != ERROR_SUCCESS) {
|
||||
srs_error("encode transaction_id failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("encode transaction_id success.");
|
||||
|
||||
if ((ret = srs_amf0_write_null(stream)) != ERROR_SUCCESS) {
|
||||
srs_error("encode args failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("encode args success.");;
|
||||
|
||||
if ((ret = srs_amf0_write_object(stream, data)) != ERROR_SUCCESS) {
|
||||
srs_error("encode data failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
srs_verbose("encode data success.");
|
||||
|
||||
srs_info("encode onStatus(Call) packet success.");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsBandwidthPacket::decode(SrsStream *stream)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if ((ret = srs_amf0_read_string(stream, command_name)) != ERROR_SUCCESS) {
|
||||
srs_error("amf0 decode play command_name failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = srs_amf0_read_number(stream, transaction_id)) != ERROR_SUCCESS) {
|
||||
srs_error("amf0 decode play transaction_id failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = srs_amf0_read_null(stream)) != ERROR_SUCCESS) {
|
||||
srs_error("amf0 decode play command_object failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// @remark, for bandwidth test, ignore the data field.
|
||||
|
||||
srs_info("decode SrsBandwidthPacket success.");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SrsBandwidthPacket::is_starting_play()
|
||||
{
|
||||
return command_name == SRS_BW_CHECK_STARTING_PLAY;
|
||||
}
|
||||
|
||||
bool SrsBandwidthPacket::is_stopped_play()
|
||||
{
|
||||
return command_name == SRS_BW_CHECK_STOPPED_PLAY;
|
||||
}
|
||||
|
||||
bool SrsBandwidthPacket::is_starting_publish()
|
||||
{
|
||||
return command_name == SRS_BW_CHECK_STARTING_PUBLISH;
|
||||
}
|
||||
|
||||
bool SrsBandwidthPacket::is_stopped_publish()
|
||||
{
|
||||
return command_name == SRS_BW_CHECK_STOPPED_PUBLISH;
|
||||
}
|
||||
|
||||
bool SrsBandwidthPacket::is_flash_final()
|
||||
{
|
||||
return command_name == SRS_BW_CHECK_FLASH_FINAL;
|
||||
}
|
||||
|
||||
SrsBandwidthPacket* SrsBandwidthPacket::create_finish()
|
||||
{
|
||||
SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
|
||||
return pkt->set_command(SRS_BW_CHECK_FINISHED);
|
||||
}
|
||||
|
||||
SrsBandwidthPacket* SrsBandwidthPacket::create_start_play()
|
||||
{
|
||||
SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
|
||||
return pkt->set_command(SRS_BW_CHECK_START_PLAY);
|
||||
}
|
||||
|
||||
SrsBandwidthPacket* SrsBandwidthPacket::create_playing()
|
||||
{
|
||||
SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
|
||||
return pkt->set_command(SRS_BW_CHECK_STARTING_PLAY);
|
||||
}
|
||||
|
||||
SrsBandwidthPacket* SrsBandwidthPacket::create_stop_play()
|
||||
{
|
||||
SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
|
||||
return pkt->set_command(SRS_BW_CHECK_STOP_PLAY);
|
||||
}
|
||||
|
||||
SrsBandwidthPacket* SrsBandwidthPacket::create_start_publish()
|
||||
{
|
||||
SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
|
||||
return pkt->set_command(SRS_BW_CHECK_START_PUBLISH);
|
||||
}
|
||||
|
||||
SrsBandwidthPacket* SrsBandwidthPacket::create_stop_publish()
|
||||
{
|
||||
SrsBandwidthPacket* pkt = new SrsBandwidthPacket();
|
||||
return pkt->set_command(SRS_BW_CHECK_STOP_PUBLISH);
|
||||
}
|
||||
|
||||
SrsBandwidthPacket* SrsBandwidthPacket::set_command(string command)
|
||||
{
|
||||
command_name = command;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
SrsOnStatusDataPacket::SrsOnStatusDataPacket()
|
||||
{
|
||||
command_name = RTMP_AMF0_COMMAND_ON_STATUS;
|
||||
|
|
|
@ -331,6 +331,7 @@ class SrsCommonMessage : public ISrsMessage
|
|||
{
|
||||
private:
|
||||
typedef ISrsMessage super;
|
||||
disable_default_copy(SrsCommonMessage);
|
||||
// decoded message payload.
|
||||
private:
|
||||
SrsStream* stream;
|
||||
|
@ -366,9 +367,10 @@ public:
|
|||
* set the encoded packet to encode_packet() to payload.
|
||||
* @stream_id, the id of stream which is created by createStream.
|
||||
* @remark, user never free the pkt, the message will auto free it.
|
||||
* @return message itself.
|
||||
*/
|
||||
// TODO: refine the send methods.
|
||||
virtual void set_packet(SrsPacket* pkt, int stream_id);
|
||||
virtual SrsCommonMessage* set_packet(SrsPacket* pkt, int stream_id);
|
||||
/**
|
||||
* encode the packet to message payload bytes.
|
||||
* @remark there exists empty packet, so maybe the payload is NULL.
|
||||
|
@ -856,6 +858,55 @@ protected:
|
|||
virtual int encode_packet(SrsStream* stream);
|
||||
};
|
||||
|
||||
/**
|
||||
* the special packet for the bandwidth test.
|
||||
* actually, it's a SrsOnStatusCallPacket, but
|
||||
* 1. encode with data field, to send data to client.
|
||||
* 2. decode ignore the data field, donot care.
|
||||
*/
|
||||
class SrsBandwidthPacket : public SrsPacket
|
||||
{
|
||||
private:
|
||||
typedef SrsPacket super;
|
||||
disable_default_copy(SrsBandwidthPacket);
|
||||
protected:
|
||||
virtual const char* get_class_name()
|
||||
{
|
||||
return CLASS_NAME_STRING(SrsBandwidthPacket);
|
||||
}
|
||||
public:
|
||||
std::string command_name;
|
||||
double transaction_id;
|
||||
SrsAmf0Null* args;
|
||||
SrsAmf0Object* data;
|
||||
public:
|
||||
SrsBandwidthPacket();
|
||||
virtual ~SrsBandwidthPacket();
|
||||
public:
|
||||
virtual int get_perfer_cid();
|
||||
public:
|
||||
virtual int get_message_type();
|
||||
protected:
|
||||
virtual int get_size();
|
||||
virtual int encode_packet(SrsStream* stream);
|
||||
public:
|
||||
virtual int decode(SrsStream* stream);
|
||||
public:
|
||||
virtual bool is_starting_play();
|
||||
virtual bool is_stopped_play();
|
||||
virtual bool is_starting_publish();
|
||||
virtual bool is_stopped_publish();
|
||||
virtual bool is_flash_final();
|
||||
static SrsBandwidthPacket* create_finish();
|
||||
static SrsBandwidthPacket* create_start_play();
|
||||
static SrsBandwidthPacket* create_playing();
|
||||
static SrsBandwidthPacket* create_stop_play();
|
||||
static SrsBandwidthPacket* create_start_publish();
|
||||
static SrsBandwidthPacket* create_stop_publish();
|
||||
private:
|
||||
virtual SrsBandwidthPacket* set_command(std::string command);
|
||||
};
|
||||
|
||||
/**
|
||||
* onStatus data, AMF0 Data
|
||||
* @remark, user must set the stream_id by SrsMessage.set_packet().
|
||||
|
|
|
@ -594,7 +594,7 @@ int SrsRtmp::set_peer_bandwidth(int bandwidth, int type)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int SrsRtmp::response_connect_app(SrsRequest *req, const char *ip)
|
||||
int SrsRtmp::response_connect_app(SrsRequest *req, const char* server_ip)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
|
@ -621,13 +621,12 @@ int SrsRtmp::response_connect_app(SrsRequest *req, const char *ip)
|
|||
data->set("srs_site", new SrsAmf0String(RTMP_SIG_SRS_WEB));
|
||||
data->set("srs_email", new SrsAmf0String(RTMP_SIG_SRS_EMAIL));
|
||||
data->set("srs_copyright", new SrsAmf0String(RTMP_SIG_SRS_COPYRIGHT));
|
||||
|
||||
if (ip) {
|
||||
data->set("srs_server_ip", new SrsAmf0String(ip));
|
||||
data->set("srs_contributor", new SrsAmf0String(RTMP_SIG_SRS_CONTRIBUTOR));
|
||||
|
||||
if (server_ip) {
|
||||
data->set("srs_server_ip", new SrsAmf0String(server_ip));
|
||||
}
|
||||
|
||||
data->set("srs_contributor", new SrsAmf0String(RTMP_SIG_SRS_CONTRIBUTOR));
|
||||
|
||||
|
||||
msg->set_packet(pkt, 0);
|
||||
|
||||
if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
|
||||
|
@ -639,27 +638,25 @@ int SrsRtmp::response_connect_app(SrsRequest *req, const char *ip)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int SrsRtmp::response_connect_reject(SrsRequest *req, const std::string &description)
|
||||
void SrsRtmp::response_connect_reject(SrsRequest *req, const char* desc)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsCommonMessage* msg = new SrsCommonMessage();
|
||||
SrsConnectAppResPacket* pkt = new SrsConnectAppResPacket();
|
||||
pkt->command_name = "_error";
|
||||
pkt->props->set(StatusLevel, new SrsAmf0String(StatusLevelError));
|
||||
pkt->props->set(StatusCode, new SrsAmf0String(StatusCodeConnectRejected));
|
||||
pkt->props->set(StatusDescription, new SrsAmf0String(description.c_str()));
|
||||
pkt->props->set(StatusDescription, new SrsAmf0String(desc));
|
||||
//pkt->props->set("objectEncoding", new SrsAmf0Number(req->objectEncoding));
|
||||
|
||||
msg->set_packet(pkt, 0);
|
||||
|
||||
SrsCommonMessage* msg = (new SrsCommonMessage())->set_packet(pkt, 0);
|
||||
if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
|
||||
srs_error("send connect app response rejected message failed. ret=%d", ret);
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
srs_info("send connect app response rejected message success.");
|
||||
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
|
||||
int SrsRtmp::on_bw_done()
|
||||
|
|
|
@ -169,8 +169,16 @@ public:
|
|||
* using the Limit type field.
|
||||
*/
|
||||
virtual int set_peer_bandwidth(int bandwidth, int type);
|
||||
<<<<<<< HEAD
|
||||
virtual int response_connect_app(SrsRequest* req, const char *ip = 0);
|
||||
virtual int response_connect_reject(SrsRequest* req, const std::string& description);
|
||||
=======
|
||||
/**
|
||||
* @param server_ip the ip of server.
|
||||
*/
|
||||
virtual int response_connect_app(SrsRequest* req, const char* server_ip = NULL);
|
||||
virtual void response_connect_reject(SrsRequest* req, const char* desc);
|
||||
>>>>>>> upstream/master
|
||||
virtual int on_bw_done();
|
||||
/**
|
||||
* recv some message to identify the client.
|
||||
|
|
|
@ -10,6 +10,8 @@ file
|
|||
..\core\srs_core_amf0.cpp,
|
||||
..\core\srs_core_autofree.hpp,
|
||||
..\core\srs_core_autofree.cpp,
|
||||
..\core\srs_core_bandwidth.hpp,
|
||||
..\core\srs_core_bandwidth.cpp,
|
||||
..\core\srs_core_buffer.hpp,
|
||||
..\core\srs_core_buffer.cpp,
|
||||
..\core\srs_core_client.hpp,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue