1
/*
2
 * Copyright (C) 2010 Igalia S.L.
3
 *
4
 * Contact: mswl-dm-2009@igalia.com
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public License
8
 * as published by the Free Software Foundation; version 2.1 of
9
 * the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful, but
12
 * WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19
 * 02110-1301 USA
20
 *
21
 */
22
23
#include <math.h>
24
25
#include "jmp-mplayer.h"
26
#include "jmp-marshal.h"
27
28
#define INITIAL_VOLUME 1.0
29
#define TICK_INTERVAL 200
30
31
G_DEFINE_TYPE (JmpMplayer, jmp_mplayer, G_TYPE_OBJECT)
32
33
enum {
34
        PROP_0,
35
        PROP_URI,
36
        PROP_VOLUME
37
};
38
39
enum {
40
        END_OF_STREAM,
41
        ERROR,
42
        PLAYBACK_TICK,
43
        LAST_SIGNAL
44
};
45
46
static guint
47
jmp_mplayer_signals[LAST_SIGNAL] = { 0 };
48
49
#define GET_PRIVATE(o) \
50
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), JMP_TYPE_MPLAYER, JmpMplayerPrivate))
51
52
struct _JmpMplayerPrivate {
53
        gchar *uri;
54
        GstElement *element;
55
        gboolean playing;
56
};
57
58
static gchar*
59
get_error_message (GstMessage *message)
60
{
61
        GError *error = NULL;
62
        gchar *debug_info = NULL;
63
        gchar *error_message = NULL;
64
65
        gst_message_parse_error (message, &error, &debug_info);
66
        error_message = g_strdup_printf ("%s\n\t%s", error->message,
67
                                         (debug_info) ? debug_info : "none");
68
69
        g_error_free (error);
70
        g_free (debug_info);
71
        return error_message;
72
}
73
74
static void
75
check_element_state (JmpMplayer *self)
76
{
77
        GstState current_state;
78
        GstState next_state;
79
        GstStateChangeReturn change_return;
80
        change_return = gst_element_get_state (self->priv->element,
81
                        &current_state, &next_state, GST_CLOCK_TIME_NONE);
82
        g_message ("Return: %s\n",
83
                   gst_element_state_change_return_get_name (change_return));
84
        g_message ("Current state: %s\n",
85
                   gst_element_state_get_name (current_state));
86
        g_message ("Next state: %s\n",
87
                   gst_element_state_get_name (next_state));
88
}
89
90
static gboolean
91
message_received_callback (GstBus *bus,
92
                           GstMessage *message,
93
                           gpointer data)
94
{
95
        JmpMplayer *self = JMP_MPLAYER (data);
96
        gchar *error_message = NULL;
97
98
        switch (GST_MESSAGE_TYPE (message)) {
99
        case GST_MESSAGE_EOS:
100
                g_signal_emit (self, jmp_mplayer_signals[END_OF_STREAM], 0);
101
                break;
102
        case GST_MESSAGE_ERROR:
103
                error_message = get_error_message (message);
104
                g_signal_emit (self, jmp_mplayer_signals[ERROR], 0,
105
                               error_message);
106
                g_free (error_message);
107
                break;
108
        default:
109
                /* unhandled messages */
110
                break;
111
        }
112
}
113
114
static void
115
jmp_mplayer_make_playbin (JmpMplayer *self)
116
{
117
        g_return_if_fail (JMP_IS_MPLAYER (self) && !self->priv->element);
118
119
        self->priv->element = gst_element_factory_make ("playbin2", "mainplayer");
120
        g_assert (self->priv->element);
121
        jmp_mplayer_set_volume (self, INITIAL_VOLUME);
122
123
        GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->element));
124
        gst_bus_add_watch (bus, message_received_callback, self);
125
        gst_object_unref (bus);
