Implement pause / resume in streaming.

This commit is contained in:
John Preston 2019-02-21 18:57:00 +04:00
parent 3b369fc98e
commit a7d9281768
10 changed files with 183 additions and 99 deletions

View File

@ -294,6 +294,8 @@ void StartStreaming(
using namespace Media::Streaming; using namespace Media::Streaming;
if (auto loader = document->createStreamingLoader(origin)) { if (auto loader = document->createStreamingLoader(origin)) {
static auto player = std::unique_ptr<Player>();
class Panel class Panel
#if defined Q_OS_MAC && !defined OS_MAC_OLD #if defined Q_OS_MAC && !defined OS_MAC_OLD
: public Ui::RpWidgetWrap<QOpenGLWidget> { : public Ui::RpWidgetWrap<QOpenGLWidget> {
@ -310,10 +312,18 @@ void StartStreaming(
protected: protected:
void paintEvent(QPaintEvent *e) override { void paintEvent(QPaintEvent *e) override {
} }
void keyPressEvent(QKeyEvent *e) override {
if (e->key() == Qt::Key_Space) {
if (player->paused()) {
player->resume();
} else {
player->pause();
}
}
}
}; };
static auto player = std::unique_ptr<Player>();
static auto video = base::unique_qptr<Panel>(); static auto video = base::unique_qptr<Panel>();
player = std::make_unique<Player>( player = std::make_unique<Player>(
&document->owner(), &document->owner(),
@ -325,10 +335,10 @@ void StartStreaming(
}); });
auto options = Media::Streaming::PlaybackOptions(); auto options = Media::Streaming::PlaybackOptions();
options.speed = 1.; options.speed = 1.7;
//options.syncVideoByAudio = false; //options.syncVideoByAudio = false;
//options.position = (document->duration() / 2) * crl::time(1000); options.position = (document->duration() / 2) * crl::time(1000);
player->init(options); player->play(options);
player->updates( player->updates(
) | rpl::start_with_next_error_done([=](Update &&update) { ) | rpl::start_with_next_error_done([=](Update &&update) {
update.data.match([&](Information &update) { update.data.match([&](Information &update) {
@ -356,7 +366,6 @@ void StartStreaming(
} }
}, video->lifetime()); }, video->lifetime());
} }
player->start();
}, [&](PreloadedVideo &update) { }, [&](PreloadedVideo &update) {
}, [&](UpdateVideo &update) { }, [&](UpdateVideo &update) {
Expects(video != nullptr); Expects(video != nullptr);

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_child_ffmpeg_loader.h" #include "media/audio/media_child_ffmpeg_loader.h"
#include "media/audio/media_audio_loaders.h" #include "media/audio/media_audio_loaders.h"
#include "media/audio/media_audio_track.h" #include "media/audio/media_audio_track.h"
#include "media/streaming/media_streaming_utility.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "platform/platform_audio.h" #include "platform/platform_audio.h"
@ -374,10 +375,10 @@ void Mixer::Track::destroyStream() {
for (auto i = 0; i != 3; ++i) { for (auto i = 0; i != 3; ++i) {
stream.buffers[i] = 0; stream.buffers[i] = 0;
} }
destroySpeedEffect(); resetSpeedEffect();
} }
void Mixer::Track::destroySpeedEffect() { void Mixer::Track::resetSpeedEffect() {
if (!speedEffect) { if (!speedEffect) {
return; return;
} else if (alIsEffect(speedEffect->effect)) { } else if (alIsEffect(speedEffect->effect)) {
@ -385,7 +386,7 @@ void Mixer::Track::destroySpeedEffect() {
alDeleteAuxiliaryEffectSlots(1, &speedEffect->effectSlot); alDeleteAuxiliaryEffectSlots(1, &speedEffect->effectSlot);
alDeleteFilters(1, &speedEffect->filter); alDeleteFilters(1, &speedEffect->filter);
} }
speedEffect = nullptr; speedEffect->effect = speedEffect->effectSlot = speedEffect->filter = 0;
} }
void Mixer::Track::reattach(AudioMsgId::Type type) { void Mixer::Track::reattach(AudioMsgId::Type type) {
@ -418,7 +419,6 @@ void Mixer::Track::reattach(AudioMsgId::Type type) {
void Mixer::Track::detach() { void Mixer::Track::detach() {
resetStream(); resetStream();
destroyStream(); destroyStream();
destroySpeedEffect();
} }
void Mixer::Track::clear() { void Mixer::Track::clear() {
@ -519,11 +519,13 @@ int Mixer::Track::getNotQueuedBufferIndex() {
} }
void Mixer::Track::setVideoData(std::unique_ptr<VideoSoundData> data) { void Mixer::Track::setVideoData(std::unique_ptr<VideoSoundData> data) {
destroySpeedEffect(); resetSpeedEffect();
if (data && data->speed != 1.) { if (data && data->speed != 1.) {
speedEffect = std::make_unique<SpeedEffect>(); speedEffect = std::make_unique<SpeedEffect>();
speedEffect->speed = data->speed; speedEffect->speed = data->speed;
speedEffect->coarseTune = CoarseTuneForSpeed(data->speed); speedEffect->coarseTune = CoarseTuneForSpeed(data->speed);
} else {
speedEffect = nullptr;
} }
videoData = std::move(data); videoData = std::move(data);
} }
@ -787,6 +789,7 @@ void Mixer::play(
if (videoData) { if (videoData) {
current->setVideoData(std::move(videoData)); current->setVideoData(std::move(videoData));
} else { } else {
current->setVideoData(nullptr);
current->file = audio.audio()->location(true); current->file = audio.audio()->location(true);
current->data = audio.audio()->data(); current->data = audio.audio()->data();
notLoadedYet = (current->file.isEmpty() && current->data.isEmpty()); notLoadedYet = (current->file.isEmpty() && current->data.isEmpty());
@ -828,19 +831,19 @@ void Mixer::forceToBufferVideo(const AudioMsgId &audioId) {
_loader->forceToBufferVideo(audioId); _loader->forceToBufferVideo(audioId);
} }
Mixer::TimeCorrection Mixer::getVideoTimeCorrection( Streaming::TimeCorrection Mixer::getVideoTimeCorrection(
const AudioMsgId &audio) const { const AudioMsgId &audio) const {
Expects(audio.type() == AudioMsgId::Type::Video); Expects(audio.type() == AudioMsgId::Type::Video);
Expects(audio.playId() != 0); Expects(audio.playId() != 0);
auto result = TimeCorrection(); auto result = Streaming::TimeCorrection();
const auto playId = audio.playId(); const auto playId = audio.playId();
QMutexLocker lock(&AudioMutex); QMutexLocker lock(&AudioMutex);
const auto track = trackForType(AudioMsgId::Type::Video); const auto track = trackForType(AudioMsgId::Type::Video);
if (track->state.id.playId() == playId && track->lastUpdateWhen > 0) { if (track->state.id.playId() == playId && track->lastUpdateWhen > 0) {
result.audioPositionValue = track->lastUpdatePosition; result.trackTime = track->lastUpdatePosition;
result.audioPositionTime = track->lastUpdateWhen; result.worldTime = track->lastUpdateWhen;
} }
return result; return result;
} }

View File

@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "media/streaming/media_streaming_common.h"
#include "storage/localimageloader.h" #include "storage/localimageloader.h"
#include "base/bytes.h" #include "base/bytes.h"
@ -15,6 +14,10 @@ struct VideoSoundData;
struct VideoSoundPart; struct VideoSoundPart;
namespace Media { namespace Media {
namespace Streaming {
struct TimeCorrection;
} // namespace Streaming
namespace Audio { namespace Audio {
class Instance; class Instance;
@ -123,15 +126,8 @@ public:
// Video player audio stream interface. // Video player audio stream interface.
void feedFromVideo(const VideoSoundPart &part); void feedFromVideo(const VideoSoundPart &part);
void forceToBufferVideo(const AudioMsgId &audioId); void forceToBufferVideo(const AudioMsgId &audioId);
struct TimeCorrection { Streaming::TimeCorrection getVideoTimeCorrection(
crl::time audioPositionValue = kTimeUnknown; const AudioMsgId &audio) const;
crl::time audioPositionTime = kTimeUnknown;
explicit operator bool() const {
return (audioPositionValue != kTimeUnknown);
}
};
TimeCorrection getVideoTimeCorrection(const AudioMsgId &audio) const;
crl::time getVideoCorrectedTime( crl::time getVideoCorrectedTime(
const AudioMsgId &id, const AudioMsgId &id,
crl::time frameMs, crl::time frameMs,
@ -244,7 +240,7 @@ private:
private: private:
void createStream(AudioMsgId::Type type); void createStream(AudioMsgId::Type type);
void destroyStream(); void destroyStream();
void destroySpeedEffect(); void resetSpeedEffect();
void resetStream(); void resetStream();
}; };

View File

@ -125,10 +125,15 @@ void AudioTrack::mixerForceToBuffer() {
Media::Player::mixer()->forceToBufferVideo(_audioId); Media::Player::mixer()->forceToBufferVideo(_audioId);
} }
void AudioTrack::start(crl::time startTime) { void AudioTrack::pause(crl::time time) {
Expects(initialized());
Media::Player::mixer()->pause(_audioId, true);
}
void AudioTrack::resume(crl::time time) {
Expects(initialized()); Expects(initialized());
// #TODO streaming support start() when paused.
Media::Player::mixer()->resume(_audioId, true); Media::Player::mixer()->resume(_audioId, true);
} }

View File

@ -26,7 +26,8 @@ public:
// Called from the main thread. // Called from the main thread.
// Must be called after 'ready' was invoked. // Must be called after 'ready' was invoked.
void start(crl::time startTime); void pause(crl::time time);
void resume(crl::time time);
// Called from the main thread. // Called from the main thread.
// Non-const, because we subscribe to changes on the first call. // Non-const, because we subscribe to changes on the first call.

View File

@ -71,37 +71,6 @@ not_null<FileDelegate*> Player::delegate() {
return static_cast<FileDelegate*>(this); return static_cast<FileDelegate*>(this);
} }
void Player::start() {
Expects(_stage == Stage::Ready);
_stage = Stage::Started;
if (_audio) {
_audio->playPosition(
) | rpl::start_with_next_done([=](crl::time position) {
audioPlayedTill(position);
}, [=] {
// audio finished
}, _lifetime);
}
if (_video) {
_video->renderNextFrame(
) | rpl::start_with_next_done([=](crl::time when) {
_nextFrameTime = when;
checkNextFrame();
}, [=] {
// video finished
}, _lifetime);
}
_startedTime = crl::now();
if (_audio) {
_audio->start(_startedTime);
}
if (_video) {
_video->start(_startedTime);
}
}
void Player::checkNextFrame() { void Player::checkNextFrame() {
Expects(_nextFrameTime != kTimeUnknown); Expects(_nextFrameTime != kTimeUnknown);
@ -238,9 +207,6 @@ void Player::fileWaitingForData() {
return; return;
} }
_waitingForData = true; _waitingForData = true;
crl::on_main(&_sessionGuard, [=] {
_updates.fire({ WaitingForData() });
});
if (_audio) { if (_audio) {
_audio->waitForData(); _audio->waitForData();
} }
@ -328,6 +294,11 @@ void Player::provideStartInformation() {
_information.video.cover = QImage(); _information.video.cover = QImage();
_updates.fire(Update{ std::move(copy) }); _updates.fire(Update{ std::move(copy) });
if (_stage == Stage::Ready && !_paused) {
_paused = true;
resume();
}
} }
} }
@ -338,7 +309,7 @@ void Player::fail() {
stopGuarded(); stopGuarded();
} }
void Player::init(const PlaybackOptions &options) { void Player::play(const PlaybackOptions &options) {
Expects(options.speed >= 0.5 && options.speed <= 2.); Expects(options.speed >= 0.5 && options.speed <= 2.);
stop(); stop();
@ -349,15 +320,67 @@ void Player::init(const PlaybackOptions &options) {
} }
void Player::pause() { void Player::pause() {
Expects(_stage != Stage::Uninitialized && _stage != Stage::Failed);
if (_paused) {
return;
}
_paused = true; _paused = true;
// #TODO streaming pause if (_stage == Stage::Started) {
_pausedTime = crl::now();
if (_audio) {
_audio->pause(_pausedTime);
}
if (_video) {
_video->pause(_pausedTime);
}
}
} }
void Player::resume() { void Player::resume() {
Expects(_stage != Stage::Uninitialized && _stage != Stage::Failed);
if (!_paused) {
return;
}
_paused = false; _paused = false;
// #TODO streaming pause if (_stage == Stage::Ready) {
start();
}
if (_stage == Stage::Started) {
_startedTime = crl::now();
if (_audio) {
_audio->resume(_startedTime);
}
if (_video) {
_video->resume(_startedTime);
}
}
} }
void Player::start() {
Expects(_stage == Stage::Ready);
_stage = Stage::Started;
if (_audio) {
_audio->playPosition(
) | rpl::start_with_next_done([=](crl::time position) {
audioPlayedTill(position);
}, [=] {
// audio finished
}, _lifetime);
}
if (_video) {
_video->renderNextFrame(
) | rpl::start_with_next_done([=](crl::time when) {
_nextFrameTime = when;
checkNextFrame();
}, [=] {
// video finished
}, _lifetime);
}
}
void Player::stop() { void Player::stop() {
_file->stop(); _file->stop();
_audio = nullptr; _audio = nullptr;

View File

@ -33,8 +33,7 @@ public:
Player(const Player &other) = delete; Player(const Player &other) = delete;
Player &operator=(const Player &other) = delete; Player &operator=(const Player &other) = delete;
void init(const PlaybackOptions &options); void play(const PlaybackOptions &options);
void start();
void pause(); void pause();
void resume(); void resume();
void stop(); void stop();
@ -76,6 +75,7 @@ private:
// Called from the main thread. // Called from the main thread.
void streamReady(Information &&information); void streamReady(Information &&information);
void streamFailed(); void streamFailed();
void start();
void provideStartInformation(); void provideStartInformation();
void fail(); void fail();
void checkNextFrame(); void checkNextFrame();
@ -118,6 +118,7 @@ private:
bool _paused = false; bool _paused = false;
crl::time _startedTime = kTimeUnknown; crl::time _startedTime = kTimeUnknown;
crl::time _pausedTime = kTimeUnknown;
crl::time _nextFrameTime = kTimeUnknown; crl::time _nextFrameTime = kTimeUnknown;
base::Timer _renderFrameTimer; base::Timer _renderFrameTimer;
rpl::event_stream<Update, Error> _updates; rpl::event_stream<Update, Error> _updates;

View File

@ -20,6 +20,18 @@ namespace Streaming {
constexpr auto kUniversalTimeBase = AVRational{ 1, AV_TIME_BASE }; constexpr auto kUniversalTimeBase = AVRational{ 1, AV_TIME_BASE };
struct TimeCorrection {
crl::time trackTime = kTimeUnknown;
crl::time worldTime = kTimeUnknown;
bool valid() const {
return (trackTime != kTimeUnknown) && (worldTime != kTimeUnknown);
}
explicit operator bool() const {
return valid();
}
};
class AvErrorWrap { class AvErrorWrap {
public: public:
AvErrorWrap(int code = 0) : _code(code) { AvErrorWrap(int code = 0) : _code(code) {

View File

@ -37,7 +37,8 @@ public:
[[nodisacrd]] rpl::producer<crl::time> displayFrameAt() const; [[nodisacrd]] rpl::producer<crl::time> displayFrameAt() const;
void start(crl::time startTime); void pause(crl::time time);
void resume(crl::time time);
void interrupt(); void interrupt();
void frameDisplayed(); void frameDisplayed();
@ -72,8 +73,9 @@ private:
bool _noMoreData = false; bool _noMoreData = false;
FnMut<void(const Information &)> _ready; FnMut<void(const Information &)> _ready;
Fn<void()> _error; Fn<void()> _error;
crl::time _startedTime = kTimeUnknown; crl::time _pausedTime = kTimeUnknown;
crl::time _startedPosition = kTimeUnknown; crl::time _resumedTime = kTimeUnknown;
mutable TimeCorrection _timeCorrection;
mutable crl::time _previousFramePosition = kTimeUnknown; mutable crl::time _previousFramePosition = kTimeUnknown;
rpl::variable<crl::time> _nextFrameDisplayTime = kTimeUnknown; rpl::variable<crl::time> _nextFrameDisplayTime = kTimeUnknown;
@ -179,6 +181,9 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
} }
void VideoTrackObject::presentFrameIfNeeded() { void VideoTrackObject::presentFrameIfNeeded() {
if (_pausedTime != kTimeUnknown) {
return;
}
const auto time = trackTime(); const auto time = trackTime();
const auto presented = _shared->presentFrame(time.trackNow); const auto presented = _shared->presentFrame(time.trackNow);
if (presented.displayPosition != kTimeUnknown) { if (presented.displayPosition != kTimeUnknown) {
@ -189,9 +194,29 @@ void VideoTrackObject::presentFrameIfNeeded() {
queueReadFrames(presented.nextCheckDelay); queueReadFrames(presented.nextCheckDelay);
} }
void VideoTrackObject::start(crl::time startTime) { void VideoTrackObject::pause(crl::time time) {
_startedTime = startTime; Expects(_timeCorrection.valid());
if (_pausedTime == kTimeUnknown) {
_pausedTime = time;
}
}
void VideoTrackObject::resume(crl::time time) {
Expects(_timeCorrection.trackTime != kTimeUnknown);
// Resumed time used to validate sync to audio.
_resumedTime = time;
if (_pausedTime != kTimeUnknown) {
Assert(_pausedTime <= time);
_timeCorrection.worldTime += (time - _pausedTime);
_pausedTime = kTimeUnknown;
} else {
_timeCorrection.worldTime = time;
}
queueReadFrames(); queueReadFrames();
Ensures(_timeCorrection.valid());
} }
bool VideoTrackObject::interrupted() const { bool VideoTrackObject::interrupted() const {
@ -221,7 +246,7 @@ bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) {
if (frame.isNull()) { if (frame.isNull()) {
return false; return false;
} }
_shared->init(std::move(frame), _startedPosition); _shared->init(std::move(frame), _timeCorrection.trackTime);
callReady(); callReady();
if (!_stream.queue.empty()) { if (!_stream.queue.empty()) {
queueReadFrames(); queueReadFrames();
@ -242,9 +267,14 @@ crl::time VideoTrackObject::currentFramePosition() const {
} }
bool VideoTrackObject::fillStateFromFrame() { bool VideoTrackObject::fillStateFromFrame() {
_startedPosition = currentFramePosition(); Expects(_timeCorrection.trackTime == kTimeUnknown);
_nextFrameDisplayTime = _startedTime;
return (_startedPosition != kTimeUnknown); const auto position = currentFramePosition();
if (position == kTimeUnknown) {
return false;
}
_nextFrameDisplayTime = _timeCorrection.trackTime = position;
return true;
} }
void VideoTrackObject::callReady() { void VideoTrackObject::callReady() {
@ -261,36 +291,30 @@ void VideoTrackObject::callReady() {
data.cover = frame->original; data.cover = frame->original;
data.rotation = _stream.rotation; data.rotation = _stream.rotation;
data.state.duration = _stream.duration; data.state.duration = _stream.duration;
data.state.position = _startedPosition; data.state.position = _timeCorrection.trackTime;
data.state.receivedTill = _noMoreData data.state.receivedTill = _noMoreData
? _stream.duration ? _stream.duration
: _startedPosition; : _timeCorrection.trackTime;
base::take(_ready)({ data }); base::take(_ready)({ data });
} }
VideoTrackObject::TrackTime VideoTrackObject::trackTime() const { VideoTrackObject::TrackTime VideoTrackObject::trackTime() const {
auto result = TrackTime(); auto result = TrackTime();
const auto started = (_startedTime != kTimeUnknown); result.worldNow = crl::now();
if (!started) { if (!_timeCorrection) {
result.worldNow = crl::now(); result.trackNow = _timeCorrection.trackTime;
result.trackNow = _startedPosition;
return result; return result;
} }
const auto correction = (_options.syncVideoByAudio && _audioId.playId()) if (_options.syncVideoByAudio && _audioId.playId()) {
? Media::Player::mixer()->getVideoTimeCorrection(_audioId) const auto mixer = Media::Player::mixer();
: Media::Player::Mixer::TimeCorrection(); const auto correction = mixer->getVideoTimeCorrection(_audioId);
const auto knownValue = correction if (correction && correction.worldTime > _resumedTime) {
? correction.audioPositionValue _timeCorrection = correction;
: _startedPosition; }
const auto knownTime = correction }
? correction.audioPositionTime const auto sinceKnown = (result.worldNow - _timeCorrection.worldTime);
: _startedTime; result.trackNow = _timeCorrection.trackTime
const auto worldNow = crl::now();
const auto sinceKnown = (worldNow - knownTime);
result.worldNow = worldNow;
result.trackNow = knownValue
+ crl::time(std::round(sinceKnown * _options.speed)); + crl::time(std::round(sinceKnown * _options.speed));
return result; return result;
} }
@ -481,9 +505,15 @@ void VideoTrack::process(Packet &&packet) {
void VideoTrack::waitForData() { void VideoTrack::waitForData() {
} }
void VideoTrack::start(crl::time startTime) { void VideoTrack::pause(crl::time time) {
_wrapped.with([=](Implementation &unwrapped) { _wrapped.with([=](Implementation &unwrapped) {
unwrapped.start(startTime); unwrapped.pause(time);
});
}
void VideoTrack::resume(crl::time time) {
_wrapped.with([=](Implementation &unwrapped) {
unwrapped.resume(time);
}); });
} }

View File

@ -36,7 +36,11 @@ public:
void waitForData(); void waitForData();
// Called from the main thread. // Called from the main thread.
void start(crl::time startTime); // Must be called after 'ready' was invoked.
void pause(crl::time time);
void resume(crl::time time);
// Called from the main thread.
// Returns the position of the displayed frame. // Returns the position of the displayed frame.
[[nodiscard]] crl::time markFrameDisplayed(crl::time now); [[nodiscard]] crl::time markFrameDisplayed(crl::time now);
[[nodiscard]] QImage frame(const FrameRequest &request) const; [[nodiscard]] QImage frame(const FrameRequest &request) const;