|
@@ -38,75 +38,66 @@ PlayerController::~PlayerController()
|
|
|
|
|
|
|
|
void PlayerController::startToPlay(const QString& file)
|
|
void PlayerController::startToPlay(const QString& file)
|
|
|
{
|
|
{
|
|
|
- // 使用局部变量保存当前状态,避免在锁外使用成员变量
|
|
|
|
|
- PlayerState currentState = m_state;
|
|
|
|
|
- QString currentFile = m_currentFile;
|
|
|
|
|
|
|
+ // 使用原子操作和局部变量避免死锁
|
|
|
|
|
+ PlayerState currentState = m_state.load();
|
|
|
|
|
+ QString currentFile;
|
|
|
|
|
+
|
|
|
|
|
+ // 快速状态检查,避免不必要的锁竞争
|
|
|
|
|
+ if (currentState == PlayerState::Initializing) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "Player is initializing. Ignoring request.";
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ // 获取当前文件名(需要锁保护)
|
|
|
{
|
|
{
|
|
|
std::lock_guard<std::mutex> lock(m_stopMutex);
|
|
std::lock_guard<std::mutex> lock(m_stopMutex);
|
|
|
- qCDebug(playerControllerLog) << "[PlayerController] m_state" << (int) m_state.load();
|
|
|
|
|
|
|
+ currentFile = m_currentFile;
|
|
|
|
|
+ currentState = m_state.load();
|
|
|
|
|
|
|
|
- // 自愈:如果状态为Playing但所有线程都已退出,强制Idle
|
|
|
|
|
- if (m_state == PlayerState::Playing && areAllThreadsStopped()) {
|
|
|
|
|
- stopAndResetThreads();
|
|
|
|
|
- m_videoState.reset();
|
|
|
|
|
- m_currentFile.clear();
|
|
|
|
|
- m_state = PlayerState::Idle;
|
|
|
|
|
|
|
+ // 自愈:如果状态为Playing但所有线程都已退出,标记需要重置
|
|
|
|
|
+ if (currentState == PlayerState::Playing && areAllThreadsStopped()) {
|
|
|
qCDebug(playerControllerLog)
|
|
qCDebug(playerControllerLog)
|
|
|
- << "[PlayerController] All threads stopped, force reset to Idle.";
|
|
|
|
|
|
|
+ << "[PlayerController] All threads stopped, will force reset to Idle.";
|
|
|
|
|
+ // 不在锁内调用stopAndResetThreads,避免死锁
|
|
|
|
|
+ currentState = PlayerState::Idle; // 标记需要重置
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- currentState = m_state;
|
|
|
|
|
- currentFile = m_currentFile;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 初始化中,忽略新请求
|
|
|
|
|
- if (currentState == PlayerState::Initializing) {
|
|
|
|
|
- qCDebug(playerControllerLog) << "Player is initializing. Ignoring request.";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 正在播放中,检查是否需要切换文件
|
|
|
|
|
|
|
+ // 正在播放中,需要先停止
|
|
|
if (currentState == PlayerState::Playing) {
|
|
if (currentState == PlayerState::Playing) {
|
|
|
- // if (currentFile == file) {
|
|
|
|
|
- // qCDebug(playerControllerLog) << "Already playing the same file. Ignoring request.";
|
|
|
|
|
- // return;
|
|
|
|
|
- // } else
|
|
|
|
|
-
|
|
|
|
|
- {
|
|
|
|
|
- qCDebug(playerControllerLog)
|
|
|
|
|
- << "Player is busy with another file, stopping and switching to:" << file;
|
|
|
|
|
- // 在锁外调用stopPlay,避免死锁
|
|
|
|
|
- stopPlay();
|
|
|
|
|
- // 停止后重新获取状态
|
|
|
|
|
- currentState = PlayerState::Idle; // 假设stopPlay会将状态设为Idle
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ qCDebug(playerControllerLog)
|
|
|
|
|
+ << "Player is busy, stopping and switching to:" << file;
|
|
|
|
|
+ // 在锁外调用stopPlay,避免死锁
|
|
|
|
|
+ stopPlay();
|
|
|
|
|
+ currentState = PlayerState::Idle;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 空闲状态,开始新播放
|
|
// 空闲状态,开始新播放
|
|
|
if (currentState == PlayerState::Idle) {
|
|
if (currentState == PlayerState::Idle) {
|
|
|
- std::lock_guard<std::mutex> lock(m_stopMutex);
|
|
|
|
|
-
|
|
|
|
|
- // 再次检查状态,确保在获取锁的过程中状态没有改变
|
|
|
|
|
- if (m_state != PlayerState::Idle) {
|
|
|
|
|
- qCDebug(playerControllerLog) << "State changed while waiting for lock. Current state:" << (int)m_state.load();
|
|
|
|
|
|
|
+ // 使用原子操作尝试设置状态
|
|
|
|
|
+ PlayerState expected = PlayerState::Idle;
|
|
|
|
|
+ if (!m_state.compare_exchange_strong(expected, PlayerState::Initializing)) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "State changed during initialization attempt. Current state:" << (int)expected;
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 确保所有线程已停止
|
|
|
|
|
- if (!areAllThreadsStopped()) {
|
|
|
|
|
- qCDebug(playerControllerLog) << "Some threads still running, stopping them first";
|
|
|
|
|
- stopAndResetThreads();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 状态已成功设置为Initializing,现在可以安全地进行初始化
|
|
|
|
|
+ {
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(m_stopMutex);
|
|
|
|
|
+
|
|
|
|
|
+ // 确保所有线程已停止
|
|
|
|
|
+ if (!areAllThreadsStopped()) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "Some threads still running, stopping them first";
|
|
|
|
|
+ stopAndResetThreads();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 重置状态
|
|
|
|
|
- m_videoState.reset();
|
|
|
|
|
- m_currentFile.clear();
|
|
|
|
|
|
|
+ // 重置状态
|
|
|
|
|
+ m_videoState.reset();
|
|
|
|
|
+ m_currentFile = file;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
qCDebug(playerControllerLog) << "Player is idle. Starting playback for:" << file;
|
|
qCDebug(playerControllerLog) << "Player is idle. Starting playback for:" << file;
|
|
|
- m_state = PlayerState::Initializing;
|
|
|
|
|
- m_currentFile = file;
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 直接进行初始化,无需异步
|
|
// 直接进行初始化,无需异步
|
|
|
bool success = !file.isEmpty();
|
|
bool success = !file.isEmpty();
|
|
|
onAsyncInitFinished(file, success);
|
|
onAsyncInitFinished(file, success);
|
|
@@ -210,8 +201,8 @@ void PlayerController::onAsyncInitFinished(const QString& file, bool success)
|
|
|
|
|
|
|
|
void PlayerController::stopPlay()
|
|
void PlayerController::stopPlay()
|
|
|
{
|
|
{
|
|
|
- // 移除互斥锁,避免与startToPlay中的锁冲突
|
|
|
|
|
- if (m_state == PlayerState::Idle)
|
|
|
|
|
|
|
+ // 使用原子操作检查状态,避免不必要的操作
|
|
|
|
|
+ if (m_state.load() == PlayerState::Idle)
|
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
qCDebug(playerControllerLog) << "Stopping playback...";
|
|
qCDebug(playerControllerLog) << "Stopping playback...";
|
|
@@ -221,11 +212,13 @@ void PlayerController::stopPlay()
|
|
|
stopAndResetThreads();
|
|
stopAndResetThreads();
|
|
|
|
|
|
|
|
// 清理视频状态
|
|
// 清理视频状态
|
|
|
- m_videoState.reset();
|
|
|
|
|
-
|
|
|
|
|
- m_currentFile.clear();
|
|
|
|
|
|
|
+ {
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(m_stopMutex);
|
|
|
|
|
+ m_videoState.reset();
|
|
|
|
|
+ m_currentFile.clear();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
m_state = PlayerState::Idle;
|
|
m_state = PlayerState::Idle;
|
|
|
-
|
|
|
|
|
qCDebug(playerControllerLog) << "Playback stopped.";
|
|
qCDebug(playerControllerLog) << "Playback stopped.";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -292,15 +285,19 @@ QString PlayerController::playingFile() const
|
|
|
|
|
|
|
|
bool PlayerController::isPlaying() const
|
|
bool PlayerController::isPlaying() const
|
|
|
{
|
|
{
|
|
|
|
|
+ // 使用原子操作获取状态,避免竞态条件
|
|
|
|
|
+ PlayerState currentState = m_state.load();
|
|
|
|
|
+
|
|
|
// 不仅检查状态标志,还检查线程是否实际运行
|
|
// 不仅检查状态标志,还检查线程是否实际运行
|
|
|
- if (m_state != PlayerState::Playing) {
|
|
|
|
|
|
|
+ if (currentState != PlayerState::Playing) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 如果状态是Playing但所有线程都已停止,则实际上不是在播放状态
|
|
// 如果状态是Playing但所有线程都已停止,则实际上不是在播放状态
|
|
|
|
|
+ // 使用锁保护线程状态检查,确保一致性
|
|
|
|
|
+ std::lock_guard<std::mutex> lock(m_stopMutex);
|
|
|
if (areAllThreadsStopped()) {
|
|
if (areAllThreadsStopped()) {
|
|
|
qCDebug(playerControllerLog) << "[isPlaying] State is Playing but all threads stopped";
|
|
qCDebug(playerControllerLog) << "[isPlaying] State is Playing but all threads stopped";
|
|
|
-
|
|
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -448,29 +445,45 @@ void PlayerController::videoSeek(double position, double increment)
|
|
|
position = state->ic->start_time / static_cast<double>(AV_TIME_BASE);
|
|
position = state->ic->start_time / static_cast<double>(AV_TIME_BASE);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 边界检查:防止seek到超出视频时长的位置
|
|
|
|
|
- double max_position = state->ic->duration / static_cast<double>(AV_TIME_BASE);
|
|
|
|
|
- if (state->ic->start_time != AV_NOPTS_VALUE)
|
|
|
|
|
- max_position += state->ic->start_time / static_cast<double>(AV_TIME_BASE);
|
|
|
|
|
-
|
|
|
|
|
- qDebug() << "[videoSeek] 边界检查: position=" << position << ", max_position=" << max_position
|
|
|
|
|
- << ", duration=" << state->ic->duration << ", start_time=" << state->ic->start_time;
|
|
|
|
|
|
|
+ // 增强的边界检查:防止seek到超出视频时长的位置
|
|
|
|
|
+ double max_position = 0.0;
|
|
|
|
|
+ double min_position = 0.0;
|
|
|
|
|
+
|
|
|
|
|
+ // 安全地计算最大位置
|
|
|
|
|
+ if (state->ic->duration > 0) {
|
|
|
|
|
+ max_position = state->ic->duration / static_cast<double>(AV_TIME_BASE);
|
|
|
|
|
+ if (state->ic->start_time != AV_NOPTS_VALUE && state->ic->start_time > 0) {
|
|
|
|
|
+ min_position = state->ic->start_time / static_cast<double>(AV_TIME_BASE);
|
|
|
|
|
+ max_position += min_position;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ qCWarning(playerControllerLog) << "[videoSeek] Invalid duration, seek operation cancelled";
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 更保守的边界检查:减去3秒作为安全边界
|
|
|
|
|
- double safe_boundary = 3.0;
|
|
|
|
|
- if (position > max_position - safe_boundary) {
|
|
|
|
|
- qDebug() << "[videoSeek] 调整seek位置: 原始position=" << position
|
|
|
|
|
- << ", 最大position=" << max_position;
|
|
|
|
|
- position = max_position - safe_boundary;
|
|
|
|
|
- if (position < 0)
|
|
|
|
|
- position = 0;
|
|
|
|
|
- qDebug() << "[videoSeek] 调整后position=" << position;
|
|
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeek] 边界检查: position=" << position
|
|
|
|
|
+ << ", min_position=" << min_position
|
|
|
|
|
+ << ", max_position=" << max_position
|
|
|
|
|
+ << ", duration=" << state->ic->duration
|
|
|
|
|
+ << ", start_time=" << state->ic->start_time;
|
|
|
|
|
+
|
|
|
|
|
+ // 更严格的边界检查:减去5秒作为安全边界,并确保不小于最小位置
|
|
|
|
|
+ double safe_boundary = 5.0;
|
|
|
|
|
+ if (position < min_position) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeek] 调整seek位置到最小值: 原始position=" << position;
|
|
|
|
|
+ position = min_position;
|
|
|
|
|
+ } else if (position > max_position - safe_boundary) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeek] 调整seek位置到安全范围: 原始position=" << position
|
|
|
|
|
+ << ", 最大position=" << max_position;
|
|
|
|
|
+ position = std::max(min_position, max_position - safe_boundary);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeek] 最终position=" << position;
|
|
|
|
|
|
|
|
// 添加量化操作,精确到0.01秒
|
|
// 添加量化操作,精确到0.01秒
|
|
|
- qDebug() << "position:" << position
|
|
|
|
|
- << "position * AV_TIME_BASE:" << static_cast<int64_t>(position * AV_TIME_BASE)
|
|
|
|
|
- << "increment * AV_TIME_BASE:" << static_cast<int64_t>(increment * AV_TIME_BASE);
|
|
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeek] position:" << position
|
|
|
|
|
+ << "position * AV_TIME_BASE:" << static_cast<int64_t>(position * AV_TIME_BASE)
|
|
|
|
|
+ << "increment * AV_TIME_BASE:" << static_cast<int64_t>(increment * AV_TIME_BASE);
|
|
|
|
|
|
|
|
// 启用精确帧定位
|
|
// 启用精确帧定位
|
|
|
int64_t target_pts = static_cast<int64_t>(position * AV_TIME_BASE);
|
|
int64_t target_pts = static_cast<int64_t>(position * AV_TIME_BASE);
|
|
@@ -542,28 +555,42 @@ void PlayerController::videoSeekEx(double value, double maxValue)
|
|
|
if (cur_stream->ic->start_time != AV_NOPTS_VALUE)
|
|
if (cur_stream->ic->start_time != AV_NOPTS_VALUE)
|
|
|
ts += cur_stream->ic->start_time;
|
|
ts += cur_stream->ic->start_time;
|
|
|
|
|
|
|
|
- // 边界检查:防止seek到超出视频时长的位置
|
|
|
|
|
- int64_t max_ts = cur_stream->ic->duration;
|
|
|
|
|
- if (cur_stream->ic->start_time != AV_NOPTS_VALUE)
|
|
|
|
|
- max_ts += cur_stream->ic->start_time;
|
|
|
|
|
-
|
|
|
|
|
- qDebug() << "[videoSeekEx] 边界检查: ts=" << ts << ", max_ts=" << max_ts
|
|
|
|
|
- << ", duration=" << cur_stream->ic->duration
|
|
|
|
|
- << ", start_time=" << cur_stream->ic->start_time
|
|
|
|
|
- << ", 请求位置(秒)=" << ts / (double) AV_TIME_BASE
|
|
|
|
|
- << ", 最大位置(秒)=" << max_ts / (double) AV_TIME_BASE;
|
|
|
|
|
-
|
|
|
|
|
- // 更保守的边界检查:减去3秒作为安全边界
|
|
|
|
|
- int64_t safe_boundary = 3 * AV_TIME_BASE;
|
|
|
|
|
- if (ts > max_ts - safe_boundary) {
|
|
|
|
|
- qDebug() << "[videoSeekEx] 调整seek位置: 原始ts=" << ts << ", 最大ts=" << max_ts;
|
|
|
|
|
- ts = max_ts - safe_boundary;
|
|
|
|
|
- if (ts < 0)
|
|
|
|
|
- ts = 0;
|
|
|
|
|
- qDebug() << "[videoSeekEx] 调整后ts=" << ts
|
|
|
|
|
- << ", 调整后位置(秒)=" << ts / (double) AV_TIME_BASE;
|
|
|
|
|
|
|
+ // 增强的边界检查:防止seek到超出视频时长的位置
|
|
|
|
|
+ int64_t max_ts = 0;
|
|
|
|
|
+ int64_t min_ts = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 安全地计算时间戳边界
|
|
|
|
|
+ if (cur_stream->ic->duration > 0) {
|
|
|
|
|
+ max_ts = cur_stream->ic->duration;
|
|
|
|
|
+ if (cur_stream->ic->start_time != AV_NOPTS_VALUE && cur_stream->ic->start_time > 0) {
|
|
|
|
|
+ min_ts = cur_stream->ic->start_time;
|
|
|
|
|
+ max_ts += min_ts;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ qCWarning(playerControllerLog) << "[videoSeekEx] Invalid duration, seek operation cancelled";
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeekEx] 边界检查: ts=" << ts
|
|
|
|
|
+ << ", min_ts=" << min_ts << ", max_ts=" << max_ts
|
|
|
|
|
+ << ", duration=" << cur_stream->ic->duration
|
|
|
|
|
+ << ", start_time=" << cur_stream->ic->start_time
|
|
|
|
|
+ << ", 请求位置(秒)=" << ts / (double) AV_TIME_BASE
|
|
|
|
|
+ << ", 最大位置(秒)=" << max_ts / (double) AV_TIME_BASE;
|
|
|
|
|
+
|
|
|
|
|
+ // 更严格的边界检查:减去5秒作为安全边界
|
|
|
|
|
+ int64_t safe_boundary = 5 * AV_TIME_BASE;
|
|
|
|
|
+ if (ts < min_ts) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeekEx] 调整seek位置到最小值: 原始ts=" << ts;
|
|
|
|
|
+ ts = min_ts;
|
|
|
|
|
+ } else if (ts > max_ts - safe_boundary) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeekEx] 调整seek位置到安全范围: 原始ts=" << ts << ", 最大ts=" << max_ts;
|
|
|
|
|
+ ts = std::max(min_ts, max_ts - safe_boundary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[videoSeekEx] 最终ts=" << ts
|
|
|
|
|
+ << ", 最终位置(秒)=" << ts / (double) AV_TIME_BASE;
|
|
|
|
|
+
|
|
|
// // 启用精确帧定位
|
|
// // 启用精确帧定位
|
|
|
// if (cur_stream->video_st) {
|
|
// if (cur_stream->video_st) {
|
|
|
// int64_t target_pts = av_rescale_q(ts,
|
|
// int64_t target_pts = av_rescale_q(ts,
|
|
@@ -592,8 +619,8 @@ void PlayerController::stopAndResetThreads()
|
|
|
<< "] try stop/join thread, isRunning=" << threadPtr->isRunning();
|
|
<< "] try stop/join thread, isRunning=" << threadPtr->isRunning();
|
|
|
threadPtr->stop();
|
|
threadPtr->stop();
|
|
|
|
|
|
|
|
- // 添加超时等待机制
|
|
|
|
|
- const int MAX_WAIT_MS = 500; // 最多等待500毫秒
|
|
|
|
|
|
|
+ // 增加超时等待时间,以便更好地处理FFmpeg清理
|
|
|
|
|
+ const int MAX_WAIT_MS = 3000; // 增加到3秒,给FFmpeg足够的清理时间
|
|
|
auto startTime = std::chrono::steady_clock::now();
|
|
auto startTime = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
while (threadPtr->isRunning()) {
|
|
while (threadPtr->isRunning()) {
|
|
@@ -603,10 +630,11 @@ void PlayerController::stopAndResetThreads()
|
|
|
if (elapsed > MAX_WAIT_MS) {
|
|
if (elapsed > MAX_WAIT_MS) {
|
|
|
qCWarning(playerControllerLog)
|
|
qCWarning(playerControllerLog)
|
|
|
<< "[stopAndReset] [" << threadName << "] Thread stop timeout after"
|
|
<< "[stopAndReset] [" << threadName << "] Thread stop timeout after"
|
|
|
- << elapsed << "ms";
|
|
|
|
|
|
|
+ << elapsed << "ms - forcing termination";
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
|
|
|
+ // 减少轮询频率,降低CPU占用
|
|
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
|
}
|
|
}
|
|
|
// 只有线程已停止才join
|
|
// 只有线程已停止才join
|
|
|
if (!threadPtr->isRunning()) {
|
|
if (!threadPtr->isRunning()) {
|
|
@@ -618,33 +646,47 @@ void PlayerController::stopAndResetThreads()
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // 按依赖顺序停止线程
|
|
|
|
|
|
|
+ // 优化线程停止顺序:先停止播放线程,然后清理FFmpeg状态,再停止解码线程,最后停止读取线程
|
|
|
|
|
+ // 这样确保解码线程在FFmpeg资源清理后能正常退出
|
|
|
|
|
+
|
|
|
|
|
+ // 第一阶段:停止播放线程
|
|
|
stopAndReset(m_beforePlayThread, "BeforePlay");
|
|
stopAndReset(m_beforePlayThread, "BeforePlay");
|
|
|
stopAndReset(m_videoPlayThread, "VideoPlay");
|
|
stopAndReset(m_videoPlayThread, "VideoPlay");
|
|
|
stopAndReset(m_audioPlayThread, "AudioPlay");
|
|
stopAndReset(m_audioPlayThread, "AudioPlay");
|
|
|
-
|
|
|
|
|
- // 解码前先 关闭流 不然会卡死异常
|
|
|
|
|
- // 注意:这里是唯一调用delete_video_state的地方,readPacketStopped不再调用
|
|
|
|
|
- // 以避免重复关闭导致的异常
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 第二阶段:清理FFmpeg状态(关键修复:在解码线程停止前清理)
|
|
|
|
|
+ // 这样解码线程就能检测到状态变化并正常退出
|
|
|
if (m_videoState && m_videoState->get_state()) {
|
|
if (m_videoState && m_videoState->get_state()) {
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[stopAndResetThreads] Cleaning up FFmpeg video state before stopping decode threads";
|
|
|
m_videoState->delete_video_state();
|
|
m_videoState->delete_video_state();
|
|
|
|
|
+
|
|
|
|
|
+ // 给解码线程一点时间检测到FFmpeg状态变化
|
|
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
+ qCDebug(playerControllerLog) << "[stopAndResetThreads] FFmpeg state cleaned, proceeding to stop decode threads";
|
|
|
}
|
|
}
|
|
|
- stopAndReset(m_packetReadThread, "PacketRead");
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 第三阶段:停止解码线程(现在FFmpeg状态已清理,线程应该能正常退出)
|
|
|
stopAndReset(m_decodeVideoThread, "DecodeVideo");
|
|
stopAndReset(m_decodeVideoThread, "DecodeVideo");
|
|
|
stopAndReset(m_decodeAudioThread, "DecodeAudio");
|
|
stopAndReset(m_decodeAudioThread, "DecodeAudio");
|
|
|
stopAndReset(m_decodeSubtitleThread, "DecodeSubtitle");
|
|
stopAndReset(m_decodeSubtitleThread, "DecodeSubtitle");
|
|
|
|
|
+
|
|
|
|
|
+ // 第四阶段:停止读取线程
|
|
|
|
|
+ stopAndReset(m_packetReadThread, "PacketRead");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool PlayerController::areAllThreadsStopped() const
|
|
bool PlayerController::areAllThreadsStopped() const
|
|
|
{
|
|
{
|
|
|
// 检查所有线程是否已停止
|
|
// 检查所有线程是否已停止
|
|
|
- return (!m_packetReadThread || !m_packetReadThread->isRunning())
|
|
|
|
|
- && (!m_decodeVideoThread || !m_decodeVideoThread->isRunning())
|
|
|
|
|
- && (!m_decodeAudioThread || !m_decodeAudioThread->isRunning())
|
|
|
|
|
- && (!m_audioPlayThread || !m_audioPlayThread->isRunning())
|
|
|
|
|
- && (!m_videoPlayThread || !m_videoPlayThread->isRunning())
|
|
|
|
|
- && (!m_decodeSubtitleThread || !m_decodeSubtitleThread->isRunning());
|
|
|
|
|
|
|
+ // 注意:此方法应在持有m_stopMutex锁的情况下调用,以确保线程状态的一致性
|
|
|
|
|
+ bool allStopped = (!m_packetReadThread || !m_packetReadThread->isRunning())
|
|
|
|
|
+ && (!m_decodeVideoThread || !m_decodeVideoThread->isRunning())
|
|
|
|
|
+ && (!m_decodeAudioThread || !m_decodeAudioThread->isRunning())
|
|
|
|
|
+ && (!m_audioPlayThread || !m_audioPlayThread->isRunning())
|
|
|
|
|
+ && (!m_videoPlayThread || !m_videoPlayThread->isRunning())
|
|
|
|
|
+ && (!m_decodeSubtitleThread || !m_decodeSubtitleThread->isRunning())
|
|
|
|
|
+ && (!m_beforePlayThread || !m_beforePlayThread->isRunning());
|
|
|
|
|
+
|
|
|
|
|
+ return allStopped;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void PlayerController::allThreadStart()
|
|
void PlayerController::allThreadStart()
|