|
|
@@ -1,6 +1,7 @@
|
|
|
#include "read_thread.h"
|
|
|
#include "AVPlayer2/playercontroller.h"
|
|
|
#include <QLoggingCategory>
|
|
|
+#include <QString>
|
|
|
Q_LOGGING_CATEGORY(playerControllerReadThread, "player.controller.ReadThread")
|
|
|
|
|
|
extern int infinite_buffer;
|
|
|
@@ -34,6 +35,13 @@ int ReadThread::loop_read()
|
|
|
int pkt_in_play_range = 0;
|
|
|
int64_t stream_start_time = 0;
|
|
|
int64_t pkt_ts = 0;
|
|
|
+
|
|
|
+ // 重连计数器和配置
|
|
|
+ static int reconnect_count = 0;
|
|
|
+ static const int max_fast_reconnect_attempts = 3; // 快速重连次数
|
|
|
+ static const int max_normal_reconnect_attempts = 10; // 正常重连次数
|
|
|
+ static bool infinite_reconnect_mode = false; // 无限重连模式
|
|
|
+
|
|
|
if (!is)
|
|
|
return ret;
|
|
|
|
|
|
@@ -156,33 +164,129 @@ int ReadThread::loop_read()
|
|
|
<< ", pb_error:" << (is->ic->pb ? is->ic->pb->error : 0)
|
|
|
<< "error:" << QString::fromUtf8(buf);
|
|
|
|
|
|
- if ((ret == AVERROR_EOF || avio_feof(is->ic->pb)) && !is->eof) {
|
|
|
- qCDebug(playerControllerReadThread) << "[ReadThread] EOF reached, send null packets.";
|
|
|
- if (is->video_stream >= 0)
|
|
|
- packet_queue_put_nullpacket(&is->videoq, pkt, is->video_stream);
|
|
|
- if (is->audio_stream >= 0)
|
|
|
- packet_queue_put_nullpacket(&is->audioq, pkt, is->audio_stream);
|
|
|
- if (is->subtitle_stream >= 0)
|
|
|
- packet_queue_put_nullpacket(&is->subtitleq, pkt, is->subtitle_stream);
|
|
|
- is->eof = 1;
|
|
|
-
|
|
|
- // if (is->loop) {
|
|
|
- // stream_seek(is, 0, 0, 0);
|
|
|
- // } else {
|
|
|
- // is->eof = 1;
|
|
|
- // break; // added for auto exit read thread
|
|
|
- // }
|
|
|
- }
|
|
|
- if (is->ic->pb && is->ic->pb->error) {
|
|
|
- qCWarning(playerControllerReadThread) << "[ReadThread] IO error detected.";
|
|
|
- break;
|
|
|
+ // 统一处理EOF和I/O错误 - 无感重连策略
|
|
|
+ bool shouldAttemptReconnect = false;
|
|
|
+ bool isEOFError = (ret == AVERROR_EOF || avio_feof(is->ic->pb)) && !is->eof;
|
|
|
+ bool isIOError = is->ic->pb && is->ic->pb->error;
|
|
|
+
|
|
|
+ // 增强错误检测:检查更多可能的网络错误
|
|
|
+ bool isNetworkError = (ret == AVERROR(EAGAIN) || ret == AVERROR(ETIMEDOUT) ||
|
|
|
+ ret == AVERROR(ECONNRESET) || ret == AVERROR(EPIPE) ||
|
|
|
+ ret == AVERROR(ECONNABORTED) || ret == -5); // -5 是常见的I/O错误
|
|
|
+
|
|
|
+ if (isEOFError || isIOError || isNetworkError) {
|
|
|
+ if (isEOFError) {
|
|
|
+ qCDebug(playerControllerReadThread) << "[ReadThread] EOF reached for stream:" << is->filename;
|
|
|
+ }
|
|
|
+ if (isIOError) {
|
|
|
+ qCWarning(playerControllerReadThread) << "[ReadThread] IO error detected, error code:" << is->ic->pb->error;
|
|
|
+ }
|
|
|
+ if (isNetworkError) {
|
|
|
+ qCWarning(playerControllerReadThread) << "[ReadThread] Network error detected, return code:" << ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否为实时流(RTMP、RTSP等)
|
|
|
+ bool isRealTimeStream = false;
|
|
|
+ if (is->filename) {
|
|
|
+ QString filename = QString::fromUtf8(is->filename);
|
|
|
+ isRealTimeStream = filename.startsWith("rtmp://") ||
|
|
|
+ filename.startsWith("rtsp://") ||
|
|
|
+ filename.startsWith("rtp://") ||
|
|
|
+ filename.startsWith("udp://") ||
|
|
|
+ filename.startsWith("tcp://") ||
|
|
|
+ filename.startsWith("http://") ||
|
|
|
+ filename.startsWith("https://");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isRealTimeStream) {
|
|
|
+ reconnect_count++;
|
|
|
+ QString errorType = isEOFError ? "EOF" : (isIOError ? "I/O error" : "Network error");
|
|
|
+
|
|
|
+ // 分层重连策略
|
|
|
+ bool shouldContinueReconnect = false;
|
|
|
+ int wait_time_ms = 0;
|
|
|
+
|
|
|
+ if (reconnect_count <= max_fast_reconnect_attempts) {
|
|
|
+ // 快速重连阶段:短间隔,快速恢复
|
|
|
+ wait_time_ms = 500 * reconnect_count; // 0.5s, 1s, 1.5s
|
|
|
+ shouldContinueReconnect = true;
|
|
|
+ qCWarning(playerControllerReadThread) << "[ReadThread] Fast reconnection" << errorType
|
|
|
+ << "attempt" << reconnect_count << "/" << max_fast_reconnect_attempts;
|
|
|
+ } else if (reconnect_count <= max_normal_reconnect_attempts) {
|
|
|
+ // 正常重连阶段:较长间隔
|
|
|
+ wait_time_ms = 2000 + (reconnect_count - max_fast_reconnect_attempts) * 1000; // 2s, 3s, 4s...
|
|
|
+ shouldContinueReconnect = true;
|
|
|
+ qCWarning(playerControllerReadThread) << "[ReadThread] Normal reconnection" << errorType
|
|
|
+ << "attempt" << reconnect_count << "/" << max_normal_reconnect_attempts;
|
|
|
+ } else {
|
|
|
+ // 无限重连模式:长间隔,但永不放弃
|
|
|
+ infinite_reconnect_mode = true;
|
|
|
+ wait_time_ms = 10000; // 固定10秒间隔
|
|
|
+ shouldContinueReconnect = true;
|
|
|
+ qCWarning(playerControllerReadThread) << "[ReadThread] Infinite reconnection mode" << errorType
|
|
|
+ << "attempt" << reconnect_count << "(never give up)";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shouldContinueReconnect) {
|
|
|
+ // 清空当前的错误状态
|
|
|
+ if (is->ic && is->ic->pb) {
|
|
|
+ is->ic->pb->error = 0;
|
|
|
+ is->ic->pb->eof_reached = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 无感重连:不设置EOF状态,保持播放器运行
|
|
|
+ // 这样即使网络中断,播放器界面也不会退出
|
|
|
+ qCDebug(playerControllerReadThread) << "[ReadThread] Seamless reconnection: waiting" << wait_time_ms << "ms...";
|
|
|
+
|
|
|
+ std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
+ m_cv.wait_for(lock, std::chrono::milliseconds(wait_time_ms), [this, is] { return isExit() || is->abort_request; });
|
|
|
+
|
|
|
+ if (!isExit() && !is->abort_request) {
|
|
|
+ qCDebug(playerControllerReadThread) << "[ReadThread] Attempting seamless reconnection...";
|
|
|
+ continue; // 继续循环,永不放弃,真正的无感重连
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 非实时流:传统处理方式
|
|
|
+ qCDebug(playerControllerReadThread) << "[ReadThread] File stream error, send null packets.";
|
|
|
+
|
|
|
+ // 发送null packets并设置EOF
|
|
|
+ if (isEOFError) {
|
|
|
+ if (is->video_stream >= 0)
|
|
|
+ packet_queue_put_nullpacket(&is->videoq, pkt, is->video_stream);
|
|
|
+ if (is->audio_stream >= 0)
|
|
|
+ packet_queue_put_nullpacket(&is->audioq, pkt, is->audio_stream);
|
|
|
+ if (is->subtitle_stream >= 0)
|
|
|
+ packet_queue_put_nullpacket(&is->subtitleq, pkt, is->subtitle_stream);
|
|
|
+ is->eof = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 对于I/O错误,直接退出循环
|
|
|
+ if (isIOError) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+ // 错误处理已经统一到上面的逻辑中
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
m_cv.wait_for(lock, std::chrono::milliseconds(10), [this, is] { return isExit() || is->abort_request; });
|
|
|
continue;
|
|
|
} else {
|
|
|
is->eof = 0;
|
|
|
+
|
|
|
+ // 成功读取数据时重置重连计数器和状态
|
|
|
+ if (reconnect_count > 0) {
|
|
|
+ if (infinite_reconnect_mode) {
|
|
|
+ qCInfo(playerControllerReadThread) << "[ReadThread] Stream recovered from infinite reconnection mode after"
|
|
|
+ << reconnect_count << "attempts";
|
|
|
+ infinite_reconnect_mode = false;
|
|
|
+ } else {
|
|
|
+ qCDebug(playerControllerReadThread) << "[ReadThread] Stream recovered, resetting reconnection counter";
|
|
|
+ }
|
|
|
+ reconnect_count = 0;
|
|
|
+ }
|
|
|
+
|
|
|
qCDebug(playerControllerReadThread)
|
|
|
<< "[ReadThread] av_read_frame success, stream_index:" << pkt->stream_index
|
|
|
<< ", pts:" << pkt->pts;
|