/*
* 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