diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2014-07-14 13:40:32 +0000 |
---|---|---|
committer | CI bot <ps-jenkins@lists.canonical.com> | 2014-07-14 13:40:32 +0000 |
commit | e213a80ef5ad8fd0d5b83c4def9e3360dd4e1057 (patch) | |
tree | 38a93a33d839cf25d6c193de1750a2f804e9d784 | |
parent | 2a28f458808556af4cda58a36997ab465cb360fb (diff) | |
parent | 3c78427b294d9c52ec265ab2e7533bf535a0c89d (diff) | |
download | ayatana-indicator-datetime-e213a80ef5ad8fd0d5b83c4def9e3360dd4e1057.tar.gz ayatana-indicator-datetime-e213a80ef5ad8fd0d5b83c4def9e3360dd4e1057.tar.bz2 ayatana-indicator-datetime-e213a80ef5ad8fd0d5b83c4def9e3360dd4e1057.zip |
Use GStreamer's API directly to play sound instead of using libcanberra. Fixes: 1283065, 1337348
Approved by: Ted Gould, PS Jenkins bot
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | debian/control | 2 | ||||
-rw-r--r-- | src/snap.cpp | 232 | ||||
-rw-r--r-- | tests/manual | 10 | ||||
-rw-r--r-- | tests/test-locations.cpp | 1 |
5 files changed, 108 insertions, 139 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1390f44..9b4987e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ pkg_check_modules (SERVICE_DEPS REQUIRED libical>=0.48 libecal-1.2>=3.5 libedataserver-1.2>=3.5 - libcanberra>=0.12 + gstreamer-1.0>=1.2 libnotify>=0.7.6 url-dispatcher-1>=1 properties-cpp>=0.0.1) diff --git a/debian/control b/debian/control index 5f45bea..bd4988f 100644 --- a/debian/control +++ b/debian/control @@ -13,7 +13,7 @@ Build-Depends: cmake, libgtest-dev, libglib2.0-dev (>= 2.35.4), libnotify-dev (>= 0.7.6), - libcanberra-dev, + libgstreamer1.0-dev, libecal1.2-dev (>= 3.5), libical-dev (>= 1.0), libedataserver1.2-dev (>= 3.5), diff --git a/src/snap.cpp b/src/snap.cpp index eab7001..4495287 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -23,12 +23,13 @@ #include <core/signal.h> -#include <canberra.h> +#include <gst/gst.h> #include <libnotify/notify.h> #include <glib/gi18n.h> #include <glib.h> +#include <mutex> // std::call_once() #include <set> #include <string> @@ -53,154 +54,140 @@ class Sound public: Sound(const std::shared_ptr<Clock>& clock, - const std::string& filename, + const std::string& uri, unsigned int volume, unsigned int duration_minutes, bool loop): m_clock(clock), - m_filename(filename), + m_uri(uri), m_volume(volume), m_loop(loop), - m_canberra_id(get_next_canberra_id()), - m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, duration_minutes, 0.0)) + m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, (int)duration_minutes, 0.0)) { + // init GST once + static std::once_flag once; + std::call_once(once, [](){ + GError* error = nullptr; + gst_init_check (nullptr, nullptr, &error); + if (error) + { + g_critical("Unable to play alarm sound: %s", error->message); + g_error_free(error); + } + }); + if (m_loop) { g_debug("Looping '%s' until cutoff time %s", - m_filename.c_str(), + m_uri.c_str(), m_loop_end_time.format("%F %T").c_str()); } else { - g_debug("Playing '%s' once", m_filename.c_str()); + g_debug("Playing '%s' once", m_uri.c_str()); } - const auto rv = ca_context_create(&m_context); - if (rv == CA_SUCCESS) - { - play(); - } - else - { - g_warning("Failed to create canberra context: %s", ca_strerror(rv)); - m_context = nullptr; - } + m_play = gst_element_factory_make("playbin", "play"); + + auto bus = gst_pipeline_get_bus(GST_PIPELINE(m_play)); + m_watch_source = gst_bus_add_watch(bus, bus_callback, this); + gst_object_unref(bus); + + play(); } ~Sound() { stop(); - g_clear_pointer(&m_context, ca_context_destroy); + g_source_remove(m_watch_source); + + if (m_play != nullptr) + { + gst_element_set_state (m_play, GST_STATE_NULL); + g_clear_pointer (&m_play, gst_object_unref); + } } private: void stop() { - if (m_context != nullptr) + if (m_play != nullptr) { - const auto rv = ca_context_cancel(m_context, m_canberra_id); - if (rv != CA_SUCCESS) - g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv)); - } - - if (m_loop_tag != 0) - { - g_source_remove(m_loop_tag); - m_loop_tag = 0; + gst_element_set_state (m_play, GST_STATE_PAUSED); } } void play() { - auto context = m_context; - g_return_if_fail(context != nullptr); - - const auto filename = m_filename.c_str(); - const float gain = get_gain_level(m_volume); - - ca_proplist* props = nullptr; - ca_proplist_create(&props); - ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename); - ca_proplist_setf(props, CA_PROP_CANBERRA_VOLUME, "%f", gain); - const auto rv = ca_context_play_full(context, m_canberra_id, props, - on_done_playing, this); - if (rv != CA_SUCCESS) - g_warning("Unable to play '%s': %s", filename, ca_strerror(rv)); - - g_clear_pointer(&props, ca_proplist_destroy); + g_return_if_fail(m_play != nullptr); + + g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(), + "volume", get_volume(), + nullptr); + gst_element_set_state (m_play, GST_STATE_PLAYING); } - static float get_gain_level(unsigned int volume) + // convert settings range [1..100] to gst playbin's range is [0...1.0] + gdouble get_volume() const { - const unsigned int clamped_volume = CLAMP(volume, 1, 100); - - /* This range isn't set in stone -- - arrived at from manual tests on Nextus 4 */ - constexpr float gain_low = -10; - constexpr float gain_high = 10; - - constexpr float gain_range = gain_high - gain_low; - return gain_low + (gain_range * (clamped_volume / 100.0f)); + constexpr int in_range_lo = 1; + constexpr int in_range_hi = 100; + const double in = CLAMP(m_volume, in_range_lo, in_range_hi); + const double pct = (in - in_range_lo) / (in_range_hi - in_range_lo); + + constexpr double out_range_lo = 0.0; + constexpr double out_range_hi = 1.0; + return out_range_lo + (pct * (out_range_hi - out_range_lo)); } - static void on_done_playing(ca_context*, uint32_t, int rv, void* gself) + static gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself) { - // if we still need to loop, wait a second, then play it again + auto self = static_cast<Sound*>(gself); - if (rv == CA_SUCCESS) + if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) && + (self->m_loop) && + (self->m_clock->localtime() < self->m_loop_end_time)) { - auto self = static_cast<Self*>(gself); - if ((self->m_loop_tag == 0) && - (self->m_loop) && - (self->m_clock->localtime() < self->m_loop_end_time)) - { - self->m_loop_tag = g_timeout_add_seconds(1, play_idle, self); - } + gst_element_seek(self->m_play, + 1.0, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, + 0, + GST_SEEK_TYPE_NONE, + (gint64)GST_CLOCK_TIME_NONE); } - } - static gboolean play_idle(gpointer gself) - { - auto self = static_cast<Self*>(gself); - self->m_loop_tag = 0; - self->play(); - return G_SOURCE_REMOVE; + return G_SOURCE_CONTINUE; // keep listening } /*** **** ***/ - static int32_t get_next_canberra_id() - { - static int32_t next_canberra_id = 1; - return next_canberra_id++; - } - const std::shared_ptr<Clock> m_clock; - const std::string m_filename; + const std::string m_uri; const unsigned int m_volume; const bool m_loop; - const int32_t m_canberra_id; const DateTime m_loop_end_time; - ca_context* m_context = nullptr; - guint m_loop_tag = 0; + guint m_watch_source = 0; + GstElement* m_play = nullptr; }; class SoundBuilder { public: void set_clock(const std::shared_ptr<Clock>& c) {m_clock = c;} - void set_filename(const std::string& s) {m_filename = s;} + void set_uri(const std::string& uri) {m_uri = uri;} void set_volume(const unsigned int v) {m_volume = v;} void set_duration_minutes(int unsigned i) {m_duration_minutes=i;} void set_looping(bool b) {m_looping=b;} Sound* operator()() { return new Sound (m_clock, - m_filename, + m_uri, m_volume, m_duration_minutes, m_looping); @@ -208,7 +195,7 @@ public: private: std::shared_ptr<Clock> m_clock; - std::string m_filename; + std::string m_uri; unsigned int m_volume = 50; unsigned int m_duration_minutes = 30; bool m_looping = true; @@ -229,14 +216,11 @@ public: { // ensure notify_init() is called once // before we start popping up dialogs - static bool m_nn_inited = false; - if (G_UNLIKELY(!m_nn_inited)) - { + static std::once_flag once; + std::call_once(once, [](){ if(!notify_init("indicator-datetime-service")) g_critical("libnotify initialization failed"); - - m_nn_inited = true; - } + }); show(); } @@ -367,13 +351,11 @@ private: static bool get_interactive() { static bool interactive; - static bool inited = false; - if (G_UNLIKELY(!inited)) - { + static std::once_flag once; + std::call_once(once, [](){ interactive = get_server_caps().count("actions") != 0; - inited = true; - } + }); return interactive; } @@ -397,38 +379,8 @@ private: *** libnotify -- snap decisions **/ -std::string get_local_filename (const std::string& str) -{ - std::string ret; - - if (!str.empty()) - { - GFile* files[] = { g_file_new_for_path(str.c_str()), - g_file_new_for_uri(str.c_str()) }; - - for(auto& file : files) - { - if (g_file_is_native(file) && g_file_query_exists(file, nullptr)) - { - char* tmp = g_file_get_path(file); - if (tmp != nullptr) - { - ret = tmp; - g_free(tmp); - break; - } - } - } - - for(auto& file : files) - g_object_unref(file); - } - - return ret; -} - -std::string get_alarm_sound(const Appointment& appointment, - const std::shared_ptr<const Settings>& settings) +std::string get_alarm_uri(const Appointment& appointment, + const std::shared_ptr<const Settings>& settings) { const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; @@ -436,20 +388,28 @@ std::string get_alarm_sound(const Appointment& appointment, settings->alarm_sound.get(), FALLBACK }; - std::string alarm_sound; + std::string uri; for(const auto& candidate : candidates) { - alarm_sound = get_local_filename(candidate); - - if (!alarm_sound.empty()) + if (gst_uri_is_valid (candidate.c_str())) + { + uri = candidate; break; + } + else if (g_file_test(candidate.c_str(), G_FILE_TEST_EXISTS)) + { + gchar* tmp = gst_filename_to_uri(candidate.c_str(), nullptr); + if (tmp != nullptr) + { + uri = tmp; + g_free (tmp); + break; + } + } } - g_debug("%s: Appointment \"%s\" using alarm sound \"%s\"", - G_STRFUNC, appointment.summary.c_str(), alarm_sound.c_str()); - - return alarm_sound; + return uri; } } // unnamed namespace @@ -481,7 +441,7 @@ void Snap::operator()(const Appointment& appointment, // create a popup... SoundBuilder sound_builder; - sound_builder.set_filename(get_alarm_sound(appointment, m_settings)); + sound_builder.set_uri(get_alarm_uri(appointment, m_settings)); sound_builder.set_volume(m_settings->alarm_volume.get()); sound_builder.set_clock(m_clock); sound_builder.set_duration_minutes(m_settings->alarm_duration.get()); diff --git a/tests/manual b/tests/manual index ecbcebb..c5c52bc 100644 --- a/tests/manual +++ b/tests/manual @@ -40,6 +40,16 @@ Test-case indicator-datetime/edited-alarm-wakeup device actually suspended or whether the suspend was aborted)</dd> </dl> +Test-case indicator-datetime/tell-snap-decision-to-dismiss +<dl> + <dt>Set an alarm and wait for it to arrive.</td> + <dd>Alarm should go off at the specified time</dd> + <dt>Press the 'Dismiss' button in the alarm's snap decision popup before the sound stops.</dt> + <dd>Popup should disappear</dd> + <dd>Sound should stop at the same time, rather than playing til the end of the file.</dd> +</dl> + + <strong> If all actions produce the expected results listed, please <a href="results#add_result">submit</a> a 'passed' result. 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>. diff --git a/tests/test-locations.cpp b/tests/test-locations.cpp index 65adbc7..48e845a 100644 --- a/tests/test-locations.cpp +++ b/tests/test-locations.cpp @@ -139,7 +139,6 @@ TEST_F(LocationsFixture, ChangeLocationStrings) EXPECT_EQ("Europe/London", l[3].zone()); EXPECT_EQ("Berlin", l[4].name()); EXPECT_EQ("Europe/Berlin", l[4].zone()); - locations_changed = false; } TEST_F(LocationsFixture, ChangeLocationVisibility) |