mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-10 11:41:20 +00:00
Support streaming speed changing.
This commit is contained in:
parent
a7d9281768
commit
93c548c013
@ -295,6 +295,12 @@ 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>();
|
static auto player = std::unique_ptr<Player>();
|
||||||
|
static auto pauseOnSeek = false;
|
||||||
|
static auto duration = crl::time(0);
|
||||||
|
static auto options = Media::Streaming::PlaybackOptions();
|
||||||
|
static auto subscribe = Fn<void()>();
|
||||||
|
static auto speed = 1.;
|
||||||
|
static auto step = pow(2., 1. / 12);
|
||||||
|
|
||||||
class Panel
|
class Panel
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
||||||
@ -319,37 +325,49 @@ void StartStreaming(
|
|||||||
} else {
|
} else {
|
||||||
player->pause();
|
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<Panel>();
|
static auto video = base::unique_qptr<Panel>();
|
||||||
player = std::make_unique<Player>(
|
subscribe = [] {
|
||||||
&document->owner(),
|
|
||||||
std::move(loader));
|
|
||||||
video = nullptr;
|
|
||||||
document->session().lifetime().add([] {
|
|
||||||
base::take(player) = nullptr;
|
|
||||||
base::take(video) = nullptr;
|
|
||||||
});
|
|
||||||
|
|
||||||
auto options = Media::Streaming::PlaybackOptions();
|
|
||||||
options.speed = 1.7;
|
|
||||||
//options.syncVideoByAudio = false;
|
|
||||||
options.position = (document->duration() / 2) * crl::time(1000);
|
|
||||||
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) {
|
||||||
if (!update.video.cover.isNull()) {
|
duration = update.video.state.duration;
|
||||||
|
if (!video && !update.video.cover.isNull()) {
|
||||||
video = base::make_unique_q<Panel>();
|
video = base::make_unique_q<Panel>();
|
||||||
video->setAttribute(Qt::WA_OpaquePaintEvent);
|
video->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
video->paintRequest(
|
video->paintRequest(
|
||||||
) | rpl::start_with_next([=](QRect rect) {
|
) | rpl::start_with_next([=](QRect rect) {
|
||||||
|
if (player->ready()) {
|
||||||
Painter(video.get()).drawImage(
|
Painter(video.get()).drawImage(
|
||||||
video->rect(),
|
video->rect(),
|
||||||
player->frame(FrameRequest()));
|
player->frame(FrameRequest()));
|
||||||
|
} else {
|
||||||
|
Painter(video.get()).fillRect(
|
||||||
|
rect,
|
||||||
|
Qt::black);
|
||||||
|
}
|
||||||
}, video->lifetime());
|
}, video->lifetime());
|
||||||
const auto size = QSize(
|
const auto size = QSize(
|
||||||
ConvertScale(update.video.size.width()),
|
ConvertScale(update.video.size.width()),
|
||||||
@ -379,8 +397,25 @@ void StartStreaming(
|
|||||||
}, [=](const Error &error) {
|
}, [=](const Error &error) {
|
||||||
base::take(video) = nullptr;
|
base::take(video) = nullptr;
|
||||||
}, [=] {
|
}, [=] {
|
||||||
base::take(video) = nullptr;
|
|
||||||
}, player->lifetime());
|
}, player->lifetime());
|
||||||
|
};
|
||||||
|
|
||||||
|
player = std::make_unique<Player>(
|
||||||
|
&document->owner(),
|
||||||
|
std::move(loader));
|
||||||
|
base::take(video) = nullptr;
|
||||||
|
player->lifetime().add([] {
|
||||||
|
base::take(video) = nullptr;
|
||||||
|
});
|
||||||
|
document->session().lifetime().add([] {
|
||||||
|
base::take(player) = nullptr;
|
||||||
|
});
|
||||||
|
|
||||||
|
options.speed = speed;
|
||||||
|
//options.syncVideoByAudio = false;
|
||||||
|
options.position = 0;
|
||||||
|
player->play(options);
|
||||||
|
subscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 Audio
|
||||||
|
|
||||||
namespace Player {
|
namespace Player {
|
||||||
@ -347,24 +355,39 @@ void Mixer::Track::createStream(AudioMsgId::Type type) {
|
|||||||
alGenBuffers(3, stream.buffers);
|
alGenBuffers(3, stream.buffers);
|
||||||
if (type == AudioMsgId::Type::Voice) {
|
if (type == AudioMsgId::Type::Voice) {
|
||||||
mixer()->updatePlaybackSpeed(this);
|
mixer()->updatePlaybackSpeed(this);
|
||||||
|
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
} else if (speedEffect) {
|
} 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);
|
alGenAuxiliaryEffectSlots(1, &speedEffect->effectSlot);
|
||||||
alGenEffects(1, &speedEffect->effect);
|
alGenEffects(1, &speedEffect->effect);
|
||||||
alGenFilters(1, &speedEffect->filter);
|
alGenFilters(1, &speedEffect->filter);
|
||||||
alEffecti(speedEffect->effect, AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER);
|
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);
|
alFilteri(speedEffect->filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
|
||||||
alFilterf(speedEffect->filter, AL_LOWPASS_GAIN, 0.f);
|
alFilterf(speedEffect->filter, AL_LOWPASS_GAIN, 0.f);
|
||||||
|
}
|
||||||
|
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);
|
alSourcef(stream.source, AL_PITCH, speedEffect->speed);
|
||||||
alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, speedEffect->effectSlot, 0, 0);
|
alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, speedEffect->effectSlot, 0, 0);
|
||||||
alSourcei(stream.source, AL_DIRECT_FILTER, speedEffect->filter);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
|
|
||||||
void Mixer::Track::destroyStream() {
|
void Mixer::Track::destroyStream() {
|
||||||
if (isStreamCreated()) {
|
if (isStreamCreated()) {
|
||||||
@ -375,19 +398,28 @@ 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;
|
||||||
}
|
}
|
||||||
|
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
resetSpeedEffect();
|
resetSpeedEffect();
|
||||||
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
void Mixer::Track::resetSpeedEffect() {
|
void Mixer::Track::resetSpeedEffect() {
|
||||||
if (!speedEffect) {
|
if (!speedEffect) {
|
||||||
return;
|
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);
|
alDeleteEffects(1, &speedEffect->effect);
|
||||||
alDeleteAuxiliaryEffectSlots(1, &speedEffect->effectSlot);
|
alDeleteAuxiliaryEffectSlots(1, &speedEffect->effectSlot);
|
||||||
alDeleteFilters(1, &speedEffect->filter);
|
alDeleteFilters(1, &speedEffect->filter);
|
||||||
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
}
|
}
|
||||||
speedEffect->effect = speedEffect->effectSlot = speedEffect->filter = 0;
|
speedEffect->effect = speedEffect->effectSlot = speedEffect->filter = 0;
|
||||||
}
|
}
|
||||||
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
|
|
||||||
void Mixer::Track::reattach(AudioMsgId::Type type) {
|
void Mixer::Track::reattach(AudioMsgId::Type type) {
|
||||||
if (isStreamCreated() || !samplesCount[0]) {
|
if (isStreamCreated() || !samplesCount[0]) {
|
||||||
@ -519,17 +551,30 @@ int Mixer::Track::getNotQueuedBufferIndex() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Mixer::Track::setVideoData(std::unique_ptr<VideoSoundData> data) {
|
void Mixer::Track::setVideoData(std::unique_ptr<VideoSoundData> data) {
|
||||||
resetSpeedEffect();
|
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
if (data && data->speed != 1.) {
|
changeSpeedEffect(data ? data->speed : 1.);
|
||||||
speedEffect = std::make_unique<SpeedEffect>();
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
speedEffect->speed = data->speed;
|
|
||||||
speedEffect->coarseTune = CoarseTuneForSpeed(data->speed);
|
|
||||||
} else {
|
|
||||||
speedEffect = nullptr;
|
|
||||||
}
|
|
||||||
videoData = std::move(data);
|
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>();
|
||||||
|
}
|
||||||
|
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() {
|
void Mixer::Track::resetStream() {
|
||||||
if (isStreamCreated()) {
|
if (isStreamCreated()) {
|
||||||
alSourceStop(stream.source);
|
alSourceStop(stream.source);
|
||||||
@ -831,12 +876,22 @@ void Mixer::forceToBufferVideo(const AudioMsgId &audioId) {
|
|||||||
_loader->forceToBufferVideo(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 {
|
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 = Streaming::TimeCorrection();
|
auto result = Streaming::TimePoint();
|
||||||
const auto playId = audio.playId();
|
const auto playId = audio.playId();
|
||||||
|
|
||||||
QMutexLocker lock(&AudioMutex);
|
QMutexLocker lock(&AudioMutex);
|
||||||
|
@ -15,7 +15,7 @@ struct VideoSoundPart;
|
|||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
struct TimeCorrection;
|
struct TimePoint;
|
||||||
} // namespace Streaming
|
} // namespace Streaming
|
||||||
|
|
||||||
namespace Audio {
|
namespace Audio {
|
||||||
@ -36,6 +36,7 @@ bool AttachToDevice();
|
|||||||
void ScheduleDetachFromDeviceSafe();
|
void ScheduleDetachFromDeviceSafe();
|
||||||
void ScheduleDetachIfNotUsedSafe();
|
void ScheduleDetachIfNotUsedSafe();
|
||||||
void StopDetachIfNotUsedSafe();
|
void StopDetachIfNotUsedSafe();
|
||||||
|
bool SupportsSpeedControl();
|
||||||
|
|
||||||
} // namespace Audio
|
} // namespace Audio
|
||||||
|
|
||||||
@ -126,7 +127,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);
|
||||||
Streaming::TimeCorrection getVideoTimeCorrection(
|
void setSpeedFromVideo(const AudioMsgId &audioId, float64 speed);
|
||||||
|
Streaming::TimePoint getVideoSyncTimePoint(
|
||||||
const AudioMsgId &audio) const;
|
const AudioMsgId &audio) const;
|
||||||
crl::time getVideoCorrectedTime(
|
crl::time getVideoCorrectedTime(
|
||||||
const AudioMsgId &id,
|
const AudioMsgId &id,
|
||||||
@ -201,6 +203,9 @@ private:
|
|||||||
int getNotQueuedBufferIndex();
|
int getNotQueuedBufferIndex();
|
||||||
|
|
||||||
void setVideoData(std::unique_ptr<VideoSoundData> data);
|
void setVideoData(std::unique_ptr<VideoSoundData> data);
|
||||||
|
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
|
void changeSpeedEffect(float64 speed);
|
||||||
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
|
|
||||||
~Track();
|
~Track();
|
||||||
|
|
||||||
@ -226,6 +231,7 @@ private:
|
|||||||
Stream stream;
|
Stream stream;
|
||||||
std::unique_ptr<VideoSoundData> videoData;
|
std::unique_ptr<VideoSoundData> videoData;
|
||||||
|
|
||||||
|
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
struct SpeedEffect {
|
struct SpeedEffect {
|
||||||
uint32 effect = 0;
|
uint32 effect = 0;
|
||||||
uint32 effectSlot = 0;
|
uint32 effectSlot = 0;
|
||||||
@ -234,14 +240,19 @@ private:
|
|||||||
float64 speed = 1.;
|
float64 speed = 1.;
|
||||||
};
|
};
|
||||||
std::unique_ptr<SpeedEffect> speedEffect;
|
std::unique_ptr<SpeedEffect> speedEffect;
|
||||||
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
crl::time lastUpdateWhen = 0;
|
crl::time lastUpdateWhen = 0;
|
||||||
crl::time lastUpdatePosition = 0;
|
crl::time lastUpdatePosition = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createStream(AudioMsgId::Type type);
|
void createStream(AudioMsgId::Type type);
|
||||||
void destroyStream();
|
void destroyStream();
|
||||||
void resetSpeedEffect();
|
|
||||||
void resetStream();
|
void resetStream();
|
||||||
|
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
|
void resetSpeedEffect();
|
||||||
|
void applySourceSpeedEffect();
|
||||||
|
void removeSourceSpeedEffect();
|
||||||
|
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -395,11 +395,8 @@ void Widget::checkForTypeChange() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Widget::hasPlaybackSpeedControl() const {
|
bool Widget::hasPlaybackSpeedControl() const {
|
||||||
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
|
return (_type == AudioMsgId::Type::Voice)
|
||||||
return (_type == AudioMsgId::Type::Voice);
|
&& Media::Audio::SupportsSpeedControl();
|
||||||
#else // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
|
||||||
return false;
|
|
||||||
#endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::setType(AudioMsgId::Type type) {
|
void Widget::setType(AudioMsgId::Type type) {
|
||||||
|
@ -137,6 +137,11 @@ void AudioTrack::resume(crl::time time) {
|
|||||||
Media::Player::mixer()->resume(_audioId, true);
|
Media::Player::mixer()->resume(_audioId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioTrack::setSpeed(float64 speed) {
|
||||||
|
_options.speed = speed;
|
||||||
|
Media::Player::mixer()->setSpeedFromVideo(_audioId, speed);
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<crl::time> AudioTrack::playPosition() {
|
rpl::producer<crl::time> AudioTrack::playPosition() {
|
||||||
Expects(_ready == nullptr);
|
Expects(_ready == nullptr);
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "media/streaming/media_streaming_utility.h"
|
#include "media/streaming/media_streaming_utility.h"
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
|
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
|
|
||||||
class AudioTrack final {
|
class AudioTrack final {
|
||||||
@ -29,6 +28,9 @@ public:
|
|||||||
void pause(crl::time time);
|
void pause(crl::time time);
|
||||||
void resume(crl::time time);
|
void resume(crl::time time);
|
||||||
|
|
||||||
|
// Called from the main thread.
|
||||||
|
void setSpeed(float64 speed);
|
||||||
|
|
||||||
// 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.
|
||||||
// Must be called after 'ready' was invoked.
|
// Must be called after 'ready' was invoked.
|
||||||
@ -55,7 +57,7 @@ private:
|
|||||||
void mixerForceToBuffer();
|
void mixerForceToBuffer();
|
||||||
void callReady();
|
void callReady();
|
||||||
|
|
||||||
const PlaybackOptions _options;
|
PlaybackOptions _options;
|
||||||
|
|
||||||
// Accessed from the same unspecified thread.
|
// Accessed from the same unspecified thread.
|
||||||
Stream _stream;
|
Stream _stream;
|
||||||
|
@ -11,8 +11,16 @@ namespace Media {
|
|||||||
|
|
||||||
constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
|
constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
|
||||||
|
|
||||||
|
namespace Audio {
|
||||||
|
bool SupportsSpeedControl();
|
||||||
|
} // namespace Audio
|
||||||
|
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
|
|
||||||
|
inline bool SupportsSpeedControl() {
|
||||||
|
return Media::Audio::SupportsSpeedControl();
|
||||||
|
}
|
||||||
|
|
||||||
class VideoTrack;
|
class VideoTrack;
|
||||||
class AudioTrack;
|
class AudioTrack;
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "media/streaming/media_streaming_loader.h"
|
#include "media/streaming/media_streaming_loader.h"
|
||||||
#include "media/streaming/media_streaming_audio_track.h"
|
#include "media/streaming/media_streaming_audio_track.h"
|
||||||
#include "media/streaming/media_streaming_video_track.h"
|
#include "media/streaming/media_streaming_video_track.h"
|
||||||
|
#include "media/audio/media_audio.h" // for SupportsSpeedControl()
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
@ -315,12 +316,15 @@ void Player::play(const PlaybackOptions &options) {
|
|||||||
stop();
|
stop();
|
||||||
|
|
||||||
_options = options;
|
_options = options;
|
||||||
|
if (!Media::Audio::SupportsSpeedControl()) {
|
||||||
|
_options.speed = 1.;
|
||||||
|
}
|
||||||
_stage = Stage::Initializing;
|
_stage = Stage::Initializing;
|
||||||
_file->start(delegate(), _options.position);
|
_file->start(delegate(), _options.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::pause() {
|
void Player::pause() {
|
||||||
Expects(_stage != Stage::Uninitialized && _stage != Stage::Failed);
|
Expects(valid());
|
||||||
|
|
||||||
if (_paused) {
|
if (_paused) {
|
||||||
return;
|
return;
|
||||||
@ -338,7 +342,7 @@ void Player::pause() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Player::resume() {
|
void Player::resume() {
|
||||||
Expects(_stage != Stage::Uninitialized && _stage != Stage::Failed);
|
Expects(valid());
|
||||||
|
|
||||||
if (!_paused) {
|
if (!_paused) {
|
||||||
return;
|
return;
|
||||||
@ -386,6 +390,7 @@ void Player::stop() {
|
|||||||
_audio = nullptr;
|
_audio = nullptr;
|
||||||
_video = nullptr;
|
_video = nullptr;
|
||||||
_paused = false;
|
_paused = false;
|
||||||
|
_information = Information();
|
||||||
invalidate_weak_ptrs(&_sessionGuard);
|
invalidate_weak_ptrs(&_sessionGuard);
|
||||||
if (_stage != Stage::Failed) {
|
if (_stage != Stage::Failed) {
|
||||||
_stage = Stage::Uninitialized;
|
_stage = Stage::Uninitialized;
|
||||||
@ -405,6 +410,32 @@ bool Player::paused() const {
|
|||||||
return _paused;
|
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<Update, Error> Player::updates() const {
|
rpl::producer<Update, Error> Player::updates() const {
|
||||||
return _updates.events();
|
return _updates.events();
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,12 @@ public:
|
|||||||
void resume();
|
void resume();
|
||||||
void stop();
|
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 failed() const;
|
||||||
[[nodiscard]] bool playing() const;
|
[[nodiscard]] bool playing() const;
|
||||||
[[nodiscard]] bool paused() const;
|
[[nodiscard]] bool paused() const;
|
||||||
|
@ -20,7 +20,7 @@ namespace Streaming {
|
|||||||
|
|
||||||
constexpr auto kUniversalTimeBase = AVRational{ 1, AV_TIME_BASE };
|
constexpr auto kUniversalTimeBase = AVRational{ 1, AV_TIME_BASE };
|
||||||
|
|
||||||
struct TimeCorrection {
|
struct TimePoint {
|
||||||
crl::time trackTime = kTimeUnknown;
|
crl::time trackTime = kTimeUnknown;
|
||||||
crl::time worldTime = kTimeUnknown;
|
crl::time worldTime = kTimeUnknown;
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ public:
|
|||||||
|
|
||||||
void pause(crl::time time);
|
void pause(crl::time time);
|
||||||
void resume(crl::time time);
|
void resume(crl::time time);
|
||||||
|
void setSpeed(float64 speed);
|
||||||
void interrupt();
|
void interrupt();
|
||||||
void frameDisplayed();
|
void frameDisplayed();
|
||||||
|
|
||||||
@ -55,14 +56,10 @@ private:
|
|||||||
// Force frame position to be clamped to [0, duration] and monotonic.
|
// Force frame position to be clamped to [0, duration] and monotonic.
|
||||||
[[nodiscard]] crl::time currentFramePosition() const;
|
[[nodiscard]] crl::time currentFramePosition() const;
|
||||||
|
|
||||||
struct TrackTime {
|
[[nodiscard]] TimePoint trackTime() const;
|
||||||
crl::time worldNow = kTimeUnknown;
|
|
||||||
crl::time trackNow = kTimeUnknown;
|
|
||||||
};
|
|
||||||
[[nodiscard]] TrackTime trackTime() const;
|
|
||||||
|
|
||||||
const crl::weak_on_queue<VideoTrackObject> _weak;
|
const crl::weak_on_queue<VideoTrackObject> _weak;
|
||||||
const PlaybackOptions _options;
|
PlaybackOptions _options;
|
||||||
|
|
||||||
// Main thread wrapper destructor will set _shared back to nullptr.
|
// Main thread wrapper destructor will set _shared back to nullptr.
|
||||||
// All queued method calls after that should be discarded.
|
// All queued method calls after that should be discarded.
|
||||||
@ -75,7 +72,7 @@ private:
|
|||||||
Fn<void()> _error;
|
Fn<void()> _error;
|
||||||
crl::time _pausedTime = kTimeUnknown;
|
crl::time _pausedTime = kTimeUnknown;
|
||||||
crl::time _resumedTime = kTimeUnknown;
|
crl::time _resumedTime = kTimeUnknown;
|
||||||
mutable TimeCorrection _timeCorrection;
|
mutable TimePoint _syncTimePoint;
|
||||||
mutable crl::time _previousFramePosition = kTimeUnknown;
|
mutable crl::time _previousFramePosition = kTimeUnknown;
|
||||||
rpl::variable<crl::time> _nextFrameDisplayTime = kTimeUnknown;
|
rpl::variable<crl::time> _nextFrameDisplayTime = kTimeUnknown;
|
||||||
|
|
||||||
@ -109,10 +106,11 @@ rpl::producer<crl::time> VideoTrackObject::displayFrameAt() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VideoTrackObject::process(Packet &&packet) {
|
void VideoTrackObject::process(Packet &&packet) {
|
||||||
_noMoreData = packet.empty();
|
|
||||||
if (interrupted()) {
|
if (interrupted()) {
|
||||||
return;
|
return;
|
||||||
} else if (_shared->initialized()) {
|
}
|
||||||
|
_noMoreData = packet.empty();
|
||||||
|
if (_shared->initialized()) {
|
||||||
_stream.queue.push_back(std::move(packet));
|
_stream.queue.push_back(std::move(packet));
|
||||||
queueReadFrames();
|
queueReadFrames();
|
||||||
} else if (!tryReadFirstFrame(std::move(packet))) {
|
} else if (!tryReadFirstFrame(std::move(packet))) {
|
||||||
@ -136,7 +134,7 @@ void VideoTrackObject::readFrames() {
|
|||||||
if (interrupted()) {
|
if (interrupted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto state = _shared->prepareState(trackTime().trackNow);
|
const auto state = _shared->prepareState(trackTime().trackTime);
|
||||||
state.match([&](Shared::PrepareFrame frame) {
|
state.match([&](Shared::PrepareFrame frame) {
|
||||||
if (readFrame(frame)) {
|
if (readFrame(frame)) {
|
||||||
presentFrameIfNeeded();
|
presentFrameIfNeeded();
|
||||||
@ -185,38 +183,55 @@ void VideoTrackObject::presentFrameIfNeeded() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto time = trackTime();
|
const auto time = trackTime();
|
||||||
const auto presented = _shared->presentFrame(time.trackNow);
|
const auto presented = _shared->presentFrame(time.trackTime);
|
||||||
if (presented.displayPosition != kTimeUnknown) {
|
if (presented.displayPosition != kTimeUnknown) {
|
||||||
const auto trackLeft = presented.displayPosition - time.trackNow;
|
const auto trackLeft = presented.displayPosition - time.trackTime;
|
||||||
_nextFrameDisplayTime = time.worldNow
|
_nextFrameDisplayTime = time.worldTime
|
||||||
+ crl::time(std::round(trackLeft / _options.speed));
|
+ crl::time(std::round(trackLeft / _options.speed));
|
||||||
}
|
}
|
||||||
queueReadFrames(presented.nextCheckDelay);
|
queueReadFrames(presented.nextCheckDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoTrackObject::pause(crl::time time) {
|
void VideoTrackObject::pause(crl::time time) {
|
||||||
Expects(_timeCorrection.valid());
|
Expects(_syncTimePoint.valid());
|
||||||
|
|
||||||
if (_pausedTime == kTimeUnknown) {
|
if (interrupted()) {
|
||||||
|
return;
|
||||||
|
} else if (_pausedTime == kTimeUnknown) {
|
||||||
_pausedTime = time;
|
_pausedTime = time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoTrackObject::resume(crl::time 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.
|
// Resumed time used to validate sync to audio.
|
||||||
_resumedTime = time;
|
_resumedTime = time;
|
||||||
if (_pausedTime != kTimeUnknown) {
|
if (_pausedTime != kTimeUnknown) {
|
||||||
Assert(_pausedTime <= time);
|
Assert(_pausedTime <= time);
|
||||||
_timeCorrection.worldTime += (time - _pausedTime);
|
_syncTimePoint.worldTime += (time - _pausedTime);
|
||||||
_pausedTime = kTimeUnknown;
|
_pausedTime = kTimeUnknown;
|
||||||
} else {
|
} else {
|
||||||
_timeCorrection.worldTime = time;
|
_syncTimePoint.worldTime = time;
|
||||||
}
|
}
|
||||||
queueReadFrames();
|
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 {
|
bool VideoTrackObject::interrupted() const {
|
||||||
@ -224,6 +239,9 @@ bool VideoTrackObject::interrupted() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VideoTrackObject::frameDisplayed() {
|
void VideoTrackObject::frameDisplayed() {
|
||||||
|
if (interrupted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
queueReadFrames();
|
queueReadFrames();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +264,7 @@ bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) {
|
|||||||
if (frame.isNull()) {
|
if (frame.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_shared->init(std::move(frame), _timeCorrection.trackTime);
|
_shared->init(std::move(frame), _syncTimePoint.trackTime);
|
||||||
callReady();
|
callReady();
|
||||||
if (!_stream.queue.empty()) {
|
if (!_stream.queue.empty()) {
|
||||||
queueReadFrames();
|
queueReadFrames();
|
||||||
@ -267,13 +285,13 @@ crl::time VideoTrackObject::currentFramePosition() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VideoTrackObject::fillStateFromFrame() {
|
bool VideoTrackObject::fillStateFromFrame() {
|
||||||
Expects(_timeCorrection.trackTime == kTimeUnknown);
|
Expects(_syncTimePoint.trackTime == kTimeUnknown);
|
||||||
|
|
||||||
const auto position = currentFramePosition();
|
const auto position = currentFramePosition();
|
||||||
if (position == kTimeUnknown) {
|
if (position == kTimeUnknown) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_nextFrameDisplayTime = _timeCorrection.trackTime = position;
|
_nextFrameDisplayTime = _syncTimePoint.trackTime = position;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,31 +309,34 @@ 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 = _timeCorrection.trackTime;
|
data.state.position = _syncTimePoint.trackTime;
|
||||||
data.state.receivedTill = _noMoreData
|
data.state.receivedTill = _noMoreData
|
||||||
? _stream.duration
|
? _stream.duration
|
||||||
: _timeCorrection.trackTime;
|
: _syncTimePoint.trackTime;
|
||||||
base::take(_ready)({ data });
|
base::take(_ready)({ data });
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoTrackObject::TrackTime VideoTrackObject::trackTime() const {
|
TimePoint VideoTrackObject::trackTime() const {
|
||||||
auto result = TrackTime();
|
auto result = TimePoint();
|
||||||
result.worldNow = crl::now();
|
result.worldTime = (_pausedTime != kTimeUnknown)
|
||||||
if (!_timeCorrection) {
|
? _pausedTime
|
||||||
result.trackNow = _timeCorrection.trackTime;
|
: crl::now();
|
||||||
|
if (!_syncTimePoint) {
|
||||||
|
result.trackTime = _syncTimePoint.trackTime;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert(_resumedTime != kTimeUnknown);
|
||||||
if (_options.syncVideoByAudio && _audioId.playId()) {
|
if (_options.syncVideoByAudio && _audioId.playId()) {
|
||||||
const auto mixer = Media::Player::mixer();
|
const auto mixer = Media::Player::mixer();
|
||||||
const auto correction = mixer->getVideoTimeCorrection(_audioId);
|
const auto point = mixer->getVideoSyncTimePoint(_audioId);
|
||||||
if (correction && correction.worldTime > _resumedTime) {
|
if (point && point.worldTime > _resumedTime) {
|
||||||
_timeCorrection = correction;
|
_syncTimePoint = point;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto sinceKnown = (result.worldNow - _timeCorrection.worldTime);
|
const auto adjust = (result.worldTime - _syncTimePoint.worldTime);
|
||||||
result.trackNow = _timeCorrection.trackTime
|
result.trackTime = _syncTimePoint.trackTime
|
||||||
+ crl::time(std::round(sinceKnown * _options.speed));
|
+ crl::time(std::round(adjust * _options.speed));
|
||||||
return result;
|
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) {
|
crl::time VideoTrack::markFrameDisplayed(crl::time now) {
|
||||||
const auto position = _shared->markFrameDisplayed(now);
|
const auto position = _shared->markFrameDisplayed(now);
|
||||||
if (position != kTimeUnknown) {
|
if (position != kTimeUnknown) {
|
||||||
|
@ -40,6 +40,9 @@ public:
|
|||||||
void pause(crl::time time);
|
void pause(crl::time time);
|
||||||
void resume(crl::time time);
|
void resume(crl::time time);
|
||||||
|
|
||||||
|
// Called from the main thread.
|
||||||
|
void setSpeed(float64 speed);
|
||||||
|
|
||||||
// Called from the main thread.
|
// 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);
|
||||||
|
Loading…
Reference in New Issue
Block a user