From a4aa34e4aea745ae8d12dd3e7c56da52cba8690c Mon Sep 17 00:00:00 2001 From: xiongziliang <771730766@qq.com> Date: Sun, 17 May 2020 18:00:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E6=B7=BB=E5=8A=A0hls?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Http/HlsParser.cpp | 139 ++++++++++++++++++++ src/Http/HlsParser.h | 95 ++++++++++++++ src/Http/HlsPlayer.cpp | 265 ++++++++++++++++++++++++++++++++++++++ src/Http/HlsPlayer.h | 144 +++++++++++++++++++++ src/Http/HttpClient.h | 3 +- src/Http/HttpClientImp.h | 3 - src/Http/HttpTSPlayer.cpp | 81 ++++++++++++ src/Http/HttpTSPlayer.h | 57 ++++++++ src/Player/PlayerBase.cpp | 11 ++ 9 files changed, 793 insertions(+), 5 deletions(-) create mode 100644 src/Http/HlsParser.cpp create mode 100644 src/Http/HlsParser.h create mode 100644 src/Http/HlsPlayer.cpp create mode 100644 src/Http/HlsPlayer.h create mode 100644 src/Http/HttpTSPlayer.cpp create mode 100644 src/Http/HttpTSPlayer.h diff --git a/src/Http/HlsParser.cpp b/src/Http/HlsParser.cpp new file mode 100644 index 00000000..613dcb59 --- /dev/null +++ b/src/Http/HlsParser.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "HlsParser.h" +#include "Util/util.h" +#include "Common/Parser.h" +using namespace toolkit; +namespace mediakit { + +bool HlsParser::parse(const string &http_url, const string &m3u8) { + float extinf_dur = 0; + ts_segment segment; + map ts_map; + _total_dur = 0; + _is_live = true; + _is_m3u8_inner = false; + int index = 0; + + auto lines = split(m3u8, "\n"); + for (auto &line : lines) { + trim(line); + if (line.size() < 2) { + continue; + } + + if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') { + segment.duration = extinf_dur; + if (line.find("http://") == 0 || line.find("https://") == 0) { + segment.url = line; + } else { + if (line.find("/") == 0) { + segment.url = http_url.substr(0, http_url.find("/", 8)) + line; + } else { + segment.url = http_url.substr(0, http_url.rfind("/") + 1) + line; + } + } + if (!_is_m3u8_inner) { + //ts按照先后顺序排序 + ts_map.emplace(index++, segment); + } else { + //子m3u8按照带宽排序 + ts_map.emplace(segment.bandwidth, segment); + } + extinf_dur = 0; + continue; + } + + _is_m3u8_inner = false; + if (line.find("#EXTINF:") == 0) { + sscanf(line.data(), "#EXTINF:%f,", &extinf_dur); + _total_dur += extinf_dur; + continue; + } + static const string s_stream_inf = "#EXT-X-STREAM-INF:"; + if (line.find(s_stream_inf) == 0) { + _is_m3u8_inner = true; + auto key_val = Parser::parseArgs(line.substr(s_stream_inf.size()), ",", "="); + segment.program_id = atoi(key_val["PROGRAM-ID"].data()); + segment.bandwidth = atoi(key_val["BANDWIDTH"].data()); + sscanf(key_val["RESOLUTION"].data(), "%dx%d", &segment.width, &segment.height); + continue; + } + + if (line == "#EXTM3U") { + _is_m3u8 = true; + continue; + } + + if (line.find("#EXT-X-ALLOW-CACHE:") == 0) { + _allow_cache = (line.find(":YES") != string::npos); + continue; + } + + if (line.find("#EXT-X-VERSION:") == 0) { + sscanf(line.data(), "#EXT-X-VERSION:%d", &_version); + continue; + } + + if (line.find("#EXT-X-TARGETDURATION:") == 0) { + sscanf(line.data(), "#EXT-X-TARGETDURATION:%d", &_target_dur); + continue; + } + + if (line.find("#EXT-X-MEDIA-SEQUENCE:") == 0) { + sscanf(line.data(), "#EXT-X-MEDIA-SEQUENCE:%lld", &_sequence); + continue; + } + + if (line.find("#EXT-X-ENDLIST") == 0) { + //点播 + _is_live = false; + continue; + } + continue; + } + + if (_is_m3u8) { + onParsed(_is_m3u8_inner, _sequence, ts_map); + } + return _is_m3u8; +} + +bool HlsParser::isM3u8() const { + return _is_m3u8; +} + +bool HlsParser::isLive() const{ + return _is_live; +} + +bool HlsParser::allowCache() const { + return _allow_cache; +} + +int HlsParser::getVersion() const { + return _version; +} + +int HlsParser::getTargetDur() const { + return _target_dur; +} + +int HlsParser::getSequence() const { + return _sequence; +} + +bool HlsParser::isM3u8Inner() const { + return _is_m3u8_inner; +} + +}//namespace mediakit \ No newline at end of file diff --git a/src/Http/HlsParser.h b/src/Http/HlsParser.h new file mode 100644 index 00000000..f33b46d0 --- /dev/null +++ b/src/Http/HlsParser.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef HTTP_HLSPARSER_H +#define HTTP_HLSPARSER_H + +#include +#include +#include +using namespace std; +namespace mediakit { + +typedef struct{ + //url地址 + string url; + //ts切片长度 + float duration; + + //////内嵌m3u8////// + //节目id + int program_id; + //带宽 + int bandwidth; + //宽度 + int width; + //高度 + int height; +} ts_segment; + +class HlsParser { +public: + HlsParser(){} + ~HlsParser(){} + bool parse(const string &http_url,const string &m3u8); + + /** + * 是否存在#EXTM3U字段,是否为m3u8文件 + */ + bool isM3u8() const; + + /** + * #EXT-X-ALLOW-CACHE值,是否允许cache + */ + bool allowCache() const; + + /** + * 是否存在#EXT-X-ENDLIST字段,是否为直播 + */ + bool isLive() const ; + + /** + * #EXT-X-VERSION值,版本号 + */ + int getVersion() const; + + /** + * #EXT-X-TARGETDURATION字段值 + */ + int getTargetDur() const; + + /** + * #EXT-X-MEDIA-SEQUENCE字段值,该m3u8序号 + */ + int getSequence() const; + + /** + * 内部是否含有子m3u8 + */ + bool isM3u8Inner() const; + +protected: + //解析出ts文件地址回调 + virtual void onParsed(bool is_m3u8_inner,int64_t sequence,const map &ts_list) {}; + +private: + bool _is_m3u8 = false; + bool _allow_cache = false; + bool _is_live = true; + int _version = 0; + int _target_dur = 0; + float _total_dur = 0; + int64_t _sequence = 0; + //每部是否有m3u8 + bool _is_m3u8_inner = false; +}; + +}//namespace mediakit +#endif //HTTP_HLSPARSER_H diff --git a/src/Http/HlsPlayer.cpp b/src/Http/HlsPlayer.cpp new file mode 100644 index 00000000..16499084 --- /dev/null +++ b/src/Http/HlsPlayer.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "HlsPlayer.h" +namespace mediakit { + +HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller){ + _segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); }); + _poller = poller ? poller : EventPollerPool::Instance().getPoller(); +} + +HlsPlayer::~HlsPlayer() {} + +void HlsPlayer::play(const string &strUrl) { + _m3u8_list.emplace_back(strUrl); + play_l(); +} + +void HlsPlayer::play_l(){ + if (_m3u8_list.empty()) { + teardown_l(SockException(Err_shutdown, "所有hls url都尝试播放失败!")); + return; + } + float playTimeOutSec = (*this)[Client::kTimeoutMS].as() / 1000.0; + setMethod("GET"); + if(!(*this)[kNetAdapter].empty()) { + setNetAdapter((*this)[kNetAdapter]); + } + sendRequest(_m3u8_list.back(), playTimeOutSec); +} + +void HlsPlayer::teardown_l(const SockException &ex){ + _timer.reset(); + _timer_ts.reset(); + _http_ts_player.reset(); + shutdown(ex); +} + +void HlsPlayer::teardown() { + teardown_l(SockException(Err_shutdown,"teardown")); +} + +void HlsPlayer::playNextTs(bool force){ + if (_ts_list.empty()) { + //播放列表为空,那么立即重新下载m3u8文件 + _timer.reset(); + play_l(); + return; + } + if (!force && _http_ts_player && _http_ts_player->alive()) { + //播放器目前还存活,正在下载中 + return; + } + auto ts_duration = _ts_list.front().duration * 1000; + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + std::shared_ptr ticker(new Ticker); + + _http_ts_player = std::make_shared(getPoller(), false); + _http_ts_player->setOnDisconnect([weakSelf, ticker, ts_duration](const SockException &err) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + auto delay = ts_duration - 500 - ticker->elapsedTime(); + if (delay <= 0) { + //播放这个ts切片花费了太长时间,我们立即下一个切片的播放 + strongSelf->playNextTs(true); + } else { + //下一个切片慢点播放 + strongSelf->_timer_ts.reset(new Timer(delay / 1000.0, [weakSelf, delay]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + strongSelf->playNextTs(true); + return false; + }, strongSelf->getPoller())); + } + }); + _http_ts_player->setOnPacket([weakSelf](const char *data, uint64_t len) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + //收到ts包 + strongSelf->onPacket_l(data, len); + }); + + _http_ts_player->setMethod("GET"); + if(!(*this)[kNetAdapter].empty()) { + _http_ts_player->setNetAdapter((*this)[Client::kNetAdapter]); + } + _http_ts_player->sendRequest(_ts_list.front().url, 2 * _ts_list.front().duration); + _ts_list.pop_front(); +} + +void HlsPlayer::onParsed(bool is_m3u8_inner,int64_t sequence,const map &ts_map){ + if (!is_m3u8_inner) { + //这是ts播放列表 + if (_last_sequence == sequence) { + return; + } + _last_sequence = sequence; + for (auto &pr : ts_map) { + auto &ts = pr.second; + if (_ts_url_cache.emplace(ts.url).second) { + //该ts未重复 + _ts_list.emplace_back(ts); + //按时间排序 + _ts_url_sort.emplace_back(ts.url); + } + } + if (_ts_url_sort.size() > 2 * ts_map.size()) { + //去除防重列表中过多的数据 + _ts_url_cache.erase(_ts_url_sort.front()); + _ts_url_sort.pop_front(); + } + playNextTs(); + } else { + //这是m3u8列表,我们播放最高清的子hls + if (ts_map.empty()) { + teardown_l(SockException(Err_shutdown, StrPrinter << "empty sub hls list:" + getUrl())); + return; + } + _timer.reset(); + + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + auto url = ts_map.rbegin()->second.url; + getPoller()->async([weakSelf, url]() { + auto strongSelf = weakSelf.lock(); + if (strongSelf) { + strongSelf->play(url); + } + }, false); + } +} + +int64_t HlsPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &headers) { + if (status != "200" && status != "206") { + //失败 + teardown_l(SockException(Err_shutdown, StrPrinter << "bad http status code:" + status)); + return 0; + } + auto contet_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"]; + _is_m3u8 = (contet_type.find("application/vnd.apple.mpegurl") == 0); + return -1; +} + +void HlsPlayer::onResponseBody(const char *buf, int64_t size, int64_t recvedSize, int64_t totalSize) { + if (recvedSize == size) { + //刚开始 + _m3u8.clear(); + } + _m3u8.append(buf, size); +} + +void HlsPlayer::onResponseCompleted() { + if (HlsParser::parse(getUrl(), _m3u8)) { + playDelay(); + if (_first) { + _first = false; + onPlayResult(SockException(Err_success, "play success")); + } + } else { + teardown_l(SockException(Err_shutdown, "解析m3u8文件失败")); + } +} + +float HlsPlayer::delaySecond(){ + if (HlsParser::isM3u8() && HlsParser::getTargetDur() > 0) { + return HlsParser::getTargetDur(); + } + return 1; +} + +void HlsPlayer::onDisconnect(const SockException &ex) { + if (_first) { + //第一次失败,则播放失败 + _first = false; + onPlayResult(ex); + return; + } + + //主动shutdown + if (ex.getErrCode() == Err_shutdown) { + if (_m3u8_list.size() <= 1) { + //全部url都播放失败 + onShutdown(ex); + } else { + _m3u8_list.pop_back(); + //还有上一级url可以重试播放 + play_l(); + } + return; + } + + //eof等,之后播放失败,那么重试播放m3u8 + playDelay(); +} + +bool HlsPlayer::onRedirectUrl(const string &url,bool temporary) { + _m3u8_list.emplace_back(url); + return true; +} + +void HlsPlayer::playDelay(){ + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + _timer.reset(new Timer(delaySecond(), [weakSelf]() { + auto strongSelf = weakSelf.lock(); + if (strongSelf) { + strongSelf->play_l(); + } + return false; + }, getPoller())); +} + +void HlsPlayer::onPacket_l(const char *data, uint64_t len){ + _segment.input(data,len); +} + +////////////////////////////////////////////////////////////////////////// + +HlsPlayerImp::HlsPlayerImp(const EventPoller::Ptr &poller) : PlayerImp(poller) { + +} + +void HlsPlayerImp::setOnPacket(const TSSegment::onSegment &cb){ + _on_ts = cb; +} + +void HlsPlayerImp::onPacket(const char *data,uint64_t len) { + if (_on_ts) { + _on_ts(data, len); + } + + if (!_decoder) { + _decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, dynamic_pointer_cast(shared_from_this())); + } + + if (_decoder) { + _decoder->input((uint8_t *) data, len); + } +} + +void HlsPlayerImp::onAllTrackReady() { + PlayerImp::onPlayResult(SockException(Err_success,"play hls success")); +} + +void HlsPlayerImp::onPlayResult(const SockException &ex) { + +} + +vector HlsPlayerImp::getTracks(bool trackReady) const { + return MediaSink::getTracks(trackReady); +} + + +}//namespace mediakit \ No newline at end of file diff --git a/src/Http/HlsPlayer.h b/src/Http/HlsPlayer.h new file mode 100644 index 00000000..d9cf9766 --- /dev/null +++ b/src/Http/HlsPlayer.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef HTTP_HLSPLAYER_H +#define HTTP_HLSPLAYER_H + +#include +#include "Util/util.h" +#include "Poller/Timer.h" +#include "Http/HttpDownloader.h" +#include "Player/MediaPlayer.h" +#include "HlsParser.h" +#include "HttpTSPlayer.h" +#include "Rtp/Decoder.h" +#include "Rtp/TSDecoder.h" + +using namespace toolkit; +namespace mediakit { + +class HlsPlayer : public HttpClientImp , public PlayerBase , public HlsParser{ +public: + HlsPlayer(const EventPoller::Ptr &poller); + ~HlsPlayer() override; + + /** + * 开始播放 + * @param strUrl + */ + void play(const string &strUrl) override; + + /** + * 停止播放 + */ + void teardown() override; + +protected: + /** + * 收到ts包 + * @param data ts数据负载 + * @param len ts包长度 + */ + virtual void onPacket(const char *data, uint64_t len) = 0; + +private: + /** + * 解析m3u8成功 + * @param is_m3u8_inner 是否为m3u8列表 + * @param sequence ts列表seq + * @param ts_map ts列表或m3u8列表 + */ + void onParsed(bool is_m3u8_inner,int64_t sequence,const map &ts_map) override; + /** + * 收到http回复头 + * @param status 状态码,譬如:200 OK + * @param headers http头 + * @return 返回后续content的长度;-1:后续数据全是content;>=0:固定长度content + * 需要指出的是,在http头中带有Content-Length字段时,该返回值无效 + */ + int64_t onResponseHeader(const string &status,const HttpHeader &headers) override; + /** + * 收到http conten数据 + * @param buf 数据指针 + * @param size 数据大小 + * @param recvedSize 已收数据大小(包含本次数据大小),当其等于totalSize时将触发onResponseCompleted回调 + * @param totalSize 总数据大小 + */ + void onResponseBody(const char *buf,int64_t size,int64_t recvedSize,int64_t totalSize) override; + + /** + * 接收http回复完毕, + */ + void onResponseCompleted() override; + + /** + * http链接断开回调 + * @param ex 断开原因 + */ + void onDisconnect(const SockException &ex) override; + + /** + * 重定向事件 + * @param url 重定向url + * @param temporary 是否为临时重定向 + * @return 是否继续 + */ + bool onRedirectUrl(const string &url,bool temporary) override; + +private: + void playDelay(); + float delaySecond(); + void playNextTs(bool force = false); + void teardown_l(const SockException &ex); + void play_l(); + void onPacket_l(const char *data, uint64_t len); + +private: + struct UrlComp { + //url忽略?后面的参数 + bool operator()(const string& __x, const string& __y) const { + return split(__x,"?")[0] < split(__y,"?")[0]; + } + }; + +private: + bool _is_m3u8 = false; + bool _first = true; + int64_t _last_sequence = -1; + string _m3u8; + Timer::Ptr _timer; + Timer::Ptr _timer_ts; + list _ts_list; + list _ts_url_sort; + list _m3u8_list; + set _ts_url_cache; + HttpTSPlayer::Ptr _http_ts_player; + TSSegment _segment; +}; + +class HlsPlayerImp : public PlayerImp , public MediaSink{ +public: + typedef std::shared_ptr Ptr; + HlsPlayerImp(const EventPoller::Ptr &poller = nullptr); + ~HlsPlayerImp() override {}; + void setOnPacket(const TSSegment::onSegment &cb); + +private: + void onPacket(const char *data, uint64_t len) override; + void onAllTrackReady() override; + void onPlayResult(const SockException &ex) override; + vector getTracks(bool trackReady = true) const override; +private: + TSSegment::onSegment _on_ts; + DecoderImp::Ptr _decoder; +}; + +}//namespace mediakit +#endif //HTTP_HLSPLAYER_H diff --git a/src/Http/HttpClient.h b/src/Http/HttpClient.h index f78934d2..e69e9694 100644 --- a/src/Http/HttpClient.h +++ b/src/Http/HttpClient.h @@ -48,8 +48,7 @@ public: } }; -class HttpClient : public TcpClient , public HttpRequestSplitter -{ +class HttpClient : public TcpClient , public HttpRequestSplitter{ public: typedef StrCaseMap HttpHeader; typedef std::shared_ptr Ptr; diff --git a/src/Http/HttpClientImp.h b/src/Http/HttpClientImp.h index 392770c3..25093f1b 100644 --- a/src/Http/HttpClientImp.h +++ b/src/Http/HttpClientImp.h @@ -13,9 +13,7 @@ #include "HttpClient.h" #include "Util/SSLBox.h" - using namespace toolkit; - namespace mediakit { class HttpClientImp: public TcpClientWithSSL { @@ -28,5 +26,4 @@ protected: }; } /* namespace mediakit */ - #endif /* SRC_HTTP_HTTPCLIENTIMP_H_ */ diff --git a/src/Http/HttpTSPlayer.cpp b/src/Http/HttpTSPlayer.cpp new file mode 100644 index 00000000..5c02c174 --- /dev/null +++ b/src/Http/HttpTSPlayer.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "HttpTSPlayer.h" +namespace mediakit { + +HttpTSPlayer::HttpTSPlayer(const EventPoller::Ptr &poller, bool split_ts){ + _segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); }); + _poller = poller ? poller : EventPollerPool::Instance().getPoller(); + _split_ts = split_ts; +} + +HttpTSPlayer::~HttpTSPlayer() {} + +int64_t HttpTSPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &headers) { + if (status != "200" && status != "206") { + //http状态码不符合预期 + shutdown(SockException(Err_other, StrPrinter << "bad http status code:" + status)); + return 0; + } + auto contet_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"]; + if (contet_type.find("video/mp2t") == 0 || contet_type.find("video/mpeg") == 0) { + _is_ts_content = true; + } + + //后续是不定长content + return -1; +} + +void HttpTSPlayer::onResponseBody(const char *buf, int64_t size, int64_t recvedSize, int64_t totalSize) { + if (recvedSize == size) { + //开始接收数据 + if (buf[0] == TS_SYNC_BYTE) { + //这是ts头 + _is_first_packet_ts = true; + } else { + WarnL << "可能不是http-ts流"; + } + } + + if (_split_ts) { + _segment.input(buf, size); + } else { + onPacket(buf, size); + } +} + +void HttpTSPlayer::onResponseCompleted() { + //接收完毕 + shutdown(SockException(Err_success, "play completed")); +} + +void HttpTSPlayer::onDisconnect(const SockException &ex) { + if (_on_disconnect) { + _on_disconnect(ex); + _on_disconnect = nullptr; + } +} + +void HttpTSPlayer::onPacket(const char *data, uint64_t len) { + if (_on_segment) { + _on_segment(data, len); + } +} + +void HttpTSPlayer::setOnDisconnect(const HttpTSPlayer::onShutdown &cb) { + _on_disconnect = cb; +} + +void HttpTSPlayer::setOnPacket(const TSSegment::onSegment &cb) { + _on_segment = cb; +} + +}//namespace mediakit \ No newline at end of file diff --git a/src/Http/HttpTSPlayer.h b/src/Http/HttpTSPlayer.h new file mode 100644 index 00000000..ec1aa129 --- /dev/null +++ b/src/Http/HttpTSPlayer.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef HTTP_HTTPTSPLAYER_H +#define HTTP_HTTPTSPLAYER_H + +#include "Http/HttpDownloader.h" +#include "Player/MediaPlayer.h" +#include "Rtp/TSDecoder.h" +using namespace toolkit; +namespace mediakit { + +//http-ts播发器,未实现ts解复用 +class HttpTSPlayer : public HttpClientImp{ +public: + typedef function onShutdown; + typedef std::shared_ptr Ptr; + + HttpTSPlayer(const EventPoller::Ptr &poller = nullptr, bool split_ts = true); + ~HttpTSPlayer() override ; + + //设置异常断开回调 + void setOnDisconnect(const onShutdown &cb); + //设置接收ts包回调 + void setOnPacket(const TSSegment::onSegment &cb); + +protected: + ///HttpClient override/// + int64_t onResponseHeader(const string &status,const HttpHeader &headers) override; + void onResponseBody(const char *buf,int64_t size,int64_t recvedSize,int64_t totalSize) override; + void onResponseCompleted() override; + void onDisconnect(const SockException &ex) override ; + + //收到ts包 + virtual void onPacket(const char *data, uint64_t len); + +private: + //是否为mpegts负载 + bool _is_ts_content = false; + //第一个包是否为ts包 + bool _is_first_packet_ts = false; + //是否判断是否是ts并split + bool _split_ts; + TSSegment _segment; + onShutdown _on_disconnect; + TSSegment::onSegment _on_segment; +}; + +}//namespace mediakit +#endif //HTTP_HTTPTSPLAYER_H diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp index 26c9f788..6e3f1c53 100644 --- a/src/Player/PlayerBase.cpp +++ b/src/Player/PlayerBase.cpp @@ -12,10 +12,17 @@ #include "PlayerBase.h" #include "Rtsp/RtspPlayerImp.h" #include "Rtmp/RtmpPlayerImp.h" +#include "Http/HlsPlayer.h" using namespace toolkit; namespace mediakit { +//字符串是否以xx结尾 +static bool end_of(const string &str, const string &substr){ + auto pos = str.rfind(substr); + return pos != string::npos && pos == str.size() - substr.size(); +} + PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller,const string &strUrl) { static auto releasePlayer = [](PlayerBase *ptr){ onceToken token(nullptr,[&](){ @@ -41,6 +48,10 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller,const st return PlayerBase::Ptr(new RtmpPlayerImp(poller),releasePlayer); } + if (strcasecmp("http",prefix.data()) == 0 && end_of(strUrl, ".m3u8")) { + return PlayerBase::Ptr(new HlsPlayerImp(poller),releasePlayer); + } + return PlayerBase::Ptr(new RtspPlayerImp(poller),releasePlayer); }