Music Hub ..
A session-wide music playback service
Loading...
Searching...
No Matches
service_implementation.cpp
Go to the documentation of this file.
1/*
2 * Copyright © 2013-2014 Canonical Ltd.
3 * Copyright © 2022 UBports Foundation.
4 *
5 * Contact: Alberto Mardegan <mardy@users.sourceforge.net>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License version 3,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Authored by: Thomas Voß <thomas.voss@canonical.com>
20 * Jim Hodapp <jim.hodapp@canonical.com>
21 * Ricardo Mendoza <ricardo.mendoza@canonical.com>
22 *
23 * Note: Some of the PulseAudio code was adapted from telepathy-ofono
24 */
25
27
28#include "apparmor/lomiri.h"
31#include "logging.h"
35#include "recorder_observer.h"
37
38#include <string>
39#include <cstdint>
40#include <cstring>
41#include <map>
42#include <memory>
43#include <thread>
44#include <utility>
45
46#include <pulse/pulseaudio.h>
47
49
50using namespace media;
51
52namespace lomiri {
53namespace MediaHubService {
54
56{
57 Q_DECLARE_PUBLIC(ServiceImplementation)
58
59public:
60 // Create all of the appropriate observers and helper class instances to be
61 // passed to the PlayerImplementation
63
64 void pause_all_multimedia_sessions(bool resume_play_after_phonecall);
65 void resume_paused_multimedia_sessions(bool resume_video_sessions = true);
67
71
72private:
73 // This holds the key of the multimedia role Player instance that was paused
74 // when the battery level reached 10% or 5%
75 media::Player::PlayerKey resume_key;
76 media::power::BatteryObserver battery_observer;
77 media::power::StateController::Ptr power_state_controller;
78 media::ClientDeathObserver::Ptr client_death_observer;
79 media::RecorderObserver recorder_observer;
80 media::audio::OutputObserver audio_output_observer;
81 media::audio::OutputState audio_output_state;
82
84 // Holds a pair of a Player key denoting what player to resume playback, and a bool
85 // for if it should be resumed after a phone call is hung up
86 std::list<std::pair<media::Player::PlayerKey, bool>> paused_sessions;
87 Player::PlayerKey m_currentPlayer;
88 QHash<Player::PlayerKey, PlayerImplementation *> m_players;
90};
91
92}} // namespace
93
96 resume_key(Player::invalidKey),
97 power_state_controller(media::power::StateController::instance()),
98 client_death_observer(ClientDeathObserver::Ptr::create()),
99 audio_output_state(media::audio::OutputState::Speaker),
100 m_currentPlayer(Player::invalidKey),
101 q_ptr(q)
102{
103 QObject::connect(&battery_observer,
105 q, [this]()
106 {
107 const bool resume_play_after_phonecall = false;
108 // When the battery level hits 10% or 5%, pause all multimedia sessions.
109 // Playback will resume when the user clears the presented notification.
110 switch (battery_observer.level())
111 {
114 // Whatever player session is currently playing, make sure it is NOT resumed after
115 // a phonecall is hung up
116 pause_all_multimedia_sessions(resume_play_after_phonecall);
117 break;
118 default:
119 break;
120 }
121 });
122
123 QObject::connect(&battery_observer,
125 q, [this]()
126 {
127 // If the low battery level notification is no longer being displayed,
128 // resume what the user was previously playing
129 if (!battery_observer.isWarningActive())
131 });
132
133 QObject::connect(&audio_output_observer,
135 q, [this]()
136 {
137 audio::OutputState state = audio_output_observer.outputState();
138 const bool resume_play_after_phonecall = false;
139 switch (state)
140 {
142 MH_INFO("AudioOutputObserver reports that output is now Headphones/Headset.");
143 break;
145 MH_INFO("AudioOutputObserver reports that output is now Speaker.");
146 // Whatever player session is currently playing, make sure it is NOT resumed after
147 // a phonecall is hung up
148 pause_all_multimedia_sessions(resume_play_after_phonecall);
149 break;
151 MH_INFO("AudioOutputObserver reports that output is now External.");
152 break;
153 }
154 audio_output_state = state;
155 });
156
157 QObject::connect(&call_monitor,
159 q, [this]()
160 {
161 const bool resume_play_after_phonecall = true;
162 switch (call_monitor.callState()) {
164 MH_INFO("Got call started signal, pausing all multimedia sessions");
165 // Whatever player session is currently playing, make sure it gets resumed after
166 // a phonecall is hung up
167 pause_all_multimedia_sessions(resume_play_after_phonecall);
168 break;
170 MH_INFO("Got call ended signal, resuming paused multimedia sessions");
172 break;
173 }
174 });
175
176 QObject::connect(&recorder_observer,
178 q, [this]()
179 {
180 RecordingState state = recorder_observer.recordingState();
182 {
183 power_state_controller->requestDisplayOn();
184 // Whatever player session is currently playing, make sure it is NOT resumed after
185 // a phonecall is hung up
186 const bool resume_play_after_phonecall = false;
187 pause_all_multimedia_sessions(resume_play_after_phonecall);
188 }
189 else if (state == media::RecordingState::stopped)
190 {
191 power_state_controller->releaseDisplayOn();
192 }
193 });
194}
195
197{
198 for (auto i = m_players.begin(); i != m_players.end(); i++) {
199 const Player::PlayerKey key = i.key();
200 PlayerImplementation *player = i.value();
201
202 if (player->playbackStatus() == Player::playing &&
204 {
205 auto paused_player_pair = std::make_pair(key, resume_play_after_phonecall);
206 paused_sessions.push_back(paused_player_pair);
207 MH_INFO("Pausing Player with key: %d, resuming after phone call? %s", key,
208 (resume_play_after_phonecall ? "yes" : "no"));
209 player->pause();
210 }
211 }
212}
213
215{
216 std::for_each(paused_sessions.begin(), paused_sessions.end(),
217 [this, resume_video_sessions](const std::pair<media::Player::PlayerKey, bool> &paused_player_pair) {
218 const media::Player::PlayerKey key = paused_player_pair.first;
219 const bool resume_play_after_phonecall = paused_player_pair.second;
220 PlayerImplementation *player = m_players.value(key);
221 if (not player) {
222 MH_WARNING("Failed to look up Player instance for key %d"
223 ", no valid Player instance for that key value and cannot automatically resume"
224 " paused players. This most likely means that media-hub-server has crashed and"
225 " restarted.", key);
226 return;
227 }
228 // Only resume video playback if explicitly desired
229 if ((resume_video_sessions || player->isAudioSource()) && resume_play_after_phonecall)
230 player->play();
231 else
232 MH_INFO("Not auto-resuming video player session or other type of player session.");
233 });
234
235 paused_sessions.clear();
236}
237
239{
240 PlayerImplementation *player = m_players.value(resume_key);
241 if (not player) {
242 MH_WARNING("Failed to look up Player instance for key %d"
243 ", no valid Player instance for that key value and cannot automatically resume"
244 " paused Player. This most likely means that media-hub-server has crashed and"
245 " restarted.", resume_key);
246 return;
247 }
248
249 if (player->playbackStatus() == Player::paused)
250 {
251 MH_INFO("Resuming playback of Player with key: %d", resume_key);
252 player->play();
253 resume_key = Player::invalidKey;
254 }
255}
256
258{
260 if (key == m_currentPlayer) return;
261 m_currentPlayer = key;
262 Q_EMIT q->currentPlayerChanged();
263}
264
266{
267 MH_TRACE("");
268
269 PlayerImplementation *current_player = m_players.value(key);
270 if (not current_player)
271 {
272 MH_WARNING("Could not find Player by key: %d", key);
273 return false;
274 }
275
276 for (auto i = m_players.begin(); i != m_players.end(); i++) {
277 const Player::PlayerKey other_key = i.key();
278 PlayerImplementation *other_player = i.value();
279 // Only pause a Player if all of the following criteria are met:
280 // 1) currently playing
281 // 2) not the same player as the one passed in my key
282 // 3) new Player has an audio stream role set to multimedia
283 // 4) has an audio stream role set to multimedia
284 if (other_player->playbackStatus() == Player::playing &&
285 other_key != key &&
286 other_player->client().name != current_player->client().name &&
287 current_player->audioStreamRole() == media::Player::multimedia &&
289 {
290 MH_INFO("Pausing Player with key: %d", other_key);
291 other_player->pause();
292 }
293 }
294 return true;
295}
296
298{
299 MH_DEBUG("==== Pausing all other multimedia player sessions");
300 if (not pause_other_sessions(key)) {
301 MH_WARNING("Failed to pause other player sessions");
302 }
303
304 MH_DEBUG("==== Updating the current player");
305 setCurrentPlayer(key);
306}
307
313
317
319 const Player::Client &client)
320{
322
323 // Create a new Player
324 auto player = new PlayerImplementation({
325 client,
326 d->client_death_observer,
327 }, this);
328
329 QObject::connect(player, &PlayerImplementation::clientDisconnected,
330 this, [this, key=client.key]()
331 {
332 Q_D(ServiceImplementation);
333 /* Update the current player if needed */
334 PlayerImplementation *player = d->m_players.value(key);
335 if (player &&
336 player->audioStreamRole() == Player::AudioStreamRole::multimedia &&
337 key == currentPlayer())
338 {
339 MH_DEBUG("==== Resetting current player");
340 d->setCurrentPlayer(Player::invalidKey);
341 }
342
343 if (player->lifetime() == Player::Lifetime::normal) {
344 d->m_players.remove(key);
345 player->deleteLater();
346 }
347 });
348
349 QObject::connect(player, &PlayerImplementation::playbackRequested,
350 this, [d, key=client.key]() {
351 d->onPlaybackRequested(key);
352 });
353
354 d->m_players[client.key] = player;
355 return player;
356}
357
359{
360 Q_D(const ServiceImplementation);
361 return d->m_players.value(key);
362}
363
365{
367 MH_TRACE("");
368 d->pause_other_sessions(key);
369}
370
372{
373 Q_D(const ServiceImplementation);
374 return d->m_currentPlayer;
375}
QSharedPointer< ClientDeathObserver > Ptr
static const PlayerKey invalidKey
Definition player.h:59
void resume_paused_multimedia_sessions(bool resume_video_sessions=true)
void pause_all_multimedia_sessions(bool resume_play_after_phonecall)
PlayerImplementation * create_session(const Player::Client &client)
PlayerImplementation * playerByKey(Player::PlayerKey key) const
#define MH_TRACE(...)
Definition logging.h:37
#define MH_INFO(...)
Definition logging.h:39
#define MH_WARNING(...)
Definition logging.h:40
#define MH_DEBUG(...)
Definition logging.h:38