mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
HLS: Rebuild m3u8 to make ts with id, for stat.
This commit is contained in:
parent
bc569d91a0
commit
c1df280211
5 changed files with 161 additions and 76 deletions
|
@ -41,6 +41,16 @@ using namespace std;
|
||||||
|
|
||||||
#define SRS_CONTEXT_IN_HLS "hls_ctx"
|
#define SRS_CONTEXT_IN_HLS "hls_ctx"
|
||||||
|
|
||||||
|
SrsM3u8CtxInfo::SrsM3u8CtxInfo()
|
||||||
|
{
|
||||||
|
req = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SrsM3u8CtxInfo::~SrsM3u8CtxInfo()
|
||||||
|
{
|
||||||
|
srs_freep(req);
|
||||||
|
}
|
||||||
|
|
||||||
SrsHlsStream::SrsHlsStream()
|
SrsHlsStream::SrsHlsStream()
|
||||||
{
|
{
|
||||||
_srs_hybrid->timer5s()->subscribe(this);
|
_srs_hybrid->timer5s()->subscribe(this);
|
||||||
|
@ -50,42 +60,40 @@ SrsHlsStream::~SrsHlsStream()
|
||||||
{
|
{
|
||||||
_srs_hybrid->timer5s()->unsubscribe(this);
|
_srs_hybrid->timer5s()->unsubscribe(this);
|
||||||
|
|
||||||
std::map<std::string, SrsM3u8CtxInfo>::iterator it;
|
std::map<std::string, SrsM3u8CtxInfo*>::iterator it;
|
||||||
for (it = map_ctx_info_.begin(); it != map_ctx_info_.end(); ++it) {
|
for (it = map_ctx_info_.begin(); it != map_ctx_info_.end(); ++it) {
|
||||||
srs_freep(it->second.req);
|
SrsM3u8CtxInfo* info = it->second;
|
||||||
|
srs_freep(info);
|
||||||
}
|
}
|
||||||
map_ctx_info_.clear();
|
map_ctx_info_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
srs_error_t SrsHlsStream::serve_m3u8_ctx(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, bool* served)
|
srs_error_t SrsHlsStream::serve_m3u8_ctx(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, string fullpath, SrsRequest* req)
|
||||||
{
|
{
|
||||||
srs_error_t err = srs_success;
|
srs_error_t err = srs_success;
|
||||||
|
|
||||||
SrsHttpMessage* hr = dynamic_cast<SrsHttpMessage*>(r);
|
string ctx = r->query_get(SRS_CONTEXT_IN_HLS);
|
||||||
srs_assert(hr);
|
|
||||||
|
|
||||||
SrsRequest* req = hr->to_request(hr->host())->as_http();
|
// Always make the ctx alive now.
|
||||||
SrsAutoFree(SrsRequest, req);
|
alive(ctx, req);
|
||||||
|
|
||||||
// discovery vhost, resolve the vhost from config
|
// Already exists context, response with rebuilt m3u8 content.
|
||||||
SrsConfDirective* parsed_vhost = _srs_config->get_vhost(req->vhost);
|
|
||||||
if (parsed_vhost) {
|
|
||||||
req->vhost = parsed_vhost->arg0();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If HLS stream is disabled, use SrsHttpFileServer to serve HLS, which is normal file server.
|
|
||||||
if (!_srs_config->get_hls_ctx_enabled(req->vhost)) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve as HLS stream, create a HLS session to serve it.
|
|
||||||
string ctx = hr->query_get(SRS_CONTEXT_IN_HLS);
|
|
||||||
if (!ctx.empty() && ctx_is_exist(ctx)) {
|
if (!ctx.empty() && ctx_is_exist(ctx)) {
|
||||||
alive(ctx, NULL);
|
return serve_exists_session(w, r, fullpath);
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a m3u8 in memory, contains the session id(ctx).
|
// Create a m3u8 in memory, contains the session id(ctx).
|
||||||
|
return serve_new_session(w, r, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
srs_error_t SrsHlsStream::serve_new_session(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsRequest* req)
|
||||||
|
{
|
||||||
|
srs_error_t err = srs_success;
|
||||||
|
|
||||||
|
SrsHttpMessage *hr = dynamic_cast<SrsHttpMessage *>(r);
|
||||||
|
srs_assert(hr);
|
||||||
|
|
||||||
|
string ctx;
|
||||||
if (ctx.empty()) {
|
if (ctx.empty()) {
|
||||||
// make sure unique
|
// make sure unique
|
||||||
do {
|
do {
|
||||||
|
@ -123,16 +131,58 @@ srs_error_t SrsHlsStream::serve_m3u8_ctx(ISrsHttpResponseWriter* w, ISrsHttpMess
|
||||||
return srs_error_wrap(err, "final request");
|
return srs_error_wrap(err, "final request");
|
||||||
}
|
}
|
||||||
|
|
||||||
alive(ctx, req->copy());
|
|
||||||
|
|
||||||
// update the statistic when source disconveried.
|
// update the statistic when source disconveried.
|
||||||
SrsStatistic* stat = SrsStatistic::instance();
|
SrsStatistic* stat = SrsStatistic::instance();
|
||||||
if ((err = stat->on_client(ctx, req, NULL, SrsHlsPlay)) != srs_success) {
|
if ((err = stat->on_client(ctx, req, NULL, SrsHlsPlay)) != srs_success) {
|
||||||
return srs_error_wrap(err, "stat on client");
|
return srs_error_wrap(err, "stat on client");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The request has been served by HLS streaming handler.
|
return err;
|
||||||
*served = true;
|
}
|
||||||
|
|
||||||
|
srs_error_t SrsHlsStream::serve_exists_session(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath)
|
||||||
|
{
|
||||||
|
srs_error_t err = srs_success;
|
||||||
|
|
||||||
|
// Read m3u8 content.
|
||||||
|
SrsFileReader fs;
|
||||||
|
if ((err = fs.open(fullpath)) != srs_success) {
|
||||||
|
return srs_error_wrap(err, "open %s", fullpath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string content;
|
||||||
|
if ((err = srs_ioutil_read_all(&fs, content)) != srs_success) {
|
||||||
|
return srs_error_wrap(err, "read %s", fullpath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the m3u8 content, make .ts with hls_ctx.
|
||||||
|
size_t pos_ts = content.find(".ts");
|
||||||
|
static string QUERY_PREFIX = string(".ts?") + string(SRS_CONTEXT_IN_HLS) + string("=");
|
||||||
|
|
||||||
|
if (pos_ts != string::npos) {
|
||||||
|
string ctx = r->query_get(SRS_CONTEXT_IN_HLS);
|
||||||
|
string query = QUERY_PREFIX + ctx;
|
||||||
|
|
||||||
|
size_t pos_query = content.find(".ts?");
|
||||||
|
if (pos_query != string::npos) {
|
||||||
|
query += "&";
|
||||||
|
content = srs_string_replace(content, ".ts?", query);
|
||||||
|
} else {
|
||||||
|
content = srs_string_replace(content, ".ts", query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response with rebuilt content.
|
||||||
|
w->header()->set_content_type("application/vnd.apple.mpegurl");
|
||||||
|
w->header()->set_content_length(content.length());
|
||||||
|
w->write_header(SRS_CONSTS_HTTP_OK);
|
||||||
|
if (!content.empty()) {
|
||||||
|
w->write((char*)content.data(), content.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((err = w->final_request()) != srs_success) {
|
||||||
|
return srs_error_wrap(err, "final request");
|
||||||
|
}
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -144,15 +194,20 @@ bool SrsHlsStream::ctx_is_exist(std::string ctx)
|
||||||
|
|
||||||
void SrsHlsStream::alive(std::string ctx, SrsRequest* req)
|
void SrsHlsStream::alive(std::string ctx, SrsRequest* req)
|
||||||
{
|
{
|
||||||
std::map<std::string, SrsM3u8CtxInfo>::iterator it;
|
std::map<std::string, SrsM3u8CtxInfo*>::iterator it = map_ctx_info_.find(ctx);
|
||||||
if ((it = map_ctx_info_.find(ctx)) != map_ctx_info_.end()) {
|
|
||||||
it->second.request_time = srs_get_system_time();
|
// Create new context.
|
||||||
} else {
|
if (it == map_ctx_info_.end()) {
|
||||||
SrsM3u8CtxInfo info;
|
SrsM3u8CtxInfo *info = new SrsM3u8CtxInfo();
|
||||||
info.req = req;
|
info->req = req->copy();
|
||||||
info.request_time = srs_get_system_time();
|
info->request_time = srs_get_system_time();
|
||||||
map_ctx_info_.insert(make_pair(ctx, info));
|
map_ctx_info_.insert(make_pair(ctx, info));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update alive time of context.
|
||||||
|
SrsM3u8CtxInfo* info = it->second;
|
||||||
|
info->request_time = srs_get_system_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
srs_error_t SrsHlsStream::http_hooks_on_play(SrsRequest* req)
|
srs_error_t SrsHlsStream::http_hooks_on_play(SrsRequest* req)
|
||||||
|
@ -222,21 +277,23 @@ srs_error_t SrsHlsStream::on_timer(srs_utime_t interval)
|
||||||
{
|
{
|
||||||
srs_error_t err = srs_success;
|
srs_error_t err = srs_success;
|
||||||
|
|
||||||
std::map<std::string, SrsM3u8CtxInfo>::iterator it;
|
std::map<std::string, SrsM3u8CtxInfo*>::iterator it;
|
||||||
for (it = map_ctx_info_.begin(); it != map_ctx_info_.end(); ++it) {
|
for (it = map_ctx_info_.begin(); it != map_ctx_info_.end(); ++it) {
|
||||||
string ctx = it->first;
|
string ctx = it->first;
|
||||||
SrsRequest* req = it->second.req;
|
SrsM3u8CtxInfo* info = it->second;
|
||||||
srs_utime_t hls_window = _srs_config->get_hls_window(req->vhost);
|
|
||||||
if (it->second.request_time + (2 * hls_window) < srs_get_system_time()) {
|
srs_utime_t hls_window = _srs_config->get_hls_window(info->req->vhost);
|
||||||
|
if (info->request_time + (2 * hls_window) < srs_get_system_time()) {
|
||||||
SrsContextRestore(_srs_context->get_id());
|
SrsContextRestore(_srs_context->get_id());
|
||||||
_srs_context->set_id(SrsContextId().set_value(ctx));
|
_srs_context->set_id(SrsContextId().set_value(ctx));
|
||||||
|
|
||||||
http_hooks_on_stop(req);
|
http_hooks_on_stop(info->req);
|
||||||
srs_freep(req);
|
|
||||||
|
|
||||||
SrsStatistic* stat = SrsStatistic::instance();
|
SrsStatistic* stat = SrsStatistic::instance();
|
||||||
stat->on_disconnect(ctx);
|
stat->on_disconnect(ctx);
|
||||||
|
|
||||||
map_ctx_info_.erase(it);
|
map_ctx_info_.erase(it);
|
||||||
|
srs_freep(info);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -388,19 +445,26 @@ srs_error_t SrsVodStream::serve_m3u8_ctx(ISrsHttpResponseWriter * w, ISrsHttpMes
|
||||||
{
|
{
|
||||||
srs_error_t err = srs_success;
|
srs_error_t err = srs_success;
|
||||||
|
|
||||||
|
SrsHttpMessage *hr = dynamic_cast<SrsHttpMessage *>(r);
|
||||||
|
srs_assert(hr);
|
||||||
|
|
||||||
|
SrsRequest *req = hr->to_request(hr->host())->as_http();
|
||||||
|
SrsAutoFree(SrsRequest, req);
|
||||||
|
|
||||||
|
// discovery vhost, resolve the vhost from config
|
||||||
|
SrsConfDirective *parsed_vhost = _srs_config->get_vhost(req->vhost);
|
||||||
|
if (parsed_vhost) {
|
||||||
|
req->vhost = parsed_vhost->arg0();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If HLS stream is disabled, use SrsHttpFileServer to serve HLS, which is normal file server.
|
||||||
|
if (!_srs_config->get_hls_ctx_enabled(req->vhost)) {
|
||||||
|
// Serve by default HLS handler.
|
||||||
|
return SrsHttpFileServer::serve_m3u8_ctx(w, r, fullpath);
|
||||||
|
}
|
||||||
|
|
||||||
// Try to serve by HLS streaming.
|
// Try to serve by HLS streaming.
|
||||||
bool served = false;
|
return hls_.serve_m3u8_ctx(w, r, fullpath, req);
|
||||||
if ((err = hls_.serve_m3u8_ctx(w, r, &served)) != srs_success) {
|
|
||||||
return srs_error_wrap(err, "hls stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done if already served.
|
|
||||||
if (served) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve by default HLS handler.
|
|
||||||
return SrsHttpFileServer::serve_m3u8_ctx(w, r, fullpath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsHttpStaticServer::SrsHttpStaticServer(SrsServer* svr)
|
SrsHttpStaticServer::SrsHttpStaticServer(SrsServer* svr)
|
||||||
|
|
|
@ -15,6 +15,8 @@ struct SrsM3u8CtxInfo
|
||||||
{
|
{
|
||||||
srs_utime_t request_time;
|
srs_utime_t request_time;
|
||||||
SrsRequest* req;
|
SrsRequest* req;
|
||||||
|
SrsM3u8CtxInfo();
|
||||||
|
virtual ~SrsM3u8CtxInfo();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Server HLS streaming.
|
// Server HLS streaming.
|
||||||
|
@ -22,17 +24,19 @@ class SrsHlsStream : public ISrsFastTimer
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// The period of validity of the ctx
|
// The period of validity of the ctx
|
||||||
std::map<std::string, SrsM3u8CtxInfo> map_ctx_info_;
|
std::map<std::string, SrsM3u8CtxInfo*> map_ctx_info_;
|
||||||
public:
|
public:
|
||||||
SrsHlsStream();
|
SrsHlsStream();
|
||||||
virtual ~SrsHlsStream();
|
virtual ~SrsHlsStream();
|
||||||
public:
|
public:
|
||||||
virtual srs_error_t serve_m3u8_ctx(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, bool* served);
|
virtual srs_error_t serve_m3u8_ctx(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath, SrsRequest* req);
|
||||||
private:
|
private:
|
||||||
virtual bool ctx_is_exist(std::string ctx);
|
srs_error_t serve_new_session(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsRequest *req);
|
||||||
virtual void alive(std::string ctx, SrsRequest* req);
|
srs_error_t serve_exists_session(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string fullpath);
|
||||||
virtual srs_error_t http_hooks_on_play(SrsRequest* req);
|
bool ctx_is_exist(std::string ctx);
|
||||||
virtual void http_hooks_on_stop(SrsRequest* req);
|
void alive(std::string ctx, SrsRequest* req);
|
||||||
|
srs_error_t http_hooks_on_play(SrsRequest* req);
|
||||||
|
void http_hooks_on_stop(SrsRequest* req);
|
||||||
// interface ISrsFastTimer
|
// interface ISrsFastTimer
|
||||||
private:
|
private:
|
||||||
srs_error_t on_timer(srs_utime_t interval);
|
srs_error_t on_timer(srs_utime_t interval);
|
||||||
|
|
|
@ -574,25 +574,7 @@ std::string SrsHttpMessage::parse_rest_id(string pattern)
|
||||||
|
|
||||||
srs_error_t SrsHttpMessage::body_read_all(string& body)
|
srs_error_t SrsHttpMessage::body_read_all(string& body)
|
||||||
{
|
{
|
||||||
srs_error_t err = srs_success;
|
return srs_ioutil_read_all(_body, body);
|
||||||
|
|
||||||
// cache to read.
|
|
||||||
char* buf = new char[SRS_HTTP_READ_CACHE_BYTES];
|
|
||||||
SrsAutoFreeA(char, buf);
|
|
||||||
|
|
||||||
// whatever, read util EOF.
|
|
||||||
while (!_body->eof()) {
|
|
||||||
ssize_t nb_read = 0;
|
|
||||||
if ((err = _body->read(buf, SRS_HTTP_READ_CACHE_BYTES, &nb_read)) != srs_success) {
|
|
||||||
return srs_error_wrap(err, "read body");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nb_read > 0) {
|
|
||||||
body.append(buf, nb_read);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ISrsHttpResponseReader* SrsHttpMessage::body_reader()
|
ISrsHttpResponseReader* SrsHttpMessage::body_reader()
|
||||||
|
|
|
@ -41,6 +41,7 @@ using namespace std;
|
||||||
#include <srs_kernel_log.hpp>
|
#include <srs_kernel_log.hpp>
|
||||||
#include <srs_kernel_utility.hpp>
|
#include <srs_kernel_utility.hpp>
|
||||||
#include <srs_protocol_http_stack.hpp>
|
#include <srs_protocol_http_stack.hpp>
|
||||||
|
#include <srs_core_autofree.hpp>
|
||||||
|
|
||||||
void srs_discovery_tc_url(string tcUrl, string& schema, string& host, string& vhost, string& app, string& stream, int& port, string& param)
|
void srs_discovery_tc_url(string tcUrl, string& schema, string& host, string& vhost, string& app, string& stream, int& port, string& param)
|
||||||
{
|
{
|
||||||
|
@ -900,3 +901,33 @@ string srs_get_system_hostname()
|
||||||
return _srs_system_hostname;
|
return _srs_system_hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srs_error_t srs_ioutil_read_all(ISrsReader* in, std::string& content)
|
||||||
|
{
|
||||||
|
srs_error_t err = srs_success;
|
||||||
|
|
||||||
|
// Cache to read, it might cause coroutine switch, so we use local cache here.
|
||||||
|
char* buf = new char[SRS_HTTP_READ_CACHE_BYTES];
|
||||||
|
SrsAutoFreeA(char, buf);
|
||||||
|
|
||||||
|
// Whatever, read util EOF.
|
||||||
|
while (true) {
|
||||||
|
ssize_t nb_read = 0;
|
||||||
|
if ((err = in->read(buf, SRS_HTTP_READ_CACHE_BYTES, &nb_read)) != srs_success) {
|
||||||
|
int code = srs_error_code(err);
|
||||||
|
if (code == ERROR_SYSTEM_FILE_EOF || code == ERROR_HTTP_RESPONSE_EOF || code == ERROR_HTTP_REQUEST_EOF
|
||||||
|
|| code == ERROR_HTTP_STREAM_EOF
|
||||||
|
) {
|
||||||
|
srs_freep(err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return srs_error_wrap(err, "read body");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nb_read > 0) {
|
||||||
|
content.append(buf, nb_read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class SrsMessageHeader;
|
||||||
class SrsSharedPtrMessage;
|
class SrsSharedPtrMessage;
|
||||||
class SrsCommonMessage;
|
class SrsCommonMessage;
|
||||||
class ISrsProtocolReadWriter;
|
class ISrsProtocolReadWriter;
|
||||||
|
class ISrsReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parse the tcUrl, output the schema, host, vhost, app and port.
|
* parse the tcUrl, output the schema, host, vhost, app and port.
|
||||||
|
@ -183,5 +184,8 @@ extern std::string srs_get_original_ip(ISrsHttpMessage* r);
|
||||||
// Get hostname
|
// Get hostname
|
||||||
extern std::string srs_get_system_hostname(void);
|
extern std::string srs_get_system_hostname(void);
|
||||||
|
|
||||||
|
// Read all content util EOF.
|
||||||
|
extern srs_error_t srs_ioutil_read_all(ISrsReader* in, std::string& content);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue