mirror of https://github.com/procxx/kepka.git
Beta 1000002001: Recreate audio device when no output or device changes.
This commit is contained in:
parent
6ae68b337d
commit
4964b8b488
|
@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,2,0
|
||||
PRODUCTVERSION 1,0,2,0
|
||||
FILEVERSION 1,0,2,1
|
||||
PRODUCTVERSION 1,0,2,1
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -51,10 +51,10 @@ BEGIN
|
|||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileVersion", "1.0.2.0"
|
||||
VALUE "FileVersion", "1.0.2.1"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.0.2.0"
|
||||
VALUE "ProductVersion", "1.0.2.1"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,2,0
|
||||
PRODUCTVERSION 1,0,2,0
|
||||
FILEVERSION 1,0,2,1
|
||||
PRODUCTVERSION 1,0,2,1
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -43,10 +43,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Updater"
|
||||
VALUE "FileVersion", "1.0.2.0"
|
||||
VALUE "FileVersion", "1.0.2.1"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.0.2.0"
|
||||
VALUE "ProductVersion", "1.0.2.1"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -97,21 +97,11 @@ enum {
|
|||
MediaOverviewStartPerPage = 5,
|
||||
MediaOverviewPreloadCount = 4,
|
||||
|
||||
AudioSimultaneousLimit = 4,
|
||||
AudioCheckPositionTimeout = 100, // 100ms per check audio pos
|
||||
AudioCheckPositionDelta = 2400, // update position called each 2400 samples
|
||||
AudioFadeTimeout = 7, // 7ms
|
||||
AudioFadeDuration = 500,
|
||||
AudioVoiceMsgSkip = 400, // 200ms
|
||||
AudioVoiceMsgFade = 300, // 300ms
|
||||
AudioPreloadSamples = 2 * 48000, // preload next part if less than 5 seconds remains
|
||||
AudioVoiceMsgFrequency = 48000, // 48 kHz
|
||||
AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes
|
||||
AudioVoiceMsgUpdateView = 100, // 100ms
|
||||
AudioVoiceMsgChannels = 2, // stereo
|
||||
AudioVoiceMsgBufferSize = 256 * 1024, // 256 Kb buffers (1.3 - 3.0 secs)
|
||||
AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded
|
||||
AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over
|
||||
|
||||
WaveformSamplesCount = 100,
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "core/utils.h"
|
||||
|
||||
#define BETA_VERSION_MACRO (0ULL)
|
||||
#define BETA_VERSION_MACRO (1000002001ULL)
|
||||
|
||||
constexpr int AppVersion = 1000002;
|
||||
constexpr str_const AppVersionStr = "1.0.2";
|
||||
|
|
|
@ -1436,44 +1436,43 @@ bool HistoryDocument::updateStatusText() const {
|
|||
} else if (_data->loading()) {
|
||||
statusSize = _data->loadOffset();
|
||||
} else if (_data->loaded()) {
|
||||
using State = Media::Player::State;
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
if (_data->voice()) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Voice);
|
||||
if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
if (auto voice = Get<HistoryDocumentVoice>()) {
|
||||
bool was = voice->_playback;
|
||||
voice->ensurePlayback(this);
|
||||
if (!was || playbackState.position != voice->_playback->_position) {
|
||||
float64 prg = playbackState.duration ? snap(float64(playbackState.position) / playbackState.duration, 0., 1.) : 0.;
|
||||
if (voice->_playback->_position < playbackState.position) {
|
||||
if (!was || state.position != voice->_playback->_position) {
|
||||
float64 prg = state.duration ? snap(float64(state.position) / state.duration, 0., 1.) : 0.;
|
||||
if (voice->_playback->_position < state.position) {
|
||||
voice->_playback->a_progress.start(prg);
|
||||
} else {
|
||||
voice->_playback->a_progress = anim::value(0., prg);
|
||||
}
|
||||
voice->_playback->_position = playbackState.position;
|
||||
voice->_playback->_position = state.position;
|
||||
voice->_playback->_a_progress.start();
|
||||
}
|
||||
}
|
||||
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
} else {
|
||||
if (auto voice = Get<HistoryDocumentVoice>()) {
|
||||
voice->checkPlaybackFinished();
|
||||
}
|
||||
}
|
||||
} else if (_data->song()) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
} else {
|
||||
}
|
||||
if (!showPause && (playing == AudioMsgId(_data, _parent->fullId()))) {
|
||||
if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId()))) {
|
||||
showPause = Media::Player::instance()->isSeeking();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3532,7 +3532,7 @@ void HistoryWidget::onRecordDone(QByteArray result, VoiceWaveform waveform, qint
|
|||
if (!canWriteMessage() || result.isEmpty()) return;
|
||||
|
||||
App::wnd()->activateWindow();
|
||||
auto duration = samples / AudioVoiceMsgFrequency;
|
||||
auto duration = samples / Media::Player::kDefaultFrequency;
|
||||
auto to = FileLoadTo(_peer->id, _silent->checked(), replyToId());
|
||||
auto caption = QString();
|
||||
_fileLoader.addTask(MakeShared<FileLoadTask>(result, duration, waveform, to, caption));
|
||||
|
@ -3547,7 +3547,7 @@ void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) {
|
|||
a_recordingLevel.start(level);
|
||||
_a_recording.start();
|
||||
_recordingSamples = samples;
|
||||
if (samples < 0 || samples >= AudioVoiceMsgFrequency * AudioVoiceMsgMaxLength) {
|
||||
if (samples < 0 || samples >= Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength) {
|
||||
stopRecording(_peer && samples > 0 && _inField);
|
||||
}
|
||||
updateField();
|
||||
|
@ -8736,7 +8736,7 @@ void HistoryWidget::drawRecording(Painter &p, float64 recordActive) {
|
|||
p.drawEllipse(_attachToggle->x() + (_attachEmoji->width() - d) / 2, _attachToggle->y() + (_attachToggle->height() - d) / 2, d, d);
|
||||
}
|
||||
|
||||
QString duration = formatDurationText(_recordingSamples / AudioVoiceMsgFrequency);
|
||||
auto duration = formatDurationText(_recordingSamples / Media::Player::kDefaultFrequency);
|
||||
p.setFont(st::historyRecordFont);
|
||||
|
||||
p.setPen(st::historyRecordDurationFg);
|
||||
|
|
|
@ -830,25 +830,24 @@ bool File::updateStatusText() const {
|
|||
} else if (document->loading()) {
|
||||
statusSize = document->loadOffset();
|
||||
} else if (document->loaded()) {
|
||||
using State = Media::Player::State;
|
||||
if (document->voice()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Voice);
|
||||
if (playing == AudioMsgId(document, FullMsgId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
} else if (document->song()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing == AudioMsgId(document, FullMsgId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
if (!showPause && (playing == AudioMsgId(document, FullMsgId())) && Media::Player::instance()->isSeeking()) {
|
||||
if (!showPause && (state.id == AudioMsgId(document, FullMsgId())) && Media::Player::instance()->isSeeking()) {
|
||||
showPause = true;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1563,10 +1563,10 @@ void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) {
|
|||
}
|
||||
|
||||
void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, audioId.type());
|
||||
if (playing == audioId && playbackState.state == AudioPlayerStoppedAtStart) {
|
||||
playbackState.state = AudioPlayerStopped;
|
||||
using State = Media::Player::State;
|
||||
auto state = Media::Player::mixer()->currentState(audioId.type());
|
||||
if (state.id == audioId && state.state == State::StoppedAtStart) {
|
||||
state.state = State::Stopped;
|
||||
Media::Player::mixer()->clearStoppedAtStart(audioId);
|
||||
|
||||
auto document = audioId.audio();
|
||||
|
@ -1578,8 +1578,8 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) {
|
|||
}
|
||||
}
|
||||
|
||||
if (playing == audioId && audioId.type() == AudioMsgId::Type::Song) {
|
||||
if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
if (state.id == audioId && audioId.type() == AudioMsgId::Type::Song) {
|
||||
if (!Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
if (!_playerUsingPanel && !_player) {
|
||||
createPlayer();
|
||||
}
|
||||
|
@ -1675,9 +1675,8 @@ void MainWidget::playerHeightUpdated() {
|
|||
updateControlsGeometry();
|
||||
}
|
||||
if (!_playerHeight && _player->isHidden()) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing && (playbackState.state & AudioPlayerStoppedMask)) {
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id && Media::Player::IsStopped(state.state)) {
|
||||
_playerVolume.destroyDelayed();
|
||||
_player.destroyDelayed();
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,44 +20,73 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
enum AudioPlayerState {
|
||||
AudioPlayerStopped = 0x01,
|
||||
AudioPlayerStoppedAtEnd = 0x02,
|
||||
AudioPlayerStoppedAtError = 0x03,
|
||||
AudioPlayerStoppedAtStart = 0x04,
|
||||
AudioPlayerStoppedMask = 0x07,
|
||||
|
||||
AudioPlayerStarting = 0x08,
|
||||
AudioPlayerPlaying = 0x10,
|
||||
AudioPlayerFinishing = 0x18,
|
||||
AudioPlayerPausing = 0x20,
|
||||
AudioPlayerPaused = 0x28,
|
||||
AudioPlayerPausedAtEnd = 0x30,
|
||||
AudioPlayerResuming = 0x38,
|
||||
};
|
||||
|
||||
struct VideoSoundData;
|
||||
struct VideoSoundPart;
|
||||
struct AudioPlaybackState {
|
||||
AudioPlayerState state = AudioPlayerStopped;
|
||||
int64 position = 0;
|
||||
TimeMs duration = 0;
|
||||
int32 frequency = 0;
|
||||
};
|
||||
|
||||
namespace Media {
|
||||
namespace Player {
|
||||
|
||||
constexpr auto kDefaultFrequency = 48000; // 48 kHz
|
||||
constexpr auto kTogetherLimit = 4;
|
||||
|
||||
class Fader;
|
||||
class Loaders;
|
||||
|
||||
void InitAudio();
|
||||
void DeInitAudio();
|
||||
|
||||
base::Observable<AudioMsgId> &Updated();
|
||||
bool CreateAudioPlaybackDevice();
|
||||
void DetachFromDeviceByTimer();
|
||||
|
||||
void PlayNotify();
|
||||
|
||||
class Fader;
|
||||
class Loaders;
|
||||
float64 ComputeVolume(AudioMsgId::Type type);
|
||||
|
||||
enum class State {
|
||||
Stopped = 0x01,
|
||||
StoppedAtEnd = 0x02,
|
||||
StoppedAtError = 0x03,
|
||||
StoppedAtStart = 0x04,
|
||||
|
||||
Starting = 0x08,
|
||||
Playing = 0x10,
|
||||
Finishing = 0x18,
|
||||
Pausing = 0x20,
|
||||
Paused = 0x28,
|
||||
PausedAtEnd = 0x30,
|
||||
Resuming = 0x38,
|
||||
};
|
||||
|
||||
inline bool IsStopped(State state) {
|
||||
return (state == State::Stopped)
|
||||
|| (state == State::StoppedAtEnd)
|
||||
|| (state == State::StoppedAtError)
|
||||
|| (state == State::StoppedAtStart);
|
||||
}
|
||||
|
||||
inline bool IsPaused(State state) {
|
||||
return (state == State::Paused)
|
||||
|| (state == State::PausedAtEnd);
|
||||
}
|
||||
|
||||
inline bool IsFading(State state) {
|
||||
return (state == State::Starting)
|
||||
|| (state == State::Finishing)
|
||||
|| (state == State::Pausing)
|
||||
|| (state == State::Resuming);
|
||||
}
|
||||
|
||||
inline bool IsActive(State state) {
|
||||
return !IsStopped(state) && !IsPaused(state);
|
||||
}
|
||||
|
||||
struct TrackState {
|
||||
AudioMsgId id;
|
||||
State state = State::Stopped;
|
||||
int64 position = 0;
|
||||
TimeMs duration = 0;
|
||||
int frequency = kDefaultFrequency;
|
||||
};
|
||||
|
||||
class Mixer : public QObject, private base::Subscriber {
|
||||
Q_OBJECT
|
||||
|
@ -67,27 +96,32 @@ public:
|
|||
|
||||
void play(const AudioMsgId &audio, int64 position = 0);
|
||||
void pauseresume(AudioMsgId::Type type, bool fast = false);
|
||||
void seek(int64 position); // type == AudioMsgId::Type::Song
|
||||
void seek(AudioMsgId::Type type, int64 position); // type == AudioMsgId::Type::Song
|
||||
void stop(AudioMsgId::Type type);
|
||||
|
||||
// Video player audio stream interface.
|
||||
void initFromVideo(uint64 videoPlayId, std_::unique_ptr<VideoSoundData> &&data, int64 position);
|
||||
void feedFromVideo(VideoSoundPart &&part);
|
||||
int64 getVideoCorrectedTime(uint64 playId, TimeMs frameMs, TimeMs systemMs);
|
||||
AudioPlaybackState currentVideoState(uint64 videoPlayId);
|
||||
TrackState currentVideoState(uint64 videoPlayId);
|
||||
void stopFromVideo(uint64 videoPlayId);
|
||||
void pauseFromVideo(uint64 videoPlayId);
|
||||
void resumeFromVideo(uint64 videoPlayId);
|
||||
|
||||
void stopAndClear();
|
||||
|
||||
AudioPlaybackState currentState(AudioMsgId *audio, AudioMsgId::Type type);
|
||||
TrackState currentState(AudioMsgId::Type type);
|
||||
|
||||
void clearStoppedAtStart(const AudioMsgId &audio);
|
||||
|
||||
void detachFromDeviceByTimer();
|
||||
void detachTracks();
|
||||
void reattachIfNeeded();
|
||||
void reattachTracks();
|
||||
|
||||
~Mixer();
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onError(const AudioMsgId &audio);
|
||||
void onStopped(const AudioMsgId &audio);
|
||||
|
||||
|
@ -107,55 +141,70 @@ signals:
|
|||
|
||||
private:
|
||||
bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0);
|
||||
bool updateCurrentStarted(AudioMsgId::Type type, int32 pos = -1);
|
||||
void resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered = -1);
|
||||
bool checkCurrentALError(AudioMsgId::Type type);
|
||||
|
||||
void videoSoundProgress(const AudioMsgId &audio);
|
||||
|
||||
struct AudioMsg {
|
||||
void clear();
|
||||
class Track {
|
||||
public:
|
||||
static constexpr int kBuffersCount = 3;
|
||||
|
||||
AudioMsgId audio;
|
||||
void reattach(AudioMsgId::Type type);
|
||||
void detach();
|
||||
void clear();
|
||||
void started();
|
||||
|
||||
bool isStreamCreated() const;
|
||||
void ensureStreamCreated();
|
||||
|
||||
int getNotQueuedBufferIndex();
|
||||
|
||||
TrackState state;
|
||||
|
||||
FileLocation file;
|
||||
QByteArray data;
|
||||
AudioPlaybackState playbackState = defaultState();
|
||||
int64 skipStart = 0;
|
||||
int64 skipEnd = 0;
|
||||
int64 bufferedPosition = 0;
|
||||
int64 bufferedLength = 0;
|
||||
bool loading = false;
|
||||
int64 started = 0;
|
||||
bool loaded = false;
|
||||
int64 fadeStartPosition = 0;
|
||||
|
||||
uint32 source = 0;
|
||||
int32 nextBuffer = 0;
|
||||
uint32 buffers[3] = { 0 };
|
||||
int64 samplesCount[3] = { 0 };
|
||||
int32 format = 0;
|
||||
int32 frequency = kDefaultFrequency;
|
||||
int samplesCount[kBuffersCount] = { 0 };
|
||||
QByteArray bufferSamples[kBuffersCount];
|
||||
|
||||
struct Stream {
|
||||
uint32 source = 0;
|
||||
uint32 buffers[kBuffersCount] = { 0 };
|
||||
};
|
||||
Stream stream;
|
||||
|
||||
uint64 videoPlayId = 0;
|
||||
std_::unique_ptr<VideoSoundData> videoData;
|
||||
|
||||
private:
|
||||
static AudioPlaybackState defaultState() {
|
||||
AudioPlaybackState result;
|
||||
result.frequency = AudioVoiceMsgFrequency;
|
||||
return result;
|
||||
}
|
||||
void createStream();
|
||||
void destroyStream();
|
||||
void resetStream();
|
||||
|
||||
};
|
||||
|
||||
void setStoppedState(AudioMsg *current, AudioPlayerState state = AudioPlayerStopped);
|
||||
void setStoppedState(Track *current, State state = State::Stopped);
|
||||
|
||||
AudioMsg *dataForType(AudioMsgId::Type type, int index = -1); // -1 uses currentIndex(type)
|
||||
const AudioMsg *dataForType(AudioMsgId::Type type, int index = -1) const;
|
||||
Track *trackForType(AudioMsgId::Type type, int index = -1); // -1 uses currentIndex(type)
|
||||
const Track *trackForType(AudioMsgId::Type type, int index = -1) const;
|
||||
int *currentIndex(AudioMsgId::Type type);
|
||||
const int *currentIndex(AudioMsgId::Type type) const;
|
||||
|
||||
int _audioCurrent = 0;
|
||||
AudioMsg _audioData[AudioSimultaneousLimit];
|
||||
Track _audioTracks[kTogetherLimit];
|
||||
|
||||
int _songCurrent = 0;
|
||||
AudioMsg _songData[AudioSimultaneousLimit];
|
||||
Track _songTracks[kTogetherLimit];
|
||||
|
||||
AudioMsg _videoData;
|
||||
Track _videoTrack;
|
||||
uint64 _lastVideoPlayId = 0;
|
||||
TimeMs _lastVideoPlaybackWhen = 0;
|
||||
TimeMs _lastVideoPlaybackCorrectedMs = 0;
|
||||
|
@ -179,7 +228,7 @@ class Fader : public QObject {
|
|||
|
||||
public:
|
||||
Fader(QThread *thread);
|
||||
void resumeDevice();
|
||||
void keepAttachedToDevice();
|
||||
|
||||
signals:
|
||||
void error(const AudioMsgId &audio);
|
||||
|
@ -187,13 +236,12 @@ signals:
|
|||
void audioStopped(const AudioMsgId &audio);
|
||||
void needToPreload(const AudioMsgId &audio);
|
||||
|
||||
void stopPauseDevice();
|
||||
|
||||
public slots:
|
||||
void onDetachFromDeviceByTimer(bool force);
|
||||
|
||||
void onInit();
|
||||
void onTimer();
|
||||
void onPauseTimer();
|
||||
void onPauseTimerStop();
|
||||
void onDetachFromDeviceTimer();
|
||||
|
||||
void onSuppressSong();
|
||||
void onUnsuppressSong();
|
||||
|
@ -208,23 +256,25 @@ private:
|
|||
EmitPositionUpdated = 0x04,
|
||||
EmitNeedToPreload = 0x08,
|
||||
};
|
||||
int32 updateOnePlayback(Mixer::AudioMsg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged);
|
||||
void setStoppedState(Mixer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped);
|
||||
int32 updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged);
|
||||
void setStoppedState(Mixer::Track *track, State state = State::Stopped);
|
||||
|
||||
QTimer _timer, _pauseTimer;
|
||||
QMutex _pauseMutex;
|
||||
bool _pauseFlag = false;
|
||||
bool _paused = true;
|
||||
QTimer _timer;
|
||||
|
||||
bool _suppressAll = false;
|
||||
bool _suppressAllAnim = false;
|
||||
bool _suppressSong = false;
|
||||
bool _suppressSongAnim = false;
|
||||
bool _songVolumeChanged, _videoVolumeChanged;
|
||||
bool _songVolumeChanged = false;
|
||||
bool _videoVolumeChanged = false;
|
||||
anim::value _suppressAllGain, _suppressSongGain;
|
||||
TimeMs _suppressAllStart = 0;
|
||||
TimeMs _suppressSongStart = 0;
|
||||
|
||||
QTimer _detachFromDeviceTimer;
|
||||
QMutex _detachFromDeviceMutex;
|
||||
bool _detachFromDeviceForce = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Player
|
||||
|
@ -233,10 +283,11 @@ private:
|
|||
namespace internal {
|
||||
|
||||
QMutex *audioPlayerMutex();
|
||||
float64 audioSuppressGain();
|
||||
float64 audioSuppressSongGain();
|
||||
bool audioCheckError();
|
||||
|
||||
// AudioMutex must be locked.
|
||||
bool CheckAudioDeviceConnected();
|
||||
|
||||
} // namespace internal
|
||||
|
||||
MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat);
|
||||
|
|
|
@ -30,10 +30,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include <AL/alext.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
namespace Capture {
|
||||
namespace {
|
||||
|
||||
constexpr auto kCaptureFrequency = Player::kDefaultFrequency;
|
||||
constexpr auto kCaptureSkipDuration = TimeMs(400);
|
||||
constexpr auto kCaptureFadeInDuration = TimeMs(300);
|
||||
|
||||
Instance *CaptureInstance = nullptr;
|
||||
|
||||
bool ErrorHappened(ALCdevice *device) {
|
||||
|
@ -72,7 +75,7 @@ Instance::Instance() : _inner(new Inner(&_thread)) {
|
|||
void Instance::check() {
|
||||
_available = false;
|
||||
if (auto defaultDevice = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) {
|
||||
if (auto device = alcCaptureOpenDevice(defaultDevice, AudioVoiceMsgFrequency, AL_FORMAT_MONO16, AudioVoiceMsgFrequency / 5)) {
|
||||
if (auto device = alcCaptureOpenDevice(defaultDevice, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5)) {
|
||||
auto error = ErrorHappened(device);
|
||||
alcCaptureCloseDevice(device);
|
||||
_available = !error;
|
||||
|
@ -117,7 +120,7 @@ struct Instance::Inner::Private {
|
|||
int32 dataPos = 0;
|
||||
|
||||
int64 waveformMod = 0;
|
||||
int64 waveformEach = (AudioVoiceMsgFrequency / 100);
|
||||
int64 waveformEach = (kCaptureFrequency / 100);
|
||||
uint16 waveformPeak = 0;
|
||||
QVector<uchar> waveform;
|
||||
|
||||
|
@ -180,7 +183,7 @@ void Instance::Inner::onStart() {
|
|||
// Start OpenAL Capture
|
||||
const ALCchar *dName = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
|
||||
DEBUG_LOG(("Audio Info: Capture device name '%1'").arg(dName));
|
||||
d->device = alcCaptureOpenDevice(dName, AudioVoiceMsgFrequency, AL_FORMAT_MONO16, AudioVoiceMsgFrequency / 5);
|
||||
d->device = alcCaptureOpenDevice(dName, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5);
|
||||
if (!d->device) {
|
||||
LOG(("Audio Error: capture device not present!"));
|
||||
emit error();
|
||||
|
@ -253,7 +256,7 @@ void Instance::Inner::onStart() {
|
|||
d->codecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
|
||||
d->codecContext->bit_rate = 64000;
|
||||
d->codecContext->channel_layout = AV_CH_LAYOUT_MONO;
|
||||
d->codecContext->sample_rate = AudioVoiceMsgFrequency;
|
||||
d->codecContext->sample_rate = kCaptureFrequency;
|
||||
d->codecContext->channels = 1;
|
||||
|
||||
if (d->fmtContext->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
|
@ -341,8 +344,9 @@ void Instance::Inner::onStop(bool needResult) {
|
|||
|
||||
// Write what is left
|
||||
if (!_captured.isEmpty()) {
|
||||
int32 fadeSamples = AudioVoiceMsgFade * AudioVoiceMsgFrequency / 1000, capturedSamples = _captured.size() / sizeof(short);
|
||||
if ((_captured.size() % sizeof(short)) || (d->fullSamples + capturedSamples < AudioVoiceMsgFrequency) || (capturedSamples < fadeSamples)) {
|
||||
auto fadeSamples = kCaptureFadeInDuration * kCaptureFrequency / 1000;
|
||||
auto capturedSamples = static_cast<int>(_captured.size() / sizeof(short));
|
||||
if ((_captured.size() % sizeof(short)) || (d->fullSamples + capturedSamples < kCaptureFrequency) || (capturedSamples < fadeSamples)) {
|
||||
d->fullSamples = 0;
|
||||
d->dataPos = 0;
|
||||
d->data.clear();
|
||||
|
@ -489,7 +493,8 @@ void Instance::Inner::onTimeout() {
|
|||
}
|
||||
if (samples > 0) {
|
||||
// Get samples from OpenAL
|
||||
int32 s = _captured.size(), news = s + samples * sizeof(short);
|
||||
auto s = _captured.size();
|
||||
auto news = s + static_cast<int>(samples * sizeof(short));
|
||||
if (news / AudioVoiceMsgBufferSize > s / AudioVoiceMsgBufferSize) {
|
||||
_captured.reserve(((news / AudioVoiceMsgBufferSize) + 1) * AudioVoiceMsgBufferSize);
|
||||
}
|
||||
|
@ -502,9 +507,10 @@ void Instance::Inner::onTimeout() {
|
|||
}
|
||||
|
||||
// Count new recording level and update view
|
||||
int32 skipSamples = AudioVoiceMsgSkip * AudioVoiceMsgFrequency / 1000, fadeSamples = AudioVoiceMsgFade * AudioVoiceMsgFrequency / 1000;
|
||||
int32 levelindex = d->fullSamples + (s / sizeof(short));
|
||||
for (const short *ptr = (const short*)(_captured.constData() + s), *end = (const short*)(_captured.constData() + news); ptr < end; ++ptr, ++levelindex) {
|
||||
auto skipSamples = kCaptureSkipDuration * kCaptureFrequency / 1000;
|
||||
auto fadeSamples = kCaptureFadeInDuration * kCaptureFrequency / 1000;
|
||||
auto levelindex = d->fullSamples + static_cast<int>(s / sizeof(short));
|
||||
for (auto ptr = (const short*)(_captured.constData() + s), end = (const short*)(_captured.constData() + news); ptr < end; ++ptr, ++levelindex) {
|
||||
if (levelindex > skipSamples) {
|
||||
uint16 value = qAbs(*ptr);
|
||||
if (levelindex < skipSamples + fadeSamples) {
|
||||
|
@ -516,7 +522,7 @@ void Instance::Inner::onTimeout() {
|
|||
}
|
||||
}
|
||||
qint32 samplesFull = d->fullSamples + _captured.size() / sizeof(short), samplesSinceUpdate = samplesFull - d->lastUpdate;
|
||||
if (samplesSinceUpdate > AudioVoiceMsgUpdateView * AudioVoiceMsgFrequency / 1000) {
|
||||
if (samplesSinceUpdate > AudioVoiceMsgUpdateView * kCaptureFrequency / 1000) {
|
||||
emit updated(d->levelMax, samplesFull);
|
||||
d->lastUpdate = samplesFull;
|
||||
d->levelMax = 0;
|
||||
|
@ -548,7 +554,7 @@ void Instance::Inner::processFrame(int32 offset, int32 framesize) {
|
|||
emit error();
|
||||
return;
|
||||
}
|
||||
int32 samplesCnt = framesize / sizeof(short);
|
||||
auto samplesCnt = static_cast<int>(framesize / sizeof(short));
|
||||
|
||||
int res = 0;
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
|
@ -557,7 +563,8 @@ void Instance::Inner::processFrame(int32 offset, int32 framesize) {
|
|||
auto srcSamplesData = &srcSamplesDataChannel;
|
||||
|
||||
// memcpy(d->srcSamplesData[0], _captured.constData() + offset, framesize);
|
||||
int32 skipSamples = AudioVoiceMsgSkip * AudioVoiceMsgFrequency / 1000, fadeSamples = AudioVoiceMsgFade * AudioVoiceMsgFrequency / 1000;
|
||||
auto skipSamples = static_cast<int>(kCaptureSkipDuration * kCaptureFrequency / 1000);
|
||||
auto fadeSamples = static_cast<int>(kCaptureFadeInDuration * kCaptureFrequency / 1000);
|
||||
if (d->fullSamples < skipSamples + fadeSamples) {
|
||||
int32 fadedCnt = qMin(samplesCnt, skipSamples + fadeSamples - d->fullSamples);
|
||||
float64 coef = 1. / fadeSamples, fadedFrom = d->fullSamples - skipSamples;
|
||||
|
|
|
@ -204,7 +204,7 @@ bool FFMpegLoader::open(qint64 &position) {
|
|||
int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout;
|
||||
srcRate = freq;
|
||||
AVSampleFormat src_sample_fmt = inputFormat, dst_sample_fmt = AudioToFormat;
|
||||
dstRate = (freq != 44100 && freq != 48000) ? AudioVoiceMsgFrequency : freq;
|
||||
dstRate = (freq != 44100 && freq != 48000) ? Media::Player::kDefaultFrequency : freq;
|
||||
|
||||
av_opt_set_int(swrContext, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(swrContext, "in_sample_rate", srcRate, 0);
|
||||
|
|
|
@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "media/media_audio.h"
|
||||
#include "media/media_audio_loader.h"
|
||||
|
||||
extern "C" {
|
||||
|
@ -49,7 +50,7 @@ public:
|
|||
~AbstractFFMpegLoader();
|
||||
|
||||
protected:
|
||||
int32 freq = AudioVoiceMsgFrequency;
|
||||
int32 freq = Media::Player::kDefaultFrequency;
|
||||
TimeMs len = 0;
|
||||
|
||||
uchar *ioBuffer = nullptr;
|
||||
|
@ -89,8 +90,8 @@ private:
|
|||
ReadResult readFromReadyFrame(QByteArray &result, int64 &samplesAdded);
|
||||
|
||||
int32 fmt = AL_FORMAT_STEREO16;
|
||||
int32 srcRate = AudioVoiceMsgFrequency;
|
||||
int32 dstRate = AudioVoiceMsgFrequency;
|
||||
int32 srcRate = Media::Player::kDefaultFrequency;
|
||||
int32 dstRate = Media::Player::kDefaultFrequency;
|
||||
int32 maxResampleSamples = 1024;
|
||||
uint8_t **dstSamplesData = nullptr;
|
||||
|
||||
|
|
|
@ -98,10 +98,10 @@ void Loaders::onStart(const AudioMsgId &audio, qint64 position) {
|
|||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
if (!mixer()) return;
|
||||
|
||||
auto data = mixer()->dataForType(type);
|
||||
if (!data) return;
|
||||
auto track = mixer()->trackForType(type);
|
||||
if (!track) return;
|
||||
|
||||
data->loading = true;
|
||||
track->loading = true;
|
||||
}
|
||||
|
||||
loadData(audio, position);
|
||||
|
@ -117,9 +117,9 @@ AudioMsgId Loaders::clear(AudioMsgId::Type type) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void Loaders::setStoppedState(Mixer::AudioMsg *m, AudioPlayerState state) {
|
||||
m->playbackState.state = state;
|
||||
m->playbackState.position = 0;
|
||||
void Loaders::setStoppedState(Mixer::Track *track, State state) {
|
||||
track->state.state = state;
|
||||
track->state.position = 0;
|
||||
}
|
||||
|
||||
void Loaders::emitError(AudioMsgId::Type type) {
|
||||
|
@ -131,9 +131,9 @@ void Loaders::onLoad(const AudioMsgId &audio) {
|
|||
}
|
||||
|
||||
void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
||||
SetupError err = SetupNoErrorStarted;
|
||||
auto err = SetupNoErrorStarted;
|
||||
auto type = audio.type();
|
||||
AudioPlayerLoader *l = setupLoader(audio, err, position);
|
||||
auto l = setupLoader(audio, err, position);
|
||||
if (!l) {
|
||||
if (err == SetupErrorAtStart) {
|
||||
emitError(type);
|
||||
|
@ -141,10 +141,10 @@ void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
|||
return;
|
||||
}
|
||||
|
||||
bool started = (err == SetupNoErrorStarted);
|
||||
bool finished = false;
|
||||
bool waiting = false;
|
||||
bool errAtStart = started;
|
||||
auto started = (err == SetupNoErrorStarted);
|
||||
auto finished = false;
|
||||
auto waiting = false;
|
||||
auto errAtStart = started;
|
||||
|
||||
QByteArray samples;
|
||||
int64 samplesCount = 0;
|
||||
|
@ -158,8 +158,9 @@ void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
|||
if (errAtStart) {
|
||||
{
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
auto m = checkLoader(type);
|
||||
if (m) m->playbackState.state = AudioPlayerStoppedAtStart;
|
||||
if (auto track = checkLoader(type)) {
|
||||
track->state.state = State::StoppedAtStart;
|
||||
}
|
||||
}
|
||||
emitError(type);
|
||||
return;
|
||||
|
@ -187,93 +188,54 @@ void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
|||
}
|
||||
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
auto m = checkLoader(type);
|
||||
if (!m) {
|
||||
auto track = checkLoader(type);
|
||||
if (!track) {
|
||||
clear(type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (started) {
|
||||
if (m->source) {
|
||||
alSourceStop(m->source);
|
||||
for (int32 i = 0; i < 3; ++i) {
|
||||
if (m->samplesCount[i]) {
|
||||
ALuint buffer = 0;
|
||||
alSourceUnqueueBuffers(m->source, 1, &buffer);
|
||||
m->samplesCount[i] = 0;
|
||||
}
|
||||
}
|
||||
m->nextBuffer = 0;
|
||||
mixer()->reattachTracks();
|
||||
|
||||
track->started();
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(track, State::StoppedAtStart);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
m->skipStart = position;
|
||||
m->skipEnd = m->playbackState.duration - position;
|
||||
m->playbackState.position = position;
|
||||
m->started = 0;
|
||||
|
||||
track->bufferedPosition = position;
|
||||
track->state.position = position;
|
||||
track->fadeStartPosition = position;
|
||||
|
||||
track->format = l->format();
|
||||
track->frequency = l->frequency();
|
||||
}
|
||||
if (samplesCount) {
|
||||
if (!m->source) {
|
||||
alGenSources(1, &m->source);
|
||||
alSourcef(m->source, AL_PITCH, 1.f);
|
||||
alSource3f(m->source, AL_POSITION, 0, 0, 0);
|
||||
alSource3f(m->source, AL_VELOCITY, 0, 0, 0);
|
||||
alSourcei(m->source, AL_LOOPING, 0);
|
||||
}
|
||||
if (!m->buffers[m->nextBuffer]) {
|
||||
alGenBuffers(3, m->buffers);
|
||||
}
|
||||
track->ensureStreamCreated();
|
||||
|
||||
// If this buffer is queued, try to unqueue some buffer.
|
||||
if (m->samplesCount[m->nextBuffer]) {
|
||||
ALint processed = 0;
|
||||
alGetSourcei(m->source, AL_BUFFERS_PROCESSED, &processed);
|
||||
if (processed < 1) { // No processed buffers, wait.
|
||||
l->saveDecodedSamples(&samples, &samplesCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unqueue some processed buffer.
|
||||
ALuint buffer = 0;
|
||||
alSourceUnqueueBuffers(m->source, 1, &buffer);
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find it in the list and make it the nextBuffer.
|
||||
bool found = false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (m->buffers[i] == buffer) {
|
||||
found = true;
|
||||
m->nextBuffer = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LOG(("Audio Error: Could not find the unqueued buffer! Buffer %1 in source %2 with processed count %3").arg(buffer).arg(m->source).arg(processed));
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m->samplesCount[m->nextBuffer]) {
|
||||
m->skipStart += m->samplesCount[m->nextBuffer];
|
||||
m->samplesCount[m->nextBuffer] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto frequency = l->frequency();
|
||||
auto format = l->format();
|
||||
m->samplesCount[m->nextBuffer] = samplesCount;
|
||||
alBufferData(m->buffers[m->nextBuffer], format, samples.constData(), samples.size(), frequency);
|
||||
|
||||
alSourceQueueBuffers(m->source, 1, m->buffers + m->nextBuffer);
|
||||
m->skipEnd -= samplesCount;
|
||||
|
||||
m->nextBuffer = (m->nextBuffer + 1) % 3;
|
||||
auto bufferIndex = track->getNotQueuedBufferIndex();
|
||||
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
setStoppedState(track, State::StoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bufferIndex < 0) { // No free buffers, wait.
|
||||
l->saveDecodedSamples(&samples, &samplesCount);
|
||||
return;
|
||||
}
|
||||
|
||||
track->bufferSamples[bufferIndex] = samples;
|
||||
track->samplesCount[bufferIndex] = samplesCount;
|
||||
track->bufferedLength += samplesCount;
|
||||
alBufferData(track->stream.buffers[bufferIndex], track->format, samples.constData(), samples.size(), track->frequency);
|
||||
|
||||
alSourceQueueBuffers(track->stream.source, 1, track->stream.buffers + bufferIndex);
|
||||
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(track, State::StoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
@ -285,31 +247,31 @@ void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
|||
}
|
||||
|
||||
if (finished) {
|
||||
m->skipEnd = 0;
|
||||
m->playbackState.duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2];
|
||||
track->loaded = true;
|
||||
track->state.duration = track->bufferedPosition + track->bufferedLength;
|
||||
clear(type);
|
||||
}
|
||||
|
||||
m->loading = false;
|
||||
if (m->playbackState.state == AudioPlayerResuming || m->playbackState.state == AudioPlayerPlaying || m->playbackState.state == AudioPlayerStarting) {
|
||||
track->loading = false;
|
||||
if (track->state.state == State::Resuming || track->state.state == State::Playing || track->state.state == State::Starting) {
|
||||
ALint state = AL_INITIAL;
|
||||
alGetSourcei(m->source, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
|
||||
if (internal::audioCheckError()) {
|
||||
if (state != AL_PLAYING) {
|
||||
switch (type) {
|
||||
case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, internal::audioSuppressGain()); break;
|
||||
case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * Global::SongVolume()); break;
|
||||
case AudioMsgId::Type::Video: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * Global::VideoVolume()); break;
|
||||
if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
alSourcef(track->stream.source, AL_GAIN, ComputeVolume(type));
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
setStoppedState(track, State::StoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
alSourcePlay(m->source);
|
||||
alSourcePlay(track->stream.source);
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
setStoppedState(track, State::StoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
@ -317,7 +279,7 @@ void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
|||
emit needToCheck();
|
||||
}
|
||||
} else {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
setStoppedState(track, State::StoppedAtError);
|
||||
emitError(type);
|
||||
}
|
||||
}
|
||||
|
@ -328,8 +290,8 @@ AudioPlayerLoader *Loaders::setupLoader(const AudioMsgId &audio, SetupError &err
|
|||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
if (!mixer()) return nullptr;
|
||||
|
||||
auto data = mixer()->dataForType(audio.type());
|
||||
if (!data || data->audio != audio || !data->loading) {
|
||||
auto track = mixer()->trackForType(audio.type());
|
||||
if (!track || track->state.id != audio || !track->loading) {
|
||||
emit error(audio);
|
||||
LOG(("Audio Error: trying to load part of audio, that is not current at the moment"));
|
||||
err = SetupErrorNotPlaying;
|
||||
|
@ -344,7 +306,7 @@ AudioPlayerLoader *Loaders::setupLoader(const AudioMsgId &audio, SetupError &err
|
|||
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_video == audio); break;
|
||||
}
|
||||
|
||||
if (l && (!isGoodId || !l->check(data->file, data->data))) {
|
||||
if (l && (!isGoodId || !l->check(track->file, track->data))) {
|
||||
clear(audio.type());
|
||||
l = nullptr;
|
||||
}
|
||||
|
@ -358,61 +320,59 @@ AudioPlayerLoader *Loaders::setupLoader(const AudioMsgId &audio, SetupError &err
|
|||
}
|
||||
|
||||
if (audio.type() == AudioMsgId::Type::Video) {
|
||||
if (!data->videoData) {
|
||||
data->playbackState.state = AudioPlayerStoppedAtError;
|
||||
if (!track->videoData) {
|
||||
track->state.state = State::StoppedAtError;
|
||||
emit error(audio);
|
||||
LOG(("Audio Error: video sound data not ready"));
|
||||
return nullptr;
|
||||
}
|
||||
_videoLoader = std_::make_unique<ChildFFMpegLoader>(data->videoPlayId, std_::move(data->videoData));
|
||||
_videoLoader = std_::make_unique<ChildFFMpegLoader>(track->videoPlayId, std_::move(track->videoData));
|
||||
l = _videoLoader.get();
|
||||
} else {
|
||||
*loader = std_::make_unique<FFMpegLoader>(data->file, data->data);
|
||||
*loader = std_::make_unique<FFMpegLoader>(track->file, track->data);
|
||||
l = loader->get();
|
||||
}
|
||||
|
||||
if (!l->open(position)) {
|
||||
data->playbackState.state = AudioPlayerStoppedAtStart;
|
||||
track->state.state = State::StoppedAtStart;
|
||||
return nullptr;
|
||||
}
|
||||
int64 duration = l->duration();
|
||||
if (duration <= 0) {
|
||||
data->playbackState.state = AudioPlayerStoppedAtStart;
|
||||
track->state.state = State::StoppedAtStart;
|
||||
return nullptr;
|
||||
}
|
||||
data->playbackState.duration = duration;
|
||||
data->playbackState.frequency = l->frequency();
|
||||
if (!data->playbackState.frequency) data->playbackState.frequency = AudioVoiceMsgFrequency;
|
||||
track->state.duration = duration;
|
||||
track->state.frequency = l->frequency();
|
||||
if (!track->state.frequency) track->state.frequency = kDefaultFrequency;
|
||||
err = SetupNoErrorStarted;
|
||||
} else {
|
||||
if (!data->skipEnd) {
|
||||
err = SetupErrorLoadedFull;
|
||||
LOG(("Audio Error: trying to load part of audio, that is already loaded to the end"));
|
||||
return nullptr;
|
||||
}
|
||||
} else if (track->loaded) {
|
||||
err = SetupErrorLoadedFull;
|
||||
LOG(("Audio Error: trying to load part of audio, that is already loaded to the end"));
|
||||
return nullptr;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
Mixer::AudioMsg *Loaders::checkLoader(AudioMsgId::Type type) {
|
||||
Mixer::Track *Loaders::checkLoader(AudioMsgId::Type type) {
|
||||
if (!mixer()) return nullptr;
|
||||
|
||||
auto data = mixer()->dataForType(type);
|
||||
bool isGoodId = false;
|
||||
auto track = mixer()->trackForType(type);
|
||||
auto isGoodId = false;
|
||||
AudioPlayerLoader *l = nullptr;
|
||||
switch (type) {
|
||||
case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (data->audio == _audio); break;
|
||||
case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (data->audio == _song); break;
|
||||
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (data->audio == _video); break;
|
||||
case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (track->state.id == _audio); break;
|
||||
case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (track->state.id == _song); break;
|
||||
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (track->state.id == _video); break;
|
||||
}
|
||||
if (!l || !data) return nullptr;
|
||||
if (!l || !track) return nullptr;
|
||||
|
||||
if (!isGoodId || !data->loading || !l->check(data->file, data->data)) {
|
||||
if (!isGoodId || !track->loading || !l->check(track->file, track->data)) {
|
||||
LOG(("Audio Error: playing changed while loading"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return data;
|
||||
return track;
|
||||
}
|
||||
|
||||
void Loaders::onCancel(const AudioMsgId &audio) {
|
||||
|
@ -425,10 +385,10 @@ void Loaders::onCancel(const AudioMsgId &audio) {
|
|||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
if (!mixer()) return;
|
||||
|
||||
for (int i = 0; i < AudioSimultaneousLimit; ++i) {
|
||||
auto data = mixer()->dataForType(audio.type(), i);
|
||||
if (data->audio == audio) {
|
||||
data->loading = false;
|
||||
for (auto i = 0; i != kTogetherLimit; ++i) {
|
||||
auto track = mixer()->trackForType(audio.type(), i);
|
||||
if (track->state.id == audio) {
|
||||
track->loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ private:
|
|||
|
||||
void emitError(AudioMsgId::Type type);
|
||||
AudioMsgId clear(AudioMsgId::Type type);
|
||||
void setStoppedState(Mixer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped);
|
||||
void setStoppedState(Mixer::Track *m, State state = State::Stopped);
|
||||
|
||||
enum SetupError {
|
||||
SetupErrorAtStart = 0,
|
||||
|
@ -78,7 +78,7 @@ private:
|
|||
};
|
||||
void loadData(AudioMsgId audio, qint64 position);
|
||||
AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 &position);
|
||||
Mixer::AudioMsg *checkLoader(AudioMsgId::Type type);
|
||||
Mixer::Track *checkLoader(AudioMsgId::Type type);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ bool ChildFFMpegLoader::open(qint64 &position) {
|
|||
int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout;
|
||||
_srcRate = _parentData->frequency;
|
||||
AVSampleFormat src_sample_fmt = _inputFormat, dst_sample_fmt = AudioToFormat;
|
||||
_dstRate = (_parentData->frequency != 44100 && _parentData->frequency != 48000) ? AudioVoiceMsgFrequency : _parentData->frequency;
|
||||
_dstRate = (_parentData->frequency != 44100 && _parentData->frequency != 48000) ? Media::Player::kDefaultFrequency : _parentData->frequency;
|
||||
|
||||
av_opt_set_int(_swrContext, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(_swrContext, "in_sample_rate", _srcRate, 0);
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "media/media_audio_loader.h"
|
||||
#include "media/media_audio.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
@ -33,7 +34,7 @@ extern "C" {
|
|||
|
||||
struct VideoSoundData {
|
||||
AVCodecContext *context = nullptr;
|
||||
int32 frequency = AudioVoiceMsgFrequency;
|
||||
int32 frequency = Media::Player::kDefaultFrequency;
|
||||
TimeMs length = 0;
|
||||
~VideoSoundData();
|
||||
};
|
||||
|
@ -120,8 +121,8 @@ private:
|
|||
|
||||
int32 _sampleSize = 2 * sizeof(uint16);
|
||||
int32 _format = AL_FORMAT_STEREO16;
|
||||
int32 _srcRate = AudioVoiceMsgFrequency;
|
||||
int32 _dstRate = AudioVoiceMsgFrequency;
|
||||
int32 _srcRate = Media::Player::kDefaultFrequency;
|
||||
int32 _dstRate = Media::Player::kDefaultFrequency;
|
||||
int32 _maxResampleSamples = 1024;
|
||||
uint8_t **_dstSamplesData = nullptr;
|
||||
|
||||
|
|
|
@ -371,7 +371,7 @@ bool FFMpegReaderImplementation::start(Mode mode, TimeMs &positionMs) {
|
|||
_audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0);
|
||||
if (_mode == Mode::OnlyGifv) {
|
||||
if (_audioStreamId >= 0) { // should be no audio stream
|
||||
_audioStreamId = -1; // do not attempt to access mixer()
|
||||
_audioStreamId = -1;
|
||||
return false;
|
||||
}
|
||||
if (dataSize() > AnimationInMemory) {
|
||||
|
@ -380,7 +380,7 @@ bool FFMpegReaderImplementation::start(Mode mode, TimeMs &positionMs) {
|
|||
if (_codecContext->codec_id != AV_CODEC_ID_H264) {
|
||||
return false;
|
||||
}
|
||||
} else if (_mode == Mode::Silent || !Player::mixer() || !_playId) {
|
||||
} else if (_mode == Mode::Silent || !_playId) {
|
||||
_audioStreamId = -1;
|
||||
}
|
||||
|
||||
|
@ -436,7 +436,7 @@ bool FFMpegReaderImplementation::start(Mode mode, TimeMs &positionMs) {
|
|||
}
|
||||
|
||||
if (_audioStreamId >= 0) {
|
||||
int64 position = (positionMs * soundData->frequency) / 1000LL;
|
||||
auto position = (positionMs * soundData->frequency) / 1000LL;
|
||||
Player::mixer()->initFromVideo(_playId, std_::move(soundData), position);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,13 +35,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
namespace Media {
|
||||
namespace Player {
|
||||
|
||||
using State = PlayButtonLayout::State;
|
||||
using ButtonState = PlayButtonLayout::State;
|
||||
|
||||
class CoverWidget::PlayButton : public Ui::AbstractButton {
|
||||
public:
|
||||
PlayButton(QWidget *parent);
|
||||
|
||||
void setState(PlayButtonLayout::State state) {
|
||||
void setState(ButtonState state) {
|
||||
_layout.setState(state);
|
||||
}
|
||||
void finishTransform() {
|
||||
|
@ -114,17 +114,15 @@ CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent)
|
|||
subscribe(instance()->playlistChangedNotifier(), [this] {
|
||||
handlePlaylistUpdate();
|
||||
});
|
||||
subscribe(instance()->updatedNotifier(), [this](const UpdatedEvent &e) {
|
||||
handleSongUpdate(e);
|
||||
subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
|
||||
handleSongUpdate(state);
|
||||
});
|
||||
subscribe(instance()->songChangedNotifier(), [this] {
|
||||
handleSongChange();
|
||||
});
|
||||
handleSongChange();
|
||||
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
handleSongUpdate(UpdatedEvent(&playing, &playbackState));
|
||||
handleSongUpdate(mixer()->currentState(AudioMsgId::Type::Song));
|
||||
_playPause->finishTransform();
|
||||
}
|
||||
|
||||
|
@ -153,10 +151,10 @@ void CoverWidget::handleSeekFinished(float64 progress) {
|
|||
auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs);
|
||||
_seekPositionMs = -1;
|
||||
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing && playbackState.duration) {
|
||||
Media::Player::mixer()->seek(qRound(progress * playbackState.duration));
|
||||
auto type = AudioMsgId::Type::Song;
|
||||
auto state = Media::Player::mixer()->currentState(type);
|
||||
if (state.id && state.duration) {
|
||||
Media::Player::mixer()->seek(type, qRound(progress * state.duration));
|
||||
}
|
||||
|
||||
instance()->stopSeeking();
|
||||
|
@ -228,52 +226,50 @@ void CoverWidget::updateRepeatTrackIcon() {
|
|||
_repeatTrack->setIconOverride(instance()->repeatEnabled() ? nullptr : &st::mediaPlayerRepeatInactiveIcon);
|
||||
}
|
||||
|
||||
void CoverWidget::handleSongUpdate(const UpdatedEvent &e) {
|
||||
auto &audioId = *e.audioId;
|
||||
auto &playbackState = *e.playbackState;
|
||||
if (!audioId || !audioId.audio()->song()) {
|
||||
void CoverWidget::handleSongUpdate(const TrackState &state) {
|
||||
if (!state.id || !state.id.audio()->song()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (audioId.audio()->loading()) {
|
||||
_playback->updateLoadingState(audioId.audio()->progress());
|
||||
if (state.id.audio()->loading()) {
|
||||
_playback->updateLoadingState(state.id.audio()->progress());
|
||||
} else {
|
||||
_playback->updateState(*e.playbackState);
|
||||
_playback->updateState(state);
|
||||
}
|
||||
|
||||
auto stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing);
|
||||
auto showPause = !stopped && (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
|
||||
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
if (instance()->isSeeking()) {
|
||||
showPause = true;
|
||||
}
|
||||
auto state = [audio = audioId.audio(), showPause] {
|
||||
auto buttonState = [audio = state.id.audio(), showPause] {
|
||||
if (audio->loading()) {
|
||||
return State::Cancel;
|
||||
return ButtonState::Cancel;
|
||||
} else if (showPause) {
|
||||
return State::Pause;
|
||||
return ButtonState::Pause;
|
||||
}
|
||||
return State::Play;
|
||||
return ButtonState::Play;
|
||||
};
|
||||
_playPause->setState(state());
|
||||
_playPause->setState(buttonState());
|
||||
|
||||
updateTimeText(audioId, playbackState);
|
||||
updateTimeText(state);
|
||||
}
|
||||
|
||||
void CoverWidget::updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState) {
|
||||
void CoverWidget::updateTimeText(const TrackState &state) {
|
||||
QString time;
|
||||
qint64 position = 0, duration = 0, display = 0;
|
||||
auto frequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
display = position = playbackState.position;
|
||||
duration = playbackState.duration;
|
||||
auto frequency = state.frequency;
|
||||
if (!IsStopped(state.state) && state.state != State::Finishing) {
|
||||
display = position = state.position;
|
||||
duration = state.duration;
|
||||
} else {
|
||||
display = playbackState.duration ? playbackState.duration : (audioId.audio()->song()->duration * frequency);
|
||||
display = state.duration ? state.duration : (state.id.audio()->song()->duration * frequency);
|
||||
}
|
||||
|
||||
_lastDurationMs = (playbackState.duration * 1000LL) / frequency;
|
||||
_lastDurationMs = (state.duration * 1000LL) / frequency;
|
||||
|
||||
if (audioId.audio()->loading()) {
|
||||
_time = QString::number(qRound(audioId.audio()->progress() * 100)) + '%';
|
||||
if (state.id.audio()->loading()) {
|
||||
_time = QString::number(qRound(state.id.audio()->progress() * 100)) + '%';
|
||||
_playback->setDisabled(true);
|
||||
} else {
|
||||
display = display / frequency;
|
||||
|
|
|
@ -21,7 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
class AudioMsgId;
|
||||
struct AudioPlaybackState;
|
||||
|
||||
namespace Ui {
|
||||
class FlatLabel;
|
||||
|
@ -37,7 +36,7 @@ class Playback;
|
|||
namespace Player {
|
||||
|
||||
class VolumeController;
|
||||
struct UpdatedEvent;
|
||||
struct TrackState;
|
||||
|
||||
class CoverWidget : public TWidget, private base::Subscriber {
|
||||
public:
|
||||
|
@ -66,11 +65,11 @@ private:
|
|||
|
||||
void updateVolumeToggleIcon();
|
||||
|
||||
void handleSongUpdate(const UpdatedEvent &e);
|
||||
void handleSongUpdate(const TrackState &state);
|
||||
void handleSongChange();
|
||||
void handlePlaylistUpdate();
|
||||
|
||||
void updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState);
|
||||
void updateTimeText(const TrackState &state);
|
||||
void updateTimeLabel();
|
||||
|
||||
TimeMs _seekPositionMs = -1;
|
||||
|
|
|
@ -150,15 +150,12 @@ Instance *instance() {
|
|||
}
|
||||
|
||||
void Instance::play() {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing) {
|
||||
if (playbackState.state & AudioPlayerStoppedMask) {
|
||||
mixer()->play(playing);
|
||||
} else {
|
||||
if (playbackState.state == AudioPlayerPausing || playbackState.state == AudioPlayerPaused || playbackState.state == AudioPlayerPausedAtEnd) {
|
||||
mixer()->pauseresume(AudioMsgId::Type::Song);
|
||||
}
|
||||
auto state = mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id) {
|
||||
if (IsStopped(state.state)) {
|
||||
mixer()->play(state.id);
|
||||
} else if (IsPaused(state.state) || state.state == State::Pausing) {
|
||||
mixer()->pauseresume(AudioMsgId::Type::Song);
|
||||
}
|
||||
} else if (_current) {
|
||||
mixer()->play(_current);
|
||||
|
@ -177,11 +174,10 @@ void Instance::play(const AudioMsgId &audioId) {
|
|||
}
|
||||
|
||||
void Instance::pause() {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing) {
|
||||
if (!(playbackState.state & AudioPlayerStoppedMask)) {
|
||||
if (playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerFinishing) {
|
||||
auto state = mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id) {
|
||||
if (!IsStopped(state.state)) {
|
||||
if (state.state == State::Starting || state.state == State::Resuming || state.state == State::Playing || state.state == State::Finishing) {
|
||||
mixer()->pauseresume(AudioMsgId::Type::Song);
|
||||
}
|
||||
}
|
||||
|
@ -193,11 +189,10 @@ void Instance::stop() {
|
|||
}
|
||||
|
||||
void Instance::playPause() {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing) {
|
||||
if (playbackState.state & AudioPlayerStoppedMask) {
|
||||
mixer()->play(playing);
|
||||
auto state = mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id) {
|
||||
if (IsStopped(state.state)) {
|
||||
mixer()->play(state.id);
|
||||
} else {
|
||||
mixer()->pauseresume(AudioMsgId::Type::Song);
|
||||
}
|
||||
|
@ -219,11 +214,10 @@ void Instance::playPauseCancelClicked() {
|
|||
return;
|
||||
}
|
||||
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
auto stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing);
|
||||
auto showPause = !stopped && (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
auto audio = playing.audio();
|
||||
auto state = mixer()->currentState(AudioMsgId::Type::Song);
|
||||
auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
|
||||
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
auto audio = state.id.audio();
|
||||
if (audio && audio->loading()) {
|
||||
audio->cancel();
|
||||
} else if (showPause) {
|
||||
|
@ -252,23 +246,22 @@ void Instance::documentLoadProgress(DocumentData *document) {
|
|||
|
||||
template <typename CheckCallback>
|
||||
void Instance::emitUpdate(CheckCallback check) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (!playing || !check(playing)) {
|
||||
auto state = mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (!state.id || !check(state.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrent(playing);
|
||||
_updatedNotifier.notify(UpdatedEvent(&playing, &playbackState), true);
|
||||
setCurrent(state.id);
|
||||
_updatedNotifier.notify(state, true);
|
||||
|
||||
if (_isPlaying && playbackState.state == AudioPlayerStoppedAtEnd) {
|
||||
if (_isPlaying && state.state == State::StoppedAtEnd) {
|
||||
if (_repeatEnabled) {
|
||||
mixer()->play(_current);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
auto isPlaying = !(playbackState.state & AudioPlayerStoppedMask);
|
||||
auto isPlaying = !IsStopped(state.state);
|
||||
if (_isPlaying != isPlaying) {
|
||||
_isPlaying = isPlaying;
|
||||
if (_isPlaying) {
|
||||
|
|
|
@ -23,7 +23,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
namespace Notify {
|
||||
struct PeerUpdate;
|
||||
} // namespace Notify
|
||||
struct AudioPlaybackState;
|
||||
class AudioMsgId;
|
||||
|
||||
namespace Media {
|
||||
|
@ -35,12 +34,7 @@ void finish();
|
|||
class Instance;
|
||||
Instance *instance();
|
||||
|
||||
struct UpdatedEvent {
|
||||
UpdatedEvent(const AudioMsgId *audioId, const AudioPlaybackState *playbackState) : audioId(audioId), playbackState(playbackState) {
|
||||
}
|
||||
const AudioMsgId *audioId;
|
||||
const AudioPlaybackState *playbackState;
|
||||
};
|
||||
struct TrackState;
|
||||
|
||||
class Instance : private base::Subscriber {
|
||||
public:
|
||||
|
@ -85,7 +79,7 @@ public:
|
|||
base::Observable<bool> &playerWidgetOver() {
|
||||
return _playerWidgetOver;
|
||||
}
|
||||
base::Observable<UpdatedEvent> &updatedNotifier() {
|
||||
base::Observable<TrackState> &updatedNotifier() {
|
||||
return _updatedNotifier;
|
||||
}
|
||||
base::Observable<void> &playlistChangedNotifier() {
|
||||
|
@ -131,7 +125,7 @@ private:
|
|||
base::Observable<bool> _usePanelPlayer;
|
||||
base::Observable<bool> _titleButtonOver;
|
||||
base::Observable<bool> _playerWidgetOver;
|
||||
base::Observable<UpdatedEvent> _updatedNotifier;
|
||||
base::Observable<TrackState> _updatedNotifier;
|
||||
base::Observable<void> _playlistChangedNotifier;
|
||||
base::Observable<void> _songChangedNotifier;
|
||||
base::Observable<void> _repeatChangedNotifier;
|
||||
|
|
|
@ -37,7 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
namespace Media {
|
||||
namespace Player {
|
||||
|
||||
using State = PlayButtonLayout::State;
|
||||
using ButtonState = PlayButtonLayout::State;
|
||||
|
||||
class Widget::PlayButton : public Ui::RippleButton {
|
||||
public:
|
||||
|
@ -128,17 +128,15 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
|
|||
subscribe(instance()->playlistChangedNotifier(), [this] {
|
||||
handlePlaylistUpdate();
|
||||
});
|
||||
subscribe(instance()->updatedNotifier(), [this](const UpdatedEvent &e) {
|
||||
handleSongUpdate(e);
|
||||
subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
|
||||
handleSongUpdate(state);
|
||||
});
|
||||
subscribe(instance()->songChangedNotifier(), [this] {
|
||||
handleSongChange();
|
||||
});
|
||||
handleSongChange();
|
||||
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
handleSongUpdate(UpdatedEvent(&playing, &playbackState));
|
||||
handleSongUpdate(mixer()->currentState(AudioMsgId::Type::Song));
|
||||
_playPause->finishTransform();
|
||||
}
|
||||
|
||||
|
@ -207,10 +205,10 @@ void Widget::handleSeekFinished(float64 progress) {
|
|||
auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs);
|
||||
_seekPositionMs = -1;
|
||||
|
||||
AudioMsgId playing;
|
||||
auto playbackState = mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing && playbackState.duration) {
|
||||
mixer()->seek(qRound(progress * playbackState.duration));
|
||||
auto type = AudioMsgId::Type::Song;
|
||||
auto state = mixer()->currentState(type);
|
||||
if (state.id && state.duration) {
|
||||
mixer()->seek(type, qRound(progress * state.duration));
|
||||
}
|
||||
|
||||
instance()->stopSeeking();
|
||||
|
@ -301,52 +299,50 @@ void Widget::updateRepeatTrackIcon() {
|
|||
_repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
|
||||
}
|
||||
|
||||
void Widget::handleSongUpdate(const UpdatedEvent &e) {
|
||||
auto &audioId = *e.audioId;
|
||||
auto &playbackState = *e.playbackState;
|
||||
if (!audioId || !audioId.audio()->song()) {
|
||||
void Widget::handleSongUpdate(const TrackState &state) {
|
||||
if (!state.id || !state.id.audio()->song()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (audioId.audio()->loading()) {
|
||||
_playback->updateLoadingState(audioId.audio()->progress());
|
||||
if (state.id.audio()->loading()) {
|
||||
_playback->updateLoadingState(state.id.audio()->progress());
|
||||
} else {
|
||||
_playback->updateState(*e.playbackState);
|
||||
_playback->updateState(state);
|
||||
}
|
||||
|
||||
auto stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing);
|
||||
auto showPause = !stopped && (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
|
||||
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
if (instance()->isSeeking()) {
|
||||
showPause = true;
|
||||
}
|
||||
auto state = [audio = audioId.audio(), showPause] {
|
||||
auto buttonState = [audio = state.id.audio(), showPause] {
|
||||
if (audio->loading()) {
|
||||
return State::Cancel;
|
||||
return ButtonState::Cancel;
|
||||
} else if (showPause) {
|
||||
return State::Pause;
|
||||
return ButtonState::Pause;
|
||||
}
|
||||
return State::Play;
|
||||
return ButtonState::Play;
|
||||
};
|
||||
_playPause->setState(state());
|
||||
_playPause->setState(buttonState());
|
||||
|
||||
updateTimeText(audioId, playbackState);
|
||||
updateTimeText(state);
|
||||
}
|
||||
|
||||
void Widget::updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState) {
|
||||
void Widget::updateTimeText(const TrackState &state) {
|
||||
QString time;
|
||||
qint64 position = 0, duration = 0, display = 0;
|
||||
auto frequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
display = position = playbackState.position;
|
||||
duration = playbackState.duration;
|
||||
auto frequency = state.frequency;
|
||||
if (!IsStopped(state.state) && state.state != State::Finishing) {
|
||||
display = position = state.position;
|
||||
duration = state.duration;
|
||||
} else {
|
||||
display = playbackState.duration ? playbackState.duration : (audioId.audio()->song()->duration * frequency);
|
||||
display = state.duration ? state.duration : (state.id.audio()->song()->duration * frequency);
|
||||
}
|
||||
|
||||
_lastDurationMs = (playbackState.duration * 1000LL) / frequency;
|
||||
_lastDurationMs = (state.duration * 1000LL) / frequency;
|
||||
|
||||
if (audioId.audio()->loading()) {
|
||||
_time = QString::number(qRound(audioId.audio()->progress() * 100)) + '%';
|
||||
if (state.id.audio()->loading()) {
|
||||
_time = QString::number(qRound(state.id.audio()->progress() * 100)) + '%';
|
||||
_playback->setDisabled(true);
|
||||
} else {
|
||||
display = display / frequency;
|
||||
|
|
|
@ -21,7 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
class AudioMsgId;
|
||||
struct AudioPlaybackState;
|
||||
|
||||
namespace Ui {
|
||||
class FlatLabel;
|
||||
|
@ -39,7 +38,7 @@ namespace Player {
|
|||
|
||||
class PlayButton;
|
||||
class VolumeWidget;
|
||||
struct UpdatedEvent;
|
||||
struct TrackState;
|
||||
|
||||
class Widget : public TWidget, private base::Subscriber {
|
||||
public:
|
||||
|
@ -81,11 +80,11 @@ private:
|
|||
|
||||
void updateVolumeToggleIcon();
|
||||
|
||||
void handleSongUpdate(const UpdatedEvent &e);
|
||||
void handleSongUpdate(const TrackState &state);
|
||||
void handleSongChange();
|
||||
void handlePlaylistUpdate();
|
||||
|
||||
void updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState);
|
||||
void updateTimeText(const TrackState &state);
|
||||
void updateTimeLabel();
|
||||
|
||||
TimeMs _seekPositionMs = -1;
|
||||
|
|
|
@ -105,14 +105,14 @@ void Controller::fadeUpdated(float64 opacity) {
|
|||
_playback->setFadeOpacity(opacity);
|
||||
}
|
||||
|
||||
void Controller::updatePlayback(const AudioPlaybackState &playbackState) {
|
||||
updatePlayPauseResumeState(playbackState);
|
||||
_playback->updateState(playbackState);
|
||||
updateTimeTexts(playbackState);
|
||||
void Controller::updatePlayback(const Player::TrackState &state) {
|
||||
updatePlayPauseResumeState(state);
|
||||
_playback->updateState(state);
|
||||
updateTimeTexts(state);
|
||||
}
|
||||
|
||||
void Controller::updatePlayPauseResumeState(const AudioPlaybackState &playbackState) {
|
||||
bool showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || _seekPositionMs >= 0);
|
||||
void Controller::updatePlayPauseResumeState(const Player::TrackState &state) {
|
||||
auto showPause = (state.state == Player::State::Playing || state.state == Player::State::Resuming || _seekPositionMs >= 0);
|
||||
if (showPause != _showPause) {
|
||||
disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed()));
|
||||
_showPause = showPause;
|
||||
|
@ -122,21 +122,21 @@ void Controller::updatePlayPauseResumeState(const AudioPlaybackState &playbackSt
|
|||
}
|
||||
}
|
||||
|
||||
void Controller::updateTimeTexts(const AudioPlaybackState &playbackState) {
|
||||
qint64 position = 0, duration = playbackState.duration;
|
||||
void Controller::updateTimeTexts(const Player::TrackState &state) {
|
||||
qint64 position = 0, duration = state.duration;
|
||||
|
||||
if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
position = playbackState.position;
|
||||
} else if (playbackState.state == AudioPlayerStoppedAtEnd) {
|
||||
position = playbackState.duration;
|
||||
if (!Player::IsStopped(state.state) && state.state != Player::State::Finishing) {
|
||||
position = state.position;
|
||||
} else if (state.state == Player::State::StoppedAtEnd) {
|
||||
position = state.duration;
|
||||
} else {
|
||||
position = 0;
|
||||
}
|
||||
auto playFrequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
auto playFrequency = state.frequency;
|
||||
auto playAlready = position / playFrequency;
|
||||
auto playLeft = (playbackState.duration / playFrequency) - playAlready;
|
||||
auto playLeft = (state.duration / playFrequency) - playAlready;
|
||||
|
||||
_lastDurationMs = (playbackState.duration * 1000LL) / playFrequency;
|
||||
_lastDurationMs = (state.duration * 1000LL) / playFrequency;
|
||||
|
||||
_timeAlready = formatDurationText(playAlready);
|
||||
auto minus = QChar(8722);
|
||||
|
|
|
@ -26,9 +26,11 @@ class FadeAnimation;
|
|||
class IconButton;
|
||||
} // namespace Ui
|
||||
|
||||
struct AudioPlaybackState;
|
||||
|
||||
namespace Media {
|
||||
namespace Player {
|
||||
struct TrackState;
|
||||
} // namespace Player
|
||||
|
||||
namespace Clip {
|
||||
|
||||
class Playback;
|
||||
|
@ -43,7 +45,7 @@ public:
|
|||
void showAnimated();
|
||||
void hideAnimated();
|
||||
|
||||
void updatePlayback(const AudioPlaybackState &playbackState);
|
||||
void updatePlayback(const Player::TrackState &state);
|
||||
void setInFullScreen(bool inFullScreen);
|
||||
|
||||
void grabStart() override;
|
||||
|
@ -74,8 +76,8 @@ private:
|
|||
void fadeFinished();
|
||||
void fadeUpdated(float64 opacity);
|
||||
|
||||
void updatePlayPauseResumeState(const AudioPlaybackState &playbackState);
|
||||
void updateTimeTexts(const AudioPlaybackState &playbackState);
|
||||
void updatePlayPauseResumeState(const Player::TrackState &state);
|
||||
void updateTimeTexts(const Player::TrackState &state);
|
||||
void refreshTimeTexts();
|
||||
|
||||
bool _showPause = false;
|
||||
|
|
|
@ -30,17 +30,17 @@ namespace Clip {
|
|||
Playback::Playback(Ui::ContinuousSlider *slider) : _slider(slider) {
|
||||
}
|
||||
|
||||
void Playback::updateState(const AudioPlaybackState &playbackState) {
|
||||
qint64 position = 0, duration = playbackState.duration;
|
||||
void Playback::updateState(const Player::TrackState &state) {
|
||||
qint64 position = 0, duration = state.duration;
|
||||
|
||||
auto wasDisabled = _slider->isDisabled();
|
||||
if (wasDisabled) setDisabled(false);
|
||||
|
||||
_playing = !(playbackState.state & AudioPlayerStoppedMask);
|
||||
if (_playing || playbackState.state == AudioPlayerStopped) {
|
||||
position = playbackState.position;
|
||||
} else if (playbackState.state == AudioPlayerStoppedAtEnd) {
|
||||
position = playbackState.duration;
|
||||
_playing = !Player::IsStopped(state.state);
|
||||
if (_playing || state.state == Player::State::Stopped) {
|
||||
position = state.position;
|
||||
} else if (state.state == Player::State::StoppedAtEnd) {
|
||||
position = state.duration;
|
||||
} else {
|
||||
position = 0;
|
||||
}
|
||||
|
|
|
@ -22,16 +22,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
|
||||
struct AudioPlaybackState;
|
||||
|
||||
namespace Media {
|
||||
namespace Player {
|
||||
struct TrackState;
|
||||
} // namespace Player
|
||||
|
||||
namespace Clip {
|
||||
|
||||
class Playback {
|
||||
public:
|
||||
Playback(Ui::ContinuousSlider *slider);
|
||||
|
||||
void updateState(const AudioPlaybackState &playbackState);
|
||||
void updateState(const Player::TrackState &state);
|
||||
void updateLoadingState(float64 progress);
|
||||
|
||||
void setFadeOpacity(float64 opacity) {
|
||||
|
|
|
@ -1536,8 +1536,8 @@ void MediaView::restartVideoAtSeekPosition(TimeMs positionMs) {
|
|||
_videoPaused = _videoIsSilent = _videoStopped = false;
|
||||
_videoPositionMs = positionMs;
|
||||
|
||||
AudioPlaybackState state;
|
||||
state.state = AudioPlayerPlaying;
|
||||
Media::Player::TrackState state;
|
||||
state.state = Media::Player::State::Playing;
|
||||
state.position = _videoPositionMs;
|
||||
state.duration = _videoDurationMs;
|
||||
state.frequency = _videoFrequencyMs;
|
||||
|
@ -1587,9 +1587,9 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) {
|
|||
}
|
||||
}
|
||||
|
||||
void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state) {
|
||||
void MediaView::updateVideoPlaybackState(const Media::Player::TrackState &state) {
|
||||
if (state.frequency) {
|
||||
if (state.state & AudioPlayerStoppedMask) {
|
||||
if (Media::Player::IsStopped(state.state)) {
|
||||
_videoStopped = true;
|
||||
}
|
||||
_clipController->updatePlayback(state);
|
||||
|
@ -1600,13 +1600,13 @@ void MediaView::updateVideoPlaybackState(const AudioPlaybackState &state) {
|
|||
}
|
||||
|
||||
void MediaView::updateSilentVideoPlaybackState() {
|
||||
AudioPlaybackState state;
|
||||
Media::Player::TrackState state;
|
||||
if (_videoPaused) {
|
||||
state.state = AudioPlayerPaused;
|
||||
state.state = Media::Player::State::Paused;
|
||||
} else if (_videoPositionMs == _videoDurationMs) {
|
||||
state.state = AudioPlayerStoppedAtEnd;
|
||||
state.state = Media::Player::State::StoppedAtEnd;
|
||||
} else {
|
||||
state.state = AudioPlayerPlaying;
|
||||
state.state = Media::Player::State::Playing;
|
||||
}
|
||||
state.position = _videoPositionMs;
|
||||
state.duration = _videoDurationMs;
|
||||
|
|
|
@ -24,6 +24,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "ui/effects/radial_animation.h"
|
||||
|
||||
namespace Media {
|
||||
namespace Player {
|
||||
struct TrackState;
|
||||
} // namespace Player
|
||||
namespace Clip {
|
||||
class Controller;
|
||||
} // namespace Clip
|
||||
|
@ -45,8 +48,6 @@ namespace Notify {
|
|||
struct PeerUpdate;
|
||||
} // namespace Notify
|
||||
|
||||
struct AudioPlaybackState;
|
||||
|
||||
class MediaView : public TWidget, private base::Subscriber, public RPCSender, public ClickHandlerHost {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -170,7 +171,7 @@ private:
|
|||
void updateCursor();
|
||||
void setZoomLevel(int newZoom);
|
||||
|
||||
void updateVideoPlaybackState(const AudioPlaybackState &state);
|
||||
void updateVideoPlaybackState(const Media::Player::TrackState &state);
|
||||
void updateSilentVideoPlaybackState();
|
||||
void restartVideoAtSeekPosition(TimeMs positionMs);
|
||||
|
||||
|
|
|
@ -649,12 +649,12 @@ bool Voice::updateStatusText() {
|
|||
statusSize = FileStatusSizeFailed;
|
||||
} else if (_data->loaded()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Voice);
|
||||
if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
using State = Media::Player::State;
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
} else {
|
||||
statusSize = FileStatusSizeReady;
|
||||
|
@ -933,14 +933,14 @@ bool Document::updateStatusText() {
|
|||
} else if (_data->loaded()) {
|
||||
if (_data->song()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
using State = Media::Player::State;
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && Media::Player::instance()->isSeeking()) {
|
||||
if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId())) && Media::Player::instance()->isSeeking()) {
|
||||
showPause = true;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -2092,10 +2092,9 @@ int32 OverviewWidget::lastScrollTop() const {
|
|||
|
||||
int32 OverviewWidget::countBestScroll() const {
|
||||
if (type() == OverviewMusicFiles) {
|
||||
AudioMsgId playing;
|
||||
Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing) {
|
||||
int32 top = _inner->itemTop(playing.contextId());
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id) {
|
||||
int32 top = _inner->itemTop(state.id.contextId());
|
||||
if (top >= 0) {
|
||||
return snap(top - int(_scroll->height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll->scrollTopMax());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX
|
||||
namespace Platform {
|
||||
namespace Audio {
|
||||
|
||||
inline void Init() {
|
||||
}
|
||||
|
||||
inline void DeInit() {
|
||||
}
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace Platform
|
||||
#elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
|
||||
#include "platform/win/audio_win.h"
|
||||
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "platform/win/audio_win.h"
|
||||
|
||||
#include "media/media_audio.h"
|
||||
|
||||
#include <mmdeviceapi.h>
|
||||
#include <audioclient.h>
|
||||
|
||||
#include <wrl\client.h>
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
namespace Platform {
|
||||
namespace Audio {
|
||||
namespace {
|
||||
|
||||
// Inspired by Chromium.
|
||||
class DeviceListener : public IMMNotificationClient {
|
||||
public:
|
||||
DeviceListener() = default;
|
||||
DeviceListener(const DeviceListener &other) = delete;
|
||||
DeviceListener &operator=(const DeviceListener &other) = delete;
|
||||
virtual ~DeviceListener() = default;
|
||||
|
||||
private:
|
||||
// IMMNotificationClient implementation.
|
||||
STDMETHOD_(ULONG, AddRef)() override {
|
||||
return 1;
|
||||
}
|
||||
STDMETHOD_(ULONG, Release)() override {
|
||||
return 1;
|
||||
}
|
||||
STDMETHOD(QueryInterface)(REFIID iid, void** object) override;
|
||||
STDMETHOD(OnPropertyValueChanged)(LPCWSTR device_id, const PROPERTYKEY key) override;
|
||||
STDMETHOD(OnDeviceAdded)(LPCWSTR device_id) override {
|
||||
return S_OK;
|
||||
}
|
||||
STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id) override {
|
||||
return S_OK;
|
||||
}
|
||||
STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD new_state) override;
|
||||
STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override;
|
||||
|
||||
};
|
||||
|
||||
STDMETHODIMP DeviceListener::QueryInterface(REFIID iid, void** object) {
|
||||
if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) {
|
||||
*object = static_cast<IMMNotificationClient*>(this);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
*object = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
STDMETHODIMP DeviceListener::OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) {
|
||||
LOG(("Audio Info: OnPropertyValueChanged() scheduling detach from audio device."));
|
||||
Media::Player::DetachFromDeviceByTimer();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP DeviceListener::OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) {
|
||||
LOG(("Audio Info: OnDeviceStateChanged() scheduling detach from audio device."));
|
||||
Media::Player::DetachFromDeviceByTimer();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP DeviceListener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) {
|
||||
// Only listen for console and communication device changes.
|
||||
if ((role != eConsole && role != eCommunications) || (flow != eRender && flow != eCapture)) {
|
||||
LOG(("Audio Info: skipping OnDefaultDeviceChanged() flow %1, role %2, new_default_device_id: %3").arg(flow).arg(role).arg(new_default_device_id ? '"' + QString::fromWCharArray(new_default_device_id) + '"' : QString("nullptr")));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
LOG(("Audio Info: OnDefaultDeviceChanged() scheduling detach from audio device, flow %1, role %2, new_default_device_id: %3").arg(flow).arg(role).arg(new_default_device_id ? '"' + QString::fromWCharArray(new_default_device_id) + '"' : QString("nullptr")));
|
||||
Media::Player::DetachFromDeviceByTimer();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
auto WasCoInitialized = false;
|
||||
ComPtr<IMMDeviceEnumerator> Enumerator;
|
||||
|
||||
DeviceListener *Listener = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
void Init() {
|
||||
auto hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&Enumerator));
|
||||
if (FAILED(hr)) {
|
||||
Enumerator.Reset();
|
||||
|
||||
if (hr == CO_E_NOTINITIALIZED) {
|
||||
LOG(("Audio Info: CoCreateInstance fails with CO_E_NOTINITIALIZED"));
|
||||
hr = CoInitialize(nullptr);
|
||||
if (SUCCEEDED(hr)) {
|
||||
WasCoInitialized = true;
|
||||
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&Enumerator));
|
||||
if (FAILED(hr)) {
|
||||
Enumerator.Reset();
|
||||
|
||||
LOG(("Audio Error: could not CoCreateInstance of MMDeviceEnumerator, HRESULT: %1").arg(hr));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG(("Audio Error: could not CoCreateInstance of MMDeviceEnumerator, HRESULT: %1").arg(hr));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Listener = new DeviceListener();
|
||||
hr = Enumerator->RegisterEndpointNotificationCallback(Listener);
|
||||
if (FAILED(hr)) {
|
||||
LOG(("Audio Error: RegisterEndpointNotificationCallback failed, HRESULT: %1").arg(hr));
|
||||
delete base::take(Listener);
|
||||
}
|
||||
}
|
||||
|
||||
void DeInit() {
|
||||
if (Enumerator) {
|
||||
if (Listener) {
|
||||
auto hr = Enumerator->UnregisterEndpointNotificationCallback(Listener);
|
||||
if (FAILED(hr)) {
|
||||
LOG(("Audio Error: UnregisterEndpointNotificationCallback failed, HRESULT: %1").arg(hr));
|
||||
}
|
||||
delete base::take(Listener);
|
||||
}
|
||||
Enumerator.Reset();
|
||||
}
|
||||
if (WasCoInitialized) {
|
||||
CoUninitialize();
|
||||
}
|
||||
AUDCLNT_E_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace Platform
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Platform {
|
||||
namespace Audio {
|
||||
|
||||
void Init();
|
||||
void DeInit();
|
||||
|
||||
} // namespace Audio
|
||||
} // namespace Platform
|
||||
|
|
@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "application.h"
|
||||
#include "localstorage.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "platform/mac/mac_utilities.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "lang.h"
|
||||
|
@ -125,6 +126,9 @@ ApplicationDelegate *_sharedDelegate = nil;
|
|||
|
||||
- (void)receiveWakeNote:(NSNotification*)aNotification {
|
||||
if (App::app()) App::app()->checkLocalTime();
|
||||
|
||||
LOG(("Audio Info: -receiveWakeNote: received, scheduling detach from audio device"));
|
||||
Media::Player::DetachFromDeviceByTimer();
|
||||
}
|
||||
|
||||
- (void)setWatchingMediaKeys:(BOOL)watching {
|
||||
|
@ -216,27 +220,21 @@ bool objc_handleMediaKeyEvent(void *ev) {
|
|||
switch (keyCode) {
|
||||
case NX_KEYTYPE_PLAY:
|
||||
if (keyState == 0) { // Play pressed and released
|
||||
if (Media::Player::exists()) {
|
||||
Media::Player::instance()->playPause();
|
||||
}
|
||||
Media::Player::instance()->playPause();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case NX_KEYTYPE_FAST:
|
||||
if (keyState == 0) { // Next pressed and released
|
||||
if (Media::Player::exists()) {
|
||||
Media::Player::instance()->next();
|
||||
}
|
||||
Media::Player::instance()->next();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case NX_KEYTYPE_REWIND:
|
||||
if (keyState == 0) { // Previous pressed and released
|
||||
if (Media::Player::exists()) {
|
||||
Media::Player::instance()->previous();
|
||||
}
|
||||
Media::Player::instance()->previous();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1174,10 +1174,10 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
|
|||
}
|
||||
}
|
||||
if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) {
|
||||
using State = Media::Player::State;
|
||||
if (playVoice) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Voice);
|
||||
if (playing == AudioMsgId(data, msgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
Media::Player::mixer()->pauseresume(AudioMsgId::Type::Voice);
|
||||
} else {
|
||||
auto audio = AudioMsgId(data, msgId);
|
||||
|
@ -1188,9 +1188,8 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
|
|||
}
|
||||
}
|
||||
} else if (playMusic) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing == AudioMsgId(data, msgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
Media::Player::mixer()->pauseresume(AudioMsgId::Type::Song);
|
||||
} else {
|
||||
auto song = AudioMsgId(data, msgId);
|
||||
|
@ -1480,25 +1479,24 @@ void DocumentData::performActionOnLoad() {
|
|||
return;
|
||||
}
|
||||
}
|
||||
using State = Media::Player::State;
|
||||
if (playVoice) {
|
||||
if (loaded()) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Voice);
|
||||
if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
Media::Player::mixer()->pauseresume(AudioMsgId::Type::Voice);
|
||||
} else if (playbackState.state & AudioPlayerStoppedMask) {
|
||||
} else if (Media::Player::IsStopped(state.state)) {
|
||||
Media::Player::mixer()->play(AudioMsgId(this, _actionOnLoadMsgId));
|
||||
if (App::main()) App::main()->mediaMarkRead(this);
|
||||
}
|
||||
}
|
||||
} else if (playMusic) {
|
||||
if (loaded()) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = Media::Player::mixer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing == AudioMsgId(this, _actionOnLoadMsgId) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
Media::Player::mixer()->pauseresume(AudioMsgId::Type::Song);
|
||||
} else if (playbackState.state & AudioPlayerStoppedMask) {
|
||||
AudioMsgId song(this, _actionOnLoadMsgId);
|
||||
} else if (Media::Player::IsStopped(state.state)) {
|
||||
auto song = AudioMsgId(this, _actionOnLoadMsgId);
|
||||
Media::Player::mixer()->play(song);
|
||||
Media::Player::Updated().notify(song);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ windowDefaultHeight: 600px;
|
|||
windowShadow: icon {{ "window_shadow", windowShadowFg }};
|
||||
windowShadowShift: 1px;
|
||||
|
||||
adaptiveChatWideWidth: 860px;
|
||||
adaptiveChatWideWidth: 880px;
|
||||
|
||||
notifyBorder: windowShadowFgFallback;
|
||||
notifyBorderWidth: 1px;
|
||||
|
|
|
@ -3,4 +3,4 @@ AppVersionStrMajor 1.0
|
|||
AppVersionStrSmall 1.0.2
|
||||
AppVersionStr 1.0.2
|
||||
AlphaChannel 0
|
||||
BetaVersion 0
|
||||
BetaVersion 1000002001
|
||||
|
|
|
@ -380,6 +380,8 @@
|
|||
'<(src_loc)/platform/mac/notifications_manager_mac.h',
|
||||
'<(src_loc)/platform/mac/window_title_mac.mm',
|
||||
'<(src_loc)/platform/mac/window_title_mac.h',
|
||||
'<(src_loc)/platform/win/audio_win.cpp',
|
||||
'<(src_loc)/platform/win/audio_win.h',
|
||||
'<(src_loc)/platform/win/main_window_win.cpp',
|
||||
'<(src_loc)/platform/win/main_window_win.h',
|
||||
'<(src_loc)/platform/win/notifications_manager_win.cpp',
|
||||
|
@ -392,6 +394,7 @@
|
|||
'<(src_loc)/platform/win/windows_dlls.h',
|
||||
'<(src_loc)/platform/win/windows_event_filter.cpp',
|
||||
'<(src_loc)/platform/win/windows_event_filter.h',
|
||||
'<(src_loc)/platform/platform_audio.h',
|
||||
'<(src_loc)/platform/platform_file_dialog.h',
|
||||
'<(src_loc)/platform/platform_main_window.h',
|
||||
'<(src_loc)/platform/platform_notifications_manager.h',
|
||||
|
@ -644,12 +647,14 @@
|
|||
'sources!': [
|
||||
'<(src_loc)/pspecific_win.cpp',
|
||||
'<(src_loc)/pspecific_win.h',
|
||||
'<(src_loc)/platform/win/audio_win.cpp',
|
||||
'<(src_loc)/platform/win/audio_win.h',
|
||||
'<(src_loc)/platform/win/main_window_win.cpp',
|
||||
'<(src_loc)/platform/win/main_window_win.h',
|
||||
'<(src_loc)/platform/win/notifications_manager_win.cpp',
|
||||
'<(src_loc)/platform/win/notifications_manager_win.h',
|
||||
'<(src_loc)/platform/win/window_title_win.cpp',
|
||||
'<(src_loc)/platform/win/window_title_win.h',
|
||||
'<(src_loc)/platform/win/window_title_win.cpp',
|
||||
'<(src_loc)/platform/win/window_title_win.h',
|
||||
'<(src_loc)/platform/win/windows_app_user_model_id.cpp',
|
||||
'<(src_loc)/platform/win/windows_app_user_model_id.h',
|
||||
'<(src_loc)/platform/win/windows_dlls.cpp',
|
||||
|
|
Loading…
Reference in New Issue