Skip to content

Commit 48d55ce

Browse files
author
Haibo Chen
committed
Enhance HLS: support http callback on_play/stop, support statistic
1 parent 7ab3e4a commit 48d55ce

File tree

7 files changed

+262
-5
lines changed

7 files changed

+262
-5
lines changed

trunk/src/app/srs_app_http_api.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,8 +836,13 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa
836836
return srs_api_response_code(w, r, ERROR_RTMP_CLIENT_NOT_FOUND);
837837
}
838838

839-
client->conn->expire();
840-
srs_warn("kickoff client id=%s ok", client_id.c_str());
839+
if (client->conn) {
840+
client->conn->expire();
841+
srs_warn("kickoff client id=%s ok", client_id.c_str());
842+
} else {
843+
srs_error("kickoff client id=%s error", client_id.c_str());
844+
return srs_api_response_code(w, r, SRS_CONSTS_HTTP_BadRequest);
845+
}
841846
} else {
842847
return srs_go_http_error(w, SRS_CONSTS_HTTP_MethodNotAllowed);
843848
}

trunk/src/app/srs_app_http_static.cpp

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,28 @@ using namespace std;
3333
#include <srs_app_pithy_print.hpp>
3434
#include <srs_app_source.hpp>
3535
#include <srs_app_server.hpp>
36+
#include <srs_service_utility.hpp>
37+
#include <srs_app_http_hooks.hpp>
38+
#include <srs_app_statistic.hpp>
39+
40+
#define SRS_SECRET_IN_HLS "srs_secret"
3641

3742
SrsVodStream::SrsVodStream(string root_dir) : SrsHttpFileServer(root_dir)
3843
{
44+
_srs_hybrid->timer5s()->subscribe(this);
3945
}
4046

4147
SrsVodStream::~SrsVodStream()
4248
{
49+
_srs_hybrid->timer5s()->unsubscribe(this);
50+
std::map<std::string, SrsRequest*>::iterator it;
51+
for (it = map_secret_req_.begin(); it != map_secret_req_.end(); ++it) {
52+
srs_freep(it->second);
53+
}
54+
map_secret_req_.clear();
55+
map_secret_validity_.clear();
4356
}
44-
57+
4558
srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int offset)
4659
{
4760
srs_error_t err = srs_success;
@@ -171,6 +184,162 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe
171184
}
172185

