为保障旁路推流场景下用户体验,避免频繁出现推流异常,你可以参考本文优化旁路推流的业务逻辑。
onRtmpStreamingStateChanged
报告的错误码进行的最佳处理方式。如果你还在使用 onStreamPublished
回调,你需要参考本文并结合推流的新旧回调有何关系进行处理。请在调用 startRtmpStreamWithTranscoding
或 startRtmpStreamWithoutTranscoding
后,根据收到的 onRtmpStreamingStateChanged
回调报告的错误码进行处理。
onRtmpStreamingEvent
回调报告的事件不影响推流的进行,仅作为事件提醒。onRtmpStreamingStateChanged
回调报告的错误会导致推流的中断,收到任何错误码都请参考文档进行相应处理。RTMP_STREAM_PUBLISH_ERROR_OK (0)
: 推流成功。RTMP_STREAM_UNPUBLISH_ERROR_OK (100)
: 正常结束推流。RTMP_STREAM_PUBLISH_ERROR_CONNECTION_TIMEOUT (3)
: 向声网推流服务器请求超时。常见出错原因:网络异常。解决方法:依次调用 stopRtmpStream
、start
重新尝试推流。RTMP_STREAM_PUBLISH_ERROR_INTERNAL_SERVER_ERROR (4)
:声网推流服务器出错。解决方法:依次调用 stopRtmpStream
、start
重新尝试推流。RTMP_STREAM_PUBLISH_ERROR_RTMP_SERVER_ERROR (5)
: CDN 网络问题,或声网推流服务器与 CDN 之间的网络问题。解决方法:依次调用 stopRtmpStream
、start
重新尝试推流。如果 3~5 次重推后,依然失败,请依次联系 CDN 厂商、声网技术支持排查问题。RTMP_STREAM_PUBLISH_ERROR_STREAM_NOT_FOUND (9)
: 找不到该推流地址。解决方法:检查并更换传入的推流地址后,调用 start
重新尝试推流。RTMP_STREAM_PUBLISH_ERROR_NET_DOWN (14)
: 主播的网络出错。解决方法:依次调用 stopRtmpStream
、start
重新尝试推流。stop
和 start
方法重推时,请确保在收到 onRtmpStreamingStateChanged
回调报告 stop
调用成功后,才调用 start
。RTMP_STREAM_PUBLISH_ERROR_INVALID_ARGUMENT (1)
: 参数无效。解决方法:检查 API 调用时序,检查传参。RTMP_STREAM_PUBLISH_ERROR_ENCRYPTED_STREAM_NOT_ALLOWED (2)
: 推送的媒体流已加密,无法进行推流。详见媒体流加密。RTMP_STREAM_PUBLISH_ERROR_REACH_LIMIT (7)
: 推流地址已达到上限。每个项目下每个频道中,每个 uid
推流地址上限为 10 个。解决方法:请先删除一些不用的推流地址。RTMP_STREAM_PUBLISH_ERROR_NOT_AUTHORIZED (8)
: 没有权限的操作。常见出错原因:将媒体流推送到另一个主播正在使用的推流地址。解决方法:检查业务逻辑。RTMP_STREAM_PUBLISH_ERROR_FORMAT_NOT_SUPPORTED (10)
: 推流地址格式错误。解决方法:检查推流地址。RTMP_STREAM_PUBLISH_ERROR_NOT_BROADCASTER (11)
: 用户角色不是主播,该用户无法使用推流功能。请检查你的应用代码逻辑。RTMP_STREAM_PUBLISH_ERROR_TRANSCODING_NO_MIX_STREAM (13)
: 非转码推流情况下,调用了 updateRtmpTranscoding
或 setLiveTranscoding
(已废弃)方法更新转码属性。请检查你的应用代码逻辑。RTMP_STREAM_PUBLISH_ERROR_INVALID_APPID (15)
: 你的 App ID 没有使用声网推流服务的权限。请参考前提条件开启推流服务。极端弱网情况下,你可能无法收到客户端与声网推流服务器中断的回调。因此除了通过监听回调的方式,声网还推荐你在推流开始后,在应用层设置一个 1 分钟的定时器,以每分钟调用一次 start
。此时,如果当前推流异常,你可能收到 onRtmpStreamingStateChanged
回调报告其他错误码,或收不到回调。请根据错误码排查或立即重推。
以 C++ 语言为例,展示依次调用 stopRtmpStream
、start
重新尝试推流的代码逻辑:
LRESULT CAgoraRtmpStreamingDlg::OnEIDRtmpStateChanged(WPARAM wParam, LPARAM lParam)
{
PRtmpStreamStreamStateChanged rtmpState = (PRtmpStreamStreamStateChanged)wParam;
CString strInfo;
m_btnRemoveStream.EnableWindow(TRUE);
switch (rtmpState->state)
{
case RTMP_STREAM_PUBLISH_STATE_IDLE:
{
strInfo.Format(_T("%s:%S��"), agoraRtmpStateIdle, rtmpState->url);
CString strUrl;
strUrl.Format(_T("%S"), rtmpState->url);
int sel = m_cmbRtmpUrl.GetCurSel();
m_cmbRtmpUrl.DeleteString(sel);
m_cmbRtmpUrl.ResetContent();
if (m_cmbRtmpUrl.GetCount() > 0) {
m_cmbRtmpUrl.SetCurSel(0);
}
for (auto iter = m_urlSet.begin(); iter != m_urlSet.end(); ++iter)
if (strUrl.Compare(*iter) == 0) {
m_urlSet.erase(iter);
break;
}
if (m_bRemoveAll) {
m_removeUrlCount++;
if (m_removeUrlCount == m_urlSet.size()) {//remove all url when leave channel
m_urlSet.clear();
m_bRemoveAll = false;
m_rtcEngine->leaveChannel();
m_lstInfo.InsertString(m_lstInfo.GetCount(), _T("leaveChannel"));
}
}
}
break;
case RTMP_STREAM_PUBLISH_STATE_CONNECTING:
{
strInfo = agoraRtmpStateConnecting;
}
break;
case RTMP_STREAM_PUBLISH_STATE_RUNNING:
strInfo = agoraRtmpStateRunning;
if (rtmpState->error == RTMP_STREAM_PUBLISH_ERROR_OK) {
strInfo = agoraRtmpStateRunningSuccess;
CString strUrl;
strUrl.Format(_T("%S"), rtmpState->url);
if (m_urlSet.find(strUrl) == m_urlSet.end()) {
m_cmbRtmpUrl.AddString(strUrl);
m_urlSet.insert(strUrl);
if (m_cmbRtmpUrl.GetCurSel() < 0)
m_cmbRtmpUrl.SetCurSel(0);
}
}
break;
case RTMP_STREAM_PUBLISH_STATE_RECOVERING:
strInfo.Format(agoraRtmpStateRecovering);
break;
case RTMP_STREAM_PUBLISH_STATE_FAILURE:
{
strInfo = agoraRtmpStateRunningSuccess;
CString strUrl;
strUrl.Format(_T("%S"), rtmpState->url);
std::string szUrl = cs2utf8(strUrl);
m_rtcEngine->stopRtmpStream(szUrl.c_str());
int error = lParam;
if (error == RTMP_STREAM_PUBLISH_ERROR_CONNECTION_TIMEOUT
|| error == RTMP_STREAM_PUBLISH_ERROR_INTERNAL_SERVER_ERROR
|| error == RTMP_STREAM_PUBLISH_ERROR_STREAM_NOT_FOUND
|| error == RTMP_STREAM_PUBLISH_ERROR_RTMP_SERVER_ERROR
|| error == RTMP_STREAM_PUBLISH_ERROR_NET_DOWN) {
if (m_mapRepublishFlag.find(szUrl.c_str()) != m_mapRepublishFlag.end()
&& m_mapRemoveFlag.find(szUrl.c_str()) != m_mapRemoveFlag.end()) {
if (m_mapRepublishFlag[szUrl.c_str()]
&& !m_mapRemoveFlag[szUrl.c_str()]) {
//republish, removePublish when error
m_rtcEngine->startRtmpStreamWithoutTranscoding(szUrl.c_str());
}
}
}
else {
// stop retrying
m_mapRemoveFlag[szUrl.c_str()] = false;
m_mapRepublishFlag[szUrl.c_str()] = true;
CString strUrl;
strUrl.Format(_T("%S"), szUrl.c_str());
for (int i = 0; i < m_cmbRtmpUrl.GetCount(); ++i) {
CString strText;
m_cmbRtmpUrl.GetLBText(i, strText);
if (strText.Compare(strUrl) == 0) {
m_cmbRtmpUrl.DeleteString(i);
break;
}
}
if (m_urlSet.find(strUrl) != m_urlSet.end()) {
m_urlSet.erase(strUrl);
}
if (m_cmbRtmpUrl.GetCurSel() < 0 && m_cmbRtmpUrl.GetCount() > 0)
m_cmbRtmpUrl.SetCurSel(0);
}
switch (rtmpState->state)
{
case RTMP_STREAM_PUBLISH_ERROR_INVALID_ARGUMENT:
{
strInfo = agoraRtmpStateInvalidArg;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_ENCRYPTED_STREAM_NOT_ALLOWED:
{
strInfo = agoraRtmpStateEncrypted;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_CONNECTION_TIMEOUT:
{
strInfo = agoraRtmpStateConnTimeout;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_INTERNAL_SERVER_ERROR:
{
strInfo = agoraRtmpStateInrealErr;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_RTMP_SERVER_ERROR:
{
strInfo = agoraRtmpStateServerErr;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_TOO_OFTEN:
{
strInfo = agoraRtmpStateTooOften;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_REACH_LIMIT:
{
strInfo = agoraRtmpStateReachLimit;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_NOT_AUTHORIZED:
{
strInfo = agoraRtmpStateNotAuth;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_STREAM_NOT_FOUND:
{
strInfo = agoraRtmpStateNotFound;
}
break;
case RTMP_STREAM_PUBLISH_ERROR_FORMAT_NOT_SUPPORTED:
{
strInfo = agoraRtmpStateNotSupported;
}
break;
default:
break;
}
}
break;
default:
break;
}
m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo);
delete[] rtmpState->url;
rtmpState->url = NULL;
delete[] rtmpState;
rtmpState = NULL;
return 0;
}
本文以 C++ 语言为例,如果你使用其他语言的 API,请参考 API 对照表:
C++ | Objective-C | Java |
---|---|---|
startRtmpStreamWithoutTranscoding |
startRtmpStreamWithoutTranscoding |
startRtmpStreamWithoutTranscoding |
startRtmpStreamWithTranscoding |
startRtmpStreamWithTranscoding |
startRtmpStreamWithTranscoding |
updateRtmpTranscoding |
updateRtmpTranscoding |
updateRtmpTranscoding |
stopRtmpStream |
stopRtmpStream |
stopRtmpStream |
onRtmpStreamingStateChanged |
rtmpStreamingChangedtoState |
onRtmpStreamingStateChanged |
onRtmpStreamingEvent |
rtmpStreamingEventWithUrl |
onRtmpStreamingEvent |