126
}
127
128
static gboolean
129
jmp_mplayer_emit_tick (gpointer data)
130
{
131
        JmpMplayer *self = JMP_MPLAYER (data);
132
        gint64 position, duration;
133
134
        if (jmp_mplayer_get_position (self, &position) &&
135
                        jmp_mplayer_get_duration (self, &duration)) {
136
137
                g_signal_emit (self, jmp_mplayer_signals[PLAYBACK_TICK], 0, position, duration);
138
139
        }
140
141
        return self->priv->playing;
142
}
143
144
static void
145
jmp_mplayer_get_property (GObject *object, guint property_id,
146
                         GValue *value, GParamSpec *pspec)
147
{
148
        JmpMplayer *self = JMP_MPLAYER (object);
149
150
        switch (property_id) {
151
        case PROP_URI:
152
                g_value_set_string (value, self->priv->uri);
153
                break;
154
        case PROP_VOLUME:
155
                g_value_set_double (value, jmp_mplayer_get_volume (self));
156
                break;
157
        default:
158
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
159
        }
160
}
161
162
static void
163
jmp_mplayer_set_property (GObject *object, guint property_id,
164
                         const GValue *value, GParamSpec *pspec)
165
{
166
        JmpMplayer *self = JMP_MPLAYER (object);
167
168
        switch (property_id) {
169
        case PROP_URI:
170
                jmp_mplayer_set_uri (self, g_value_get_string (value));
171
                break;
172
        case PROP_VOLUME:
173
                jmp_mplayer_set_volume (self, g_value_get_double (value));
174
                break;
175
        default:
176
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
177
        }
178
}
179
180
static void
181
jmp_mplayer_finalize (GObject *object)
182
{
183
        JmpMplayer *self = JMP_MPLAYER (object);
184
185
        if (self->priv->element) {
186
                gst_element_set_state (self->priv->element, GST_STATE_NULL);
187
                gst_object_unref (GST_OBJECT (self->priv->element));
188
        }
189
190
        if (self->priv->uri)
191
                g_free (self->priv->uri);
192
193
        G_OBJECT_CLASS (jmp_mplayer_parent_class)->finalize (object);
194
}
195
196
static void
197
jmp_mplayer_class_init (JmpMplayerClass *klass)
198
{
199
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
200
201
        g_type_class_add_private (klass, sizeof (JmpMplayerPrivate));
202
203
        object_class->get_property = jmp_mplayer_get_property;
204
        object_class->set_property = jmp_mplayer_set_property;
205
        object_class->finalize = jmp_mplayer_finalize;
206
207
        g_object_class_install_property
208
                (object_class, PROP_URI,
209
                 g_param_spec_string ("uri", "Stream URI",
210
                                      "URI of the stream to playback",
211
                                      NULL,
212
                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
213
214
        g_object_class_install_property
215
                (object_class, PROP_VOLUME,
216
                 g_param_spec_double ("volume", "Playback volume",
217
                                 "Volume of the stream to playback",
218
                                 0.0,
219
                                 1.0,
220
                                 1.0,
221
                                 G_PARAM_READWRITE));
222
223
        jmp_mplayer_signals[END_OF_STREAM] =
224
                        g_signal_newv ("end-of-stream",
225
                                        G_TYPE_FROM_CLASS (klass),
226
                                        G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
227
                                        NULL,
228
                                        NULL,
229
                                        NULL,
230
                                        g_cclosure_marshal_VOID__VOID,
231
                                        G_TYPE_NONE,
232
                                        0,
233
                                        NULL);
234
235
        jmp_mplayer_signals[ERROR] =
236
                        g_signal_new ("error",
237
                                        G_TYPE_FROM_CLASS (klass),
238
                                        G_SIGNAL_RUN_LAST,
239
                                        0,
240
                                        NULL,
241
                                        NULL,
242
                                        g_cclosure_marshal_VOID__STRING,
243
                                        G_TYPE_NONE,
244
                                        1,
245
                                        G_TYPE_STRING);
246
247
        jmp_mplayer_signals[PLAYBACK_TICK] =
248
                        g_signal_new ("playback-tick",
249
                                        G_TYPE_FROM_CLASS (klass),
250
                                        G_SIGNAL_RUN_LAST,
251
                                        0,
252
                                        NULL,
253
                                        NULL,
254
                                        jmp_marshal_VOID__INT64_INT64,
255
                                        G_TYPE_NONE,
256
                                        2,
257
                                        G_TYPE_INT64,
258
                                        G_TYPE_INT64,
259
                                        NULL);
260
}
261
262
static void
263
jmp_mplayer_init (JmpMplayer *self)
264
{
265
        self->priv = GET_PRIVATE (self);
266
        self->priv->uri = NULL;
267
        self->priv->element = NULL;
268
}
269
270
JmpMplayer*
271
jmp_mplayer_new (void)
272
{
273
        return g_object_new (JMP_TYPE_MPLAYER, NULL);
274
}
275
276
gboolean
277
jmp_mplayer_set_uri (JmpMplayer *self, const gchar *uri)
278
{
279
        g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE);
280
281
        if (self->priv->uri) {
282
                g_free (self->priv->uri);
283
        }
284
285
        if (self->priv->element) {
286
                gst_element_set_state (self->priv->element, GST_STATE_NULL);
287
        }
288
289
        if (uri) {
290
                self->priv->uri = g_strdup (uri);
291
                if (!self->priv->element) {
292
                        jmp_mplayer_make_playbin (self);
293
                }
294
                g_object_set (G_OBJECT (self->priv->element),
295
                              "uri", self->priv->uri, NULL);
296
        }
297
        return TRUE;
298
}
299
300
gboolean
301
jmp_mplayer_play (JmpMplayer *self)
302
{
303
        g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE);
304
305
        if (self->priv->element && !self->priv->playing) {
306
                gst_element_set_state (self->priv->element, GST_STATE_PLAYING);
307
308
                self->priv->playing = TRUE;
309
                g_timeout_add (TICK_INTERVAL, jmp_mplayer_emit_tick, self);
310
311
                return TRUE;
312
        }
313
        return FALSE;
314
}
315
316
gboolean
317
jmp_mplayer_stop (JmpMplayer *self)
318
{
319
        g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE);