173186
return err;
187+
}
188+
189+
srs_error_t SrsVodStream::serve_m3u8_secret(ISrsHttpResponseWriter * w, ISrsHttpMessage * r, std::string fullpath)
190+
{
191+
srs_error_t err = srs_success;
192+
193+
SrsHttpMessage* hr = dynamic_cast<SrsHttpMessage*>(r);
194+
srs_assert(hr);
195+
196+
SrsRequest* req = hr->to_request(hr->host())->as_http();
197+
SrsAutoFree(SrsRequest, req);
198+
199+
string secret = r->query_get(SRS_SECRET_IN_HLS);
200+
if (!secret.empty() && secret_is_exist(secret)) {
201+
alive(secret);
202+
return SrsHttpFileServer::serve_m3u8_secret(w, r, fullpath);
203+
}
204+
205+
if ((err = http_hooks_on_play(req)) != srs_success) {
206+
return srs_error_wrap(err, "HLS: http_hooks_on_play");
207+
}
208+
209+
if (secret.empty()) {
210+
// make sure unique
211+
do {
212+
secret = srs_random_str(8);
213+
} while (secret_is_exist(secret));
214+
}
215+
216+
std::string res = "#EXTM3U\r";
217+
res += "#EXT-X-STREAM-INF:BANDWIDTH=1,AVERAGE-BANDWIDTH=1\r";
218+
res += hr->path() + "?" + SRS_SECRET_IN_HLS + "=" + secret;
219+
220+
int length = res.length();
221+
222+
w->header()->set_content_length(length);
223+
w->header()->set_content_type("application/vnd.apple.mpegurl");
224+
w->write_header(SRS_CONSTS_HTTP_OK);
225+
226+
if ((err = w->write((char*)res.c_str(), length)) != srs_success) {
227+
return srs_error_wrap(err, "write bytes=%d", length);
228+
}
229+
230+
if ((err = w->final_request()) != srs_success) {
231+
return srs_error_wrap(err, "final request");
232+
}
233+
234+
// update the statistic when source disconveried.
235+
SrsStatistic* stat = SrsStatistic::instance();
236+
if ((err = stat->on_client(secret, req, NULL, SrsRtmpConnPlay)) != srs_success) {
237+
return srs_error_wrap(err, "stat on client");
238+
}
239+
240+
// save req for on_disconnect when timeout
241+
map_secret_req_.insert(make_pair(secret, req->copy()));
242+
alive(secret);
243+
244+
return err;
245+
}
246+
247+
bool SrsVodStream::secret_is_exist(std::string secret)
248+
{
249+
return (map_secret_validity_.find(secret) != map_secret_validity_.end());
250+
}
251+
252+
void SrsVodStream::alive(std::string secret)
253+
{
254+
map_secret_validity_[secret] = srs_get_system_time();
255+
}
256+
257+
srs_error_t SrsVodStream::http_hooks_on_play(SrsRequest* req)
258+
{
259+
srs_error_t err = srs_success;
260+
261+
if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
262+
return err;
263+
}
264+
265+
// the http hooks will cause context switch,
266+
// so we must copy all hooks for the on_connect may freed.
267+
// @see https://github.com/ossrs/srs/issues/475
268+
vector<string> hooks;
269+
270+
if (true) {
271+
SrsConfDirective* conf = _srs_config->get_vhost_on_play(req->vhost);
272+
273+
if (!conf) {
274+
return err;
275+
}
276+
277+
hooks = conf->args;
278+
}
279+
280+
for (int i = 0; i < (int)hooks.size(); i++) {
281+
std::string url = hooks.at(i);
282+
if ((err = SrsHttpHooks::on_play(url, req)) != srs_success) {
283+
return srs_error_wrap(err, "http on_play %s", url.c_str());
284+
}
285+
}
286+
287+
return err;
288+
}
289+
290+
void SrsVodStream::http_hooks_on_stop(SrsRequest* req)
291+
{
292+
if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
293+
return;
294+
}
295+
296+
// the http hooks will cause context switch,
297+
// so we must copy all hooks for the on_connect may freed.
298+
// @see https://github.com/ossrs/srs/issues/475
299+
vector<string> hooks;
300+
301+
if (true) {
302+
SrsConfDirective* conf = _srs_config->get_vhost_on_stop(req->vhost);
303+
304+
if (!conf) {
305+
srs_info("ignore the empty http callback: on_stop");
306+
return;
307+
}
308+
309+
hooks = conf->args;
310+
}
311+
312+
for (int i = 0; i < (int)hooks.size(); i++) {
313+
std::string url = hooks.at(i);
314+
SrsHttpHooks::on_stop(url, req);
315+
}
316+
317+
return;
318+
}
319+
320+
srs_error_t SrsVodStream::on_timer(srs_utime_t interval)
321+
{
322+
srs_error_t err = srs_success;
323+
324+
std::map<std::string, srs_utime_t>::iterator it;
325+
for (it = map_secret_validity_.begin(); it != map_secret_validity_.end(); ++it) {
326+
string secret = it->first;
327+
SrsRequest* req = map_secret_req_[secret];
328+
srs_utime_t hls_window = _srs_config->get_hls_window(req->vhost);
329+
if (it->second + (2 * hls_window) < srs_get_system_time()) {
330+
http_hooks_on_stop(req);
331+
srs_freep(req);
332+
map_secret_req_.erase(secret);
333+
334+
SrsStatistic* stat = SrsStatistic::instance();
335+
stat->on_disconnect(secret);
336+
map_secret_validity_.erase(it);
337+
338+
break;
339+
}
340+
}
341+
342+
return err;
174343
}
175344

176345
SrsHttpStaticServer::SrsHttpStaticServer(SrsServer* svr)

