Merge lp:~jhodapp/media-hub/fix-music-next into lp:media-hub

Proposed by Jim Hodapp
Status: Merged
Approved by: Manuel de la Peña
Approved revision: 50
Merged at revision: 51
Proposed branch: lp:~jhodapp/media-hub/fix-music-next
Merge into: lp:media-hub
Diff against target: 537 lines (+246/-34)
9 files modified
README (+4/-0)
src/core/media/CMakeLists.txt (+0/-1)
src/core/media/engine.h (+1/-0)
src/core/media/gstreamer/engine.cpp (+18/-1)
src/core/media/gstreamer/engine.h (+2/-1)
src/core/media/gstreamer/playbin.h (+3/-0)
src/core/media/player_implementation.cpp (+143/-29)
src/core/media/powerd_service.h (+2/-2)
src/core/media/util/timeout.h (+73/-0)
To merge this branch: bzr merge lp:~jhodapp/media-hub/fix-music-next
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve
PS Jenkins bot continuous-integration Needs Fixing
Review via email: mp+230326@code.launchpad.net

Commit message

Allow music to advance to the next song when the device is not charging

Description of the change

Allow music to advance to the next song when the device is not charging

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~jhodapp/media-hub/fix-music-next updated
50. By Jim Hodapp

Make sure the display wakelock is cleared after video playback stops.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Manuel de la Peña (mandel) :
review: Approve
lp:~jhodapp/media-hub/fix-music-next updated
51. By Jim Hodapp

Rework the system and display wakelock logic. Should take care of the edge case issues.

52. By Jim Hodapp

Make sure a display or system wakelock are not held if the client disconnects during playback

53. By Jim Hodapp