320
321
        if (self->priv->element) {
322
                gst_element_set_state (self->priv->element, GST_STATE_NULL);
323
324
                self->priv->playing = FALSE;
325
326
                return TRUE;
327
        }
328
        return FALSE;
329
}
330
331
gboolean
332
jmp_mplayer_pause (JmpMplayer *self)
333
{
334
        g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE);
335
336
        if (self->priv->element) {
337
                gst_element_set_state (self->priv->element, GST_STATE_PAUSED);
338
339
                self->priv->playing = FALSE;
340
341
                return TRUE;
342
        }
343
        return FALSE;
344
}
345
346
gboolean
347
jmp_mplayer_set_volume (JmpMplayer *self, gdouble volume)
348
{
349
        g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE);
350
351
        g_return_val_if_fail (self->priv->element, FALSE);
352
353
        /* Volume range should be in the [0, 10] interval, according to
354
         * playbin2 documentation */
355
        if (volume > 10.0 || volume < 0.0)
356
                return FALSE;
357
358
        g_object_set (self->priv->element, "volume", volume, NULL);
359
360
        return TRUE;
361
}
362
363
gdouble
364
jmp_mplayer_get_volume (JmpMplayer *self)
365
{
366
        g_return_if_fail (JMP_IS_MPLAYER(self));
367
368
        g_return_if_fail (self->priv->element);
369
        gdouble volume;
370
371
        g_object_get (self->priv->element, "volume", &volume, NULL);
372
        return volume;
373
}
374
375
gboolean
376
jmp_mplayer_toggle_mute (JmpMplayer *self)
377
{
378
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
379
380
        gboolean mute;
381
382
        g_return_val_if_fail (self->priv->element, FALSE);
383
        g_object_get (self->priv->element, "mute", &mute, NULL);
384
        g_object_set (self->priv->element, "mute", !mute, NULL);
385
386
        return TRUE;
387
}
388
389
gboolean
390
jmp_mplayer_get_duration (JmpMplayer *self, gint64 *duration)
391
{
392
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
393
        g_return_val_if_fail (self->priv->element, FALSE);
394
395
        GstFormat format = GST_FORMAT_TIME;
396
        return gst_element_query_duration (self->priv->element, &format, duration);
397
}
398
399
gboolean
400
jmp_mplayer_get_duration_string (JmpMplayer *self, gchar **duration)
401
{
402
403
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
404
        g_return_val_if_fail (self->priv->element, FALSE);
405
406
        gint64 dur;
407
408
        if (jmp_mplayer_get_duration (self, &dur)) {
409
               *duration =  g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
410
               return TRUE;
411
        }
412
413
        return FALSE;
414
}
415
416
gboolean
417
jmp_mplayer_get_position (JmpMplayer *self, gint64 *position)
418
{
419
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
420
        g_return_val_if_fail (self->priv->element, FALSE);
421
422
        GstFormat format = GST_FORMAT_TIME;
423
        return gst_element_query_position (self->priv->element, &format, position);
424
}
425
426
gboolean
427
jmp_mplayer_get_position_in_percentage (JmpMplayer *self, gdouble *position_percentage)
428
{
429
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
430
        g_return_val_if_fail (self->priv->element, FALSE);
431
        g_return_val_if_fail (position_percentage, FALSE);
432
433
        GstFormat format = GST_FORMAT_PERCENT;
434
        gint64 position = 0, duration = 0;
435
436
        if (gst_element_query_position (self->priv->element,
437
                                        &format, &position)) {
438
                *position_percentage = (1.0 * position) / (1.0 * GST_FORMAT_PERCENT_MAX);
439
                return TRUE;
440
        } else {
441
                format = GST_FORMAT_TIME;
442
443
                if (gst_element_query_position (self->priv->element,
444
                                                &format, &position) &&
445
                    gst_element_query_duration (self->priv->element, &format, &duration)) {
446
                        *position_percentage = (1.0 * position) / (1.0 * duration);
447
                        return TRUE;
448
                }
449
        }
450
451
        return FALSE;
452
}
453
454
gboolean
455
jmp_mplayer_get_position_string (JmpMplayer *self, gchar **position)
456
{
457
458
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
459
        g_return_val_if_fail (self->priv->element, FALSE);
460
461
        gint64 pos;
462
463
        if (jmp_mplayer_get_position (self, &pos)) {
464
               *position =  g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
465
               return TRUE;
466
        }
467
468
        return FALSE;
469
}
470
471
gboolean
472
jmp_mplayer_seek (JmpMplayer *self, gint64 seek_position)
473
{
474
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
475
        g_return_val_if_fail (self->priv->element, FALSE);
476
477
        return gst_element_seek_simple (self->priv->element,
478
                        GST_FORMAT_TIME,
479
                        GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
480
                        seek_position);
481
}
482
483
gboolean
484
jmp_mplayer_seek_in_percentage (JmpMplayer *self, gdouble seek_position_percentage)
485
{
486
        g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE);
487
        g_return_val_if_fail (self->priv->element, FALSE);
488
489
        GstFormat format = GST_FORMAT_PERCENT;
490
        gint64 position = 0, duration = 0;
491
492
        position = (gint64)(seek_position_percentage * GST_FORMAT_PERCENT_MAX);
493
494
        if (gst_element_seek_simple (self->priv->element,
495
                                     format,
496
                                     GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
497
                                     position)) {
498
                return TRUE;
499
        } else {
500
                format = GST_FORMAT_TIME;
501
502
                if (gst_element_query_duration (self->priv->element, &format,
503
                                                &duration)) {
504
505
                        position = nearbyint (duration * seek_position_percentage);
506
507
                        gst_element_seek_simple
508
                                (self->priv->element,
509
                                 format,
510
                                 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
511
                                 position);
512
                        return TRUE;
513
                }
514
        }
515
        return FALSE;
516
}