|
|
@@ -366,6 +366,7 @@ void PlayerController::playStarted(bool success)
|
|
|
|
|
|
void PlayerController::playFailed(const QString& file)
|
|
|
{
|
|
|
+ dump();
|
|
|
qCWarning(playerControllerLog) << "Playback failed for file:" << toNativePath(file);
|
|
|
|
|
|
// 确保状态一致性
|
|
|
@@ -383,39 +384,49 @@ void PlayerController::playFailed(const QString& file)
|
|
|
// 线程 finished 槽函数只做日志和信号
|
|
|
void PlayerController::readPacketStopped()
|
|
|
{
|
|
|
+ dump();
|
|
|
qCDebug(playerControllerLog) << "************* Read packets thread stopped signal received.";
|
|
|
//m_packetReadThread.reset();
|
|
|
|
|
|
- if (m_videoState) {
|
|
|
- m_videoState->delete_video_state();
|
|
|
+ auto state = m_videoState->get_state();
|
|
|
+ if (state) {
|
|
|
+ state->abort_request = 1;
|
|
|
}
|
|
|
+ // if (m_videoState) {
|
|
|
+ // m_videoState->delete_video_state();
|
|
|
+ // }
|
|
|
|
|
|
emit audioStopped();
|
|
|
}
|
|
|
|
|
|
void PlayerController::decodeVideoStopped()
|
|
|
{
|
|
|
+ dump();
|
|
|
qCDebug(playerControllerLog) << "************* Video decode thread stopped.";
|
|
|
}
|
|
|
|
|
|
void PlayerController::decodeAudioStopped()
|
|
|
{
|
|
|
+ dump();
|
|
|
qCDebug(playerControllerLog) << "************* Audio decode thread stopped.";
|
|
|
}
|
|
|
|
|
|
void PlayerController::decodeSubtitleStopped()
|
|
|
{
|
|
|
+ dump();
|
|
|
qCDebug(playerControllerLog) << "************* Subtitle decode thread stopped.";
|
|
|
}
|
|
|
|
|
|
void PlayerController::audioPlayStopped()
|
|
|
{
|
|
|
+ dump();
|
|
|
qCDebug(playerControllerLog) << "************* Audio play thread stopped.";
|
|
|
emit audioStopped();
|
|
|
}
|
|
|
|
|
|
void PlayerController::videoPlayStopped()
|
|
|
{
|
|
|
+ dump();
|
|
|
qCDebug(playerControllerLog) << "************* Video play thread stopped.";
|
|
|
emit videoStopped();
|
|
|
}
|
|
|
@@ -456,11 +467,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;
|
|
|
+
|
|
|
+ // 更保守的边界检查:减去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;
|
|
|
+ }
|
|
|
+
|
|
|
// 添加量化操作,精确到0.01秒
|
|
|
- position = round(position * 100) / 100.0;
|
|
|
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);
|
|
|
+
|
|
|
+ // 启用精确帧定位
|
|
|
+ int64_t target_pts = static_cast<int64_t>(position * AV_TIME_BASE);
|
|
|
+ // if (state->video_st) {
|
|
|
+ // // 将目标时间转换为视频流的时间基准
|
|
|
+ // target_pts = av_rescale_q(target_pts,
|
|
|
+ // AV_TIME_BASE_Q,
|
|
|
+ // state->video_st->time_base);
|
|
|
+ // qDebug() << "[精确帧定位] 设置目标PTS:" << target_pts
|
|
|
+ // << "原始位置(秒):" << position
|
|
|
+ // << "视频时间基准:" << state->video_st->time_base.num << "/" << state->video_st->time_base.den;
|
|
|
+ // state->exact_seek = 1;
|
|
|
+ // state->target_pts = target_pts;
|
|
|
+ // }
|
|
|
+
|
|
|
stream_seek(state,
|
|
|
static_cast<int64_t>(position * AV_TIME_BASE),
|
|
|
static_cast<int64_t>(increment * AV_TIME_BASE),
|
|
|
@@ -516,6 +561,41 @@ void PlayerController::videoSeekEx(double value, double maxValue)
|
|
|
ts = frac * cur_stream->ic->duration;
|
|
|
if (cur_stream->ic->start_time != AV_NOPTS_VALUE)
|
|
|
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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // // 启用精确帧定位
|
|
|
+ // if (cur_stream->video_st) {
|
|
|
+ // int64_t target_pts = av_rescale_q(ts,
|
|
|
+ // AV_TIME_BASE_Q,
|
|
|
+ // cur_stream->video_st->time_base);
|
|
|
+ // qDebug() << "[精确帧定位Ex] 设置目标PTS:" << target_pts
|
|
|
+ // << "原始位置(秒):" << ts / (double)AV_TIME_BASE
|
|
|
+ // << "视频时间基准:" << cur_stream->video_st->time_base.num << "/" << cur_stream->video_st->time_base.den;
|
|
|
+ // state->exact_seek = 1;
|
|
|
+ // state->target_pts = target_pts;
|
|
|
+ // }
|
|
|
+
|
|
|
stream_seek(cur_stream, ts, 0, 0);
|
|
|
}
|
|
|
return;
|
|
|
@@ -566,11 +646,15 @@ void PlayerController::stopAndResetThreads()
|
|
|
if (m_videoState) {
|
|
|
m_videoState->delete_video_state();
|
|
|
}
|
|
|
+ // auto state = m_videoState->get_state();
|
|
|
+ // if (state) {
|
|
|
+ // state->abort_request = 1;
|
|
|
+ // }
|
|
|
+ stopAndReset(m_packetReadThread, "PacketRead");
|
|
|
|
|
|
stopAndReset(m_decodeVideoThread, "DecodeVideo");
|
|
|
stopAndReset(m_decodeAudioThread, "DecodeAudio");
|
|
|
stopAndReset(m_decodeSubtitleThread, "DecodeSubtitle");
|
|
|
- stopAndReset(m_packetReadThread, "PacketRead");
|
|
|
}
|
|
|
|
|
|
bool PlayerController::areAllThreadsStopped() const
|
|
|
@@ -827,7 +911,7 @@ bool PlayerController::createAudioPlayThread()
|
|
|
m_audioPlayThread->setOnFinished([this]() { audioPlayStopped(); });
|
|
|
m_audioPlayThread->setOnUpdatePlayTime([this]() {
|
|
|
// TODO: 实现 PlayerController::onUpdatePlayTime() 处理播放时间更新
|
|
|
- //emit updatePlayTime();
|
|
|
+ emit updatePlayTime();
|
|
|
});
|
|
|
m_audioPlayThread->setOnDataVisualReady([this](const AudioData& data) {
|
|
|
// 异步 ?
|
|
|
@@ -885,3 +969,89 @@ void PlayerController::onFrameReady(AVFrame* frame)
|
|
|
// 这里可以做帧处理、缓存、同步等操作
|
|
|
emit frameReady(frame); // 直接转发给 UI 层
|
|
|
}
|
|
|
+
|
|
|
+void PlayerController::dump() const
|
|
|
+{
|
|
|
+ qCInfo(playerControllerLog) << "=== PlayerController Thread Status Dump ===";
|
|
|
+ qCInfo(playerControllerLog) << "Current State:" << static_cast<int>(m_state.load());
|
|
|
+ qCInfo(playerControllerLog) << "Current File:" << m_currentFile;
|
|
|
+
|
|
|
+ // 检查数据包读取线程
|
|
|
+ if (m_packetReadThread) {
|
|
|
+ qCInfo(playerControllerLog)
|
|
|
+ << "ReadThread: exists, isRunning:" << m_packetReadThread->isRunning()
|
|
|
+ << ", isFinished:" << m_packetReadThread->isExit();
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "ReadThread: null";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查视频解码线程
|
|
|
+ if (m_decodeVideoThread) {
|
|
|
+ qCInfo(playerControllerLog)
|
|
|
+ << "VideoDecodeThread: exists, isRunning:" << m_decodeVideoThread->isRunning()
|
|
|
+ << ", isFinished:" << m_decodeVideoThread->isExit();
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "VideoDecodeThread: null";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查音频解码线程
|
|
|
+ if (m_decodeAudioThread) {
|
|
|
+ qCInfo(playerControllerLog)
|
|
|
+ << "AudioDecodeThread: exists, isRunning:" << m_decodeAudioThread->isRunning()
|
|
|
+ << ", isFinished:" << m_decodeAudioThread->isExit();
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "AudioDecodeThread: null";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查字幕解码线程
|
|
|
+ if (m_decodeSubtitleThread) {
|
|
|
+ qCInfo(playerControllerLog)
|
|
|
+ << "SubtitleDecodeThread: exists, isRunning:" << m_decodeSubtitleThread->isRunning()
|
|
|
+ << ", isFinished:" << m_decodeSubtitleThread->isExit();
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "SubtitleDecodeThread: null";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查音频播放线程
|
|
|
+ if (m_audioPlayThread) {
|
|
|
+ qCInfo(playerControllerLog)
|
|
|
+ << "AudioPlayThread: exists, isRunning:" << m_audioPlayThread->isRunning()
|
|
|
+ << ", isFinished:" << m_audioPlayThread->isExit();
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "AudioPlayThread: null";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查视频播放线程
|
|
|
+ if (m_videoPlayThread) {
|
|
|
+ qCInfo(playerControllerLog)
|
|
|
+ << "VideoPlayThread: exists, isRunning:" << m_videoPlayThread->isRunning()
|
|
|
+ << ", isFinished:" << m_videoPlayThread->isExit();
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "VideoPlayThread: null";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查播放前准备线程
|
|
|
+ if (m_beforePlayThread) {
|
|
|
+ qCInfo(playerControllerLog)
|
|
|
+ << "StartPlayThread: exists, isRunning:" << m_beforePlayThread->isRunning()
|
|
|
+ << ", isFinished:" << m_beforePlayThread->isExit();
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "StartPlayThread: null";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查初始化线程
|
|
|
+ if (m_initThread.joinable()) {
|
|
|
+ qCInfo(playerControllerLog) << "InitThread: joinable (running)";
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "InitThread: not joinable (stopped or not started)";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查事件线程
|
|
|
+ if (m_eventThread.joinable()) {
|
|
|
+ qCInfo(playerControllerLog) << "EventThread: joinable (running)";
|
|
|
+ } else {
|
|
|
+ qCInfo(playerControllerLog) << "EventThread: not joinable (stopped or not started)";
|
|
|
+ }
|
|
|
+
|
|
|
+ qCInfo(playerControllerLog) << "=== End of Thread Status Dump ===";
|
|
|
+}
|