/* * Copyright (C) 2010 Igalia S.L. * * Contact: mswl-dm-2009@igalia.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include "jmp-mplayer.h" #include "jmp-marshal.h" #define INITIAL_VOLUME 1.0 #define TICK_INTERVAL 200 G_DEFINE_TYPE (JmpMplayer, jmp_mplayer, G_TYPE_OBJECT) enum { PROP_0, PROP_URI, PROP_VOLUME }; enum { END_OF_STREAM, ERROR, PLAYBACK_TICK, LAST_SIGNAL }; static guint jmp_mplayer_signals[LAST_SIGNAL] = { 0 }; #define GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), JMP_TYPE_MPLAYER, JmpMplayerPrivate)) struct _JmpMplayerPrivate { gchar *uri; GstElement *element; gboolean playing; }; static gchar* get_error_message (GstMessage *message) { GError *error = NULL; gchar *debug_info = NULL; gchar *error_message = NULL; gst_message_parse_error (message, &error, &debug_info); error_message = g_strdup_printf ("%s\n\t%s", error->message, (debug_info) ? debug_info : "none"); g_error_free (error); g_free (debug_info); return error_message; } static void check_element_state (JmpMplayer *self) { GstState current_state; GstState next_state; GstStateChangeReturn change_return; change_return = gst_element_get_state (self->priv->element, ¤t_state, &next_state, GST_CLOCK_TIME_NONE); g_message ("Return: %s\n", gst_element_state_change_return_get_name (change_return)); g_message ("Current state: %s\n", gst_element_state_get_name (current_state)); g_message ("Next state: %s\n", gst_element_state_get_name (next_state)); } static gboolean message_received_callback (GstBus *bus, GstMessage *message, gpointer data) { JmpMplayer *self = JMP_MPLAYER (data); gchar *error_message = NULL; switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_EOS: g_signal_emit (self, jmp_mplayer_signals[END_OF_STREAM], 0); break; case GST_MESSAGE_ERROR: error_message = get_error_message (message); g_signal_emit (self, jmp_mplayer_signals[ERROR], 0, error_message); g_free (error_message); break; default: /* unhandled messages */ break; } } static void jmp_mplayer_make_playbin (JmpMplayer *self) { g_return_if_fail (JMP_IS_MPLAYER (self) && !self->priv->element); self->priv->element = gst_element_factory_make ("playbin2", "mainplayer"); g_assert (self->priv->element); jmp_mplayer_set_volume (self, INITIAL_VOLUME); GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->element)); gst_bus_add_watch (bus, message_received_callback, self); gst_object_unref (bus); } static gboolean jmp_mplayer_emit_tick (gpointer data) { JmpMplayer *self = JMP_MPLAYER (data); gint64 position, duration; if (jmp_mplayer_get_position (self, &position) && jmp_mplayer_get_duration (self, &duration)) { g_signal_emit (self, jmp_mplayer_signals[PLAYBACK_TICK], 0, position, duration); } return self->priv->playing; } static void jmp_mplayer_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { JmpMplayer *self = JMP_MPLAYER (object); switch (property_id) { case PROP_URI: g_value_set_string (value, self->priv->uri); break; case PROP_VOLUME: g_value_set_double (value, jmp_mplayer_get_volume (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void jmp_mplayer_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { JmpMplayer *self = JMP_MPLAYER (object); switch (property_id) { case PROP_URI: jmp_mplayer_set_uri (self, g_value_get_string (value)); break; case PROP_VOLUME: jmp_mplayer_set_volume (self, g_value_get_double (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void jmp_mplayer_finalize (GObject *object) { JmpMplayer *self = JMP_MPLAYER (object); if (self->priv->element) { gst_element_set_state (self->priv->element, GST_STATE_NULL); gst_object_unref (GST_OBJECT (self->priv->element)); } if (self->priv->uri) g_free (self->priv->uri); G_OBJECT_CLASS (jmp_mplayer_parent_class)->finalize (object); } static void jmp_mplayer_class_init (JmpMplayerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (JmpMplayerPrivate)); object_class->get_property = jmp_mplayer_get_property; object_class->set_property = jmp_mplayer_set_property; object_class->finalize = jmp_mplayer_finalize; g_object_class_install_property (object_class, PROP_URI, g_param_spec_string ("uri", "Stream URI", "URI of the stream to playback", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_VOLUME, g_param_spec_double ("volume", "Playback volume", "Volume of the stream to playback", 0.0, 1.0, 1.0, G_PARAM_READWRITE)); jmp_mplayer_signals[END_OF_STREAM] = g_signal_newv ("end-of-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, NULL, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); jmp_mplayer_signals[ERROR] = g_signal_new ("error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); jmp_mplayer_signals[PLAYBACK_TICK] = g_signal_new ("playback-tick", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, jmp_marshal_VOID__INT64_INT64, G_TYPE_NONE, 2, G_TYPE_INT64, G_TYPE_INT64, NULL); } static void jmp_mplayer_init (JmpMplayer *self) { self->priv = GET_PRIVATE (self); self->priv->uri = NULL; self->priv->element = NULL; } JmpMplayer* jmp_mplayer_new (void) { return g_object_new (JMP_TYPE_MPLAYER, NULL); } gboolean jmp_mplayer_set_uri (JmpMplayer *self, const gchar *uri) { g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE); if (self->priv->uri) { g_free (self->priv->uri); } if (self->priv->element) { gst_element_set_state (self->priv->element, GST_STATE_NULL); } if (uri) { self->priv->uri = g_strdup (uri); if (!self->priv->element) { jmp_mplayer_make_playbin (self); } g_object_set (G_OBJECT (self->priv->element), "uri", self->priv->uri, NULL); } return TRUE; } gboolean jmp_mplayer_play (JmpMplayer *self) { g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE); if (self->priv->element && !self->priv->playing) { gst_element_set_state (self->priv->element, GST_STATE_PLAYING); self->priv->playing = TRUE; g_timeout_add (TICK_INTERVAL, jmp_mplayer_emit_tick, self); return TRUE; } return FALSE; } gboolean jmp_mplayer_stop (JmpMplayer *self) { g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE); if (self->priv->element) { gst_element_set_state (self->priv->element, GST_STATE_NULL); self->priv->playing = FALSE; return TRUE; } return FALSE; } gboolean jmp_mplayer_pause (JmpMplayer *self) { g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE); if (self->priv->element) { gst_element_set_state (self->priv->element, GST_STATE_PAUSED); self->priv->playing = FALSE; return TRUE; } return FALSE; } gboolean jmp_mplayer_set_volume (JmpMplayer *self, gdouble volume) { g_return_val_if_fail (JMP_IS_MPLAYER(self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); /* Volume range should be in the [0, 10] interval, according to * playbin2 documentation */ if (volume > 10.0 || volume < 0.0) return FALSE; g_object_set (self->priv->element, "volume", volume, NULL); return TRUE; } gdouble jmp_mplayer_get_volume (JmpMplayer *self) { g_return_if_fail (JMP_IS_MPLAYER(self)); g_return_if_fail (self->priv->element); gdouble volume; g_object_get (self->priv->element, "volume", &volume, NULL); return volume; } gboolean jmp_mplayer_toggle_mute (JmpMplayer *self) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); gboolean mute; g_return_val_if_fail (self->priv->element, FALSE); g_object_get (self->priv->element, "mute", &mute, NULL); g_object_set (self->priv->element, "mute", !mute, NULL); return TRUE; } gboolean jmp_mplayer_get_duration (JmpMplayer *self, gint64 *duration) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); GstFormat format = GST_FORMAT_TIME; return gst_element_query_duration (self->priv->element, &format, duration); } gboolean jmp_mplayer_get_duration_string (JmpMplayer *self, gchar **duration) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); gint64 dur; if (jmp_mplayer_get_duration (self, &dur)) { *duration = g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (dur)); return TRUE; } return FALSE; } gboolean jmp_mplayer_get_position (JmpMplayer *self, gint64 *position) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); GstFormat format = GST_FORMAT_TIME; return gst_element_query_position (self->priv->element, &format, position); } gboolean jmp_mplayer_get_position_in_percentage (JmpMplayer *self, gdouble *position_percentage) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); g_return_val_if_fail (position_percentage, FALSE); GstFormat format = GST_FORMAT_PERCENT; gint64 position = 0, duration = 0; if (gst_element_query_position (self->priv->element, &format, &position)) { *position_percentage = (1.0 * position) / (1.0 * GST_FORMAT_PERCENT_MAX); return TRUE; } else { format = GST_FORMAT_TIME; if (gst_element_query_position (self->priv->element, &format, &position) && gst_element_query_duration (self->priv->element, &format, &duration)) { *position_percentage = (1.0 * position) / (1.0 * duration); return TRUE; } } return FALSE; } gboolean jmp_mplayer_get_position_string (JmpMplayer *self, gchar **position) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); gint64 pos; if (jmp_mplayer_get_position (self, &pos)) { *position = g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); return TRUE; } return FALSE; } gboolean jmp_mplayer_seek (JmpMplayer *self, gint64 seek_position) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); return gst_element_seek_simple (self->priv->element, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, seek_position); } gboolean jmp_mplayer_seek_in_percentage (JmpMplayer *self, gdouble seek_position_percentage) { g_return_val_if_fail (JMP_IS_MPLAYER (self), FALSE); g_return_val_if_fail (self->priv->element, FALSE); GstFormat format = GST_FORMAT_PERCENT; gint64 position = 0, duration = 0; position = (gint64)(seek_position_percentage * GST_FORMAT_PERCENT_MAX); if (gst_element_seek_simple (self->priv->element, format, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, position)) { return TRUE; } else { format = GST_FORMAT_TIME; if (gst_element_query_duration (self->priv->element, &format, &duration)) { position = nearbyint (duration * seek_position_percentage); gst_element_seek_simple (self->priv->element, format, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, position); return TRUE; } } return FALSE; }