/* * Copyright 2014 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Charles Kerr */ #include #include #include // std::call_once() namespace ayatana { namespace indicator { namespace notifications { /*** **** ***/ /** * Plays a sound, possibly looping. */ class Sound::Impl { public: Impl(const std::string& role, const std::string& uri, unsigned int volume, bool loop): m_role(role), m_uri(uri), m_volume(volume), m_loop(loop) { // init GST once static std::once_flag once; std::call_once(once, [](){ GError* error = nullptr; if (!gst_init_check (nullptr, nullptr, &error)) { g_critical("Unable to play alarm sound: %s", error->message); g_error_free(error); } }); 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); g_debug("Playing '%s'", m_uri.c_str()); 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); } ~Impl() { 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: // convert settings range [1..100] to gst playbin's range is [0...1.0] gdouble get_volume() const { 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 gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself) { auto self = static_cast(gself); const auto message_type = GST_MESSAGE_TYPE(msg); if ((message_type == GST_MESSAGE_EOS) && (self->m_loop)) { 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); } else if (message_type == GST_MESSAGE_STREAM_START) { /* Set the media role if audio sink is pulsesink */ GstElement *audio_sink = nullptr; g_object_get(self->m_play, "audio-sink", &audio_sink, nullptr); if (audio_sink) { GstPluginFeature *feature = nullptr; feature = GST_PLUGIN_FEATURE_CAST(GST_ELEMENT_GET_CLASS(audio_sink)->elementfactory); if (feature && g_strcmp0(gst_plugin_feature_get_name(feature), "pulsesink") == 0) { auto role_str = g_strdup_printf("props,media.role=%s", self->m_role.c_str()); GstStructure *props = gst_structure_from_string(role_str, nullptr); g_object_set(audio_sink, "stream-properties", props, nullptr); gst_structure_free(props); g_free(role_str); } gst_object_unref(audio_sink); } } return G_SOURCE_CONTINUE; // keep listening } /*** **** ***/ const std::string m_role; const std::string m_uri; const unsigned int m_volume; const bool m_loop; guint m_watch_source = 0; GstElement* m_play = nullptr; }; Sound::Sound(const std::string& role, const std::string& uri, unsigned int volume, bool loop): impl (new Impl(role, uri, volume, loop)) { } Sound::~Sound() { } /*** **** ***/ } // namespace notifications } // namespace indicator } // namespace ayatana