File indexing completed on 2024-05-19 05:38:37

0001 #/*
0002     SPDX-FileCopyrightText: 2007-2012 Alex Merry <alex.merry@kdemail.net>
0003     SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "multiplexer.h"
0009 
0010 #include "mpris2filterproxymodel.h"
0011 #include "mpris2sourcemodel.h"
0012 #include "playercontainer.h"
0013 
0014 std::shared_ptr<Multiplexer> Multiplexer::self()
0015 {
0016     static std::weak_ptr<Multiplexer> s_multiplexer;
0017     if (s_multiplexer.expired()) {
0018         auto ptr = std::make_shared<Multiplexer>();
0019         s_multiplexer = ptr;
0020         return ptr;
0021     }
0022 
0023     return s_multiplexer.lock();
0024 }
0025 
0026 Multiplexer::Multiplexer(QObject *parent)
0027     : QObject(parent)
0028     , m_filterModel(Mpris2FilterProxyModel::self())
0029 {
0030     for (int i = 0, size = m_filterModel->rowCount(); i < size; ++i) {
0031         PlayerContainer *const container = m_filterModel->index(i, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>();
0032         connect(container, &PlayerContainer::playbackStatusChanged, this, &Multiplexer::onPlaybackStatusChanged);
0033     }
0034 
0035     connect(m_filterModel.get(), &QAbstractListModel::rowsInserted, this, &Multiplexer::onRowsInserted);
0036     connect(m_filterModel.get(), &QAbstractListModel::rowsAboutToBeRemoved, this, &Multiplexer::onRowsAboutToBeRemoved);
0037     // rowsRemoved also triggers updates in MultiplexerModel, but we want to update MultiplexerModel after rowsRemoved
0038     connect(m_filterModel.get(), &QAbstractListModel::rowsRemoved, this, &Multiplexer::onRowsRemoved);
0039 }
0040 
0041 Multiplexer::~Multiplexer()
0042 {
0043 }
0044 
0045 QBindable<int> Multiplexer::activePlayerIndex() const
0046 {
0047     return &m_activePlayerIndex;
0048 }
0049 
0050 QBindable<PlayerContainer *> Multiplexer::activePlayer() const
0051 {
0052     return &m_activePlayer;
0053 }
0054 
0055 void Multiplexer::onRowsInserted(const QModelIndex &, int first, int)
0056 {
0057     PlayerContainer *const container = m_filterModel->index(first, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>();
0058     connect(container, &PlayerContainer::playbackStatusChanged, this, &Multiplexer::onPlaybackStatusChanged);
0059 
0060     if (m_activePlayer && m_activePlayer->playbackStatus() == PlaybackStatus::Playing) {
0061         // Keep the current player
0062         // No need to update index as the new item is inserted at the end
0063         return;
0064     }
0065 
0066     if (!m_activePlayer || container->playbackStatus() == PlaybackStatus::Playing) {
0067         // Use the new player
0068         m_activePlayer = container;
0069         m_activePlayerIndex = first;
0070     }
0071 }
0072 
0073 void Multiplexer::onRowsAboutToBeRemoved(const QModelIndex &, int first, int)
0074 {
0075     Q_ASSERT_X(m_activePlayer, Q_FUNC_INFO, qUtf8Printable(QString::number(first)));
0076     PlayerContainer *const container = m_filterModel->index(first, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>();
0077     // Need to manually disconnect from the container because the source can be filtered out but not gone (e.g. a browser)
0078     disconnect(container, &PlayerContainer::playbackStatusChanged, this, &Multiplexer::onPlaybackStatusChanged);
0079     if (m_activePlayerIndex == first) {
0080         m_activePlayer = nullptr;
0081         // Index is updated in evaluatePlayers() later
0082     }
0083 }
0084 
0085 void Multiplexer::onRowsRemoved(const QModelIndex &, int, int)
0086 {
0087     if (!m_activePlayer) {
0088         evaluatePlayers();
0089     } else {
0090         // Only update index
0091         updateIndex();
0092     }
0093 }
0094 
0095 void Multiplexer::onPlaybackStatusChanged()
0096 {
0097     // m_activePlayer can't be nullptr here, otherwise something is wrong
0098     if (m_activePlayer->playbackStatus() == PlaybackStatus::Playing) {
0099         // Keep the current player
0100         return;
0101     }
0102 
0103     PlayerContainer *container = static_cast<PlayerContainer *>(sender());
0104     if (container->playbackStatus() == PlaybackStatus::Playing) {
0105         // Use the new player
0106         m_activePlayer = container;
0107         updateIndex();
0108     } else {
0109         evaluatePlayers();
0110     }
0111 }
0112 
0113 void Multiplexer::updateIndex()
0114 {
0115     const auto sourceModel = static_cast<Mpris2SourceModel *>(m_filterModel->sourceModel());
0116     const auto beginIt = sourceModel->m_containers.cbegin();
0117     const auto endIt = sourceModel->m_containers.cend();
0118     const int sourceRow = std::distance(beginIt, std::find(beginIt, endIt, m_activePlayer.value()));
0119     const QModelIndex idx = m_filterModel->mapFromSource(sourceModel->index(sourceRow, 0));
0120     Q_ASSERT_X(idx.isValid(),
0121                Q_FUNC_INFO,
0122                qUtf8Printable(QStringLiteral("Current active player: \"%1\" Available players: \"%2\" Pending players: \"%3\"")
0123                                   .arg(m_activePlayer->identity(),
0124                                        std::accumulate(beginIt,
0125                                                        endIt,
0126                                                        QString(),
0127                                                        [](QString left, PlayerContainer *right) {
0128                                                            return std::move(left) + QLatin1Char(',') + right->identity();
0129                                                        }),
0130                                        std::accumulate(sourceModel->m_pendingContainers.cbegin(),
0131                                                        sourceModel->m_pendingContainers.cend(),
0132                                                        QString(),
0133                                                        [](QString left, auto &right) {
0134                                                            return std::move(left) + QLatin1Char(',') + right.first /* sourceName */;
0135                                                        }))));
0136     m_activePlayerIndex = idx.row();
0137 }
0138 
0139 void Multiplexer::evaluatePlayers()
0140 {
0141     PlayerContainer *container = nullptr;
0142     for (int i = 0, size = m_filterModel->rowCount(); i < size; ++i) {
0143         PlayerContainer *c = m_filterModel->index(i, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>();
0144         if (c->playbackStatus() == PlaybackStatus::Playing) {
0145             container = c;
0146             break;
0147         }
0148     }
0149 
0150     if (container) {
0151         // Has an active player
0152         m_activePlayer = container;
0153         updateIndex();
0154     } else if (!m_activePlayer && m_filterModel->rowCount() > 0) {
0155         // No active player, use the first player
0156         m_activePlayer = m_filterModel->index(0, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>();
0157         m_activePlayerIndex = 0;
0158     } else if (m_filterModel->rowCount() == 0) {
0159         m_activePlayer = nullptr;
0160         m_activePlayerIndex = -1;
0161     }
0162 
0163     // If there was an active player and currently there is no player that is playing, keep the previous selection
0164 }