Commit 07ef4172 by xiongziliang

优化HLS直播相关代码

parent 380a0204
...@@ -219,8 +219,9 @@ static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, con ...@@ -219,8 +219,9 @@ static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, con
}); });
} }
if(!ret && create_new){ if(!ret && create_new && schema != HLS_SCHEMA){
//未查找媒体源,则创建一个 //未查找媒体源,则读取mp4创建一个
//播放hls不触发mp4点播(因为HLS也可以用于录像,不是纯粹的直播)
ret = MediaSource::createFromMP4(schema, vhost, app, id); ret = MediaSource::createFromMP4(schema, vhost, app, id);
} }
return ret; return ret;
......
...@@ -27,7 +27,7 @@ static int kHlsCookieSecond = 60; ...@@ -27,7 +27,7 @@ static int kHlsCookieSecond = 60;
static const string kCookieName = "ZL_COOKIE"; static const string kCookieName = "ZL_COOKIE";
static const string kHlsSuffix = "/hls.m3u8"; static const string kHlsSuffix = "/hls.m3u8";
class HttpCookieAttachment{ class HttpCookieAttachment {
public: public:
HttpCookieAttachment() {}; HttpCookieAttachment() {};
~HttpCookieAttachment() {}; ~HttpCookieAttachment() {};
...@@ -160,7 +160,7 @@ const string &HttpFileManager::getContentType(const char *name) { ...@@ -160,7 +160,7 @@ const string &HttpFileManager::getContentType(const char *name) {
dot = strrchr(name, '.'); dot = strrchr(name, '.');
static StrCaseMap mapType; static StrCaseMap mapType;
static onceToken token([&]() { static onceToken token([&]() {
for (unsigned int i = 0; i < sizeof (s_mime_src) / sizeof (s_mime_src[0]); ++i) { for (unsigned int i = 0; i < sizeof(s_mime_src) / sizeof(s_mime_src[0]); ++i) {
mapType.emplace(s_mime_src[i][0], s_mime_src[i][1]); mapType.emplace(s_mime_src[i][0], s_mime_src[i][1]);
} }
}); });
...@@ -183,8 +183,8 @@ static string searchIndexFile(const string &dir){ ...@@ -183,8 +183,8 @@ static string searchIndexFile(const string &dir){
} }
set<string> setFile; set<string> setFile;
while ((pDirent = readdir(pDir)) != NULL) { while ((pDirent = readdir(pDir)) != NULL) {
static set<const char *,StrCaseCompare> indexSet = {"index.html","index.htm","index"}; static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm", "index"};
if(indexSet.find(pDirent->d_name) != indexSet.end()){ if (indexSet.find(pDirent->d_name) != indexSet.end()) {
string ret = pDirent->d_name; string ret = pDirent->d_name;
closedir(pDir); closedir(pDir);
return ret; return ret;
...@@ -196,16 +196,16 @@ static string searchIndexFile(const string &dir){ ...@@ -196,16 +196,16 @@ static string searchIndexFile(const string &dir){
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) { static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
GET_CONFIG(bool, dirMenu, Http::kDirMenu); GET_CONFIG(bool, dirMenu, Http::kDirMenu);
if(!dirMenu){ if (!dirMenu) {
//不允许浏览文件夹 //不允许浏览文件夹
return false; return false;
} }
string strPathPrefix(strFullPath); string strPathPrefix(strFullPath);
string last_dir_name; string last_dir_name;
if(strPathPrefix.back() == '/'){ if (strPathPrefix.back() == '/') {
strPathPrefix.pop_back(); strPathPrefix.pop_back();
}else{ } else {
last_dir_name = split(strPathPrefix,"/").back(); last_dir_name = split(strPathPrefix, "/").back();
} }
if (!File::is_dir(strPathPrefix.data())) { if (!File::is_dir(strPathPrefix.data())) {
...@@ -249,24 +249,24 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st ...@@ -249,24 +249,24 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
if (File::is_special_dir(pDirent->d_name)) { if (File::is_special_dir(pDirent->d_name)) {
continue; continue;
} }
if(pDirent->d_name[0] == '.'){ if (pDirent->d_name[0] == '.') {
continue; continue;
} }
setFile.emplace(pDirent->d_name); setFile.emplace(pDirent->d_name);
} }
int i = 0; int i = 0;
for(auto &strFile :setFile ){ for (auto &strFile :setFile) {
string strAbsolutePath = strPathPrefix + "/" + strFile; string strAbsolutePath = strPathPrefix + "/" + strFile;
bool isDir = File::is_dir(strAbsolutePath.data()); bool isDir = File::is_dir(strAbsolutePath.data());
ss << "<li><span>" << i++ << "</span>\t"; ss << "<li><span>" << i++ << "</span>\t";
ss << "<a href=\""; ss << "<a href=\"";
if(!last_dir_name.empty()){ if (!last_dir_name.empty()) {
ss << last_dir_name << "/" << strFile; ss << last_dir_name << "/" << strFile;
}else{ } else {
ss << strFile; ss << strFile;
} }
if(isDir){ if (isDir) {
ss << "/"; ss << "/";
} }
ss << "\">"; ss << "\">";
...@@ -307,14 +307,14 @@ static bool end_of(const string &str, const string &substr){ ...@@ -307,14 +307,14 @@ static bool end_of(const string &str, const string &substr){
//拦截hls的播放请求 //拦截hls的播放请求
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件 //访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){ Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) {
//cookie有效期为kHlsCookieSecond //cookie有效期为kHlsCookieSecond
invoker(err,"",kHlsCookieSecond); invoker(err, "", kHlsCookieSecond);
}; };
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,mediaInfo,mediaAuthInvoker,static_cast<SockInfo &>(sender)); bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, mediaInfo, auth_invoker, static_cast<SockInfo &>(sender));
if(!flag){ if (!flag) {
//未开启鉴权,那么允许播放 //未开启鉴权,那么允许播放
mediaAuthInvoker(""); auth_invoker("");
} }
return flag; return flag;
} }
...@@ -325,23 +325,23 @@ public: ...@@ -325,23 +325,23 @@ public:
SockInfoImp() = default; SockInfoImp() = default;
~SockInfoImp() override = default; ~SockInfoImp() override = default;
string get_local_ip() override{ string get_local_ip() override {
return _local_ip; return _local_ip;
} }
uint16_t get_local_port() override{ uint16_t get_local_port() override {
return _local_port; return _local_port;
} }
string get_peer_ip() override{ string get_peer_ip() override {
return _peer_ip; return _peer_ip;
} }
uint16_t get_peer_port() override{ uint16_t get_peer_port() override {
return _peer_port; return _peer_port;
} }
string getIdentifier() const override{ string getIdentifier() const override {
return _identifier; return _identifier;
} }
...@@ -384,7 +384,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI ...@@ -384,7 +384,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
//上次cookie是限定本目录 //上次cookie是限定本目录
if (attachment._err_msg.empty()) { if (attachment._err_msg.empty()) {
//上次鉴权成功 //上次鉴权成功
if(attachment._is_hls){ if (attachment._is_hls) {
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新) //如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
cookie->updateTime(); cookie->updateTime();
cookie_from_header = false; cookie_from_header = false;
...@@ -434,7 +434,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI ...@@ -434,7 +434,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
attachment._err_msg = errMsg; attachment._err_msg = errMsg;
//记录访问的是否为hls //记录访问的是否为hls
attachment._is_hls = is_hls; attachment._is_hls = is_hls;
if(is_hls){ if (is_hls) {
//hls相关信息 //hls相关信息
attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info); attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info);
//hls未查找MediaSource //hls未查找MediaSource
...@@ -442,7 +442,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI ...@@ -442,7 +442,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
} }
(*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment)); (*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
callback(errMsg, cookie); callback(errMsg, cookie);
}else{ } else {
callback(errMsg, nullptr); callback(errMsg, nullptr);
} }
}; };
...@@ -465,15 +465,15 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI ...@@ -465,15 +465,15 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
* 发送404 Not Found * 发送404 Not Found
*/ */
static void sendNotFound(const HttpFileManager::invoker &cb) { static void sendNotFound(const HttpFileManager::invoker &cb) {
GET_CONFIG(string,notFound,Http::kNotFound); GET_CONFIG(string, notFound, Http::kNotFound);
cb("404 Not Found","text/html",StrCaseMap(),std::make_shared<HttpStringBody>(notFound)); cb("404 Not Found", "text/html", StrCaseMap(), std::make_shared<HttpStringBody>(notFound));
} }
/** /**
* 拼接文件路径 * 拼接文件路径
*/ */
static string pathCat(const string &a, const string &b){ static string pathCat(const string &a, const string &b){
if(a.back() == '/'){ if (a.back() == '/') {
return a + b; return a + b;
} }
return a + '/' + b; return a + '/' + b;
...@@ -496,7 +496,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo ...@@ -496,7 +496,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
return; return;
} }
if(is_hls){ if (is_hls) {
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS //hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA; const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, ""); replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
...@@ -506,7 +506,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo ...@@ -506,7 +506,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
//判断是否有权限访问该文件 //判断是否有权限访问该文件
canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) { canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
auto strongSession = weakSession.lock(); auto strongSession = weakSession.lock();
if(!strongSession){ if (!strongSession) {
//http客户端已经断开,不需要回复 //http客户端已经断开,不需要回复
return; return;
} }
...@@ -514,6 +514,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo ...@@ -514,6 +514,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
//文件鉴权失败 //文件鉴权失败
StrCaseMap headerOut; StrCaseMap headerOut;
if (cookie) { if (cookie) {
auto lck = cookie->getLock();
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path); headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
} }
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg)); cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
...@@ -523,11 +524,12 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo ...@@ -523,11 +524,12 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) { auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
StrCaseMap httpHeader; StrCaseMap httpHeader;
if (cookie) { if (cookie) {
auto lck = cookie->getLock();
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path); httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
} }
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) { HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
if (cookie && file_exist) { if (cookie && file_exist) {
cookie->getLock(); auto lck = cookie->getLock();
auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls; auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
if (is_hls) { if (is_hls) {
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize()); (*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize());
...@@ -541,44 +543,47 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo ...@@ -541,44 +543,47 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
if (!is_hls) { if (!is_hls) {
//不是hls,直接回复文件或404 //不是hls,直接回复文件或404
response_file(cookie, cb, strFile, parser); response_file(cookie, cb, strFile, parser);
} else { return;
//是hls直播,判断是否存在 }
bool have_find_media_src = false;
if(cookie){ //是hls直播,判断HLS直播流是否已经注册
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source; bool have_find_media_src = false;
if(!have_find_media_src){ if (cookie) {
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true; auto lck = cookie->getLock();
} have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
if (!have_find_media_src) {
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
} }
if(have_find_media_src){ }
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准 if (have_find_media_src) {
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
response_file(cookie, cb, strFile, parser);
return;
}
//hls文件不存在,我们等待其生成并延后回复
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
if (cookie) {
auto lck = cookie->getLock();
//尝试添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(0);
}
if (src && File::is_file(strFile.data())) {
//流和m3u8文件都存在,那么直接返回文件
response_file(cookie, cb, strFile, parser);
return;
}
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
if (!hls) {
//流不存在,那么直接返回文件(相当于纯粹的HLS文件服务器,但是会挂起播放器15秒左右(用于等待HLS流的注册))
response_file(cookie, cb, strFile, parser); response_file(cookie, cb, strFile, parser);
return; return;
} }
//hls文件不存在,我们等待其生成并延后回复
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
if(cookie){
//尝试添加HlsMediaSource的观看人数
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(0);
}
if (src && File::is_file(strFile.data())) {
//流和m3u8文件都存在,那么直接返回文件
response_file(cookie, cb, strFile, parser);
return;
}
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
if (!hls) {
//流不存在,那么直接返回文件
response_file(cookie, cb, strFile, parser);
return;
}
//流存在,但是m3u8文件不存在,那么等待生成m3u8文件 //流存在,但是m3u8文件不存在,那么等待生成m3u8文件(HLS源注册后,并不会立即生成HLS文件,有人观看才会按需生成HLS文件)
hls->waitForFile([response_file, cookie, cb, strFile, parser]() { hls->waitForFile([response_file, cookie, cb, strFile, parser]() {
response_file(cookie, cb, strFile, parser); response_file(cookie, cb, strFile, parser);
});
}); });
} });
}); });
} }
...@@ -639,13 +644,13 @@ void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const Htt ...@@ -639,13 +644,13 @@ void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const Htt
////////////////////////////////////HttpResponseInvokerImp////////////////////////////////////// ////////////////////////////////////HttpResponseInvokerImp//////////////////////////////////////
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{ void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
if(_lambad){ if (_lambad) {
_lambad(codeOut,headerOut,body); _lambad(codeOut, headerOut, body);
} }
} }
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{ void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{
this->operator()(codeOut,headerOut,std::make_shared<HttpStringBody>(body)); this->operator()(codeOut, headerOut, std::make_shared<HttpStringBody>(body));
} }
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){
...@@ -653,23 +658,23 @@ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::Htt ...@@ -653,23 +658,23 @@ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::Htt
} }
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
if(!lambda){ if (!lambda) {
_lambad = nullptr; _lambad = nullptr;
return; return;
} }
_lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body){ _lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
string str; string str;
if(body && body->remainSize()){ if (body && body->remainSize()) {
str = body->readData(body->remainSize())->toString(); str = body->readData(body->remainSize())->toString();
} }
lambda(codeOut,headerOut,str); lambda(codeOut, headerOut, str);
}; };
} }
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
const StrCaseMap &responseHeader, const StrCaseMap &responseHeader,
const string &filePath) const { const string &filePath) const {
StrCaseMap &httpHeader = const_cast<StrCaseMap&>(responseHeader); StrCaseMap &httpHeader = const_cast<StrCaseMap &>(responseHeader);
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) { std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
if (fp) { if (fp) {
fclose(fp); fclose(fp);
...@@ -678,8 +683,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, ...@@ -678,8 +683,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
if (!fp) { if (!fp) {
//打开文件失败 //打开文件失败
GET_CONFIG(string,notFound,Http::kNotFound); GET_CONFIG(string, notFound, Http::kNotFound);
GET_CONFIG(string,charSet,Http::kCharSet); GET_CONFIG(string, charSet, Http::kCharSet);
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl; auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
httpHeader["Content-Type"] = strContentType; httpHeader["Content-Type"] = strContentType;
...@@ -689,14 +694,14 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, ...@@ -689,14 +694,14 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"]; auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
int64_t iRangeStart = 0; int64_t iRangeStart = 0;
int64_t iRangeEnd = 0 ; int64_t iRangeEnd = 0;
int64_t fileSize = HttpMultiFormBody::fileSize(fp.get()); int64_t fileSize = HttpMultiFormBody::fileSize(fp.get());
const char *pcHttpResult = NULL; const char *pcHttpResult = NULL;
if (strRange.size() == 0) { if (strRange.size() == 0) {
//全部下载 //全部下载
pcHttpResult = "200 OK"; pcHttpResult = "200 OK";
iRangeEnd = fileSize - 1; iRangeEnd = fileSize - 1;
} else { } else {
//分节下载 //分节下载
pcHttpResult = "206 Partial Content"; pcHttpResult = "206 Partial Content";
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论