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
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2014-06-11 04:04:52 +0000
+++ CMakeLists.txt 2014-07-13 22:23:20 +0000
@@ -38,7 +38,7 @@
38 libical>=0.4838 libical>=0.48
39 libecal-1.2>=3.539 libecal-1.2>=3.5
40 libedataserver-1.2>=3.540 libedataserver-1.2>=3.5
41 libcanberra>=0.1241 gstreamer-1.0>=1.2
42 libnotify>=0.7.642 libnotify>=0.7.6
43 url-dispatcher-1>=143 url-dispatcher-1>=1
44 properties-cpp>=0.0.1)44 properties-cpp>=0.0.1)
4545
=== modified file 'debian/control'
--- debian/control 2014-06-12 22:14:46 +0000
+++ debian/control 2014-07-13 22:23:20 +0000
@@ -13,7 +13,7 @@
13 libgtest-dev,13 libgtest-dev,
14 libglib2.0-dev (>= 2.35.4),14 libglib2.0-dev (>= 2.35.4),
15 libnotify-dev (>= 0.7.6),15 libnotify-dev (>= 0.7.6),
16 libcanberra-dev,16 libgstreamer1.0-dev,
17 libecal1.2-dev (>= 3.5),17 libecal1.2-dev (>= 3.5),
18 libical-dev (>= 1.0),18 libical-dev (>= 1.0),
19 libedataserver1.2-dev (>= 3.5),19 libedataserver1.2-dev (>= 3.5),
2020
=== modified file 'src/snap.cpp'
--- src/snap.cpp 2014-06-27 14:46:21 +0000
+++ src/snap.cpp 2014-07-13 22:23:20 +0000
@@ -23,12 +23,13 @@
2323
24#include <core/signal.h>24#include <core/signal.h>
2525
26#include <canberra.h>26#include <gst/gst.h>
27#include <libnotify/notify.h>27#include <libnotify/notify.h>
2828
29#include <glib/gi18n.h>29#include <glib/gi18n.h>
30#include <glib.h>30#include <glib.h>
3131
32#include <mutex> // std::call_once()
32#include <set>33#include <set>
33#include <string>34#include <string>
3435
@@ -53,154 +54,140 @@
53public:54public:
5455
55 Sound(const std::shared_ptr<Clock>& clock,56 Sound(const std::shared_ptr<Clock>& clock,
56 const std::string& filename,57 const std::string& uri,
57 unsigned int volume,58 unsigned int volume,
58 unsigned int duration_minutes,59 unsigned int duration_minutes,
59 bool loop):60 bool loop):
60 m_clock(clock),61 m_clock(clock),
61 m_filename(filename),62 m_uri(uri),
62 m_volume(volume),63 m_volume(volume),
63 m_loop(loop),64 m_loop(loop),
64 m_canberra_id(get_next_canberra_id()),65 m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, (int)duration_minutes, 0.0))
65 m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, duration_minutes, 0.0))
66 {66 {
67 // init GST once
68 static std::once_flag once;
69 std::call_once(once, [](){
70 GError* error = nullptr;
71 gst_init_check (nullptr, nullptr, &error);
72 if (error)
73 {
74 g_critical("Unable to play alarm sound: %s", error->message);
75 g_error_free(error);
76 }
77 });
78
67 if (m_loop)79 if (m_loop)
68 {80 {
69 g_debug("Looping '%s' until cutoff time %s",81 g_debug("Looping '%s' until cutoff time %s",
70 m_filename.c_str(),82 m_uri.c_str(),
71 m_loop_end_time.format("%F %T").c_str());83 m_loop_end_time.format("%F %T").c_str());
72 }84 }
73 else85 else
74 {86 {
75 g_debug("Playing '%s' once", m_filename.c_str());87 g_debug("Playing '%s' once", m_uri.c_str());
76 }88 }
7789
78 const auto rv = ca_context_create(&m_context);90 m_play = gst_element_factory_make("playbin", "play");
79 if (rv == CA_SUCCESS)91
80 {92 auto bus = gst_pipeline_get_bus(GST_PIPELINE(m_play));
81 play();93 m_watch_source = gst_bus_add_watch(bus, bus_callback, this);
82 }94 gst_object_unref(bus);
83 else95
84 {96 play();
85 g_warning("Failed to create canberra context: %s", ca_strerror(rv));
86 m_context = nullptr;
87 }
88 }97 }
8998
90 ~Sound()99 ~Sound()
91 {100 {
92 stop();101 stop();
93102
94 g_clear_pointer(&m_context, ca_context_destroy);103 g_source_remove(m_watch_source);
104
105 if (m_play != nullptr)
106 {
107 gst_element_set_state (m_play, GST_STATE_NULL);
108 g_clear_pointer (&m_play, gst_object_unref);
109 }
95 }110 }
96111
97private:112private:
98113
99 void stop()114 void stop()
100 {115 {
101 if (m_context != nullptr)116 if (m_play != nullptr)
102 {117 {
103 const auto rv = ca_context_cancel(m_context, m_canberra_id);118 gst_element_set_state (m_play, GST_STATE_PAUSED);
104 if (rv != CA_SUCCESS)
105 g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
106 }
107
108 if (m_loop_tag != 0)
109 {
110 g_source_remove(m_loop_tag);
111 m_loop_tag = 0;
112 }119 }
113 }120 }
114121
115 void play()122 void play()
116 {123 {
117 auto context = m_context;124 g_return_if_fail(m_play != nullptr);
118 g_return_if_fail(context != nullptr);125
119126 g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(),
120 const auto filename = m_filename.c_str();127 "volume", get_volume(),
121 const float gain = get_gain_level(m_volume);128 nullptr);
122129 gst_element_set_state (m_play, GST_STATE_PLAYING);
123 ca_proplist* props = nullptr;130 }
124 ca_proplist_create(&props);131
125 ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);132 // convert settings range [1..100] to gst playbin's range is [0...1.0]
126 ca_proplist_setf(props, CA_PROP_CANBERRA_VOLUME, "%f", gain);133 gdouble get_volume() const
127 const auto rv = ca_context_play_full(context, m_canberra_id, props,134 {
128 on_done_playing, this);135 constexpr int in_range_lo = 1;
129 if (rv != CA_SUCCESS)136 constexpr int in_range_hi = 100;
130 g_warning("Unable to play '%s': %s", filename, ca_strerror(rv));137 const double in = CLAMP(m_volume, in_range_lo, in_range_hi);
131138 const double pct = (in - in_range_lo) / (in_range_hi - in_range_lo);
132 g_clear_pointer(&props, ca_proplist_destroy);139
133 }140 constexpr double out_range_lo = 0.0;
134141 constexpr double out_range_hi = 1.0;
135 static float get_gain_level(unsigned int volume)142 return out_range_lo + (pct * (out_range_hi - out_range_lo));
136 {143 }
137 const unsigned int clamped_volume = CLAMP(volume, 1, 100);144
138145 static gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself)
139 /* This range isn't set in stone --146 {
140 arrived at from manual tests on Nextus 4 */147 auto self = static_cast<Sound*>(gself);
141 constexpr float gain_low = -10;148
142 constexpr float gain_high = 10;149 if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) &&
143150 (self->m_loop) &&
144 constexpr float gain_range = gain_high - gain_low;151 (self->m_clock->localtime() < self->m_loop_end_time))
145 return gain_low + (gain_range * (clamped_volume / 100.0f));
146 }
147
148 static void on_done_playing(ca_context*, uint32_t, int rv, void* gself)
149 {
150 // if we still need to loop, wait a second, then play it again
151
152 if (rv == CA_SUCCESS)
153 {152 {
154 auto self = static_cast<Self*>(gself);153 gst_element_seek(self->m_play,
155 if ((self->m_loop_tag == 0) &&154 1.0,
156 (self->m_loop) &&155 GST_FORMAT_TIME,
157 (self->m_clock->localtime() < self->m_loop_end_time))156 GST_SEEK_FLAG_FLUSH,
158 {157 GST_SEEK_TYPE_SET,
159 self->m_loop_tag = g_timeout_add_seconds(1, play_idle, self);158 0,
160 }159 GST_SEEK_TYPE_NONE,
160 (gint64)GST_CLOCK_TIME_NONE);
161 }161 }
162 }
163162
164 static gboolean play_idle(gpointer gself)163 return G_SOURCE_CONTINUE; // keep listening
165 {
166 auto self = static_cast<Self*>(gself);
167 self->m_loop_tag = 0;
168 self->play();
169 return G_SOURCE_REMOVE;
170 }164 }
171165
172 /***166 /***
173 ****167 ****
174 ***/168 ***/
175169
176 static int32_t get_next_canberra_id()
177 {
178 static int32_t next_canberra_id = 1;
179 return next_canberra_id++;
180 }
181
182 const std::shared_ptr<Clock> m_clock;170 const std::shared_ptr<Clock> m_clock;
183 const std::string m_filename;171 const std::string m_uri;
184 const unsigned int m_volume;172 const unsigned int m_volume;
185 const bool m_loop;173 const bool m_loop;
186 const int32_t m_canberra_id;
187 const DateTime m_loop_end_time;174 const DateTime m_loop_end_time;
188 ca_context* m_context = nullptr;175 guint m_watch_source = 0;
189 guint m_loop_tag = 0;176 GstElement* m_play = nullptr;
190};177};
191178
192class SoundBuilder179class SoundBuilder
193{180{
194public:181public:
195 void set_clock(const std::shared_ptr<Clock>& c) {m_clock = c;}182 void set_clock(const std::shared_ptr<Clock>& c) {m_clock = c;}
196 void set_filename(const std::string& s) {m_filename = s;}183 void set_uri(const std::string& uri) {m_uri = uri;}
197 void set_volume(const unsigned int v) {m_volume = v;}184 void set_volume(const unsigned int v) {m_volume = v;}
198 void set_duration_minutes(int unsigned i) {m_duration_minutes=i;}185 void set_duration_minutes(int unsigned i) {m_duration_minutes=i;}
199 void set_looping(bool b) {m_looping=b;}186 void set_looping(bool b) {m_looping=b;}
200187
201 Sound* operator()() {188 Sound* operator()() {
202 return new Sound (m_clock,189 return new Sound (m_clock,
203 m_filename,190 m_uri,
204 m_volume,191 m_volume,
205 m_duration_minutes,192 m_duration_minutes,
206 m_looping);193 m_looping);
@@ -208,7 +195,7 @@
208195
209private:196private:
210 std::shared_ptr<Clock> m_clock;197 std::shared_ptr<Clock> m_clock;
211 std::string m_filename;198 std::string m_uri;
212 unsigned int m_volume = 50;199 unsigned int m_volume = 50;
213 unsigned int m_duration_minutes = 30;200 unsigned int m_duration_minutes = 30;
214 bool m_looping = true;201 bool m_looping = true;
@@ -229,14 +216,11 @@
229 {216 {
230 // ensure notify_init() is called once217 // ensure notify_init() is called once
231 // before we start popping up dialogs218 // before we start popping up dialogs
232 static bool m_nn_inited = false;219 static std::once_flag once;
233 if (G_UNLIKELY(!m_nn_inited))220 std::call_once(once, [](){
234 {
235 if(!notify_init("indicator-datetime-service"))221 if(!notify_init("indicator-datetime-service"))
236 g_critical("libnotify initialization failed");222 g_critical("libnotify initialization failed");
237223 });
238 m_nn_inited = true;
239 }
240224
241 show();225 show();
242 }226 }
@@ -367,13 +351,11 @@
367 static bool get_interactive()351 static bool get_interactive()
368 {352 {
369 static bool interactive;353 static bool interactive;
370 static bool inited = false;
371354
372 if (G_UNLIKELY(!inited))355 static std::once_flag once;
373 {356 std::call_once(once, [](){
374 interactive = get_server_caps().count("actions") != 0;357 interactive = get_server_caps().count("actions") != 0;
375 inited = true;358 });
376 }
377359
378 return interactive;360 return interactive;
379 }361 }
@@ -397,38 +379,8 @@
397*** libnotify -- snap decisions379*** libnotify -- snap decisions
398**/380**/
399381
400std::string get_local_filename (const std::string& str)382std::string get_alarm_uri(const Appointment& appointment,
401{383 const std::shared_ptr<const Settings>& settings)
402 std::string ret;
403
404 if (!str.empty())
405 {
406 GFile* files[] = { g_file_new_for_path(str.c_str()),
407 g_file_new_for_uri(str.c_str()) };
408
409 for(auto& file : files)
410 {
411 if (g_file_is_native(file) && g_file_query_exists(file, nullptr))
412 {
413 char* tmp = g_file_get_path(file);
414 if (tmp != nullptr)
415 {
416 ret = tmp;
417 g_free(tmp);
418 break;
419 }
420 }
421 }
422
423 for(auto& file : files)
424 g_object_unref(file);
425 }
426
427 return ret;
428}
429
430std::string get_alarm_sound(const Appointment& appointment,
431 const std::shared_ptr<const Settings>& settings)
432{384{
433 const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"};385 const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"};
434386
@@ -436,20 +388,28 @@
436 settings->alarm_sound.get(),388 settings->alarm_sound.get(),
437 FALLBACK };389 FALLBACK };
438390
439 std::string alarm_sound;391 std::string uri;
440392
441 for(const auto& candidate : candidates)393 for(const auto& candidate : candidates)
442 {394 {
443 alarm_sound = get_local_filename(candidate);395 if (gst_uri_is_valid (candidate.c_str()))
444396 {
445 if (!alarm_sound.empty())397 uri = candidate;
446 break;398 break;
399 }
400 else if (g_file_test(candidate.c_str(), G_FILE_TEST_EXISTS))
401 {
402 gchar* tmp = gst_filename_to_uri(candidate.c_str(), nullptr);
403 if (tmp != nullptr)
404 {
405 uri = tmp;
406 g_free (tmp);
407 break;
408 }
409 }
447 }410 }
448411
449 g_debug("%s: Appointment \"%s\" using alarm sound \"%s\"",412 return uri;
450 G_STRFUNC, appointment.summary.c_str(), alarm_sound.c_str());
451
452 return alarm_sound;
453}413}
454414
455} // unnamed namespace415} // unnamed namespace
@@ -481,7 +441,7 @@
481441
482 // create a popup...442 // create a popup...
483 SoundBuilder sound_builder;443 SoundBuilder sound_builder;
484 sound_builder.set_filename(get_alarm_sound(appointment, m_settings));444 sound_builder.set_uri(get_alarm_uri(appointment, m_settings));
485 sound_builder.set_volume(m_settings->alarm_volume.get());445 sound_builder.set_volume(m_settings->alarm_volume.get());
486 sound_builder.set_clock(m_clock);446 sound_builder.set_clock(m_clock);
487 sound_builder.set_duration_minutes(m_settings->alarm_duration.get());447 sound_builder.set_duration_minutes(m_settings->alarm_duration.get());
488448
=== modified file 'tests/manual'
--- tests/manual 2014-06-30 14:54:34 +0000
+++ tests/manual 2014-07-13 22:23:20 +0000
@@ -40,6 +40,16 @@
40 device actually suspended or whether the suspend was aborted)</dd>40 device actually suspended or whether the suspend was aborted)</dd>
41</dl>41</dl>
4242
43Test-case indicator-datetime/tell-snap-decision-to-dismiss
44<dl>
45 <dt>Set an alarm and wait for it to arrive.</td>
46 <dd>Alarm should go off at the specified time</dd>
47 <dt>Press the 'Dismiss' button in the alarm's snap decision popup before the sound stops.</dt>
48 <dd>Popup should disappear</dd>
49 <dd>Sound should stop at the same time, rather than playing til the end of the file.</dd>
50</dl>
51
52
43<strong>53<strong>
44 If all actions produce the expected results listed, please <a href="results#add_result">submit</a> a 'passed' result.54 If all actions produce the expected results listed, please <a href="results#add_result">submit</a> a 'passed' result.
45 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>.55 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>.
4656
=== modified file 'tests/test-locations.cpp'
--- tests/test-locations.cpp 2014-01-22 15:06:01 +0000
+++ tests/test-locations.cpp 2014-07-13 22:23:20 +0000
@@ -139,7 +139,6 @@
139 EXPECT_EQ("Europe/London", l[3].zone());139 EXPECT_EQ("Europe/London", l[3].zone());
140 EXPECT_EQ("Berlin", l[4].name());140 EXPECT_EQ("Berlin", l[4].name());
141 EXPECT_EQ("Europe/Berlin", l[4].zone());141 EXPECT_EQ("Europe/Berlin", l[4].zone());
142 locations_changed = false;
143}142}
144143
145TEST_F(LocationsFixture, ChangeLocationVisibility)144TEST_F(LocationsFixture, ChangeLocationVisibility)

Subscribers

People subscribed via source and target branches