Using media player for voice / video messages.

Media::Player::Widget displays the current voice / video message
if it is played and the current song otherwise.

It is created when a voice / video message starts and is destroyed
when all the voice / video messages in the playlist are finished.
This commit is contained in:
John Preston 2017-05-21 16:16:39 +03:00
parent 2661fe5cd5
commit 7873cb4373
24 changed files with 325 additions and 192 deletions

View File

@ -1165,6 +1165,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_call_rate_label" = "Please rate the quality of your call"; "lng_call_rate_label" = "Please rate the quality of your call";
"lng_call_rate_comment" = "Comment (optional)"; "lng_call_rate_comment" = "Comment (optional)";
"lng_player_message_today" = "Today at {time}";
"lng_player_message_yesterday" = "Yesterday at {time}";
"lng_player_message_date" = "{date} at {time}";
// Not used // Not used
"lng_topbar_info" = "Info"; "lng_topbar_info" = "Info";

View File

@ -997,7 +997,7 @@ void HistoryItem::audioTrackUpdated() {
auto audio = reader->audioMsgId(); auto audio = reader->audioMsgId();
auto current = Media::Player::mixer()->currentState(audio.type()); auto current = Media::Player::mixer()->currentState(audio.type());
if (current.id != audio || Media::Player::IsStopped(current.state) || current.state == Media::Player::State::Finishing) { if (current.id != audio || Media::Player::IsStoppedOrStopping(current.state)) {
media->stopInline(); media->stopInline();
} else if (Media::Player::IsPaused(current.state) || current.state == Media::Player::State::Pausing) { } else if (Media::Player::IsPaused(current.state) || current.state == Media::Player::State::Pausing) {
if (!reader->videoPaused()) { if (!reader->videoPaused()) {

View File

@ -1427,7 +1427,7 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin; auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
if (x >= nameleft && x < nameleft + namewidth && y >= nametop && y < waveformbottom) { if (x >= nameleft && x < nameleft + namewidth && y >= nametop && y < waveformbottom) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state)) { if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (!voice->seeking()) { if (!voice->seeking()) {
voice->setSeekingStart((x - nameleft) / float64(namewidth)); voice->setSeekingStart((x - nameleft) / float64(namewidth));
} }
@ -1585,7 +1585,7 @@ bool HistoryDocument::updateStatusText() const {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
if (_data->voice()) { if (_data->voice()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); 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 (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (auto voice = Get<HistoryDocumentVoice>()) { if (auto voice = Get<HistoryDocumentVoice>()) {
bool was = (voice->_playback != nullptr); bool was = (voice->_playback != nullptr);
voice->ensurePlayback(this); voice->ensurePlayback(this);
@ -1615,7 +1615,7 @@ bool HistoryDocument::updateStatusText() const {
} }
} else if (_data->song()) { } else if (_data->song()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); 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) { if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency); realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
@ -2337,10 +2337,10 @@ void HistoryGif::updateStatusText() const {
if (state.id == _gif->audioMsgId()) { if (state.id == _gif->audioMsgId()) {
if (state.length) { if (state.length) {
auto position = int64(0); auto position = int64(0);
if (!Media::Player::IsStopped(state.state) && state.state != Media::Player::State::Finishing) { if (Media::Player::IsStoppedAtEnd(state.state)) {
position = state.position;
} else if (state.state == Media::Player::State::StoppedAtEnd) {
position = state.length; position = state.length;
} else if (!Media::Player::IsStoppedOrStopping(state.state)) {
position = state.position;
} }
accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1)); accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1));
} }

View File

@ -836,7 +836,7 @@ bool File::updateStatusText() const {
if (document->voice()) { if (document->voice()) {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency); realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
@ -844,7 +844,7 @@ bool File::updateStatusText() const {
} else if (document->song()) { } else if (document->song()) {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency); realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);

View File

@ -186,6 +186,14 @@ MainWidget::MainWidget(QWidget *parent, gsl::not_null<Window::Controller*> contr
_playerPlaylist->hideFromOther(); _playerPlaylist->hideFromOther();
} }
}); });
subscribe(Media::Player::instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Voice) {
auto songState = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (!songState.id || IsStoppedOrStopping(songState.state)) {
closeBothPlayers();
}
}
});
subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); }); subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); });
@ -1540,14 +1548,9 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) {
} }
} }
if (state.id == audioId && audioId.type() == AudioMsgId::Type::Song) { if (state.id == audioId && (audioId.type() == AudioMsgId::Type::Song || audioId.type() == AudioMsgId::Type::Voice)) {
if (!Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (!Media::Player::IsStoppedOrStopping(state.state)) {
if (!_playerUsingPanel && !_player) { createPlayer();
createPlayer();
}
} else if (_player && _player->isHidden() && !_playerUsingPanel) {
_player.destroyDelayed();
_playerVolume.destroyDelayed();
} }
} }
@ -1604,30 +1607,38 @@ void MainWidget::closeBothPlayers() {
Media::Player::instance()->usePanelPlayer().notify(false, true); Media::Player::instance()->usePanelPlayer().notify(false, true);
_playerPanel->hideIgnoringEnterEvents(); _playerPanel->hideIgnoringEnterEvents();
_playerPlaylist->hideIgnoringEnterEvents(); _playerPlaylist->hideIgnoringEnterEvents();
Media::Player::instance()->stop(AudioMsgId::Type::Voice);
Media::Player::instance()->stop(AudioMsgId::Type::Song); Media::Player::instance()->stop(AudioMsgId::Type::Song);
Shortcuts::disableMediaShortcuts(); Shortcuts::disableMediaShortcuts();
} }
void MainWidget::createPlayer() { void MainWidget::createPlayer() {
_player.create(this, [this] { playerHeightUpdated(); }); if (_playerUsingPanel) {
_player->entity()->setCloseCallback([this] { closeBothPlayers(); }); return;
_playerVolume.create(this); }
_player->entity()->volumeWidgetCreated(_playerVolume); if (!_player) {
orderWidgets(); _player.create(this, [this] { playerHeightUpdated(); });
if (_a_show.animating()) { _player->entity()->setCloseCallback([this] { closeBothPlayers(); });
_player->showFast(); _playerVolume.create(this);
_player->hide(); _player->entity()->volumeWidgetCreated(_playerVolume);
} else { orderWidgets();
_player->hideFast(); if (_a_show.animating()) {
if (_player) { _player->showFast();
_player->hide();
Shortcuts::enableMediaShortcuts();
} else {
_player->hideFast();
}
}
if (_player && _player->isHiddenOrHiding()) {
if (!_a_show.animating()) {
_player->showAnimated(); _player->showAnimated();
_playerHeight = _contentScrollAddToY = _player->contentHeight(); _playerHeight = _contentScrollAddToY = _player->contentHeight();
updateControlsGeometry(); updateControlsGeometry();
Shortcuts::enableMediaShortcuts();
} }
} }
Shortcuts::enableMediaShortcuts();
} }
void MainWidget::playerHeightUpdated() { void MainWidget::playerHeightUpdated() {
@ -1638,8 +1649,8 @@ void MainWidget::playerHeightUpdated() {
updateControlsGeometry(); updateControlsGeometry();
} }
if (!_playerHeight && _player->isHidden()) { if (!_playerHeight && _player->isHidden()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); auto state = Media::Player::mixer()->currentState(Media::Player::instance()->getActiveType());
if (state.id && Media::Player::IsStopped(state.state)) { if (!state.id || Media::Player::IsStoppedOrStopping(state.state)) {
_playerVolume.destroyDelayed(); _playerVolume.destroyDelayed();
_player.destroyDelayed(); _player.destroyDelayed();
} }

View File

@ -596,12 +596,12 @@ bool Mixer::fadedStop(AudioMsgId::Type type, bool *fadedStart) {
case State::Starting: case State::Starting:
case State::Resuming: case State::Resuming:
case State::Playing: { case State::Playing: {
current->state.state = State::Finishing; current->state.state = State::Stopping;
resetFadeStartPosition(type); resetFadeStartPosition(type);
if (fadedStart) *fadedStart = true; if (fadedStart) *fadedStart = true;
} break; } break;
case State::Pausing: { case State::Pausing: {
current->state.state = State::Finishing; current->state.state = State::Stopping;
if (fadedStart) *fadedStart = true; if (fadedStart) *fadedStart = true;
} break; } break;
case State::Paused: case State::Paused:
@ -642,7 +642,7 @@ void Mixer::play(const AudioMsgId &audio, std::unique_ptr<VideoSoundData> videoD
current->state.state = State::Pausing; current->state.state = State::Pausing;
resetFadeStartPosition(type); resetFadeStartPosition(type);
} break; } break;
case State::Finishing: { case State::Stopping: {
current->state.state = State::Pausing; current->state.state = State::Pausing;
} break; } break;
@ -781,7 +781,7 @@ void Mixer::pause(const AudioMsgId &audio, bool fast) {
} }
} break; } break;
case State::Finishing: { case State::Stopping: {
track->state.state = fast ? State::Paused : State::Pausing; track->state.state = fast ? State::Paused : State::Pausing;
} break; } break;
} }
@ -874,7 +874,7 @@ void Mixer::seek(AudioMsgId::Type type, int64 position) {
auto fastSeek = (position >= current->bufferedPosition && position < current->bufferedPosition + current->bufferedLength - (current->loaded ? 0 : kDefaultFrequency)); auto fastSeek = (position >= current->bufferedPosition && position < current->bufferedPosition + current->bufferedLength - (current->loaded ? 0 : kDefaultFrequency));
if (!streamCreated) { if (!streamCreated) {
fastSeek = false; fastSeek = false;
} else if (IsStopped(current->state.state) || (current->state.state == State::Finishing)) { } else if (IsStoppedOrStopping(current->state.state)) {
fastSeek = false; fastSeek = false;
} }
if (fastSeek) { if (fastSeek) {
@ -908,7 +908,7 @@ void Mixer::seek(AudioMsgId::Type type, int64 position) {
emit unsuppressSong(); emit unsuppressSong();
} }
} break; } break;
case State::Finishing: case State::Stopping:
case State::Stopped: case State::Stopped:
case State::StoppedAtEnd: case State::StoppedAtEnd:
case State::StoppedAtError: case State::StoppedAtError:
@ -940,6 +940,29 @@ void Mixer::stop(const AudioMsgId &audio) {
if (current) emit updated(current); if (current) emit updated(current);
} }
void Mixer::stop(const AudioMsgId &audio, State state) {
Expects(IsStopped(state));
AudioMsgId current;
{
QMutexLocker lock(&AudioMutex);
auto type = audio.type();
auto track = trackForType(type);
if (!track || track->state.id != audio || IsStopped(track->state.state)) {
return;
}
current = track->state.id;
setStoppedState(track, state);
if (type == AudioMsgId::Type::Voice) {
emit unsuppressSong();
} else if (type == AudioMsgId::Type::Video) {
track->clear();
}
}
if (current) emit updated(current);
}
void Mixer::stopAndClear() { void Mixer::stopAndClear() {
Track *current_audio = nullptr, *current_song = nullptr; Track *current_audio = nullptr, *current_song = nullptr;
{ {
@ -1012,11 +1035,7 @@ void Mixer::reattachIfNeeded() {
auto reattachNeeded = [this] { auto reattachNeeded = [this] {
auto isPlayingState = [](const Track &track) { auto isPlayingState = [](const Track &track) {
auto state = track.state.state; auto state = track.state.state;
return (state == State::Starting) return (state == State::Playing) || IsFading(state);
|| (state == State::Playing)
|| (state == State::Finishing)
|| (state == State::Pausing)
|| (state == State::Resuming);
}; };
for (auto i = 0; i != kTogetherLimit; ++i) { for (auto i = 0; i != kTogetherLimit; ++i) {
if (isPlayingState(*trackForType(AudioMsgId::Type::Voice, i)) if (isPlayingState(*trackForType(AudioMsgId::Type::Voice, i))
@ -1174,7 +1193,7 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
} }
switch (track->state.state) { switch (track->state.state) {
case State::Finishing: case State::Stopping:
case State::Pausing: case State::Pausing:
case State::Starting: case State::Starting:
case State::Resuming: { case State::Resuming: {
@ -1186,29 +1205,33 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
} }
auto fullPosition = track->bufferedPosition + positionInBuffered; auto fullPosition = track->bufferedPosition + positionInBuffered;
if (fading && (state == AL_PLAYING || !track->loading)) { if (state != AL_PLAYING && !track->loading) {
auto fadingForSamplesCount = (fullPosition - track->fadeStartPosition); if (fading || playing) {
if (state != AL_PLAYING) {
fading = false; fading = false;
if (track->stream.source) { playing = false;
if (track->isStreamCreated()) {
alSourceStop(track->stream.source); alSourceStop(track->stream.source);
alSourcef(track->stream.source, AL_GAIN, 1); alSourcef(track->stream.source, AL_GAIN, 1);
if (errorHappened()) return EmitError; if (errorHappened()) return EmitError;
} }
if (track->state.state == State::Pausing) { if (track->state.state == State::Pausing) {
track->state.state = State::PausedAtEnd; track->state.state = State::PausedAtEnd;
} else if (track->state.state == State::Stopping) {
setStoppedState(track, State::Stopped);
} else { } else {
setStoppedState(track, State::StoppedAtEnd); setStoppedState(track, State::StoppedAtEnd);
} }
emitSignals |= EmitStopped; emitSignals |= EmitStopped;
} else if (TimeMs(1000) * fadingForSamplesCount >= kFadeDuration * track->state.frequency) { }
} else if (fading && state == AL_PLAYING) {
auto fadingForSamplesCount = (fullPosition - track->fadeStartPosition);
if (TimeMs(1000) * fadingForSamplesCount >= kFadeDuration * track->state.frequency) {
fading = false; fading = false;
alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier); alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier);
if (errorHappened()) return EmitError; if (errorHappened()) return EmitError;
switch (track->state.state) { switch (track->state.state) {
case State::Finishing: { case State::Stopping: {
alSourceStop(track->stream.source); alSourceStop(track->stream.source);
if (errorHappened()) return EmitError; if (errorHappened()) return EmitError;
@ -1229,23 +1252,14 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
} }
} else { } else {
auto newGain = TimeMs(1000) * fadingForSamplesCount / float64(kFadeDuration * track->state.frequency); auto newGain = TimeMs(1000) * fadingForSamplesCount / float64(kFadeDuration * track->state.frequency);
if (track->state.state == State::Pausing || track->state.state == State::Finishing) { if (track->state.state == State::Pausing || track->state.state == State::Stopping) {
newGain = 1. - newGain; newGain = 1. - newGain;
} }
alSourcef(track->stream.source, AL_GAIN, newGain * volumeMultiplier); alSourcef(track->stream.source, AL_GAIN, newGain * volumeMultiplier);
if (errorHappened()) return EmitError; if (errorHappened()) return EmitError;
} }
} else if (playing && (state == AL_PLAYING || !track->loading)) { } else if (playing && state == AL_PLAYING) {
if (state != AL_PLAYING) { if (volumeChanged) {
playing = false;
if (track->isStreamCreated()) {
alSourceStop(track->stream.source);
alSourcef(track->stream.source, AL_GAIN, 1);
if (errorHappened()) return EmitError;
}
setStoppedState(track, State::StoppedAtEnd);
emitSignals |= EmitStopped;
} else if (volumeChanged) {
alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier); alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier);
if (errorHappened()) return EmitError; if (errorHappened()) return EmitError;
} }

View File

@ -69,7 +69,7 @@ enum class State {
Starting = 0x08, Starting = 0x08,
Playing = 0x10, Playing = 0x10,
Finishing = 0x18, Stopping = 0x18,
Pausing = 0x20, Pausing = 0x20,
Paused = 0x28, Paused = 0x28,
PausedAtEnd = 0x30, PausedAtEnd = 0x30,
@ -83,6 +83,14 @@ inline bool IsStopped(State state) {
|| (state == State::StoppedAtStart); || (state == State::StoppedAtStart);
} }
inline bool IsStoppedOrStopping(State state) {
return IsStopped(state) || (state == State::Stopping);
}
inline bool IsStoppedAtEnd(State state) {
return (state == State::StoppedAtEnd);
}
inline bool IsPaused(State state) { inline bool IsPaused(State state) {
return (state == State::Paused) return (state == State::Paused)
|| (state == State::PausedAtEnd); || (state == State::PausedAtEnd);
@ -90,7 +98,7 @@ inline bool IsPaused(State state) {
inline bool IsFading(State state) { inline bool IsFading(State state) {
return (state == State::Starting) return (state == State::Starting)
|| (state == State::Finishing) || (state == State::Stopping)
|| (state == State::Pausing) || (state == State::Pausing)
|| (state == State::Resuming); || (state == State::Resuming);
} }
@ -119,6 +127,7 @@ public:
void resume(const AudioMsgId &audio, bool fast = false); void resume(const AudioMsgId &audio, bool fast = false);
void seek(AudioMsgId::Type type, int64 position); // type == AudioMsgId::Type::Song void seek(AudioMsgId::Type type, int64 position); // type == AudioMsgId::Type::Song
void stop(const AudioMsgId &audio); void stop(const AudioMsgId &audio);
void stop(const AudioMsgId &audio, State state);
// Video player audio stream interface. // Video player audio stream interface.
void feedFromVideo(VideoSoundPart &&part); void feedFromVideo(VideoSoundPart &&part);

View File

@ -218,18 +218,6 @@ TimeMs FFMpegReaderImplementation::durationMs() const {
return (_fmtContext->streams[_streamId]->duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; return (_fmtContext->streams[_streamId]->duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
} }
void FFMpegReaderImplementation::pauseAudio() {
if (_audioStreamId >= 0) {
Player::mixer()->pause(_audioMsgId, true);
}
}
void FFMpegReaderImplementation::resumeAudio() {
if (_audioStreamId >= 0) {
Player::mixer()->resume(_audioMsgId, true);
}
}
bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
Expects(_frameRead); Expects(_frameRead);
_frameRead = false; _frameRead = false;
@ -425,7 +413,7 @@ bool FFMpegReaderImplementation::start(Mode mode, TimeMs &positionMs) {
positionMs = countPacketMs(&packet); positionMs = countPacketMs(&packet);
} }
if (_audioStreamId >= 0) { if (hasAudio()) {
auto position = (positionMs * soundData->frequency) / 1000LL; auto position = (positionMs * soundData->frequency) / 1000LL;
Player::mixer()->play(_audioMsgId, std::move(soundData), position); Player::mixer()->play(_audioMsgId, std::move(soundData), position);
} }
@ -480,10 +468,6 @@ QString FFMpegReaderImplementation::logData() const {
} }
FFMpegReaderImplementation::~FFMpegReaderImplementation() { FFMpegReaderImplementation::~FFMpegReaderImplementation() {
if (_audioStreamId >= 0) {
Player::mixer()->stop(_audioMsgId);
}
clearPacketQueue(); clearPacketQueue();
if (_frameRead) { if (_frameRead) {

View File

@ -48,8 +48,6 @@ public:
bool hasAudio() const override { bool hasAudio() const override {
return (_audioStreamId >= 0); return (_audioStreamId >= 0);
} }
void pauseAudio() override;
void resumeAudio() override;
bool start(Mode mode, TimeMs &positionMs) override; bool start(Mode mode, TimeMs &positionMs) override;
bool inspectAt(TimeMs &positionMs); bool inspectAt(TimeMs &positionMs);

View File

@ -55,8 +55,6 @@ public:
virtual TimeMs durationMs() const = 0; virtual TimeMs durationMs() const = 0;
virtual bool hasAudio() const = 0; virtual bool hasAudio() const = 0;
virtual void pauseAudio() = 0;
virtual void resumeAudio() = 0;
virtual bool start(Mode mode, TimeMs &positionMs) = 0; virtual bool start(Mode mode, TimeMs &positionMs) = 0;

View File

@ -42,10 +42,6 @@ public:
bool hasAudio() const override { bool hasAudio() const override {
return false; return false;
} }
void pauseAudio() override {
}
void resumeAudio() override {
}
bool start(Mode mode, TimeMs &positionMs) override; bool start(Mode mode, TimeMs &positionMs) override;

View File

@ -421,8 +421,8 @@ public:
} }
if (!_started) { if (!_started) {
_started = true; _started = true;
if (!_videoPausedAtMs) { if (!_videoPausedAtMs && _hasAudio) {
_implementation->resumeAudio(); Player::mixer()->resume(_audioMsgId, true);
} }
} }
@ -436,7 +436,7 @@ public:
auto frameMs = _seekPositionMs + ms - _animationStarted; auto frameMs = _seekPositionMs + ms - _animationStarted;
auto readResult = _implementation->readFramesTill(frameMs, ms); auto readResult = _implementation->readFramesTill(frameMs, ms);
if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile) { if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile) {
stop(); stop(Player::State::StoppedAtEnd);
_state = State::Finished; _state = State::Finished;
return ProcessResult::Finished; return ProcessResult::Finished;
} else if (readResult == internal::ReaderImplementation::ReadResult::Error) { } else if (readResult == internal::ReaderImplementation::ReadResult::Error) {
@ -501,7 +501,9 @@ public:
if (_videoPausedAtMs) return; // Paused already. if (_videoPausedAtMs) return; // Paused already.
_videoPausedAtMs = ms; _videoPausedAtMs = ms;
_implementation->pauseAudio(); if (_hasAudio) {
Player::mixer()->pause(_audioMsgId, true);
}
} }
void resumeVideo(TimeMs ms) { void resumeVideo(TimeMs ms) {
@ -512,17 +514,22 @@ public:
_nextFrameWhen += delta; _nextFrameWhen += delta;
_videoPausedAtMs = 0; _videoPausedAtMs = 0;
_implementation->resumeAudio(); if (_hasAudio) {
Player::mixer()->resume(_audioMsgId, true);
}
} }
ProcessResult error() { ProcessResult error() {
stop(); stop(Player::State::StoppedAtError);
_state = State::Error; _state = State::Error;
return ProcessResult::Error; return ProcessResult::Error;
} }
void stop() { void stop(Player::State audioState) {
_implementation = nullptr; _implementation = nullptr;
if (_hasAudio) {
Player::mixer()->stop(_audioMsgId, audioState);
}
if (_location) { if (_location) {
if (_accessed) { if (_accessed) {
@ -534,7 +541,7 @@ public:
} }
~ReaderPrivate() { ~ReaderPrivate() {
stop(); stop(Player::State::Stopped);
_data.clear(); _data.clear();
} }

View File

@ -117,17 +117,25 @@ CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent)
Global::RefSongVolumeChanged().notify(); Global::RefSongVolumeChanged().notify();
}); });
subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); }); subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); });
subscribe(instance()->repeatChangedNotifier(), [this] { subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
updateRepeatTrackIcon(); if (type == AudioMsgId::Type::Song) {
updateRepeatTrackIcon();
}
}); });
subscribe(instance()->playlistChangedNotifier(), [this] { subscribe(instance()->playlistChangedNotifier(), [this](AudioMsgId::Type type) {
handlePlaylistUpdate(); if (type == AudioMsgId::Type::Song) {
handlePlaylistUpdate();
}
}); });
subscribe(instance()->updatedNotifier(), [this](const TrackState &state) { subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
handleSongUpdate(state); if (state.id.type() == AudioMsgId::Type::Song) {
handleSongUpdate(state);
}
}); });
subscribe(instance()->songChangedNotifier(), [this] { subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
handleSongChange(); if (type == AudioMsgId::Type::Song) {
handleSongChange();
}
}); });
handleSongChange(); handleSongChange();
@ -246,7 +254,7 @@ void CoverWidget::handleSongUpdate(const TrackState &state) {
_playback->updateState(state); _playback->updateState(state);
} }
auto stopped = (IsStopped(state.state) || state.state == State::Finishing); auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
if (instance()->isSeeking(AudioMsgId::Type::Song)) { if (instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true; showPause = true;
@ -268,7 +276,7 @@ void CoverWidget::updateTimeText(const TrackState &state) {
QString time; QString time;
qint64 position = 0, length = 0, display = 0; qint64 position = 0, length = 0, display = 0;
auto frequency = state.frequency; auto frequency = state.frequency;
if (!IsStopped(state.state) && state.state != State::Finishing) { if (!IsStoppedOrStopping(state.state)) {
display = position = state.position; display = position = state.position;
length = state.length; length = state.length;
} else { } else {
@ -305,7 +313,7 @@ void CoverWidget::updateTimeLabel() {
} }
void CoverWidget::handleSongChange() { void CoverWidget::handleSongChange() {
auto &current = instance()->current(AudioMsgId::Type::Song); auto current = instance()->current(AudioMsgId::Type::Song);
auto song = current.audio()->song(); auto song = current.audio()->song();
if (!song) { if (!song) {
return; return;
@ -325,8 +333,8 @@ void CoverWidget::handleSongChange() {
} }
void CoverWidget::handlePlaylistUpdate() { void CoverWidget::handlePlaylistUpdate() {
auto &current = instance()->current(AudioMsgId::Type::Song); auto current = instance()->current(AudioMsgId::Type::Song);
auto &playlist = instance()->playlist(AudioMsgId::Type::Song); auto playlist = instance()->playlist(AudioMsgId::Type::Song);
auto index = playlist.indexOf(current.contextId()); auto index = playlist.indexOf(current.contextId());
if (!current || index < 0) { if (!current || index < 0) {
destroyPrevNextButtons(); destroyPrevNextButtons();

View File

@ -86,7 +86,7 @@ AudioMsgId::Type Instance::getActiveType() const {
auto voiceData = getData(AudioMsgId::Type::Voice); auto voiceData = getData(AudioMsgId::Type::Voice);
if (voiceData->current) { if (voiceData->current) {
auto state = mixer()->currentState(voiceData->type); auto state = mixer()->currentState(voiceData->type);
if (IsActive(state.state)) { if (voiceData->current == state.id && !IsStoppedOrStopping(state.state)) {
return voiceData->type; return voiceData->type;
} }
} }
@ -136,7 +136,7 @@ void Instance::setCurrent(const AudioMsgId &audioId) {
data->history = nullptr; data->history = nullptr;
data->migrated = nullptr; data->migrated = nullptr;
} }
_songChangedNotifier.notify(true); _trackChangedNotifier.notify(data->type, true);
if (data->history != history || data->migrated != migrated) { if (data->history != history || data->migrated != migrated) {
rebuildPlaylist(data); rebuildPlaylist(data);
} }
@ -163,25 +163,27 @@ void Instance::rebuildPlaylist(Data *data) {
data->playlist.push_back(FullMsgId(data->history->channelId(), msgId)); data->playlist.push_back(FullMsgId(data->history->channelId(), msgId));
} }
} }
_playlistChangedNotifier.notify(true); _playlistChangedNotifier.notify(data->type, true);
} }
void Instance::moveInPlaylist(Data *data, int delta) { bool Instance::moveInPlaylist(Data *data, int delta) {
Expects(data != nullptr); Expects(data != nullptr);
auto index = data->playlist.indexOf(data->current.contextId()); auto index = data->playlist.indexOf(data->current.contextId());
auto newIndex = index + delta; auto newIndex = index + delta;
if (!data->current || index < 0 || newIndex < 0 || newIndex >= data->playlist.size()) { if (!data->current || index < 0 || newIndex < 0 || newIndex >= data->playlist.size()) {
rebuildPlaylist(data); rebuildPlaylist(data);
return; return false;
} }
auto msgId = data->playlist[newIndex]; auto msgId = data->playlist[newIndex];
if (auto item = App::histItemById(msgId)) { if (auto item = App::histItemById(msgId)) {
if (auto media = item->getMedia()) { if (auto media = item->getMedia()) {
media->playInline(); media->playInline();
return true;
} }
} }
return false;
} }
Instance *instance() { Instance *instance() {
@ -254,16 +256,18 @@ void Instance::playPause(AudioMsgId::Type type) {
} }
} }
void Instance::next(AudioMsgId::Type type) { bool Instance::next(AudioMsgId::Type type) {
if (auto data = getData(type)) { if (auto data = getData(type)) {
moveInPlaylist(data, 1); return moveInPlaylist(data, 1);
} }
return false;
} }
void Instance::previous(AudioMsgId::Type type) { bool Instance::previous(AudioMsgId::Type type) {
if (auto data = getData(type)) { if (auto data = getData(type)) {
moveInPlaylist(data, -1); return moveInPlaylist(data, -1);
} }
return false;
} }
void Instance::playPauseCancelClicked(AudioMsgId::Type type) { void Instance::playPauseCancelClicked(AudioMsgId::Type type) {
@ -272,7 +276,7 @@ void Instance::playPauseCancelClicked(AudioMsgId::Type type) {
} }
auto state = mixer()->currentState(type); auto state = mixer()->currentState(type);
auto stopped = (IsStopped(state.state) || state.state == State::Finishing); auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
auto audio = state.id.audio(); auto audio = state.id.audio();
if (audio && audio->loading()) { if (audio && audio->loading()) {
@ -319,8 +323,8 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
if (data->isPlaying && state.state == State::StoppedAtEnd) { if (data->isPlaying && state.state == State::StoppedAtEnd) {
if (data->repeatEnabled) { if (data->repeatEnabled) {
play(data->current); play(data->current);
} else { } else if (!next(type)) {
next(type); _tracksFinishedNotifier.notify(type);
} }
} }
auto isPlaying = !IsStopped(state.state); auto isPlaying = !IsStopped(state.state);

View File

@ -42,8 +42,10 @@ public:
void pause(AudioMsgId::Type type); void pause(AudioMsgId::Type type);
void stop(AudioMsgId::Type type); void stop(AudioMsgId::Type type);
void playPause(AudioMsgId::Type type); void playPause(AudioMsgId::Type type);
void next(AudioMsgId::Type type); bool next(AudioMsgId::Type type);
void previous(AudioMsgId::Type type); bool previous(AudioMsgId::Type type);
AudioMsgId::Type getActiveType() const;
void play() { void play() {
play(getActiveType()); play(getActiveType());
@ -57,11 +59,11 @@ public:
void playPause() { void playPause() {
playPause(getActiveType()); playPause(getActiveType());
} }
void next() { bool next() {
next(getActiveType()); return next(getActiveType());
} }
void previous() { bool previous() {
previous(getActiveType()); return previous(getActiveType());
} }
void playPauseCancelClicked(AudioMsgId::Type type); void playPauseCancelClicked(AudioMsgId::Type type);
@ -83,7 +85,7 @@ public:
void toggleRepeat(AudioMsgId::Type type) { void toggleRepeat(AudioMsgId::Type type) {
if (auto data = getData(type)) { if (auto data = getData(type)) {
data->repeatEnabled = !data->repeatEnabled; data->repeatEnabled = !data->repeatEnabled;
_repeatChangedNotifier.notify(); _repeatChangedNotifier.notify(type);
} }
} }
@ -115,13 +117,16 @@ public:
base::Observable<TrackState> &updatedNotifier() { base::Observable<TrackState> &updatedNotifier() {
return _updatedNotifier; return _updatedNotifier;
} }
base::Observable<void> &playlistChangedNotifier() { base::Observable<AudioMsgId::Type> &tracksFinishedNotifier() {
return _tracksFinishedNotifier;
}
base::Observable<AudioMsgId::Type> &playlistChangedNotifier() {
return _playlistChangedNotifier; return _playlistChangedNotifier;
} }
base::Observable<void> &songChangedNotifier() { base::Observable<AudioMsgId::Type> &trackChangedNotifier() {
return _songChangedNotifier; return _trackChangedNotifier;
} }
base::Observable<void> &repeatChangedNotifier() { base::Observable<AudioMsgId::Type> &repeatChangedNotifier() {
return _repeatChangedNotifier; return _repeatChangedNotifier;
} }
@ -148,8 +153,6 @@ private:
bool isPlaying = false; bool isPlaying = false;
}; };
AudioMsgId::Type getActiveType() const;
// Observed notifications. // Observed notifications.
void notifyPeerUpdated(const Notify::PeerUpdate &update); void notifyPeerUpdated(const Notify::PeerUpdate &update);
void handleSongUpdate(const AudioMsgId &audioId); void handleSongUpdate(const AudioMsgId &audioId);
@ -157,7 +160,7 @@ private:
void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update); void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update);
void setCurrent(const AudioMsgId &audioId); void setCurrent(const AudioMsgId &audioId);
void rebuildPlaylist(Data *data); void rebuildPlaylist(Data *data);
void moveInPlaylist(Data *data, int delta); bool moveInPlaylist(Data *data, int delta);
void preloadNext(Data *data); void preloadNext(Data *data);
void handleLogout(); void handleLogout();
@ -189,9 +192,10 @@ private:
base::Observable<bool> _titleButtonOver; base::Observable<bool> _titleButtonOver;
base::Observable<bool> _playerWidgetOver; base::Observable<bool> _playerWidgetOver;
base::Observable<TrackState> _updatedNotifier; base::Observable<TrackState> _updatedNotifier;
base::Observable<void> _playlistChangedNotifier; base::Observable<AudioMsgId::Type> _tracksFinishedNotifier;
base::Observable<void> _songChangedNotifier; base::Observable<AudioMsgId::Type> _playlistChangedNotifier;
base::Observable<void> _repeatChangedNotifier; base::Observable<AudioMsgId::Type> _trackChangedNotifier;
base::Observable<AudioMsgId::Type> _repeatChangedNotifier;
}; };

View File

@ -30,7 +30,7 @@ namespace Player {
ListWidget::ListWidget(QWidget *parent) : TWidget(parent) { ListWidget::ListWidget(QWidget *parent) : TWidget(parent) {
setMouseTracking(true); setMouseTracking(true);
playlistUpdated(); playlistUpdated();
subscribe(instance()->playlistChangedNotifier(), [this] { playlistUpdated(); }); subscribe(instance()->playlistChangedNotifier(), [this](AudioMsgId::Type type) { playlistUpdated(); });
subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) { subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) {
itemRemoved(item); itemRemoved(item);
}); });
@ -183,7 +183,7 @@ int ListWidget::marginTop() const {
void ListWidget::playlistUpdated() { void ListWidget::playlistUpdated() {
auto newHeight = 0; auto newHeight = 0;
auto &playlist = instance()->playlist(AudioMsgId::Type::Song); auto playlist = instance()->playlist(AudioMsgId::Type::Song);
auto playlistSize = playlist.size(); auto playlistSize = playlist.size();
auto existingSize = _list.size(); auto existingSize = _list.size();
if (playlistSize > existingSize) { if (playlistSize > existingSize) {

View File

@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "lang.h"
#include "media/media_audio.h" #include "media/media_audio.h"
#include "media/view/media_clip_playback.h" #include "media/view/media_clip_playback.h"
#include "media/player/media_player_button.h" #include "media/player/media_player_button.h"
@ -107,15 +108,21 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
_playbackSlider->setValue(value); _playbackSlider->setValue(value);
}); });
_playbackSlider->setChangeProgressCallback([this](float64 value) { _playbackSlider->setChangeProgressCallback([this](float64 value) {
if (_type != AudioMsgId::Type::Song) {
return; // Round video seek is not supported for now :(
}
handleSeekProgress(value); handleSeekProgress(value);
_playback->setValue(value, false); _playback->setValue(value, false);
}); });
_playbackSlider->setChangeFinishedCallback([this](float64 value) { _playbackSlider->setChangeFinishedCallback([this](float64 value) {
if (_type != AudioMsgId::Type::Song) {
return; // Round video seek is not supported for now :(
}
handleSeekFinished(value); handleSeekFinished(value);
_playback->setValue(value, false); _playback->setValue(value, false);
}); });
_playPause->setClickedCallback([this] { _playPause->setClickedCallback([this] {
instance()->playPauseCancelClicked(AudioMsgId::Type::Song); instance()->playPauseCancelClicked(_type);
}); });
updateVolumeToggleIcon(); updateVolumeToggleIcon();
@ -131,23 +138,35 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
instance()->toggleRepeat(AudioMsgId::Type::Song); instance()->toggleRepeat(AudioMsgId::Type::Song);
}); });
subscribe(instance()->repeatChangedNotifier(), [this] { subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
updateRepeatTrackIcon(); if (type == _type) {
}); updateRepeatTrackIcon();
subscribe(instance()->playlistChangedNotifier(), [this] {
handlePlaylistUpdate();
});
subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
if (state.id.type() == AudioMsgId::Type::Song) {
handleSongUpdate(state);
} }
}); });
subscribe(instance()->songChangedNotifier(), [this] { subscribe(instance()->playlistChangedNotifier(), [this](AudioMsgId::Type type) {
handleSongChange(); if (type == _type) {
handlePlaylistUpdate();
}
}); });
handleSongChange(); subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
handleSongUpdate(state);
handleSongUpdate(mixer()->currentState(AudioMsgId::Type::Song)); });
subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == _type) {
handleSongChange();
}
});
subscribe(instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Voice) {
_voiceIsActive = false;
auto currentSong = instance()->current(AudioMsgId::Type::Song);
auto songState = mixer()->currentState(AudioMsgId::Type::Song);
if (currentSong == songState.id && !IsStoppedOrStopping(songState.state)) {
setType(AudioMsgId::Type::Song);
}
}
});
setType(AudioMsgId::Type::Song);
_playPause->finishTransform(); _playPause->finishTransform();
} }
@ -167,8 +186,19 @@ void Widget::updateVolumeToggleIcon() {
_volumeToggle->setIconOverride(icon()); _volumeToggle->setIconOverride(icon());
} }
void Widget::setCloseCallback(CloseCallback &&callback) { void Widget::setCloseCallback(base::lambda<void()> callback) {
_close->setClickedCallback(std::move(callback)); _close->setClickedCallback([this, callback = std::move(callback)] {
_voiceIsActive = false;
if (_type == AudioMsgId::Type::Voice) {
auto songData = instance()->current(AudioMsgId::Type::Song);
auto songState = mixer()->currentState(AudioMsgId::Type::Song);
if (songData == songState.id && !IsStoppedOrStopping(songState.state)) {
instance()->stop(AudioMsgId::Type::Voice);
return;
}
}
callback();
});
} }
void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) { void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) {
@ -206,7 +236,7 @@ void Widget::handleSeekProgress(float64 progress) {
_seekPositionMs = positionMs; _seekPositionMs = positionMs;
updateTimeLabel(); updateTimeLabel();
instance()->startSeeking(AudioMsgId::Type::Song); instance()->startSeeking(_type);
} }
} }
@ -216,13 +246,12 @@ void Widget::handleSeekFinished(float64 progress) {
auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs); auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs);
_seekPositionMs = -1; _seekPositionMs = -1;
auto type = AudioMsgId::Type::Song; auto state = mixer()->currentState(_type);
auto state = mixer()->currentState(type);
if (state.id && state.length) { if (state.id && state.length) {
mixer()->seek(type, qRound(progress * state.length)); mixer()->seek(_type, qRound(progress * state.length));
} }
instance()->stopSeeking(AudioMsgId::Type::Song); instance()->stopSeeking(_type);
} }
void Widget::resizeEvent(QResizeEvent *e) { void Widget::resizeEvent(QResizeEvent *e) {
@ -287,7 +316,10 @@ int Widget::getLabelsLeft() const {
} }
int Widget::getLabelsRight() const { int Widget::getLabelsRight() const {
auto result = st::mediaPlayerCloseRight + _close->width() + _repeatTrack->width() + _volumeToggle->width(); auto result = st::mediaPlayerCloseRight + _close->width();
if (_type == AudioMsgId::Type::Song) {
result += _repeatTrack->width() + _volumeToggle->width();
}
result += st::mediaPlayerPadding; result += st::mediaPlayerPadding;
return result; return result;
} }
@ -310,8 +342,35 @@ void Widget::updateRepeatTrackIcon() {
_repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg); _repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
} }
void Widget::checkForTypeChange() {
auto hasActiveType = [](AudioMsgId::Type type) {
auto current = instance()->current(type);
auto state = mixer()->currentState(type);
return (current == state.id && !IsStoppedOrStopping(state.state));
};
if (hasActiveType(AudioMsgId::Type::Voice)) {
_voiceIsActive = true;
setType(AudioMsgId::Type::Voice);
} else if (!_voiceIsActive && hasActiveType(AudioMsgId::Type::Song)) {
setType(AudioMsgId::Type::Song);
}
}
void Widget::setType(AudioMsgId::Type type) {
if (_type != type) {
_type = type;
_repeatTrack->setVisible(_type == AudioMsgId::Type::Song);
_volumeToggle->setVisible(_type == AudioMsgId::Type::Song);
_playbackSlider->setVisible(_type == AudioMsgId::Type::Song);
updateLabelsGeometry();
handleSongChange();
handleSongUpdate(mixer()->currentState(_type));
}
}
void Widget::handleSongUpdate(const TrackState &state) { void Widget::handleSongUpdate(const TrackState &state) {
if (!state.id.audio()) { checkForTypeChange();
if (state.id.type() != _type || !state.id.audio()) {
return; return;
} }
@ -321,9 +380,9 @@ void Widget::handleSongUpdate(const TrackState &state) {
_playback->updateState(state); _playback->updateState(state);
} }
auto stopped = (IsStopped(state.state) || state.state == State::Finishing); auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
if (instance()->isSeeking(AudioMsgId::Type::Song)) { if (instance()->isSeeking(_type)) {
showPause = true; showPause = true;
} }
auto buttonState = [audio = state.id.audio(), showPause] { auto buttonState = [audio = state.id.audio(), showPause] {
@ -343,7 +402,7 @@ void Widget::updateTimeText(const TrackState &state) {
QString time; QString time;
qint64 position = 0, length = 0, display = 0; qint64 position = 0, length = 0, display = 0;
auto frequency = state.frequency; auto frequency = state.frequency;
if (!IsStopped(state.state) && state.state != State::Finishing) { if (!IsStoppedOrStopping(state.state)) {
display = position = state.position; display = position = state.position;
length = state.length; length = state.length;
} else if (state.length) { } else if (state.length) {
@ -381,16 +440,41 @@ void Widget::updateTimeLabel() {
} }
void Widget::handleSongChange() { void Widget::handleSongChange() {
auto &current = instance()->current(AudioMsgId::Type::Song); auto current = instance()->current(_type);
auto song = current.audio()->song(); if (!current || !current.audio()) {
return;
}
TextWithEntities textWithEntities; TextWithEntities textWithEntities;
if (!song || song->performer.isEmpty()) { if (current.audio()->voice() || current.audio()->isRoundVideo()) {
textWithEntities.text = (!song || song->title.isEmpty()) ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title; if (auto item = App::histItemById(current.contextId())) {
auto name = App::peerName(item->fromOriginal());
auto date = [item] {
auto date = item->date.date();
auto time = item->date.time().toString(cTimeFormat());
auto today = QDateTime::currentDateTime().date();
if (date == today) {
return lng_player_message_today(lt_time, time);
} else if (date.addDays(1) == today) {
return lng_player_message_yesterday(lt_time, time);
}
return lng_player_message_date(lt_date, langDayOfMonthFull(date), lt_time, time);
};
textWithEntities.text = name + ' ' + date();
textWithEntities.entities.append({ EntityInTextBold, 0, name.size(), QString() });
} else {
textWithEntities.text = lang(lng_media_audio);
}
} else { } else {
auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title); auto song = current.audio()->song();
textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title; if (!song || song->performer.isEmpty()) {
textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() }); textWithEntities.text = (!song || song->title.isEmpty()) ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
} else {
auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title);
textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
}
} }
_nameLabel->setMarkedText(textWithEntities); _nameLabel->setMarkedText(textWithEntities);
@ -398,8 +482,8 @@ void Widget::handleSongChange() {
} }
void Widget::handlePlaylistUpdate() { void Widget::handlePlaylistUpdate() {
auto &current = instance()->current(AudioMsgId::Type::Song); auto current = instance()->current(_type);
auto &playlist = instance()->playlist(AudioMsgId::Type::Song); auto playlist = instance()->playlist(_type);
auto index = playlist.indexOf(current.contextId()); auto index = playlist.indexOf(current.contextId());
if (!current || index < 0) { if (!current || index < 0) {
destroyPrevNextButtons(); destroyPrevNextButtons();

View File

@ -45,8 +45,7 @@ class Widget : public TWidget, private base::Subscriber {
public: public:
Widget(QWidget *parent); Widget(QWidget *parent);
using CloseCallback = base::lambda<void()>; void setCloseCallback(base::lambda<void()> callback);
void setCloseCallback(CloseCallback &&callback);
void setShadowGeometryToLeft(int x, int y, int w, int h); void setShadowGeometryToLeft(int x, int y, int w, int h);
void showShadow(); void showShadow();
@ -81,6 +80,8 @@ private:
void updateVolumeToggleIcon(); void updateVolumeToggleIcon();
void checkForTypeChange();
void setType(AudioMsgId::Type type);
void handleSongUpdate(const TrackState &state); void handleSongUpdate(const TrackState &state);
void handleSongChange(); void handleSongChange();
void handlePlaylistUpdate(); void handlePlaylistUpdate();
@ -92,6 +93,13 @@ private:
TimeMs _lastDurationMs = 0; TimeMs _lastDurationMs = 0;
QString _time; QString _time;
// We display all the controls according to _type.
// We switch to Type::Voice if a voice/video message is played.
// We switch to Type::Song only if _voiceIsActive == false.
// We change _voiceIsActive to false only manually or from tracksFinished().
AudioMsgId::Type _type = AudioMsgId::Type::Unknown;
bool _voiceIsActive = false;
class PlayButton; class PlayButton;
object_ptr<Ui::FlatLabel> _nameLabel; object_ptr<Ui::FlatLabel> _nameLabel;
object_ptr<Ui::LabelSimple> _timeLabel; object_ptr<Ui::LabelSimple> _timeLabel;

View File

@ -133,10 +133,10 @@ void Controller::updatePlayPauseResumeState(const Player::TrackState &state) {
void Controller::updateTimeTexts(const Player::TrackState &state) { void Controller::updateTimeTexts(const Player::TrackState &state) {
qint64 position = 0, length = state.length; qint64 position = 0, length = state.length;
if (!Player::IsStopped(state.state) && state.state != Player::State::Finishing) { if (Player::IsStoppedAtEnd(state.state)) {
position = state.position;
} else if (state.state == Player::State::StoppedAtEnd) {
position = state.length; position = state.length;
} else if (!Player::IsStoppedOrStopping(state.state)) {
position = state.position;
} else { } else {
position = 0; position = 0;
} }

View File

@ -46,10 +46,10 @@ void Playback::updateState(const Player::TrackState &state) {
} }
_playing = !Player::IsStopped(state.state); _playing = !Player::IsStopped(state.state);
if (_playing || state.state == Player::State::Stopped) { if (Player::IsStoppedAtEnd(state.state)) {
position = state.position;
} else if (state.state == Player::State::StoppedAtEnd) {
position = state.length; position = state.length;
} else if (!Player::IsStoppedOrStopping(state.state)) {
position = state.position;
} else { } else {
position = 0; position = 0;
} }

View File

@ -666,7 +666,7 @@ bool Voice::updateStatusText() {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
using State = Media::Player::State; using State = Media::Player::State;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); 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 (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency); realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
@ -950,7 +950,7 @@ bool Document::updateStatusText() {
statusSize = FileStatusSizeLoaded; statusSize = FileStatusSizeLoaded;
using State = Media::Player::State; using State = Media::Player::State;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); 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) { if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency); realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);

View File

@ -36,4 +36,4 @@ void disableMediaShortcuts();
void finish(); void finish();
} } // namespace Shortcuts

View File

@ -1202,7 +1202,7 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
using State = Media::Player::State; using State = Media::Player::State;
if (playVoice) { if (playVoice) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id); Media::Player::mixer()->resume(state.id);
} else { } else {
@ -1218,7 +1218,7 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
} }
} else if (playMusic) { } else if (playMusic) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id); Media::Player::mixer()->resume(state.id);
} else { } else {
@ -1510,7 +1510,7 @@ void DocumentData::performActionOnLoad() {
if (playVoice) { if (playVoice) {
if (loaded()) { if (loaded()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id); Media::Player::mixer()->resume(state.id);
} else { } else {
@ -1524,7 +1524,7 @@ void DocumentData::performActionOnLoad() {
} else if (playMusic) { } else if (playMusic) {
if (loaded()) { if (loaded()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id); Media::Player::mixer()->resume(state.id);
} else { } else {

View File

@ -53,6 +53,10 @@ public:
} }
void toggleFast(bool visible); void toggleFast(bool visible);
bool isHiddenOrHiding() const {
return isHidden() || (_a_height.animating() && _hiding);
}
void finishAnimation() { void finishAnimation() {
_a_height.finish(); _a_height.finish();
myEnsureResized(_entity); myEnsureResized(_entity);