Merge lp:~phablet-team/media-hub/wily-sync into lp:media-hub
- wily-sync
- Merge into trunk
Proposed by
Alfonso Sanchez-Beato
Status: | Merged |
---|---|
Approved by: | Jim Hodapp |
Approved revision: | 158 |
Merged at revision: | 158 |
Proposed branch: | lp:~phablet-team/media-hub/wily-sync |
Merge into: | lp:media-hub |
Diff against target: |
1082 lines (+428/-137) 19 files modified
CMakeLists.txt (+1/-1) README (+9/-0) debian/changelog (+22/-0) include/core/media/service.h (+3/-0) src/core/media/gstreamer/meta_data_extractor.h (+37/-5) src/core/media/gstreamer/playbin.cpp (+30/-7) src/core/media/gstreamer/playbin.h (+1/-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/wily-sync |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jim Hodapp (community) | code | Approve | |
Review via email: mp+274676@code.launchpad.net |
Commit message
Description of the change
[ Simon Fels ]
* Prevent us from leaking file descriptors and memory due to not
releasing gstreamer elements correctly.
[ Alfonso Sanchez-Beato ]
* Avoid getting additional references for audio_sink, which were not
being freed. Also, remove unneeded unrefs that must be performed in
gstreamer instead.
* Emit CanPlay, CanPause, and CanGo[Next|
path
[ Jim Hodapp ]
* Make sure the correct player is set as the current player controled by
MPRIS.
* Improved the can_go_next() and can_go_previous() logic to always
return true if the loop_status is currently set to loop over the
entire playlist.
To post a comment you must log in.
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-09-01 08:30:08 +0000 |
3 | +++ CMakeLists.txt 2015-10-16 08:36:42 +0000 |
4 | @@ -3,7 +3,7 @@ |
5 | project(ubuntu-media-hub) |
6 | |
7 | set(UBUNTU_MEDIA_HUB_VERSION_MAJOR 4) |
8 | -set(UBUNTU_MEDIA_HUB_VERSION_MINOR 0) |
9 | +set(UBUNTU_MEDIA_HUB_VERSION_MINOR 1) |
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-16 08:36:42 +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-09-07 10:26:04 +0000 |
33 | +++ debian/changelog 2015-10-16 08:36:42 +0000 |
34 | @@ -1,3 +1,25 @@ |
35 | +media-hub (4.1.0+15.10.20151016-0ubuntu1) UNRELEASED; urgency=medium |
36 | + |
37 | + [ Simon Fels ] |
38 | + * Prevent us from leaking file descriptors and memory due to not releasing |
39 | + gstreamer elements correctly. |
40 | + |
41 | + [ Alfonso Sanchez-Beato ] |
42 | + * Avoid getting additional references for audio_sink, which were not |
43 | + being freed. Also, remove unneeded unrefs that must be performed in |
44 | + gstreamer instead. |
45 | + * Emit CanPlay, CanPause, and CanGo[Next|Previous] in the mpris object path |
46 | + |
47 | + [ Jim Hodapp ] |
48 | + * Make sure the correct player is set as the current player controled by |
49 | + MPRIS. |
50 | + * Improved the can_go_next() and can_go_previous() logic to always return |
51 | + true if the loop_status is currently set to loop over the entire |
52 | + playlist. |
53 | + |
54 | + |
55 | + -- Alfonso Sanchez-Beato (email Canonical) <alfonso.sanchez-beato@canonical.com> Fri, 16 Oct 2015 10:28:38 +0200 |
56 | + |
57 | media-hub (4.0.0+15.10.20150907-0ubuntu1) wily; urgency=medium |
58 | |
59 | * Gather information about missing codecs and selected streams from |
60 | |
61 | === modified file 'include/core/media/service.h' |
62 | --- include/core/media/service.h 2015-04-14 02:43:56 +0000 |
63 | +++ include/core/media/service.h 2015-10-16 08:36:42 +0000 |
64 | @@ -60,6 +60,9 @@ |
65 | /** @brief Resumes a fixed-name session directly by player key. */ |
66 | virtual std::shared_ptr<Player> resume_session(Player::PlayerKey) = 0; |
67 | |
68 | + /** @brief Sets the current player that the MPRIS interface will control */ |
69 | + virtual void set_current_player(Player::PlayerKey) = 0; |
70 | + |
71 | /** @brief Pauses sessions other than the supplied one. */ |
72 | virtual void pause_other_sessions(Player::PlayerKey) = 0; |
73 | |
74 | |
75 | === modified file 'src/core/media/gstreamer/meta_data_extractor.h' |
76 | --- src/core/media/gstreamer/meta_data_extractor.h 2015-04-27 23:01:48 +0000 |
77 | +++ src/core/media/gstreamer/meta_data_extractor.h 2015-10-16 08:36:42 +0000 |
78 | @@ -147,7 +147,7 @@ |
79 | MetaDataExtractor() |
80 | : pipe(gst_pipeline_new("meta_data_extractor_pipeline")), |
81 | decoder(gst_element_factory_make ("uridecodebin", NULL)), |
82 | - bus(GST_ELEMENT_BUS(pipe)) |
83 | + bus(gst_element_get_bus(pipe)) |
84 | { |
85 | gst_bin_add(GST_BIN(pipe), decoder); |
86 | |
87 | @@ -159,8 +159,40 @@ |
88 | |
89 | ~MetaDataExtractor() |
90 | { |
91 | - gst_element_set_state(pipe, GST_STATE_NULL); |
92 | - // gst_object_unref(pipe); |
93 | + set_state_and_wait(GST_STATE_NULL); |
94 | + gst_object_unref(pipe); |
95 | + } |
96 | + |
97 | + bool set_state_and_wait(GstState new_state) |
98 | + { |
99 | + static const std::chrono::nanoseconds state_change_timeout |
100 | + { |
101 | + // We choose a quite high value here as tests are run under valgrind |
102 | + // and gstreamer pipeline setup/state changes take longer in that scenario. |
103 | + // The value does not negatively impact runtime performance. |
104 | + std::chrono::milliseconds{5000} |
105 | + }; |
106 | + |
107 | + auto ret = gst_element_set_state(pipe, new_state); |
108 | + |
109 | + bool result = false; GstState current, pending; |
110 | + switch(ret) |
111 | + { |
112 | + case GST_STATE_CHANGE_FAILURE: |
113 | + result = false; break; |
114 | + case GST_STATE_CHANGE_NO_PREROLL: |
115 | + case GST_STATE_CHANGE_SUCCESS: |
116 | + result = true; break; |
117 | + case GST_STATE_CHANGE_ASYNC: |
118 | + result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state( |
119 | + pipe, |
120 | + ¤t, |
121 | + &pending, |
122 | + state_change_timeout.count()); |
123 | + break; |
124 | + } |
125 | + |
126 | + return result; |
127 | } |
128 | |
129 | core::ubuntu::media::Track::MetaData meta_data_for_track_with_uri(const core::ubuntu::media::Track::UriType& uri) |
130 | @@ -193,11 +225,11 @@ |
131 | |
132 | if (std::future_status::ready != future.wait_for(std::chrono::seconds(4))) |
133 | { |
134 | - gst_element_set_state(pipe, GST_STATE_NULL); |
135 | + set_state_and_wait(GST_STATE_NULL); |
136 | throw std::runtime_error("Problem extracting meta data for track"); |
137 | } else |
138 | { |
139 | - gst_element_set_state(pipe, GST_STATE_NULL); |
140 | + set_state_and_wait(GST_STATE_NULL); |
141 | } |
142 | |
143 | return future.get(); |
144 | |
145 | === modified file 'src/core/media/gstreamer/playbin.cpp' |
146 | --- src/core/media/gstreamer/playbin.cpp 2015-09-07 10:15:51 +0000 |
147 | +++ src/core/media/gstreamer/playbin.cpp 2015-10-16 08:36:42 +0000 |
148 | @@ -101,6 +101,7 @@ |
149 | bus{gst_element_get_bus(pipeline)}, |
150 | file_type(MEDIA_FILE_TYPE_NONE), |
151 | video_sink(nullptr), |
152 | + audio_sink(nullptr), |
153 | on_new_message_connection_async( |
154 | bus.on_new_message_async.connect( |
155 | std::bind( |
156 | @@ -142,13 +143,39 @@ |
157 | ); |
158 | } |
159 | |
160 | +// Note that we might be accessing freed memory here, so activate DEBUG_REFS |
161 | +// only for debugging |
162 | +//#define DEBUG_REFS |
163 | +#ifdef DEBUG_REFS |
164 | +static void print_refs(const gstreamer::Playbin &pb, const char *func) |
165 | +{ |
166 | + using namespace std; |
167 | + |
168 | + cout << func << endl; |
169 | + if (pb.pipeline) |
170 | + cout << "pipeline: " << GST_OBJECT_REFCOUNT(pb.pipeline) << endl; |
171 | + if (pb.video_sink) |
172 | + cout << "video_sink: " << GST_OBJECT_REFCOUNT(pb.video_sink) << endl; |
173 | + if (pb.audio_sink) |
174 | + cout << "audio_sink: " << GST_OBJECT_REFCOUNT(pb.audio_sink) << endl; |
175 | +} |
176 | +#endif |
177 | + |
178 | gstreamer::Playbin::~Playbin() |
179 | { |
180 | +#ifdef DEBUG_REFS |
181 | + print_refs(*this, "gstreamer::Playbin::~Playbin pipeline"); |
182 | +#endif |
183 | + |
184 | g_signal_handler_disconnect(pipeline, about_to_finish_handler_id); |
185 | g_signal_handler_disconnect(pipeline, source_setup_handler_id); |
186 | |
187 | if (pipeline) |
188 | gst_object_unref(pipeline); |
189 | + |
190 | +#ifdef DEBUG_REFS |
191 | + print_refs(*this, "gstreamer::Playbin::~Playbin pipeline"); |
192 | +#endif |
193 | } |
194 | |
195 | void gstreamer::Playbin::reset() |
196 | @@ -290,7 +317,7 @@ |
197 | |
198 | if (::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME") != nullptr) |
199 | { |
200 | - auto audio_sink = gst_element_factory_make ( |
201 | + audio_sink = gst_element_factory_make ( |
202 | ::getenv("CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME"), |
203 | "audio-sink"); |
204 | |
205 | @@ -374,17 +401,13 @@ |
206 | /** Sets the new audio stream role on the pulsesink in playbin */ |
207 | void gstreamer::Playbin::set_audio_stream_role(media::Player::AudioStreamRole new_audio_role) |
208 | { |
209 | - GstElement *audio_sink = NULL; |
210 | - g_object_get (pipeline, "audio-sink", &audio_sink, NULL); |
211 | - |
212 | std::string role_str("props,media.role=" + get_audio_role_str(new_audio_role)); |
213 | std::cout << "Audio stream role: " << role_str << std::endl; |
214 | |
215 | GstStructure *props = gst_structure_from_string (role_str.c_str(), NULL); |
216 | - if (audio_sink != nullptr && props != nullptr) |
217 | + if (audio_sink != nullptr && props != nullptr) { |
218 | g_object_set (audio_sink, "stream-properties", props, NULL); |
219 | - else |
220 | - { |
221 | + } else { |
222 | std::cerr << |
223 | "Warning: couldn't set audio stream role - couldn't get audio_sink from pipeline" << |
224 | std::endl; |
225 | |
226 | === modified file 'src/core/media/gstreamer/playbin.h' |
227 | --- src/core/media/gstreamer/playbin.h 2015-08-10 13:07:04 +0000 |
228 | +++ src/core/media/gstreamer/playbin.h 2015-10-16 08:36:42 +0000 |
229 | @@ -117,6 +117,7 @@ |
230 | gstreamer::Bus bus; |
231 | MediaFileType file_type; |
232 | GstElement* video_sink; |
233 | + GstElement* audio_sink; |
234 | core::Connection on_new_message_connection_async; |
235 | bool is_seeking; |
236 | mutable uint64_t previous_position; |
237 | |
238 | === modified file 'src/core/media/mpris/player.h' |
239 | --- src/core/media/mpris/player.h 2015-04-06 20:59:14 +0000 |
240 | +++ src/core/media/mpris/player.h 2015-10-16 08:36:42 +0000 |
241 | @@ -192,12 +192,12 @@ |
242 | // Default values for properties |
243 | struct Defaults |
244 | { |
245 | - Properties::CanPlay::ValueType can_play{true}; |
246 | - Properties::CanPause::ValueType can_pause{true}; |
247 | + Properties::CanPlay::ValueType can_play{false}; |
248 | + Properties::CanPause::ValueType can_pause{false}; |
249 | Properties::CanSeek::ValueType can_seek{true}; |
250 | Properties::CanControl::ValueType can_control{true}; |
251 | - Properties::CanGoNext::ValueType can_go_next{true}; |
252 | - Properties::CanGoPrevious::ValueType can_go_previous{true}; |
253 | + Properties::CanGoNext::ValueType can_go_next{false}; |
254 | + Properties::CanGoPrevious::ValueType can_go_previous{false}; |
255 | Properties::IsVideoSource::ValueType is_video_source{false}; |
256 | Properties::IsAudioSource::ValueType is_audio_source{true}; |
257 | Properties::PlaybackStatus::ValueType playback_status{PlaybackStatus::stopped}; |
258 | @@ -307,6 +307,26 @@ |
259 | { |
260 | on_property_value_changed<Properties::Shuffle>(shuffle); |
261 | }); |
262 | + |
263 | + properties.can_play->changed().connect([this](bool can_play) |
264 | + { |
265 | + on_property_value_changed<Properties::CanPlay>(can_play); |
266 | + }); |
267 | + |
268 | + properties.can_pause->changed().connect([this](bool can_pause) |
269 | + { |
270 | + on_property_value_changed<Properties::CanPause>(can_pause); |
271 | + }); |
272 | + |
273 | + properties.can_go_next->changed().connect([this](bool can_go_next) |
274 | + { |
275 | + on_property_value_changed<Properties::CanGoNext>(can_go_next); |
276 | + }); |
277 | + |
278 | + properties.can_go_previous->changed().connect([this](bool can_go_previous) |
279 | + { |
280 | + on_property_value_changed<Properties::CanGoPrevious>(can_go_previous); |
281 | + }); |
282 | } |
283 | |
284 | template<typename Property> |
285 | |
286 | === modified file 'src/core/media/mpris/service.h' |
287 | --- src/core/media/mpris/service.h 2015-04-10 16:13:55 +0000 |
288 | +++ src/core/media/mpris/service.h 2015-10-16 08:36:42 +0000 |
289 | @@ -115,6 +115,7 @@ |
290 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(DestroySession, Service, 1000) |
291 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(CreateFixedSession, Service, 1000) |
292 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(ResumeSession, Service, 1000) |
293 | + DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(SetCurrentPlayer, Service, 1000) |
294 | DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(PauseOtherSessions, Service, 1000) |
295 | }; |
296 | } |
297 | |
298 | === modified file 'src/core/media/player_implementation.cpp' |
299 | --- src/core/media/player_implementation.cpp 2015-07-27 22:15:33 +0000 |
300 | +++ src/core/media/player_implementation.cpp 2015-10-16 08:36:42 +0000 |
301 | @@ -301,6 +301,27 @@ |
302 | } |
303 | } |
304 | |
305 | + void update_mpris_properties(void) |
306 | + { |
307 | + bool has_previous = track_list->has_previous() |
308 | + or parent->Parent::loop_status() != Player::LoopStatus::none; |
309 | + bool has_next = track_list->has_next() |
310 | + or parent->Parent::loop_status() != Player::LoopStatus::none; |
311 | + auto n_tracks = track_list->tracks()->size(); |
312 | + bool has_tracks = (n_tracks > 0) ? true : false; |
313 | + |
314 | + std::cout << "Updating MPRIS TrackList properties" |
315 | + << "; Tracks: " << n_tracks |
316 | + << ", has_previous: " << has_previous |
317 | + << ", has_next: " << has_next << std::endl; |
318 | + |
319 | + // Update properties |
320 | + parent->can_play().set(has_tracks); |
321 | + parent->can_pause().set(has_tracks); |
322 | + parent->can_go_previous().set(has_previous); |
323 | + parent->can_go_next().set(has_next); |
324 | + } |
325 | + |
326 | // Our link back to our parent. |
327 | media::PlayerImplementation<Parent>* parent; |
328 | // We just store the parameters passed on construction. |
329 | @@ -327,8 +348,8 @@ |
330 | d{std::make_shared<Private>(this, config)} |
331 | { |
332 | // Initialize default values for Player interface properties |
333 | - Parent::can_play().set(true); |
334 | - Parent::can_pause().set(true); |
335 | + Parent::can_play().set(false); |
336 | + Parent::can_pause().set(false); |
337 | Parent::can_seek().set(true); |
338 | Parent::can_go_previous().set(false); |
339 | Parent::can_go_next().set(false); |
340 | @@ -376,13 +397,15 @@ |
341 | |
342 | std::function<bool()> can_go_next_getter = [this]() |
343 | { |
344 | - return d->track_list->has_next(); |
345 | + // If LoopStatus == playlist, then there is always a next track |
346 | + return d->track_list->has_next() or Parent::loop_status() != Player::LoopStatus::none; |
347 | }; |
348 | Parent::can_go_next().install(can_go_next_getter); |
349 | |
350 | std::function<bool()> can_go_previous_getter = [this]() |
351 | { |
352 | - return d->track_list->has_previous(); |
353 | + // If LoopStatus == playlist, then there is always a next previous |
354 | + return d->track_list->has_previous() or Parent::loop_status() != Player::LoopStatus::none; |
355 | }; |
356 | Parent::can_go_previous().install(can_go_previous_getter); |
357 | |
358 | @@ -520,7 +543,26 @@ |
359 | std::cout << "** Track was added, handling in PlayerImplementation" << std::endl; |
360 | if (d->track_list->tracks()->size() == 1) |
361 | d->open_first_track_from_tracklist(id); |
362 | - }); |
363 | + |
364 | + d->update_mpris_properties(); |
365 | + }); |
366 | + |
367 | + d->track_list->on_track_removed().connect([this](const media::Track::Id&) |
368 | + { |
369 | + d->update_mpris_properties(); |
370 | + }); |
371 | + |
372 | + d->track_list->on_track_changed().connect([this](const media::Track::Id&) |
373 | + { |
374 | + d->update_mpris_properties(); |
375 | + }); |
376 | + |
377 | + d->track_list->on_track_list_replaced().connect( |
378 | + [this](const media::TrackList::ContainerTrackIdTuple&) |
379 | + { |
380 | + d->update_mpris_properties(); |
381 | + } |
382 | + ); |
383 | |
384 | // Everything is setup, we now subscribe to death notifications. |
385 | std::weak_ptr<Private> wp{d}; |
386 | |
387 | === modified file 'src/core/media/service_implementation.cpp' |
388 | --- src/core/media/service_implementation.cpp 2015-04-17 16:44:30 +0000 |
389 | +++ src/core/media/service_implementation.cpp 2015-10-16 08:36:42 +0000 |
390 | @@ -246,8 +246,15 @@ |
391 | return std::shared_ptr<media::Player>(); |
392 | } |
393 | |
394 | +void media::ServiceImplementation::set_current_player(Player::PlayerKey key) |
395 | +{ |
396 | + // no impl |
397 | +} |
398 | + |
399 | void media::ServiceImplementation::pause_other_sessions(media::Player::PlayerKey key) |
400 | { |
401 | + std::cout << __PRETTY_FUNCTION__ << std::endl; |
402 | + |
403 | if (not d->configuration.player_store->has_player_for_key(key)) |
404 | { |
405 | cerr << "Could not find Player by key: " << key << endl; |
406 | |
407 | === modified file 'src/core/media/service_implementation.h' |
408 | --- src/core/media/service_implementation.h 2015-04-16 15:19:35 +0000 |
409 | +++ src/core/media/service_implementation.h 2015-10-16 08:36:42 +0000 |
410 | @@ -49,6 +49,7 @@ |
411 | void destroy_session(const std::string&, const Player::Configuration&); |
412 | std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&); |
413 | std::shared_ptr<Player> resume_session(Player::PlayerKey key); |
414 | + void set_current_player(Player::PlayerKey key); |
415 | void pause_other_sessions(Player::PlayerKey key); |
416 | |
417 | private: |
418 | |
419 | === modified file 'src/core/media/service_skeleton.cpp' |
420 | --- src/core/media/service_skeleton.cpp 2015-08-11 16:10:14 +0000 |
421 | +++ src/core/media/service_skeleton.cpp 2015-10-16 08:36:42 +0000 |
422 | @@ -59,7 +59,7 @@ |
423 | object(impl->access_service()->add_object_for_path( |
424 | dbus::traits::Service<media::Service>::object_path())), |
425 | configuration(config), |
426 | - exported(impl->access_bus(), config.cover_art_resolver) |
427 | + exported(impl->access_bus(), config.cover_art_resolver, impl) |
428 | { |
429 | object->install_method_handler<mpris::Service::CreateSession>( |
430 | std::bind( |
431 | @@ -91,6 +91,11 @@ |
432 | &Private::handle_resume_session, |
433 | this, |
434 | std::placeholders::_1)); |
435 | + object->install_method_handler<mpris::Service::SetCurrentPlayer>( |
436 | + std::bind( |
437 | + &Private::handle_set_current_player, |
438 | + this, |
439 | + std::placeholders::_1)); |
440 | object->install_method_handler<mpris::Service::PauseOtherSessions>( |
441 | std::bind( |
442 | &Private::handle_pause_other_sessions, |
443 | @@ -102,7 +107,7 @@ |
444 | { |
445 | static unsigned int session_counter = 0; |
446 | |
447 | - unsigned int current_session = session_counter++; |
448 | + const unsigned int current_session = session_counter++; |
449 | boost::uuids::uuid uuid = gen(); |
450 | |
451 | std::stringstream ss; |
452 | @@ -134,7 +139,8 @@ |
453 | configuration.player_store->add_player_for_key(key, impl->create_session(config)); |
454 | uuid_player_map.insert(std::make_pair(uuid, key)); |
455 | |
456 | - request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, key, msg](const media::apparmor::ubuntu::Context& context) |
457 | + request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), |
458 | + [this, key, msg](const media::apparmor::ubuntu::Context& context) |
459 | { |
460 | fprintf(stderr, "%s():%d -- app_name='%s', attached\n", __func__, __LINE__, context.str().c_str()); |
461 | player_owner_map.insert(std::make_pair(key, std::make_tuple(context.str(), true, msg->sender()))); |
462 | @@ -213,7 +219,8 @@ |
463 | ss << "/core/ubuntu/media/Service/sessions/" << key; |
464 | dbus::types::ObjectPath op{ss.str()}; |
465 | |
466 | - request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, msg, key, op](const media::apparmor::ubuntu::Context& context) |
467 | + request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), |
468 | + [this, msg, key, op](const media::apparmor::ubuntu::Context& context) |
469 | { |
470 | auto info = player_owner_map.at(key); |
471 | 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()); |
472 | @@ -224,6 +231,12 @@ |
473 | // Signal player reconnection |
474 | auto player = configuration.player_store->player_for_key(key); |
475 | player->reconnect(); |
476 | + // We only care to allow the MPRIS controls to apply to multimedia player (i.e. audio, video) |
477 | + if (player->audio_stream_role() == media::Player::AudioStreamRole::multimedia) |
478 | + { |
479 | + std::cout << "Setting current_player" << std::endl; |
480 | + exported.set_current_player(player); |
481 | + } |
482 | |
483 | auto reply = dbus::Message::make_method_return(msg); |
484 | reply->writer() << op; |
485 | @@ -280,7 +293,8 @@ |
486 | // the session is no longer usable. |
487 | uuid_player_map.erase(uuid); |
488 | |
489 | - request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, msg, key](const media::apparmor::ubuntu::Context& context) |
490 | + request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), |
491 | + [this, msg, key](const media::apparmor::ubuntu::Context& context) |
492 | { |
493 | auto info = player_owner_map.at(key); |
494 | 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()); |
495 | @@ -424,9 +438,18 @@ |
496 | } |
497 | } |
498 | |
499 | + void handle_set_current_player(const core::dbus::Message::Ptr& msg) |
500 | + { |
501 | + Player::PlayerKey key; |
502 | + msg->reader() >> key; |
503 | + impl->set_current_player(key); |
504 | + |
505 | + auto reply = dbus::Message::make_method_return(msg); |
506 | + impl->access_bus()->send(reply); |
507 | + } |
508 | + |
509 | void handle_pause_other_sessions(const core::dbus::Message::Ptr& msg) |
510 | { |
511 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
512 | Player::PlayerKey key; |
513 | msg->reader() >> key; |
514 | impl->pause_other_sessions(key); |
515 | @@ -469,45 +492,33 @@ |
516 | { |
517 | mpris::Player::Skeleton::Configuration::Defaults defaults; |
518 | |
519 | - // Disabled as track list is not fully implemented yet. |
520 | - defaults.can_go_next = false; |
521 | - // Disabled as track list is not fully implemented yet. |
522 | - defaults.can_go_previous = false; |
523 | - |
524 | return defaults; |
525 | } |
526 | |
527 | - static std::string service_name() |
528 | - { |
529 | - static const bool export_to_indicator_sound_via_mpris |
530 | - { |
531 | - core::posix::this_process::env::get("UBUNTU_MEDIA_HUB_EXPORT_TO_INDICATOR_VIA_MPRIS", "0") == "1" |
532 | - }; |
533 | - |
534 | - return export_to_indicator_sound_via_mpris ? "org.mpris.MediaPlayer2.MediaHub" : |
535 | - "hidden.org.mpris.MediaPlayer2.MediaHub"; |
536 | - } |
537 | - |
538 | - explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver) |
539 | + explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver, |
540 | + media::ServiceSkeleton* impl) |
541 | : bus{bus}, |
542 | - service{dbus::Service::add_service(bus, service_name())}, |
543 | + /* Export MediaHub service interface on dbus */ |
544 | + service{dbus::Service::add_service(bus, "org.mpris.MediaPlayer2.MediaHub")}, |
545 | object{service->add_object_for_path(dbus::types::ObjectPath{"/org/mpris/MediaPlayer2"})}, |
546 | media_player{mpris::MediaPlayer2::Skeleton::Configuration{bus, object, media_player_defaults()}}, |
547 | player{mpris::Player::Skeleton::Configuration{bus, object, player_defaults()}}, |
548 | playlists{mpris::Playlists::Skeleton::Configuration{bus, object, mpris::Playlists::Skeleton::Configuration::Defaults{}}}, |
549 | - cover_art_resolver{cover_art_resolver} |
550 | + cover_art_resolver{cover_art_resolver}, |
551 | + impl{impl} |
552 | { |
553 | object->install_method_handler<core::dbus::interfaces::Properties::GetAll>([this](const core::dbus::Message::Ptr& msg) |
554 | { |
555 | // Extract the interface |
556 | - std::string itf; msg->reader() >> itf; |
557 | + std::string interface; |
558 | + msg->reader() >> interface; |
559 | core::dbus::Message::Ptr reply = core::dbus::Message::make_method_return(msg); |
560 | |
561 | - if (itf == mpris::Player::name()) |
562 | + if (interface == mpris::Player::name()) |
563 | reply->writer() << player.get_all_properties(); |
564 | - else if (itf == mpris::MediaPlayer2::name()) |
565 | + else if (interface == mpris::MediaPlayer2::name()) |
566 | reply->writer() << media_player.get_all_properties(); |
567 | - else if (itf == mpris::Playlists::name()) |
568 | + else if (interface == mpris::Playlists::name()) |
569 | reply->writer() << playlists.get_all_properties(); |
570 | |
571 | Exported::bus->send(reply); |
572 | @@ -516,9 +527,9 @@ |
573 | // Setup method handlers for mpris::Player methods. |
574 | auto next = [this](const core::dbus::Message::Ptr& msg) |
575 | { |
576 | - auto sp = current_player.lock(); |
577 | + const auto sp = current_player.lock(); |
578 | |
579 | - if (sp) |
580 | + if (is_multimedia_role()) |
581 | sp->next(); |
582 | |
583 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
584 | @@ -527,9 +538,9 @@ |
585 | |
586 | auto previous = [this](const core::dbus::Message::Ptr& msg) |
587 | { |
588 | - auto sp = current_player.lock(); |
589 | + const auto sp = current_player.lock(); |
590 | |
591 | - if (sp) |
592 | + if (is_multimedia_role()) |
593 | sp->previous(); |
594 | |
595 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
596 | @@ -538,9 +549,9 @@ |
597 | |
598 | auto pause = [this](const core::dbus::Message::Ptr& msg) |
599 | { |
600 | - auto sp = current_player.lock(); |
601 | + const auto sp = current_player.lock(); |
602 | |
603 | - if (sp) |
604 | + if (is_multimedia_role() and sp->can_pause()) |
605 | sp->pause(); |
606 | |
607 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
608 | @@ -549,36 +560,52 @@ |
609 | |
610 | auto stop = [this](const core::dbus::Message::Ptr& msg) |
611 | { |
612 | - auto sp = current_player.lock(); |
613 | + const auto sp = current_player.lock(); |
614 | |
615 | - if (sp) |
616 | + if (is_multimedia_role()) |
617 | sp->stop(); |
618 | |
619 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
620 | }; |
621 | object->install_method_handler<mpris::Player::Stop>(stop); |
622 | |
623 | - auto play = [this](const core::dbus::Message::Ptr& msg) |
624 | + auto play = [this, impl](const core::dbus::Message::Ptr& msg) |
625 | { |
626 | - auto sp = current_player.lock(); |
627 | - |
628 | - if (sp) |
629 | + const auto sp = current_player.lock(); |
630 | + |
631 | + if (is_multimedia_role() and sp->can_play()) |
632 | + { |
633 | + // Make sure other player sessions that are already playing |
634 | + // are paused before triggering new player (sp) to play |
635 | + if (impl) |
636 | + impl->pause_other_sessions(sp->key()); |
637 | + |
638 | sp->play(); |
639 | + } |
640 | |
641 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
642 | }; |
643 | object->install_method_handler<mpris::Player::Play>(play); |
644 | |
645 | - auto play_pause = [this](const core::dbus::Message::Ptr& msg) |
646 | + auto play_pause = [this, impl](const core::dbus::Message::Ptr& msg) |
647 | { |
648 | - auto sp = current_player.lock(); |
649 | + const auto sp = current_player.lock(); |
650 | |
651 | - if (sp) |
652 | + if (is_multimedia_role()) |
653 | { |
654 | - if (sp->playback_status() == media::Player::PlaybackStatus::playing) |
655 | + if (sp->playback_status() == media::Player::PlaybackStatus::playing |
656 | + and sp->can_pause()) |
657 | sp->pause(); |
658 | - else if (sp->playback_status() != media::Player::PlaybackStatus::null) |
659 | + else if (sp->playback_status() != media::Player::PlaybackStatus::null |
660 | + and sp->can_play()) |
661 | + { |
662 | + // Make sure other player sessions that are already playing |
663 | + // are paused before triggering new player (sp) to play |
664 | + if (impl) |
665 | + impl->pause_other_sessions(sp->key()); |
666 | + |
667 | sp->play(); |
668 | + } |
669 | } |
670 | |
671 | Exported::bus->send(core::dbus::Message::make_method_return(msg)); |
672 | @@ -586,15 +613,21 @@ |
673 | object->install_method_handler<mpris::Player::PlayPause>(play_pause); |
674 | } |
675 | |
676 | + inline bool is_multimedia_role() |
677 | + { |
678 | + const auto sp = current_player.lock(); |
679 | + |
680 | + return (sp ? sp->audio_stream_role() == media::Player::AudioStreamRole::multimedia : false); |
681 | + } |
682 | + |
683 | void set_current_player(const std::shared_ptr<media::Player>& cp) |
684 | { |
685 | - unset_current_player(); |
686 | - |
687 | + std::cout << "*** " << __PRETTY_FUNCTION__ << std::endl; |
688 | // We will not keep the object alive. |
689 | current_player = cp; |
690 | |
691 | // And announce that we can be controlled again. |
692 | - player.properties.can_control->set(false); |
693 | + player.properties.can_control->set(true); |
694 | |
695 | // We wire up player state changes |
696 | connections.seeked_to = cp->seeked_to().connect([this](std::uint64_t position) |
697 | @@ -612,68 +645,103 @@ |
698 | player.properties.position->set(position); |
699 | }); |
700 | |
701 | - connections.playback_status_changed = cp->playback_status().changed().connect([this](core::ubuntu::media::Player::PlaybackStatus status) |
702 | + connections.playback_status_changed = cp->playback_status().changed().connect( |
703 | + [this](core::ubuntu::media::Player::PlaybackStatus status) |
704 | { |
705 | player.properties.playback_status->set(mpris::Player::PlaybackStatus::from(status)); |
706 | }); |
707 | |
708 | - connections.loop_status_changed = cp->loop_status().changed().connect([this](core::ubuntu::media::Player::LoopStatus status) |
709 | + connections.loop_status_changed = cp->loop_status().changed().connect( |
710 | + [this](core::ubuntu::media::Player::LoopStatus status) |
711 | { |
712 | player.properties.loop_status->set(mpris::Player::LoopStatus::from(status)); |
713 | }); |
714 | |
715 | - connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect([this](const core::ubuntu::media::Track::MetaData& md) |
716 | - { |
717 | - mpris::Player::Dictionary dict; |
718 | - |
719 | - bool has_title = md.count(xesam::Title::name) > 0; |
720 | - bool has_album_name = md.count(xesam::Album::name) > 0; |
721 | - bool has_artist_name = md.count(xesam::Artist::name) > 0; |
722 | - |
723 | - if (has_title) |
724 | - dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name)); |
725 | - if (has_album_name) |
726 | - dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name)); |
727 | - if (has_artist_name) |
728 | - dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name)); |
729 | - |
730 | - dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode( |
731 | - cover_art_resolver( |
732 | - has_title ? md.get(xesam::Title::name) : "", |
733 | - has_album_name ? md.get(xesam::Album::name) : "", |
734 | - has_artist_name ? md.get(xesam::Artist::name) : "")); |
735 | - |
736 | - mpris::Player::Dictionary wrap; |
737 | - wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict); |
738 | - |
739 | - player.signals.properties_changed->emit( |
740 | - std::make_tuple( |
741 | - dbus::traits::Service<mpris::Player::Properties::Metadata::Interface>::interface_name(), |
742 | - wrap, |
743 | - std::vector<std::string>())); |
744 | - }); |
745 | + connections.can_play_changed = cp->can_play().changed().connect( |
746 | + [this](bool can_play) |
747 | + { |
748 | + player.properties.can_play->set(can_play); |
749 | + }); |
750 | + |
751 | + connections.can_pause_changed = cp->can_pause().changed().connect( |
752 | + [this](bool can_pause) |
753 | + { |
754 | + player.properties.can_pause->set(can_pause); |
755 | + }); |
756 | + |
757 | + connections.can_go_previous_changed = cp->can_go_previous().changed().connect( |
758 | + [this](bool can_go_previous) |
759 | + { |
760 | + player.properties.can_go_previous->set(can_go_previous); |
761 | + }); |
762 | + |
763 | + connections.can_go_next_changed = cp->can_go_next().changed().connect( |
764 | + [this](bool can_go_next) |
765 | + { |
766 | + player.properties.can_go_next->set(can_go_next); |
767 | + }); |
768 | + |
769 | + // Sync property values between session and player mpris::Player instances |
770 | + // TODO Getters from media::Player actually return values from a |
771 | + // mpris::Player::Skeleton instance different from "player". Each of them use |
772 | + // different DBus object paths, /core/ubuntu/media/Service/sessions/<n> |
773 | + // and /org/mpris/MediaPlayer2 (this is the one enforced by the MPRIS spec). |
774 | + // Discuss why this is needed with tvoss. |
775 | + player.properties.duration->set(cp->duration().get()); |
776 | + player.properties.position->set(cp->position().get()); |
777 | + player.properties.playback_status->set(mpris::Player::PlaybackStatus::from( |
778 | + cp->playback_status().get())); |
779 | + player.properties.loop_status->set(mpris::Player::LoopStatus::from( |
780 | + cp->loop_status().get())); |
781 | + player.properties.can_play->set(cp->can_play().get()); |
782 | + player.properties.can_pause->set(cp->can_pause().get()); |
783 | + player.properties.can_go_previous->set(cp->can_go_previous().get()); |
784 | + player.properties.can_go_next->set(cp->can_go_next().get()); |
785 | + |
786 | +#if 0 |
787 | + // TODO cover_art_resolver() is not implemented yet |
788 | + connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect( |
789 | + [this](const core::ubuntu::media::Track::MetaData& md) |
790 | + { |
791 | + mpris::Player::Dictionary dict; |
792 | + |
793 | + bool has_title = md.count(xesam::Title::name) > 0; |
794 | + bool has_album_name = md.count(xesam::Album::name) > 0; |
795 | + bool has_artist_name = md.count(xesam::Artist::name) > 0; |
796 | + |
797 | + if (has_title) |
798 | + dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name)); |
799 | + if (has_album_name) |
800 | + dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name)); |
801 | + if (has_artist_name) |
802 | + dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name)); |
803 | + |
804 | + dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode( |
805 | + cover_art_resolver( |
806 | + has_title ? md.get(xesam::Title::name) : "", |
807 | + has_album_name ? md.get(xesam::Album::name) : "", |
808 | + has_artist_name ? md.get(xesam::Artist::name) : "")); |
809 | + |
810 | + mpris::Player::Dictionary wrap; |
811 | + wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict); |
812 | + |
813 | + player.signals.properties_changed->emit( |
814 | + std::make_tuple( |
815 | + dbus::traits::Service< |
816 | + mpris::Player::Properties::Metadata::Interface> |
817 | + ::interface_name(), |
818 | + wrap, |
819 | + std::vector<std::string>())); |
820 | + }); |
821 | +#endif |
822 | } |
823 | |
824 | - void unset_current_player() |
825 | + void reset_current_player() |
826 | { |
827 | + std::cout << __PRETTY_FUNCTION__ << std::endl; |
828 | + // And announce that we can no longer be controlled. |
829 | + player.properties.can_control->set(false); |
830 | current_player.reset(); |
831 | - |
832 | - // We disconnect all previous event connections. |
833 | - connections.seeked_to.disconnect(); |
834 | - connections.duration_changed.disconnect(); |
835 | - connections.position_changed.disconnect(); |
836 | - connections.playback_status_changed.disconnect(); |
837 | - connections.loop_status_changed.disconnect(); |
838 | - connections.meta_data_changed.disconnect(); |
839 | - |
840 | - // And announce that we cannot be controlled anymore. |
841 | - player.properties.can_control->set(false); |
842 | - } |
843 | - |
844 | - void unset_if_current(const std::shared_ptr<media::Player>& cp) |
845 | - { |
846 | - if (cp == current_player.lock()) |
847 | - unset_current_player(); |
848 | } |
849 | |
850 | dbus::Bus::Ptr bus; |
851 | @@ -688,6 +756,9 @@ |
852 | media::CoverArtResolver cover_art_resolver; |
853 | // The actual player instance. |
854 | std::weak_ptr<media::Player> current_player; |
855 | + |
856 | + media::ServiceSkeleton* impl; |
857 | + |
858 | // We track event connections. |
859 | struct |
860 | { |
861 | @@ -711,6 +782,22 @@ |
862 | { |
863 | the_empty_signal.connect([](){}) |
864 | }; |
865 | + core::Connection can_play_changed |
866 | + { |
867 | + the_empty_signal.connect([](){}) |
868 | + }; |
869 | + core::Connection can_pause_changed |
870 | + { |
871 | + the_empty_signal.connect([](){}) |
872 | + }; |
873 | + core::Connection can_go_previous_changed |
874 | + { |
875 | + the_empty_signal.connect([](){}) |
876 | + }; |
877 | + core::Connection can_go_next_changed |
878 | + { |
879 | + the_empty_signal.connect([](){}) |
880 | + }; |
881 | core::Connection meta_data_changed |
882 | { |
883 | the_empty_signal.connect([](){}) |
884 | @@ -759,6 +846,14 @@ |
885 | return d->configuration.impl->resume_session(key); |
886 | } |
887 | |
888 | +void media::ServiceSkeleton::set_current_player(media::Player::PlayerKey key) |
889 | +{ |
890 | + const auto player = d->configuration.player_store->player_for_key(key); |
891 | + // We only care to allow the MPRIS controls to apply to multimedia player (i.e. audio, video) |
892 | + if (player->audio_stream_role() == media::Player::AudioStreamRole::multimedia) |
893 | + d->exported.set_current_player(player); |
894 | +} |
895 | + |
896 | void media::ServiceSkeleton::pause_other_sessions(media::Player::PlayerKey key) |
897 | { |
898 | d->configuration.impl->pause_other_sessions(key); |
899 | |
900 | === modified file 'src/core/media/service_skeleton.h' |
901 | --- src/core/media/service_skeleton.h 2015-04-16 15:19:35 +0000 |
902 | +++ src/core/media/service_skeleton.h 2015-10-16 08:36:42 +0000 |
903 | @@ -59,6 +59,7 @@ |
904 | void destroy_session(const std::string&, const media::Player::Configuration&); |
905 | std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&); |
906 | std::shared_ptr<Player> resume_session(Player::PlayerKey); |
907 | + void set_current_player(Player::PlayerKey key); |
908 | void pause_other_sessions(Player::PlayerKey key); |
909 | |
910 | void run(); |
911 | |
912 | === modified file 'src/core/media/service_stub.cpp' |
913 | --- src/core/media/service_stub.cpp 2015-04-17 16:44:30 +0000 |
914 | +++ src/core/media/service_stub.cpp 2015-10-16 08:36:42 +0000 |
915 | @@ -139,9 +139,17 @@ |
916 | }); |
917 | } |
918 | |
919 | +void media::ServiceStub::set_current_player(Player::PlayerKey key) |
920 | +{ |
921 | + auto op = d->object->invoke_method_synchronously<mpris::Service::SetCurrentPlayer, |
922 | + void>(key); |
923 | + |
924 | + if (op.is_error()) |
925 | + throw std::runtime_error("Problem setting current player: " + op.error()); |
926 | +} |
927 | + |
928 | void media::ServiceStub::pause_other_sessions(media::Player::PlayerKey key) |
929 | { |
930 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
931 | auto op = d->object->invoke_method_synchronously<mpris::Service::PauseOtherSessions, |
932 | void>(key); |
933 | |
934 | |
935 | === modified file 'src/core/media/service_stub.h' |
936 | --- src/core/media/service_stub.h 2015-04-10 16:13:55 +0000 |
937 | +++ src/core/media/service_stub.h 2015-10-16 08:36:42 +0000 |
938 | @@ -45,6 +45,7 @@ |
939 | void destroy_session(const std::string& uuid, const Player::Configuration&); |
940 | std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&); |
941 | std::shared_ptr<Player> resume_session(Player::PlayerKey key); |
942 | + void set_current_player(Player::PlayerKey key); |
943 | void pause_other_sessions(Player::PlayerKey key); |
944 | |
945 | private: |
946 | |
947 | === modified file 'src/core/media/track_list_implementation.cpp' |
948 | --- src/core/media/track_list_implementation.cpp 2015-08-11 15:25:50 +0000 |
949 | +++ src/core/media/track_list_implementation.cpp 2015-10-16 08:36:42 +0000 |
950 | @@ -215,20 +215,26 @@ |
951 | |
952 | void media::TrackListImplementation::reset() |
953 | { |
954 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
955 | - |
956 | // Make sure playback stops |
957 | on_end_of_tracklist()(); |
958 | + // And make sure there is no "current" track |
959 | + media::TrackListSkeleton::reset(); |
960 | |
961 | auto result = tracks().update([this](TrackList::Container& container) |
962 | { |
963 | - container.clear(); |
964 | + TrackList::Container ids = container; |
965 | + |
966 | + for (auto it = container.begin(); it != container.end(); ) { |
967 | + auto id = *it; |
968 | + it = container.erase(it); |
969 | + on_track_removed()(id); |
970 | + } |
971 | + |
972 | container.resize(0); |
973 | d->track_counter = 0; |
974 | + |
975 | return true; |
976 | }); |
977 | |
978 | - media::TrackListSkeleton::reset(); |
979 | - |
980 | (void) result; |
981 | } |
982 | |
983 | === modified file 'src/core/media/track_list_skeleton.cpp' |
984 | --- src/core/media/track_list_skeleton.cpp 2015-08-11 15:25:50 +0000 |
985 | +++ src/core/media/track_list_skeleton.cpp 2015-10-16 08:36:42 +0000 |
986 | @@ -241,16 +241,31 @@ |
987 | |
988 | bool media::TrackListSkeleton::has_next() |
989 | { |
990 | - if (tracks().get().empty()) |
991 | + const auto n_tracks = tracks().get().size(); |
992 | + |
993 | + if (n_tracks == 0) |
994 | return false; |
995 | |
996 | + // TODO Using current_iterator() makes media-hub crash later. Logic for |
997 | + // handling the iterators must be reviewed. As a minimum updates to the |
998 | + // track list should update current_track instead of the list being sneakly |
999 | + // changed in player_implementation.cpp. |
1000 | + // To avoid the crash we consider that current_track will be eventually |
1001 | + // initialized to the first track when current_iterator() gets called. |
1002 | + if (d->current_track == d->empty_iterator) { |
1003 | + if (n_tracks < 2) |
1004 | + return false; |
1005 | + else |
1006 | + return true; |
1007 | + } |
1008 | + |
1009 | const auto next_track = std::next(current_iterator()); |
1010 | return !is_last_track(next_track); |
1011 | } |
1012 | |
1013 | bool media::TrackListSkeleton::has_previous() |
1014 | { |
1015 | - if (tracks().get().empty()) |
1016 | + if (tracks().get().empty() || d->current_track == d->empty_iterator) |
1017 | return false; |
1018 | |
1019 | // If we are looping over the entire list, then there is always a previous track |
1020 | @@ -273,8 +288,11 @@ |
1021 | media::Track::Id media::TrackListSkeleton::next() |
1022 | { |
1023 | std::cout << __PRETTY_FUNCTION__ << std::endl; |
1024 | - if (tracks().get().empty()) |
1025 | - return *(d->empty_iterator); |
1026 | + if (tracks().get().empty()) { |
1027 | + // TODO Change ServiceSkeleton to return with error from DBus call |
1028 | + std::cerr << "ERROR: no tracks, cannot go to next" << std::endl; |
1029 | + return media::Track::Id{}; |
1030 | + } |
1031 | |
1032 | const auto next_track = std::next(current_iterator()); |
1033 | bool do_go_to_next_track = false; |
1034 | @@ -328,8 +346,11 @@ |
1035 | media::Track::Id media::TrackListSkeleton::previous() |
1036 | { |
1037 | std::cout << __PRETTY_FUNCTION__ << std::endl; |
1038 | - if (tracks().get().empty()) |
1039 | - return *(d->empty_iterator); |
1040 | + if (tracks().get().empty()) { |
1041 | + // TODO Change ServiceSkeleton to return with error from DBus call |
1042 | + std::cerr << "ERROR: no tracks, cannot go to previous" << std::endl; |
1043 | + return media::Track::Id{}; |
1044 | + } |
1045 | |
1046 | bool do_go_to_previous_track = false; |
1047 | |
1048 | @@ -521,7 +542,6 @@ |
1049 | |
1050 | void media::TrackListSkeleton::reset() |
1051 | { |
1052 | - std::cout << __PRETTY_FUNCTION__ << std::endl; |
1053 | d->current_track = d->empty_iterator; |
1054 | } |
1055 | |
1056 | |
1057 | === modified file 'src/core/media/track_list_skeleton.h' |
1058 | --- src/core/media/track_list_skeleton.h 2015-07-27 22:15:33 +0000 |
1059 | +++ src/core/media/track_list_skeleton.h 2015-10-16 08:36:42 +0000 |
1060 | @@ -51,10 +51,12 @@ |
1061 | const core::Property<Container>& tracks() const; |
1062 | |
1063 | const core::Signal<ContainerTrackIdTuple>& on_track_list_replaced() const; |
1064 | + core::Signal<ContainerTrackIdTuple>& on_track_list_replaced(); |
1065 | const core::Signal<Track::Id>& on_track_added() const; |
1066 | core::Signal<Track::Id>& on_track_added(); |
1067 | const core::Signal<Track::Id>& on_track_removed() const; |
1068 | const core::Signal<Track::Id>& on_track_changed() const; |
1069 | + core::Signal<Track::Id>& on_track_changed(); |
1070 | const core::Signal<std::pair<Track::Id, bool>>& on_go_to_track() const; |
1071 | core::Signal<std::pair<Track::Id, bool>>& on_go_to_track(); |
1072 | const core::Signal<void>& on_end_of_tracklist() const; |
1073 | @@ -77,9 +79,6 @@ |
1074 | |
1075 | core::Property<bool>& can_edit_tracks(); |
1076 | |
1077 | - core::Signal<ContainerTrackIdTuple>& on_track_list_replaced(); |
1078 | - core::Signal<Track::Id>& on_track_changed(); |
1079 | - |
1080 | void reset(); |
1081 | |
1082 | private: |
LGTM.