~ps-jenkins/indicator-datetime/utopic-proposed

« back to all changes in this revision

Viewing changes to src/snap.cpp

  • Committer: CI bot
  • Author(s): Charles Kerr
  • Date: 2014-07-14 13:40:32 UTC
  • mfrom: (352.1.6 use-gst)
  • Revision ID: ps-jenkins@lists.canonical.com-20140714134032-0wl9oy7f60af40uy
Use GStreamer's API directly to play sound instead of using libcanberra. Fixes: 1283065, 1337348
Approved by: Ted Gould, PS Jenkins bot

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
 
24
24
#include <core/signal.h>
25
25
 
26
 
#include <canberra.h>
 
26
#include <gst/gst.h>
27
27
#include <libnotify/notify.h>
28
28
 
29
29
#include <glib/gi18n.h>
30
30
#include <glib.h>
31
31
 
 
32
#include <mutex> // std::call_once()
32
33
#include <set>
33
34
#include <string>
34
35
 
53
54
public:
54
55
 
55
56
    Sound(const std::shared_ptr<Clock>& clock,
56
 
          const std::string& filename,
 
57
          const std::string& uri,
57
58
          unsigned int volume,
58
59
          unsigned int duration_minutes,
59
60
          bool loop):
60
61
        m_clock(clock),
61
 
        m_filename(filename),
 
62
        m_uri(uri),
62
63
        m_volume(volume),
63
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, duration_minutes, 0.0))
 
65
        m_loop_end_time(clock->localtime().add_full(0, 0, 0, 0, (int)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
79
        if (m_loop)
68
80
        {
69
81
            g_debug("Looping '%s' until cutoff time %s",
70
 
                    m_filename.c_str(),
 
82
                    m_uri.c_str(),
71
83
                    m_loop_end_time.format("%F %T").c_str());
72
84
        }
73
85
        else
74
86
        {
75
 
            g_debug("Playing '%s' once", m_filename.c_str());
76
 
        }
77
 
 
78
 
        const auto rv = ca_context_create(&m_context);
79
 
        if (rv == CA_SUCCESS)
80
 
        {
81
 
            play();
82
 
        }
83
 
        else
84
 
        {
85
 
            g_warning("Failed to create canberra context: %s", ca_strerror(rv));
86
 
            m_context = nullptr;
87
 
        }
 
87
            g_debug("Playing '%s' once", m_uri.c_str());
 
88
        }
 
89
 
 
90
        m_play = gst_element_factory_make("playbin", "play");
 
91
 
 
92
        auto bus = gst_pipeline_get_bus(GST_PIPELINE(m_play));
 
93
        m_watch_source = gst_bus_add_watch(bus, bus_callback, this);
 
94
        gst_object_unref(bus);
 
95
 
 
96
        play();
88
97
    }
89
98
 
90
99
    ~Sound()
91
100
    {
92
101
        stop();
93
102
 
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
    }
96
111
 
97
112
private:
98
113
 
99
114
    void stop()
100
115
    {
101
 
        if (m_context != nullptr)
102
 
        {
103
 
            const auto rv = ca_context_cancel(m_context, m_canberra_id);
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;
 
116
        if (m_play != nullptr)
 
117
        {
 
118
            gst_element_set_state (m_play, GST_STATE_PAUSED);
112
119
        }
113
120
    }
114
121
 
115
122
    void play()
116
123
    {
117
 
        auto context = m_context;
118
 
        g_return_if_fail(context != nullptr);
119
 
 
120
 
        const auto filename = m_filename.c_str();
121
 
        const float gain = get_gain_level(m_volume);
122
 
 
123
 
        ca_proplist* props = nullptr;
124
 
        ca_proplist_create(&props);
125
 
        ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
126
 
        ca_proplist_setf(props, CA_PROP_CANBERRA_VOLUME, "%f", gain);
127
 
        const auto rv = ca_context_play_full(context, m_canberra_id, props,
128
 
                                             on_done_playing, this);
129
 
        if (rv != CA_SUCCESS)
130
 
            g_warning("Unable to play '%s': %s", filename, ca_strerror(rv));
131
 
 
132
 
        g_clear_pointer(&props, ca_proplist_destroy);
133
 
    }
134
 
 
135
 
    static float get_gain_level(unsigned int volume)
136
 
    {
137
 
        const unsigned int clamped_volume = CLAMP(volume, 1, 100);
138
 
 
139
 
        /* This range isn't set in stone --
140
 
           arrived at from manual tests on Nextus 4 */
141
 
        constexpr float gain_low = -10;
142
 
        constexpr float gain_high = 10;
143
 
 
144
 
        constexpr float gain_range = gain_high - gain_low;
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)
 
124
       g_return_if_fail(m_play != nullptr);
 
125
 
 
126
       g_object_set(G_OBJECT (m_play), "uri", m_uri.c_str(),
 
127
                                       "volume", get_volume(),
 
128
                                       nullptr);
 
129
       gst_element_set_state (m_play, GST_STATE_PLAYING);
 
130
    }
 
131
 
 
132
    // convert settings range [1..100] to gst playbin's range is [0...1.0]
 
133
    gdouble get_volume() const
 
134
    {
 
135
        constexpr int in_range_lo = 1;
 
136
        constexpr int in_range_hi = 100;
 
137
        const double in = CLAMP(m_volume, in_range_lo, in_range_hi);
 
138
        const double pct = (in - in_range_lo) / (in_range_hi - in_range_lo);
 
139
 
 
140
        constexpr double out_range_lo = 0.0; 
 
141
        constexpr double out_range_hi = 1.0; 
 
142
        return out_range_lo + (pct * (out_range_hi - out_range_lo));
 
143
    }
 
