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 }