Music Hub  ..
A session-wide music playback service
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 
26 #include "service_implementation.h"
27 
28 #include "apparmor/lomiri.h"
29 #include "audio/output_observer.h"
30 #include "client_death_observer.h"
31 #include "logging.h"
32 #include "player_implementation.h"
33 #include "power/battery_observer.h"
34 #include "power/state_controller.h"
35 #include "recorder_observer.h"
36 #include "telephony/call_monitor.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 
48 namespace media = lomiri::MediaHubService;
49 
50 using namespace media;
51 
52 namespace lomiri {
53 namespace MediaHubService {
54 
56 {
57  Q_DECLARE_PUBLIC(ServiceImplementation)
58 
59 public:
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);
66  void resume_multimedia_session();
67 
68  void setCurrentPlayer(Player::PlayerKey key);
69  bool pause_other_sessions(Player::PlayerKey key);
70  void onPlaybackRequested(Player::PlayerKey key);
71 
72 private:
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 
83  media::telephony::CallMonitor call_monitor;
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;
89  ServiceImplementation *q_ptr;
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  {
112  case media::power::Level::low:
113  case media::power::Level::very_low:
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()) {
163  case media::telephony::CallMonitor::State::OffHook:
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;
169  case media::telephony::CallMonitor::State::OnHook:
170  MH_INFO("Got call ended signal, resuming paused multimedia sessions");
171  resume_paused_multimedia_sessions(false);
172  break;
173  }
174  });
175 
176  QObject::connect(&recorder_observer,
178  q, [this]()
179  {
180  RecordingState state = recorder_observer.recordingState();
181  if (state == media::RecordingState::started)
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 
238 void ServiceImplementationPrivate::resume_multimedia_session()
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 
257 void ServiceImplementationPrivate::setCurrentPlayer(Player::PlayerKey key)
258 {
260  if (key == m_currentPlayer) return;
261  m_currentPlayer = key;
262  Q_EMIT q->currentPlayerChanged();
263 }
264 
265 bool ServiceImplementationPrivate::pause_other_sessions(media::Player::PlayerKey key)
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 &&
288  other_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 
297 void ServiceImplementationPrivate::onPlaybackRequested(Player::PlayerKey key)
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 
308 ServiceImplementation::ServiceImplementation(QObject *parent):
309  QObject(parent),
310  d_ptr(new ServiceImplementationPrivate(this))
311 {
312 }
313 
315 {
316 }
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 }
QObject
lomiri::MediaHubService::ServiceImplementationPrivate::pause_all_multimedia_sessions
void pause_all_multimedia_sessions(bool resume_play_after_phonecall)
Definition: service_implementation.cpp:196
lomiri::MediaHubService::PlayerImplementation::client
const Player::Client & client() const
Definition: player_implementation.cpp:597
lomiri::MediaHubService::PlayerImplementation
Definition: player_implementation.h:44
lomiri::MediaHubService::audio::OutputState::Speaker
@ Speaker
lomiri::MediaHubService::audio
Definition: ostream_reporter.h:33
lomiri::MediaHubService::power::BatteryObserver::levelChanged
void levelChanged()
battery_observer.h
lomiri::MediaHubService::ClientDeathObserver
Definition: client_death_observer.h:39
lomiri::MediaHubService::PlayerImplementation::playbackRequested
void playbackRequested()
recorder_observer.h
output_observer.h
lomiri::MediaHubService::Player::multimedia
@ multimedia
Definition: player.h:117
lomiri::MediaHubService::RecordingState::stopped
@ stopped
lomiri::MediaHubService::power::BatteryObserver::isWarningActiveChanged
void isWarningActiveChanged()
lomiri::MediaHubService::PlayerImplementation::clientDisconnected
void clientDisconnected()
lomiri::MediaHubService::ServiceImplementation::~ServiceImplementation
~ServiceImplementation()
Definition: service_implementation.cpp:314
call_monitor.h
lomiri::MediaHubService::Player::Client
Definition: player.h:61
lomiri::MediaHubService::ServiceImplementationPrivate::resume_paused_multimedia_sessions
void resume_paused_multimedia_sessions(bool resume_video_sessions=true)
Definition: service_implementation.cpp:214
player_implementation.h
lomiri::MediaHubService::Player::PlayerKey
uint32_t PlayerKey
Definition: player.h:55
state_controller.h
lomiri::MediaHubService::ServiceImplementationPrivate::ServiceImplementationPrivate
ServiceImplementationPrivate(ServiceImplementation *q)
Definition: service_implementation.cpp:94
lomiri::MediaHubService::ServiceImplementationPrivate::resume_multimedia_session
void resume_multimedia_session()
Definition: service_implementation.cpp:238
lomiri::MediaHubService::audio::OutputState::Earpiece
@ Earpiece
client_death_observer.h
lomiri::MediaHubService::ClientDeathObserver::Ptr
QSharedPointer< ClientDeathObserver > Ptr
Definition: client_death_observer.h:45
lomiri::MediaHubService::ServiceImplementationPrivate
Definition: service_implementation.cpp:55
lomiri::MediaHubService::Player::Client::name
QString name
Definition: player.h:63
lomiri::MediaHubService::audio::OutputState
OutputState
Definition: output_observer.h:35
lomiri::MediaHubService::telephony::CallMonitor::callStateChanged
void callStateChanged()
MH_TRACE
#define MH_TRACE(...)
Definition: logging.h:37
lomiri::MediaHubService::RecordingState
RecordingState
Definition: recorder_observer.h:33
lomiri::MediaHubService::RecorderObserver::recordingStateChanged
void recordingStateChanged()
lomiri::MediaHubService
Definition: context.h:28
lomiri::MediaHubService::PlayerImplementation::audioStreamRole
Player::AudioStreamRole audioStreamRole() const
Definition: player_implementation.cpp:746
lomiri::MediaHubService::ServiceImplementation::playerByKey
PlayerImplementation * playerByKey(Player::PlayerKey key) const
Definition: service_implementation.cpp:358
lomiri::MediaHubService::audio::OutputObserver::outputStateChanged
void outputStateChanged()
lomiri::MediaHubService::ServiceImplementation::create_session
PlayerImplementation * create_session(const Player::Client &client)
Definition: service_implementation.cpp:318
lomiri::MediaHubService::PlayerImplementation::playbackStatus
Player::PlaybackStatus playbackStatus() const
Definition: player_implementation.cpp:692
lomiri.h
lomiri::MediaHubService::ServiceImplementation
Definition: service_implementation.h:37
MH_DEBUG
#define MH_DEBUG(...)
Definition: logging.h:38
MH_INFO
#define MH_INFO(...)
Definition: logging.h:39
lomiri::MediaHubService::RecorderObserver::recordingState
RecordingState recordingState() const
Definition: recorder_observer.cpp:65
lomiri
Definition: dbus_utils.h:24
lomiri::MediaHubService::Player
Definition: player.h:50
lomiri::MediaHubService::ServiceImplementation::pause_other_sessions
void pause_other_sessions(Player::PlayerKey key)
Definition: service_implementation.cpp:364
lomiri::MediaHubService::audio::OutputState::External
@ External
lomiri::MediaHubService::PlayerImplementation::pause
void pause()
Definition: player_implementation.cpp:847
MH_WARNING
#define MH_WARNING(...)
Definition: logging.h:40
logging.h
lomiri::MediaHubService::Player::playing
@ playing
Definition: player.h:96
service_implementation.h
lomiri::MediaHubService::Player::Client::key
PlayerKey key
Definition: player.h:62
lomiri::MediaHubService::ServiceImplementation::currentPlayer
Player::PlayerKey currentPlayer() const
Definition: service_implementation.cpp:371
lomiri::MediaHubService::RecorderObserver
Definition: recorder_observer.h:44