Remove some debugging statements and improve the code comments

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2014-01-13 21:51:14 +0000
3+++ README 2014-08-13 01:41:13 +0000
4@@ -6,6 +6,10 @@
5 cmake ..
6 make
7
8+To Run From Console:
9+--------------------
10+CORE_UBUNTU_MEDIA_SERVICE_VIDEO_SINK_NAME=mirsink CORE_UBUNTU_MEDIA_SERVICE_AUDIO_SINK_NAME=pulsesink src/core/media/media-hub-server
11+
12 To Run Unit Tests:
13 ------------------
14
15
16=== modified file 'src/core/media/CMakeLists.txt'
17--- src/core/media/CMakeLists.txt 2014-04-04 14:31:43 +0000
18+++ src/core/media/CMakeLists.txt 2014-08-13 01:41:13 +0000
19@@ -1,5 +1,4 @@
20 pkg_check_modules(PC_GSTREAMER_1_0 REQUIRED gstreamer-1.0)
21-
22 include_directories(${PC_GSTREAMER_1_0_INCLUDE_DIRS} ${HYBRIS_MEDIA_CFLAGS})
23
24 add_library(
25
26=== modified file 'src/core/media/engine.h'
27--- src/core/media/engine.h 2014-04-25 17:53:00 +0000
28+++ src/core/media/engine.h 2014-08-13 01:41:13 +0000
29@@ -118,6 +118,7 @@
30
31 virtual const core::Signal<void>& about_to_finish_signal() const = 0;
32 virtual const core::Signal<uint64_t>& seeked_to_signal() const = 0;
33+ virtual const core::Signal<void>& client_disconnected_signal() const = 0;
34 virtual const core::Signal<void>& end_of_stream_signal() const = 0;
35 virtual const core::Signal<core::ubuntu::media::Player::PlaybackStatus>& playback_status_changed_signal() const = 0;
36 };
37
38=== modified file 'src/core/media/gstreamer/engine.cpp'
39--- src/core/media/gstreamer/engine.cpp 2014-04-25 17:53:00 +0000
40+++ src/core/media/gstreamer/engine.cpp 2014-08-13 01:41:13 +0000
41@@ -1,5 +1,5 @@
42 /*
43- * Copyright © 2013 Canonical Ltd.
44+ * Copyright © 2013-2014 Canonical Ltd.
45 *
46 * This program is free software: you can redistribute it and/or modify it
47 * under the terms of the GNU Lesser General Public License version 3,
48@@ -175,6 +175,11 @@
49 seeked_to(value);
50 }
51
52+ void on_client_disconnected()
53+ {
54+ client_disconnected();
55+ }
56+
57 void on_end_of_stream()
58 {
59 end_of_stream();
60@@ -214,6 +219,11 @@
61 &Private::on_seeked_to,
62 this,
63 std::placeholders::_1))),
64+ client_disconnected_connection(
65+ playbin.signals.client_disconnected.connect(
66+ std::bind(
67+ &Private::on_client_disconnected,
68+ this))),
69 on_end_of_stream_connection(
70 playbin.signals.on_end_of_stream.connect(
71 std::bind(
72@@ -236,10 +246,12 @@
73 core::ScopedConnection on_tag_available_connection;
74 core::ScopedConnection on_volume_changed_connection;
75 core::ScopedConnection on_seeked_to_connection;
76+ core::ScopedConnection client_disconnected_connection;
77 core::ScopedConnection on_end_of_stream_connection;
78
79 core::Signal<void> about_to_finish;
80 core::Signal<uint64_t> seeked_to;
81+ core::Signal<void> client_disconnected;
82 core::Signal<void> end_of_stream;
83 core::Signal<media::Player::PlaybackStatus> playback_status_changed;
84 };
85@@ -383,6 +395,11 @@
86 return d->seeked_to;
87 }
88
89+const core::Signal<void>& gstreamer::Engine::client_disconnected_signal() const
90+{
91+ return d->client_disconnected;
92+}
93+
94 const core::Signal<void>& gstreamer::Engine::end_of_stream_signal() const
95 {
96 return d->end_of_stream;
97
98=== modified file 'src/core/media/gstreamer/engine.h'
99--- src/core/media/gstreamer/engine.h 2014-04-25 17:53:00 +0000
100+++ src/core/media/gstreamer/engine.h 2014-08-13 01:41:13 +0000
101@@ -1,5 +1,5 @@
102 /*
103- * Copyright © 2013 Canonical Ltd.
104+ * Copyright © 2013-2014 Canonical Ltd.
105 *
106 * This program is free software: you can redistribute it and/or modify it
107 * under the terms of the GNU Lesser General Public License version 3,
108@@ -53,6 +53,7 @@
109
110 const core::Signal<void>& about_to_finish_signal() const;
111 const core::Signal<uint64_t>& seeked_to_signal() const;
112+ const core::Signal<void>& client_disconnected_signal() const;
113 const core::Signal<void>& end_of_stream_signal() const;
114 const core::Signal<core::ubuntu::media::Player::PlaybackStatus>& playback_status_changed_signal() const;
115
116
117=== modified file 'src/core/media/gstreamer/playbin.h'
118--- src/core/media/gstreamer/playbin.h 2014-04-25 21:23:10 +0000
119+++ src/core/media/gstreamer/playbin.h 2014-08-13 01:41:13 +0000
120@@ -116,6 +116,8 @@
121 // in a state that is ready for the next client that connects to the
122 // service
123 reset_pipeline();
124+ // Signal to the Player class that the client side has disconnected
125+ signals.client_disconnected();
126 }
127
128 void reset_pipeline()
129@@ -411,6 +413,7 @@
130 core::Signal<uint64_t> on_seeked_to;
131 core::Signal<void> on_end_of_stream;
132 core::Signal<media::Player::PlaybackStatus> on_playback_status_changed;
133+ core::Signal<void> client_disconnected;
134 } signals;
135 };
136 }
137
138=== modified file 'src/core/media/player_implementation.cpp'
139--- src/core/media/player_implementation.cpp 2014-06-24 17:03:27 +0000
140+++ src/core/media/player_implementation.cpp 2014-08-13 01:41:13 +0000
141@@ -13,9 +13,11 @@
142 * along with this program. If not, see <http://www.gnu.org/licenses/>.
143 *
144 * Authored by: Thomas Voß <thomas.voss@canonical.com>
145+ * Jim Hodapp <jim.hodapp@canonical.com>
146 */
147
148 #include "player_implementation.h"
149+#include "util/timeout.h"
150
151 #include <unistd.h>
152
153@@ -28,6 +30,7 @@
154
155 #include <exception>
156 #include <iostream>
157+#include <mutex>
158
159 #define UNUSED __attribute__((unused))
160
161@@ -38,6 +41,14 @@
162
163 struct media::PlayerImplementation::Private
164 {
165+ enum class wakelock_clear_t
166+ {
167+ WAKELOCK_CLEAR_INACTIVE,
168+ WAKELOCK_CLEAR_DISPLAY,
169+ WAKELOCK_CLEAR_SYSTEM,
170+ WAKELOCK_CLEAR_INVALID
171+ };
172+
173 Private(PlayerImplementation* parent,
174 const dbus::types::ObjectPath& session_path,
175 const std::shared_ptr<media::Service>& service,
176@@ -51,7 +62,10 @@
177 session_path.as_string() + "/TrackList",
178 engine->meta_data_extractor())),
179 sys_lock_name("media-hub-music-playback"),
180- active_display_on_request(false),
181+ disp_cookie(-1),
182+ system_wakelock_count(0),
183+ display_wakelock_count(0),
184+ previous_state(Engine::State::stopped),
185 key(key)
186 {
187 auto bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::system));
188@@ -63,6 +77,14 @@
189 auto uscreen_stub_service = dbus::Service::use_service(bus, dbus::traits::Service<core::UScreen>::interface_name());
190 uscreen_session = uscreen_stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/Unity/Screen"));
191
192+ /*
193+ * Wakelock state logic:
194+ *
195+ * PLAYING->READY: delay 4 seconds and try to clear current wakelock type
196+ * PLAYING->PAUSED or PLAYING->STOPPED: delay 4 seconds and try to clear current wakelock type
197+ * READY->PAUSED: request a new wakelock (system or display)
198+ * PLAYING->PAUSED: delay 4 seconds and try to clear current wakelock type
199+ */
200 engine->state().changed().connect(
201 [parent, this](const Engine::State& state)
202 {
203@@ -71,97 +93,178 @@
204 case Engine::State::ready:
205 {
206 parent->playback_status().set(media::Player::ready);
207- clear_power_state();
208+ if (previous_state == Engine::State::playing)
209+ {
210+ wakelock_timeout.reset(new timeout(4000, true, std::bind(&Private::clear_wakelock,
211+ this, std::placeholders::_1), current_wakelock_type()));
212+ }
213 break;
214 }
215 case Engine::State::playing:
216 {
217 parent->playback_status().set(media::Player::playing);
218- request_power_state();
219+ if (previous_state == Engine::State::stopped || previous_state == Engine::State::paused)
220+ {
221+ request_power_state();
222+ }
223 break;
224 }
225 case Engine::State::stopped:
226 {
227 parent->playback_status().set(media::Player::stopped);
228- clear_power_state();
229 break;
230 }
231 case Engine::State::paused:
232 {
233 parent->playback_status().set(media::Player::paused);
234- clear_power_state();
235+ if (previous_state == Engine::State::ready)
236+ {
237+ request_power_state();
238+ }
239+ else if (previous_state == Engine::State::playing)
240+ {
241+ wakelock_timeout.reset(new timeout(4000, true, std::bind(&Private::clear_wakelock,
242+ this, std::placeholders::_1), current_wakelock_type()));
243+ }
244 break;
245 }
246 default:
247 break;
248 };
249+
250+ // Keep track of the previous Engine playback state:
251+ previous_state = state;
252 });
253
254 }
255
256+ ~Private()
257+ {
258+ // Make sure that we don't hold on to the wakelocks if media-hub-server
259+ // ever gets restarted manually or automatically
260+ clear_wakelocks();
261+ }
262+
263 void request_power_state()
264 {
265 try
266 {
267 if (parent->is_video_source())
268 {
269- if (!active_display_on_request)
270+ if (display_wakelock_count == 0)
271 {
272 auto result = uscreen_session->invoke_method_synchronously<core::UScreen::keepDisplayOn, int>();
273 if (result.is_error())
274 throw std::runtime_error(result.error().print());
275-
276 disp_cookie = result.value();
277- active_display_on_request = true;
278+ cout << "Requested new display wakelock" << endl;
279+ }
280+
281+ {
282+ // Keep track of how many display wakelocks have been requested
283+ std::lock_guard<std::mutex> lock(wakelock_mutex);
284+ ++display_wakelock_count;
285 }
286 }
287 else
288 {
289- if (sys_cookie.empty())
290+ if (system_wakelock_count == 0)
291 {
292 auto result = powerd_session->invoke_method_synchronously<core::Powerd::requestSysState, std::string>(sys_lock_name, static_cast<int>(1));
293 if (result.is_error())
294 throw std::runtime_error(result.error().print());
295-
296 sys_cookie = result.value();
297+ cout << "Requested new system wakelock" << endl;
298+ }
299+
300+ {
301+ // Keep track of how many system wakelocks have been requested
302+ std::lock_guard<std::mutex> lock(wakelock_mutex);
303+ ++system_wakelock_count;
304 }
305 }
306 }
307- catch(std::exception& e)
308+ catch(const std::exception& e)
309 {
310 std::cerr << "Warning: failed to request power state: ";
311 std::cerr << e.what() << std::endl;
312 }
313 }
314
315- void clear_power_state()
316+ void clear_wakelock(const wakelock_clear_t &wakelock)
317 {
318+ cout << __PRETTY_FUNCTION__ << endl;
319 try
320 {
321- if (parent->is_video_source())
322- {
323- if (active_display_on_request)
324- {
325- uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(disp_cookie);
326- active_display_on_request = false;
327- }
328- }
329- else
330- {
331- if (!sys_cookie.empty())
332- {
333- powerd_session->invoke_method_synchronously<core::Powerd::clearSysState, void>(sys_cookie);
334- sys_cookie.clear();
335- }
336+ switch (wakelock)
337+ {
338+ case wakelock_clear_t::WAKELOCK_CLEAR_INACTIVE:
339+ break;
340+ case wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM:
341+ {
342+ std::lock_guard<std::mutex> lock(wakelock_mutex);
343+ --system_wakelock_count;
344+ }
345+ // Only actually clear the system wakelock once the count reaches zero
346+ if (system_wakelock_count == 0)
347+ {
348+ cout << "Clearing system wakelock" << endl;
349+ powerd_session->invoke_method_synchronously<core::Powerd::clearSysState, void>(sys_cookie);
350+ sys_cookie.clear();
351+ }
352+ break;
353+ case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY:
354+ {
355+ std::lock_guard<std::mutex> lock(wakelock_mutex);
356+ --display_wakelock_count;
357+ }
358+ // Only actually clear the display wakelock once the count reaches zero
359+ if (display_wakelock_count == 0)
360+ {
361+ cout << "Clearing display wakelock" << endl;
362+ uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(disp_cookie);
363+ disp_cookie = -1;
364+ }
365+ break;
366+ case wakelock_clear_t::WAKELOCK_CLEAR_INVALID:
367+ default:
368+ cerr << "Can't clear invalid wakelock type" << endl;
369 }
370 }
371- catch(std::exception& e)
372+ catch(const std::exception& e)
373 {
374 std::cerr << "Warning: failed to clear power state: ";
375 std::cerr << e.what() << std::endl;
376 }
377 }
378
379+ wakelock_clear_t current_wakelock_type() const
380+ {
381+ return (parent->is_video_source()) ?
382+ wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY : wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM;
383+ }
384+
385+ void clear_wakelocks()
386+ {
387+ // Clear both types of wakelocks (display and system)
388+ if (system_wakelock_count > 0)
389+ {
390+ {
391+ std::lock_guard<std::mutex> lock(wakelock_mutex);
392+ system_wakelock_count = 1;
393+ }
394+ clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM);
395+ }
396+ if (display_wakelock_count > 0)
397+ {
398+ {
399+ std::lock_guard<std::mutex> lock(wakelock_mutex);
400+ display_wakelock_count = 1;
401+ }
402+ clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY);
403+ }
404+ }
405+
406 PlayerImplementation* parent;
407 std::shared_ptr<Service> service;
408 std::shared_ptr<Engine> engine;
409@@ -171,8 +274,12 @@
410 std::shared_ptr<dbus::Object> uscreen_session;
411 std::string sys_lock_name;
412 int disp_cookie;
413- bool active_display_on_request;
414 std::string sys_cookie;
415+ std::mutex wakelock_mutex;
416+ uint8_t system_wakelock_count;
417+ uint8_t display_wakelock_count;
418+ std::unique_ptr<timeout> wakelock_timeout;
419+ Engine::State previous_state;
420 PlayerImplementation::PlayerKey key;
421 };
422
423@@ -240,6 +347,13 @@
424 }
425 });
426
427+ d->engine->client_disconnected_signal().connect([this]()
428+ {
429+ // If the client disconnects, make sure both wakelock types
430+ // are cleared
431+ d->clear_wakelocks();
432+ });
433+
434 d->engine->seeked_to_signal().connect([this](uint64_t value)
435 {
436 seeked_to()(value);
437
438=== modified file 'src/core/media/powerd_service.h'
439--- src/core/media/powerd_service.h 2014-06-16 22:28:53 +0000
440+++ src/core/media/powerd_service.h 2014-08-13 01:41:13 +0000
441@@ -41,7 +41,7 @@
442 static std::string s = "com.canonical.powerd";
443 return s;
444 }
445-
446+
447 struct requestSysState
448 {
449 static std::string name()
450@@ -62,7 +62,7 @@
451 static std::string s = "clearSysState";
452 return s;
453 }
454-
455+
456 static const std::chrono::milliseconds default_timeout() { return std::chrono::seconds{1}; }
457
458 typedef Powerd Interface;
459
460=== added directory 'src/core/media/util'
461=== added file 'src/core/media/util/timeout.h'
462--- src/core/media/util/timeout.h 1970-01-01 00:00:00 +0000
463+++ src/core/media/util/timeout.h 2014-08-13 01:41:13 +0000
464@@ -0,0 +1,73 @@
465+/*
466+ *
467+ * This program is free software: you can redistribute it and/or modify it
468+ * under the terms of the GNU Lesser General Public License version 3,
469+ * as published by the Free Software Foundation.
470+ *
471+ * This program is distributed in the hope that it will be useful,
472+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
473+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
474+ * GNU Lesser General Public License for more details.
475+ *
476+ * You should have received a copy of the GNU Lesser General Public License
477+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
478+ *
479+ * Authored by: Jim Hodapp <jim.hodapp@canonical.com>
480+ *
481+ * Adapted from: http://stackoverflow.com/questions/14650885/how-to-create-timer-events-using-c-11
482+ *
483+ */
484+
485+#ifndef TIMEOUT_H_
486+#define TIMEOUT_H_
487+
488+#include <functional>
489+#include <chrono>
490+#include <future>
491+#include <cstdio>
492+
493+namespace core
494+{
495+namespace ubuntu
496+{
497+namespace media
498+{
499+
500+class timeout
501+{
502+public:
503+ /**
504+ * @brief Start a timeout with millisecond resolution
505+ * @param timeout_ms Timeout value in milliseconds
506+ * @param async Whether to block while the timeout proceeds or not
507+ * @param f The function to call when the timeout expires
508+ * @param args Any arguments to pass to f when the timeout expires
509+ */
510+ template <class callable, class... arguments>
511+ timeout(int timeout_ms, bool async, callable&& f, arguments&&... args)
512+ {
513+ std::function<typename std::result_of<callable(arguments...)>::type()>
514+ task(std::bind(std::forward<callable>(f), std::forward<arguments>(args)...));
515+
516+ if (async)
517+ {
518+ // Timeout without blocking
519+ std::thread([timeout_ms, task]() {
520+ std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms));
521+ task();
522+ }).detach();
523+ }
524+ else
525+ {
526+ // Timeout with blocking
527+ std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms));
528+ task();
529+ }
530+ }
531+};
532+
533+}
534+}
535+}
536+
537+#endif // TIMEOUT_H_

Subscribers

People subscribed via source and target branches