Merge lp:~phablet-team/media-hub/mpris-take-2 into lp:media-hub/stable
- mpris-take-2
- Merge into stable
Proposed by
Alfonso Sanchez-Beato
Status: | Merged |
---|---|
Approved by: | Jim Hodapp |
Approved revision: | 186 |
Merged at revision: | 159 |
Proposed branch: | lp:~phablet-team/media-hub/mpris-take-2 |
Merge into: | lp:media-hub/stable |
Diff against target: |
907 lines (+348/-125) 16 files modified
CMakeLists.txt (+1/-1) README (+9/-0) debian/changelog (+10/-0) include/core/media/service.h (+3/-0) src/core/media/mpris/player.h (+24/-4) src/core/media/mpris/service.h (+1/-0) src/core/media/player_implementation.cpp (+47/-5) src/core/media/service_implementation.cpp (+7/-0) src/core/media/service_implementation.h (+1/-0) src/core/media/service_skeleton.cpp (+194/-99) src/core/media/service_skeleton.h (+1/-0) src/core/media/service_stub.cpp (+9/-1) src/core/media/service_stub.h (+1/-0) src/core/media/track_list_implementation.cpp (+11/-5) src/core/media/track_list_skeleton.cpp (+27/-7) src/core/media/track_list_skeleton.h (+2/-3) |
To merge this branch: | bzr merge lp:~phablet-team/media-hub/mpris-take-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jim Hodapp (community) | Approve | ||
Manuel de la Peña (community) | Approve | ||
Simon Fels | Approve | ||
Review via email: mp+271942@code.launchpad.net |
Commit message
Re-enable MPRIS player controls and improve background playlists
Description of the change
Re-enable MPRIS player controls and improve background playlists
To post a comment you must log in.
Revision history for this message
Simon Fels (morphis) : | # |
Revision history for this message
Alfonso Sanchez-Beato (alfonsosanchezbeato) wrote : | # |
Revision history for this message
Simon Fels (morphis) : | # |
review:
Approve
Revision history for this message
Manuel de la Peña (mandel) : | # |
review:
Approve
- 183. By Alfonso Sanchez-Beato
-
[ Simon Fels ]
* Prevent us from leaking file descriptors and memory due to not releasing
gstreamer elements correctly.
[ CI Train Bot ]
* No-change rebuild. - 184. By Alfonso Sanchez-Beato
-
Fix changelog
Revision history for this message
Jim Hodapp (jhodapp) wrote : | # |
A few comments inline below.
review:
Needs Fixing
(code)
Revision history for this message
Jim Hodapp (jhodapp) wrote : | # |
One more change below
review:
Needs Fixing
- 185. By Alfonso Sanchez-Beato
-
Addess review comments
- 186. By Alfonso Sanchez-Beato
-
Update CanPlay and CanPause depending on whether there are items on the
playlist or not.
- 187. By Jim Hodapp
-
Merged with stable
- 188. By Jim Hodapp
-
Make sure that other sessions pause when the MPRIS play method is called.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2015-04-10 16:21:16 +0000 |
3 | +++ CMakeLists.txt 2015-10-14 20:54:59 +0000 |
4 | @@ -3,7 +3,7 @@ |
5 | project(ubuntu-media-hub) |
6 | |
7 | set(UBUNTU_MEDIA_HUB_VERSION_MAJOR 3) |
8 | -set(UBUNTU_MEDIA_HUB_VERSION_MINOR 1) |
9 | +set(UBUNTU_MEDIA_HUB_VERSION_MINOR 2) |
10 | set(UBUNTU_MEDIA_HUB_VERSION_PATCH 0) |
11 | |
12 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -fPIC -pthread") |
13 | |
14 | === modified file 'README' |
15 | --- README 2014-08-08 14:36:29 +0000 |
16 | +++ README 2015-10-14 20:54:59 +0000 |
17 | @@ -1,3 +1,12 @@ |
18 | +Coding Convention: |
19 | +------------------ |
20 | + |
21 | +NOTE: the media-hub code did not start out with the following coding convention, but it is being introduced to try and |
22 | +converge on a standard from this point forward: |
23 | + |
24 | +https://google-styleguide.googlecode.com/svn/trunk/cppguide.html |
25 | + |
26 | + |
27 | To Build: |
28 | --------- |
29 | |
30 | |
31 | === modified file 'debian/changelog' |
32 | --- debian/changelog 2015-10-02 14:23:08 +0000 |
33 | +++ debian/changelog 2015-10-14 20:54:59 +0000 |
34 | @@ -1,3 +1,13 @@ |
35 | +media-hub (3.2.0+15.04.20150928-0ubuntu1) UNRELEASED; urgency=medium |
36 | + |
37 | + * Make sure the correct player is set as the current player controled by |
38 | + MPRIS. |
39 | + * Improved the can_go_next() and can_go_previous() logic to always return |
40 | + true if the loop_status is currently set to loop over the entire |
41 | + playlist. |
42 | + |
43 | + -- Jim Hodapp <jim.hodapp@canonical.com> Tue, 08 Sep 2015 17:18:15 -0400 |
44 | + |
45 | media-hub (3.1.0+15.04.20151002-0ubuntu1) vivid; urgency=medium |
46 | |
47 | * Avoid getting additional references for audio_sink, which were not |
48 | |
49 | === modified file 'include/core/media/service.h' |
50 | --- include/core/media/service.h 2015-04-14 02:43:56 +0000 |
51 | +++ include/core/media/service.h 2015-10-14 20:54:59 +0000 |
52 | @@ -60,6 +60,9 @@ |
53 | /** @brief Resumes a fixed-name session directly by player key. */ |
54 | virtual std::shared_ptr<Player> resume_session(Player::PlayerKey) = 0; |
55 | |
56 | + /** @brief Sets the current player that the MPRIS interface will control */ |
57 | + virtual void set_current_player(Player::PlayerKey) = 0; |
58 | + |
59 | /** @brief Pauses sessions other than the supplied one. */ |
60 | virtual void pause_other_sessions(Player::PlayerKey) = 0; |
61 | |
62 | |
63 | === modified file 'src/core/media/mpris/player.h' |
64 | --- src/core/media/mpris/player.h 2015-04-06 20:59:14 +0000 |
65 | +++ src/core/media/mpris/player.h 2015-10-14 20:54:59 +0000 |
66 | @@ -192,12 +192,12 @@ |
67 | // Default values for properties |
68 | struct Defaults |
69 | { |
70 | - Properties::CanPlay::ValueType can_play{true}; |
71 | - Properties::CanPause::ValueType can_pause{true}; |
72 | + Properties::CanPlay::ValueType can_play{false}; |
73 | + Properties::CanPause::ValueType can_pause{false}; |
74 | Properties::CanSeek::ValueType can_seek{true}; |
75 | Properties::CanControl::ValueType can_control{true}; |
76 | - Properties::CanGoNext::ValueType can_go_next{true}; |
77 | - Properties::CanGoPrevious::ValueType can_go_previous{true}; |
78 | + Properties::CanGoNext::ValueType can_go_next{false}; |
79 | + Properties::CanGoPrevious::ValueType can_go_previous{false}; |
80 | Properties::IsVideoSource::ValueType is_video_source{false}; |
81 | Properties::IsAudioSource::ValueType is_audio_source{true}; |
82 | Properties::PlaybackStatus::ValueType playback_status{PlaybackStatus::stopped}; |
83 | @@ -307,6 +307,26 @@ |
84 | { |
85 | on_property_value_changed<Properties::Shuffle>(shuffle); |
86 | }); |
87 | + |
88 | + properties.can_play->changed().connect([this](bool can_play) |
89 | + { |
90 | + on_property_value_changed<Properties::CanPlay>(can_play); |
91 | + }); |
92 | + |
93 | + properties.can_pause->changed().connect([this](bool can_pause) |
94 | + { |
95 | + on_property_value_changed<Properties::CanPause>(can_pause); |
96 | + }); |
97 | + |
98 | + properties.can_go_next->changed().connect([this](bool can_go_next) |
99 | + { |
100 | + on_property_value_changed<Properties::CanGoNext>(can_go_next); |
101 | + }); |
102 | + |
103 | + properties.can_go_previous->changed().connect([this](bool can_go_previous) |
104 | + { |
105 | + on_property_value_changed<Properties::CanGoPrevious>(can_go_previous); |
106 | + }); |
107 | } |
108 | |
109 | template<typename Property> |
110 | |
111 | === modified file 'src/core/media/mpris/service.h' |
112 | --- src/core/media/mpris/service.h 2015-04-10 16:13:55 +0000 |
113 | +++ src/core/media/mpris/service.h 2015-10-14 20:54:59 +0000 |
114 | @@ -115,6 +115,7 @@ |
115 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(DestroySession, Service, 1000) |
116 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(CreateFixedSession, Service, 1000) |
117 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(ResumeSession, Service, 1000) |
118 | + DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(SetCurrentPlayer, Service, 1000) |
119 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(PauseOtherSessions, Service, 1000) |
120 | }; |
121 | } |
122 | |
123 | === modified file 'src/core/media/player_implementation.cpp' |
124 | --- src/core/media/player_implementation.cpp 2015-07-27 22:15:33 +0000 |
125 | +++ src/core/media/player_implementation.cpp 2015-10-14 20:54:59 +0000 |
126 | @@ -301,6 +301,27 @@ |
127 | } |
128 | } |
129 | |
130 | + void update_mpris_properties(void) |
131 | + { |
132 | + bool has_previous = track_list->has_previous() |
133 | + or parent->Parent::loop_status() != Player::LoopStatus::none; |
134 | + bool has_next = track_list->has_next() |
135 | + or parent->Parent::loop_status() != Player::LoopStatus::none; |
136 | + auto n_tracks = track_list->tracks()->size(); |
137 | + bool has_tracks = (n_tracks > 0) ? true : false; |
138 | + |
139 | + std::cout << "Updating MPRIS TrackList properties" |
140 | + << "; Tracks: " << n_tracks |
141 | + << ", has_previous: " << has_previous |
142 | + << ", has_next: " << has_next << std::endl; |
143 | + |
144 | + // Update properties |
145 | + parent->can_play().set(has_tracks); |
146 | + parent->can_pause().set(has_tracks); |
147 | + parent->can_go_previous().set(has_previous); |
148 | + parent->can_go_next().set(has_next); |
149 | + } |
150 | + |
151 | // Our link back to our parent. |
152 | media::PlayerImplementation<Parent>* parent; |
153 | // We just store the parameters passed on construction. |
154 | @@ -327,8 +348,8 @@ |
155 | d{std::make_shared<Private>(this, config)} |
156 | { |
157 | // Initialize default values for Player interface properties |
158 | - Parent::can_play().set(true); |
159 | - Parent::can_pause().set(true); |
160 | + Parent::can_play().set(false); |
161 | + Parent::can_pause().set(false); |
162 | Parent::can_seek().set(true); |
163 | Parent::can_go_previous().set(false); |
164 | Parent::can_go_next().set(false); |
165 | @@ -376,13 +397,15 @@ |
166 | |
167 | std::function<bool()> can_go_next_getter = [this]() |
168 | { |
169 | - return d->track_list->has_next(); |
170 | + // If LoopStatus == playlist, then there is always a next track |
171 | + return d->track_list->has_next() or Parent::loop_status() != Player::LoopStatus::none; |
172 | }; |
173 | Parent::can_go_next().install(can_go_next_getter); |
174 | |
175 | std::function<bool()> can_go_previous_getter = [this]() |
176 | { |
177 | - return d->track_list->has_previous(); |
178 | + // If LoopStatus == playlist, then there is always a next previous |
179 | + return d->track_list->has_previous() or Parent::loop_status() != Player::LoopStatus::none; |
180 | }; |
181 | Parent::can_go_previous().install(can_go_previous_getter); |
182 | |
183 | @@ -520,7 +543,26 @@ |
184 | std::cout << "** Track was added, handling in PlayerImplementation" << std::endl; |
185 | if (d->track_list->tracks()->size() == 1) |
186 | d->open_first_track_from_tracklist(id); |
187 | - }); |
188 | + |
189 | + d->update_mpris_properties(); |
190 | + }); |
191 | + |
192 | + d->track_list->on_track_removed().connect([this](const media::Track::Id&) |
193 | + { |
194 | + d->update_mpris_properties(); |
195 | + }); |
196 | + |
197 | + d->track_list->on_track_changed().connect([this](const media::Track::Id&) |
198 | + { |
199 | + d->update_mpris_properties(); |
200 | + }); |
201 | + |
202 | + d->track_list->on_track_list_replaced().connect( |
203 | + [this](const media::TrackList::ContainerTrackIdTuple&) |
204 | + { |
205 | + d->update_mpris_properties(); |
206 | + } |
207 | + ); |
208 | |
209 | // Everything is setup, we now subscribe to death notifications. |
210 | std::weak_ptr<Private> wp{d}; |
211 | |
212 | === modified file 'src/core/media/service_implementation.cpp' |
213 | --- src/core/media/service_implementation.cpp 2015-04-17 16:44:30 +0000 |
214 | +++ src/core/media/service_implementation.cpp 2015-10-14 20:54:59 +0000 |
215 | @@ -246,8 +246,15 @@ |
216 | return std::shared_ptr<media::Player>(); |
217 | } |
218 | |
219 | +void media::ServiceImplementation::set_current_player(Player::PlayerKey key) |
220 | +{ |
221 | + // no impl |
222 | +} |
223 | + |
224 | void media::ServiceImplementation::pause_other_sessions(media::Player::PlayerKey key) |
225 | { |
226 | + std::cout << __PRETTY_FUNCTION__ << std::endl; |
227 | + |
228 | if (not d->configuration.player_store->has_player_for_key(key)) |
229 | { |
230 | cerr << "Could not find Player by key: " << key << endl; |
231 | |
232 | === modified file 'src/core/media/service_implementation.h' |
233 | --- src/core/media/service_implementation.h 2015-04-16 15:19:35 +0000 |
234 | +++ src/core/media/service_implementation.h 2015-10-14 20:54:59 +0000 |
235 | @@ -49,6 +49,7 @@ |
236 | void destroy_session(const std::string&, const Player::Configuration&); |
237 | std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&); |
238 | std::shared_ptr<Player> resume_session(Player::PlayerKey key); |
239 | + void set_current_player(Player::PlayerKey key); |
240 | void pause_other_sessions(Player::PlayerKey key); |
241 | |
242 | private: |
243 | |
244 | === modified file 'src/core/media/service_skeleton.cpp' |
245 | --- src/core/media/service_skeleton.cpp 2015-08-11 16:10:14 +0000 |
246 | +++ src/core/media/service_skeleton.cpp 2015-10-14 20:54:59 +0000 |
247 | @@ -59,7 +59,7 @@ |
248 | object(impl->access_service()->add_object_for_path( |
249 | dbus::traits::Service<media::Service>::object_path())), |
250 | configuration(config), |
251 | - exported(impl->access_bus(), config.cover_art_resolver) |
252 | + exported(impl->access_bus(), config.cover_art_resolver, impl) |
253 | { |
254 | object->install_method_handler<mpris::Service::CreateSession>( |
255 | std::bind( |
256 | @@ -91,6 +91,11 @@ |
257 | &Private::handle_resume_session, |
258 | this, |
259 | std::placeholders::_1)); |
260 | + object->install_method_handler<mpris::Service::SetCurrentPlayer>( |
261 | + std::bind( |
262 | + &Private::handle_set_current_player, |
263 | + this, |
264 | + std::placeholders::_1)); |
265 | object->install_method_handler<mpris::Service::PauseOtherSessions>( |
266 | std::bind( |
267 | &Private::handle_pause_other_sessions, |
268 | @@ -102,7 +107,7 @@ |
269 | { |
270 | static unsigned int session_counter = 0; |
271 | |
272 | - unsigned int current_session = session_counter++; |
273 | + const unsigned int current_session = session_counter++; |
274 | boost::uuids::uuid uuid = gen(); |
275 | |
276 | std::stringstream ss; |
277 | @@ -134,7 +139,8 @@ |
278 | configuration.player_store->add_player_for_key(key, impl->create_session(config)); |
279 | uuid_player_map.insert(std::make_pair(uuid, key)); |
280 | |
281 | - request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, key, msg](const media::apparmor::ubuntu::Context& context) |
282 | + request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), |
283 | + [this, key, msg](const media::apparmor::ubuntu::Context& context) |
284 | { |
285 | fprintf(stderr, "%s():%d -- app_name='%s', attached\n", __func__, __LINE__, context.str().c_str()); |
286 | player_owner_map.insert(std::make_pair(key, std::make_tuple(context.str(), true, msg->sender()))); |
287 | @@ -213,7 +219,8 @@ |
288 | ss << "/core/ubuntu/media/Service/sessions/" << key; |
289 | dbus::types::ObjectPath op{ss.str()}; |
290 | |
291 | - request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, msg, key, op](const media::apparmor::ubuntu::Context& context) |
292 | + request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), |
293 | + [this, msg, key, op](const media::apparmor::ubuntu::Context& context) |
294 | { |
295 | auto info = player_owner_map.at(key); |
296 | fprintf(stderr, "%s():%d -- reattach app_name='%s', info='%s', '%s'\n", __func__, __LINE__, context.str().c_str(), std::get<0>(info).c_str(), std::get<2>(info).c_str()); |
297 | @@ -224,6 +231,12 @@ |
298 | // Signal player reconnection |
299 | auto player = configuration.player_store->player_for_key(key); |
300 | player->reconnect(); |
301 | + // We only care to allow the MPRIS controls to apply to multimedia player (i.e. audio, video) |
302 | + if (player->audio_stream_role() == media::Player::AudioStreamRole::multimedia) |
303 | + { |
304 | + std::cout << "Setting current_player" << std::endl; |
305 | + exported.set_current_player(player); |
306 | + } |
307 | |
308 | auto reply = dbus::Message::make_method_return(msg); |
309 | reply->writer() << op; |
310 | @@ -280,7 +293,8 @@ |
311 | // the session is no longer usable. |
312 | uuid_player_map.erase(uuid); |
313 | |
314 | - request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, msg, key](const media::apparmor::ubuntu::Context& context) |
315 | + request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), |
316 | + [this, msg, key](const media::apparmor::ubuntu::Context& context) |
317 | { |
318 | auto info = player_owner_map.at(key); |
319 | fprintf(stderr, "%s():%d -- Destroying app_name='%s', info='%s', '%s'\n", __func__, __LINE__, context.str().c_str(), std::get<0>(info).c_str(), std::get<2>(info).c_str()); |
320 | @@ -424,9 +438,18 @@ |
321 | } |
322 | } |
323 | |
324 | + void handle_set_current_player(const core::dbus::Message::Ptr& msg) |
325 | + { |
326 | + Player::PlayerKey key; |
327 | + msg->reader() >> key; |
328 | + impl->set_current_player(key); |
329 | + |
330 | + auto reply = dbus::Message::make_method_return(msg); |
331 | + impl->access_bus()->send(reply); |
332 | + } |
333 | + |
334 | void handle_pause_other_sessions(const core::dbus::Message::Ptr& msg) |
335 | { |
336 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
337 | Player::PlayerKey key; |
338 | msg->reader() >> key; |
339 | impl->pause_other_sessions(key); |
340 | @@ -469,45 +492,33 @@ |
341 | { |
342 | mpris::Player::Skeleton::Configuration::Defaults defaults; |
343 | |
344 | - // Disabled as track list is not fully implemented yet. |
345 | - defaults.can_go_next = false; |
346 | - // Disabled as track list is not fully implemented yet. |
347 | - defaults.can_go_previous = false; |
348 | - |
349 | return defaults; |
350 | } |
351 | |
352 | - static std::string service_name() |
353 | - { |
354 | - static const bool export_to_indicator_sound_via_mpris |
355 | - { |
356 | - core::posix::this_process::env::get("UBUNTU_MEDIA_HUB_EXPORT_TO_INDICATOR_VIA_MPRIS", "0") == "1" |
357 | - }; |
358 | - |
359 | - return export_to_indicator_sound_via_mpris ? "org.mpris.MediaPlayer2.MediaHub" : |
360 | - "hidden.org.mpris.MediaPlayer2.MediaHub"; |
361 | - } |
362 | - |
363 | - explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver) |
364 | + explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver, |
365 | + media::ServiceSkeleton* impl) |
366 | : bus{bus}, |
367 | - service{dbus::Service::add_service(bus, service_name())}, |
368 | + /* Export MediaHub service interface on dbus */ |
369 | + service{dbus::Service::add_service(bus, "org.mpris.MediaPlayer2.MediaHub")}, |
370 | object{service->add_object_for_path(dbus::types::ObjectPath{"/org/mpris/MediaPlayer2"})}, |
371 | media_player{mpris::MediaPlayer2::Skeleton::Configuration{bus, object, media_player_defaults()}}, |
372 | player{mpris::Player::Skeleton::Configuration{bus, object, player_defaults()}}, |
373 | playlists{mpris::Playlists::Skeleton::Configuration{bus, object, mpris::Playlists::Skeleton::Configuration::Defaults{}}}, |
374 | - cover_art_resolver{cover_art_resolver} |
375 | + cover_art_resolver{cover_art_resolver}, |
376 | + impl{impl} |
377 | { |
378 | object->install_method_handler<core::dbus::interfaces::Properties::GetAll>([this](const core::dbus::Message::Ptr& msg) |
379 | { |
380 | // Extract the interface |
381 | - std::string itf; msg->reader() >> itf; |
382 | + std::string interface; |
383 | + msg->reader() >> interface; |
384 | core::dbus::Message::Ptr reply = core::dbus::Message::make_method_return(msg); |
385 | |
386 | - if (itf == mpris::Player::name()) |
387 | + if (interface == mpris::Player::name()) |
388 | reply->writer() << player.get_all_properties(); |
389 | - else if (itf == mpris::MediaPlayer2::name()) |
390 | + else if (interface == mpris::MediaPlayer2::name()) |
391 | reply->writer() << media_player.get_all_properties(); |
392 | - else if (itf == mpris::Playlists::name()) |
393 | + else if (interface == mpris::Playlists::name()) |
394 | reply->writer() << playlists.get_all_properties(); |
395 | |
396 | Exported::bus->send(reply); |
397 | @@ -516,9 +527,9 @@ |
398 | // Setup method handlers for mpris::Player methods. |
399 | auto next = [this](const core::dbus::Message::Ptr& msg) |
400 | { |
401 | - auto sp = current_player.lock(); |
402 | + const auto sp = current_player.lock(); |
403 | |
404 | - if (sp) |
405 | + if (is_multimedia_role()) |
406 | sp->next(); |
407 | |
408 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
409 | @@ -527,9 +538,9 @@ |
410 | |
411 | auto previous = [this](const core::dbus::Message::Ptr& msg) |
412 | { |
413 | - auto sp = current_player.lock(); |
414 | + const auto sp = current_player.lock(); |
415 | |
416 | - if (sp) |
417 | + if (is_multimedia_role()) |
418 | sp->previous(); |
419 | |
420 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
421 | @@ -538,9 +549,9 @@ |
422 | |
423 | auto pause = [this](const core::dbus::Message::Ptr& msg) |
424 | { |
425 | - auto sp = current_player.lock(); |
426 | + const auto sp = current_player.lock(); |
427 | |
428 | - if (sp) |
429 | + if (is_multimedia_role() and sp->can_pause()) |
430 | sp->pause(); |
431 | |
432 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
433 | @@ -549,36 +560,52 @@ |
434 | |
435 | auto stop = [this](const core::dbus::Message::Ptr& msg) |
436 | { |
437 | - auto sp = current_player.lock(); |
438 | + const auto sp = current_player.lock(); |
439 | |
440 | - if (sp) |
441 | + if (is_multimedia_role()) |
442 | sp->stop(); |
443 | |
444 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
445 | }; |
446 | object->install_method_handler<mpris::Player::Stop>(stop); |
447 | |
448 | - auto play = [this](const core::dbus::Message::Ptr& msg) |
449 | + auto play = [this, impl](const core::dbus::Message::Ptr& msg) |
450 | { |
451 | - auto sp = current_player.lock(); |
452 | - |
453 | - if (sp) |
454 | + const auto sp = current_player.lock(); |
455 | + |
456 | + if (is_multimedia_role() and sp->can_play()) |
457 | + { |
458 | + // Make sure other player sessions that are already playing |
459 | + // are paused before triggering new player (sp) to play |
460 | + if (impl) |
461 | + impl->pause_other_sessions(sp->key()); |
462 | + |
463 | sp->play(); |
464 | + } |
465 | |
466 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
467 | }; |
468 | object->install_method_handler<mpris::Player::Play>(play); |
469 | |
470 | - auto play_pause = [this](const core::dbus::Message::Ptr& msg) |
471 | + auto play_pause = [this, impl](const core::dbus::Message::Ptr& msg) |
472 | { |
473 | - auto sp = current_player.lock(); |
474 | + const auto sp = current_player.lock(); |
475 | |
476 | - if (sp) |
477 | + if (is_multimedia_role()) |
478 | { |
479 | - if (sp->playback_status() == media::Player::PlaybackStatus::playing) |
480 | + if (sp->playback_status() == media::Player::PlaybackStatus::playing |
481 | + and sp->can_pause()) |
482 | sp->pause(); |
483 | - else if (sp->playback_status() != media::Player::PlaybackStatus::null) |
484 | + else if (sp->playback_status() != media::Player::PlaybackStatus::null |
485 | + and sp->can_play()) |
486 | + { |
487 | + // Make sure other player sessions that are already playing |
488 | + // are paused before triggering new player (sp) to play |
489 | + if (impl) |
490 | + impl->pause_other_sessions(sp->key()); |
491 | + |
492 | sp->play(); |
493 | + } |
494 | } |
495 | |
496 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
497 | @@ -586,15 +613,21 @@ |
498 | object->install_method_handler<mpris::Player::PlayPause>(play_pause); |
499 | } |
500 | |
501 | + inline bool is_multimedia_role() |
502 | + { |
503 | + const auto sp = current_player.lock(); |
504 | + |
505 | + return (sp ? sp->audio_stream_role() == media::Player::AudioStreamRole::multimedia : false); |
506 | + } |
507 | + |
508 | void set_current_player(const std::shared_ptr<media::Player>& cp) |
509 | { |
510 | - unset_current_player(); |
511 | - |
512 | + std::cout << "*** " << __PRETTY_FUNCTION__ << std::endl; |
513 | // We will not keep the object alive. |
514 | current_player = cp; |
515 | |
516 | // And announce that we can be controlled again. |
517 | - player.properties.can_control->set(false); |
518 | + player.properties.can_control->set(true); |
519 | |
520 | // We wire up player state changes |
521 | connections.seeked_to = cp->seeked_to().connect([this](std::uint64_t position) |
522 | @@ -612,68 +645,103 @@ |
523 | player.properties.position->set(position); |
524 | }); |
525 | |
526 | - connections.playback_status_changed = cp->playback_status().changed().connect([this](core::ubuntu::media::Player::PlaybackStatus status) |
527 | + connections.playback_status_changed = cp->playback_status().changed().connect( |
528 | + [this](core::ubuntu::media::Player::PlaybackStatus status) |
529 | { |
530 | player.properties.playback_status->set(mpris::Player::PlaybackStatus::from(status)); |
531 | }); |
532 | |
533 | - connections.loop_status_changed = cp->loop_status().changed().connect([this](core::ubuntu::media::Player::LoopStatus status) |
534 | + connections.loop_status_changed = cp->loop_status().changed().connect( |
535 | + [this](core::ubuntu::media::Player::LoopStatus status) |
536 | { |
537 | player.properties.loop_status->set(mpris::Player::LoopStatus::from(status)); |
538 | }); |
539 | |
540 | - connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect([this](const core::ubuntu::media::Track::MetaData& md) |
541 | - { |
542 | - mpris::Player::Dictionary dict; |
543 | - |
544 | - bool has_title = md.count(xesam::Title::name) > 0; |
545 | - bool has_album_name = md.count(xesam::Album::name) > 0; |
546 | - bool has_artist_name = md.count(xesam::Artist::name) > 0; |
547 | - |
548 | - if (has_title) |
549 | - dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name)); |
550 | - if (has_album_name) |
551 | - dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name)); |
552 | - if (has_artist_name) |
553 | - dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name)); |
554 | - |
555 | - dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode( |
556 | - cover_art_resolver( |
557 | - has_title ? md.get(xesam::Title::name) : "", |
558 | - has_album_name ? md.get(xesam::Album::name) : "", |
559 | - has_artist_name ? md.get(xesam::Artist::name) : "")); |
560 | - |
561 | - mpris::Player::Dictionary wrap; |
562 | - wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict); |
563 | - |
564 | - player.signals.properties_changed->emit( |
565 | - std::make_tuple( |
566 | - dbus::traits::Service<mpris::Player::Properties::Metadata::Interface>::interface_name(), |
567 | - wrap, |
568 | - std::vector<std::string>())); |
569 | - }); |
570 | + connections.can_play_changed = cp->can_play().changed().connect( |
571 | + [this](bool can_play) |
572 | + { |
573 | + player.properties.can_play->set(can_play); |
574 | + }); |
575 | + |
576 | + connections.can_pause_changed = cp->can_pause().changed().connect( |
577 | + [this](bool can_pause) |
578 | + { |
579 | + player.properties.can_pause->set(can_pause); |
580 | + }); |
581 | + |
582 | + connections.can_go_previous_changed = cp->can_go_previous().changed().connect( |
583 | + [this](bool can_go_previous) |
584 | + { |
585 | + player.properties.can_go_previous->set(can_go_previous); |
586 | + }); |
587 | + |
588 | + connections.can_go_next_changed = cp->can_go_next().changed().connect( |
589 | + [this](bool can_go_next) |
590 | + { |
591 | + player.properties.can_go_next->set(can_go_next); |
592 | + }); |
593 | + |
594 | + // Sync property values between session and player mpris::Player instances |
595 | + // TODO Getters from media::Player actually return values from a |
596 | + // mpris::Player::Skeleton instance different from "player". Each of them use |
597 | + // different DBus object paths, /core/ubuntu/media/Service/sessions/<n> |
598 | + // and /org/mpris/MediaPlayer2 (this is the one enforced by the MPRIS spec). |
599 | + // Discuss why this is needed with tvoss. |
600 | + player.properties.duration->set(cp->duration().get()); |
601 | + player.properties.position->set(cp->position().get()); |
602 | + player.properties.playback_status->set(mpris::Player::PlaybackStatus::from( |
603 | + cp->playback_status().get())); |
604 | + player.properties.loop_status->set(mpris::Player::LoopStatus::from( |
605 | + cp->loop_status().get())); |
606 | + player.properties.can_play->set(cp->can_play().get()); |
607 | + player.properties.can_pause->set(cp->can_pause().get()); |
608 | + player.properties.can_go_previous->set(cp->can_go_previous().get()); |
609 | + player.properties.can_go_next->set(cp->can_go_next().get()); |
610 | + |
611 | +#if 0 |
612 | + // TODO cover_art_resolver() is not implemented yet |
613 | + connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect( |
614 | + [this](const core::ubuntu::media::Track::MetaData& md) |
615 | + { |
616 | + mpris::Player::Dictionary dict; |
617 | + |
618 | + bool has_title = md.count(xesam::Title::name) > 0; |
619 | + bool has_album_name = md.count(xesam::Album::name) > 0; |
620 | + bool has_artist_name = md.count(xesam::Artist::name) > 0; |
621 | + |
622 | + if (has_title) |
623 | + dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name)); |
624 | + if (has_album_name) |
625 | + dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name)); |
626 | + if (has_artist_name) |
627 | + dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name)); |
628 | + |
629 | + dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode( |
630 | + cover_art_resolver( |
631 | + has_title ? md.get(xesam::Title::name) : "", |
632 | + has_album_name ? md.get(xesam::Album::name) : "", |
633 | + has_artist_name ? md.get(xesam::Artist::name) : "")); |
634 | + |
635 | + mpris::Player::Dictionary wrap; |
636 | + wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict); |
637 | + |
638 | + player.signals.properties_changed->emit( |
639 | + std::make_tuple( |
640 | + dbus::traits::Service< |
641 | + mpris::Player::Properties::Metadata::Interface> |
642 | + ::interface_name(), |
643 | + wrap, |
644 | + std::vector<std::string>())); |
645 | + }); |
646 | +#endif |
647 | } |
648 | |
649 | - void unset_current_player() |
650 | + void reset_current_player() |
651 | { |
652 | + std::cout << __PRETTY_FUNCTION__ << std::endl; |
653 | + // And announce that we can no longer be controlled. |
654 | + player.properties.can_control->set(false); |
655 | current_player.reset(); |
656 | - |
657 | - // We disconnect all previous event connections. |
658 | - connections.seeked_to.disconnect(); |
659 | - connections.duration_changed.disconnect(); |
660 | - connections.position_changed.disconnect(); |
661 | - connections.playback_status_changed.disconnect(); |
662 | - connections.loop_status_changed.disconnect(); |
663 | - connections.meta_data_changed.disconnect(); |
664 | - |
665 | - // And announce that we cannot be controlled anymore. |
666 | - player.properties.can_control->set(false); |
667 | - } |
668 | - |
669 | - void unset_if_current(const std::shared_ptr<media::Player>& cp) |
670 | - { |
671 | - if (cp == current_player.lock()) |
672 | - unset_current_player(); |
673 | } |
674 | |
675 | dbus::Bus::Ptr bus; |
676 | @@ -688,6 +756,9 @@ |
677 | media::CoverArtResolver cover_art_resolver; |
678 | // The actual player instance. |
679 | std::weak_ptr<media::Player> current_player; |
680 | + |
681 | + media::ServiceSkeleton* impl; |
682 | + |
683 | // We track event connections. |
684 | struct |
685 | { |
686 | @@ -711,6 +782,22 @@ |
687 | { |
688 | the_empty_signal.connect([](){}) |
689 | }; |
690 | + core::Connection can_play_changed |
691 | + { |
692 | + the_empty_signal.connect([](){}) |
693 | + }; |
694 | + core::Connection can_pause_changed |
695 | + { |
696 | + the_empty_signal.connect([](){}) |
697 | + }; |
698 | + core::Connection can_go_previous_changed |
699 | + { |
700 | + the_empty_signal.connect([](){}) |
701 | + }; |
702 | + core::Connection can_go_next_changed |
703 | + { |
704 | + the_empty_signal.connect([](){}) |
705 | + }; |
706 | core::Connection meta_data_changed |
707 | { |
708 | the_empty_signal.connect([](){}) |
709 | @@ -759,6 +846,14 @@ |
710 | return d->configuration.impl->resume_session(key); |
711 | } |
712 | |
713 | +void media::ServiceSkeleton::set_current_player(media::Player::PlayerKey key) |
714 | +{ |
715 | + const auto player = d->configuration.player_store->player_for_key(key); |
716 | + // We only care to allow the MPRIS controls to apply to multimedia player (i.e. audio, video) |
717 | + if (player->audio_stream_role() == media::Player::AudioStreamRole::multimedia) |
718 | + d->exported.set_current_player(player); |
719 | +} |
720 | + |
721 | void media::ServiceSkeleton::pause_other_sessions(media::Player::PlayerKey key) |
722 | { |
723 | d->configuration.impl->pause_other_sessions(key); |
724 | |
725 | === modified file 'src/core/media/service_skeleton.h' |
726 | --- src/core/media/service_skeleton.h 2015-04-16 15:19:35 +0000 |
727 | +++ src/core/media/service_skeleton.h 2015-10-14 20:54:59 +0000 |
728 | @@ -59,6 +59,7 @@ |
729 | void destroy_session(const std::string&, const media::Player::Configuration&); |
730 | std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&); |
731 | std::shared_ptr<Player> resume_session(Player::PlayerKey); |
732 | + void set_current_player(Player::PlayerKey key); |
733 | void pause_other_sessions(Player::PlayerKey key); |
734 | |
735 | void run(); |
736 | |
737 | === modified file 'src/core/media/service_stub.cpp' |
738 | --- src/core/media/service_stub.cpp 2015-04-17 16:44:30 +0000 |
739 | +++ src/core/media/service_stub.cpp 2015-10-14 20:54:59 +0000 |
740 | @@ -139,9 +139,17 @@ |
741 | }); |
742 | } |
743 | |
744 | +void media::ServiceStub::set_current_player(Player::PlayerKey key) |
745 | +{ |
746 | + auto op = d->object->invoke_method_synchronously<mpris::Service::SetCurrentPlayer, |
747 | + void>(key); |
748 | + |
749 | + if (op.is_error()) |
750 | + throw std::runtime_error("Problem setting current player: " + op.error()); |
751 | +} |
752 | + |
753 | void media::ServiceStub::pause_other_sessions(media::Player::PlayerKey key) |
754 | { |
755 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
756 | auto op = d->object->invoke_method_synchronously<mpris::Service::PauseOtherSessions, |
757 | void>(key); |
758 | |
759 | |
760 | === modified file 'src/core/media/service_stub.h' |
761 | --- src/core/media/service_stub.h 2015-04-10 16:13:55 +0000 |
762 | +++ src/core/media/service_stub.h 2015-10-14 20:54:59 +0000 |
763 | @@ -45,6 +45,7 @@ |
764 | void destroy_session(const std::string& uuid, const Player::Configuration&); |
765 | std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&); |
766 | std::shared_ptr<Player> resume_session(Player::PlayerKey key); |
767 | + void set_current_player(Player::PlayerKey key); |
768 | void pause_other_sessions(Player::PlayerKey key); |
769 | |
770 | private: |
771 | |
772 | === modified file 'src/core/media/track_list_implementation.cpp' |
773 | --- src/core/media/track_list_implementation.cpp 2015-08-11 15:25:50 +0000 |
774 | +++ src/core/media/track_list_implementation.cpp 2015-10-14 20:54:59 +0000 |
775 | @@ -215,20 +215,26 @@ |
776 | |
777 | void media::TrackListImplementation::reset() |
778 | { |
779 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
780 | - |
781 | // Make sure playback stops |
782 | on_end_of_tracklist()(); |
783 | + // And make sure there is no "current" track |
784 | + media::TrackListSkeleton::reset(); |
785 | |
786 | auto result = tracks().update([this](TrackList::Container& container) |
787 | { |
788 | - container.clear(); |
789 | + TrackList::Container ids = container; |
790 | + |
791 | + for (auto it = container.begin(); it != container.end(); ) { |
792 | + auto id = *it; |
793 | + it = container.erase(it); |
794 | + on_track_removed()(id); |
795 | + } |
796 | + |
797 | container.resize(0); |
798 | d->track_counter = 0; |
799 | + |
800 | return true; |
801 | }); |
802 | |
803 | - media::TrackListSkeleton::reset(); |
804 | - |
805 | (void) result; |
806 | } |
807 | |
808 | === modified file 'src/core/media/track_list_skeleton.cpp' |
809 | --- src/core/media/track_list_skeleton.cpp 2015-08-11 15:25:50 +0000 |
810 | +++ src/core/media/track_list_skeleton.cpp 2015-10-14 20:54:59 +0000 |
811 | @@ -241,16 +241,31 @@ |
812 | |
813 | bool media::TrackListSkeleton::has_next() |
814 | { |
815 | - if (tracks().get().empty()) |
816 | + const auto n_tracks = tracks().get().size(); |
817 | + |
818 | + if (n_tracks == 0) |
819 | return false; |
820 | |
821 | + // TODO Using current_iterator() makes media-hub crash later. Logic for |
822 | + // handling the iterators must be reviewed. As a minimum updates to the |
823 | + // track list should update current_track instead of the list being sneakly |
824 | + // changed in player_implementation.cpp. |
825 | + // To avoid the crash we consider that current_track will be eventually |
826 | + // initialized to the first track when current_iterator() gets called. |
827 | + if (d->current_track == d->empty_iterator) { |
828 | + if (n_tracks < 2) |
829 | + return false; |
830 | + else |
831 | + return true; |
832 | + } |
833 | + |
834 | const auto next_track = std::next(current_iterator()); |
835 | return !is_last_track(next_track); |
836 | } |
837 | |
838 | bool media::TrackListSkeleton::has_previous() |
839 | { |
840 | - if (tracks().get().empty()) |
841 | + if (tracks().get().empty() || d->current_track == d->empty_iterator) |
842 | return false; |
843 | |
844 | // If we are looping over the entire list, then there is always a previous track |
845 | @@ -273,8 +288,11 @@ |
846 | media::Track::Id media::TrackListSkeleton::next() |
847 | { |
848 | std::cout << __PRETTY_FUNCTION__ << std::endl; |
849 | - if (tracks().get().empty()) |
850 | - return *(d->empty_iterator); |
851 | + if (tracks().get().empty()) { |
852 | + // TODO Change ServiceSkeleton to return with error from DBus call |
853 | + std::cerr << "ERROR: no tracks, cannot go to next" << std::endl; |
854 | + return media::Track::Id{}; |
855 | + } |
856 | |
857 | const auto next_track = std::next(current_iterator()); |
858 | bool do_go_to_next_track = false; |
859 | @@ -328,8 +346,11 @@ |
860 | media::Track::Id media::TrackListSkeleton::previous() |
861 | { |
862 | std::cout << __PRETTY_FUNCTION__ << std::endl; |
863 | - if (tracks().get().empty()) |
864 | - return *(d->empty_iterator); |
865 | + if (tracks().get().empty()) { |
866 | + // TODO Change ServiceSkeleton to return with error from DBus call |
867 | + std::cerr << "ERROR: no tracks, cannot go to previous" << std::endl; |
868 | + return media::Track::Id{}; |
869 | + } |
870 | |
871 | bool do_go_to_previous_track = false; |
872 | |
873 | @@ -521,7 +542,6 @@ |
874 | |
875 | void media::TrackListSkeleton::reset() |
876 | { |
877 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
878 | d->current_track = d->empty_iterator; |
879 | } |
880 | |
881 | |
882 | === modified file 'src/core/media/track_list_skeleton.h' |
883 | --- src/core/media/track_list_skeleton.h 2015-07-27 22:15:33 +0000 |
884 | +++ src/core/media/track_list_skeleton.h 2015-10-14 20:54:59 +0000 |
885 | @@ -51,10 +51,12 @@ |
886 | const core::Property<Container>& tracks() const; |
887 | |
888 | const core::Signal<ContainerTrackIdTuple>& on_track_list_replaced() const; |
889 | + core::Signal<ContainerTrackIdTuple>& on_track_list_replaced(); |
890 | const core::Signal<Track::Id>& on_track_added() const; |
891 | core::Signal<Track::Id>& on_track_added(); |
892 | const core::Signal<Track::Id>& on_track_removed() const; |
893 | const core::Signal<Track::Id>& on_track_changed() const; |
894 | + core::Signal<Track::Id>& on_track_changed(); |
895 | const core::Signal<std::pair<Track::Id, bool>>& on_go_to_track() const; |
896 | core::Signal<std::pair<Track::Id, bool>>& on_go_to_track(); |
897 | const core::Signal<void>& on_end_of_tracklist() const; |
898 | @@ -77,9 +79,6 @@ |
899 | |
900 | core::Property<bool>& can_edit_tracks(); |
901 | |
902 | - core::Signal<ContainerTrackIdTuple>& on_track_list_replaced(); |
903 | - core::Signal<Track::Id>& on_track_changed(); |
904 | - |
905 | void reset(); |
906 | |
907 | private: |
Bugs created to track the "TODO" comments:
https:/ /bugs.launchpad .net/ubuntu/ +source/ media-hub/ +bug/1498962 /bugs.launchpad .net/ubuntu/ +source/ media-hub/ +bug/1498961
https:/
Re: the trace in reset_current_ player( ) I prefer to keep it so it is symmetrical with the one in set_current_ player( ). These traces are not printed very frequently and are quite useful for debugging as they happen in kind of critical moments.