mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-26 20:47:08 +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:
|
||||
typedef StrCaseMap HttpHeader;
|
||||
typedef std::shared_ptr<HttpClient> Ptr;
|
||||
|
@ -13,9 +13,7 @@
|
||||
|
||||
#include "HttpClient.h"
|
||||
#include "Util/SSLBox.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HttpClientImp: public TcpClientWithSSL<HttpClient> {
|
||||
@ -28,5 +26,4 @@ protected:
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#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 "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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user