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

View File

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

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/media_audio_track.h"
#include "platform/platform_audio.h"
#include "messenger.h"
#include "facades.h"
#include <AL/al.h>
#include <AL/alc.h>
@ -44,6 +45,13 @@ namespace {
Player::Mixer *MixerInstance = nullptr;
struct PlaybackSpeedData {
ALuint uiEffectSlot = 0;
ALuint uiEffect = 0;
ALuint uiFilter = 0;
};
PlaybackSpeedData _playbackSpeedData;
// Thread: Any.
bool ContextErrorHappened() {
ALenum errCode;
@ -144,6 +152,24 @@ bool CreatePlaybackDevice() {
alListener3f(AL_VELOCITY, 0.f, 0.f, 0.f);
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);
return true;
@ -154,6 +180,15 @@ void ClosePlaybackDevice() {
if (!AudioDevice) return;
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()) {
Player::mixer()->detachTracks();
}
@ -290,6 +325,7 @@ void Mixer::Track::createStream() {
alSource3f(stream.source, AL_VELOCITY, 0, 0, 0);
alSourcei(stream.source, AL_LOOPING, 0);
alGenBuffers(3, stream.buffers);
mixer()->updatePlaybackSpeed();
}
void Mixer::Track::destroyStream() {
@ -983,6 +1019,29 @@ void Mixer::stop(const AudioMsgId &audio, State state) {
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() {
Track *current_audio = nullptr, *current_song = nullptr;
{

View File

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

View File

@ -72,6 +72,32 @@ mediaPlayerRepeatInactiveIcon: icon {
{ "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 {
{ "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_mediaview.h"
#include "history/history_item.h"
#include "storage/localstorage.h"
#include "layout.h"
#include "facades.h"
namespace Media {
namespace Player {
@ -80,6 +82,7 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
, _playPause(this)
, _volumeToggle(this, st::mediaPlayerVolumeToggle)
, _repeatTrack(this, st::mediaPlayerRepeatButton)
, _playbackSpeed(this, st::mediaPlayerSpeedButton)
, _close(this, st::mediaPlayerClose)
, _shadow(this)
, _playbackSlider(this, st::mediaPlayerPlayback)
@ -128,6 +131,14 @@ Widget::Widget(QWidget *parent) : RpWidget(parent)
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) {
if (type == _type) {
updateRepeatTrackIcon();
@ -247,6 +258,9 @@ void Widget::handleSeekFinished(float64 progress) {
void Widget::resizeEvent(QResizeEvent *e) {
auto right = st::mediaPlayerCloseRight;
_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();
_volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width();
@ -330,6 +344,8 @@ int Widget::getLabelsRight() const {
auto result = st::mediaPlayerCloseRight + _close->width();
if (_type == AudioMsgId::Type::Song) {
result += _repeatTrack->width() + _volumeToggle->width();
} else if (_type == AudioMsgId::Type::Voice) {
result += _playbackSpeed->width();
}
result += st::mediaPlayerPadding;
return result;
@ -353,6 +369,14 @@ void Widget::updateRepeatTrackIcon() {
_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() {
auto hasActiveType = [](AudioMsgId::Type type) {
auto current = instance()->current(type);
@ -372,6 +396,7 @@ void Widget::setType(AudioMsgId::Type type) {
_type = type;
_repeatTrack->setVisible(_type == AudioMsgId::Type::Song);
_volumeToggle->setVisible(_type == AudioMsgId::Type::Song);
_playbackSpeed->setVisible(_type == AudioMsgId::Type::Voice);
if (!_shadow->isHidden()) {
_playbackSlider->setVisible(_type == AudioMsgId::Type::Song);
}
@ -384,6 +409,9 @@ void Widget::setType(AudioMsgId::Type type) {
) | rpl::start_with_next([=] {
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 updateLabelsGeometry();
void updateRepeatTrackIcon();
void updatePlaybackSpeedIcon();
void createPrevNextButtons();
void destroyPrevNextButtons();
@ -103,6 +104,7 @@ private:
object_ptr<Ui::IconButton> _nextTrack = { nullptr };
object_ptr<Ui::IconButton> _volumeToggle;
object_ptr<Ui::IconButton> _repeatTrack;
object_ptr<Ui::IconButton> _playbackSpeed;
object_ptr<Ui::IconButton> _close;
object_ptr<Ui::PlainShadow> _shadow = { nullptr };
object_ptr<Ui::FilledSlider> _playbackSlider;

View File

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