From 93c548c013fdbafc46d9837e9e722db980c51f14 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 21 Feb 2019 20:01:55 +0400 Subject: [PATCH] Support streaming speed changing. --- Telegram/SourceFiles/data/data_document.cpp | 131 +++++++++++------- .../SourceFiles/media/audio/media_audio.cpp | 95 ++++++++++--- .../SourceFiles/media/audio/media_audio.h | 17 ++- .../media/player/media_player_widget.cpp | 7 +- .../streaming/media_streaming_audio_track.cpp | 5 + .../streaming/media_streaming_audio_track.h | 6 +- .../media/streaming/media_streaming_common.h | 8 ++ .../streaming/media_streaming_player.cpp | 35 ++++- .../media/streaming/media_streaming_player.h | 6 + .../media/streaming/media_streaming_utility.h | 2 +- .../streaming/media_streaming_video_track.cpp | 97 ++++++++----- .../streaming/media_streaming_video_track.h | 3 + 12 files changed, 296 insertions(+), 116 deletions(-) diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 31f49ee356..99fba8d8ea 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -295,6 +295,12 @@ void StartStreaming( using namespace Media::Streaming; if (auto loader = document->createStreamingLoader(origin)) { static auto player = std::unique_ptr(); + static auto pauseOnSeek = false; + static auto duration = crl::time(0); + static auto options = Media::Streaming::PlaybackOptions(); + static auto subscribe = Fn(); + static auto speed = 1.; + static auto step = pow(2., 1. / 12); class Panel #if defined Q_OS_MAC && !defined OS_MAC_OLD @@ -319,68 +325,97 @@ void StartStreaming( } else { player->pause(); } + } else if (e->key() == Qt::Key_Plus) { + speed = std::min(speed * step, 2.); + player->setSpeed(speed); + } else if (e->key() == Qt::Key_Minus) { + speed = std::max(speed / step, 0.5); + player->setSpeed(speed); } } + void mousePressEvent(QMouseEvent *e) override { + pauseOnSeek = player->paused(); + player->pause(); + } + void mouseReleaseEvent(QMouseEvent *e) override { + options.position = std::clamp( + (duration * e->pos().x()) / width(), + crl::time(0), + crl::time(duration)); + player->play(options); + subscribe(); + } }; static auto video = base::unique_qptr(); + subscribe = [] { + player->updates( + ) | rpl::start_with_next_error_done([=](Update &&update) { + update.data.match([&](Information &update) { + duration = update.video.state.duration; + if (!video && !update.video.cover.isNull()) { + video = base::make_unique_q(); + video->setAttribute(Qt::WA_OpaquePaintEvent); + video->paintRequest( + ) | rpl::start_with_next([=](QRect rect) { + if (player->ready()) { + Painter(video.get()).drawImage( + video->rect(), + player->frame(FrameRequest())); + } else { + Painter(video.get()).fillRect( + rect, + Qt::black); + } + }, video->lifetime()); + const auto size = QSize( + ConvertScale(update.video.size.width()), + ConvertScale(update.video.size.height())); + const auto center = App::wnd()->geometry().center(); + video->setGeometry(QRect( + center - QPoint(size.width(), size.height()) / 2, + size)); + video->show(); + video->shownValue( + ) | rpl::start_with_next([=](bool shown) { + if (!shown) { + base::take(player) = nullptr; + } + }, video->lifetime()); + } + }, [&](PreloadedVideo &update) { + }, [&](UpdateVideo &update) { + Expects(video != nullptr); + + video->update(); + }, [&](PreloadedAudio &update) { + }, [&](UpdateAudio &update) { + }, [&](WaitingForData &update) { + }, [&](MutedByOther &update) { + }); + }, [=](const Error &error) { + base::take(video) = nullptr; + }, [=] { + }, player->lifetime()); + }; + player = std::make_unique( &document->owner(), std::move(loader)); - video = nullptr; - document->session().lifetime().add([] { - base::take(player) = nullptr; + base::take(video) = nullptr; + player->lifetime().add([] { base::take(video) = nullptr; }); + document->session().lifetime().add([] { + base::take(player) = nullptr; + }); - auto options = Media::Streaming::PlaybackOptions(); - options.speed = 1.7; + options.speed = speed; //options.syncVideoByAudio = false; - options.position = (document->duration() / 2) * crl::time(1000); + options.position = 0; player->play(options); - player->updates( - ) | rpl::start_with_next_error_done([=](Update &&update) { - update.data.match([&](Information &update) { - if (!update.video.cover.isNull()) { - video = base::make_unique_q(); - video->setAttribute(Qt::WA_OpaquePaintEvent); - video->paintRequest( - ) | rpl::start_with_next([=](QRect rect) { - Painter(video.get()).drawImage( - video->rect(), - player->frame(FrameRequest())); - }, video->lifetime()); - const auto size = QSize( - ConvertScale(update.video.size.width()), - ConvertScale(update.video.size.height())); - const auto center = App::wnd()->geometry().center(); - video->setGeometry(QRect( - center - QPoint(size.width(), size.height()) / 2, - size)); - video->show(); - video->shownValue( - ) | rpl::start_with_next([=](bool shown) { - if (!shown) { - base::take(player) = nullptr; - } - }, video->lifetime()); - } - }, [&](PreloadedVideo &update) { - }, [&](UpdateVideo &update) { - Expects(video != nullptr); - - video->update(); - }, [&](PreloadedAudio &update) { - }, [&](UpdateAudio &update) { - }, [&](WaitingForData &update) { - }, [&](MutedByOther &update) { - }); - }, [=](const Error &error) { - base::take(video) = nullptr; - }, [=] { - base::take(video) = nullptr; - }, player->lifetime()); + subscribe(); } } diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index b8f8267194..71631b2167 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -303,6 +303,14 @@ void StopDetachIfNotUsedSafe() { }); } +bool SupportsSpeedControl() { +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS + return true; +#else // TDESKTOP_DISABLE_OPENAL_EFFECTS + return false; +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS +} + } // namespace Audio namespace Player { @@ -347,24 +355,39 @@ void Mixer::Track::createStream(AudioMsgId::Type type) { alGenBuffers(3, stream.buffers); if (type == AudioMsgId::Type::Voice) { mixer()->updatePlaybackSpeed(this); +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS } else if (speedEffect) { + applySourceSpeedEffect(); + } else { + removeSourceSpeedEffect(); +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS + } +} + +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS +void Mixer::Track::removeSourceSpeedEffect() { + alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, 0); + alSourcei(stream.source, AL_DIRECT_FILTER, AL_FILTER_NULL); +} + +void Mixer::Track::applySourceSpeedEffect() { + Expects(speedEffect != nullptr); + + if (!speedEffect->effect || !alIsEffect(speedEffect->effect)) { alGenAuxiliaryEffectSlots(1, &speedEffect->effectSlot); alGenEffects(1, &speedEffect->effect); alGenFilters(1, &speedEffect->filter); alEffecti(speedEffect->effect, AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER); - alEffecti(speedEffect->effect, AL_PITCH_SHIFTER_COARSE_TUNE, speedEffect->coarseTune); - alAuxiliaryEffectSloti(speedEffect->effectSlot, AL_EFFECTSLOT_EFFECT, speedEffect->effect); alFilteri(speedEffect->filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); alFilterf(speedEffect->filter, AL_LOWPASS_GAIN, 0.f); - - alSourcef(stream.source, AL_PITCH, speedEffect->speed); - alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, speedEffect->effectSlot, 0, 0); - alSourcei(stream.source, AL_DIRECT_FILTER, speedEffect->filter); - } else { - alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, 0); - alSourcei(stream.source, AL_DIRECT_FILTER, AL_FILTER_NULL); } + alEffecti(speedEffect->effect, AL_PITCH_SHIFTER_COARSE_TUNE, speedEffect->coarseTune); + alAuxiliaryEffectSloti(speedEffect->effectSlot, AL_EFFECTSLOT_EFFECT, speedEffect->effect); + alSourcef(stream.source, AL_PITCH, speedEffect->speed); + alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, speedEffect->effectSlot, 0, 0); + alSourcei(stream.source, AL_DIRECT_FILTER, speedEffect->filter); } +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS void Mixer::Track::destroyStream() { if (isStreamCreated()) { @@ -375,19 +398,28 @@ void Mixer::Track::destroyStream() { for (auto i = 0; i != 3; ++i) { stream.buffers[i] = 0; } +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS resetSpeedEffect(); +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS } +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS void Mixer::Track::resetSpeedEffect() { if (!speedEffect) { return; - } else if (alIsEffect(speedEffect->effect)) { + } else if (speedEffect->effect && alIsEffect(speedEffect->effect)) { + if (isStreamCreated()) { + removeSourceSpeedEffect(); + } +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS alDeleteEffects(1, &speedEffect->effect); alDeleteAuxiliaryEffectSlots(1, &speedEffect->effectSlot); alDeleteFilters(1, &speedEffect->filter); +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS } speedEffect->effect = speedEffect->effectSlot = speedEffect->filter = 0; } +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS void Mixer::Track::reattach(AudioMsgId::Type type) { if (isStreamCreated() || !samplesCount[0]) { @@ -519,17 +551,30 @@ int Mixer::Track::getNotQueuedBufferIndex() { } void Mixer::Track::setVideoData(std::unique_ptr data) { - resetSpeedEffect(); - if (data && data->speed != 1.) { - speedEffect = std::make_unique(); - speedEffect->speed = data->speed; - speedEffect->coarseTune = CoarseTuneForSpeed(data->speed); - } else { - speedEffect = nullptr; - } +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS + changeSpeedEffect(data ? data->speed : 1.); +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS videoData = std::move(data); } +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS +void Mixer::Track::changeSpeedEffect(float64 speed) { + if (speed != 1.) { + if (!speedEffect) { + speedEffect = std::make_unique(); + } + speedEffect->speed = speed; + speedEffect->coarseTune = CoarseTuneForSpeed(speed); + if (isStreamCreated()) { + applySourceSpeedEffect(); + } + } else if (speedEffect) { + resetSpeedEffect(); + speedEffect = nullptr; + } +} +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS + void Mixer::Track::resetStream() { if (isStreamCreated()) { alSourceStop(stream.source); @@ -831,12 +876,22 @@ void Mixer::forceToBufferVideo(const AudioMsgId &audioId) { _loader->forceToBufferVideo(audioId); } -Streaming::TimeCorrection Mixer::getVideoTimeCorrection( +void Mixer::setSpeedFromVideo(const AudioMsgId &audioId, float64 speed) { +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS + QMutexLocker lock(&AudioMutex); + const auto track = trackForType(AudioMsgId::Type::Video); + if (track->state.id == audioId) { + track->changeSpeedEffect(speed); + } +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS +} + +Streaming::TimePoint Mixer::getVideoSyncTimePoint( const AudioMsgId &audio) const { Expects(audio.type() == AudioMsgId::Type::Video); Expects(audio.playId() != 0); - auto result = Streaming::TimeCorrection(); + auto result = Streaming::TimePoint(); const auto playId = audio.playId(); QMutexLocker lock(&AudioMutex); diff --git a/Telegram/SourceFiles/media/audio/media_audio.h b/Telegram/SourceFiles/media/audio/media_audio.h index 677af592a1..711988fb5e 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.h +++ b/Telegram/SourceFiles/media/audio/media_audio.h @@ -15,7 +15,7 @@ struct VideoSoundPart; namespace Media { namespace Streaming { -struct TimeCorrection; +struct TimePoint; } // namespace Streaming namespace Audio { @@ -36,6 +36,7 @@ bool AttachToDevice(); void ScheduleDetachFromDeviceSafe(); void ScheduleDetachIfNotUsedSafe(); void StopDetachIfNotUsedSafe(); +bool SupportsSpeedControl(); } // namespace Audio @@ -126,7 +127,8 @@ public: // Video player audio stream interface. void feedFromVideo(const VideoSoundPart &part); void forceToBufferVideo(const AudioMsgId &audioId); - Streaming::TimeCorrection getVideoTimeCorrection( + void setSpeedFromVideo(const AudioMsgId &audioId, float64 speed); + Streaming::TimePoint getVideoSyncTimePoint( const AudioMsgId &audio) const; crl::time getVideoCorrectedTime( const AudioMsgId &id, @@ -201,6 +203,9 @@ private: int getNotQueuedBufferIndex(); void setVideoData(std::unique_ptr data); +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS + void changeSpeedEffect(float64 speed); +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS ~Track(); @@ -226,6 +231,7 @@ private: Stream stream; std::unique_ptr videoData; +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS struct SpeedEffect { uint32 effect = 0; uint32 effectSlot = 0; @@ -234,14 +240,19 @@ private: float64 speed = 1.; }; std::unique_ptr speedEffect; +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS crl::time lastUpdateWhen = 0; crl::time lastUpdatePosition = 0; private: void createStream(AudioMsgId::Type type); void destroyStream(); - void resetSpeedEffect(); void resetStream(); +#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS + void resetSpeedEffect(); + void applySourceSpeedEffect(); + void removeSourceSpeedEffect(); +#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS }; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 78daf7d3ff..a9bed77b70 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -395,11 +395,8 @@ void Widget::checkForTypeChange() { } bool Widget::hasPlaybackSpeedControl() const { -#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS - return (_type == AudioMsgId::Type::Voice); -#else // TDESKTOP_DISABLE_OPENAL_EFFECTS - return false; -#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS + return (_type == AudioMsgId::Type::Voice) + && Media::Audio::SupportsSpeedControl(); } void Widget::setType(AudioMsgId::Type type) { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp index 870ecbc3bc..44be385231 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.cpp @@ -137,6 +137,11 @@ void AudioTrack::resume(crl::time time) { Media::Player::mixer()->resume(_audioId, true); } +void AudioTrack::setSpeed(float64 speed) { + _options.speed = speed; + Media::Player::mixer()->setSpeedFromVideo(_audioId, speed); +} + rpl::producer AudioTrack::playPosition() { Expects(_ready == nullptr); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h index 1006ce79fe..c6e98936ee 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_audio_track.h @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/streaming/media_streaming_utility.h" namespace Media { - namespace Streaming { class AudioTrack final { @@ -29,6 +28,9 @@ public: void pause(crl::time time); void resume(crl::time time); + // Called from the main thread. + void setSpeed(float64 speed); + // Called from the main thread. // Non-const, because we subscribe to changes on the first call. // Must be called after 'ready' was invoked. @@ -55,7 +57,7 @@ private: void mixerForceToBuffer(); void callReady(); - const PlaybackOptions _options; + PlaybackOptions _options; // Accessed from the same unspecified thread. Stream _stream; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_common.h b/Telegram/SourceFiles/media/streaming/media_streaming_common.h index 7890805692..5a9f1dbaf9 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_common.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_common.h @@ -11,8 +11,16 @@ namespace Media { constexpr auto kTimeUnknown = std::numeric_limits::min(); +namespace Audio { +bool SupportsSpeedControl(); +} // namespace Audio + namespace Streaming { +inline bool SupportsSpeedControl() { + return Media::Audio::SupportsSpeedControl(); +} + class VideoTrack; class AudioTrack; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index 018edaf757..bc5f3b1219 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/streaming/media_streaming_loader.h" #include "media/streaming/media_streaming_audio_track.h" #include "media/streaming/media_streaming_video_track.h" +#include "media/audio/media_audio.h" // for SupportsSpeedControl() namespace Media { namespace Streaming { @@ -315,12 +316,15 @@ void Player::play(const PlaybackOptions &options) { stop(); _options = options; + if (!Media::Audio::SupportsSpeedControl()) { + _options.speed = 1.; + } _stage = Stage::Initializing; _file->start(delegate(), _options.position); } void Player::pause() { - Expects(_stage != Stage::Uninitialized && _stage != Stage::Failed); + Expects(valid()); if (_paused) { return; @@ -338,7 +342,7 @@ void Player::pause() { } void Player::resume() { - Expects(_stage != Stage::Uninitialized && _stage != Stage::Failed); + Expects(valid()); if (!_paused) { return; @@ -386,6 +390,7 @@ void Player::stop() { _audio = nullptr; _video = nullptr; _paused = false; + _information = Information(); invalidate_weak_ptrs(&_sessionGuard); if (_stage != Stage::Failed) { _stage = Stage::Uninitialized; @@ -405,6 +410,32 @@ bool Player::paused() const { return _paused; } +void Player::setSpeed(float64 speed) { + Expects(valid()); + Expects(speed >= 0.5 && speed <= 2.); + + if (!Media::Audio::SupportsSpeedControl()) { + speed = 1.; + } + if (_options.speed != speed) { + _options.speed = speed; + if (_audio) { + _audio->setSpeed(speed); + } + if (_video) { + _video->setSpeed(speed); + } + } +} + +bool Player::valid() const { + return (_stage != Stage::Uninitialized) && (_stage != Stage::Failed); +} + +bool Player::ready() const { + return valid() && (_stage != Stage::Initializing); +} + rpl::producer Player::updates() const { return _updates.events(); } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.h b/Telegram/SourceFiles/media/streaming/media_streaming_player.h index 32ff947651..8834c99888 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.h @@ -38,6 +38,12 @@ public: void resume(); void stop(); + bool valid() const; + bool ready() const; + + float64 speed() const; + void setSpeed(float64 speed); // 0.5 <= speed <= 2. + [[nodiscard]] bool failed() const; [[nodiscard]] bool playing() const; [[nodiscard]] bool paused() const; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h index 47da98319d..9fca949019 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h @@ -20,7 +20,7 @@ namespace Streaming { constexpr auto kUniversalTimeBase = AVRational{ 1, AV_TIME_BASE }; -struct TimeCorrection { +struct TimePoint { crl::time trackTime = kTimeUnknown; crl::time worldTime = kTimeUnknown; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp index 959fc914b2..37a776f106 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp @@ -39,6 +39,7 @@ public: void pause(crl::time time); void resume(crl::time time); + void setSpeed(float64 speed); void interrupt(); void frameDisplayed(); @@ -55,14 +56,10 @@ private: // Force frame position to be clamped to [0, duration] and monotonic. [[nodiscard]] crl::time currentFramePosition() const; - struct TrackTime { - crl::time worldNow = kTimeUnknown; - crl::time trackNow = kTimeUnknown; - }; - [[nodiscard]] TrackTime trackTime() const; + [[nodiscard]] TimePoint trackTime() const; const crl::weak_on_queue _weak; - const PlaybackOptions _options; + PlaybackOptions _options; // Main thread wrapper destructor will set _shared back to nullptr. // All queued method calls after that should be discarded. @@ -75,7 +72,7 @@ private: Fn _error; crl::time _pausedTime = kTimeUnknown; crl::time _resumedTime = kTimeUnknown; - mutable TimeCorrection _timeCorrection; + mutable TimePoint _syncTimePoint; mutable crl::time _previousFramePosition = kTimeUnknown; rpl::variable _nextFrameDisplayTime = kTimeUnknown; @@ -109,10 +106,11 @@ rpl::producer VideoTrackObject::displayFrameAt() const { } void VideoTrackObject::process(Packet &&packet) { - _noMoreData = packet.empty(); if (interrupted()) { return; - } else if (_shared->initialized()) { + } + _noMoreData = packet.empty(); + if (_shared->initialized()) { _stream.queue.push_back(std::move(packet)); queueReadFrames(); } else if (!tryReadFirstFrame(std::move(packet))) { @@ -136,7 +134,7 @@ void VideoTrackObject::readFrames() { if (interrupted()) { return; } - const auto state = _shared->prepareState(trackTime().trackNow); + const auto state = _shared->prepareState(trackTime().trackTime); state.match([&](Shared::PrepareFrame frame) { if (readFrame(frame)) { presentFrameIfNeeded(); @@ -185,38 +183,55 @@ void VideoTrackObject::presentFrameIfNeeded() { return; } const auto time = trackTime(); - const auto presented = _shared->presentFrame(time.trackNow); + const auto presented = _shared->presentFrame(time.trackTime); if (presented.displayPosition != kTimeUnknown) { - const auto trackLeft = presented.displayPosition - time.trackNow; - _nextFrameDisplayTime = time.worldNow + const auto trackLeft = presented.displayPosition - time.trackTime; + _nextFrameDisplayTime = time.worldTime + crl::time(std::round(trackLeft / _options.speed)); } queueReadFrames(presented.nextCheckDelay); } void VideoTrackObject::pause(crl::time time) { - Expects(_timeCorrection.valid()); + Expects(_syncTimePoint.valid()); - if (_pausedTime == kTimeUnknown) { + if (interrupted()) { + return; + } else if (_pausedTime == kTimeUnknown) { _pausedTime = time; } } void VideoTrackObject::resume(crl::time time) { - Expects(_timeCorrection.trackTime != kTimeUnknown); + Expects(_syncTimePoint.trackTime != kTimeUnknown); + + if (interrupted()) { + return; + } // Resumed time used to validate sync to audio. _resumedTime = time; if (_pausedTime != kTimeUnknown) { Assert(_pausedTime <= time); - _timeCorrection.worldTime += (time - _pausedTime); + _syncTimePoint.worldTime += (time - _pausedTime); _pausedTime = kTimeUnknown; } else { - _timeCorrection.worldTime = time; + _syncTimePoint.worldTime = time; } queueReadFrames(); - Ensures(_timeCorrection.valid()); + Ensures(_syncTimePoint.valid()); + Ensures(_pausedTime == kTimeUnknown); +} + +void VideoTrackObject::setSpeed(float64 speed) { + if (interrupted()) { + return; + } + if (_syncTimePoint.valid()) { + _syncTimePoint = trackTime(); + } + _options.speed = speed; } bool VideoTrackObject::interrupted() const { @@ -224,6 +239,9 @@ bool VideoTrackObject::interrupted() const { } void VideoTrackObject::frameDisplayed() { + if (interrupted()) { + return; + } queueReadFrames(); } @@ -246,7 +264,7 @@ bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) { if (frame.isNull()) { return false; } - _shared->init(std::move(frame), _timeCorrection.trackTime); + _shared->init(std::move(frame), _syncTimePoint.trackTime); callReady(); if (!_stream.queue.empty()) { queueReadFrames(); @@ -267,13 +285,13 @@ crl::time VideoTrackObject::currentFramePosition() const { } bool VideoTrackObject::fillStateFromFrame() { - Expects(_timeCorrection.trackTime == kTimeUnknown); + Expects(_syncTimePoint.trackTime == kTimeUnknown); const auto position = currentFramePosition(); if (position == kTimeUnknown) { return false; } - _nextFrameDisplayTime = _timeCorrection.trackTime = position; + _nextFrameDisplayTime = _syncTimePoint.trackTime = position; return true; } @@ -291,31 +309,34 @@ void VideoTrackObject::callReady() { data.cover = frame->original; data.rotation = _stream.rotation; data.state.duration = _stream.duration; - data.state.position = _timeCorrection.trackTime; + data.state.position = _syncTimePoint.trackTime; data.state.receivedTill = _noMoreData ? _stream.duration - : _timeCorrection.trackTime; + : _syncTimePoint.trackTime; base::take(_ready)({ data }); } -VideoTrackObject::TrackTime VideoTrackObject::trackTime() const { - auto result = TrackTime(); - result.worldNow = crl::now(); - if (!_timeCorrection) { - result.trackNow = _timeCorrection.trackTime; +TimePoint VideoTrackObject::trackTime() const { + auto result = TimePoint(); + result.worldTime = (_pausedTime != kTimeUnknown) + ? _pausedTime + : crl::now(); + if (!_syncTimePoint) { + result.trackTime = _syncTimePoint.trackTime; return result; } + Assert(_resumedTime != kTimeUnknown); if (_options.syncVideoByAudio && _audioId.playId()) { const auto mixer = Media::Player::mixer(); - const auto correction = mixer->getVideoTimeCorrection(_audioId); - if (correction && correction.worldTime > _resumedTime) { - _timeCorrection = correction; + const auto point = mixer->getVideoSyncTimePoint(_audioId); + if (point && point.worldTime > _resumedTime) { + _syncTimePoint = point; } } - const auto sinceKnown = (result.worldNow - _timeCorrection.worldTime); - result.trackNow = _timeCorrection.trackTime - + crl::time(std::round(sinceKnown * _options.speed)); + const auto adjust = (result.worldTime - _syncTimePoint.worldTime); + result.trackTime = _syncTimePoint.trackTime + + crl::time(std::round(adjust * _options.speed)); return result; } @@ -517,6 +538,12 @@ void VideoTrack::resume(crl::time time) { }); } +void VideoTrack::setSpeed(float64 speed) { + _wrapped.with([=](Implementation &unwrapped) { + unwrapped.setSpeed(speed); + }); +} + crl::time VideoTrack::markFrameDisplayed(crl::time now) { const auto position = _shared->markFrameDisplayed(now); if (position != kTimeUnknown) { diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h index cef9004577..653c54fdde 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h @@ -40,6 +40,9 @@ public: void pause(crl::time time); void resume(crl::time time); + // Called from the main thread. + void setSpeed(float64 speed); + // Called from the main thread. // Returns the position of the displayed frame. [[nodiscard]] crl::time markFrameDisplayed(crl::time now);