Merge lp:~charlesk/indicator-datetime/lp-1337348-use-gstreamer-api into lp:indicator-datetime/13.10

Proposed by Charles Kerr
Status: Merged
Approved by: Ted Gould
Approved revision: 357
Merged at revision: 353
Proposed branch: lp:~charlesk/indicator-datetime/lp-1337348-use-gstreamer-api
Merge into: lp:indicator-datetime/13.10
Diff against target: 441 lines (+118/-149)
5 files modified
CMakeLists.txt (+1/-1)
debian/control (+1/-1)
src/snap.cpp (+106/-146)
tests/manual (+10/-0)
tests/test-locations.cpp (+0/-1)
To merge this branch: bzr merge lp:~charlesk/indicator-datetime/lp-1337348-use-gstreamer-api
Reviewer Review Type Date Requested Status
Ted Gould (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+225835@code.launchpad.net

Commit message

Use GStreamer's API directly to play sound instead of using libcanberra.

Description of the change

== Description

use GStreamer's API directly to play sound instead of using libcanberra.

This lets us drop the canberra requirement and also fixes the alarm-sound-doesn't-stop-immediately-when-snap-is-dismissed phone issue.

== MP Submission Checklist

 * Are there any related MPs required for this MP to build/function as expected? Please list.

No

 * Is your branch in sync with latest trunk? (e.g. bzr pull lp:trunk -> no changes)

Yes

 * Did the code build without warnings?

Yes

 * Did the tests run successfully?

Yes

 * Did you perform an exploratory manual test run of your code change and any related functionality?

Yes

 * Has your component test plan been executed successfully on emulator, N4?

Yes

 * Please list which manual tests are germane for the reviewer in this MP.

The new manual test included in this MP: tell-snap-decision-to-dismiss

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ted Gould (ted) :
355. By Charles Kerr

use GStreamer's full volume range.

356. By Charles Kerr

copyedit the new manual test for clarity.

357. By Charles Kerr

in snap.cpp, use std::call_once() as suggested by tedg

Revision history for this message
Charles Kerr (charlesk) :
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ted Gould (ted) :
review: Approve
358. By Charles Kerr

silence dead store assignment found by clang static analyzer

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 2014-06-11 04:04:52 +0000
3+++ CMakeLists.txt 2014-07-13 22:23:20 +0000
4@@ -38,7 +38,7 @@
5 libical>=0.48
6 libecal-1.2>=3.5
7 libedataserver-1.2>=3.5
8- libcanberra>=0.12
9+ gstreamer-1.0>=1.2
10 libnotify>=0.7.6
11 url-dispatcher-1>=1
12 properties-cpp>=0.0.1)
13
14=== modified file 'debian/control'
15--- debian/control 2014-06-12 22:14:46 +0000
16+++ debian/control 2014-07-13 22:23:20 +0000
17@@ -13,7 +13,7 @@
18 libgtest-dev,
19 libglib2.0-dev (>= 2.35.4),
20 libnotify-dev (>= 0.7.6),
21- libcanberra-dev,
22+ libgstreamer1.0-dev,
23 libecal1.2-dev (>= 3.5),
24 libical-dev (>= 1.0),
25 libedataserver1.2-dev (>= 3.5),
26
27=== modified file 'src/snap.cpp'
28--- src/snap.cpp 2014-06-27 14:46:21 +0000
29+++ src/snap.cpp 2014-07-13 22:23:20 +0000
30@@ -23,12 +23,13 @@
31
32 #include <core/signal.h>
33
34-#include <canberra.h>
35+#include <gst/gst.h>
36 #include <libnotify/notify.h>
37
38 #include <glib/gi18n.h>
39 #include <glib.h>
40
41+#include <mutex> // std::call_once()
42 #include <set>
43 #include <string>
44
45@@ -53,154 +54,140 @@
46 public:
47
48 Sound(const std::shared_ptr<Clock>& clock,
49- const std::string& filename,
50+ const std::string& uri,
51 unsigned int volume,
52 unsigned int duration_minutes,
53 bool loop):
54 m_clock(clock),
55- m_filename(filename),
56+ m_uri(uri),
57 m_volume(volume),
58 m_loop(loop),
59- m_canberra_id(get_next_canberra_id()),
60- m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, duration_minutes, 0.0))
61+ m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, (int)duration_minutes, 0.0))
62 {
63+ // init GST once
64+ static std::once_flag once;
65+ std::call_once(once, [](){
66+ GError* error = nullptr;
67+ gst_init_check (nullptr, nullptr, &error);
68+ if (error)
69+ {
70+ g_critical("Unable to play alarm sound: %s", error->message);
71+ g_error_free(error);
72+ }
73+ });
74+
75 if (m_loop)
76 {
77 g_debug("Looping '%s' until cutoff time %s",
78- m_filename.c_str(),
79+ m_uri.c_str(),
80 m_loop_end_time.format("%F %T").c_str());
81 }
82 else
83 {
84- g_debug("Playing '%s' once", m_filename.c_str());
85- }
86-
87- const auto rv = ca_context_create(&m_context);
88- if (rv == CA_SUCCESS)
89- {
90- play();
91- }
92- else
93- {
94- g_warning("Failed to create canberra context: %s", ca_strerror(rv));
95- m_context = nullptr;
96- }
97+ g_debug("Playing '%s' once", m_uri.c_str());
98+ }
99+
100+ m_play = gst_element_factory_make("playbin", "play");
101+
102+ auto bus = gst_pipeline_get_bus(GST_PIPELINE(m_play));
103+ m_watch_source = gst_bus_add_watch(bus, bus_callback, this);
104+ gst_object_unref(bus);
105+
106+ play();
107 }
108
109 ~Sound()
110 {
111 stop();
112
113- g_clear_pointer(&m_context, ca_context_destroy);
114+ g_source_remove(m_watch_source);
115+
116+ if (m_play != nullptr)
117+ {
118+ gst_element_set_state (m_play, GST_STATE_NULL);
119+ g_clear_pointer (&m_play, gst_object_unref);
120+ }
121 }
122
123 private:
124
125 void stop()
126 {
127- if (m_context != nullptr)
128- {
129- const auto rv = ca_context_cancel(m_context, m_canberra_id);
130- if (rv != CA_SUCCESS)
131- g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
132- }
133-
134- if (m_loop_tag != 0)
135- {
136- g_source_remove(m_loop_tag);
137- m_loop_tag = 0;
138+ if (m_play != nullptr)
139+ {
140+ gst_element_set_state (m_play, GST_STATE_PAUSED);
141 }
142 }
143
144 void play()
145 {
146- auto context = m_context;
147- g_return_if_fail(context != nullptr);
148-
149- const auto filename = m_filename.c_str();
150- const float gain = get_gain_level(m_volume);
151-
152- ca_proplist* props = nullptr;
153- ca_proplist_create(&props);
154- ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
155- ca_proplist_setf(props, CA_PROP_CANBERRA_VOLUME, "%f", gain);
156- const auto rv = ca_context_play_full(context, m_canberra_id, props,
157- on_done_playing, this);
158- if (rv != CA_SUCCESS)
159- g_warning("Unable to play '%s': %s", filename, ca_strerror(rv));
160-
161- g_clear_pointer(&props, ca_proplist_destroy);
162- }
163-
164- static float get_gain_level(unsigned int volume)
165- {
166- const unsigned int clamped_volume = CLAMP(volume, 1, 100);
167-
168- /* This range isn't set in stone --
169- arrived at from manual tests on Nextus 4 */
170- constexpr float gain_low = -10;
171- constexpr float gain_high = 10;
172-
173- constexpr float gain_range = gain_high - gain_low;
174- return gain_low + (gain_range * (clamped_volume / 100.0f));
175- }
176-
177- static void on_done_playing(ca_context*, uint32_t, int rv, void* gself)
178- {
179- // if we still need to loop, wait a second, then play it again
180-
181- if (rv == CA_SUCCESS)
182+ g_return_if_fail(m_play != nullptr);
183+
184+ g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(),
185+ "volume", get_volume(),
186+ nullptr);
187+ gst_element_set_state (m_play, GST_STATE_PLAYING);
188+ }
189+
190+ // convert settings range [1..100] to gst playbin's range is [0...1.0]
191+ gdouble get_volume() const
192+ {
193+ constexpr int in_range_lo = 1;
194+ constexpr int in_range_hi = 100;
195+ const double in = CLAMP(m_volume, in_range_lo, in_range_hi);
196+ const double pct = (in - in_range_lo) / (in_range_hi - in_range_lo);
197+
198+ constexpr double out_range_lo = 0.0;
199+ constexpr double out_range_hi = 1.0;
200+ return out_range_lo + (pct * (out_range_hi - out_range_lo));
201+ }
202+
203+ static gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself)
204+ {
205+ auto self = static_cast<Sound*>(gself);
206+
207+ if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) &&
208+ (self->m_loop) &&
209+ (self->m_clock->localtime() < self->m_loop_end_time))
210 {
211- auto self = static_cast<Self*>(gself);
212- if ((self->m_loop_tag == 0) &&
213- (self->m_loop) &&
214- (self->m_clock->localtime() < self->m_loop_end_time))
215- {
216- self->m_loop_tag = g_timeout_add_seconds(1, play_idle, self);
217- }
218+ gst_element_seek(self->m_play,
219+ 1.0,
220+ GST_FORMAT_TIME,
221+ GST_SEEK_FLAG_FLUSH,
222+ GST_SEEK_TYPE_SET,
223+ 0,
224+ GST_SEEK_TYPE_NONE,
225+ (gint64)GST_CLOCK_TIME_NONE);
226 }
227- }
228
229- static gboolean play_idle(gpointer gself)
230- {
231- auto self = static_cast<Self*>(gself);
232- self->m_loop_tag = 0;
233- self->play();
234- return G_SOURCE_REMOVE;
235+ return G_SOURCE_CONTINUE; // keep listening
236 }
237
238 /***
239 ****
240 ***/
241
242- static int32_t get_next_canberra_id()
243- {
244- static int32_t next_canberra_id = 1;
245- return next_canberra_id++;
246- }
247-
248 const std::shared_ptr<Clock> m_clock;
249- const std::string m_filename;
250+ const std::string m_uri;
251 const unsigned int m_volume;
252 const bool m_loop;
253- const int32_t m_canberra_id;
254 const DateTime m_loop_end_time;
255- ca_context* m_context = nullptr;
256- guint m_loop_tag = 0;
257+ guint m_watch_source = 0;
258+ GstElement* m_play = nullptr;
259 };
260
261 class SoundBuilder
262 {
263 public:
264 void set_clock(const std::shared_ptr<Clock>& c) {m_clock = c;}
265- void set_filename(const std::string& s) {m_filename = s;}
266+ void set_uri(const std::string& uri) {m_uri = uri;}
267 void set_volume(const unsigned int v) {m_volume = v;}
268 void set_duration_minutes(int unsigned i) {m_duration_minutes=i;}
269 void set_looping(bool b) {m_looping=b;}
270
271 Sound* operator()() {
272 return new Sound (m_clock,
273- m_filename,
274+ m_uri,
275 m_volume,
276 m_duration_minutes,
277 m_looping);
278@@ -208,7 +195,7 @@
279
280 private:
281 std::shared_ptr<Clock> m_clock;
282- std::string m_filename;
283+ std::string m_uri;
284 unsigned int m_volume = 50;
285 unsigned int m_duration_minutes = 30;
286 bool m_looping = true;
287@@ -229,14 +216,11 @@
288 {
289 // ensure notify_init() is called once
290 // before we start popping up dialogs
291- static bool m_nn_inited = false;
292- if (G_UNLIKELY(!m_nn_inited))
293- {
294+ static std::once_flag once;
295+ std::call_once(once, [](){
296 if(!notify_init("indicator-datetime-service"))
297 g_critical("libnotify initialization failed");
298-
299- m_nn_inited = true;
300- }
301+ });
302
303 show();
304 }
305@@ -367,13 +351,11 @@
306 static bool get_interactive()
307 {
308 static bool interactive;
309- static bool inited = false;
310
311- if (G_UNLIKELY(!inited))
312- {
313+ static std::once_flag once;
314+ std::call_once(once, [](){
315 interactive = get_server_caps().count("actions") != 0;
316- inited = true;
317- }
318+ });
319
320 return interactive;
321 }
322@@ -397,38 +379,8 @@
323 *** libnotify -- snap decisions
324 **/
325
326-std::string get_local_filename (const std::string& str)
327-{
328- std::string ret;
329-
330- if (!str.empty())
331- {
332- GFile* files[] = { g_file_new_for_path(str.c_str()),
333- g_file_new_for_uri(str.c_str()) };
334-
335- for(auto& file : files)
336- {
337- if (g_file_is_native(file) && g_file_query_exists(file, nullptr))
338- {
339- char* tmp = g_file_get_path(file);
340- if (tmp != nullptr)
341- {
342- ret = tmp;
343- g_free(tmp);
344- break;
345- }
346- }
347- }
348-
349- for(auto& file : files)
350- g_object_unref(file);
351- }
352-
353- return ret;
354-}
355-
356-std::string get_alarm_sound(const Appointment& appointment,
357- const std::shared_ptr<const Settings>& settings)
358+std::string get_alarm_uri(const Appointment& appointment,
359+ const std::shared_ptr<const Settings>& settings)
360 {
361 const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"};
362
363@@ -436,20 +388,28 @@
364 settings->alarm_sound.get(),
365 FALLBACK };
366
367- std::string alarm_sound;
368+ std::string uri;
369
370 for(const auto& candidate : candidates)
371 {
372- alarm_sound = get_local_filename(candidate);
373-
374- if (!alarm_sound.empty())
375+ if (gst_uri_is_valid (candidate.c_str()))
376+ {
377+ uri = candidate;
378 break;
379+ }
380+ else if (g_file_test(candidate.c_str(), G_FILE_TEST_EXISTS))
381+ {
382+ gchar* tmp = gst_filename_to_uri(candidate.c_str(), nullptr);
383+ if (tmp != nullptr)
384+ {
385+ uri = tmp;
386+ g_free (tmp);
387+ break;
388+ }
389+ }
390 }
391
392- g_debug("%s: Appointment \"%s\" using alarm sound \"%s\"",
393- G_STRFUNC, appointment.summary.c_str(), alarm_sound.c_str());
394-
395- return alarm_sound;
396+ return uri;
397 }
398
399 } // unnamed namespace
400@@ -481,7 +441,7 @@
401
402 // create a popup...
403 SoundBuilder sound_builder;
404- sound_builder.set_filename(get_alarm_sound(appointment, m_settings));
405+ sound_builder.set_uri(get_alarm_uri(appointment, m_settings));
406 sound_builder.set_volume(m_settings->alarm_volume.get());
407 sound_builder.set_clock(m_clock);
408 sound_builder.set_duration_minutes(m_settings->alarm_duration.get());
409
410=== modified file 'tests/manual'
411--- tests/manual 2014-06-30 14:54:34 +0000
412+++ tests/manual 2014-07-13 22:23:20 +0000
413@@ -40,6 +40,16 @@
414 device actually suspended or whether the suspend was aborted)</dd>
415 </dl>
416
417+Test-case indicator-datetime/tell-snap-decision-to-dismiss
418+<dl>
419+ <dt>Set an alarm and wait for it to arrive.</td>
420+ <dd>Alarm should go off at the specified time</dd>
421+ <dt>Press the 'Dismiss' button in the alarm's snap decision popup before the sound stops.</dt>
422+ <dd>Popup should disappear</dd>
423+ <dd>Sound should stop at the same time, rather than playing til the end of the file.</dd>
424+</dl>
425+
426+
427 <strong>
428 If all actions produce the expected results listed, please <a href="results#add_result">submit</a> a 'passed' result.
429 If an action fails, or produces an unexpected result, please <a href="results#add_result">submit</a> a 'failed' result and <a href="../../buginstructions">file a bug</a>. Please be sure to include the bug number when you <a href="results#add_result">submit</a> your result</strong>.
430
431=== modified file 'tests/test-locations.cpp'
432--- tests/test-locations.cpp 2014-01-22 15:06:01 +0000
433+++ tests/test-locations.cpp 2014-07-13 22:23:20 +0000
434@@ -139,7 +139,6 @@
435 EXPECT_EQ("Europe/London", l[3].zone());
436 EXPECT_EQ("Berlin", l[4].name());
437 EXPECT_EQ("Europe/Berlin", l[4].zone());
438- locations_changed = false;
439 }
440
441 TEST_F(LocationsFixture, ChangeLocationVisibility)

Subscribers

People subscribed via source and target branches