fix: sync danmaku and buffer updates with mpv

This commit is contained in:
dela
2026-06-22 13:49:35 +08:00
parent 9a5e04d281
commit 53ef34195e
7 changed files with 113 additions and 103 deletions
@@ -217,6 +217,7 @@ bool MpvController::init() {
observeProperty("eof-reached", MPV_FORMAT_FLAG); observeProperty("eof-reached", MPV_FORMAT_FLAG);
observeProperty("cache-speed", MPV_FORMAT_INT64); observeProperty("cache-speed", MPV_FORMAT_INT64);
observeProperty("demuxer-cache-time", MPV_FORMAT_DOUBLE);
observeProperty("volume", MPV_FORMAT_DOUBLE); observeProperty("volume", MPV_FORMAT_DOUBLE);
observeProperty("mute", MPV_FORMAT_FLAG); observeProperty("mute", MPV_FORMAT_FLAG);
observeProperty("speed", MPV_FORMAT_DOUBLE); observeProperty("speed", MPV_FORMAT_DOUBLE);
+1
View File
@@ -120,6 +120,7 @@ void MpvWidget::onMpvRenderUpdate(void *ctx) {
} }
void MpvWidget::maybeUpdate() { void MpvWidget::maybeUpdate() {
Q_EMIT renderUpdateRequested();
update(); update();
} }
+1
View File
@@ -33,6 +33,7 @@ signals:
void playbackStateChanged(bool isPaused); void playbackStateChanged(bool isPaused);
void networkSpeedChanged(qint64 bytesPerSecond); void networkSpeedChanged(qint64 bytesPerSecond);
void relayActiveChanged(bool active); void relayActiveChanged(bool active);
void renderUpdateRequested();
void errorOccurred(const QString &errorMsg); void errorOccurred(const QString &errorMsg);
protected: protected:
@@ -155,16 +155,8 @@ NativeDanmakuOverlay::NativeDanmakuOverlay(MpvController *controller,
m_frameTimer = new QTimer(this); m_frameTimer = new QTimer(this);
m_frameTimer->setTimerType(Qt::PreciseTimer); m_frameTimer->setTimerType(Qt::PreciseTimer);
m_frameTimer->setInterval(kFallbackFrameIntervalMs); m_frameTimer->setInterval(kFallbackFrameIntervalMs);
connect(m_frameTimer, &QTimer::timeout, this, [this]() { connect(m_frameTimer, &QTimer::timeout, this,
const qint64 positionMs = currentPositionMs(); &NativeDanmakuOverlay::advanceFrame);
if (m_lastResolvedPositionMs < 0 || positionMs < m_lastResolvedPositionMs ||
positionMs - m_lastResolvedPositionMs > 1500) {
rebuildActiveItems(positionMs);
} else {
updateActiveItems(positionMs);
}
update();
});
m_scheduleRebuildTimer = new QTimer(this); m_scheduleRebuildTimer = new QTimer(this);
m_scheduleRebuildTimer->setSingleShot(true); m_scheduleRebuildTimer->setSingleShot(true);
@@ -279,6 +271,32 @@ void NativeDanmakuOverlay::setBottomSubtitleProtected(bool enabled)
requestScheduleRebuild(false, false); requestScheduleRebuild(false, false);
} }
void NativeDanmakuOverlay::setExternalFrameClockEnabled(bool enabled)
{
if (m_externalFrameClockEnabled == enabled) {
return;
}
m_externalFrameClockEnabled = enabled;
syncFrameTimer();
}
void NativeDanmakuOverlay::advanceFrame()
{
if (!shouldAdvanceFrames()) {
return;
}
const qint64 positionMs = currentPositionMs();
if (m_lastResolvedPositionMs < 0 || positionMs < m_lastResolvedPositionMs ||
positionMs - m_lastResolvedPositionMs > 1500) {
rebuildActiveItems(positionMs);
} else {
updateActiveItems(positionMs);
}
update();
}
void NativeDanmakuOverlay::paintEvent(QPaintEvent *event) void NativeDanmakuOverlay::paintEvent(QPaintEvent *event)
{ {
QPainter painter(this); QPainter painter(this);
@@ -649,15 +667,17 @@ void NativeDanmakuOverlay::clearScheduledState(bool clearSpriteCache)
void NativeDanmakuOverlay::syncFrameTimer() void NativeDanmakuOverlay::syncFrameTimer()
{ {
if (m_externalFrameClockEnabled) {
m_frameTimer->stop();
return;
}
const int targetInterval = targetFrameIntervalMs(); const int targetInterval = targetFrameIntervalMs();
if (m_frameTimer->interval() != targetInterval) { if (m_frameTimer->interval() != targetInterval) {
m_frameTimer->setInterval(targetInterval); m_frameTimer->setInterval(targetInterval);
} }
const bool shouldRun = if (shouldAdvanceFrames()) {
m_visibleRequested && isVisible() && !m_schedule.isEmpty() &&
!m_playbackPaused;
if (shouldRun) {
if (!m_frameTimer->isActive()) { if (!m_frameTimer->isActive()) {
m_frameTimer->start(); m_frameTimer->start();
} }
@@ -667,6 +687,12 @@ void NativeDanmakuOverlay::syncFrameTimer()
m_frameTimer->stop(); m_frameTimer->stop();
} }
bool NativeDanmakuOverlay::shouldAdvanceFrames() const
{
return m_visibleRequested && isVisible() && !m_schedule.isEmpty() &&
!m_playbackPaused;
}
int NativeDanmakuOverlay::targetFrameIntervalMs() const int NativeDanmakuOverlay::targetFrameIntervalMs() const
{ {
const QScreen *screen = targetScreen(); const QScreen *screen = targetScreen();
@@ -35,6 +35,8 @@ public:
void setDanmakuVisible(bool visible); void setDanmakuVisible(bool visible);
void setBottomSubtitleProtected(bool enabled); void setBottomSubtitleProtected(bool enabled);
void setExternalFrameClockEnabled(bool enabled);
void advanceFrame();
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
@@ -70,6 +72,7 @@ private:
void updateActiveItems(qint64 positionMs); void updateActiveItems(qint64 positionMs);
void clearScheduledState(bool clearSpriteCache = false); void clearScheduledState(bool clearSpriteCache = false);
void syncFrameTimer(); void syncFrameTimer();
bool shouldAdvanceFrames() const;
int targetFrameIntervalMs() const; int targetFrameIntervalMs() const;
QScreen *targetScreen() const; QScreen *targetScreen() const;
void updateScreenRefreshBinding(); void updateScreenRefreshBinding();
@@ -125,6 +128,7 @@ private:
bool m_bottomSubtitleProtected = false; bool m_bottomSubtitleProtected = false;
bool m_scheduleDirty = false; bool m_scheduleDirty = false;
bool m_clearSpriteCacheOnNextRebuild = false; bool m_clearSpriteCacheOnNextRebuild = false;
bool m_externalFrameClockEnabled = false;
mutable bool m_surfaceRectDirty = true; mutable bool m_surfaceRectDirty = true;
}; };
+63 -87
View File
@@ -384,93 +384,19 @@ PlayerView::PlayerView(QEmbyCore *core, QWidget *parent)
m_bufferTimer = new QTimer(this); m_bufferTimer = new QTimer(this);
m_bufferTimer->setInterval(500); m_bufferTimer->setInterval(250);
connect(m_bufferTimer, &QTimer::timeout, this, connect(m_bufferTimer, &QTimer::timeout, this,
[this]() [this]()
{ {
if (m_totalDuration > 0 && m_mpvWidget && m_mpvWidget->controller()) updateBufferProgress();
{
double bufferedPos = m_currentPosition;
QVariant stateVar = m_mpvWidget->controller()->getProperty("demuxer-cache-state");
if (stateVar.isValid() && stateVar.metaType().id() == QMetaType::QVariantMap)
{
auto ranges = stateVar.toMap()["seekable-ranges"].toList();
if (!ranges.isEmpty())
{
bufferedPos = ranges.last().toMap()["end"].toDouble();
}
}
else
{
double cacheDuration =
m_mpvWidget->controller()->getProperty("demuxer-cache-duration").toDouble();
if (cacheDuration > 0)
{
bufferedPos = m_currentPosition + cacheDuration;
}
}
if (bufferedPos > m_totalDuration)
{
bufferedPos = m_totalDuration;
}
m_progressSlider->setBufferValue(static_cast<int>(bufferedPos));
}
if (m_showStatisticsOverlay && m_statisticsOverlay) if (m_showStatisticsOverlay && m_statisticsOverlay)
{ {
updateStatisticsDisplay(); m_bufferTimerTick = (m_bufferTimerTick + 1) % 2;
} if (m_bufferTimerTick == 0)
});
m_mousePollTimer = new QTimer(this);
m_mousePollTimer->setInterval(100);
connect(m_mousePollTimer, &QTimer::timeout, this,
[this]()
{
if (!m_isPlaying)
{
if (m_topOpacity->opacity() < 1.0)
{ {
showControls(); updateStatisticsDisplay();
} }
return;
}
if (areControlsFullyVisible() && this->cursor().shape() != Qt::BlankCursor &&
(!m_mpvWidget || m_mpvWidget->cursor().shape() != Qt::BlankCursor))
{
return;
}
QPoint currentPos = QCursor::pos();
if (currentPos == m_lastMousePos)
{
return;
}
m_lastMousePos = currentPos;
if (this->window()->isMinimized() || !this->isVisible())
{
return;
}
QPoint localPos = this->mapFromGlobal(currentPos);
bool isMouseInside = this->rect().contains(localPos);
if (isMouseInside)
{
handlePointerActivity(currentPos);
} }
}); });
@@ -1118,6 +1044,9 @@ void PlayerView::setupUi()
m_mpvWidget->installEventFilter(this); m_mpvWidget->installEventFilter(this);
m_mpvWidget->setMouseTracking(true); m_mpvWidget->setMouseTracking(true);
m_nativeDanmakuOverlay = new NativeDanmakuOverlay(m_mpvWidget->controller(), this); m_nativeDanmakuOverlay = new NativeDanmakuOverlay(m_mpvWidget->controller(), this);
m_nativeDanmakuOverlay->setExternalFrameClockEnabled(true);
connect(m_mpvWidget, &MpvWidget::renderUpdateRequested, m_nativeDanmakuOverlay,
&NativeDanmakuOverlay::advanceFrame);
m_danmakuController = new PlayerDanmakuController(m_core, m_mpvWidget, m_nativeDanmakuOverlay, this); m_danmakuController = new PlayerDanmakuController(m_core, m_mpvWidget, m_nativeDanmakuOverlay, this);
connect(m_danmakuController, &PlayerDanmakuController::toastRequested, this, connect(m_danmakuController, &PlayerDanmakuController::toastRequested, this,
[this](const QString &message) { showToast(message); }); [this](const QString &message) { showToast(message); });
@@ -2419,8 +2348,6 @@ void PlayerView::stopAndReport()
if (m_reportTimer) if (m_reportTimer)
m_reportTimer->stop(); m_reportTimer->stop();
if (m_mousePollTimer)
m_mousePollTimer->stop();
m_longPressHandler->stopKeyLongPress(false); m_longPressHandler->stopKeyLongPress(false);
m_longPressHandler->stopMouseEdgeLongPress(); m_longPressHandler->stopMouseEdgeLongPress();
if (m_bufferTimer) if (m_bufferTimer)
@@ -2512,8 +2439,6 @@ void PlayerView::beginViewTeardown()
if (m_reportTimer) if (m_reportTimer)
m_reportTimer->stop(); m_reportTimer->stop();
if (m_mousePollTimer)
m_mousePollTimer->stop();
if (m_bufferTimer) if (m_bufferTimer)
m_bufferTimer->stop(); m_bufferTimer->stop();
if (m_hideTimer) if (m_hideTimer)
@@ -2974,8 +2899,7 @@ bool PlayerView::eventFilter(QObject *watched, QEvent *event)
else if (event->type() == QEvent::MouseMove) else if (event->type() == QEvent::MouseMove)
{ {
auto *me = static_cast<QMouseEvent *>(event); auto *me = static_cast<QMouseEvent *>(event);
m_lastMousePos = me->globalPosition().toPoint(); handlePointerActivity(me->globalPosition().toPoint());
handlePointerActivity(m_lastMousePos);
const QPoint localPos = this->mapFromGlobal(me->globalPosition().toPoint()); const QPoint localPos = this->mapFromGlobal(me->globalPosition().toPoint());
const bool isOnHud = m_topHUD->geometry().contains(localPos) || m_bottomHUD->geometry().contains(localPos); const bool isOnHud = m_topHUD->geometry().contains(localPos) || m_bottomHUD->geometry().contains(localPos);
const bool movedEnough = const bool movedEnough =
@@ -3044,6 +2968,10 @@ bool PlayerView::eventFilter(QObject *watched, QEvent *event)
return true; return true;
} }
} }
else if (event->type() == QEvent::Enter)
{
handlePointerActivity(QCursor::pos());
}
} }
@@ -3121,6 +3049,30 @@ void PlayerView::handlePointerActivity(const QPoint &globalPos)
showControls(); showControls();
} }
void PlayerView::updateBufferProgress()
{
if (!m_progressSlider || m_totalDuration <= 0)
{
return;
}
double bufferedPos = m_bufferedPosition;
if (std::isnan(bufferedPos) || std::isinf(bufferedPos) || bufferedPos <= 0.0)
{
bufferedPos = m_currentPosition;
}
if (bufferedPos < m_currentPosition)
{
bufferedPos = m_currentPosition;
}
if (bufferedPos > m_totalDuration)
{
bufferedPos = m_totalDuration;
}
m_progressSlider->setBufferValue(static_cast<int>(bufferedPos));
}
void PlayerView::setCursorHidden(bool hidden) void PlayerView::setCursorHidden(bool hidden)
{ {
const auto targetShape = hidden ? Qt::BlankCursor : Qt::ArrowCursor; const auto targetShape = hidden ? Qt::BlankCursor : Qt::ArrowCursor;
@@ -3363,8 +3315,22 @@ void PlayerView::onMpvPropertyChanged(const QString &property, const QVariant &v
else if (property == "seeking") else if (property == "seeking")
{ {
const bool wasSeeking = m_isSeeking;
m_isSeeking = value.toBool(); m_isSeeking = value.toBool();
updateLoadingState(); updateLoadingState();
if (wasSeeking && !m_isSeeking)
{
QTimer::singleShot(0, this, [this]() { reportProgressToServer(); });
}
}
else if (property == "demuxer-cache-time")
{
const double cacheTime = value.toDouble();
if (!std::isnan(cacheTime) && !std::isinf(cacheTime) && cacheTime >= 0.0)
{
m_bufferedPosition = cacheTime;
updateBufferProgress();
}
} }
else if (property == "eof-reached") else if (property == "eof-reached")
{ {
@@ -4686,6 +4652,8 @@ void PlayerView::playMedia(const QString &mediaId, const QString &title, const Q
m_hasReportedStop = false; m_hasReportedStop = false;
m_isPlaybackFinished = false; m_isPlaybackFinished = false;
m_autoPlayAdvanceInProgress = false; m_autoPlayAdvanceInProgress = false;
m_bufferedPosition = 0.0;
m_bufferTimerTick = 0;
m_currentMediaId = mediaId; m_currentMediaId = mediaId;
m_currentMediaSourceId = mediaId; m_currentMediaSourceId = mediaId;
m_currentPlaySessionId.clear(); m_currentPlaySessionId.clear();
@@ -4776,6 +4744,7 @@ void PlayerView::playMedia(const QString &mediaId, const QString &title, const Q
m_currentPosition = m_pendingSeekSeconds; m_currentPosition = m_pendingSeekSeconds;
m_totalDuration = 0.0; m_totalDuration = 0.0;
updateBufferProgress();
if (m_statisticsOverlay) if (m_statisticsOverlay)
{ {
@@ -5109,7 +5078,6 @@ void PlayerView::playMedia(const QString &mediaId, const QString &title, const Q
startPositionTicks); startPositionTicks);
m_reportTimer->start(); m_reportTimer->start();
m_mousePollTimer->start();
m_bufferTimer->start(); m_bufferTimer->start();
showControls(); showControls();
} }
@@ -5141,7 +5109,15 @@ void PlayerView::onBackClicked()
prepareForStackLeave(); prepareForStackLeave();
Q_EMIT navigateBack(); QTimer::singleShot(0, this,
[this]()
{
if (!m_hasReportedStop)
{
return;
}
Q_EMIT navigateBack();
});
+3 -2
View File
@@ -145,6 +145,7 @@ private:
void setCursorHidden(bool hidden); void setCursorHidden(bool hidden);
bool areControlsFullyVisible() const; bool areControlsFullyVisible() const;
void setPlayerChromeVisible(bool visible); void setPlayerChromeVisible(bool visible);
void updateBufferProgress();
void stopTransientUiAnimations(bool immediate = false); void stopTransientUiAnimations(bool immediate = false);
void beginViewTeardown(); void beginViewTeardown();
@@ -261,10 +262,10 @@ private:
QParallelAnimationGroup *m_fadeGroup; QParallelAnimationGroup *m_fadeGroup;
QTimer *m_hideTimer; QTimer *m_hideTimer;
QTimer *m_reportTimer; QTimer *m_reportTimer;
QTimer *m_mousePollTimer;
QTimer *m_bufferTimer; QTimer *m_bufferTimer;
int m_bufferTimerTick = 0;
QString m_currentMediaId; QString m_currentMediaId;
QString m_currentMediaSourceId; QString m_currentMediaSourceId;
@@ -283,6 +284,7 @@ private:
double m_currentPosition; double m_currentPosition;
double m_totalDuration = 0.0; double m_totalDuration = 0.0;
double m_bufferedPosition = 0.0;
double m_pendingSeekSeconds = 0.0; double m_pendingSeekSeconds = 0.0;
double m_osdSeekPreviewPosition = -1.0; double m_osdSeekPreviewPosition = -1.0;
@@ -333,7 +335,6 @@ private:
QRect m_originalGeometry; QRect m_originalGeometry;
bool m_wasMaximized = false; bool m_wasMaximized = false;
QPoint m_dragPos; QPoint m_dragPos;
QPoint m_lastMousePos;
bool m_didDrag = false; bool m_didDrag = false;
QTimer *m_singleClickTimer = nullptr; QTimer *m_singleClickTimer = nullptr;