Implement double playback speed

This adds double playback speed for both voice messages and round video
messages.
The 2x playback speed setting is global and is saved in local storage.

Fixes #4907
This commit is contained in:
Magnus Groß 2018-10-26 17:43:24 +02:00 committed by John Preston
parent 8ef67c393b
commit de8518a112
11 changed files with 130 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -649,6 +649,7 @@ struct Data {
bool SuggestEmoji = true; bool SuggestEmoji = true;
bool SuggestStickersByEmoji = true; bool SuggestStickersByEmoji = true;
base::Observable<void> ReplaceEmojiChanged; base::Observable<void> ReplaceEmojiChanged;
float VoiceMsgPlaybackSpeed = 1.f;
bool SoundNotify = true; bool SoundNotify = true;
bool DesktopNotify = true; bool DesktopNotify = true;
bool RestoreSoundNotifyFromTray = false; bool RestoreSoundNotifyFromTray = false;
@ -778,6 +779,7 @@ DefineVar(Global, bool, ReplaceEmoji);
DefineVar(Global, bool, SuggestEmoji); DefineVar(Global, bool, SuggestEmoji);
DefineVar(Global, bool, SuggestStickersByEmoji); DefineVar(Global, bool, SuggestStickersByEmoji);
DefineRefVar(Global, base::Observable<void>, ReplaceEmojiChanged); DefineRefVar(Global, base::Observable<void>, ReplaceEmojiChanged);
DefineVar(Global, float, VoiceMsgPlaybackSpeed);
DefineVar(Global, bool, SoundNotify); DefineVar(Global, bool, SoundNotify);
DefineVar(Global, bool, DesktopNotify); DefineVar(Global, bool, DesktopNotify);
DefineVar(Global, bool, RestoreSoundNotifyFromTray); DefineVar(Global, bool, RestoreSoundNotifyFromTray);

View File

@ -304,6 +304,7 @@ DeclareVar(bool, ReplaceEmoji);
DeclareVar(bool, SuggestEmoji); DeclareVar(bool, SuggestEmoji);
DeclareVar(bool, SuggestStickersByEmoji); DeclareVar(bool, SuggestStickersByEmoji);
DeclareRefVar(base::Observable<void>, ReplaceEmojiChanged); DeclareRefVar(base::Observable<void>, ReplaceEmojiChanged);
DeclareVar(float, VoiceMsgPlaybackSpeed);
DeclareVar(bool, SoundNotify); DeclareVar(bool, SoundNotify);
DeclareVar(bool, DesktopNotify); DeclareVar(bool, DesktopNotify);
DeclareVar(bool, RestoreSoundNotifyFromTray); DeclareVar(bool, RestoreSoundNotifyFromTray);

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/media_audio_track.h" #include "media/media_audio_track.h"
#include "platform/platform_audio.h" #include "platform/platform_audio.h"
#include "messenger.h" #include "messenger.h"
#include "facades.h"
#include <AL/al.h> #include <AL/al.h>
#include <AL/alc.h> #include <AL/alc.h>
@ -44,6 +45,13 @@ namespace {
Player::Mixer *MixerInstance = nullptr; Player::Mixer *MixerInstance = nullptr;
struct PlaybackSpeedData {
ALuint uiEffectSlot = 0;
ALuint uiEffect = 0;
ALuint uiFilter = 0;
};
PlaybackSpeedData _playbackSpeedData;
// Thread: Any. // Thread: Any.
bool ContextErrorHappened() { bool ContextErrorHappened() {
ALenum errCode; ALenum errCode;
@ -144,6 +152,24 @@ bool CreatePlaybackDevice() {
alListener3f(AL_VELOCITY, 0.f, 0.f, 0.f); alListener3f(AL_VELOCITY, 0.f, 0.f, 0.f);
alListenerfv(AL_ORIENTATION, v); alListenerfv(AL_ORIENTATION, v);
// playback speed related init
// generate an effect slot and an effect
alGenAuxiliaryEffectSlots(1, &_playbackSpeedData.uiEffectSlot);
alGenEffects(1, &_playbackSpeedData.uiEffect);
// initialize the pitch shifter effect
alEffecti(_playbackSpeedData.uiEffect, AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER);
// 12 semitones = 1 octave
alEffecti(_playbackSpeedData.uiEffect, AL_PITCH_SHIFTER_COARSE_TUNE, -12);
// connect the effect with the effect slot
alAuxiliaryEffectSloti(_playbackSpeedData.uiEffectSlot, AL_EFFECTSLOT_EFFECT, _playbackSpeedData.uiEffect);
// initialize a filter to disable the direct (dry) path
alGenFilters(1, &_playbackSpeedData.uiFilter);
alFilteri(_playbackSpeedData.uiFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
// disable all frequencies
alFilterf(_playbackSpeedData.uiFilter, AL_LOWPASS_GAIN, 0.f);
// to use the modified playback speed:
// connect both the effect slot and filter with the stream source and set AL_PITCH
alDistanceModel(AL_NONE); alDistanceModel(AL_NONE);
return true; return true;
@ -154,6 +180,15 @@ void ClosePlaybackDevice() {
if (!AudioDevice) return; if (!AudioDevice) return;
LOG(("Audio Info: Closing audio playback device.")); LOG(("Audio Info: Closing audio playback device."));
// playback speed related
alDeleteFilters(1, &_playbackSpeedData.uiFilter);
alDeleteEffects(1, &_playbackSpeedData.uiEffect);
alDeleteAuxiliaryEffectSlots(1, &_playbackSpeedData.uiEffectSlot);
_playbackSpeedData.uiFilter = 0;
_playbackSpeedData.uiEffect = 0;
_playbackSpeedData.uiEffectSlot = 0;
if (Player::mixer()) { if (Player::mixer()) {
Player::mixer()->detachTracks(); Player::mixer()->detachTracks();
} }
@ -290,6 +325,7 @@ void Mixer::Track::createStream() {
alSource3f(stream.source, AL_VELOCITY, 0, 0, 0); alSource3f(stream.source, AL_VELOCITY, 0, 0, 0);
alSourcei(stream.source, AL_LOOPING, 0); alSourcei(stream.source, AL_LOOPING, 0);
alGenBuffers(3, stream.buffers); alGenBuffers(3, stream.buffers);
mixer()->updatePlaybackSpeed();
} }
void Mixer::Track::destroyStream() { void Mixer::Track::destroyStream() {
@ -983,6 +1019,29 @@ void Mixer::stop(const AudioMsgId &audio, State state) {
if (current) emit updated(current); if (current) emit updated(current);
} }
void Mixer::updatePlaybackSpeed()
{
const auto track = trackForType(AudioMsgId::Type::Voice);
if (!track || track->state.id.type() != AudioMsgId::Type::Voice || !track->isStreamCreated()) {
return;
}
const auto src = track->stream.source;
// Note: This alters the playback speed AND the pitch
alSourcef(src, AL_PITCH, Global::VoiceMsgPlaybackSpeed());
// fix the pitch using effects and filters
if (Global::VoiceMsgPlaybackSpeed() > 1.f) {
// connect the effect slot with the stream
alSource3i(src, AL_AUXILIARY_SEND_FILTER, Media::Audio::_playbackSpeedData.uiEffectSlot, 0, 0);
// connect the filter with the stream
alSourcei(src, AL_DIRECT_FILTER, Media::Audio::_playbackSpeedData.uiFilter);
} else {
// disconnect the effect slot
alSource3i(src, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, 0);
// disconnect the filter
alSourcei(src, AL_DIRECT_FILTER, AL_FILTER_NULL);
}
}
void Mixer::stopAndClear() { void Mixer::stopAndClear() {
Track *current_audio = nullptr, *current_song = nullptr; Track *current_audio = nullptr, *current_song = nullptr;
{ {

View File

@ -117,6 +117,8 @@ public:
void stop(const AudioMsgId &audio); void stop(const AudioMsgId &audio);
void stop(const AudioMsgId &audio, State state); void stop(const AudioMsgId &audio, State state);
void updatePlaybackSpeed();
// Video player audio stream interface. // Video player audio stream interface.
void feedFromVideo(VideoSoundPart &&part); void feedFromVideo(VideoSoundPart &&part);
int64 getVideoCorrectedTime(const AudioMsgId &id, TimeMs frameMs, TimeMs systemMs); int64 getVideoCorrectedTime(const AudioMsgId &id, TimeMs frameMs, TimeMs systemMs);

View File

@ -72,6 +72,32 @@ mediaPlayerRepeatInactiveIcon: icon {
{ "player_repeat", mediaPlayerInactiveFg, point(9px, 11px)} { "player_repeat", mediaPlayerInactiveFg, point(9px, 11px)}
}; };
mediaPlayerSpeedButton: IconButton {
width: 31px;
height: 30px;
icon: icon {
{ "voice2x", mediaPlayerActiveFg, point(8px, 11px) }
};
iconPosition: point(0px, 0px);
rippleAreaPosition: point(3px, 5px);
rippleAreaSize: 25px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
}
mediaPlayerSpeedDisabledIcon: icon {
{ "voice2x", menuIconFg, point(8px, 11px)}
};
mediaPlayerSpeedDisabledIconOver: icon {
{ "voice2x", menuIconFgOver, point(8px, 11px)}
};
mediaPlayerSpeedDisabledRippleBg: windowBgOver;
mediaPlayerSpeedInactiveIcon: icon {
{ "voice2x", mediaPlayerInactiveFg, point(8px, 11px)}
};
mediaPlayerVolumeIcon0: icon { mediaPlayerVolumeIcon0: icon {
{ "player_volume0", mediaPlayerActiveFg }, { "player_volume0", mediaPlayerActiveFg },
}; };

View File

@ -22,7 +22,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_media_player.h" #include "styles/style_media_player.h"
#include "styles/style_mediaview.h" #include "styles/style_mediaview.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "storage/localstorage.h"
#include "layout.h" #include "layout.h"
#include "facades.h"
namespace Media { namespace Media {
namespace Player { namespace Player {
@ -80,6 +82,7 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
, _playPause(this) , _playPause(this)
, _volumeToggle(this, st::mediaPlayerVolumeToggle) , _volumeToggle(this, st::mediaPlayerVolumeToggle)
, _repeatTrack(this, st::mediaPlayerRepeatButton) , _repeatTrack(this, st::mediaPlayerRepeatButton)
, _playbackSpeed(this, st::mediaPlayerSpeedButton)
, _close(this, st::mediaPlayerClose) , _close(this, st::mediaPlayerClose)
, _shadow(this) , _shadow(this)
, _playbackSlider(this, st::mediaPlayerPlayback) , _playbackSlider(this, st::mediaPlayerPlayback)
@ -128,6 +131,14 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
instance()->toggleRepeat(AudioMsgId::Type::Song); instance()->toggleRepeat(AudioMsgId::Type::Song);
}); });
updatePlaybackSpeedIcon();
_playbackSpeed->setClickedCallback([=] {
Global::SetVoiceMsgPlaybackSpeed(Global::VoiceMsgPlaybackSpeed() == 1.f ? 2.f : 1.f);
mixer()->updatePlaybackSpeed();
updatePlaybackSpeedIcon();
Local::writeSettings();
});
subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) { subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == _type) { if (type == _type) {
updateRepeatTrackIcon(); updateRepeatTrackIcon();
@ -247,6 +258,9 @@ void Widget::handleSeekFinished(float64 progress) {
void Widget::resizeEvent(QResizeEvent *e) { void Widget::resizeEvent(QResizeEvent *e) {
auto right = st::mediaPlayerCloseRight; auto right = st::mediaPlayerCloseRight;
_close->moveToRight(right, st::mediaPlayerPlayTop); right += _close->width(); _close->moveToRight(right, st::mediaPlayerPlayTop); right += _close->width();
if (_type == AudioMsgId::Type::Voice) {
_playbackSpeed->moveToRight(right, st::mediaPlayerPlayTop); right += _playbackSpeed->width();
}
_repeatTrack->moveToRight(right, st::mediaPlayerPlayTop); right += _repeatTrack->width(); _repeatTrack->moveToRight(right, st::mediaPlayerPlayTop); right += _repeatTrack->width();
_volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width(); _volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width();
@ -330,6 +344,8 @@ int Widget::getLabelsRight() const {
auto result = st::mediaPlayerCloseRight + _close->width(); auto result = st::mediaPlayerCloseRight + _close->width();
if (_type == AudioMsgId::Type::Song) { if (_type == AudioMsgId::Type::Song) {
result += _repeatTrack->width() + _volumeToggle->width(); result += _repeatTrack->width() + _volumeToggle->width();
} else if (_type == AudioMsgId::Type::Voice) {
result += _playbackSpeed->width();
} }
result += st::mediaPlayerPadding; result += st::mediaPlayerPadding;
return result; return result;
@ -353,6 +369,14 @@ void Widget::updateRepeatTrackIcon() {
_repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg); _repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
} }
void Widget::updatePlaybackSpeedIcon()
{
const auto playbackSpeed = Global::VoiceMsgPlaybackSpeed();
const auto isDefaultSpeed = playbackSpeed == 1.f;
_playbackSpeed->setIconOverride(isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIcon : nullptr, isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIconOver : nullptr);
_playbackSpeed->setRippleColorOverride(isDefaultSpeed ? &st::mediaPlayerSpeedDisabledRippleBg : nullptr);
}
void Widget::checkForTypeChange() { void Widget::checkForTypeChange() {
auto hasActiveType = [](AudioMsgId::Type type) { auto hasActiveType = [](AudioMsgId::Type type) {
auto current = instance()->current(type); auto current = instance()->current(type);
@ -372,6 +396,7 @@ void Widget::setType(AudioMsgId::Type type) {
_type = type; _type = type;
_repeatTrack->setVisible(_type == AudioMsgId::Type::Song); _repeatTrack->setVisible(_type == AudioMsgId::Type::Song);
_volumeToggle->setVisible(_type == AudioMsgId::Type::Song); _volumeToggle->setVisible(_type == AudioMsgId::Type::Song);
_playbackSpeed->setVisible(_type == AudioMsgId::Type::Voice);
if (!_shadow->isHidden()) { if (!_shadow->isHidden()) {
_playbackSlider->setVisible(_type == AudioMsgId::Type::Song); _playbackSlider->setVisible(_type == AudioMsgId::Type::Song);
} }
@ -384,6 +409,9 @@ void Widget::setType(AudioMsgId::Type type) {
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
handlePlaylistUpdate(); handlePlaylistUpdate();
}); });
// maybe the type change causes a change of the button layout
QResizeEvent event = { size(), size() };
resizeEvent(&event);
} }
} }

View File

@ -66,6 +66,7 @@ private:
void updatePlayPrevNextPositions(); void updatePlayPrevNextPositions();
void updateLabelsGeometry(); void updateLabelsGeometry();
void updateRepeatTrackIcon(); void updateRepeatTrackIcon();
void updatePlaybackSpeedIcon();
void createPrevNextButtons(); void createPrevNextButtons();
void destroyPrevNextButtons(); void destroyPrevNextButtons();
@ -103,6 +104,7 @@ private:
object_ptr<Ui::IconButton> _nextTrack = { nullptr }; object_ptr<Ui::IconButton> _nextTrack = { nullptr };
object_ptr<Ui::IconButton> _volumeToggle; object_ptr<Ui::IconButton> _volumeToggle;
object_ptr<Ui::IconButton> _repeatTrack; object_ptr<Ui::IconButton> _repeatTrack;
object_ptr<Ui::IconButton> _playbackSpeed;
object_ptr<Ui::IconButton> _close; object_ptr<Ui::IconButton> _close;
object_ptr<Ui::PlainShadow> _shadow = { nullptr }; object_ptr<Ui::PlainShadow> _shadow = { nullptr };
object_ptr<Ui::FilledSlider> _playbackSlider; object_ptr<Ui::FilledSlider> _playbackSlider;

View File

@ -598,6 +598,7 @@ enum {
dbiCacheSettings = 0x56, dbiCacheSettings = 0x56,
dbiAnimationsDisabled = 0x57, dbiAnimationsDisabled = 0x57,
dbiScalePercent = 0x58, dbiScalePercent = 0x58,
dbiPlaybackSpeed = 0x59,
dbiEncryptedWithSalt = 333, dbiEncryptedWithSalt = 333,
dbiEncrypted = 444, dbiEncrypted = 444,
@ -1749,6 +1750,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
Global::SetVideoVolume(snap(v / 1e6, 0., 1.)); Global::SetVideoVolume(snap(v / 1e6, 0., 1.));
} break; } break;
case dbiPlaybackSpeed: {
quint32 v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
Global::SetVoiceMsgPlaybackSpeed(v);
} break;
default: default:
LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId)); LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId));
return false; return false;
@ -2596,6 +2605,7 @@ void writeSettings() {
data.stream << quint32(dbiLoggedPhoneNumber) << cLoggedPhoneNumber(); data.stream << quint32(dbiLoggedPhoneNumber) << cLoggedPhoneNumber();
data.stream << quint32(dbiTxtDomainString) << Global::TxtDomainString(); data.stream << quint32(dbiTxtDomainString) << Global::TxtDomainString();
data.stream << quint32(dbiAnimationsDisabled) << qint32(anim::Disabled() ? 1 : 0); data.stream << quint32(dbiAnimationsDisabled) << qint32(anim::Disabled() ? 1 : 0);
data.stream << quint32(dbiPlaybackSpeed) << quint32(Global::VoiceMsgPlaybackSpeed());
data.stream << quint32(dbiConnectionType) << qint32(dbictProxiesList); data.stream << quint32(dbiConnectionType) << qint32(dbictProxiesList);
data.stream << qint32(proxies.size()); data.stream << qint32(proxies.size());