From 611a3e2f8ad0e31b1ccc1c8099415832f0cf0015 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 19 May 2017 21:11:33 +0300 Subject: [PATCH] Handle voice playlist in Media::Player::Instance. This allows video and voice messages to autoplay one after another. --- .../history/history_media_types.cpp | 11 + .../SourceFiles/history/history_media_types.h | 2 + Telegram/SourceFiles/mainwidget.cpp | 2 +- .../media/player/media_player_cover.cpp | 12 +- .../media/player/media_player_instance.cpp | 232 ++++++++++-------- .../media/player/media_player_instance.h | 116 ++++++--- .../media/player/media_player_list.cpp | 4 +- .../media/player/media_player_widget.cpp | 12 +- 8 files changed, 243 insertions(+), 148 deletions(-) diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index 66546b289..1fcf6afeb 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -1675,6 +1675,17 @@ void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool HistoryFileMedia::clickHandlerPressedChanged(p, pressed); } +bool HistoryDocument::playInline(bool autoplay) { + if (_data->voice()) { + DocumentOpenClickHandler::doOpen(_data, _parent, ActionOnLoadPlayInline); + return true; + } else if (_data->song()) { + Media::Player::instance()->play(AudioMsgId(_data, _parent->fullId())); + return true; + } + return false; +} + void HistoryDocument::attachToParent() { App::regDocumentItem(_data, _parent); } diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index 46d8c1265..770b7b2e5 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -411,6 +411,8 @@ public: return _data; } + bool playInline(bool autoplay) override; + void attachToParent() override; void detachFromParent() override; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 96ad98921..36a00eefa 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1604,7 +1604,7 @@ void MainWidget::closeBothPlayers() { Media::Player::instance()->usePanelPlayer().notify(false, true); _playerPanel->hideIgnoringEnterEvents(); _playerPlaylist->hideIgnoringEnterEvents(); - Media::Player::instance()->stop(); + Media::Player::instance()->stop(AudioMsgId::Type::Song); Shortcuts::disableMediaShortcuts(); } diff --git a/Telegram/SourceFiles/media/player/media_player_cover.cpp b/Telegram/SourceFiles/media/player/media_player_cover.cpp index 4c5db51a9..f18442688 100644 --- a/Telegram/SourceFiles/media/player/media_player_cover.cpp +++ b/Telegram/SourceFiles/media/player/media_player_cover.cpp @@ -102,12 +102,12 @@ CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent) _playback->setValue(value, false); }); _playPause->setClickedCallback([this] { - instance()->playPauseCancelClicked(); + instance()->playPauseCancelClicked(AudioMsgId::Type::Song); }); updateRepeatTrackIcon(); _repeatTrack->setClickedCallback([this] { - instance()->toggleRepeat(); + instance()->toggleRepeat(AudioMsgId::Type::Song); }); updateVolumeToggleIcon(); @@ -232,7 +232,7 @@ void CoverWidget::updateLabelPositions() { } void CoverWidget::updateRepeatTrackIcon() { - _repeatTrack->setIconOverride(instance()->repeatEnabled() ? nullptr : &st::mediaPlayerRepeatInactiveIcon); + _repeatTrack->setIconOverride(instance()->repeatEnabled(AudioMsgId::Type::Song) ? nullptr : &st::mediaPlayerRepeatInactiveIcon); } void CoverWidget::handleSongUpdate(const TrackState &state) { @@ -305,7 +305,7 @@ void CoverWidget::updateTimeLabel() { } void CoverWidget::handleSongChange() { - auto ¤t = instance()->current(); + auto ¤t = instance()->current(AudioMsgId::Type::Song); auto song = current.audio()->song(); if (!song) { return; @@ -325,8 +325,8 @@ void CoverWidget::handleSongChange() { } void CoverWidget::handlePlaylistUpdate() { - auto ¤t = instance()->current(); - auto &playlist = instance()->playlist(); + auto ¤t = instance()->current(AudioMsgId::Type::Song); + auto &playlist = instance()->playlist(AudioMsgId::Type::Song); auto index = playlist.indexOf(current.contextId()); if (!current || index < 0) { destroyPrevNextButtons(); diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 8a656ae1c..85a4304f0 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -49,7 +49,9 @@ void finish() { Audio::Finish(); } -Instance::Instance() { +Instance::Instance() +: _songData(AudioMsgId::Type::Song, OverviewMusicFiles) +, _voiceData(AudioMsgId::Type::Voice, OverviewRoundVoiceFiles) { subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) { handleSongUpdate(audioId); }); @@ -80,18 +82,36 @@ Instance::Instance() { handleAuthSessionChange(); } -void Instance::notifyPeerUpdated(const Notify::PeerUpdate &update) { - if (!_history) { - return; - } - if (!(update.mediaTypesMask & (1 << OverviewMusicFiles))) { - return; - } - if (update.peer != _history->peer && (!_migrated || update.peer != _migrated->peer)) { - return; +AudioMsgId::Type Instance::getActiveType() const { + auto voiceData = getData(AudioMsgId::Type::Voice); + if (voiceData->current) { + auto state = mixer()->currentState(voiceData->type); + if (IsActive(state.state)) { + return voiceData->type; + } } + return AudioMsgId::Type::Song; +} - rebuildPlaylist(); +void Instance::notifyPeerUpdated(const Notify::PeerUpdate &update) { + checkPeerUpdate(AudioMsgId::Type::Song, update); + checkPeerUpdate(AudioMsgId::Type::Voice, update); +} + +void Instance::checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update) { + if (auto data = getData(type)) { + if (!data->history) { + return; + } + if (!(update.mediaTypesMask & (1 << data->overview))) { + return; + } + if (update.peer != data->history->peer && (!data->migrated || update.peer != data->migrated->peer)) { + return; + } + + rebuildPlaylist(data); + } } void Instance::handleSongUpdate(const AudioMsgId &audioId) { @@ -101,97 +121,105 @@ void Instance::handleSongUpdate(const AudioMsgId &audioId) { } void Instance::setCurrent(const AudioMsgId &audioId) { - if (audioId.type() == AudioMsgId::Type::Song) { - if (_current != audioId) { - _current = audioId; - _isPlaying = false; + if (auto data = getData(audioId.type())) { + if (data->current != audioId) { + data->current = audioId; + data->isPlaying = false; - auto history = _history, migrated = _migrated; - auto item = _current ? App::histItemById(_current.contextId()) : nullptr; + auto history = data->history; + auto migrated = data->migrated; + auto item = data->current ? App::histItemById(data->current.contextId()) : nullptr; if (item) { - _history = item->history()->peer->migrateTo() ? App::history(item->history()->peer->migrateTo()) : item->history(); - _migrated = _history->peer->migrateFrom() ? App::history(_history->peer->migrateFrom()) : nullptr; + data->history = item->history()->peer->migrateTo() ? App::history(item->history()->peer->migrateTo()) : item->history(); + data->migrated = data->history->peer->migrateFrom() ? App::history(data->history->peer->migrateFrom()) : nullptr; } else { - _history = _migrated = nullptr; + data->history = nullptr; + data->migrated = nullptr; } _songChangedNotifier.notify(true); - if (_history != history || _migrated != migrated) { - rebuildPlaylist(); + if (data->history != history || data->migrated != migrated) { + rebuildPlaylist(data); } } - } else if (audioId.type() == AudioMsgId::Type::Voice) { - if (_currentVoice != audioId) { - _currentVoice = audioId; - } } } -void Instance::rebuildPlaylist() { - _playlist.clear(); - if (_history && _history->loadedAtBottom()) { - auto &historyOverview = _history->overview[OverviewMusicFiles]; - if (_migrated && _migrated->loadedAtBottom() && _history->loadedAtTop()) { - auto &migratedOverview = _migrated->overview[OverviewMusicFiles]; - _playlist.reserve(migratedOverview.size() + historyOverview.size()); +void Instance::rebuildPlaylist(Data *data) { + Expects(data != nullptr); + + data->playlist.clear(); + if (data->history && data->history->loadedAtBottom()) { + auto &historyOverview = data->history->overview[data->overview]; + if (data->migrated && data->migrated->loadedAtBottom() && data->history->loadedAtTop()) { + auto &migratedOverview = data->migrated->overview[data->overview]; + data->playlist.reserve(migratedOverview.size() + historyOverview.size()); for_const (auto msgId, migratedOverview) { - _playlist.push_back(FullMsgId(_migrated->channelId(), msgId)); + data->playlist.push_back(FullMsgId(data->migrated->channelId(), msgId)); } } else { - _playlist.reserve(historyOverview.size()); + data->playlist.reserve(historyOverview.size()); } for_const (auto msgId, historyOverview) { - _playlist.push_back(FullMsgId(_history->channelId(), msgId)); + data->playlist.push_back(FullMsgId(data->history->channelId(), msgId)); } } _playlistChangedNotifier.notify(true); } -void Instance::moveInPlaylist(int delta) { - auto index = _playlist.indexOf(_current.contextId()); +void Instance::moveInPlaylist(Data *data, int delta) { + Expects(data != nullptr); + + auto index = data->playlist.indexOf(data->current.contextId()); auto newIndex = index + delta; - if (!_current || index < 0 || newIndex < 0 || newIndex >= _playlist.size()) { - rebuildPlaylist(); + if (!data->current || index < 0 || newIndex < 0 || newIndex >= data->playlist.size()) { + rebuildPlaylist(data); return; } - auto msgId = _playlist[newIndex]; + auto msgId = data->playlist[newIndex]; if (auto item = App::histItemById(msgId)) { if (auto media = item->getMedia()) { - if (auto document = media->getDocument()) { - if (auto song = document->song()) { - play(AudioMsgId(document, msgId)); - } - } + media->playInline(); } } } Instance *instance() { - t_assert(SingleInstance != nullptr); + Expects(SingleInstance != nullptr); return SingleInstance; } -void Instance::play() { - auto state = mixer()->currentState(AudioMsgId::Type::Song); +void Instance::play(AudioMsgId::Type type) { + auto state = mixer()->currentState(type); if (state.id) { if (IsStopped(state.state)) { - mixer()->play(state.id); + play(state.id); } else { mixer()->resume(state.id); } - } else if (_current) { - mixer()->play(_current); + } else if (auto data = getData(type)) { + if (data->current) { + play(data->current); + } } } void Instance::play(const AudioMsgId &audioId) { - if (!audioId || !audioId.audio()->song()) { + if (!audioId) { return; } - mixer()->play(audioId); - setCurrent(audioId); - if (audioId.audio()->loading()) { - documentLoadProgress(audioId.audio()); + if (audioId.audio()->song() || audioId.audio()->voice()) { + mixer()->play(audioId); + setCurrent(audioId); + if (audioId.audio()->loading()) { + documentLoadProgress(audioId.audio()); + } + } else if (audioId.audio()->isRoundVideo()) { + if (auto item = App::histItemById(audioId.contextId())) { + if (auto media = item->getMedia()) { + media->playInline(); + } + } } } @@ -202,69 +230,71 @@ void Instance::pause(AudioMsgId::Type type) { } } -void Instance::stop() { - auto state = mixer()->currentState(AudioMsgId::Type::Song); +void Instance::stop(AudioMsgId::Type type) { + auto state = mixer()->currentState(type); if (state.id) { mixer()->stop(state.id); } } -void Instance::playPause() { - auto state = mixer()->currentState(AudioMsgId::Type::Song); +void Instance::playPause(AudioMsgId::Type type) { + auto state = mixer()->currentState(type); if (state.id) { if (IsStopped(state.state)) { - mixer()->play(state.id); + play(state.id); } else if (IsPaused(state.state) || state.state == State::Pausing) { mixer()->resume(state.id); } else { mixer()->pause(state.id); } - } else if (_current) { - mixer()->play(_current); + } else if (auto data = getData(type)) { + if (data->current) { + play(data->current); + } } } -void Instance::next() { - moveInPlaylist(1); +void Instance::next(AudioMsgId::Type type) { + if (auto data = getData(type)) { + moveInPlaylist(data, 1); + } } -void Instance::previous() { - moveInPlaylist(-1); +void Instance::previous(AudioMsgId::Type type) { + if (auto data = getData(type)) { + moveInPlaylist(data, -1); + } } -void Instance::playPauseCancelClicked() { - if (isSeeking(AudioMsgId::Type::Song)) { +void Instance::playPauseCancelClicked(AudioMsgId::Type type) { + if (isSeeking(type)) { return; } - auto state = mixer()->currentState(AudioMsgId::Type::Song); + auto state = mixer()->currentState(type); 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) { - pause(AudioMsgId::Type::Song); + pause(type); } else { - play(); + play(type); } } void Instance::startSeeking(AudioMsgId::Type type) { - if (type == AudioMsgId::Type::Song) { - _seeking = _current; - } else if (type == AudioMsgId::Type::Voice) { - _seekingVoice = _currentVoice; + if (auto data = getData(type)) { + data->seeking = data->current; } pause(type); emitUpdate(type, [](const AudioMsgId &playing) { return true; }); } void Instance::stopSeeking(AudioMsgId::Type type) { - if (type == AudioMsgId::Type::Song) { - _seeking = AudioMsgId(); - } else if (type == AudioMsgId::Type::Voice) { - _seekingVoice = AudioMsgId(); + if (auto data = getData(type)) { + data->seeking = AudioMsgId(); } emitUpdate(type, [](const AudioMsgId &playing) { return true; }); } @@ -285,37 +315,39 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) { setCurrent(state.id); _updatedNotifier.notify(state, true); - if (type == AudioMsgId::Type::Song) { - if (_isPlaying && state.state == State::StoppedAtEnd) { - if (_repeatEnabled) { - mixer()->play(_current); + if (auto data = getData(type)) { + if (data->isPlaying && state.state == State::StoppedAtEnd) { + if (data->repeatEnabled) { + play(data->current); } else { - next(); + next(type); } } auto isPlaying = !IsStopped(state.state); - if (_isPlaying != isPlaying) { - _isPlaying = isPlaying; - if (_isPlaying) { - preloadNext(); + if (data->isPlaying != isPlaying) { + data->isPlaying = isPlaying; + if (data->isPlaying) { + preloadNext(data); } } } } -void Instance::preloadNext() { - if (!_current) { +void Instance::preloadNext(Data *data) { + Expects(data != nullptr); + + if (!data->current) { return; } - auto index = _playlist.indexOf(_current.contextId()); + auto index = data->playlist.indexOf(data->current.contextId()); if (index < 0) { return; } auto nextIndex = index + 1; - if (nextIndex >= _playlist.size()) { + if (nextIndex >= data->playlist.size()) { return; } - if (auto item = App::histItemById(_playlist[nextIndex])) { + if (auto item = App::histItemById(data->playlist[nextIndex])) { if (auto media = item->getMedia()) { if (auto document = media->getDocument()) { if (!document->loaded(DocumentData::FilePathResolveSaveFromDataSilent)) { @@ -327,14 +359,8 @@ void Instance::preloadNext() { } void Instance::handleLogout() { - _current = _seeking = AudioMsgId(); - _history = nullptr; - _migrated = nullptr; - - _repeatEnabled = _isPlaying = false; - - _playlist.clear(); - + *getData(AudioMsgId::Type::Voice) = Data(AudioMsgId::Type::Voice, OverviewRoundVoiceFiles); + *getData(AudioMsgId::Type::Song) = Data(AudioMsgId::Type::Song, OverviewMusicFiles); _usePanelPlayer.notify(false, true); } diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 1b4642ca7..650da26b8 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -38,41 +38,69 @@ struct TrackState; class Instance : private base::Subscriber { public: - void play(); + void play(AudioMsgId::Type type); void pause(AudioMsgId::Type type); - void stop(); - void playPause(); - void next(); - void previous(); + void stop(AudioMsgId::Type type); + void playPause(AudioMsgId::Type type); + void next(AudioMsgId::Type type); + void previous(AudioMsgId::Type type); - void playPauseCancelClicked(); + void play() { + play(getActiveType()); + } + void pause() { + pause(getActiveType()); + } + void stop() { + stop(getActiveType()); + } + void playPause() { + playPause(getActiveType()); + } + void next() { + next(getActiveType()); + } + void previous() { + previous(getActiveType()); + } + + void playPauseCancelClicked(AudioMsgId::Type type); void play(const AudioMsgId &audioId); - const AudioMsgId ¤t() const { - return _current; + AudioMsgId current(AudioMsgId::Type type) const { + if (auto data = getData(type)) { + return data->current; + } + return AudioMsgId(); } - bool repeatEnabled() const { - return _repeatEnabled; + bool repeatEnabled(AudioMsgId::Type type) const { + if (auto data = getData(type)) { + return data->repeatEnabled; + } + return false; } - void toggleRepeat() { - _repeatEnabled = !_repeatEnabled; - _repeatChangedNotifier.notify(); + void toggleRepeat(AudioMsgId::Type type) { + if (auto data = getData(type)) { + data->repeatEnabled = !data->repeatEnabled; + _repeatChangedNotifier.notify(); + } } bool isSeeking(AudioMsgId::Type type) const { - if (type == AudioMsgId::Type::Song) { - return (_seeking == _current); - } else if (type == AudioMsgId::Type::Voice) { - return (_seekingVoice == _currentVoice); + if (auto data = getData(type)) { + return (data->seeking == data->current); } return false; } void startSeeking(AudioMsgId::Type type); void stopSeeking(AudioMsgId::Type type); - const QList &playlist() const { - return _playlist; + QList playlist(AudioMsgId::Type type) const { + if (auto data = getData(type)) { + return data->playlist; + } + return QList(); } base::Observable &usePanelPlayer() { @@ -105,29 +133,57 @@ private: Instance(); friend void start(); + struct Data { + Data(AudioMsgId::Type type, MediaOverviewType overview) : type(type), overview(overview) { + } + + AudioMsgId::Type type; + MediaOverviewType overview; + AudioMsgId current; + AudioMsgId seeking; + History *history = nullptr; + History *migrated = nullptr; + bool repeatEnabled = false; + QList playlist; + bool isPlaying = false; + }; + + AudioMsgId::Type getActiveType() const; + // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); void handleSongUpdate(const AudioMsgId &audioId); + void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update); void setCurrent(const AudioMsgId &audioId); - void rebuildPlaylist(); - void moveInPlaylist(int delta); - void preloadNext(); + void rebuildPlaylist(Data *data); + void moveInPlaylist(Data *data, int delta); + void preloadNext(Data *data); void handleLogout(); template void emitUpdate(AudioMsgId::Type type, CheckCallback check); - AudioMsgId _current, _seeking; - History *_history = nullptr; - History *_migrated = nullptr; + Data *getData(AudioMsgId::Type type) { + if (type == AudioMsgId::Type::Song) { + return &_songData; + } else if (type == AudioMsgId::Type::Voice) { + return &_voiceData; + } + return nullptr; + } - bool _repeatEnabled = false; + const Data *getData(AudioMsgId::Type type) const { + if (type == AudioMsgId::Type::Song) { + return &_songData; + } else if (type == AudioMsgId::Type::Voice) { + return &_voiceData; + } + return nullptr; + } - QList _playlist; - bool _isPlaying = false; - - AudioMsgId _currentVoice, _seekingVoice; + Data _songData; + Data _voiceData; base::Observable _usePanelPlayer; base::Observable _titleButtonOver; diff --git a/Telegram/SourceFiles/media/player/media_player_list.cpp b/Telegram/SourceFiles/media/player/media_player_list.cpp index 994c5fc18..3bfcc5add 100644 --- a/Telegram/SourceFiles/media/player/media_player_list.cpp +++ b/Telegram/SourceFiles/media/player/media_player_list.cpp @@ -156,7 +156,7 @@ void ListWidget::itemRemoved(HistoryItem *item) { QRect ListWidget::getCurrentTrackGeometry() const { auto top = marginTop(); - auto current = instance()->current(); + auto current = instance()->current(AudioMsgId::Type::Song); auto fullMsgId = current.contextId(); for_const (auto layout, _list) { auto layoutHeight = layout->height(); @@ -183,7 +183,7 @@ int ListWidget::marginTop() const { void ListWidget::playlistUpdated() { auto newHeight = 0; - auto &playlist = instance()->playlist(); + auto &playlist = instance()->playlist(AudioMsgId::Type::Song); auto playlistSize = playlist.size(); auto existingSize = _list.size(); if (playlistSize > existingSize) { diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index dd2a9812c..b63655d50 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -115,7 +115,7 @@ Widget::Widget(QWidget *parent) : TWidget(parent) _playback->setValue(value, false); }); _playPause->setClickedCallback([this] { - instance()->playPauseCancelClicked(); + instance()->playPauseCancelClicked(AudioMsgId::Type::Song); }); updateVolumeToggleIcon(); @@ -128,7 +128,7 @@ Widget::Widget(QWidget *parent) : TWidget(parent) updateRepeatTrackIcon(); _repeatTrack->setClickedCallback([this] { - instance()->toggleRepeat(); + instance()->toggleRepeat(AudioMsgId::Type::Song); }); subscribe(instance()->repeatChangedNotifier(), [this] { @@ -305,7 +305,7 @@ void Widget::updateLabelsGeometry() { } void Widget::updateRepeatTrackIcon() { - auto repeating = instance()->repeatEnabled(); + auto repeating = instance()->repeatEnabled(AudioMsgId::Type::Song); _repeatTrack->setIconOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledIcon, repeating ? nullptr : &st::mediaPlayerRepeatDisabledIconOver); _repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg); } @@ -381,7 +381,7 @@ void Widget::updateTimeLabel() { } void Widget::handleSongChange() { - auto ¤t = instance()->current(); + auto ¤t = instance()->current(AudioMsgId::Type::Song); auto song = current.audio()->song(); TextWithEntities textWithEntities; @@ -398,8 +398,8 @@ void Widget::handleSongChange() { } void Widget::handlePlaylistUpdate() { - auto ¤t = instance()->current(); - auto &playlist = instance()->playlist(); + auto ¤t = instance()->current(AudioMsgId::Type::Song); + auto &playlist = instance()->playlist(AudioMsgId::Type::Song); auto index = playlist.indexOf(current.contextId()); if (!current || index < 0) { destroyPrevNextButtons();