trunk/src/app/srs_app_http_static.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,27 @@
1515
// For example, http://server/file.flv?start=10240
1616
// server will write flv header and sequence header,
1717
// then seek(10240) and response flv tag data.
18-
class SrsVodStream : public SrsHttpFileServer
18+
class SrsVodStream : public SrsHttpFileServer, public ISrsFastTimer
1919
{
20+
private:
21+
// The period of validity of the secret
22+
std::map<std::string, srs_utime_t> map_secret_validity_;
23+
std::map<std::string, SrsRequest*> map_secret_req_;
2024
public:
2125
SrsVodStream(std::string root_dir);
2226
virtual ~SrsVodStream();
2327
protected:
2428
virtual srs_error_t serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int offset);
2529
virtual srs_error_t serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int start, int end);
30+
virtual srs_error_t serve_m3u8_secret(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
31+
private:
32+
virtual bool secret_is_exist(std::string secret);
33+
virtual void alive(std::string secret);
34+
virtual srs_error_t http_hooks_on_play(SrsRequest* req);
35+
virtual void http_hooks_on_stop(SrsRequest* req);
36+
// interface ISrsFastTimer
37+
private:
38+
srs_error_t on_timer(srs_utime_t interval);
2639
};
2740

2841
// The http static server instance,

trunk/src/protocol/srs_http_stack.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes
364364

365365
string upath = r->path();
366366
string fullpath = srs_http_fs_fullpath(dir, entry->pattern, upath);
367-
367+
368368
// stat current dir, if exists, return error.
369369
if (!_srs_path_exists(fullpath)) {
370370
srs_warn("http miss file=%s, pattern=%s, upath=%s",
@@ -380,6 +380,8 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes
380380
return serve_flv_file(w, r, fullpath);
381381
} else if (srs_string_ends_with(fullpath, ".mp4")) {
382382
return serve_mp4_file(w, r, fullpath);
383+
} else if (srs_string_ends_with(upath, ".m3u8")) {
384+
return serve_m3u8_file(w, r, fullpath);
383385
}
384386

385387
// serve common static file.
@@ -522,6 +524,11 @@ srs_error_t SrsHttpFileServer::serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHtt
522524
}
523525

524526
return serve_mp4_stream(w, r, fullpath, start, end);
527+
}
528+
529+
srs_error_t SrsHttpFileServer::serve_m3u8_file(ISrsHttpResponseWriter * w, ISrsHttpMessage * r, std::string fullpath)
530+
{
531+
return serve_m3u8_secret(w, r, fullpath);
525532
}
526533

527534
srs_error_t SrsHttpFileServer::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, int offset)
@@ -536,6 +543,13 @@ srs_error_t SrsHttpFileServer::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsH
536543
// @remark For common http file server, we don't support stream request, please use SrsVodStream instead.
537544
// TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests
538545
return serve_file(w, r, fullpath);
546+
}
547+
548+
srs_error_t SrsHttpFileServer::serve_m3u8_secret(ISrsHttpResponseWriter * w, ISrsHttpMessage * r, std::string fullpath)
549+
{
550+
// @remark For common http file server, we don't support stream request, please use SrsVodStream instead.
551+
// TODO: FIXME: Support range in header https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests
552+
return serve_file(w, r, fullpath);
539553
}
540554

541555
srs_error_t SrsHttpFileServer::copy(ISrsHttpResponseWriter* w, SrsFileReader* fs, ISrsHttpMessage* r, int size)

