2017-10-09 22:11:01 +08:00
|
|
|
|
/*
|
2017-09-27 16:20:30 +08:00
|
|
|
|
* MIT License
|
2017-04-01 16:35:56 +08:00
|
|
|
|
*
|
2019-05-08 15:40:07 +08:00
|
|
|
|
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
2017-09-27 16:20:30 +08:00
|
|
|
|
*
|
|
|
|
|
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
* copies or substantial portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
* SOFTWARE.
|
2017-04-01 16:35:56 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "RtmpPlayer.h"
|
2017-04-25 11:35:41 +08:00
|
|
|
|
#include "Rtmp/utils.h"
|
2017-04-01 16:35:56 +08:00
|
|
|
|
#include "Util/util.h"
|
|
|
|
|
#include "Util/onceToken.h"
|
2017-04-25 11:35:41 +08:00
|
|
|
|
#include "Thread/ThreadPool.h"
|
2018-10-24 17:17:55 +08:00
|
|
|
|
using namespace toolkit;
|
2019-03-27 18:56:49 +08:00
|
|
|
|
using namespace mediakit::Client;
|
2017-04-25 11:35:41 +08:00
|
|
|
|
|
2018-10-24 17:17:55 +08:00
|
|
|
|
namespace mediakit {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
|
|
|
|
unordered_map<string, RtmpPlayer::rtmpCMDHandle> RtmpPlayer::g_mapCmd;
|
2019-04-01 10:16:15 +08:00
|
|
|
|
RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
static onceToken token([]() {
|
|
|
|
|
g_mapCmd.emplace("_error",&RtmpPlayer::onCmd_result);
|
|
|
|
|
g_mapCmd.emplace("_result",&RtmpPlayer::onCmd_result);
|
|
|
|
|
g_mapCmd.emplace("onStatus",&RtmpPlayer::onCmd_onStatus);
|
|
|
|
|
g_mapCmd.emplace("onMetaData",&RtmpPlayer::onCmd_onMetaData);
|
|
|
|
|
}, []() {});
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RtmpPlayer::~RtmpPlayer() {
|
|
|
|
|
DebugL << endl;
|
|
|
|
|
}
|
|
|
|
|
void RtmpPlayer::teardown() {
|
|
|
|
|
if (alive()) {
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_strApp.clear();
|
|
|
|
|
_strStream.clear();
|
|
|
|
|
_strTcUrl.clear();
|
2017-05-11 11:06:34 +08:00
|
|
|
|
|
|
|
|
|
{
|
2018-10-24 15:43:52 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
|
|
|
|
_mapOnResultCB.clear();
|
2017-05-11 11:06:34 +08:00
|
|
|
|
}
|
2017-04-01 16:35:56 +08:00
|
|
|
|
{
|
2018-10-24 15:43:52 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
|
|
|
|
_dqOnStatusCB.clear();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_pBeatTimer.reset();
|
|
|
|
|
_pPlayTimer.reset();
|
|
|
|
|
_pMediaTimer.reset();
|
2018-10-26 14:12:16 +08:00
|
|
|
|
_iSeekTo = 0;
|
|
|
|
|
CLEAR_ARR(_aiFistStamp);
|
|
|
|
|
CLEAR_ARR(_aiNowStamp);
|
2017-08-03 13:55:46 +08:00
|
|
|
|
reset();
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,"teardown"));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-27 18:41:52 +08:00
|
|
|
|
void RtmpPlayer::play(const string &strUrl) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
teardown();
|
2019-03-27 18:41:52 +08:00
|
|
|
|
string strHost = FindField(strUrl.data(), "://", "/");
|
|
|
|
|
_strApp = FindField(strUrl.data(), (strHost + "/").data(), "/");
|
|
|
|
|
_strStream = FindField(strUrl.data(), (strHost + "/" + _strApp + "/").data(), NULL);
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_strTcUrl = string("rtmp://") + strHost + "/" + _strApp;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
2018-10-24 15:43:52 +08:00
|
|
|
|
if (!_strApp.size() || !_strStream.size()) {
|
2019-03-01 14:23:28 +08:00
|
|
|
|
onPlayResult_l(SockException(Err_other,"rtmp url非法"));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2018-10-24 15:43:52 +08:00
|
|
|
|
DebugL << strHost << " " << _strApp << " " << _strStream;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
2019-03-27 18:41:52 +08:00
|
|
|
|
auto iPort = atoi(FindField(strHost.data(), ":", NULL).data());
|
2017-04-01 16:35:56 +08:00
|
|
|
|
if (iPort <= 0) {
|
|
|
|
|
//rtmp 默认端口1935
|
|
|
|
|
iPort = 1935;
|
|
|
|
|
} else {
|
|
|
|
|
//服务器域名
|
2019-03-27 18:41:52 +08:00
|
|
|
|
strHost = FindField(strHost.data(), NULL, ":");
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2019-03-27 18:56:49 +08:00
|
|
|
|
if(!(*this)[kNetAdapter].empty()){
|
|
|
|
|
setNetAdapter((*this)[kNetAdapter]);
|
2018-05-21 23:18:17 +08:00
|
|
|
|
}
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
|
|
|
|
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
2019-03-27 18:56:49 +08:00
|
|
|
|
float playTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
2019-03-01 14:23:28 +08:00
|
|
|
|
_pPlayTimer.reset( new Timer(playTimeOutSec, [weakSelf]() {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
auto strongSelf=weakSelf.lock();
|
|
|
|
|
if(!strongSelf) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-03-01 14:23:28 +08:00
|
|
|
|
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtmp timeout"));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
return false;
|
2019-01-30 17:00:28 +08:00
|
|
|
|
},getPoller()));
|
2019-03-01 14:23:28 +08:00
|
|
|
|
|
2019-09-24 11:42:27 +08:00
|
|
|
|
_metadata_got = false;
|
2019-03-01 14:23:28 +08:00
|
|
|
|
startConnect(strHost, iPort , playTimeOutSec);
|
|
|
|
|
}
|
|
|
|
|
void RtmpPlayer::onErr(const SockException &ex){
|
2019-03-28 12:02:59 +08:00
|
|
|
|
onPlayResult_l(ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpPlayer::onPlayResult_l(const SockException &ex) {
|
|
|
|
|
WarnL << ex.getErrCode() << " " << ex.what();
|
2019-05-08 15:27:37 +08:00
|
|
|
|
|
|
|
|
|
if(!ex){
|
|
|
|
|
//恢复rtmp接收超时定时器
|
|
|
|
|
_mediaTicker.resetTime();
|
|
|
|
|
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
|
|
|
|
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
|
|
|
|
_pMediaTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() {
|
|
|
|
|
auto strongSelf=weakSelf.lock();
|
|
|
|
|
if(!strongSelf) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if(strongSelf->_mediaTicker.elapsedTime()> timeoutMS) {
|
|
|
|
|
//recv media timeout!
|
|
|
|
|
strongSelf->onPlayResult_l(SockException(Err_timeout,"recv rtmp timeout"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},getPoller()));
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-28 12:02:59 +08:00
|
|
|
|
if (_pPlayTimer) {
|
2019-05-08 15:27:37 +08:00
|
|
|
|
//开始播放阶段
|
2019-03-28 12:02:59 +08:00
|
|
|
|
_pPlayTimer.reset();
|
|
|
|
|
onPlayResult(ex);
|
2019-05-08 15:27:37 +08:00
|
|
|
|
}else {
|
|
|
|
|
//播放中途阶段
|
|
|
|
|
if (ex) {
|
|
|
|
|
//播放成功后异常断开回调
|
|
|
|
|
onShutdown(ex);
|
|
|
|
|
}else{
|
|
|
|
|
//恢复播放
|
|
|
|
|
onResume();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-28 12:02:59 +08:00
|
|
|
|
|
|
|
|
|
if(ex){
|
|
|
|
|
teardown();
|
|
|
|
|
}
|
2019-03-01 14:23:28 +08:00
|
|
|
|
}
|
|
|
|
|
void RtmpPlayer::onConnect(const SockException &err){
|
|
|
|
|
if(err.getErrCode()!=Err_success) {
|
|
|
|
|
onPlayResult_l(err);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
2017-04-01 16:35:56 +08:00
|
|
|
|
startClientSession([weakSelf](){
|
|
|
|
|
auto strongSelf=weakSelf.lock();
|
|
|
|
|
if(!strongSelf) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
strongSelf->send_connect();
|
|
|
|
|
});
|
|
|
|
|
}
|
2018-02-23 15:36:51 +08:00
|
|
|
|
void RtmpPlayer::onRecv(const Buffer::Ptr &pBuf){
|
2017-04-01 16:35:56 +08:00
|
|
|
|
try {
|
|
|
|
|
onParseRtmp(pBuf->data(), pBuf->size());
|
|
|
|
|
} catch (exception &e) {
|
|
|
|
|
SockException ex(Err_other, e.what());
|
2019-03-01 14:23:28 +08:00
|
|
|
|
onPlayResult_l(ex);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpPlayer::pause(bool bPause) {
|
|
|
|
|
send_pause(bPause);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void RtmpPlayer::send_connect() {
|
|
|
|
|
AMFValue obj(AMF_OBJECT);
|
2018-10-24 15:43:52 +08:00
|
|
|
|
obj.set("app", _strApp);
|
|
|
|
|
obj.set("tcUrl", _strTcUrl);
|
2017-05-27 11:26:49 +08:00
|
|
|
|
//未使用代理
|
2017-04-01 16:35:56 +08:00
|
|
|
|
obj.set("fpad", false);
|
2017-05-27 11:26:49 +08:00
|
|
|
|
//参考librtmp,什么作用?
|
2017-04-01 16:35:56 +08:00
|
|
|
|
obj.set("capabilities", 15);
|
2017-05-27 11:26:49 +08:00
|
|
|
|
//SUPPORT_VID_CLIENT_SEEK 支持seek
|
2017-04-01 16:35:56 +08:00
|
|
|
|
obj.set("videoFunction", 1);
|
|
|
|
|
//只支持aac
|
2017-05-27 11:26:49 +08:00
|
|
|
|
obj.set("audioCodecs", (double)(0x0400));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//只支持H264
|
2017-05-27 11:26:49 +08:00
|
|
|
|
obj.set("videoCodecs", (double)(0x0080));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
sendInvoke("connect", obj);
|
|
|
|
|
addOnResultCB([this](AMFDecoder &dec){
|
|
|
|
|
//TraceL << "connect result";
|
|
|
|
|
dec.load<AMFValue>();
|
|
|
|
|
auto val = dec.load<AMFValue>();
|
|
|
|
|
auto level = val["level"].as_string();
|
|
|
|
|
auto code = val["code"].as_string();
|
|
|
|
|
if(level != "status"){
|
2017-08-09 18:39:30 +08:00
|
|
|
|
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
send_createStream();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void RtmpPlayer::send_createStream() {
|
|
|
|
|
AMFValue obj(AMF_NULL);
|
|
|
|
|
sendInvoke("createStream", obj);
|
|
|
|
|
addOnResultCB([this](AMFDecoder &dec){
|
|
|
|
|
//TraceL << "createStream result";
|
|
|
|
|
dec.load<AMFValue>();
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_ui32StreamId = dec.load<int>();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
send_play();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void RtmpPlayer::send_play() {
|
|
|
|
|
AMFEncoder enc;
|
2018-10-24 15:43:52 +08:00
|
|
|
|
enc << "play" << ++_iReqID << nullptr << _strStream << (double)_ui32StreamId;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
sendRequest(MSG_CMD, enc.data());
|
|
|
|
|
auto fun = [this](AMFValue &val){
|
|
|
|
|
//TraceL << "play onStatus";
|
|
|
|
|
auto level = val["level"].as_string();
|
|
|
|
|
auto code = val["code"].as_string();
|
|
|
|
|
if(level != "status"){
|
2017-08-09 18:39:30 +08:00
|
|
|
|
throw std::runtime_error(StrPrinter <<"play 失败:" << level << " " << code << endl);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
addOnStatusCB(fun);
|
|
|
|
|
addOnStatusCB(fun);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void RtmpPlayer::send_pause(bool bPause) {
|
|
|
|
|
AMFEncoder enc;
|
2018-10-24 15:43:52 +08:00
|
|
|
|
enc << "pause" << ++_iReqID << nullptr << bPause;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
sendRequest(MSG_CMD, enc.data());
|
|
|
|
|
auto fun = [this,bPause](AMFValue &val){
|
|
|
|
|
//TraceL << "pause onStatus";
|
|
|
|
|
auto level = val["level"].as_string();
|
|
|
|
|
auto code = val["code"].as_string();
|
|
|
|
|
if(level != "status") {
|
|
|
|
|
if(!bPause){
|
2017-08-09 18:39:30 +08:00
|
|
|
|
throw std::runtime_error(StrPrinter <<"pause 恢复播放失败:" << level << " " << code << endl);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
}else{
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_bPaused = bPause;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
if(!bPause){
|
2019-03-01 14:23:28 +08:00
|
|
|
|
onPlayResult_l(SockException(Err_success, "rtmp resum success"));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}else{
|
|
|
|
|
//暂停播放
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_pMediaTimer.reset();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
addOnStatusCB(fun);
|
|
|
|
|
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_pBeatTimer.reset();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
if(bPause){
|
|
|
|
|
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
2019-03-01 14:23:28 +08:00
|
|
|
|
_pBeatTimer.reset(new Timer((*this)[kBeatIntervalMS].as<int>() / 1000.0,[weakSelf](){
|
2017-04-01 16:35:56 +08:00
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if (!strongSelf){
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
uint32_t timeStamp = ::time(NULL);
|
|
|
|
|
strongSelf->sendUserControl(CONTROL_PING_REQUEST, timeStamp);
|
|
|
|
|
return true;
|
2019-01-30 17:00:28 +08:00
|
|
|
|
},getPoller()));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpPlayer::onCmd_result(AMFDecoder &dec){
|
|
|
|
|
auto iReqId = dec.load<int>();
|
2018-10-24 15:43:52 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
|
|
|
|
auto it = _mapOnResultCB.find(iReqId);
|
|
|
|
|
if(it != _mapOnResultCB.end()){
|
2017-04-01 16:35:56 +08:00
|
|
|
|
it->second(dec);
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_mapOnResultCB.erase(it);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}else{
|
|
|
|
|
WarnL << "unhandled _result";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
|
|
|
|
AMFValue val;
|
|
|
|
|
while(true){
|
|
|
|
|
val = dec.load<AMFValue>();
|
|
|
|
|
if(val.type() == AMF_OBJECT){
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(val.type() != AMF_OBJECT){
|
2017-08-09 18:39:30 +08:00
|
|
|
|
throw std::runtime_error("onStatus:the result object was not found");
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-24 15:43:52 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
|
|
|
|
if(_dqOnStatusCB.size()){
|
|
|
|
|
_dqOnStatusCB.front()(val);
|
|
|
|
|
_dqOnStatusCB.pop_front();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}else{
|
|
|
|
|
auto level = val["level"];
|
|
|
|
|
auto code = val["code"].as_string();
|
|
|
|
|
if(level.type() == AMF_STRING){
|
|
|
|
|
if(level.as_string() != "status"){
|
|
|
|
|
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//WarnL << "unhandled onStatus:" << code;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
|
|
|
|
//TraceL;
|
|
|
|
|
auto val = dec.load<AMFValue>();
|
|
|
|
|
if(!onCheckMeta(val)){
|
2019-09-24 11:42:27 +08:00
|
|
|
|
throw std::runtime_error("onCheckMeta failed");
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2019-09-24 11:42:27 +08:00
|
|
|
|
_metadata_got = true;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpPlayer::onStreamDry(uint32_t ui32StreamId) {
|
|
|
|
|
//TraceL << ui32StreamId;
|
2019-03-28 12:02:59 +08:00
|
|
|
|
onPlayResult_l(SockException(Err_other,"rtmp stream dry"));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-01 13:12:24 +08:00
|
|
|
|
void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &packet) {
|
|
|
|
|
_mediaTicker.resetTime();
|
|
|
|
|
if(!_pPlayTimer){
|
|
|
|
|
//已经触发了onPlayResult事件,直接触发onMediaData事件
|
|
|
|
|
onMediaData(packet);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(packet->isCfgFrame()){
|
|
|
|
|
//输入配置帧以便初始化完成各个track
|
|
|
|
|
onMediaData(packet);
|
|
|
|
|
}else{
|
|
|
|
|
//先触发onPlayResult事件,这个时候解码器才能初始化完毕
|
|
|
|
|
onPlayResult_l(SockException(Err_success,"play rtmp success"));
|
|
|
|
|
//触发onPlayResult事件后,再把帧数据输入到解码器
|
|
|
|
|
onMediaData(packet);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
|
|
|
|
void RtmpPlayer::onRtmpChunk(RtmpPacket &chunkData) {
|
|
|
|
|
switch (chunkData.typeId) {
|
|
|
|
|
case MSG_CMD:
|
|
|
|
|
case MSG_CMD3:
|
|
|
|
|
case MSG_DATA:
|
|
|
|
|
case MSG_DATA3: {
|
|
|
|
|
AMFDecoder dec(chunkData.strBuf, 0);
|
|
|
|
|
std::string type = dec.load<std::string>();
|
|
|
|
|
auto it = g_mapCmd.find(type);
|
|
|
|
|
if(it != g_mapCmd.end()){
|
|
|
|
|
auto fun = it->second;
|
|
|
|
|
(this->*fun)(dec);
|
|
|
|
|
}else{
|
|
|
|
|
WarnL << "can not support cmd:" << type;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MSG_AUDIO:
|
|
|
|
|
case MSG_VIDEO: {
|
|
|
|
|
auto idx = chunkData.typeId%2;
|
2018-10-24 15:43:52 +08:00
|
|
|
|
if (_aNowStampTicker[idx].elapsedTime() > 500) {
|
2019-08-01 13:12:24 +08:00
|
|
|
|
//计算播放进度时间轴用
|
2018-10-26 14:12:16 +08:00
|
|
|
|
_aiNowStamp[idx] = chunkData.timeStamp;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2019-09-24 11:42:27 +08:00
|
|
|
|
if(!_metadata_got){
|
|
|
|
|
if(!onCheckMeta(TitleMeta().getMetadata())){
|
|
|
|
|
throw std::runtime_error("onCheckMeta failed");
|
|
|
|
|
}
|
2019-09-24 11:44:23 +08:00
|
|
|
|
_metadata_got = true;
|
2019-09-24 11:42:27 +08:00
|
|
|
|
}
|
2019-03-01 14:23:28 +08:00
|
|
|
|
onMediaData_l(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-26 14:12:16 +08:00
|
|
|
|
uint32_t RtmpPlayer::getProgressMilliSecond() const{
|
|
|
|
|
uint32_t iTime[2] = {0,0};
|
2017-04-01 16:35:56 +08:00
|
|
|
|
for(auto i = 0 ;i < 2 ;i++){
|
2018-10-26 14:12:16 +08:00
|
|
|
|
iTime[i] = _aiNowStamp[i] - _aiFistStamp[i];
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2018-10-26 14:12:16 +08:00
|
|
|
|
return _iSeekTo + MAX(iTime[0],iTime[1]);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2018-10-26 14:12:16 +08:00
|
|
|
|
void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){
|
2018-10-24 15:43:52 +08:00
|
|
|
|
if (_bPaused) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
pause(false);
|
|
|
|
|
}
|
|
|
|
|
AMFEncoder enc;
|
2018-10-26 14:12:16 +08:00
|
|
|
|
enc << "seek" << ++_iReqID << nullptr << seekMS * 1.0;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
sendRequest(MSG_CMD, enc.data());
|
2018-10-26 14:12:16 +08:00
|
|
|
|
addOnStatusCB([this,seekMS](AMFValue &val) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//TraceL << "seek result";
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_aNowStampTicker[0].resetTime();
|
|
|
|
|
_aNowStampTicker[1].resetTime();
|
2018-10-26 14:48:09 +08:00
|
|
|
|
int iTimeInc = seekMS - getProgressMilliSecond();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
for(auto i = 0 ;i < 2 ;i++){
|
2018-10-26 14:12:16 +08:00
|
|
|
|
_aiFistStamp[i] = _aiNowStamp[i] + iTimeInc;
|
|
|
|
|
_aiNowStamp[i] = _aiFistStamp[i];
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2018-10-26 14:12:16 +08:00
|
|
|
|
_iSeekTo = seekMS;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-24 17:17:55 +08:00
|
|
|
|
} /* namespace mediakit */
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|