144
 
 
145
    static gboolean bus_callback(GstBus*, GstMessage* msg, gpointer gself)
 
146
    {
 
147
        auto self = static_cast<Sound*>(gself);
 
148
 
 
149
        if ((GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) &&
 
150
            (self->m_loop) &&
 
151
            (self->m_clock->localtime() < self->m_loop_end_time))
153
152
        {
154
 
            auto self = static_cast<Self*>(gself);
155
 
            if ((self->m_loop_tag == 0) &&
156
 
                (self->m_loop) &&
157
 
                (self->m_clock->localtime() < self->m_loop_end_time))
158
 
            {
159
 
                self->m_loop_tag = g_timeout_add_seconds(1, play_idle, self);
160
 
            }
 
153
            gst_element_seek(self->m_play,
 
154
                             1.0,
 
155
                             GST_FORMAT_TIME,
 
156
                             GST_SEEK_FLAG_FLUSH,
 
157
                             GST_SEEK_TYPE_SET,
 
158
                             0,
 
159
                             GST_SEEK_TYPE_NONE,
 
160
                             (gint64)GST_CLOCK_TIME_NONE);
161
161
        }
162
 
    }
163
162
 
164
 
    static gboolean play_idle(gpointer gself)
165
 
    {
166
 
        auto self = static_cast<Self*>(gself);
167
 
        self->m_loop_tag = 0;
168
 
        self->play();
169
 
        return G_SOURCE_REMOVE;
 
163
        return G_SOURCE_CONTINUE; // keep listening
170
164
    }
171
165
 
172
166
    /***
173
167
    ****
174
168
    ***/
175
169
 
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
170
    const std::shared_ptr<Clock> m_clock;
183
 
    const std::string m_filename;
 
171
    const std::string m_uri;
184
172
    const unsigned int m_volume;
185
173
    const bool m_loop;
186
 
    const int32_t m_canberra_id;
187
174
    const DateTime m_loop_end_time;
188
 
    ca_context* m_context = nullptr;
189
 
    guint m_loop_tag = 0;
 
175
    guint m_watch_source = 0;
 
176
    GstElement* m_play = nullptr;
190
177
};
191
178
 
192
179
class SoundBuilder
193
180
{
194
181
public:
195
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
184
    void set_volume(const unsigned int v) {m_volume = v;}
198
185
    void set_duration_minutes(int unsigned i) {m_duration_minutes=i;}
199
186
    void set_looping(bool b) {m_looping=b;}
200
187
 
201
188
    Sound* operator()() {
202
189
        return new Sound (m_clock,
203
 
                          m_filename,
 
190
                          m_uri,
204
191
                          m_volume,
205
192
                          m_duration_minutes,
206
193
                          m_looping);
208
195
 
209
196
private:
210
197
    std::shared_ptr<Clock> m_clock;
211
 
    std::string m_filename;
 
198
    std::string m_uri;
212
199
    unsigned int m_volume = 50;
213
200
    unsigned int m_duration_minutes = 30;
214
201
    bool m_looping = true;
229
216
    {
230
217
        // ensure notify_init() is called once
231
218
        // before we start popping up dialogs
232
 
        static bool m_nn_inited = false;
233
 
        if (G_UNLIKELY(!m_nn_inited))
234
 
        {
 
219
        static std::once_flag once;
 
220
        std::call_once(once, [](){
235
221
            if(!notify_init("indicator-datetime-service"))
236
222
                g_critical("libnotify initialization failed");
237
 
 
238
 
            m_nn_inited = true;
239
 
        }
 
223
        });
240
224
 
241
225
        show();
242
226
    }
367
351
    static bool get_interactive()
368
352
    {
369
353
        static bool interactive;
370
 
        static bool inited = false;
371
354
 
372
 
        if (G_UNLIKELY(!inited))
373
 
        {
 
355
        static std::once_flag once;
 
356
        std::call_once(once, [](){
374
357
            interactive = get_server_caps().count("actions") != 0;
375
 
            inited = true;
376
 
        }
 
358
        });
377
359
 
378
360
        return interactive;
379
361
    }
397
379
***  libnotify -- snap decisions
398
380
**/
399
381
 
400
 
std::string get_local_filename (const std::string& str)
401
 
{
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
 
 
430
 
std::string get_alarm_sound(const Appointment& appointment,
431
 
                            const std::shared_ptr<const Settings>& settings)
 
382
std::string get_alarm_uri(const Appointment& appointment,
 
383
                          const std::shared_ptr<const Settings>& settings)
432
384
{
433
385
    const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"};
434
386
 
436
388
                                       settings->alarm_sound.get(),
437
389
                                       FALLBACK };
438
390
 
439
 
    std::string alarm_sound;
 
391
    std::string uri;
440
392
 
441
393
    for(const auto& candidate : candidates)
442
394
    {
443
 
        alarm_sound = get_local_filename(candidate);
444
 
 
445
 
        if (!alarm_sound.empty())
 
395
        if (gst_uri_is_valid (candidate.c_str()))
 
396
        {
 
397
            uri = candidate;
446
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
    }
448
411
 
449
 
    g_debug("%s: Appointment \"%s\" using alarm sound \"%s\"",
450
 
            G_STRFUNC, appointment.summary.c_str(), alarm_sound.c_str());
451
 
 
452
 
    return alarm_sound;
 
412
    return uri;
453
413
}
454
414
 
455
415
} // unnamed namespace
481
441
 
482
442
    // create a popup...
483
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
445
    sound_builder.set_volume(m_settings->alarm_volume.get());
486
446
    sound_builder.set_clock(m_clock);
487
447
    sound_builder.set_duration_minutes(m_settings->alarm_duration.get());