trunk/src/protocol/srs_http_stack.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ class SrsHttpFileServer : public ISrsHttpHandler
285285
virtual srs_error_t serve_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
286286
virtual srs_error_t serve_flv_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
287287
virtual srs_error_t serve_mp4_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
288+
virtual srs_error_t serve_m3u8_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
288289
protected:
289290
// When access flv file with x.flv?start=xxx
290291
virtual srs_error_t serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int offset);
@@ -293,6 +294,16 @@ class SrsHttpFileServer : public ISrsHttpHandler
293294
// @param end the end offset in bytes. -1 to end of file.
294295
// @remark response data in [start, end].
295296
virtual srs_error_t serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, int start, int end);
297+
// For HLS protocol.
298+
// When the request url, like as "http://127.0.0.1:8080/live/livestream.m3u8",
299+
// returns the response like as "http://127.0.0.1:8080/live/livestream.m3u8?srs_secret=12345678" .
300+
// SRS use "srs_secret" to keep track of subsequent requests that is short-connection.
301+
// Remark 1:
302+
// Fill the parameter "srs_secret" by yourself in the first request is allowed, SRS will use it.
303+
// And MUST make sure it is unique.
304+
// Remark 2:
305+
// If use two same "srs_secret" in different requests, SRS cannot detect so that they will be treated as one.
306+
virtual srs_error_t serve_m3u8_secret(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
296307
protected:
297308
// Copy the fs to response writer in size bytes.
298309
virtual srs_error_t copy(ISrsHttpResponseWriter* w, SrsFileReader* fs, ISrsHttpMessage* r, int size);

trunk/src/utest/srs_utest_http.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ string mock_http_response2(int status, string content)
143143
return ss.str();
144144
}
145145

146+
bool is_string_contain(string substr, string str)
147+
{
148+
return (string::npos != str.find(substr));
149+
}
150+
146151
class MockFileReaderFactory : public ISrsFileReaderFactory
147152
{
148153
public:
@@ -1183,6 +1188,42 @@ VOID TEST(ProtocolHTTPTest, VodStreamHandlers)
11831188
HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
11841189
__MOCK_HTTP_EXPECT_STREQ(200, "Hello, world!", w);
11851190
}
1191+
1192+
// should return "srs_secret"
1193+
if (true) {
1194+
SrsHttpMuxEntry e;
1195+
e.pattern = "/";
1196+
1197+
SrsVodStream h("/tmp");
1198+
h.set_fs_factory(new MockFileReaderFactory("Hello, world!"));
1199+
h.set_path_check(_mock_srs_path_always_exists);
1200+
h.entry = &e;
1201+
1202+
MockResponseWriter w;
1203+
SrsHttpMessage r(NULL, NULL);
1204+
HELPER_ASSERT_SUCCESS(r.set_url("/index.m3u8", false));
1205+
1206+
HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
1207+
__MOCK_HTTP_EXPECT_STRCT(200, "index.m3u8?srs_secret=", w);
1208+
}
1209+
1210+
// should return "srs_secret"
1211+
if (true) {
1212+
SrsHttpMuxEntry e;
1213+
e.pattern = "/";
1214+
1215+
SrsVodStream h("/tmp");
1216+
h.set_fs_factory(new MockFileReaderFactory("Hello, world!"));
1217+
h.set_path_check(_mock_srs_path_always_exists);
1218+
h.entry = &e;
1219+
1220+
MockResponseWriter w;
1221+
SrsHttpMessage r(NULL, NULL);
1222+
HELPER_ASSERT_SUCCESS(r.set_url("/index.m3u8?srs_secret=123456", false));
1223+
1224+
HELPER_ASSERT_SUCCESS(h.serve_http(&w, &r));
1225+
__MOCK_HTTP_EXPECT_STRCT(200, "index.m3u8?srs_secret=123456", w);
1226+
}
11861227
}
11871228

11881229
VOID TEST(ProtocolHTTPTest, BasicHandlers)

trunk/src/utest/srs_utest_http.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,16 @@ class MockResponseWriter : public ISrsHttpResponseWriter, public ISrsHttpHeaderF
3939

4040
string mock_http_response(int status, string content);
4141
string mock_http_response2(int status, string content);
42+
bool is_string_contain(string substr, string str);
4243

4344
#define __MOCK_HTTP_EXPECT_STREQ(status, text, w) \
4445
EXPECT_STREQ(mock_http_response(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str())
4546

4647
#define __MOCK_HTTP_EXPECT_STREQ2(status, text, w) \
4748
EXPECT_STREQ(mock_http_response2(status, text).c_str(), HELPER_BUFFER2STR(&w.io.out_buffer).c_str())
4849

50+
#define __MOCK_HTTP_EXPECT_STRCT(status, text, w) \
51+
EXPECT_PRED2(is_string_contain, text, HELPER_BUFFER2STR(&w.io.out_buffer).c_str())
52+
4953
#endif
5054

0 commit comments

Comments
 (0)