mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-27 05:38:31 +08:00
初步添加hls播放器
This commit is contained in:
parent
198f223d63
commit
a4aa34e4ae
139
src/Http/HlsParser.cpp
Normal file
139
src/Http/HlsParser.cpp
Normal file
@ -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 <cstdlib>
|
||||||
|
#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<int, ts_segment> 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
|
95
src/Http/HlsParser.h
Normal file
95
src/Http/HlsParser.h
Normal file
@ -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 <string>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
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<int,ts_segment> &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
|
265
src/Http/HlsPlayer.cpp
Normal file
265
src/Http/HlsPlayer.cpp
Normal file
@ -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<int>() / 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<HlsPlayer> weakSelf = dynamic_pointer_cast<HlsPlayer>(shared_from_this());
|
||||||
|
std::shared_ptr<Ticker> ticker(new Ticker);
|
||||||
|
|
||||||
|
_http_ts_player = std::make_shared<HttpTSPlayer>(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<int,ts_segment> &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<HlsPlayer> weakSelf = dynamic_pointer_cast<HlsPlayer>(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<HlsPlayer> weakSelf = dynamic_pointer_cast<HlsPlayer>(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<HlsPlayer, PlayerBase>(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<HlsPlayerImp>(shared_from_this()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_decoder) {
|
||||||
|
_decoder->input((uint8_t *) data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HlsPlayerImp::onAllTrackReady() {
|
||||||
|
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(SockException(Err_success,"play hls success"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HlsPlayerImp::onPlayResult(const SockException &ex) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<Track::Ptr> HlsPlayerImp::getTracks(bool trackReady) const {
|
||||||
|
return MediaSink::getTracks(trackReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}//namespace mediakit
|
144
src/Http/HlsPlayer.h
Normal file
144
src/Http/HlsPlayer.h
Normal file
@ -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 <unordered_set>
|
||||||
|
#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<int,ts_segment> &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_segment> _ts_list;
|
||||||
|
list<string> _ts_url_sort;
|
||||||
|
list<string> _m3u8_list;
|
||||||
|
set<string, UrlComp> _ts_url_cache;
|
||||||
|
HttpTSPlayer::Ptr _http_ts_player;
|
||||||
|
TSSegment _segment;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HlsPlayerImp : public PlayerImp<HlsPlayer, PlayerBase> , public MediaSink{
|
||||||
|
public:
|
||||||
|
typedef std::shared_ptr<HlsPlayerImp> 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<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||||
|
private:
|
||||||
|
TSSegment::onSegment _on_ts;
|
||||||
|
DecoderImp::Ptr _decoder;
|
||||||
|
};
|
||||||
|
|
||||||
|
}//namespace mediakit
|
||||||
|
#endif //HTTP_HLSPLAYER_H
|
@ -48,8 +48,7 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpClient : public TcpClient , public HttpRequestSplitter
|
class HttpClient : public TcpClient , public HttpRequestSplitter{
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
typedef StrCaseMap HttpHeader;
|
typedef StrCaseMap HttpHeader;
|
||||||
typedef std::shared_ptr<HttpClient> Ptr;
|
typedef std::shared_ptr<HttpClient> Ptr;
|
||||||
|
@ -13,9 +13,7 @@
|
|||||||
|
|
||||||
#include "HttpClient.h"
|
#include "HttpClient.h"
|
||||||
#include "Util/SSLBox.h"
|
#include "Util/SSLBox.h"
|
||||||
|
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class HttpClientImp: public TcpClientWithSSL<HttpClient> {
|
class HttpClientImp: public TcpClientWithSSL<HttpClient> {
|
||||||
@ -28,5 +26,4 @@ protected:
|
|||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
|
||||||
#endif /* SRC_HTTP_HTTPCLIENTIMP_H_ */
|
#endif /* SRC_HTTP_HTTPCLIENTIMP_H_ */
|
||||||
|
81
src/Http/HttpTSPlayer.cpp
Normal file
81
src/Http/HttpTSPlayer.cpp
Normal file
@ -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
|
57
src/Http/HttpTSPlayer.h
Normal file
57
src/Http/HttpTSPlayer.h
Normal file
@ -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<void(const SockException &)> onShutdown;
|
||||||
|
typedef std::shared_ptr<HttpTSPlayer> 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
|
@ -12,10 +12,17 @@
|
|||||||
#include "PlayerBase.h"
|
#include "PlayerBase.h"
|
||||||
#include "Rtsp/RtspPlayerImp.h"
|
#include "Rtsp/RtspPlayerImp.h"
|
||||||
#include "Rtmp/RtmpPlayerImp.h"
|
#include "Rtmp/RtmpPlayerImp.h"
|
||||||
|
#include "Http/HlsPlayer.h"
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
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) {
|
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller,const string &strUrl) {
|
||||||
static auto releasePlayer = [](PlayerBase *ptr){
|
static auto releasePlayer = [](PlayerBase *ptr){
|
||||||
onceToken token(nullptr,[&](){
|
onceToken token(nullptr,[&](){
|
||||||
@ -41,6 +48,10 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller,const st
|
|||||||
return PlayerBase::Ptr(new RtmpPlayerImp(poller),releasePlayer);
|
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);
|
return PlayerBase::Ptr(new RtspPlayerImp(poller),releasePlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user