mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 19:00:01 +08:00
Merge pull request #1419 from ZLMediaKit/feature/mmap
通过共享mmap方式提高http文件/hls服务器性能
This commit is contained in:
commit
d432888ad4
@ -76,7 +76,8 @@ void HlsPlayer::fetchSegment() {
|
||||
}
|
||||
return Socket::createSocket(poller, true);
|
||||
});
|
||||
|
||||
auto benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
|
||||
if (!benchmark_mode) {
|
||||
_http_ts_player->setOnPacket([weak_self](const char *data, size_t len) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
@ -85,6 +86,7 @@ void HlsPlayer::fetchSegment() {
|
||||
//收到ts包
|
||||
strong_self->onPacket_l(data, len);
|
||||
});
|
||||
}
|
||||
|
||||
if (!(*this)[Client::kNetAdapter].empty()) {
|
||||
_http_ts_player->setNetAdapter((*this)[Client::kNetAdapter]);
|
||||
@ -349,7 +351,8 @@ void HlsPlayerImp::addTrackCompleted() {
|
||||
}
|
||||
|
||||
void HlsPlayerImp::onPlayResult(const SockException &ex) {
|
||||
if (ex) {
|
||||
auto benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
|
||||
if (ex || benchmark_mode) {
|
||||
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex);
|
||||
} else {
|
||||
auto demuxer = std::make_shared<HlsDemuxer>();
|
||||
@ -364,6 +367,9 @@ void HlsPlayerImp::onShutdown(const SockException &ex) {
|
||||
}
|
||||
|
||||
vector<Track::Ptr> HlsPlayerImp::getTracks(bool ready) const {
|
||||
if (!_demuxer) {
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
return static_pointer_cast<HlsDemuxer>(_demuxer)->getTracks(ready);
|
||||
}
|
||||
|
||||
|
@ -9,16 +9,24 @@
|
||||
*/
|
||||
|
||||
#include <csignal>
|
||||
#include "HttpBody.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/File.h"
|
||||
#include "Util/uv_errno.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "HttpClient.h"
|
||||
#include <tuple>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#if defined(__linux__) || defined(__linux)
|
||||
#include <sys/sendfile.h>
|
||||
#endif
|
||||
|
||||
#include "Util/File.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/uv_errno.h"
|
||||
|
||||
#include "HttpBody.h"
|
||||
#include "HttpClient.h"
|
||||
#include "Common/macros.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#define ENABLE_MMAP
|
||||
@ -33,7 +41,7 @@ HttpStringBody::HttpStringBody(string str){
|
||||
_str = std::move(str);
|
||||
}
|
||||
|
||||
ssize_t HttpStringBody::remainSize() {
|
||||
int64_t HttpStringBody::remainSize() {
|
||||
return _str.size() - _offset;
|
||||
}
|
||||
|
||||
@ -50,70 +58,122 @@ Buffer::Ptr HttpStringBody::readData(size_t size) {
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
HttpFileBody::HttpFileBody(const string &filePath, bool use_mmap) {
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
#ifdef ENABLE_MMAP
|
||||
|
||||
static mutex s_mtx;
|
||||
static unordered_map<string /*file_path*/, std::tuple<char */*ptr*/, int64_t /*size*/, weak_ptr<char> /*mmap*/ > > s_shared_mmap;
|
||||
|
||||
//删除mmap记录
|
||||
static void delSharedMmap(const string &file_path, char *ptr) {
|
||||
lock_guard<mutex> lck(s_mtx);
|
||||
auto it = s_shared_mmap.find(file_path);
|
||||
if (it != s_shared_mmap.end() && std::get<0>(it->second) == ptr) {
|
||||
s_shared_mmap.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &file_size) {
|
||||
{
|
||||
lock_guard<mutex> lck(s_mtx);
|
||||
auto it = s_shared_mmap.find(file_path);
|
||||
if (it != s_shared_mmap.end()) {
|
||||
auto ret = std::get<2>(it->second).lock();
|
||||
if (ret) {
|
||||
//命中mmap缓存
|
||||
file_size = std::get<1>(it->second);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//打开文件
|
||||
std::shared_ptr<FILE> fp(fopen(file_path.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (!fp) {
|
||||
init(fp, 0, 0, use_mmap);
|
||||
} else {
|
||||
init(fp, 0, File::fileSize(fp.get()), use_mmap);
|
||||
}
|
||||
}
|
||||
|
||||
HttpFileBody::HttpFileBody(const std::shared_ptr<FILE> &fp, size_t offset, size_t max_size, bool use_mmap) {
|
||||
init(fp, offset, max_size, use_mmap);
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(__linux)
|
||||
#include <sys/sendfile.h>
|
||||
#endif
|
||||
|
||||
int HttpFileBody::sendFile(int fd) {
|
||||
#if defined(__linux__) || defined(__linux)
|
||||
static onceToken s_token([]() {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
});
|
||||
off_t off = _file_offset;
|
||||
return sendfile(fd, fileno(_fp.get()), &off, _max_size);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HttpFileBody::init(const std::shared_ptr<FILE> &fp, size_t offset, size_t max_size, bool use_mmap) {
|
||||
_fp = fp;
|
||||
_max_size = max_size;
|
||||
_file_offset = offset;
|
||||
#ifdef ENABLE_MMAP
|
||||
if (use_mmap) {
|
||||
do {
|
||||
if (!_fp) {
|
||||
//文件不存在
|
||||
break;
|
||||
file_size = -1;
|
||||
return nullptr;
|
||||
}
|
||||
//获取文件大小
|
||||
file_size = File::fileSize(fp.get());
|
||||
|
||||
int fd = fileno(fp.get());
|
||||
if (fd < 0) {
|
||||
WarnL << "fileno failed:" << get_uv_errmsg(false);
|
||||
break;
|
||||
return nullptr;
|
||||
}
|
||||
auto ptr = (char *) mmap(NULL, max_size, PROT_READ, MAP_SHARED, fd, offset);
|
||||
auto ptr = (char *)mmap(NULL, file_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (ptr == MAP_FAILED) {
|
||||
WarnL << "mmap failed:" << get_uv_errmsg(false);
|
||||
break;
|
||||
WarnL << "mmap " << file_path << " failed:" << get_uv_errmsg(false);
|
||||
return nullptr;
|
||||
}
|
||||
_map_addr.reset(ptr, [max_size, fp](char *ptr) {
|
||||
munmap(ptr, max_size);
|
||||
std::shared_ptr<char> ret(ptr, [file_size, fp, file_path](char *ptr) {
|
||||
munmap(ptr, file_size);
|
||||
delSharedMmap(file_path, ptr);
|
||||
});
|
||||
|
||||
#if 0
|
||||
if (file_size < 10 * 1024 * 1024 && file_path.rfind(".ts") != string::npos) {
|
||||
//如果是小ts文件,那么尝试先加载到内存
|
||||
auto buf = BufferRaw::create();
|
||||
buf->assign(ret.get(), file_size);
|
||||
ret.reset(buf->data(), [buf, file_path](char *ptr) {
|
||||
delSharedMmap(file_path, ptr);
|
||||
});
|
||||
} while (false);
|
||||
}
|
||||
#endif
|
||||
if (!_map_addr && offset && fp.get()) {
|
||||
//未映射,那么fseek设置偏移量
|
||||
fseek64(fp.get(), offset, SEEK_SET);
|
||||
{
|
||||
lock_guard<mutex> lck(s_mtx);
|
||||
s_shared_mmap[file_path] = std::make_tuple(ret.get(), file_size, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
HttpFileBody::HttpFileBody(const string &file_path, bool use_mmap) {
|
||||
#ifdef ENABLE_MMAP
|
||||
if (use_mmap ) {
|
||||
_map_addr = getSharedMmap(file_path, _read_to);
|
||||
}
|
||||
#endif
|
||||
if (!_map_addr && _read_to != -1) {
|
||||
_fp.reset(fopen(file_path.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (!_fp) {
|
||||
//文件不存在
|
||||
_read_to = -1;
|
||||
return;
|
||||
}
|
||||
_read_to = File::fileSize(_fp.get());
|
||||
}
|
||||
}
|
||||
|
||||
void HttpFileBody::setRange(uint64_t offset, uint64_t max_size) {
|
||||
CHECK((int64_t)offset <= _read_to && (int64_t)(max_size + offset) <= _read_to);
|
||||
_read_to = max_size + offset;
|
||||
_file_offset = offset;
|
||||
if (_fp && !_map_addr) {
|
||||
fseek64(_fp.get(), _file_offset, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
int HttpFileBody::sendFile(int fd) {
|
||||
#if defined(__linux__) || defined(__linux)
|
||||
if (!_fp) {
|
||||
return -1;
|
||||
}
|
||||
static onceToken s_token([]() { signal(SIGPIPE, SIG_IGN); });
|
||||
off_t off = _file_offset;
|
||||
return sendfile(fd, fileno(_fp.get()), &off, _read_to - _file_offset);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
class BufferMmap : public Buffer {
|
||||
@ -126,20 +186,17 @@ public:
|
||||
}
|
||||
~BufferMmap() override {};
|
||||
//返回数据长度
|
||||
char *data() const override {
|
||||
return _data;
|
||||
}
|
||||
size_t size() const override{
|
||||
return _size;
|
||||
}
|
||||
char *data() const override { return _data; }
|
||||
size_t size() const override { return _size; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<char> _map_addr;
|
||||
char *_data;
|
||||
size_t _size;
|
||||
std::shared_ptr<char> _map_addr;
|
||||
};
|
||||
|
||||
ssize_t HttpFileBody::remainSize() {
|
||||
return _max_size - _offset;
|
||||
int64_t HttpFileBody::remainSize() {
|
||||
return _read_to - _file_offset;
|
||||
}
|
||||
|
||||
Buffer::Ptr HttpFileBody::readData(size_t size) {
|
||||
@ -160,32 +217,28 @@ Buffer::Ptr HttpFileBody::readData(size_t size) {
|
||||
if (iRead > 0) {
|
||||
//读到数据了
|
||||
ret->setSize(iRead);
|
||||
_offset += iRead;
|
||||
_file_offset += iRead;
|
||||
return std::move(ret);
|
||||
}
|
||||
//读取文件异常,文件真实长度小于声明长度
|
||||
_offset = _max_size;
|
||||
_file_offset = _read_to;
|
||||
WarnL << "read file err:" << get_uv_errmsg();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// mmap模式
|
||||
auto ret = std::make_shared<BufferMmap>(_map_addr,_offset,size);
|
||||
_offset += size;
|
||||
auto ret = std::make_shared<BufferMmap>(_map_addr, _file_offset, size);
|
||||
_file_offset += size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
HttpMultiFormBody::HttpMultiFormBody(const HttpArgs &args, const string &filePath, const string &boundary) {
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
if(fp){
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if(!fp){
|
||||
_fileBody = std::make_shared<HttpFileBody>(filePath);
|
||||
if (_fileBody->remainSize() < 0) {
|
||||
throw std::invalid_argument(StrPrinter << "open file failed:" << filePath << " " << get_uv_errmsg());
|
||||
}
|
||||
_fileBody = std::make_shared<HttpFileBody>(fp, 0, File::fileSize(fp.get()));
|
||||
|
||||
auto fileName = filePath;
|
||||
auto pos = filePath.rfind('/');
|
||||
@ -197,7 +250,7 @@ HttpMultiFormBody::HttpMultiFormBody(const HttpArgs &args,const string &filePath
|
||||
_totalSize = _bodyPrefix.size() + _bodySuffix.size() + _fileBody->remainSize();
|
||||
}
|
||||
|
||||
ssize_t HttpMultiFormBody::remainSize() {
|
||||
int64_t HttpMultiFormBody::remainSize() {
|
||||
return _totalSize - _offset;
|
||||
}
|
||||
|
||||
@ -251,7 +304,9 @@ string HttpMultiFormBody::multiFormBodyPrefix(const HttpArgs &args,const string
|
||||
body << pr.second << "\r\n";
|
||||
}
|
||||
body << MPboundary << "\r\n";
|
||||
body << "Content-Disposition: form-data; name=\"" << "file" << "\";filename=\"" << fileName << "\"\r\n";
|
||||
body << "Content-Disposition: form-data; name=\""
|
||||
<< "file"
|
||||
<< "\";filename=\"" << fileName << "\"\r\n";
|
||||
body << "Content-Type: application/octet-stream\r\n\r\n";
|
||||
return std::move(body);
|
||||
}
|
||||
@ -260,7 +315,7 @@ HttpBufferBody::HttpBufferBody(Buffer::Ptr buffer) {
|
||||
_buffer = std::move(buffer);
|
||||
}
|
||||
|
||||
ssize_t HttpBufferBody::remainSize() {
|
||||
int64_t HttpBufferBody::remainSize() {
|
||||
return _buffer ? _buffer->size() : 0;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ public:
|
||||
/**
|
||||
* 剩余数据大小,如果返回-1, 那么就不设置content-length
|
||||
*/
|
||||
virtual ssize_t remainSize() { return 0;};
|
||||
virtual int64_t remainSize() { return 0;};
|
||||
|
||||
/**
|
||||
* 读取一定字节数,返回大小可能小于size
|
||||
@ -77,7 +77,7 @@ public:
|
||||
HttpStringBody(std::string str);
|
||||
~HttpStringBody() override = default;
|
||||
|
||||
ssize_t remainSize() override;
|
||||
int64_t remainSize() override;
|
||||
toolkit::Buffer::Ptr readData(size_t size) override ;
|
||||
|
||||
private:
|
||||
@ -94,7 +94,7 @@ public:
|
||||
HttpBufferBody(toolkit::Buffer::Ptr buffer);
|
||||
~HttpBufferBody() override = default;
|
||||
|
||||
ssize_t remainSize() override;
|
||||
int64_t remainSize() override;
|
||||
toolkit::Buffer::Ptr readData(size_t size) override;
|
||||
|
||||
private:
|
||||
@ -110,26 +110,26 @@ public:
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param fp 文件句柄,文件的偏移量必须为0
|
||||
* @param offset 相对文件头的偏移量
|
||||
* @param max_size 最大读取字节数,未判断是否大于文件真实大小
|
||||
* @param file_path 文件路径
|
||||
* @param use_mmap 是否使用mmap方式访问文件
|
||||
*/
|
||||
HttpFileBody(const std::shared_ptr<FILE> &fp, size_t offset, size_t max_size, bool use_mmap = true);
|
||||
HttpFileBody(const std::string &file_path, bool use_mmap = true);
|
||||
~HttpFileBody() override = default;
|
||||
|
||||
ssize_t remainSize() override ;
|
||||
/**
|
||||
* 设置读取范围
|
||||
* @param offset 相对文件头的偏移量
|
||||
* @param max_size 最大读取字节数
|
||||
*/
|
||||
void setRange(uint64_t offset, uint64_t max_size);
|
||||
|
||||
int64_t remainSize() override;
|
||||
toolkit::Buffer::Ptr readData(size_t size) override;
|
||||
int sendFile(int fd) override;
|
||||
|
||||
private:
|
||||
void init(const std::shared_ptr<FILE> &fp,size_t offset,size_t max_size, bool use_mmap);
|
||||
|
||||
private:
|
||||
size_t _max_size;
|
||||
size_t _offset = 0;
|
||||
size_t _file_offset = 0;
|
||||
int64_t _read_to = 0;
|
||||
uint64_t _file_offset = 0;
|
||||
std::shared_ptr<FILE> _fp;
|
||||
std::shared_ptr<char> _map_addr;
|
||||
toolkit::ResourcePool<toolkit::BufferRaw> _pool;
|
||||
@ -152,7 +152,7 @@ public:
|
||||
*/
|
||||
HttpMultiFormBody(const HttpArgs &args,const std::string &filePath,const std::string &boundary = "0xKhTmLbOuNdArY");
|
||||
virtual ~HttpMultiFormBody(){}
|
||||
ssize_t remainSize() override ;
|
||||
int64_t remainSize() override ;
|
||||
toolkit::Buffer::Ptr readData(size_t size) override;
|
||||
|
||||
public:
|
||||
@ -161,8 +161,8 @@ public:
|
||||
static std::string multiFormContentType(const std::string &boundary);
|
||||
|
||||
private:
|
||||
size_t _offset = 0;
|
||||
size_t _totalSize;
|
||||
uint64_t _offset = 0;
|
||||
int64_t _totalSize;
|
||||
std::string _bodyPrefix;
|
||||
std::string _bodySuffix;
|
||||
HttpFileBody::Ptr _fileBody;
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
#include "HttpCookie.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "Util/strptime_win.h"
|
||||
@ -24,24 +24,71 @@ namespace mediakit {
|
||||
void HttpCookie::setPath(const string &path) {
|
||||
_path = path;
|
||||
}
|
||||
|
||||
void HttpCookie::setHost(const string &host) {
|
||||
_host = host;
|
||||
}
|
||||
|
||||
static long s_gmtoff = 0; //时间差
|
||||
static onceToken s_token([]() {
|
||||
#ifdef _WIN32
|
||||
TIME_ZONE_INFORMATION tzinfo;
|
||||
DWORD dwStandardDaylight;
|
||||
long bias;
|
||||
dwStandardDaylight = GetTimeZoneInformation(&tzinfo);
|
||||
bias = tzinfo.Bias;
|
||||
if (dwStandardDaylight == TIME_ZONE_ID_STANDARD) {
|
||||
bias += tzinfo.StandardBias;
|
||||
}
|
||||
if (dwStandardDaylight == TIME_ZONE_ID_DAYLIGHT) {
|
||||
bias += tzinfo.DaylightBias;
|
||||
}
|
||||
s_gmtoff = -bias * 60; //时间差(分钟)
|
||||
#else
|
||||
s_gmtoff = getLocalTime(time(nullptr)).tm_gmtoff;
|
||||
#endif // _WIN32
|
||||
});
|
||||
|
||||
// from https://gmbabar.wordpress.com/2010/12/01/mktime-slow-use-custom-function/#comment-58
|
||||
static time_t time_to_epoch(const struct tm *ltm, int utcdiff) {
|
||||
const int mon_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
long tyears, tdays, leaps, utc_hrs;
|
||||
int i;
|
||||
|
||||
tyears = ltm->tm_year - 70; // tm->tm_year is from 1900.
|
||||
leaps = (tyears + 2) / 4; // no of next two lines until year 2100.
|
||||
// i = (ltm->tm_year – 100) / 100;
|
||||
// leaps -= ( (i/4)*3 + i%4 );
|
||||
tdays = 0;
|
||||
for (i = 0; i < ltm->tm_mon; i++)
|
||||
tdays += mon_days[i];
|
||||
|
||||
tdays += ltm->tm_mday - 1; // days of month passed.
|
||||
tdays = tdays + (tyears * 365) + leaps;
|
||||
|
||||
utc_hrs = ltm->tm_hour + utcdiff; // for your time zone.
|
||||
return (tdays * 86400) + (utc_hrs * 3600) + (ltm->tm_min * 60) + ltm->tm_sec;
|
||||
}
|
||||
|
||||
static time_t timeStrToInt(const string &date) {
|
||||
struct tm tt;
|
||||
strptime(date.data(), "%a, %b %d %Y %H:%M:%S %Z", &tt);
|
||||
return mktime(&tt);
|
||||
// mktime内部有使用互斥锁,非常影响性能
|
||||
return time_to_epoch(&tt, s_gmtoff / 3600); // mktime(&tt);
|
||||
}
|
||||
|
||||
void HttpCookie::setExpires(const string &expires, const string &server_date) {
|
||||
_expire = timeStrToInt(expires);
|
||||
if (!server_date.empty()) {
|
||||
_expire = time(NULL) + (_expire - timeStrToInt(server_date));
|
||||
}
|
||||
}
|
||||
|
||||
void HttpCookie::setKeyVal(const string &key, const string &val) {
|
||||
_key = key;
|
||||
_val = val;
|
||||
}
|
||||
|
||||
HttpCookie::operator bool() {
|
||||
return !_host.empty() && !_key.empty() && !_val.empty() && (_expire > time(NULL));
|
||||
}
|
||||
@ -54,7 +101,6 @@ const string &HttpCookie::getKey() const{
|
||||
return _key;
|
||||
}
|
||||
|
||||
|
||||
HttpCookieStorage &HttpCookieStorage::Instance() {
|
||||
static HttpCookieStorage instance;
|
||||
return instance;
|
||||
@ -97,5 +143,4 @@ vector<HttpCookie::Ptr> HttpCookieStorage::get(const string &host, const string
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -8,10 +8,10 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "Util/util.h"
|
||||
#include "Util/MD5.h"
|
||||
#include "Common/config.h"
|
||||
#include "HttpCookieManager.h"
|
||||
#include "Common/config.h"
|
||||
#include "Util/MD5.h"
|
||||
#include "Util/util.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
@ -19,11 +19,9 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
//////////////////////////////HttpServerCookie////////////////////////////////////
|
||||
HttpServerCookie::HttpServerCookie(const std::shared_ptr<HttpCookieManager> &manager,
|
||||
const string &cookie_name,
|
||||
const string &uid,
|
||||
const string &cookie,
|
||||
uint64_t max_elapsed){
|
||||
HttpServerCookie::HttpServerCookie(
|
||||
const std::shared_ptr<HttpCookieManager> &manager, const string &cookie_name, const string &uid,
|
||||
const string &cookie, uint64_t max_elapsed) {
|
||||
_uid = uid;
|
||||
_max_elapsed = max_elapsed;
|
||||
_cookie_uuid = cookie;
|
||||
@ -63,8 +61,8 @@ bool HttpServerCookie::isExpired() {
|
||||
return _ticker.elapsedTime() > _max_elapsed * 1000;
|
||||
}
|
||||
|
||||
std::shared_ptr<lock_guard<recursive_mutex> > HttpServerCookie::getLock(){
|
||||
return std::make_shared<lock_guard<recursive_mutex> >(_mtx);
|
||||
void HttpServerCookie::setAttach(std::shared_ptr<void> attach) {
|
||||
_attach = std::move(attach);
|
||||
}
|
||||
|
||||
string HttpServerCookie::cookieExpireTime() const {
|
||||
@ -78,10 +76,13 @@ INSTANCE_IMP(HttpCookieManager);
|
||||
|
||||
HttpCookieManager::HttpCookieManager() {
|
||||
//定时删除过期的cookie,防止内存膨胀
|
||||
_timer = std::make_shared<Timer>(10.0f,[this](){
|
||||
_timer = std::make_shared<Timer>(
|
||||
10.0f,
|
||||
[this]() {
|
||||
onManager();
|
||||
return true;
|
||||
}, nullptr);
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
HttpCookieManager::~HttpCookieManager() {
|
||||
@ -113,10 +114,11 @@ void HttpCookieManager::onManager() {
|
||||
}
|
||||
}
|
||||
|
||||
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name,const string &uidIn,uint64_t max_elapsed,int max_client) {
|
||||
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in,
|
||||
uint64_t max_elapsed, std::shared_ptr<void> attach, int max_client) {
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
auto cookie = _geneator.obtain();
|
||||
auto uid = uidIn.empty() ? cookie : uidIn;
|
||||
auto uid = uid_in.empty() ? cookie : uid_in;
|
||||
auto oldCookie = getOldestCookie(cookie_name, uid, max_client);
|
||||
if (!oldCookie.empty()) {
|
||||
//假如该账号已经登录了,那么删除老的cookie。
|
||||
@ -124,6 +126,7 @@ HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name,con
|
||||
delCookie(cookie_name, oldCookie);
|
||||
}
|
||||
HttpServerCookie::Ptr data(new HttpServerCookie(shared_from_this(), cookie_name, uid, cookie, max_elapsed));
|
||||
data->setAttach(std::move(attach));
|
||||
//保存该账号下的新cookie
|
||||
_map_cookie[cookie_name][cookie] = data;
|
||||
return data;
|
||||
@ -198,6 +201,7 @@ void HttpCookieManager::onAddCookie(const string &cookie_name,const string &uid,
|
||||
//相同用户下可以存在多个cookie(意味多地登录),这些cookie根据登录时间的早晚依次排序
|
||||
_map_uid_to_cookie[cookie_name][uid][getCurrentMillisecond()] = cookie;
|
||||
}
|
||||
|
||||
void HttpCookieManager::onDelCookie(const string &cookie_name, const string &uid, const string &cookie) {
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
//回收随机字符串
|
||||
@ -237,7 +241,6 @@ void HttpCookieManager::onDelCookie(const string &cookie_name,const string &uid,
|
||||
_map_uid_to_cookie.erase(it_name);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string HttpCookieManager::getOldestCookie(const string &cookie_name, const string &uid, int max_client) {
|
||||
@ -272,6 +275,7 @@ string RandStrGeneator::obtain(){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RandStrGeneator::release(const string &str) {
|
||||
//从防膨胀库中移除
|
||||
_obtained.erase(str);
|
||||
|
@ -11,13 +11,13 @@
|
||||
#ifndef SRC_HTTP_COOKIEMANAGER_H
|
||||
#define SRC_HTTP_COOKIEMANAGER_H
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "Common/Parser.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Util/mini.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Common/Parser.h"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#define COOKIE_DEFAULT_LIFE (7 * 24 * 60 * 60)
|
||||
|
||||
@ -28,9 +28,9 @@ class HttpCookieManager;
|
||||
/**
|
||||
* cookie对象,用于保存cookie的一些相关属性
|
||||
*/
|
||||
class HttpServerCookie : public toolkit::AnyStorage , public toolkit::noncopyable{
|
||||
class HttpServerCookie : public toolkit::noncopyable {
|
||||
public:
|
||||
typedef std::shared_ptr<HttpServerCookie> Ptr;
|
||||
using Ptr = std::shared_ptr<HttpServerCookie>;
|
||||
/**
|
||||
* 构建cookie
|
||||
* @param manager cookie管理者对象
|
||||
@ -40,11 +40,9 @@ public:
|
||||
* @param max_elapsed 最大过期时间,单位秒
|
||||
*/
|
||||
|
||||
HttpServerCookie(const std::shared_ptr<HttpCookieManager> &manager,
|
||||
const std::string &cookie_name,
|
||||
const std::string &uid,
|
||||
const std::string &cookie,
|
||||
uint64_t max_elapsed);
|
||||
HttpServerCookie(
|
||||
const std::shared_ptr<HttpCookieManager> &manager, const std::string &cookie_name, const std::string &uid,
|
||||
const std::string &cookie, uint64_t max_elapsed);
|
||||
~HttpServerCookie();
|
||||
|
||||
/**
|
||||
@ -85,19 +83,28 @@ public:
|
||||
bool isExpired();
|
||||
|
||||
/**
|
||||
* 获取区域锁
|
||||
* @return
|
||||
* 设置附加数据
|
||||
*/
|
||||
std::shared_ptr<std::lock_guard<std::recursive_mutex> > getLock();
|
||||
void setAttach(std::shared_ptr<void> attach);
|
||||
|
||||
/*
|
||||
* 获取附加数据
|
||||
*/
|
||||
template <class T>
|
||||
const T& getAttach() const {
|
||||
return *static_cast<const T *>(_attach.get());
|
||||
}
|
||||
|
||||
private:
|
||||
std::string cookieExpireTime() const;
|
||||
|
||||
private:
|
||||
std::string _uid;
|
||||
std::string _cookie_name;
|
||||
std::string _cookie_uuid;
|
||||
uint64_t _max_elapsed;
|
||||
toolkit::Ticker _ticker;
|
||||
std::recursive_mutex _mtx;
|
||||
std::shared_ptr<void> _attach;
|
||||
std::weak_ptr<HttpCookieManager> _manager;
|
||||
};
|
||||
|
||||
@ -120,8 +127,10 @@ public:
|
||||
* @param str 随机字符串
|
||||
*/
|
||||
void release(const std::string &str);
|
||||
|
||||
private:
|
||||
std::string obtain_l();
|
||||
|
||||
private:
|
||||
//碰撞库
|
||||
std::unordered_set<std::string> _obtained;
|
||||
@ -135,8 +144,8 @@ private:
|
||||
*/
|
||||
class HttpCookieManager : public std::enable_shared_from_this<HttpCookieManager> {
|
||||
public:
|
||||
typedef std::shared_ptr<HttpCookieManager> Ptr;
|
||||
friend class HttpServerCookie;
|
||||
using Ptr = std::shared_ptr<HttpCookieManager>;
|
||||
~HttpCookieManager();
|
||||
|
||||
/**
|
||||
@ -152,7 +161,10 @@ public:
|
||||
* @param max_elapsed 该cookie过期时间,单位秒
|
||||
* @return cookie对象
|
||||
*/
|
||||
HttpServerCookie::Ptr addCookie(const std::string &cookie_name,const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,int max_client = 1);
|
||||
HttpServerCookie::Ptr addCookie(
|
||||
const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,
|
||||
std::shared_ptr<void> attach = nullptr,
|
||||
int max_client = 1);
|
||||
|
||||
/**
|
||||
* 根据cookie随机字符串查找cookie对象
|
||||
@ -184,8 +196,10 @@ public:
|
||||
* @return
|
||||
*/
|
||||
bool delCookie(const HttpServerCookie::Ptr &cookie);
|
||||
|
||||
private:
|
||||
HttpCookieManager();
|
||||
|
||||
void onManager();
|
||||
/**
|
||||
* 构造cookie对象时触发,目的是记录某账号下多个cookie
|
||||
@ -219,9 +233,15 @@ private:
|
||||
* @return 成功true
|
||||
*/
|
||||
bool delCookie(const std::string &cookie_name, const std::string &cookie);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string/*cookie_name*/,std::unordered_map<std::string/*cookie*/,HttpServerCookie::Ptr/*cookie_data*/> >_map_cookie;
|
||||
std::unordered_map<std::string/*cookie_name*/,std::unordered_map<std::string/*uid*/,std::map<uint64_t/*cookie time stamp*/,std::string/*cookie*/> > >_map_uid_to_cookie;
|
||||
std::unordered_map<
|
||||
std::string /*cookie_name*/, std::unordered_map<std::string /*cookie*/, HttpServerCookie::Ptr /*cookie_data*/>>
|
||||
_map_cookie;
|
||||
std::unordered_map<
|
||||
std::string /*cookie_name*/,
|
||||
std::unordered_map<std::string /*uid*/, std::map<uint64_t /*cookie time stamp*/, std::string /*cookie*/>>>
|
||||
_map_uid_to_cookie;
|
||||
std::recursive_mutex _mtx_cookie;
|
||||
toolkit::Timer::Ptr _timer;
|
||||
RandStrGeneator _geneator;
|
||||
@ -229,5 +249,4 @@ private:
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
|
||||
#endif // SRC_HTTP_COOKIEMANAGER_H
|
||||
|
@ -33,9 +33,6 @@ static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kHlsSuffix = "/hls.m3u8";
|
||||
|
||||
class HttpCookieAttachment {
|
||||
public:
|
||||
HttpCookieAttachment() {};
|
||||
~HttpCookieAttachment() {};
|
||||
public:
|
||||
//cookie生效作用域,本cookie只对该目录下的文件生效
|
||||
string _path;
|
||||
@ -192,13 +189,13 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
|
||||
}
|
||||
|
||||
//拦截hls的播放请求
|
||||
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
|
||||
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
|
||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||
Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) {
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
invoker(err, "", kHlsCookieSecond);
|
||||
};
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, mediaInfo, auth_invoker, static_cast<SockInfo &>(sender));
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast<SockInfo &>(sender));
|
||||
if (!flag) {
|
||||
//未开启鉴权,那么允许播放
|
||||
auth_invoker("");
|
||||
@ -247,7 +244,7 @@ public:
|
||||
* 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
|
||||
* 5、触发kBroadcastHttpAccess事件
|
||||
*/
|
||||
static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, bool is_dir,
|
||||
static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir,
|
||||
const function<void(const string &errMsg, const HttpServerCookie::Ptr &cookie)> &callback) {
|
||||
//获取用户唯一id
|
||||
auto uid = parser.Params();
|
||||
@ -265,13 +262,12 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
|
||||
if (cookie) {
|
||||
//找到了cookie,对cookie上锁先
|
||||
auto lck = cookie->getLock();
|
||||
auto attachment = (*cookie)[kCookieName].get<HttpCookieAttachment>();
|
||||
if (path.find(attachment._path) == 0) {
|
||||
auto& attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
if (path.find(attach._path) == 0) {
|
||||
//上次cookie是限定本目录
|
||||
if (attachment._err_msg.empty()) {
|
||||
if (attach._err_msg.empty()) {
|
||||
//上次鉴权成功
|
||||
if (attachment._is_hls) {
|
||||
if (attach._is_hls) {
|
||||
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
|
||||
cookie->updateTime();
|
||||
cookie_from_header = false;
|
||||
@ -282,7 +278,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
//上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
|
||||
if (parser.Params().empty() || parser.Params() == cookie->getUid()) {
|
||||
//url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
|
||||
callback(attachment._err_msg, cookie_from_header ? nullptr : cookie);
|
||||
callback(attach._err_msg, cookie_from_header ? nullptr : cookie);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -290,7 +286,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
HttpCookieManager::Instance().delCookie(cookie);
|
||||
}
|
||||
|
||||
bool is_hls = mediaInfo._schema == HLS_SCHEMA;
|
||||
bool is_hls = media_info._schema == HLS_SCHEMA;
|
||||
|
||||
SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
|
||||
info->_identifier = sender.getIdentifier();
|
||||
@ -300,10 +296,10 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
info->_local_port = sender.get_local_port();
|
||||
|
||||
//该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
|
||||
HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, mediaInfo, info]
|
||||
(const string &errMsg, const string &cookie_path_in, int cookieLifeSecond) {
|
||||
HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, media_info, info]
|
||||
(const string &err_msg, const string &cookie_path_in, int life_second) {
|
||||
HttpServerCookie::Ptr cookie;
|
||||
if (cookieLifeSecond) {
|
||||
if (life_second) {
|
||||
//本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
|
||||
string cookie_path = cookie_path_in;
|
||||
if (cookie_path.empty()) {
|
||||
@ -311,32 +307,28 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
cookie_path = is_dir ? path : path.substr(0, path.rfind("/") + 1);
|
||||
}
|
||||
|
||||
cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
|
||||
//对cookie上锁
|
||||
auto lck = cookie->getLock();
|
||||
HttpCookieAttachment attachment;
|
||||
auto attach = std::make_shared<HttpCookieAttachment>();
|
||||
//记录用户能访问的路径
|
||||
attachment._path = cookie_path;
|
||||
attach->_path = cookie_path;
|
||||
//记录能否访问
|
||||
attachment._err_msg = errMsg;
|
||||
attach->_err_msg = err_msg;
|
||||
//记录访问的是否为hls
|
||||
attachment._is_hls = is_hls;
|
||||
attach->_is_hls = is_hls;
|
||||
if (is_hls) {
|
||||
// hls相关信息
|
||||
attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info);
|
||||
attach->_hls_data = std::make_shared<HlsCookieData>(media_info, info);
|
||||
// hls未查找MediaSource
|
||||
attachment._have_find_media_source = false;
|
||||
attach->_have_find_media_source = false;
|
||||
}
|
||||
(*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
|
||||
callback(errMsg, cookie);
|
||||
callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, attach));
|
||||
} else {
|
||||
callback(errMsg, nullptr);
|
||||
callback(err_msg, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
if (is_hls) {
|
||||
//是hls的播放鉴权,拦截之
|
||||
emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender);
|
||||
emitHlsPlayed(parser, media_info, accessPathInvoker, sender);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -370,13 +362,13 @@ static string pathCat(const string &a, const string &b){
|
||||
* 访问文件
|
||||
* @param sender 事件触发者
|
||||
* @param parser http请求
|
||||
* @param mediaInfo http url信息
|
||||
* @param strFile 文件绝对路径
|
||||
* @param media_info http url信息
|
||||
* @param file_path 文件绝对路径
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, const string &strFile, const HttpFileManager::invoker &cb) {
|
||||
bool is_hls = end_with(strFile, kHlsSuffix);
|
||||
bool file_exist = File::is_file(strFile.data());
|
||||
static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) {
|
||||
bool is_hls = end_with(file_path, kHlsSuffix);
|
||||
bool file_exist = File::is_file(file_path.data());
|
||||
if (!is_hls && !file_exist) {
|
||||
//文件不存在且不是hls,那么直接返回404
|
||||
sendNotFound(cb);
|
||||
@ -385,13 +377,13 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
|
||||
if (is_hls) {
|
||||
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
|
||||
const_cast<string &>(media_info._schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(media_info._streamid), kHlsSuffix, "");
|
||||
}
|
||||
|
||||
weak_ptr<TcpSession> weakSession = sender.shared_from_this();
|
||||
//判断是否有权限访问该文件
|
||||
canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
|
||||
canAccessPath(sender, parser, media_info, false, [cb, file_path, parser, is_hls, media_info, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
|
||||
auto strongSession = weakSession.lock();
|
||||
if (!strongSession) {
|
||||
//http客户端已经断开,不需要回复
|
||||
@ -401,80 +393,82 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
//文件鉴权失败
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
headerOut["Set-Cookie"] = cookie->getCookie(cookie->getAttach<HttpCookieAttachment>()._path);
|
||||
}
|
||||
cb(401, "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
auto response_file = [file_exist, is_hls](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
|
||||
auto response_file = [file_exist, is_hls](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb,
|
||||
const string &file_path, const Parser &parser, const string &file_content = "") {
|
||||
StrCaseMap httpHeader;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie(cookie->getAttach<HttpCookieAttachment>()._path);
|
||||
}
|
||||
HttpSession::HttpResponseInvoker invoker = [&](int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
||||
if (cookie && file_exist) {
|
||||
auto lck = cookie->getLock();
|
||||
auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
|
||||
if (is_hls) {
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize());
|
||||
auto& attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
if (attach._is_hls) {
|
||||
attach._hls_data->addByteUsage(body->remainSize());
|
||||
}
|
||||
}
|
||||
cb(code, HttpFileManager::getContentType(strFile.data()), headerOut, body);
|
||||
cb(code, HttpFileManager::getContentType(file_path.data()), headerOut, body);
|
||||
};
|
||||
invoker.responseFile(parser.getHeader(), httpHeader, strFile, !is_hls);
|
||||
invoker.responseFile(parser.getHeader(), httpHeader, file_content.empty() ? file_path : file_content, !is_hls, file_content.empty());
|
||||
};
|
||||
|
||||
if (!is_hls) {
|
||||
//不是hls,直接回复文件或404
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
response_file(cookie, cb, file_path, parser);
|
||||
return;
|
||||
}
|
||||
|
||||
//是hls直播,判断HLS直播流是否已经注册
|
||||
bool have_find_media_src = false;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
|
||||
auto& attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
have_find_media_src = attach._have_find_media_source;
|
||||
if (!have_find_media_src) {
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
|
||||
const_cast<HttpCookieAttachment &>(attach)._have_find_media_source = true;
|
||||
} else {
|
||||
auto src = attach._hls_data->getMediaSource();
|
||||
if (src) {
|
||||
//直接从内存获取m3u8索引文件(而不是从文件系统)
|
||||
response_file(cookie, cb, file_path, parser, src->getIndexFile());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (have_find_media_src) {
|
||||
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
response_file(cookie, cb, file_path, parser);
|
||||
return;
|
||||
}
|
||||
//hls文件不存在,我们等待其生成并延后回复
|
||||
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
|
||||
MediaSource::findAsync(media_info, strongSession, [response_file, cookie, cb, file_path, 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 &attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
attach._hls_data->addByteUsage(0);
|
||||
attach._hls_data->setMediaSource(dynamic_pointer_cast<HlsMediaSource>(src));
|
||||
}
|
||||
|
||||
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
|
||||
if (!hls) {
|
||||
//流不存在,那么直接返回文件(相当于纯粹的HLS文件服务器,但是会挂起播放器15秒左右(用于等待HLS流的注册))
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
response_file(cookie, cb, file_path, parser);
|
||||
return;
|
||||
}
|
||||
|
||||
//流存在,但是m3u8文件不存在,那么等待生成m3u8文件(HLS源注册后,并不会立即生成HLS文件,有人观看才会按需生成HLS文件)
|
||||
hls->waitForFile([response_file, cookie, cb, strFile, parser]() {
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
//可能异步获取m3u8索引文件
|
||||
hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) {
|
||||
response_file(cookie, cb, file_path, parser, file);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSession &sender){
|
||||
static string getFilePath(const Parser &parser,const MediaInfo &media_info, TcpSession &sender){
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
GET_CONFIG(string, rootPath, Http::kRootPath);
|
||||
GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) {
|
||||
@ -482,17 +476,17 @@ static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSe
|
||||
});
|
||||
|
||||
string url, path;
|
||||
auto it = virtualPathMap.find(mediaInfo._app);
|
||||
auto it = virtualPathMap.find(media_info._app);
|
||||
if (it != virtualPathMap.end()) {
|
||||
//访问的是virtualPath
|
||||
path = it->second;
|
||||
url = parser.Url().substr(1 + mediaInfo._app.size());
|
||||
url = parser.Url().substr(1 + media_info._app.size());
|
||||
} else {
|
||||
//访问的是rootPath
|
||||
path = rootPath;
|
||||
url = parser.Url();
|
||||
}
|
||||
auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + url : url, path);
|
||||
auto ret = File::absolutePath(enableVhost ? media_info._vhost + url : url, path);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
|
||||
return ret;
|
||||
}
|
||||
@ -505,33 +499,33 @@ static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSe
|
||||
*/
|
||||
void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const HttpFileManager::invoker &cb) {
|
||||
auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl();
|
||||
MediaInfo mediaInfo(fullUrl);
|
||||
auto strFile = getFilePath(parser, mediaInfo, sender);
|
||||
MediaInfo media_info(fullUrl);
|
||||
auto file_path = getFilePath(parser, media_info, sender);
|
||||
//访问的是文件夹
|
||||
if (File::is_dir(strFile.data())) {
|
||||
auto indexFile = searchIndexFile(strFile);
|
||||
if (File::is_dir(file_path.data())) {
|
||||
auto indexFile = searchIndexFile(file_path);
|
||||
if (!indexFile.empty()) {
|
||||
//发现该文件夹下有index文件
|
||||
strFile = pathCat(strFile, indexFile);
|
||||
file_path = pathCat(file_path, indexFile);
|
||||
parser.setUrl(pathCat(parser.Url(), indexFile));
|
||||
accessFile(sender, parser, mediaInfo, strFile, cb);
|
||||
accessFile(sender, parser, media_info, file_path, cb);
|
||||
return;
|
||||
}
|
||||
string strMenu;
|
||||
//生成文件夹菜单索引
|
||||
if (!makeFolderMenu(parser.Url(), strFile, strMenu)) {
|
||||
if (!makeFolderMenu(parser.Url(), file_path, strMenu)) {
|
||||
//文件夹不存在
|
||||
sendNotFound(cb);
|
||||
return;
|
||||
}
|
||||
//判断是否有权限访问该目录
|
||||
canAccessPath(sender, parser, mediaInfo, true, [strMenu, cb](const string &errMsg, const HttpServerCookie::Ptr &cookie) mutable{
|
||||
canAccessPath(sender, parser, media_info, true, [strMenu, cb](const string &errMsg, const HttpServerCookie::Ptr &cookie) mutable{
|
||||
if (!errMsg.empty()) {
|
||||
strMenu = errMsg;
|
||||
}
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
headerOut["Set-Cookie"] = cookie->getCookie(cookie->getAttach<HttpCookieAttachment>()._path);
|
||||
}
|
||||
cb(errMsg.empty() ? 200 : 401, "text/html", headerOut, std::make_shared<HttpStringBody>(strMenu));
|
||||
});
|
||||
@ -539,7 +533,7 @@ void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const Htt
|
||||
}
|
||||
|
||||
//访问的是文件
|
||||
accessFile(sender, parser, mediaInfo, strFile, cb);
|
||||
accessFile(sender, parser, media_info, file_path, cb);
|
||||
};
|
||||
|
||||
|
||||
@ -579,16 +573,19 @@ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::Htt
|
||||
|
||||
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
const StrCaseMap &responseHeader,
|
||||
const string &filePath,
|
||||
bool use_mmap) const {
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap &>(responseHeader);
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
const string &file,
|
||||
bool use_mmap,
|
||||
bool is_path) const {
|
||||
if (!is_path) {
|
||||
//file是文件内容
|
||||
(*this)(200, responseHeader, std::make_shared<HttpStringBody>(file));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!fp) {
|
||||
//file是文件路径
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap &>(responseHeader);
|
||||
auto fileBody = std::make_shared<HttpFileBody>(file, use_mmap);
|
||||
if (fileBody->remainSize() < 0) {
|
||||
//打开文件失败
|
||||
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
@ -600,29 +597,23 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
}
|
||||
|
||||
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
||||
size_t iRangeStart = 0;
|
||||
size_t iRangeEnd = 0;
|
||||
size_t fileSize = File::fileSize(fp.get());
|
||||
|
||||
int code;
|
||||
if (strRange.size() == 0) {
|
||||
//全部下载
|
||||
code = 200;
|
||||
iRangeEnd = fileSize - 1;
|
||||
} else {
|
||||
int code = 200;
|
||||
if (!strRange.empty()) {
|
||||
//分节下载
|
||||
code = 206;
|
||||
iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
|
||||
iRangeEnd = atoll(FindField(strRange.data(), "-", nullptr).data());
|
||||
auto iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
|
||||
auto iRangeEnd = atoll(FindField(strRange.data(), "-", nullptr).data());
|
||||
auto fileSize = fileBody->remainSize();
|
||||
if (iRangeEnd == 0) {
|
||||
iRangeEnd = fileSize - 1;
|
||||
}
|
||||
//设置文件范围
|
||||
fileBody->setRange(iRangeStart, iRangeEnd - iRangeStart + 1);
|
||||
//分节下载返回Content-Range头
|
||||
httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << fileSize << endl);
|
||||
}
|
||||
|
||||
//回复文件
|
||||
HttpBody::Ptr fileBody = std::make_shared<HttpFileBody>(fp, iRangeStart, iRangeEnd - iRangeStart + 1, use_mmap);
|
||||
(*this)(code, httpHeader, fileBody);
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
void operator()(int code, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const;
|
||||
void operator()(int code, const StrCaseMap &headerOut, const std::string &body) const;
|
||||
|
||||
void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const std::string &filePath, bool use_mmap = true) const;
|
||||
void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const std::string &file, bool use_mmap = true, bool is_path = true) const;
|
||||
operator bool();
|
||||
private:
|
||||
HttpResponseInvokerLambda0 _lambad;
|
||||
|
@ -519,7 +519,7 @@ void HttpSession::sendResponse(int code,
|
||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||
|
||||
//body默认为空
|
||||
ssize_t size = 0;
|
||||
int64_t size = 0;
|
||||
if (body && body->remainSize()) {
|
||||
//有body,获取body大小
|
||||
size = body->remainSize();
|
||||
@ -594,10 +594,13 @@ void HttpSession::sendResponse(int code,
|
||||
return;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//sendfile跟共享mmap相比并没有性能上的优势,相反,sendfile还有功能上的缺陷,先屏蔽
|
||||
if (typeid(*this) == typeid(HttpSession) && !body->sendFile(getSock()->rawFD())) {
|
||||
// http支持sendfile优化
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
|
||||
if (body->remainSize() > sendBufSize) {
|
||||
@ -607,9 +610,7 @@ void HttpSession::sendResponse(int code,
|
||||
|
||||
//发送http body
|
||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(), body, bClose);
|
||||
getSock()->setOnFlush([data]() {
|
||||
return AsyncSender::onSocketFlushed(data);
|
||||
});
|
||||
getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); });
|
||||
AsyncSender::onSocketFlushed(data);
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ TsPlayer::TsPlayer(const EventPoller::Ptr &poller) : HttpTSPlayer(poller, true)
|
||||
void TsPlayer::play(const string &url) {
|
||||
TraceL << "play http-ts: " << url;
|
||||
_play_result = false;
|
||||
_benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
|
||||
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
|
||||
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
|
||||
setMethod("GET");
|
||||
@ -45,7 +46,9 @@ void TsPlayer::onResponseBody(const char *buf, size_t size) {
|
||||
_play_result = true;
|
||||
onPlayResult(SockException(Err_success, "play http-ts success"));
|
||||
}
|
||||
if (!_benchmark_mode) {
|
||||
HttpTSPlayer::onResponseBody(buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediakit
|
@ -37,6 +37,7 @@ protected:
|
||||
|
||||
private:
|
||||
bool _play_result = true;
|
||||
bool _benchmark_mode = false;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
@ -33,7 +33,8 @@ void TsPlayerImp::addTrackCompleted() {
|
||||
}
|
||||
|
||||
void TsPlayerImp::onPlayResult(const SockException &ex) {
|
||||
if (ex) {
|
||||
auto benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
|
||||
if (ex || benchmark_mode) {
|
||||
PlayerImp<TsPlayer, PlayerBase>::onPlayResult(ex);
|
||||
} else {
|
||||
auto demuxer = std::make_shared<HlsDemuxer>();
|
||||
@ -48,6 +49,9 @@ void TsPlayerImp::onShutdown(const SockException &ex) {
|
||||
}
|
||||
|
||||
vector<Track::Ptr> TsPlayerImp::getTracks(bool ready) const {
|
||||
if (!_demuxer) {
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
return static_pointer_cast<HlsDemuxer>(_demuxer)->getTracks(ready);
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ void HlsMaker::makeIndexFile(bool eof) {
|
||||
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
|
||||
m3u8.append(file_content);
|
||||
}
|
||||
onWriteHls(m3u8.data(), m3u8.size());
|
||||
onWriteHls(m3u8);
|
||||
}
|
||||
|
||||
|
||||
|
@ -72,10 +72,8 @@ protected:
|
||||
|
||||
/**
|
||||
* 写m3u8文件回调
|
||||
* @param data
|
||||
* @param len
|
||||
*/
|
||||
virtual void onWriteHls(const char *data, size_t len) = 0;
|
||||
virtual void onWriteHls(const std::string &data) = 0;
|
||||
|
||||
/**
|
||||
* 上一个 ts 切片写入完成, 可在这里进行通知处理
|
||||
|
@ -111,13 +111,13 @@ void HlsMakerImp::onWriteSegment(const char *data, size_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMakerImp::onWriteHls(const char *data, size_t len) {
|
||||
void HlsMakerImp::onWriteHls(const std::string &data) {
|
||||
auto hls = makeFile(_path_hls);
|
||||
if (hls) {
|
||||
fwrite(data, len, 1, hls.get());
|
||||
fwrite(data.data(), data.size(), 1, hls.get());
|
||||
hls.reset();
|
||||
if (_media_src) {
|
||||
_media_src->registHls(true);
|
||||
_media_src->registHls(data);
|
||||
}
|
||||
} else {
|
||||
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
|
||||
|
@ -53,7 +53,7 @@ protected:
|
||||
std::string onOpenSegment(uint64_t index) override ;
|
||||
void onDelSegment(uint64_t index) override;
|
||||
void onWriteSegment(const char *data, size_t len) override;
|
||||
void onWriteHls(const char *data, size_t len) override;
|
||||
void onWriteHls(const std::string &data) override;
|
||||
void onFlushLastSegment(uint32_t duration_ms) override;
|
||||
|
||||
private:
|
||||
|
@ -39,8 +39,8 @@ void HlsCookieData::addReaderCount(){
|
||||
HlsCookieData::~HlsCookieData() {
|
||||
if (*_added) {
|
||||
uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000;
|
||||
WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port() << ") "
|
||||
<< "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid
|
||||
WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port()
|
||||
<< ") " << "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid
|
||||
<< ")断开,耗时(s):" << duration;
|
||||
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
@ -57,6 +57,12 @@ void HlsCookieData::addByteUsage(size_t bytes) {
|
||||
_ticker.resetTime();
|
||||
}
|
||||
|
||||
void HlsCookieData::setMediaSource(const HlsMediaSource::Ptr &src) {
|
||||
_src = src;
|
||||
}
|
||||
|
||||
HlsMediaSource::Ptr HlsCookieData::getMediaSource() const {
|
||||
return _src.lock();
|
||||
}
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
|
@ -11,9 +11,9 @@
|
||||
#ifndef ZLMEDIAKIT_HLSMEDIASOURCE_H
|
||||
#define ZLMEDIAKIT_HLSMEDIASOURCE_H
|
||||
|
||||
#include <atomic>
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include <atomic>
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
@ -24,28 +24,25 @@ public:
|
||||
using RingType = toolkit::RingBuffer<std::string>;
|
||||
using Ptr = std::shared_ptr<HlsMediaSource>;
|
||||
|
||||
HlsMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){}
|
||||
HlsMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id)
|
||||
: MediaSource(HLS_SCHEMA, vhost, app, stream_id) {}
|
||||
~HlsMediaSource() override = default;
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
*/
|
||||
const RingType::Ptr &getRing() const {
|
||||
return _ring;
|
||||
}
|
||||
const RingType::Ptr &getRing() const { return _ring; }
|
||||
|
||||
/**
|
||||
* 获取播放器个数
|
||||
*/
|
||||
int readerCount() override {
|
||||
return _ring ? _ring->readerCount() : 0;
|
||||
}
|
||||
int readerCount() override { return _ring ? _ring->readerCount() : 0; }
|
||||
|
||||
/**
|
||||
* 生成m3u8文件时触发
|
||||
* @param file_created 是否产生了hls文件
|
||||
* @param index_file m3u8文件内容
|
||||
*/
|
||||
void registHls(bool file_created){
|
||||
void registHls(std::string index_file) {
|
||||
if (!_is_regist) {
|
||||
_is_regist = true;
|
||||
std::weak_ptr<HlsMediaSource> weakSelf = std::dynamic_pointer_cast<HlsMediaSource>(shared_from_this());
|
||||
@ -61,44 +58,55 @@ public:
|
||||
regist();
|
||||
}
|
||||
|
||||
if (!file_created) {
|
||||
//没产生文件
|
||||
if (index_file.empty()) {
|
||||
//没产生索引文件, 只是为了触发媒体注册
|
||||
return;
|
||||
}
|
||||
//m3u8文件生成,发送给播放器
|
||||
decltype(_list_cb) copy;
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(_mtx_cb);
|
||||
copy.swap(_list_cb);
|
||||
}
|
||||
copy.for_each([](const std::function<void()> &cb) {
|
||||
cb();
|
||||
});
|
||||
|
||||
//赋值m3u8索引文件内容
|
||||
std::lock_guard<std::mutex> lck(_mtx_index);
|
||||
_index_file = std::move(index_file);
|
||||
|
||||
_list_cb.for_each([&](const std::function<void(const std::string &str)> &cb) { cb(_index_file); });
|
||||
_list_cb.clear();
|
||||
}
|
||||
|
||||
void waitForFile(std::function<void()> cb) {
|
||||
void getIndexFile(std::function<void(const std::string &str)> cb) {
|
||||
std::lock_guard<std::mutex> lck(_mtx_index);
|
||||
if (!_index_file.empty()) {
|
||||
cb(_index_file);
|
||||
return;
|
||||
}
|
||||
//等待生成m3u8文件
|
||||
std::lock_guard<std::mutex> lck(_mtx_cb);
|
||||
_list_cb.emplace_back(std::move(cb));
|
||||
}
|
||||
|
||||
void onSegmentSize(size_t bytes) {
|
||||
_speed[TrackVideo] += bytes;
|
||||
std::string getIndexFile() const {
|
||||
std::lock_guard<std::mutex> lck(_mtx_index);
|
||||
return _index_file;
|
||||
}
|
||||
|
||||
void onSegmentSize(size_t bytes) { _speed[TrackVideo] += bytes; }
|
||||
|
||||
private:
|
||||
bool _is_regist = false;
|
||||
RingType::Ptr _ring;
|
||||
std::mutex _mtx_cb;
|
||||
toolkit::List<std::function<void()> > _list_cb;
|
||||
|
||||
std::string _index_file;
|
||||
mutable std::mutex _mtx_index;
|
||||
toolkit::List<std::function<void(const std::string &)> > _list_cb;
|
||||
};
|
||||
|
||||
class HlsCookieData {
|
||||
public:
|
||||
typedef std::shared_ptr<HlsCookieData> Ptr;
|
||||
using Ptr = std::shared_ptr<HlsCookieData>;
|
||||
|
||||
HlsCookieData(const MediaInfo &info, const std::shared_ptr<toolkit::SockInfo> &sock_info);
|
||||
~HlsCookieData();
|
||||
|
||||
void addByteUsage(size_t bytes);
|
||||
void setMediaSource(const HlsMediaSource::Ptr &src);
|
||||
HlsMediaSource::Ptr getMediaSource() const;
|
||||
|
||||
private:
|
||||
void addReaderCount();
|
||||
@ -108,6 +116,7 @@ private:
|
||||
MediaInfo _info;
|
||||
std::shared_ptr<bool> _added;
|
||||
toolkit::Ticker _ticker;
|
||||
std::weak_ptr<HlsMediaSource> _src;
|
||||
std::shared_ptr<toolkit::SockInfo> _sock_info;
|
||||
HlsMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||
};
|
||||
|
@ -39,7 +39,7 @@ public:
|
||||
setDelegate(listener);
|
||||
_hls->getMediaSource()->setListener(shared_from_this());
|
||||
//先注册媒体流,后续可以按需生成
|
||||
_hls->getMediaSource()->registHls(false);
|
||||
_hls->getMediaSource()->registHls("");
|
||||
}
|
||||
|
||||
int readerCount() {
|
||||
|
Loading…
Reference in New Issue
Block a user