1854 lines
48 KiB
C
1854 lines
48 KiB
C
/* Heavily based on totems bacon-video-widget .. */
|
|
|
|
#include "clutter-video-texture.h"
|
|
#include "clutter-main.h"
|
|
#include "clutter-private.h" /* for DBG */
|
|
#include "clutter-marshal.h"
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/video/gstvideosink.h>
|
|
#include <gst/audio/gstbaseaudiosink.h>
|
|
|
|
#include <GL/glx.h>
|
|
#include <GL/gl.h>
|
|
|
|
#include <glib.h>
|
|
|
|
static void
|
|
got_time_tick (GstElement *play,
|
|
gint64 time_nanos,
|
|
ClutterVideoTexture *video_texture);
|
|
static void
|
|
stop_play_pipeline (ClutterVideoTexture *video_texture);
|
|
|
|
G_DEFINE_TYPE (ClutterVideoTexture, \
|
|
clutter_video_texture, \
|
|
CLUTTER_TYPE_TEXTURE);
|
|
|
|
enum
|
|
{
|
|
SIGNAL_ERROR,
|
|
SIGNAL_EOS,
|
|
SIGNAL_REDIRECT,
|
|
SIGNAL_TITLE_CHANGE,
|
|
SIGNAL_CHANNELS_CHANGE,
|
|
SIGNAL_TICK,
|
|
SIGNAL_GOT_METADATA,
|
|
SIGNAL_BUFFERING,
|
|
SIGNAL_SPEED_WARNING,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
/* Properties */
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_POSITION,
|
|
PROP_CURRENT_TIME,
|
|
PROP_STREAM_LENGTH,
|
|
PROP_PLAYING,
|
|
PROP_SEEKABLE,
|
|
};
|
|
|
|
struct ClutterVideoTexturePrivate
|
|
{
|
|
GstElement *play, *video_sink, *audio_sink;
|
|
gboolean has_video, has_audio;
|
|
gint64 stream_length;
|
|
gint64 current_time_nanos;
|
|
gint64 current_time;
|
|
float current_position;
|
|
gchar *mrl;
|
|
|
|
gboolean got_redirect;
|
|
gint eos_id;
|
|
guint update_id;
|
|
|
|
GstTagList *tagcache, *audiotags, *videotags;
|
|
|
|
const GValue *movie_par; /* Movie pixel aspect ratio */
|
|
gint video_fps_d, video_fps_n;
|
|
gint video_width, video_height;
|
|
ClutterVideoTextureAspectRatio ratio_type;
|
|
|
|
GstMessageType ignore_messages_mask;
|
|
GstBus *bus;
|
|
gulong sig_bus_async;
|
|
};
|
|
|
|
static int cvt_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
GQuark
|
|
clutter_video_texture_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("clutter-video-texture-error-quark");
|
|
}
|
|
|
|
/* This is a hack to avoid doing poll_for_state_change() indirectly
|
|
* from the bus message callback (via EOS => totem => close => wait for READY)
|
|
* and deadlocking there. We need something like a
|
|
* gst_bus_set_auto_flushing(bus, FALSE) ... */
|
|
static gboolean
|
|
signal_eos_delayed (gpointer user_data)
|
|
{
|
|
ClutterVideoTexture *video_texture = (ClutterVideoTexture*)user_data;
|
|
|
|
g_signal_emit (video_texture, cvt_signals[SIGNAL_EOS], 0, NULL);
|
|
|
|
video_texture->priv->eos_id = 0;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
query_timeout (ClutterVideoTexture *video_texture)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
GstFormat fmt = GST_FORMAT_TIME;
|
|
gint64 prev_len = -1, pos = -1, len = -1;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
/* check length/pos of stream */
|
|
prev_len = priv->stream_length;
|
|
|
|
if (gst_element_query_duration (priv->play, &fmt, &len))
|
|
{
|
|
if (len != -1 && fmt == GST_FORMAT_TIME)
|
|
{
|
|
priv->stream_length = len / GST_MSECOND;
|
|
if (priv->stream_length != prev_len)
|
|
{
|
|
g_signal_emit (video_texture,
|
|
cvt_signals[SIGNAL_GOT_METADATA], 0, NULL);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CLUTTER_DBG ("could not get duration");
|
|
}
|
|
|
|
if (gst_element_query_position (priv->play, &fmt, &pos))
|
|
{
|
|
if (pos != -1 && fmt == GST_FORMAT_TIME)
|
|
{
|
|
got_time_tick (GST_ELEMENT (priv->play), pos, video_texture);
|
|
}
|
|
}
|
|
else
|
|
CLUTTER_DBG ("could not get position");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
got_video_size (ClutterVideoTexture *video_texture)
|
|
{
|
|
GstMessage *msg;
|
|
|
|
g_return_if_fail (video_texture != NULL);
|
|
|
|
/* Do we even care about this info as comes from texture sizing ? */
|
|
CLUTTER_DBG("%ix%i",
|
|
video_texture->priv->video_width,
|
|
video_texture->priv->video_height);
|
|
|
|
msg = gst_message_new_application
|
|
(GST_OBJECT (video_texture->priv->play),
|
|
gst_structure_new ("video-size",
|
|
"width", G_TYPE_INT,
|
|
video_texture->priv->video_width,
|
|
"height", G_TYPE_INT,
|
|
video_texture->priv->video_height, NULL));
|
|
|
|
gst_element_post_message (video_texture->priv->play, msg);
|
|
}
|
|
|
|
|
|
static void
|
|
caps_set (GObject *obj,
|
|
GParamSpec *pspec,
|
|
ClutterVideoTexture *video_texture)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
GstPad *pad = GST_PAD (obj);
|
|
GstStructure *s;
|
|
GstCaps *caps;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
if (!(caps = gst_pad_get_negotiated_caps (pad)))
|
|
return;
|
|
|
|
/* Get video decoder caps */
|
|
s = gst_caps_get_structure (caps, 0);
|
|
|
|
/* Again do we even need this - sizing info from texture signal.. */
|
|
|
|
if (s)
|
|
{
|
|
/* We need at least width/height and framerate */
|
|
if (!(gst_structure_get_fraction (s, "framerate",
|
|
&priv->video_fps_n,
|
|
&priv->video_fps_d)
|
|
&&
|
|
gst_structure_get_int (s, "width", &priv->video_width) &&
|
|
gst_structure_get_int (s, "height", &priv->video_height)))
|
|
return;
|
|
|
|
/* Get the movie PAR if available */
|
|
priv->movie_par = gst_structure_get_value (s, "pixel-aspect-ratio");
|
|
|
|
/* Now set for real */
|
|
clutter_video_texture_set_aspect_ratio (video_texture, priv->ratio_type);
|
|
}
|
|
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
static void
|
|
parse_stream_info (ClutterVideoTexture *video_texture)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
GList *streaminfo = NULL;
|
|
GstPad *videopad = NULL;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
g_object_get (priv->play, "stream-info", &streaminfo, NULL);
|
|
|
|
streaminfo = g_list_copy (streaminfo);
|
|
|
|
g_list_foreach (streaminfo, (GFunc) g_object_ref, NULL);
|
|
|
|
for ( ; streaminfo != NULL; streaminfo = streaminfo->next)
|
|
{
|
|
GObject *info = streaminfo->data;
|
|
gint type;
|
|
GParamSpec *pspec;
|
|
GEnumValue *val;
|
|
|
|
if (!info)
|
|
continue;
|
|
|
|
g_object_get (info, "type", &type, NULL);
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info), "type");
|
|
val = g_enum_get_value (G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
|
|
|
|
if (!g_strcasecmp (val->value_nick, "audio"))
|
|
{
|
|
priv->has_audio = TRUE;
|
|
}
|
|
else if (!g_strcasecmp (val->value_nick, "video"))
|
|
{
|
|
priv->has_video = TRUE;
|
|
|
|
if (!videopad)
|
|
g_object_get (info, "object", &videopad, NULL);
|
|
}
|
|
}
|
|
|
|
if (videopad)
|
|
{
|
|
GstCaps *caps;
|
|
|
|
if ((caps = gst_pad_get_negotiated_caps (videopad)))
|
|
{
|
|
caps_set (G_OBJECT (videopad), NULL, video_texture);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
g_signal_connect (videopad, "notify::caps",
|
|
G_CALLBACK (caps_set), video_texture);
|
|
}
|
|
|
|
g_list_foreach (streaminfo, (GFunc) g_object_unref, NULL);
|
|
g_list_free (streaminfo);
|
|
}
|
|
|
|
static void
|
|
handle_element_message (ClutterVideoTexture *video_texture, GstMessage *msg)
|
|
{
|
|
const gchar *type_name = NULL;
|
|
gchar *src_name;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
src_name = gst_object_get_name (msg->src);
|
|
|
|
if (msg->structure)
|
|
type_name = gst_structure_get_name (msg->structure);
|
|
|
|
if (type_name == NULL)
|
|
goto unhandled;
|
|
|
|
if (strcmp (type_name, "redirect") == 0)
|
|
{
|
|
const gchar *new_location;
|
|
|
|
new_location = gst_structure_get_string (msg->structure, "new-location");
|
|
|
|
CLUTTER_DBG ("Got redirect to '%s'", GST_STR_NULL (new_location));
|
|
|
|
if (new_location && *new_location)
|
|
{
|
|
g_signal_emit (video_texture,
|
|
cvt_signals[SIGNAL_REDIRECT],
|
|
0,
|
|
new_location);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
unhandled:
|
|
CLUTTER_DBG ("Unhandled element message '%s' from element '%s'",
|
|
GST_STR_NULL (type_name), src_name);
|
|
done:
|
|
g_free (src_name);
|
|
}
|
|
|
|
static void
|
|
handle_application_message (ClutterVideoTexture *video_texture,
|
|
GstMessage *msg)
|
|
{
|
|
const gchar *msg_name;
|
|
|
|
msg_name = gst_structure_get_name (msg->structure);
|
|
|
|
g_return_if_fail (msg_name != NULL);
|
|
|
|
CLUTTER_DBG ("Handling application message");
|
|
|
|
if (strcmp (msg_name, "notify-streaminfo") == 0)
|
|
{
|
|
g_signal_emit (video_texture, cvt_signals[SIGNAL_GOT_METADATA], 0, NULL);
|
|
g_signal_emit (video_texture, cvt_signals[SIGNAL_CHANNELS_CHANGE], 0);
|
|
}
|
|
else if (strcmp (msg_name, "video-size") == 0)
|
|
{
|
|
g_signal_emit (video_texture, cvt_signals[SIGNAL_GOT_METADATA], 0, NULL);
|
|
|
|
CLUTTER_DBG("Got video size");
|
|
}
|
|
else
|
|
CLUTTER_DBG ("Unhandled application message %s", msg_name);
|
|
}
|
|
|
|
static void
|
|
bus_message_cb (GstBus *bus, GstMessage *message, gpointer data)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
ClutterVideoTexture *video_texture = (ClutterVideoTexture*)data;
|
|
GstMessageType msg_type;
|
|
|
|
g_return_if_fail (video_texture != NULL);
|
|
g_return_if_fail (CLUTTER_IS_VIDEO_TEXTURE(video_texture));
|
|
|
|
priv = video_texture->priv;
|
|
|
|
msg_type = GST_MESSAGE_TYPE (message);
|
|
|
|
/* somebody else is handling the message,
|
|
probably in poll_for_state_change */
|
|
if (priv->ignore_messages_mask & msg_type)
|
|
{
|
|
gchar *src_name = gst_object_get_name (message->src);
|
|
CLUTTER_DBG ("Ignoring %s message from element %s as requested",
|
|
gst_message_type_get_name (msg_type), src_name);
|
|
g_free (src_name);
|
|
return;
|
|
}
|
|
|
|
if (msg_type != GST_MESSAGE_STATE_CHANGED && clutter_want_debug())
|
|
{
|
|
gchar *src_name = gst_object_get_name (message->src);
|
|
CLUTTER_DBG ("Handling %s message from element %s",
|
|
gst_message_type_get_name (msg_type), src_name);
|
|
g_free (src_name);
|
|
}
|
|
|
|
switch (msg_type)
|
|
{
|
|
case GST_MESSAGE_ERROR:
|
|
{
|
|
GError *error = NULL;
|
|
gchar *debug = NULL;
|
|
|
|
gst_message_parse_error (message, &error, &debug);
|
|
|
|
CLUTTER_DBG ("Error message: %s [%s]",
|
|
GST_STR_NULL (error->message),
|
|
GST_STR_NULL (debug));
|
|
|
|
g_signal_emit (video_texture,
|
|
cvt_signals[SIGNAL_ERROR],
|
|
0,
|
|
error->message,
|
|
TRUE,
|
|
FALSE);
|
|
|
|
g_error_free (error);
|
|
|
|
if (priv->play)
|
|
gst_element_set_state (priv->play, GST_STATE_NULL);
|
|
|
|
g_free (debug);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_WARNING:
|
|
{
|
|
GError *error = NULL;
|
|
gchar *debug = NULL;
|
|
|
|
gst_message_parse_warning (message, &error, &debug);
|
|
|
|
g_warning ("%s [%s]",
|
|
GST_STR_NULL (error->message),
|
|
GST_STR_NULL (debug));
|
|
|
|
g_error_free (error);
|
|
g_free (debug);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_TAG:
|
|
{
|
|
GstTagList *tag_list, *result;
|
|
GstElementFactory *f;
|
|
|
|
gst_message_parse_tag (message, &tag_list);
|
|
|
|
CLUTTER_DBG ("Tags: %p", tag_list);
|
|
|
|
/* all tags */
|
|
result = gst_tag_list_merge (priv->tagcache,
|
|
tag_list,
|
|
GST_TAG_MERGE_KEEP);
|
|
|
|
if (priv->tagcache)
|
|
gst_tag_list_free (priv->tagcache);
|
|
priv->tagcache = result;
|
|
|
|
/* media-type-specific tags */
|
|
if (GST_IS_ELEMENT (message->src) &&
|
|
(f = gst_element_get_factory (GST_ELEMENT (message->src))))
|
|
{
|
|
const gchar *klass = gst_element_factory_get_klass (f);
|
|
GstTagList **cache = NULL;
|
|
|
|
if (g_strrstr (klass, "Video"))
|
|
{
|
|
cache = &priv->videotags;
|
|
}
|
|
else if (g_strrstr (klass, "Audio"))
|
|
{
|
|
cache = &priv->audiotags;
|
|
}
|
|
|
|
if (cache)
|
|
{
|
|
result = gst_tag_list_merge (*cache, tag_list,
|
|
GST_TAG_MERGE_KEEP);
|
|
if (*cache)
|
|
gst_tag_list_free (*cache);
|
|
*cache = result;
|
|
}
|
|
}
|
|
|
|
gst_tag_list_free (tag_list);
|
|
g_signal_emit (video_texture, cvt_signals[SIGNAL_GOT_METADATA], 0);
|
|
break;
|
|
}
|
|
case GST_MESSAGE_EOS:
|
|
CLUTTER_DBG ("GST_MESSAGE_EOS");
|
|
|
|
if (priv->eos_id == 0)
|
|
priv->eos_id = g_idle_add (signal_eos_delayed, video_texture);
|
|
break;
|
|
case GST_MESSAGE_BUFFERING:
|
|
{
|
|
gint percent = 0;
|
|
gst_structure_get_int (message->structure, "buffer-percent", &percent);
|
|
|
|
CLUTTER_DBG ("Buffering message (%u%%)", percent);
|
|
|
|
g_signal_emit (video_texture,
|
|
cvt_signals[SIGNAL_BUFFERING],
|
|
0,
|
|
percent);
|
|
break;
|
|
}
|
|
|
|
case GST_MESSAGE_APPLICATION:
|
|
handle_application_message (video_texture, message);
|
|
break;
|
|
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
{
|
|
GstState old_state, new_state;
|
|
gchar *src_name;
|
|
|
|
CLUTTER_DBG ("GST_MESSAGE_STATE_CHANGED");
|
|
|
|
gst_message_parse_state_changed (message,
|
|
&old_state, &new_state, NULL);
|
|
|
|
if (old_state == new_state)
|
|
break;
|
|
|
|
/* we only care about playbin (pipeline) state changes */
|
|
if (GST_MESSAGE_SRC (message) != GST_OBJECT (priv->play))
|
|
break;
|
|
|
|
src_name = gst_object_get_name (message->src);
|
|
|
|
CLUTTER_DBG ("%s changed state from %s to %s", src_name,
|
|
gst_element_state_get_name (old_state),
|
|
gst_element_state_get_name (new_state));
|
|
|
|
g_free (src_name);
|
|
|
|
if (new_state <= GST_STATE_PAUSED)
|
|
{
|
|
if (priv->update_id != 0)
|
|
{
|
|
CLUTTER_DBG ("removing tick timeout");
|
|
g_source_remove (priv->update_id);
|
|
priv->update_id = 0;
|
|
}
|
|
}
|
|
else if (new_state > GST_STATE_PAUSED)
|
|
{
|
|
if (priv->update_id == 0)
|
|
{
|
|
CLUTTER_DBG ("starting tick timeout");
|
|
|
|
priv->update_id = g_timeout_add (200,
|
|
(GSourceFunc) query_timeout,
|
|
video_texture);
|
|
}
|
|
}
|
|
|
|
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED)
|
|
{
|
|
parse_stream_info (video_texture);
|
|
}
|
|
else if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_READY)
|
|
{
|
|
priv->has_video = FALSE;
|
|
priv->has_audio = FALSE;
|
|
|
|
/* clean metadata cache */
|
|
if (priv->tagcache)
|
|
{
|
|
gst_tag_list_free (priv->tagcache);
|
|
priv->tagcache = NULL;
|
|
}
|
|
|
|
if (priv->audiotags)
|
|
{
|
|
gst_tag_list_free (priv->audiotags);
|
|
priv->audiotags = NULL;
|
|
}
|
|
|
|
if (priv->videotags)
|
|
{
|
|
gst_tag_list_free (priv->videotags);
|
|
priv->videotags = NULL;
|
|
}
|
|
|
|
priv->video_width = 0;
|
|
priv->video_height = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GST_MESSAGE_ELEMENT:
|
|
handle_element_message (video_texture, message);
|
|
break;
|
|
|
|
case GST_MESSAGE_DURATION:
|
|
{
|
|
CLUTTER_DBG ("GST_MESSAGE_DURATION");
|
|
/* force _get_stream_length() to do new duration query */
|
|
priv->stream_length = 0;
|
|
if (clutter_video_texture_get_stream_length (video_texture) == 0)
|
|
CLUTTER_DBG ("Failed to query duration after DURATION message?!");
|
|
break;
|
|
}
|
|
|
|
case GST_MESSAGE_CLOCK_PROVIDE:
|
|
case GST_MESSAGE_CLOCK_LOST:
|
|
case GST_MESSAGE_NEW_CLOCK:
|
|
case GST_MESSAGE_STATE_DIRTY:
|
|
break;
|
|
default:
|
|
CLUTTER_DBG ("Unhandled message of type '%s' (0x%x)",
|
|
gst_message_type_get_name (msg_type), msg_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
got_time_tick (GstElement *play,
|
|
gint64 time_nanos,
|
|
ClutterVideoTexture *video_texture)
|
|
{
|
|
gboolean seekable;
|
|
ClutterVideoTexturePrivate *priv;
|
|
|
|
g_return_if_fail (video_texture != NULL);
|
|
g_return_if_fail (CLUTTER_IS_VIDEO_TEXTURE(video_texture));
|
|
|
|
priv = video_texture->priv;
|
|
|
|
priv->current_time_nanos = time_nanos;
|
|
priv->current_time = (gint64) time_nanos / GST_MSECOND;
|
|
|
|
if (priv->stream_length == 0)
|
|
{
|
|
priv->current_position = 0;
|
|
seekable = clutter_video_texture_is_seekable (video_texture);
|
|
}
|
|
else
|
|
{
|
|
priv->current_position =
|
|
(gfloat) priv->current_time / priv->stream_length;
|
|
seekable = TRUE;
|
|
}
|
|
|
|
g_signal_emit (video_texture,
|
|
cvt_signals[SIGNAL_TICK],
|
|
0,
|
|
priv->current_time,
|
|
priv->stream_length,
|
|
priv->current_position,
|
|
seekable);
|
|
}
|
|
|
|
static void
|
|
playbin_got_source (GObject *play,
|
|
GParamSpec *pspec,
|
|
ClutterVideoTexture *video_texture)
|
|
{
|
|
/* Called via notify::source on playbin */
|
|
|
|
ClutterVideoTexturePrivate *priv;
|
|
GObject *source = NULL;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
if (priv->tagcache)
|
|
{
|
|
gst_tag_list_free (priv->tagcache);
|
|
priv->tagcache = NULL;
|
|
}
|
|
|
|
if (priv->audiotags)
|
|
{
|
|
gst_tag_list_free (priv->audiotags);
|
|
priv->audiotags = NULL;
|
|
}
|
|
|
|
if (priv->videotags)
|
|
{
|
|
gst_tag_list_free (priv->videotags);
|
|
priv->videotags = NULL;
|
|
}
|
|
|
|
g_object_get (play, "source", &source, NULL);
|
|
|
|
if (!source)
|
|
return;
|
|
|
|
g_object_unref (source);
|
|
}
|
|
|
|
static void
|
|
playbin_stream_info_set (GObject *obj,
|
|
GParamSpec *pspec,
|
|
ClutterVideoTexture *video_texture)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
GstMessage *msg;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
parse_stream_info (video_texture);
|
|
|
|
msg = gst_message_new_application (GST_OBJECT (priv->play),
|
|
gst_structure_new ("notify-streaminfo",
|
|
NULL));
|
|
gst_element_post_message (priv->play, msg);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
poll_for_state_change_full (ClutterVideoTexture *video_texture,
|
|
GstElement *element,
|
|
GstState state,
|
|
GError **error,
|
|
gint64 timeout)
|
|
{
|
|
GstBus *bus;
|
|
GstMessageType events, saved_events;
|
|
ClutterVideoTexturePrivate *priv;
|
|
|
|
priv = video_texture->priv;
|
|
bus = gst_element_get_bus (element);
|
|
saved_events = priv->ignore_messages_mask;
|
|
|
|
events = GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS;
|
|
|
|
if (element != NULL && element == priv->play)
|
|
{
|
|
/* we do want the main handler to process state changed messages for
|
|
* playbin as well, otherwise it won't hook up the timeout etc. */
|
|
priv->ignore_messages_mask |= (events ^ GST_MESSAGE_STATE_CHANGED);
|
|
}
|
|
else
|
|
priv->ignore_messages_mask |= events;
|
|
|
|
while (TRUE)
|
|
{
|
|
GstMessage *message;
|
|
GstElement *src;
|
|
|
|
message = gst_bus_poll (bus, events, timeout);
|
|
|
|
if (!message)
|
|
goto timed_out;
|
|
|
|
src = (GstElement*)GST_MESSAGE_SRC (message);
|
|
|
|
switch (GST_MESSAGE_TYPE (message))
|
|
{
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
{
|
|
GstState old, new, pending;
|
|
|
|
if (src == element)
|
|
{
|
|
gst_message_parse_state_changed (message,
|
|
&old, &new, &pending);
|
|
if (new == state)
|
|
{
|
|
gst_message_unref (message);
|
|
goto success;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case GST_MESSAGE_ERROR:
|
|
{
|
|
gchar *debug = NULL;
|
|
GError *gsterror = NULL;
|
|
|
|
gst_message_parse_error (message, &gsterror, &debug);
|
|
|
|
g_warning ("Error: %s (%s)", gsterror->message, debug);
|
|
|
|
gst_message_unref (message);
|
|
g_error_free (gsterror);
|
|
g_free (debug);
|
|
goto error;
|
|
}
|
|
break;
|
|
case GST_MESSAGE_EOS:
|
|
g_set_error (error, CLUTTER_VIDEO_TEXTURE_ERROR,
|
|
CLUTTER_VIDEO_TEXTURE_ERROR_FILE_GENERIC,
|
|
"Media file could not be played.");
|
|
gst_message_unref (message);
|
|
goto error;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
gst_message_unref (message);
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
|
|
success:
|
|
/* state change succeeded */
|
|
CLUTTER_DBG ("state change to %s succeeded",
|
|
gst_element_state_get_name (state));
|
|
|
|
priv->ignore_messages_mask = saved_events;
|
|
return TRUE;
|
|
|
|
timed_out:
|
|
/* it's taking a long time to open -- just tell totem it was ok, this allows
|
|
* the user to stop the loading process with the normal stop button */
|
|
CLUTTER_DBG ("state change to %s timed out, returning success and handling "
|
|
"errors asynchroneously", gst_element_state_get_name (state));
|
|
priv->ignore_messages_mask = saved_events;
|
|
return TRUE;
|
|
|
|
error:
|
|
CLUTTER_DBG ("error while waiting for state change to %s: %s",
|
|
gst_element_state_get_name (state),
|
|
(error && *error) ? (*error)->message : "unknown");
|
|
priv->ignore_messages_mask = saved_events;
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
poll_for_state_change (ClutterVideoTexture *video_texture,
|
|
GstElement *element,
|
|
GstState state,
|
|
GError **error)
|
|
{
|
|
return poll_for_state_change_full (video_texture,
|
|
element,
|
|
state,
|
|
error,
|
|
GST_SECOND/4 );
|
|
}
|
|
|
|
static void
|
|
fakesink_handoff_cb (GstElement *fakesrc,
|
|
GstBuffer *buffer,
|
|
GstPad *pad,
|
|
gpointer user_data)
|
|
{
|
|
|
|
GstStructure *structure;
|
|
int width, height;
|
|
GdkPixbuf *pixb;
|
|
|
|
structure = gst_caps_get_structure(GST_CAPS(buffer->caps), 0);
|
|
gst_structure_get_int(structure, "width", &width);
|
|
gst_structure_get_int(structure, "height", &height);
|
|
|
|
/* FIXME: We really dont want to do this every time as gobject creation
|
|
* really need a clutter_texture_set_from_data call ?
|
|
*/
|
|
pixb = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buffer),
|
|
GDK_COLORSPACE_RGB,
|
|
FALSE,
|
|
8,
|
|
width,
|
|
height,
|
|
(3 * width + 3) &~ 3,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (pixb)
|
|
{
|
|
clutter_texture_set_pixbuf (CLUTTER_TEXTURE(user_data), pixb);
|
|
g_object_unref(G_OBJECT(pixb));
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_video_texture_finalize (GObject *object)
|
|
{
|
|
ClutterVideoTexture *self;
|
|
ClutterVideoTexturePrivate *priv;
|
|
|
|
self = CLUTTER_VIDEO_TEXTURE(object);
|
|
priv = self->priv;
|
|
|
|
if (priv->bus)
|
|
{
|
|
/* make bus drop all messages to make sure none of our callbacks is ever
|
|
* called again (main loop might be run again to display error dialog) */
|
|
gst_bus_set_flushing (priv->bus, TRUE);
|
|
|
|
if (priv->sig_bus_async)
|
|
g_signal_handler_disconnect (priv->bus, priv->sig_bus_async);
|
|
|
|
gst_object_unref (priv->bus);
|
|
priv->bus = NULL;
|
|
}
|
|
|
|
if (priv->mrl)
|
|
g_free (priv->mrl);
|
|
priv->mrl = NULL;
|
|
|
|
if (priv->play != NULL && GST_IS_ELEMENT (priv->play))
|
|
{
|
|
gst_element_set_state (priv->play, GST_STATE_NULL);
|
|
gst_object_unref (priv->play);
|
|
priv->play = NULL;
|
|
}
|
|
|
|
if (priv->update_id)
|
|
{
|
|
g_source_remove (priv->update_id);
|
|
priv->update_id = 0;
|
|
}
|
|
|
|
if (priv->tagcache)
|
|
{
|
|
gst_tag_list_free (priv->tagcache);
|
|
priv->tagcache = NULL;
|
|
}
|
|
|
|
if (priv->audiotags)
|
|
{
|
|
gst_tag_list_free (priv->audiotags);
|
|
priv->audiotags = NULL;
|
|
}
|
|
|
|
if (priv->videotags)
|
|
{
|
|
gst_tag_list_free (priv->videotags);
|
|
priv->videotags = NULL;
|
|
}
|
|
|
|
if (priv->eos_id != 0)
|
|
g_source_remove (priv->eos_id);
|
|
|
|
g_free (priv);
|
|
|
|
self->priv = NULL;
|
|
|
|
G_OBJECT_CLASS (clutter_video_texture_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
clutter_video_texture_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id)
|
|
{
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_video_texture_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterVideoTexture *video_texture;
|
|
|
|
video_texture = CLUTTER_VIDEO_TEXTURE (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_POSITION:
|
|
g_value_set_int64 (value,
|
|
clutter_video_texture_get_position (video_texture));
|
|
break;
|
|
case PROP_STREAM_LENGTH:
|
|
g_value_set_int64 (value,
|
|
clutter_video_texture_get_stream_length (video_texture));
|
|
break;
|
|
case PROP_PLAYING:
|
|
g_value_set_boolean (value,
|
|
clutter_video_texture_is_playing (video_texture));
|
|
break;
|
|
case PROP_SEEKABLE:
|
|
g_value_set_boolean (value,
|
|
clutter_video_texture_is_seekable (video_texture));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
clutter_video_texture_class_init (ClutterVideoTextureClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
ClutterElementClass *element_class;
|
|
|
|
object_class = (GObjectClass*)klass;
|
|
element_class = (ClutterElementClass*)klass;
|
|
|
|
object_class->finalize = clutter_video_texture_finalize;
|
|
object_class->set_property = clutter_video_texture_set_property;
|
|
object_class->get_property = clutter_video_texture_get_property;
|
|
|
|
/* Properties */
|
|
g_object_class_install_property (object_class, PROP_POSITION,
|
|
g_param_spec_int ("position", NULL, NULL,
|
|
0, G_MAXINT, 0,
|
|
G_PARAM_READABLE));
|
|
g_object_class_install_property (object_class, PROP_STREAM_LENGTH,
|
|
g_param_spec_int64 ("stream_length", NULL,
|
|
NULL, 0, G_MAXINT64, 0,
|
|
G_PARAM_READABLE));
|
|
g_object_class_install_property (object_class, PROP_PLAYING,
|
|
g_param_spec_boolean ("playing", NULL,
|
|
NULL, FALSE,
|
|
G_PARAM_READABLE));
|
|
g_object_class_install_property (object_class, PROP_SEEKABLE,
|
|
g_param_spec_boolean ("seekable", NULL,
|
|
NULL, FALSE,
|
|
G_PARAM_READABLE));
|
|
|
|
/* Signals */
|
|
cvt_signals[SIGNAL_ERROR] =
|
|
g_signal_new ("error",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, error),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__STRING_BOOLEAN_BOOLEAN,
|
|
G_TYPE_NONE, 3, G_TYPE_STRING,
|
|
G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
|
|
|
|
cvt_signals[SIGNAL_EOS] =
|
|
g_signal_new ("eos",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, eos),
|
|
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
|
|
|
|
cvt_signals[SIGNAL_GOT_METADATA] =
|
|
g_signal_new ("got-metadata",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, got_metadata),
|
|
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
|
|
|
|
cvt_signals[SIGNAL_REDIRECT] =
|
|
g_signal_new ("got-redirect",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, got_redirect),
|
|
NULL, NULL, g_cclosure_marshal_VOID__STRING,
|
|
G_TYPE_NONE, 1, G_TYPE_STRING);
|
|
|
|
cvt_signals[SIGNAL_TITLE_CHANGE] =
|
|
g_signal_new ("title-change",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, title_change),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__STRING,
|
|
G_TYPE_NONE, 1, G_TYPE_STRING);
|
|
|
|
cvt_signals[SIGNAL_CHANNELS_CHANGE] =
|
|
g_signal_new ("channels-change",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, channels_change),
|
|
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
|
|
|
|
cvt_signals[SIGNAL_TICK] =
|
|
g_signal_new ("tick",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, tick),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__INT64_INT64_FLOAT_BOOLEAN,
|
|
G_TYPE_NONE, 4, G_TYPE_INT64, G_TYPE_INT64, G_TYPE_FLOAT,
|
|
G_TYPE_BOOLEAN);
|
|
|
|
cvt_signals[SIGNAL_BUFFERING] =
|
|
g_signal_new ("buffering",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, buffering),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
|
|
|
|
cvt_signals[SIGNAL_SPEED_WARNING] =
|
|
g_signal_new ("speed-warning",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterVideoTextureClass, speed_warning),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
}
|
|
|
|
|
|
static void
|
|
clutter_video_texture_init (ClutterVideoTexture *video_texture)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
GstElement *audio_sink, *video_sink, *bin, *capsfilter;
|
|
GstCaps *filtercaps;
|
|
GstPad *ghost_pad;
|
|
|
|
priv = g_new0 (ClutterVideoTexturePrivate, 1);
|
|
video_texture->priv = priv;
|
|
|
|
priv->ratio_type = CLUTTER_VIDEO_TEXTURE_AUTO;
|
|
|
|
priv->play = gst_element_factory_make ("playbin", "play");
|
|
|
|
if (!priv->play)
|
|
{
|
|
g_warning ("Could not create element 'playbin'");
|
|
return;
|
|
}
|
|
|
|
priv->bus = gst_element_get_bus (priv->play);
|
|
gst_bus_add_signal_watch (priv->bus);
|
|
priv->sig_bus_async = g_signal_connect (priv->bus,
|
|
"message",
|
|
G_CALLBACK (bus_message_cb),
|
|
video_texture);
|
|
|
|
audio_sink = gst_element_factory_make ("gconfaudiosink", "audio-sink");
|
|
|
|
if (audio_sink == NULL)
|
|
{
|
|
g_warning ("Could not create element 'gconfaudiosink' trying autosink");
|
|
audio_sink = gst_element_factory_make ("autoaudiosink", "audio-sink");
|
|
|
|
if (audio_sink == NULL)
|
|
{
|
|
g_warning ("Could not create element 'autoaudiosink' "
|
|
"trying fakesink");
|
|
audio_sink = gst_element_factory_make ("fakesink",
|
|
"audio-fake-sink");
|
|
if (audio_sink == NULL)
|
|
{
|
|
g_warning ("Could not create element 'fakesink' for audio, giving up. ");
|
|
}
|
|
}
|
|
}
|
|
|
|
priv->audio_sink = audio_sink;
|
|
|
|
video_sink = gst_element_factory_make ("fakesink", "fakesink");
|
|
|
|
if (video_sink == NULL)
|
|
{
|
|
g_warning ("Could not create element 'fakesink' for video playback");
|
|
return;
|
|
}
|
|
|
|
bin = gst_bin_new ("video-bin");
|
|
|
|
capsfilter = gst_element_factory_make ("capsfilter", "vfilter");
|
|
|
|
filtercaps
|
|
= gst_caps_new_simple("video/x-raw-rgb",
|
|
"bpp", G_TYPE_INT, 24,
|
|
"depth", G_TYPE_INT, 24,
|
|
"endianness", G_TYPE_INT, G_BIG_ENDIAN,
|
|
"red_mask", G_TYPE_INT, 0xff0000 /* >> 8 for 24bpp */,
|
|
"green_mask", G_TYPE_INT, 0xff00,
|
|
"blue_mask", G_TYPE_INT, 0xff,
|
|
"width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
|
|
"height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
|
|
"framerate", GST_TYPE_FRACTION_RANGE,
|
|
0, 1, G_MAXINT, 1,
|
|
NULL);
|
|
|
|
g_object_set(G_OBJECT(capsfilter), "caps", filtercaps, NULL);
|
|
|
|
gst_bin_add(GST_BIN(bin), capsfilter);
|
|
gst_bin_add(GST_BIN(bin), video_sink);
|
|
|
|
gst_element_link (capsfilter, video_sink);
|
|
|
|
ghost_pad = gst_ghost_pad_new ("sink",
|
|
gst_element_get_pad (capsfilter, "sink"));
|
|
|
|
gst_element_add_pad (bin, ghost_pad);
|
|
|
|
g_object_set (G_OBJECT(video_sink),
|
|
"signal-handoffs", TRUE,
|
|
"sync", TRUE,
|
|
NULL);
|
|
|
|
g_signal_connect(G_OBJECT (video_sink), "handoff",
|
|
G_CALLBACK(fakesink_handoff_cb), video_texture);
|
|
|
|
priv->video_sink = bin;
|
|
|
|
if (priv->video_sink)
|
|
g_object_set (priv->play, "video-sink", bin, NULL);
|
|
|
|
if (priv->audio_sink)
|
|
g_object_set (priv->play, "audio-sink", audio_sink, NULL);
|
|
|
|
g_signal_connect (priv->play, "notify::source",
|
|
G_CALLBACK (playbin_got_source), video_texture);
|
|
g_signal_connect (priv->play, "notify::stream-info",
|
|
G_CALLBACK (playbin_stream_info_set), video_texture);
|
|
return;
|
|
}
|
|
|
|
ClutterElement*
|
|
clutter_video_texture_new (void)
|
|
{
|
|
ClutterVideoTexture *video_texture;
|
|
|
|
video_texture = g_object_new (CLUTTER_TYPE_VIDEO_TEXTURE,
|
|
"tiled", FALSE,
|
|
"pixel-format", GL_RGB,
|
|
NULL);
|
|
|
|
return CLUTTER_ELEMENT(video_texture);
|
|
}
|
|
|
|
gboolean
|
|
clutter_video_texture_open (ClutterVideoTexture *video_texture,
|
|
const gchar *mrl,
|
|
const gchar *subtitle_uri,
|
|
GError **error)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
gboolean ret;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (mrl != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE(video_texture), FALSE);
|
|
g_return_val_if_fail (priv->play != NULL, FALSE);
|
|
|
|
if (priv->mrl && strcmp (priv->mrl, mrl) == 0)
|
|
return TRUE;
|
|
|
|
/* this allows non-URI type of files in the thumbnailer and so on */
|
|
if (priv->mrl)
|
|
g_free (priv->mrl);
|
|
|
|
if (mrl[0] == '/')
|
|
{
|
|
priv->mrl = g_strdup_printf ("file://%s", mrl);
|
|
}
|
|
else
|
|
{
|
|
if (strchr (mrl, ':'))
|
|
{
|
|
priv->mrl = g_strdup (mrl);
|
|
}
|
|
else
|
|
{
|
|
gchar *cur_dir = g_get_current_dir ();
|
|
if (!cur_dir)
|
|
{
|
|
g_set_error (error, CLUTTER_VIDEO_TEXTURE_ERROR,
|
|
CLUTTER_VIDEO_TEXTURE_ERROR_GENERIC,
|
|
"Failed to retrieve working directory");
|
|
return FALSE;
|
|
}
|
|
priv->mrl = g_strdup_printf ("file://%s/%s", cur_dir, mrl);
|
|
g_free (cur_dir);
|
|
}
|
|
}
|
|
|
|
priv->got_redirect = FALSE;
|
|
priv->has_video = FALSE;
|
|
priv->has_audio = FALSE;
|
|
priv->stream_length = 0;
|
|
|
|
if (g_strrstr (priv->mrl, "#subtitle:"))
|
|
{
|
|
gchar **uris;
|
|
gchar *subtitle_uri;
|
|
|
|
uris = g_strsplit (priv->mrl, "#subtitle:", 2);
|
|
/* Try to fix subtitle uri if needed */
|
|
if (uris[1][0] == '/')
|
|
{
|
|
subtitle_uri = g_strdup_printf ("file://%s", uris[1]);
|
|
}
|
|
else
|
|
{
|
|
if (strchr (uris[1], ':'))
|
|
{
|
|
subtitle_uri = g_strdup (uris[1]);
|
|
}
|
|
else
|
|
{
|
|
gchar *cur_dir = g_get_current_dir ();
|
|
if (!cur_dir)
|
|
{
|
|
g_set_error (error, CLUTTER_VIDEO_TEXTURE_ERROR,
|
|
CLUTTER_VIDEO_TEXTURE_ERROR_GENERIC,
|
|
"Failed to retrieve working directory");
|
|
return FALSE;
|
|
}
|
|
|
|
subtitle_uri = g_strdup_printf ("file://%s/%s",
|
|
cur_dir, uris[1]);
|
|
g_free (cur_dir);
|
|
}
|
|
}
|
|
|
|
g_object_set (priv->play,
|
|
"uri", priv->mrl,
|
|
"suburi", subtitle_uri,
|
|
NULL);
|
|
g_free (subtitle_uri);
|
|
g_strfreev (uris);
|
|
}
|
|
else
|
|
{
|
|
g_object_set (priv->play,
|
|
"uri", priv->mrl,
|
|
"suburi", subtitle_uri,
|
|
NULL);
|
|
}
|
|
|
|
gst_element_set_state (priv->play, GST_STATE_PAUSED);
|
|
|
|
ret = poll_for_state_change (video_texture,
|
|
priv->play,
|
|
GST_STATE_PAUSED,
|
|
NULL);
|
|
|
|
if (!ret)
|
|
{
|
|
priv->ignore_messages_mask |= GST_MESSAGE_ERROR;
|
|
stop_play_pipeline (video_texture);
|
|
g_free (priv->mrl);
|
|
priv->mrl = NULL;
|
|
}
|
|
else
|
|
g_signal_emit (video_texture, cvt_signals[SIGNAL_CHANNELS_CHANGE], 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
clutter_video_texture_play (ClutterVideoTexture *video_texture,
|
|
GError ** error)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
gboolean ret;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE(video_texture), FALSE);
|
|
g_return_val_if_fail (priv->play != NULL, FALSE);
|
|
|
|
gst_element_set_state (priv->play, GST_STATE_PLAYING);
|
|
|
|
ret = poll_for_state_change (video_texture,
|
|
priv->play,
|
|
GST_STATE_PLAYING,
|
|
error);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
clutter_video_texture_pause (ClutterVideoTexture *video_texture)
|
|
{
|
|
g_return_if_fail (video_texture != NULL);
|
|
g_return_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture));
|
|
g_return_if_fail (GST_IS_ELEMENT (video_texture->priv->play));
|
|
|
|
CLUTTER_DBG ("Pausing");
|
|
|
|
gst_element_set_state (GST_ELEMENT (video_texture->priv->play),
|
|
GST_STATE_PAUSED);
|
|
}
|
|
|
|
|
|
gboolean
|
|
clutter_video_texture_can_direct_seek (ClutterVideoTexture *video_texture)
|
|
{
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), FALSE);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (video_texture->priv->play), FALSE);
|
|
|
|
if (!video_texture->priv->mrl)
|
|
return FALSE;
|
|
|
|
/* (instant seeking only make sense with video, hence no cdda:// here) */
|
|
if (g_str_has_prefix (video_texture->priv->mrl, "file://") ||
|
|
g_str_has_prefix (video_texture->priv->mrl, "dvd://") ||
|
|
g_str_has_prefix (video_texture->priv->mrl, "vcd://"))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
clutter_video_texture_seek_time (ClutterVideoTexture *video_texture,
|
|
gint64 time,
|
|
GError **gerror)
|
|
{
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), FALSE);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (video_texture->priv->play), FALSE);
|
|
|
|
got_time_tick (video_texture->priv->play, time * GST_MSECOND, video_texture);
|
|
|
|
gst_element_seek (video_texture->priv->play,
|
|
1.0,
|
|
GST_FORMAT_TIME,
|
|
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
|
|
GST_SEEK_TYPE_SET,
|
|
time * GST_MSECOND,
|
|
GST_SEEK_TYPE_NONE,
|
|
GST_CLOCK_TIME_NONE);
|
|
|
|
gst_element_get_state (video_texture->priv->play,
|
|
NULL, NULL, 100 * GST_MSECOND);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
clutter_video_texture_seek (ClutterVideoTexture *video_texture,
|
|
float position,
|
|
GError **error)
|
|
{
|
|
gint64 seek_time, length_nanos;
|
|
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), FALSE);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (video_texture->priv->play), FALSE);
|
|
|
|
length_nanos = (gint64) (video_texture->priv->stream_length * GST_MSECOND);
|
|
seek_time = (gint64) (length_nanos * position);
|
|
|
|
return clutter_video_texture_seek_time (video_texture,
|
|
seek_time / GST_MSECOND, error);
|
|
}
|
|
|
|
static void
|
|
stop_play_pipeline (ClutterVideoTexture *video_texture)
|
|
{
|
|
GstElement *playbin = video_texture->priv->play;
|
|
GstState current_state;
|
|
|
|
/* first go to ready, that way our state change handler gets to see
|
|
* our state change messages (before the bus goes to flushing) and
|
|
* cleans up */
|
|
gst_element_get_state (playbin, ¤t_state, NULL, 0);
|
|
|
|
if (current_state > GST_STATE_READY)
|
|
{
|
|
GError *err = NULL;
|
|
|
|
gst_element_set_state (playbin, GST_STATE_READY);
|
|
poll_for_state_change_full (video_texture,
|
|
playbin,
|
|
GST_STATE_READY, &err, -1);
|
|
if (err)
|
|
g_error_free (err);
|
|
}
|
|
|
|
/* now finally go to null state */
|
|
gst_element_set_state (playbin, GST_STATE_NULL);
|
|
gst_element_get_state (playbin, NULL, NULL, -1);
|
|
}
|
|
|
|
void
|
|
clutter_video_texture_stop (ClutterVideoTexture *video_texture)
|
|
{
|
|
g_return_if_fail (video_texture != NULL);
|
|
g_return_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture));
|
|
g_return_if_fail (GST_IS_ELEMENT (video_texture->priv->play));
|
|
|
|
stop_play_pipeline (video_texture);
|
|
|
|
/* Reset position to 0 when stopping */
|
|
got_time_tick (GST_ELEMENT (video_texture->priv->play), 0, video_texture);
|
|
}
|
|
|
|
gboolean
|
|
clutter_video_texture_can_set_volume (ClutterVideoTexture *video_texture)
|
|
{
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), FALSE);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (video_texture->priv->play), FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
clutter_video_texture_set_volume (ClutterVideoTexture *video_texture,
|
|
int volume)
|
|
{
|
|
g_return_if_fail (video_texture != NULL);
|
|
g_return_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture));
|
|
g_return_if_fail (GST_IS_ELEMENT (video_texture->priv->play));
|
|
|
|
if (clutter_video_texture_can_set_volume (video_texture) != FALSE)
|
|
{
|
|
volume = CLAMP (volume, 0, 100);
|
|
g_object_set (video_texture->priv->play, "volume",
|
|
(gdouble) (1. * volume / 100), NULL);
|
|
}
|
|
}
|
|
|
|
int
|
|
clutter_video_texture_get_volume (ClutterVideoTexture *video_texture)
|
|
{
|
|
gdouble vol;
|
|
|
|
g_return_val_if_fail (video_texture != NULL, -1);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), -1);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (video_texture->priv->play), -1);
|
|
|
|
g_object_get (G_OBJECT (video_texture->priv->play), "volume", &vol, NULL);
|
|
|
|
return (gint) (vol * 100 + 0.5);
|
|
}
|
|
|
|
gint64
|
|
clutter_video_texture_get_current_time (ClutterVideoTexture *video_texture)
|
|
{
|
|
g_return_val_if_fail (video_texture != NULL, -1);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), -1);
|
|
|
|
return video_texture->priv->current_time;
|
|
}
|
|
|
|
gint64
|
|
clutter_video_texture_get_stream_length (ClutterVideoTexture *video_texture)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
|
|
g_return_val_if_fail (video_texture != NULL, -1);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), -1);
|
|
|
|
priv = video_texture->priv;
|
|
|
|
if (priv->stream_length == 0 && priv->play != NULL)
|
|
{
|
|
GstFormat fmt = GST_FORMAT_TIME;
|
|
gint64 len = -1;
|
|
|
|
if (gst_element_query_duration (priv->play, &fmt, &len) && len != -1)
|
|
priv->stream_length = len / GST_MSECOND;
|
|
}
|
|
|
|
return priv->stream_length;
|
|
}
|
|
|
|
gboolean
|
|
clutter_video_texture_is_playing (ClutterVideoTexture *video_texture)
|
|
{
|
|
GstState cur, pending;
|
|
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), FALSE);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (video_texture->priv->play), FALSE);
|
|
|
|
gst_element_get_state (video_texture->priv->play, &cur, &pending, 0);
|
|
|
|
if (cur == GST_STATE_PLAYING || pending == GST_STATE_PLAYING)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
clutter_video_texture_is_seekable (ClutterVideoTexture *video_texture)
|
|
{
|
|
gboolean res;
|
|
|
|
g_return_val_if_fail (video_texture != NULL, FALSE);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), FALSE);
|
|
g_return_val_if_fail (GST_IS_ELEMENT (video_texture->priv->play), FALSE);
|
|
|
|
if (video_texture->priv->stream_length == 0)
|
|
res = (clutter_video_texture_get_stream_length (video_texture) > 0);
|
|
else
|
|
res = (video_texture->priv->stream_length > 0);
|
|
|
|
return res;
|
|
}
|
|
|
|
float
|
|
clutter_video_texture_get_position (ClutterVideoTexture *video_texture)
|
|
{
|
|
g_return_val_if_fail (video_texture != NULL, -1);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), -1);
|
|
|
|
return video_texture->priv->current_position;
|
|
}
|
|
|
|
void
|
|
clutter_video_texture_set_aspect_ratio (ClutterVideoTexture *video_texture,
|
|
ClutterVideoTextureAspectRatio ratio)
|
|
{
|
|
g_return_if_fail (video_texture != NULL);
|
|
g_return_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture));
|
|
|
|
video_texture->priv->ratio_type = ratio;
|
|
got_video_size (video_texture);
|
|
}
|
|
|
|
ClutterVideoTextureAspectRatio
|
|
clutter_video_texture_get_aspect_ratio (ClutterVideoTexture *video_texture)
|
|
{
|
|
g_return_val_if_fail (video_texture != NULL, 0);
|
|
g_return_val_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture), 0);
|
|
|
|
return video_texture->priv->ratio_type;
|
|
}
|
|
|
|
/* Metadata */
|
|
|
|
static const struct _metadata_map_info
|
|
{
|
|
ClutterVideoTextureMetadataType type;
|
|
const gchar *str;
|
|
} metadata_str_map[] = {
|
|
{ CLUTTER_INFO_TITLE, "title" },
|
|
{ CLUTTER_INFO_ARTIST, "artist" },
|
|
{ CLUTTER_INFO_YEAR, "year" },
|
|
{ CLUTTER_INFO_ALBUM, "album" },
|
|
{ CLUTTER_INFO_DURATION, "duration" },
|
|
{ CLUTTER_INFO_TRACK_NUMBER, "track-number" },
|
|
{ CLUTTER_INFO_HAS_VIDEO, "has-video" },
|
|
{ CLUTTER_INFO_DIMENSION_X, "dimension-x" },
|
|
{ CLUTTER_INFO_DIMENSION_Y, "dimension-y" },
|
|
{ CLUTTER_INFO_VIDEO_BITRATE,"video-bitrate" },
|
|
{ CLUTTER_INFO_VIDEO_CODEC, "video-codec" },
|
|
{ CLUTTER_INFO_FPS, "fps" },
|
|
{ CLUTTER_INFO_HAS_AUDIO, "has-audio" },
|
|
{ CLUTTER_INFO_AUDIO_BITRATE,"audio-bitrate" },
|
|
{ CLUTTER_INFO_AUDIO_CODEC, "audio-codec" }
|
|
};
|
|
|
|
static const gchar*
|
|
get_metadata_type_name (ClutterVideoTextureMetadataType type)
|
|
{
|
|
guint i;
|
|
for (i = 0; i < G_N_ELEMENTS (metadata_str_map); ++i)
|
|
{
|
|
if (metadata_str_map[i].type == type)
|
|
return metadata_str_map[i].str;
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
static void
|
|
get_metadata_string (ClutterVideoTexture *video_texture,
|
|
ClutterVideoTextureMetadataType type,
|
|
GValue *value)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
char *string = NULL;
|
|
gboolean res = FALSE;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
g_value_init (value, G_TYPE_STRING);
|
|
|
|
if (priv->play == NULL || priv->tagcache == NULL)
|
|
{
|
|
g_value_set_string (value, NULL);
|
|
return;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case CLUTTER_INFO_TITLE:
|
|
res = gst_tag_list_get_string_index (priv->tagcache,
|
|
GST_TAG_TITLE, 0, &string);
|
|
break;
|
|
case CLUTTER_INFO_ARTIST:
|
|
res = gst_tag_list_get_string_index (priv->tagcache,
|
|
GST_TAG_ARTIST, 0, &string);
|
|
break;
|
|
case CLUTTER_INFO_YEAR:
|
|
{
|
|
GDate *date;
|
|
if ((res = gst_tag_list_get_date (priv->tagcache,
|
|
GST_TAG_DATE, &date)))
|
|
{
|
|
string = g_strdup_printf ("%d", g_date_get_year (date));
|
|
g_date_free (date);
|
|
}
|
|
break;
|
|
}
|
|
case CLUTTER_INFO_ALBUM:
|
|
res = gst_tag_list_get_string_index (priv->tagcache,
|
|
GST_TAG_ALBUM, 0, &string);
|
|
break;
|
|
case CLUTTER_INFO_VIDEO_CODEC:
|
|
res = gst_tag_list_get_string (priv->tagcache,
|
|
GST_TAG_VIDEO_CODEC, &string);
|
|
break;
|
|
case CLUTTER_INFO_AUDIO_CODEC:
|
|
res = gst_tag_list_get_string (priv->tagcache,
|
|
GST_TAG_AUDIO_CODEC, &string);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
if (res)
|
|
{
|
|
g_value_take_string (value, string);
|
|
CLUTTER_DBG ("%s = '%s'", get_metadata_type_name (type), string);
|
|
}
|
|
else
|
|
g_value_set_string (value, NULL);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
get_metadata_int (ClutterVideoTexture *video_texture,
|
|
ClutterVideoTextureMetadataType type,
|
|
GValue *value)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
int integer = 0;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
g_value_init (value, G_TYPE_INT);
|
|
|
|
if (priv->play == NULL)
|
|
{
|
|
g_value_set_int (value, 0);
|
|
return;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case CLUTTER_INFO_DURATION:
|
|
integer = clutter_video_texture_get_stream_length (video_texture) / 1000;
|
|
break;
|
|
case CLUTTER_INFO_TRACK_NUMBER:
|
|
if (!gst_tag_list_get_uint (priv->tagcache,
|
|
GST_TAG_TRACK_NUMBER, (guint *) &integer))
|
|
integer = 0;
|
|
break;
|
|
case CLUTTER_INFO_DIMENSION_X:
|
|
integer = priv->video_width;
|
|
break;
|
|
case CLUTTER_INFO_DIMENSION_Y:
|
|
integer = priv->video_height;
|
|
break;
|
|
case CLUTTER_INFO_FPS:
|
|
if (priv->video_fps_d > 0)
|
|
{
|
|
/* Round up/down to the nearest integer framerate */
|
|
integer = (priv->video_fps_n + priv->video_fps_d/2) /
|
|
priv->video_fps_d;
|
|
}
|
|
else
|
|
integer = 0;
|
|
break;
|
|
case CLUTTER_INFO_AUDIO_BITRATE:
|
|
if (priv->audiotags == NULL)
|
|
break;
|
|
if (gst_tag_list_get_uint (priv->audiotags, GST_TAG_BITRATE,
|
|
(guint *)&integer) ||
|
|
gst_tag_list_get_uint (priv->audiotags, GST_TAG_NOMINAL_BITRATE,
|
|
(guint *)&integer)) {
|
|
integer /= 1000;
|
|
}
|
|
break;
|
|
case CLUTTER_INFO_VIDEO_BITRATE:
|
|
if (priv->videotags == NULL)
|
|
break;
|
|
if (gst_tag_list_get_uint (priv->videotags, GST_TAG_BITRATE,
|
|
(guint *)&integer) ||
|
|
gst_tag_list_get_uint (priv->videotags, GST_TAG_NOMINAL_BITRATE,
|
|
(guint *)&integer)) {
|
|
integer /= 1000;
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
g_value_set_int (value, integer);
|
|
CLUTTER_DBG ("%s = %d", get_metadata_type_name (type), integer);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
get_metadata_bool (ClutterVideoTexture *video_texture,
|
|
ClutterVideoTextureMetadataType type,
|
|
GValue *value)
|
|
{
|
|
ClutterVideoTexturePrivate *priv;
|
|
gboolean boolean = FALSE;
|
|
|
|
priv = video_texture->priv;
|
|
|
|
g_value_init (value, G_TYPE_BOOLEAN);
|
|
|
|
if (priv->play == NULL)
|
|
{
|
|
g_value_set_boolean (value, FALSE);
|
|
return;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case CLUTTER_INFO_HAS_VIDEO:
|
|
boolean = priv->has_video;
|
|
/* if properties dialog, show the metadata we
|
|
* have even if we cannot decode the stream */
|
|
if (!boolean
|
|
&& priv->tagcache != NULL
|
|
&& gst_structure_has_field ((GstStructure *) priv->tagcache,
|
|
GST_TAG_VIDEO_CODEC))
|
|
boolean = TRUE;
|
|
break;
|
|
case CLUTTER_INFO_HAS_AUDIO:
|
|
boolean = priv->has_audio;
|
|
/* if properties dialog, show the metadata we
|
|
* have even if we cannot decode the stream */
|
|
if (!boolean
|
|
&& priv->tagcache != NULL
|
|
&& gst_structure_has_field ((GstStructure *) priv->tagcache,
|
|
GST_TAG_AUDIO_CODEC))
|
|
boolean = TRUE;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
g_value_set_boolean (value, boolean);
|
|
CLUTTER_DBG ("%s = %s", get_metadata_type_name (type),
|
|
(boolean) ? "yes" : "no");
|
|
return;
|
|
}
|
|
|
|
void
|
|
clutter_video_texture_get_metadata (ClutterVideoTexture *video_texture,
|
|
ClutterVideoTextureMetadataType type,
|
|
GValue *value)
|
|
{
|
|
g_return_if_fail (video_texture != NULL);
|
|
g_return_if_fail (CLUTTER_IS_VIDEO_TEXTURE (video_texture));
|
|
g_return_if_fail (GST_IS_ELEMENT (video_texture->priv->play));
|
|
|
|
switch (type)
|
|
{
|
|
case CLUTTER_INFO_TITLE:
|
|
case CLUTTER_INFO_ARTIST:
|
|
case CLUTTER_INFO_YEAR:
|
|
case CLUTTER_INFO_ALBUM:
|
|
case CLUTTER_INFO_VIDEO_CODEC:
|
|
case CLUTTER_INFO_AUDIO_CODEC:
|
|
get_metadata_string (video_texture, type, value);
|
|
break;
|
|
case CLUTTER_INFO_DURATION:
|
|
case CLUTTER_INFO_DIMENSION_X:
|
|
case CLUTTER_INFO_DIMENSION_Y:
|
|
case CLUTTER_INFO_FPS:
|
|
case CLUTTER_INFO_AUDIO_BITRATE:
|
|
case CLUTTER_INFO_VIDEO_BITRATE:
|
|
case CLUTTER_INFO_TRACK_NUMBER:
|
|
get_metadata_int (video_texture, type, value);
|
|
break;
|
|
case CLUTTER_INFO_HAS_VIDEO:
|
|
case CLUTTER_INFO_HAS_AUDIO:
|
|
get_metadata_bool (video_texture, type, value);
|
|
break;
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
return;
|
|
}
|