From b75b5525da2c0929221b4d3336ac23c37486f5e2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 16 Nov 2007 15:44:48 +0000 Subject: [PATCH] gst/playback/: Add playbin2. Original commit message from CVS: * gst/playback/Makefile.am: * gst/playback/gstplayback.c: (plugin_init): * gst/playback/test7.c: (update_scale), (warning_cb), (error_cb), (eos_cb), (about_to_finish_cb), (main): Add playbin2. Added gapless playback example. * gst/playback/gstplaybasebin.c: * gst/playback/gstplaybasebin.h: * gst/playback/gstplaybin.c: (gst_play_bin_plugin_init): * gst/playback/gstqueue2.c: * gst/playback/test.c: * gst/playback/gsturidecodebin.c: (gst_uri_decode_bin_class_init), (pad_removed_cb): * gst/playback/gststreaminfo.h: Change email. * gst/playback/gstplaybin2.c: (gst_play_bin_get_type), (gst_play_bin_class_init), (init_group), (gst_play_bin_init), (gst_play_bin_dispose), (gst_play_bin_set_uri), (gst_play_bin_set_suburi), (gst_play_bin_set_property), (gst_play_bin_get_property), (gst_play_bin_handle_message), (pad_added_cb), (pad_removed_cb), (no_more_pads_cb), (perform_eos), (drained_cb), (unlink_group), (activate_group), (setup_next_source), (gst_play_bin_change_state), (gst_play_bin2_plugin_init): Added raw first version of playbin2. Does chained oggs and gapless playback fine. No support for raw sinks yet. No visualisations or subtitles yet. * gst/playback/gstplaysink.c: (gst_play_sink_get_type), (gst_play_sink_class_init), (gst_play_sink_init), (gst_play_sink_dispose), (gst_play_sink_vis_unblocked), (gst_play_sink_vis_blocked), (gst_play_sink_set_video_sink), (gst_play_sink_set_audio_sink), (gst_play_sink_set_vis_plugin), (gst_play_sink_set_property), (gst_play_sink_get_property), (post_missing_element_message), (free_chain), (add_chain), (activate_chain), (gen_video_chain), (gen_text_element), (gen_audio_chain), (gen_vis_element), (gst_play_sink_get_mode), (gst_play_sink_set_mode), (gst_play_sink_request_pad), (gst_play_sink_release_pad), (gst_play_sink_send_event_to_sink), (gst_play_sink_send_event), (gst_play_sink_change_state): * gst/playback/gstplaysink.h: Added Element that abstracts the sinks and their pipelines for playbin2. --- ChangeLog | 47 ++ gst/playback/Makefile.am | 11 +- gst/playback/gstplayback.c | 56 ++ gst/playback/gstplaybasebin.c | 2 +- gst/playback/gstplaybasebin.h | 1 + gst/playback/gstplaybin.c | 18 +- gst/playback/gstplaybin2.c | 1112 +++++++++++++++++++++++++++++++++++ gst/playback/gstplaysink.c | 1261 ++++++++++++++++++++++++++++++++++++++++ gst/playback/gstplaysink.h | 74 +++ gst/playback/gstqueue2.c | 2 +- gst/playback/gststreaminfo.h | 1 + gst/playback/gsturidecodebin.c | 2 +- gst/playback/test.c | 2 +- gst/playback/test7.c | 149 +++++ 14 files changed, 2716 insertions(+), 22 deletions(-) create mode 100644 gst/playback/gstplayback.c create mode 100644 gst/playback/gstplaybin2.c create mode 100644 gst/playback/gstplaysink.c create mode 100644 gst/playback/gstplaysink.h create mode 100644 gst/playback/test7.c diff --git a/ChangeLog b/ChangeLog index d476340..87d4d70 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,52 @@ 2007-11-16 Wim Taymans + * gst/playback/Makefile.am: + * gst/playback/gstplayback.c: (plugin_init): + * gst/playback/test7.c: (update_scale), (warning_cb), (error_cb), + (eos_cb), (about_to_finish_cb), (main): + Add playbin2. + Added gapless playback example. + + * gst/playback/gstplaybasebin.c: + * gst/playback/gstplaybasebin.h: + * gst/playback/gstplaybin.c: (gst_play_bin_plugin_init): + * gst/playback/gstqueue2.c: + * gst/playback/test.c: + * gst/playback/gsturidecodebin.c: (gst_uri_decode_bin_class_init), + (pad_removed_cb): + * gst/playback/gststreaminfo.h: + Change email. + + * gst/playback/gstplaybin2.c: (gst_play_bin_get_type), + (gst_play_bin_class_init), (init_group), (gst_play_bin_init), + (gst_play_bin_dispose), (gst_play_bin_set_uri), + (gst_play_bin_set_suburi), (gst_play_bin_set_property), + (gst_play_bin_get_property), (gst_play_bin_handle_message), + (pad_added_cb), (pad_removed_cb), (no_more_pads_cb), (perform_eos), + (drained_cb), (unlink_group), (activate_group), + (setup_next_source), (gst_play_bin_change_state), + (gst_play_bin2_plugin_init): + Added raw first version of playbin2. Does chained oggs and gapless + playback fine. No support for raw sinks yet. No visualisations or + subtitles yet. + + * gst/playback/gstplaysink.c: (gst_play_sink_get_type), + (gst_play_sink_class_init), (gst_play_sink_init), + (gst_play_sink_dispose), (gst_play_sink_vis_unblocked), + (gst_play_sink_vis_blocked), (gst_play_sink_set_video_sink), + (gst_play_sink_set_audio_sink), (gst_play_sink_set_vis_plugin), + (gst_play_sink_set_property), (gst_play_sink_get_property), + (post_missing_element_message), (free_chain), (add_chain), + (activate_chain), (gen_video_chain), (gen_text_element), + (gen_audio_chain), (gen_vis_element), (gst_play_sink_get_mode), + (gst_play_sink_set_mode), (gst_play_sink_request_pad), + (gst_play_sink_release_pad), (gst_play_sink_send_event_to_sink), + (gst_play_sink_send_event), (gst_play_sink_change_state): + * gst/playback/gstplaysink.h: + Added Element that abstracts the sinks and their pipelines for playbin2. + +2007-11-16 Wim Taymans + * gst/playback/gststreamselector.c: (gst_selector_pad_get_type), (gst_selector_pad_class_init), (gst_selector_pad_init), (gst_selector_pad_finalize), (gst_selector_pad_reset), diff --git a/gst/playback/Makefile.am b/gst/playback/Makefile.am index 87b4855..57cfd63 100644 --- a/gst/playback/Makefile.am +++ b/gst/playback/Makefile.am @@ -8,10 +8,13 @@ built_headers = gstplay-marshal.h plugindir = $(libdir)/gstreamer-@GST_MAJORMINOR@ plugin_LTLIBRARIES = libgstplaybin.la libgstdecodebin.la libgstdecodebin2.la libgstqueue2.la \ - libgsturidecodebin.la + libgsturidecodebin.la libgstplaybin_la_SOURCES = \ + gstplayback.c \ gstplaybin.c \ + gstplaybin2.c \ + gstplaysink.c \ gstplaybasebin.c \ gststreaminfo.c \ gststreamselector.c @@ -55,10 +58,11 @@ libgsturidecodebin_la_LIBADD = \ noinst_HEADERS = \ gstplaybasebin.h \ + gstplaysink.h \ gststreaminfo.h \ gststreamselector.h -noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 +noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7 test_LDADD = $(GST_LIBS) test_CFLAGS = $(GST_CFLAGS) @@ -78,6 +82,9 @@ test5_CFLAGS = $(GST_CFLAGS) test6_LDADD = $(GST_LIBS) test6_CFLAGS = $(GST_CFLAGS) +test7_LDADD = $(GST_LIBS) +test7_CFLAGS = $(GST_CFLAGS) + decodetest_LDADD = $(GST_LIBS) decodetest_CFLAGS = $(GST_CFLAGS) diff --git a/gst/playback/gstplayback.c b/gst/playback/gstplayback.c new file mode 100644 index 0000000..d5e9a1a --- /dev/null +++ b/gst/playback/gstplayback.c @@ -0,0 +1,56 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +gboolean gst_play_bin_plugin_init (GstPlugin * plugin); +gboolean gst_play_bin2_plugin_init (GstPlugin * plugin); + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean res; + + gst_pb_utils_init (); + +#ifdef ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); +#endif /* ENABLE_NLS */ + + res = gst_play_bin_plugin_init (plugin); + res &= gst_play_bin2_plugin_init (plugin); + + return res; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "playback", + "various playback elements", plugin_init, VERSION, GST_LICENSE, + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/playback/gstplaybasebin.c b/gst/playback/gstplaybasebin.c index cc308ba..9e30301 100644 --- a/gst/playback/gstplaybasebin.c +++ b/gst/playback/gstplaybasebin.c @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2007> Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public diff --git a/gst/playback/gstplaybasebin.h b/gst/playback/gstplaybasebin.h index 0939ed0..1a5a864 100644 --- a/gst/playback/gstplaybasebin.h +++ b/gst/playback/gstplaybasebin.h @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) <1999> Erik Walthinsen + * <2007> Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public diff --git a/gst/playback/gstplaybin.c b/gst/playback/gstplaybin.c index 209bec9..4effe1d 100644 --- a/gst/playback/gstplaybin.c +++ b/gst/playback/gstplaybin.c @@ -1829,25 +1829,11 @@ gst_play_bin_change_state (GstElement * element, GstStateChange transition) return ret; } -static gboolean -plugin_init (GstPlugin * plugin) +gboolean +gst_play_bin_plugin_init (GstPlugin * plugin) { GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin"); - gst_pb_utils_init (); - -#ifdef ENABLE_NLS - GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, - LOCALEDIR); - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); -#endif /* ENABLE_NLS */ - return gst_element_register (plugin, "playbin", GST_RANK_NONE, GST_TYPE_PLAY_BIN); } - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "playbin", - "player bin", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, - GST_PACKAGE_ORIGIN) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c new file mode 100644 index 0000000..5ea8641 --- /dev/null +++ b/gst/playback/gstplaybin2.c @@ -0,0 +1,1112 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-playbin2 + * + * + * + * Playbin provides a stand-alone everything-in-one abstraction for an + * audio and/or video player. + * + * + * It can handle both audio and video files and features + * + * + * automatic file type recognition and based on that automatic + * selection and usage of the right audio/video/subtitle demuxers/decoders + * + * + * visualisations for audio files + * + * + * subtitle support for video files + * + * + * stream selection between different audio/subtitles streams + * + * + * meta info (tag) extraction + * + * + * easy access to the last video frame + * + * + * buffering when playing streams over a network + * + * + * volume control + * + * + * + * Usage + * + * A playbin element can be created just like any other element using + * gst_element_factory_make(). The file/URI to play should be set via the "uri" + * property. This must be an absolute URI, relative file paths are not allowed. + * Example URIs are file:///home/joe/movie.avi or http://www.joedoe.com/foo.ogg + * + * + * Playbin is a #GstPipeline. It will notify the application of everything + * that's happening (errors, end of stream, tags found, state changes, etc.) + * by posting messages on its #GstBus. The application needs to watch the + * bus. + * + * + * Playback can be initiated by setting the element to PLAYING state using + * gst_element_set_state(). Note that the state change will take place in + * the background in a separate thread, when the function returns playback + * is probably not happening yet and any errors might not have occured yet. + * Applications using playbin should ideally be written to deal with things + * completely asynchroneous. + * + * + * When playback has finished (an EOS message has been received on the bus) + * or an error has occured (an ERROR message has been received on the bus) or + * the user wants to play a different track, playbin should be set back to + * READY or NULL state, then the "uri" property should be set to the new + * location and then playbin be set to PLAYING state again. + * + * + * Seeking can be done using gst_element_seek_simple() or gst_element_seek() + * on the playbin element. Again, the seek will not be executed + * instantaneously, but will be done in a background thread. When the seek + * call returns the seek will most likely still be in process. An application + * may wait for the seek to finish (or fail) using gst_element_get_state() with + * -1 as the timeout, but this will block the user interface and is not + * recommended at all. + * + * + * Applications may query the current position and duration of the stream + * via gst_element_query_position() and gst_element_query_duration() and + * setting the format passed to GST_FORMAT_TIME. If the query was successful, + * the duration or position will have been returned in units of nanoseconds. + * + * Advanced Usage: specifying the audio and video sink + * + * By default, if no audio sink or video sink has been specified via the + * "audio-sink" or "video-sink" property, playbin will use the autoaudiosink + * and autovideosink elements to find the first-best available output method. + * This should work in most cases, but is not always desirable. Often either + * the user or application might want to specify more explicitly what to use + * for audio and video output. + * + * + * If the application wants more control over how audio or video should be + * output, it may create the audio/video sink elements itself (for example + * using gst_element_factory_make()) and provide them to playbin using the + * "audio-sink" or "video-sink" property. + * + * + * GNOME-based applications, for example, will usually want to create + * gconfaudiosink and gconfvideosink elements and make playbin use those, + * so that output happens to whatever the user has configured in the GNOME + * Multimedia System Selector confinguration dialog. + * + * + * The sink elements do not necessarily need to be ready-made sinks. It is + * possible to create container elements that look like a sink to playbin, + * but in reality contain a number of custom elements linked together. This + * can be achieved by creating a #GstBin and putting elements in there and + * linking them, and then creating a sink #GstGhostPad for the bin and pointing + * it to the sink pad of the first element within the bin. This can be used + * for a number of purposes, for example to force output to a particular + * format or to modify or observe the data before it is output. + * + * + * It is also possible to 'suppress' audio and/or video output by using + * 'fakesink' elements (or capture it from there using the fakesink element's + * "handoff" signal, which, nota bene, is fired from the streaming thread!). + * + * Retrieving Tags and Other Meta Data + * + * Most of the common meta data (artist, title, etc.) can be retrieved by + * watching for TAG messages on the pipeline's bus (see above). + * + * + * Other more specific meta information like width/height/framerate of video + * streams or samplerate/number of channels of audio streams can be obtained + * using the "stream-info" property, which will return a GList of stream info + * objects, one for each stream. These are opaque objects that can only be + * accessed via the standard GObject property interface, ie. g_object_get(). + * Each stream info object has the following properties: + * + * "object" (GstObject) (the decoder source pad usually) + * "type" (enum) (if this is an audio/video/subtitle stream) + * "decoder" (string) (name of decoder used to decode this stream) + * "mute" (boolean) (to mute or unmute this stream) + * "caps" (GstCaps) (caps of the decoded stream) + * "language-code" (string) (ISO-639 language code for this stream, mostly used for audio/subtitle streams) + * "codec" (string) (format this stream was encoded in) + * + * Stream information from the stream-info properties is best queried once + * playbin has changed into PAUSED or PLAYING state (which can be detected + * via a state-changed message on the bus where old_state=READY and + * new_state=PAUSED), since before that the list might not be complete yet or + * not contain all available information (like language-codes). + * + * Buffering + * + * Playbin handles buffering automatically for the most part, but applications + * need to handle parts of the buffering process as well. Whenever playbin is + * buffering, it will post BUFFERING messages on the bus with a percentage + * value that shows the progress of the buffering process. Applications need + * to set playbin to PLAYING or PAUSED state in response to these messages. + * They may also want to convey the buffering progress to the user in some + * way. Here is how to extract the percentage information from the message + * (requires GStreamer >= 0.10.11): + * + * + * + * switch (GST_MESSAGE_TYPE (msg)) { + * case GST_MESSAGE_BUFFERING: { + * gint percent = 0; + * gst_message_parse_buffering (msg, &percent); + * g_print ("Buffering (%%u percent done)", percent); + * break; + * } + * ... + * } + * + * Note that applications should keep/set the pipeline in the PAUSED state when + * a BUFFERING message is received with a buffer percent value < 100 and set + * the pipeline back to PLAYING state when a BUFFERING message with a value + * of 100 percent is received (if PLAYING is the desired state, that is). + * + * Embedding the video window in your application + * + * By default, playbin (or rather the video sinks used) will create their own + * window. Applications will usually want to force output to a window of their + * own, however. This can be done using the GstXOverlay interface, which most + * video sinks implement. See the documentation there for more details. + * + * Specifying which CD/DVD device to use + * + * The device to use for CDs/DVDs needs to be set on the source element + * playbin creates before it is opened. The only way to do this at the moment + * is to connect to playbin's "notify::source" signal, which will be emitted + * by playbin when it has created the source element for a particular URI. + * In the signal callback you can check if the source element has a "device" + * property and set it appropriately. In future ways might be added to specify + * the device as part of the URI, but at the time of writing this is not + * possible yet. + * + * Examples + * + * Here is a simple pipeline to play back a video or audio file: + * + * gst-launch -v playbin uri=file:///path/to/somefile.avi + * + * This will play back the given AVI video file, given that the video and + * audio decoders required to decode the content are installed. Since no + * special audio sink or video sink is supplied (not possible via gst-launch), + * playbin will try to find a suitable audio and video sink automatically + * using the autoaudiosink and autovideosink elements. + * + * + * Here is a another pipeline to play track 4 of an audio CD: + * + * gst-launch -v playbin uri=cdda://4 + * + * This will play back track 4 on an audio CD in your disc drive (assuming + * the drive is detected automatically by the plugin). + * + * + * Here is a another pipeline to play title 1 of a DVD: + * + * gst-launch -v playbin uri=dvd://1 + * + * This will play back title 1 of a DVD in your disc drive (assuming + * the drive is detected automatically by the plugin). + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include "gstplaysink.h" +#include "gststreaminfo.h" +#include "gststreamselector.h" + +GST_DEBUG_CATEGORY_STATIC (gst_play_bin_debug); +#define GST_CAT_DEFAULT gst_play_bin_debug + +#define GST_TYPE_PLAY_BIN (gst_play_bin_get_type()) +#define GST_PLAY_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_BIN,GstPlayBin)) +#define GST_PLAY_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_BIN,GstPlayBinClass)) +#define GST_IS_PLAY_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_BIN)) +#define GST_IS_PLAY_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_BIN)) + +#define VOLUME_MAX_DOUBLE 10.0 +#define CONNECTION_SPEED_DEFAULT 0 + +typedef struct _GstPlayBin GstPlayBin; +typedef struct _GstPlayBinClass GstPlayBinClass; +typedef struct _GstSourceGroup GstSourceGroup; +typedef struct _GstSourceSelect GstSourceSelect; + +/* has the info for a selector and provides the link to the sink */ +struct _GstSourceSelect +{ + const gchar *media; /* the media type of the selector */ + GstPlaySinkType type; /* the sink pad type of the selector */ + GstPlaySinkMode mode; /* the sink pad mode of the selector */ + + GstElement *selector; /* the selector */ + gint current; /* the currently selected stream */ + GstPad *srcpad; /* the source pad of the selector */ + GstPad *sinkpad; /* the sinkpad of the sink when the selector is linked */ +}; + +/* a structure to hold the objects for decoding a uri and the subtitle uri + */ +struct _GstSourceGroup +{ + GstPlayBin *playbin; + + gboolean valid; /* the group has valid info to start playback */ + + /* properties */ + gchar *uri; + gchar *suburi; + GValueArray *streaminfo; + GstElement *source; + gchar *subencoding; /* encoding to propagate to the subtitle elements */ + + /* uridecodebins for uri and subtitle uri */ + GstElement *uridecodebin; + GstElement *suburidecodebin; + + /* selectors for different streams */ + GstSourceSelect selector[GST_PLAY_SINK_TYPE_LAST]; +}; + +struct _GstPlayBin +{ + GstPipeline parent; + + /* the groups, we use a double buffer to switch between current and next */ + GstSourceGroup groups[2]; /* array with group info */ + GstSourceGroup *curr_group; /* pointer to the currently playing group */ + GstSourceGroup *next_group; /* pointer to the next group */ + + /* properties */ + guint connection_speed; /* connection speed in bits/sec (0 = unknown) */ + + /* our play sink */ + GstPlaySink *playsink; +}; + +struct _GstPlayBinClass +{ + GstPipelineClass parent_class; + + void (*about_to_finish) (void); +}; + +/* props */ +enum +{ + PROP_0, + PROP_URI, + PROP_SUBURI, + PROP_NEXT_URI, + PROP_NEXT_SUBURI, + PROP_STREAMINFO, + PROP_SOURCE, + PROP_CURRENT_VIDEO, + PROP_CURRENT_AUDIO, + PROP_CURRENT_TEXT, + PROP_SUBTITLE_ENCODING, + PROP_AUDIO_SINK, + PROP_VIDEO_SINK, + PROP_VIS_PLUGIN, + PROP_VOLUME, + PROP_FRAME, + PROP_FONT_DESC, + PROP_CONNECTION_SPEED +}; + +/* signals */ +enum +{ + SIGNAL_ABOUT_TO_FINISH, + LAST_SIGNAL +}; + +static void gst_play_bin_class_init (GstPlayBinClass * klass); +static void gst_play_bin_init (GstPlayBin * play_bin); +static void gst_play_bin_dispose (GObject * object); + +static void gst_play_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * spec); +static void gst_play_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * spec); + +static GstStateChangeReturn gst_play_bin_change_state (GstElement * element, + GstStateChange transition); + +static void gst_play_bin_handle_message (GstBin * bin, GstMessage * message); + +static gboolean setup_next_source (GstPlayBin * playbin); + +static GstElementClass *parent_class; + +static guint gst_play_bin_signals[LAST_SIGNAL] = { 0 }; + +static const GstElementDetails gst_play_bin_details = +GST_ELEMENT_DETAILS ("Player Bin", + "Generic/Bin/Player", + "Autoplug and play media from an uri", + "Wim Taymans "); + +static GType +gst_play_bin_get_type (void) +{ + static GType gst_play_bin_type = 0; + + if (!gst_play_bin_type) { + static const GTypeInfo gst_play_bin_info = { + sizeof (GstPlayBinClass), + NULL, + NULL, + (GClassInitFunc) gst_play_bin_class_init, + NULL, + NULL, + sizeof (GstPlayBin), + 0, + (GInstanceInitFunc) gst_play_bin_init, + NULL + }; + + gst_play_bin_type = g_type_register_static (GST_TYPE_PIPELINE, + "GstPlayBin2", &gst_play_bin_info, 0); + } + + return gst_play_bin_type; +} + +static void +gst_play_bin_class_init (GstPlayBinClass * klass) +{ + GObjectClass *gobject_klass; + GstElementClass *gstelement_klass; + GstBinClass *gstbin_klass; + + gobject_klass = (GObjectClass *) klass; + gstelement_klass = (GstElementClass *) klass; + gstbin_klass = (GstBinClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_klass->set_property = gst_play_bin_set_property; + gobject_klass->get_property = gst_play_bin_get_property; + + gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_bin_dispose); + + g_object_class_install_property (gobject_klass, PROP_URI, + g_param_spec_string ("uri", "URI", "URI of the media to play", + NULL, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_SUBURI, + g_param_spec_string ("suburi", ".sub-URI", "Optional URI of a subtitle", + NULL, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_NEXT_URI, + g_param_spec_string ("next-uri", "Next URI", + "URI of the next media to play", NULL, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_NEXT_SUBURI, + g_param_spec_string ("next-suburi", "Next .sub-URI", + "Optional URI of a next subtitle", NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_klass, PROP_STREAMINFO, + g_param_spec_value_array ("stream-info", + "StreamInfo GValueArray", "value array of streaminfo", + g_param_spec_object ("streaminfo", "StreamInfo", "Streaminfo object", + GST_TYPE_STREAM_INFO, G_PARAM_READABLE), G_PARAM_READABLE)); + + g_object_class_install_property (gobject_klass, PROP_SOURCE, + g_param_spec_object ("source", "Source", "Source element", + GST_TYPE_ELEMENT, G_PARAM_READABLE)); + + g_object_class_install_property (gobject_klass, PROP_CURRENT_VIDEO, + g_param_spec_int ("current-video", "Current video", + "Currently playing video stream (-1 = none)", + -1, G_MAXINT, -1, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_CURRENT_AUDIO, + g_param_spec_int ("current-audio", "Current audio", + "Currently playing audio stream (-1 = none)", + -1, G_MAXINT, -1, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_CURRENT_TEXT, + g_param_spec_int ("current-text", "Current text", + "Currently playing text stream (-1 = none)", + -1, G_MAXINT, -1, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_klass, PROP_SUBTITLE_ENCODING, + g_param_spec_string ("subtitle-encoding", "subtitle encoding", + "Encoding to assume if input subtitles are not in UTF-8 encoding. " + "If not set, the GST_SUBTITLE_ENCODING environment variable will " + "be checked for an encoding to use. If that is not set either, " + "ISO-8859-15 will be assumed.", NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK, + g_param_spec_object ("video-sink", "Video Sink", + "the video output element to use (NULL = default sink)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_AUDIO_SINK, + g_param_spec_object ("audio-sink", "Audio Sink", + "the audio output element to use (NULL = default sink)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_VIS_PLUGIN, + g_param_spec_object ("vis-plugin", "Vis plugin", + "the visualization element to use (NULL = none)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_VOLUME, + g_param_spec_double ("volume", "volume", "volume", + 0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_FRAME, + gst_param_spec_mini_object ("frame", "Frame", + "The last frame (NULL = no video available)", + GST_TYPE_BUFFER, G_PARAM_READABLE)); + g_object_class_install_property (gobject_klass, PROP_FONT_DESC, + g_param_spec_string ("subtitle-font-desc", + "Subtitle font description", + "Pango font description of font " + "to be used for subtitle rendering", NULL, G_PARAM_WRITABLE)); + + g_object_class_install_property (gobject_klass, PROP_CONNECTION_SPEED, + g_param_spec_uint ("connection-speed", "Connection Speed", + "Network connection speed in kbps (0 = unknown)", + 0, G_MAXUINT, CONNECTION_SPEED_DEFAULT, G_PARAM_READWRITE)); + /** + * GstPlayBin::about-to-finish: + * + * This signal is emitted when the current uri is about to finish. You can + * set the next-uri and next-suburi to make sure that playback continues. + */ + gst_play_bin_signals[SIGNAL_ABOUT_TO_FINISH] = + g_signal_new ("about-to-finish", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstPlayBinClass, about_to_finish), NULL, NULL, + gst_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + + gst_element_class_set_details (gstelement_klass, &gst_play_bin_details); + + gstelement_klass->change_state = + GST_DEBUG_FUNCPTR (gst_play_bin_change_state); + + gstbin_klass->handle_message = + GST_DEBUG_FUNCPTR (gst_play_bin_handle_message); +} + +static void +init_group (GstPlayBin * playbin, GstSourceGroup * group) +{ + /* init selectors */ + group->playbin = playbin; + group->selector[0].media = "audio/"; + group->selector[0].type = GST_PLAY_SINK_TYPE_AUDIO; + group->selector[0].mode = GST_PLAY_SINK_MODE_AUDIO; + group->selector[1].media = "video/"; + group->selector[1].type = GST_PLAY_SINK_TYPE_VIDEO; + group->selector[1].mode = GST_PLAY_SINK_MODE_VIDEO; + group->selector[2].media = "text/"; + group->selector[2].type = GST_PLAY_SINK_TYPE_TEXT; + group->selector[2].mode = GST_PLAY_SINK_MODE_TEXT; +} + +static void +gst_play_bin_init (GstPlayBin * playbin) +{ + /* init groups */ + playbin->curr_group = &playbin->groups[0]; + playbin->next_group = &playbin->groups[1]; + init_group (playbin, &playbin->groups[0]); + init_group (playbin, &playbin->groups[1]); +} + +static void +gst_play_bin_dispose (GObject * object) +{ + GstPlayBin *play_bin; + + play_bin = GST_PLAY_BIN (object); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_play_bin_set_uri (GstPlayBin * play_bin, const gchar * uri) +{ + GstSourceGroup *group; + + if (uri == NULL) { + g_warning ("cannot set NULL uri"); + return; + } + + GST_OBJECT_LOCK (play_bin); + group = play_bin->next_group; + + /* if we have no previous uri, or the new uri is different from the + * old one, replug */ + g_free (group->uri); + group->uri = g_strdup (uri); + group->valid = TRUE; + + GST_DEBUG ("setting new uri to %s", uri); + GST_OBJECT_UNLOCK (play_bin); +} + +static void +gst_play_bin_set_suburi (GstPlayBin * play_bin, const gchar * suburi) +{ + GstSourceGroup *group; + + GST_OBJECT_LOCK (play_bin); + group = play_bin->next_group; + + if ((!suburi && !group->suburi) || + (suburi && group->suburi && !strcmp (group->suburi, suburi))) + goto done; + + g_free (group->suburi); + group->suburi = g_strdup (suburi); + + GST_DEBUG ("setting new .sub uri to %s", suburi); + +done: + GST_OBJECT_UNLOCK (play_bin); +} + +static void +gst_play_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPlayBin *play_bin; + + play_bin = GST_PLAY_BIN (object); + + switch (prop_id) { + case PROP_URI: + gst_play_bin_set_uri (play_bin, g_value_get_string (value)); + break; + case PROP_SUBURI: + gst_play_bin_set_suburi (play_bin, g_value_get_string (value)); + break; + case PROP_NEXT_URI: + gst_play_bin_set_uri (play_bin, g_value_get_string (value)); + break; + case PROP_NEXT_SUBURI: + gst_play_bin_set_suburi (play_bin, g_value_get_string (value)); + break; + case PROP_VIDEO_SINK: + break; + case PROP_AUDIO_SINK: + break; + case PROP_VIS_PLUGIN: + break; + case PROP_VOLUME: + break; + case PROP_FONT_DESC: + break; + case PROP_CONNECTION_SPEED: + GST_OBJECT_LOCK (play_bin); + play_bin->connection_speed = g_value_get_uint (value) * 1000; + GST_OBJECT_UNLOCK (play_bin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstPlayBin *play_bin; + + play_bin = GST_PLAY_BIN (object); + + switch (prop_id) { + case PROP_URI: + GST_OBJECT_LOCK (play_bin); + /* get the currently playing group first, then the queued one */ + if (play_bin->curr_group) + g_value_set_string (value, play_bin->curr_group->uri); + else + g_value_set_string (value, play_bin->next_group->uri); + GST_OBJECT_UNLOCK (play_bin); + break; + case PROP_SUBURI: + GST_OBJECT_LOCK (play_bin); + /* get the currently playing group first, then the queued one */ + if (play_bin->curr_group) + g_value_set_string (value, play_bin->curr_group->suburi); + else + g_value_set_string (value, play_bin->next_group->suburi); + GST_OBJECT_UNLOCK (play_bin); + break; + case PROP_NEXT_URI: + GST_OBJECT_LOCK (play_bin); + g_value_set_string (value, play_bin->next_group->uri); + GST_OBJECT_UNLOCK (play_bin); + break; + case PROP_NEXT_SUBURI: + GST_OBJECT_LOCK (play_bin); + g_value_set_string (value, play_bin->next_group->suburi); + GST_OBJECT_UNLOCK (play_bin); + break; + case PROP_VIDEO_SINK: + break; + case PROP_AUDIO_SINK: + break; + case PROP_VIS_PLUGIN: + break; + case PROP_VOLUME: + break; + case PROP_FRAME: + break; + case PROP_CONNECTION_SPEED: + GST_OBJECT_LOCK (play_bin); + g_value_set_uint (value, play_bin->connection_speed / 1000); + GST_OBJECT_UNLOCK (play_bin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* mime types we are not handling on purpose right now, don't post a + * missing-plugin message for these */ +static const gchar *blacklisted_mimes[] = { + "video/x-dvd-subpicture", NULL +}; + +static void +gst_play_bin_handle_message (GstBin * bin, GstMessage * msg) +{ + if (gst_is_missing_plugin_message (msg)) { + gchar *detail; + guint i; + + detail = gst_missing_plugin_message_get_installer_detail (msg); + for (i = 0; detail != NULL && blacklisted_mimes[i] != NULL; ++i) { + if (strstr (detail, "|decoder-") && strstr (detail, blacklisted_mimes[i])) { + GST_LOG_OBJECT (bin, "suppressing message %" GST_PTR_FORMAT, msg); + gst_message_unref (msg); + g_free (detail); + return; + } + } + g_free (detail); + } + GST_BIN_CLASS (parent_class)->handle_message (bin, msg); +} + +/* this function is called when a new pad is added to decodebin. We check the + * type of the pad and add it to the selecter element of the group. + */ +static void +pad_added_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group) +{ + GstPlayBin *playbin; + GstCaps *caps; + const GstStructure *s; + const gchar *name; + GstPad *sinkpad; + GstPadLinkReturn res; + GstSourceSelect *select = NULL; + gint i; + + playbin = group->playbin; + + caps = gst_pad_get_caps (pad); + s = gst_caps_get_structure (caps, 0); + name = gst_structure_get_name (s); + + GST_DEBUG_OBJECT (playbin, + "pad %s:%s with caps %" GST_PTR_FORMAT " added in group %p", + GST_DEBUG_PAD_NAME (pad), caps, group); + + /* major type of the pad, this determines the selector to use */ + for (i = 0; i < 3; i++) { + if (g_str_has_prefix (name, group->selector[i].media)) { + select = &group->selector[i]; + break; + } + } + /* no selector found for the media type, don't bother linking it to a + * selector. This will leave the pad unlinked and thus ignored. */ + if (select == NULL) + goto unknown_type; + + if (select->selector == NULL) { + /* no selector, create one */ + GST_DEBUG_OBJECT (playbin, "creating new selector"); + select->selector = g_object_new (GST_TYPE_STREAM_SELECTOR, NULL); + if (select->selector == NULL) + goto no_selector; + + GST_DEBUG_OBJECT (playbin, "adding new selector %p", select->selector); + gst_bin_add (GST_BIN_CAST (playbin), select->selector); + gst_element_set_state (select->selector, GST_STATE_PAUSED); + + /* save source pad */ + select->srcpad = gst_element_get_pad (select->selector, "src"); + } + + /* get sinkpad for the new stream */ + if ((sinkpad = gst_element_get_request_pad (select->selector, "sink%d"))) { + GST_DEBUG_OBJECT (playbin, "got pad %s:%s from selector", + GST_DEBUG_PAD_NAME (sinkpad)); + res = gst_pad_link (pad, sinkpad); + if (GST_PAD_LINK_FAILED (res)) + goto link_failed; + + /* store selector pad so we can release it */ + g_object_set_data (G_OBJECT (pad), "playbin2.selector", sinkpad); + } + GST_DEBUG_OBJECT (playbin, "linked pad %s:%s to selector %p", + GST_DEBUG_PAD_NAME (pad), select->selector); + + return; + + /* ERRORS */ +unknown_type: + { + GST_ERROR_OBJECT (playbin, "unknown type %s for pad %s:%s", + name, GST_DEBUG_PAD_NAME (pad)); + return; + } +no_selector: + { + GST_ERROR_OBJECT (playbin, "could not create selector for pad %s:%s", + GST_DEBUG_PAD_NAME (pad)); + return; + } +link_failed: + { + GST_ERROR_OBJECT (playbin, + "failed to link pad %s:%s to selector, reason %d", + GST_DEBUG_PAD_NAME (pad), res); + return; + } +} + +/* called when a pad is removed form the uridecodebin. We unlink the pad from + * the selector. This will make the selector select a new pad. */ +static void +pad_removed_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group) +{ + GstPlayBin *playbin; + GstPad *peer; + GstElement *selector; + + playbin = group->playbin; + + GST_DEBUG_OBJECT (playbin, + "pad %s:%s removed from group %p", GST_DEBUG_PAD_NAME (pad), group); + + /* get the selector sinkpad */ + peer = g_object_get_data (G_OBJECT (pad), "playbin2.selector"); + if (!peer) + goto not_linked; + + /* unlink the pad now (can fail, the pad is unlinked before it's removed) */ + gst_pad_unlink (pad, peer); + + /* get selector */ + selector = GST_ELEMENT_CAST (gst_pad_get_parent (peer)); + + /* release the pad to the selector, this will make the selector choose a new + * pad. */ + gst_element_release_request_pad (selector, peer); + + gst_object_unref (selector); + + return; + + /* ERRORS */ +not_linked: + { + GST_DEBUG_OBJECT (playbin, "pad not linked"); + return; + } + +} + +/* we get called when all pads are available and we must connect the sinks to + * them. + * The main purpose of the code is to see if we have video/audio and subtitles + * and pick the right pipelines to display them. + * + * The selectors installed on the group tell us about the presence of + * audio/video and subtitle streams. This allows us to see if we need + * visualisation, video or/and audio. + */ +static void +no_more_pads_cb (GstElement * decodebin, GstSourceGroup * group) +{ + GstPlayBin *playbin; + GstPlaySinkMode mode = 0; + GstPadLinkReturn res; + gint i; + + playbin = group->playbin; + + GST_DEBUG_OBJECT (playbin, "no more pads in group %p", group); + + for (i = 0; i < 3; i++) { + GstSourceSelect *select = &group->selector[i]; + + if (select->selector) { + select->sinkpad = + gst_play_sink_request_pad (playbin->playsink, select->type); + res = gst_pad_link (select->srcpad, select->sinkpad); + GST_DEBUG_OBJECT (playbin, "linking type %s: %d", select->media, res); + mode |= select->mode; + } + } + /* configure the modes now */ + gst_play_sink_set_mode (playbin->playsink, mode); +} + +/* send an EOS event to all of the selectors */ +static void +perform_eos (GstPlayBin * playbin, GstSourceGroup * group) +{ + GstEvent *event; + gint i; + + GST_DEBUG_OBJECT (playbin, "doing EOS in group %p", group); + + event = gst_event_new_eos (); + for (i = 0; i < 3; i++) { + GstSourceSelect *select = &group->selector[i]; + + if (select->selector) { + GST_DEBUG_OBJECT (playbin, "send EOS in selector %s", select->media); + gst_event_ref (event); + gst_pad_push_event (select->srcpad, event); + } + } + gst_event_unref (event); +} + +static void +drained_cb (GstElement * decodebin, GstSourceGroup * group) +{ + GstPlayBin *playbin; + + playbin = group->playbin; + + GST_DEBUG_OBJECT (playbin, "about to finish in group %p", group); + + /* after this call, we should have a next group to activate or we EOS */ + g_signal_emit (G_OBJECT (playbin), + gst_play_bin_signals[SIGNAL_ABOUT_TO_FINISH], 0, NULL); + + /* now activate the next group. If the app did not set a next-uri, this will + * fail and we can do EOS */ + if (!setup_next_source (playbin)) { + perform_eos (playbin, group); + } +} + +/* unlink a group of uridecodebins from the sink */ +static void +unlink_group (GstPlayBin * playbin, GstSourceGroup * group) +{ + gint i; + + GST_DEBUG_OBJECT (playbin, "unlinking group %p", group); + + for (i = 0; i < 3; i++) { + GstSourceSelect *select = &group->selector[i]; + + if (!select->selector) + continue; + + GST_DEBUG_OBJECT (playbin, "unlinking selector %s", select->media); + gst_pad_unlink (select->srcpad, select->sinkpad); + + /* release back */ + gst_play_sink_release_pad (playbin->playsink, select->sinkpad); + select->sinkpad = NULL; + + gst_object_unref (select->srcpad); + select->srcpad = NULL; + + gst_element_set_state (select->selector, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (playbin), select->selector); + select->selector = NULL; + } + group->valid = FALSE; +} + +static gboolean +activate_group (GstPlayBin * playbin, GstSourceGroup * group) +{ + GstElement *uridecodebin; + + uridecodebin = gst_element_factory_make ("uridecodebin", NULL); + if (!uridecodebin) + goto no_decodebin; + + /* configure uri */ + g_object_set (uridecodebin, "uri", group->uri, NULL); + + /* connect pads and other things */ + g_signal_connect (uridecodebin, "pad-added", G_CALLBACK (pad_added_cb), + group); + g_signal_connect (uridecodebin, "pad-removed", G_CALLBACK (pad_removed_cb), + group); + g_signal_connect (uridecodebin, "no-more-pads", G_CALLBACK (no_more_pads_cb), + group); + /* is called when the uridecodebin is out of data and we can switch to the + * next uri */ + g_signal_connect (uridecodebin, "drained", G_CALLBACK (drained_cb), group); + + /* */ + gst_bin_add (GST_BIN_CAST (playbin), uridecodebin); + group->uridecodebin = uridecodebin; + + gst_element_set_state (uridecodebin, GST_STATE_PAUSED); + + return TRUE; + + /* ERRORS */ +no_decodebin: + { + return FALSE; + } +} + +/* setup the next group to play */ +static gboolean +setup_next_source (GstPlayBin * playbin) +{ + GstSourceGroup *new_group, *old_group; + + GST_DEBUG_OBJECT (playbin, "setup sources"); + + /* see if there is a next group */ + new_group = playbin->next_group; + if (!new_group || !new_group->valid) + goto no_next_group; + + /* first unlink the current source, if any */ + old_group = playbin->curr_group; + if (old_group && old_group->valid) { + /* unlink our pads with the sink */ + unlink_group (playbin, old_group); + } + + /* activate the new group */ + if (!activate_group (playbin, new_group)) + goto activate_failed; + + /* swap old and new */ + playbin->curr_group = new_group; + playbin->next_group = old_group; + + return TRUE; + + /* ERRORS */ +no_next_group: + { + GST_DEBUG_OBJECT (playbin, "no next group"); + return FALSE; + } +activate_failed: + { + GST_DEBUG_OBJECT (playbin, "activate failed"); + return FALSE; + } +} + +static GstStateChangeReturn +gst_play_bin_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstPlayBin *play_bin; + + play_bin = GST_PLAY_BIN (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (play_bin->playsink == NULL) { + play_bin->playsink = g_object_new (GST_TYPE_PLAY_SINK, NULL); + gst_bin_add (GST_BIN_CAST (play_bin), + GST_ELEMENT_CAST (play_bin->playsink)); + } + if (!setup_next_source (play_bin)) + goto source_failed; + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* FIXME Release audio device when we implement that */ + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (play_bin->playsink) { + gst_bin_remove (GST_BIN_CAST (play_bin), + GST_ELEMENT_CAST (play_bin->playsink)); + play_bin->playsink = NULL; + } + break; + default: + break; + } + + return ret; + + /* ERRORS */ +source_failed: + { + return GST_STATE_CHANGE_FAILURE; + } +} + +gboolean +gst_play_bin2_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin2", 0, "play bin"); + + return gst_element_register (plugin, "playbin2", GST_RANK_NONE, + GST_TYPE_PLAY_BIN); +} diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c new file mode 100644 index 0000000..387a706 --- /dev/null +++ b/gst/playback/gstplaysink.c @@ -0,0 +1,1261 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include "gstplaysink.h" + +GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug); +#define GST_CAT_DEFAULT gst_play_sink_debug + +#define VOLUME_MAX_DOUBLE 10.0 +#define CONNECTION_SPEED_DEFAULT 0 + +/* holds the common data fields for the audio and video pipelines. We keep them + * in a structure to more easily have all the info available. */ +typedef struct +{ + GstPlaySink *playsink; + GstPad *sinkpad; + GstElement *bin; + gboolean added; + gboolean activated; +} GstPlayChain; + +typedef struct +{ + GstPlayChain chain; + GstElement *conv; + GstElement *resample; + GstElement *volume; + GstElement *sink; +} GstPlayAudioChain; + +typedef struct +{ + GstPlayChain chain; + GstElement *conv; + GstElement *queue; + GstElement *scale; + GstElement *sink; +} GstPlayVideoChain; + +struct _GstPlaySink +{ + GstBin bin; + + GstPlaySinkMode mode; + + GstPlayChain *audiochain; + GstPlayChain *videochain; + + GstPad *audio_pad; + GstPad *video_pad; + GstPad *text_pad; + + /* properties */ + GstElement *audio_sink; + GstElement *video_sink; + GstElement *visualisation; + gfloat volume; + gchar *font_desc; /* font description */ + guint connection_speed; /* connection speed in bits/sec (0 = unknown) */ + + /* internal elements */ + GstElement *textoverlay_element; + + GstElement *pending_visualisation; + GstElement *fakesink; +}; + +struct _GstPlaySinkClass +{ + GstBinClass parent_class; +}; + + +/* props */ +enum +{ + PROP_0, + PROP_AUDIO_SINK, + PROP_VIDEO_SINK, + PROP_VIS_PLUGIN, + PROP_VOLUME, + PROP_FRAME, + PROP_FONT_DESC, + PROP_LAST +}; + +/* signals */ +enum +{ + LAST_SIGNAL +}; + +static void gst_play_sink_class_init (GstPlaySinkClass * klass); +static void gst_play_sink_init (GstPlaySink * play_sink); +static void gst_play_sink_dispose (GObject * object); + +static void gst_play_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * spec); +static void gst_play_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * spec); + +static gboolean gst_play_sink_send_event (GstElement * element, + GstEvent * event); +static GstStateChangeReturn gst_play_sink_change_state (GstElement * element, + GstStateChange transition); + +static GstElementClass *parent_class; + +/* static guint gst_play_sink_signals[LAST_SIGNAL] = { 0 }; */ + +static const GstElementDetails gst_play_sink_details = +GST_ELEMENT_DETAILS ("Player Sink", + "Generic/Bin/Player", + "Autoplug and play media from an uri", + "Wim Taymans "); + +GType +gst_play_sink_get_type (void) +{ + static GType gst_play_sink_type = 0; + + if (!gst_play_sink_type) { + static const GTypeInfo gst_play_sink_info = { + sizeof (GstPlaySinkClass), + NULL, + NULL, + (GClassInitFunc) gst_play_sink_class_init, + NULL, + NULL, + sizeof (GstPlaySink), + 0, + (GInstanceInitFunc) gst_play_sink_init, + NULL + }; + + gst_play_sink_type = g_type_register_static (GST_TYPE_BIN, + "GstPlaySink", &gst_play_sink_info, 0); + } + + return gst_play_sink_type; +} + +static void +gst_play_sink_class_init (GstPlaySinkClass * klass) +{ + GObjectClass *gobject_klass; + GstElementClass *gstelement_klass; + GstBinClass *gstbin_klass; + + gobject_klass = (GObjectClass *) klass; + gstelement_klass = (GstElementClass *) klass; + gstbin_klass = (GstBinClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_klass->set_property = gst_play_sink_set_property; + gobject_klass->get_property = gst_play_sink_get_property; + + gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_sink_dispose); + + g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK, + g_param_spec_object ("video-sink", "Video Sink", + "the video output element to use (NULL = default sink)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_AUDIO_SINK, + g_param_spec_object ("audio-sink", "Audio Sink", + "the audio output element to use (NULL = default sink)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_VIS_PLUGIN, + g_param_spec_object ("vis-plugin", "Vis plugin", + "the visualization element to use (NULL = none)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_VOLUME, + g_param_spec_double ("volume", "volume", "volume", + 0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_klass, PROP_FRAME, + gst_param_spec_mini_object ("frame", "Frame", + "The last frame (NULL = no video available)", + GST_TYPE_BUFFER, G_PARAM_READABLE)); + g_object_class_install_property (gobject_klass, PROP_FONT_DESC, + g_param_spec_string ("subtitle-font-desc", + "Subtitle font description", + "Pango font description of font " + "to be used for subtitle rendering", NULL, G_PARAM_WRITABLE)); + + gst_element_class_set_details (gstelement_klass, &gst_play_sink_details); + + gstelement_klass->change_state = + GST_DEBUG_FUNCPTR (gst_play_sink_change_state); + gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_sink_send_event); + + GST_DEBUG_CATEGORY_INIT (gst_play_sink_debug, "playsink", 0, "play bin"); +} + +static void +gst_play_sink_init (GstPlaySink * play_sink) +{ + /* init groups */ + play_sink->video_sink = NULL; + play_sink->audio_sink = NULL; + play_sink->visualisation = NULL; + play_sink->pending_visualisation = NULL; + play_sink->textoverlay_element = NULL; + play_sink->volume = 1.0; + play_sink->font_desc = NULL; +} + +static void +gst_play_sink_dispose (GObject * object) +{ + GstPlaySink *play_sink; + + play_sink = GST_PLAY_SINK (object); + + if (play_sink->audio_sink != NULL) { + gst_element_set_state (play_sink->audio_sink, GST_STATE_NULL); + gst_object_unref (play_sink->audio_sink); + play_sink->audio_sink = NULL; + } + if (play_sink->video_sink != NULL) { + gst_element_set_state (play_sink->video_sink, GST_STATE_NULL); + gst_object_unref (play_sink->video_sink); + play_sink->video_sink = NULL; + } + if (play_sink->visualisation != NULL) { + gst_element_set_state (play_sink->visualisation, GST_STATE_NULL); + gst_object_unref (play_sink->visualisation); + play_sink->visualisation = NULL; + } + if (play_sink->pending_visualisation != NULL) { + gst_element_set_state (play_sink->pending_visualisation, GST_STATE_NULL); + gst_object_unref (play_sink->pending_visualisation); + play_sink->pending_visualisation = NULL; + } + if (play_sink->textoverlay_element != NULL) { + gst_object_unref (play_sink->textoverlay_element); + play_sink->textoverlay_element = NULL; + } + g_free (play_sink->font_desc); + play_sink->font_desc = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_play_sink_vis_unblocked (GstPad * tee_pad, gboolean blocked, + gpointer user_data) +{ + GstPlaySink *play_sink = GST_PLAY_SINK (user_data); + + if (play_sink->pending_visualisation) + gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_sink_vis_unblocked, + play_sink); +} + +static void +gst_play_sink_vis_blocked (GstPad * tee_pad, gboolean blocked, + gpointer user_data) +{ + GstPlaySink *play_sink = GST_PLAY_SINK (user_data); + GstBin *vis_bin = NULL; + GstPad *vis_sink_pad = NULL, *vis_src_pad = NULL, *vqueue_pad = NULL; + GstState bin_state; + GstElement *pending_visualisation; + + GST_OBJECT_LOCK (play_sink); + pending_visualisation = play_sink->pending_visualisation; + play_sink->pending_visualisation = NULL; + GST_OBJECT_UNLOCK (play_sink); + + /* We want to disable visualisation */ + if (!GST_IS_ELEMENT (pending_visualisation)) { + /* Set visualisation element to READY */ + gst_element_set_state (play_sink->visualisation, GST_STATE_READY); + goto beach; + } + + vis_bin = + GST_BIN_CAST (gst_object_get_parent (GST_OBJECT_CAST (play_sink-> + visualisation))); + + if (!GST_IS_BIN (vis_bin) || !GST_IS_PAD (tee_pad)) { + goto beach; + } + + vis_src_pad = gst_element_get_pad (play_sink->visualisation, "src"); + vis_sink_pad = gst_pad_get_peer (tee_pad); + + /* Can be fakesink */ + if (GST_IS_PAD (vis_src_pad)) { + vqueue_pad = gst_pad_get_peer (vis_src_pad); + } + + if (!GST_IS_PAD (vis_sink_pad)) { + goto beach; + } + + /* Check the bin's state */ + GST_OBJECT_LOCK (vis_bin); + bin_state = GST_STATE (vis_bin); + GST_OBJECT_UNLOCK (vis_bin); + + /* Unlink */ + gst_pad_unlink (tee_pad, vis_sink_pad); + gst_object_unref (vis_sink_pad); + vis_sink_pad = NULL; + + if (GST_IS_PAD (vqueue_pad)) { + gst_pad_unlink (vis_src_pad, vqueue_pad); + gst_object_unref (vis_src_pad); + vis_src_pad = NULL; + } + + /* Remove from vis_bin */ + gst_bin_remove (vis_bin, play_sink->visualisation); + /* Set state to NULL */ + gst_element_set_state (play_sink->visualisation, GST_STATE_NULL); + /* And loose our ref */ + gst_object_unref (play_sink->visualisation); + + if (pending_visualisation) { + /* Ref this new visualisation element before adding to the bin */ + gst_object_ref (pending_visualisation); + /* Add the new one */ + gst_bin_add (vis_bin, pending_visualisation); + /* Synchronizing state */ + gst_element_set_state (pending_visualisation, bin_state); + + vis_sink_pad = gst_element_get_pad (pending_visualisation, "sink"); + vis_src_pad = gst_element_get_pad (pending_visualisation, "src"); + + if (!GST_IS_PAD (vis_sink_pad) || !GST_IS_PAD (vis_src_pad)) { + goto beach; + } + + /* Link */ + gst_pad_link (tee_pad, vis_sink_pad); + gst_pad_link (vis_src_pad, vqueue_pad); + } + + /* We are done */ + gst_object_unref (play_sink->visualisation); + play_sink->visualisation = pending_visualisation; + +beach: + if (vis_sink_pad) { + gst_object_unref (vis_sink_pad); + } + if (vis_src_pad) { + gst_object_unref (vis_src_pad); + } + if (vqueue_pad) { + gst_object_unref (vqueue_pad); + } + if (vis_bin) { + gst_object_unref (vis_bin); + } + + /* Unblock the pad */ + gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_sink_vis_unblocked, + play_sink); +} + +static void +gst_play_sink_set_video_sink (GstPlaySink * play_sink, GstElement * sink) +{ + GST_OBJECT_LOCK (play_sink); + if (play_sink->video_sink) + gst_object_unref (play_sink->video_sink); + + if (sink) { + gst_object_ref (sink); + gst_object_sink (sink); + } + play_sink->video_sink = sink; + GST_OBJECT_UNLOCK (play_sink); +} + +static void +gst_play_sink_set_audio_sink (GstPlaySink * play_sink, GstElement * sink) +{ + GST_OBJECT_LOCK (play_sink); + if (play_sink->audio_sink) + gst_object_unref (play_sink->audio_sink); + + if (sink) { + gst_object_ref (sink); + gst_object_sink (sink); + } + play_sink->audio_sink = sink; + GST_OBJECT_UNLOCK (play_sink); +} + +static void +gst_play_sink_set_vis_plugin (GstPlaySink * play_sink, + GstElement * pending_visualisation) +{ + /* Take ownership */ + if (pending_visualisation) { + gst_object_ref (pending_visualisation); + gst_object_sink (pending_visualisation); + } + + /* Do we already have a visualisation change pending? If yes, change the + * pending vis with the new one. */ + GST_OBJECT_LOCK (play_sink); + if (play_sink->pending_visualisation) { + gst_object_unref (play_sink->pending_visualisation); + play_sink->pending_visualisation = pending_visualisation; + GST_OBJECT_UNLOCK (play_sink); + } else { + GST_OBJECT_UNLOCK (play_sink); + /* Was there a visualisation already set ? */ + if (play_sink->visualisation != NULL) { + GstBin *vis_bin = NULL; + + vis_bin = + GST_BIN_CAST (gst_object_get_parent (GST_OBJECT_CAST (play_sink-> + visualisation))); + + /* Check if the visualisation is already in a bin */ + if (GST_IS_BIN (vis_bin)) { + GstPad *vis_sink_pad = NULL, *tee_pad = NULL; + + /* Now get tee pad and block it async */ + vis_sink_pad = gst_element_get_pad (play_sink->visualisation, "sink"); + if (!GST_IS_PAD (vis_sink_pad)) { + goto beach; + } + tee_pad = gst_pad_get_peer (vis_sink_pad); + if (!GST_IS_PAD (tee_pad)) { + goto beach; + } + + play_sink->pending_visualisation = pending_visualisation; + /* Block with callback */ + gst_pad_set_blocked_async (tee_pad, TRUE, gst_play_sink_vis_blocked, + play_sink); + beach: + if (vis_sink_pad) { + gst_object_unref (vis_sink_pad); + } + if (tee_pad) { + gst_object_unref (tee_pad); + } + gst_object_unref (vis_bin); + } else { + play_sink->visualisation = pending_visualisation; + } + } else { + play_sink->visualisation = pending_visualisation; + } + } +} + +static void +gst_play_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPlaySink *play_sink; + + play_sink = GST_PLAY_SINK (object); + + switch (prop_id) { + case PROP_VIDEO_SINK: + gst_play_sink_set_video_sink (play_sink, g_value_get_object (value)); + break; + case PROP_AUDIO_SINK: + gst_play_sink_set_audio_sink (play_sink, g_value_get_object (value)); + break; + case PROP_VIS_PLUGIN: + gst_play_sink_set_vis_plugin (play_sink, g_value_get_object (value)); + break; + case PROP_VOLUME: + GST_OBJECT_LOCK (play_sink); + play_sink->volume = g_value_get_double (value); + GST_OBJECT_UNLOCK (play_sink); + break; + case PROP_FONT_DESC: + GST_OBJECT_LOCK (play_sink); + g_free (play_sink->font_desc); + play_sink->font_desc = g_strdup (g_value_get_string (value)); + if (play_sink->textoverlay_element) { + g_object_set (G_OBJECT (play_sink->textoverlay_element), + "font-desc", g_value_get_string (value), NULL); + } + GST_OBJECT_UNLOCK (play_sink); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_play_sink_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstPlaySink *play_sink; + + play_sink = GST_PLAY_SINK (object); + + switch (prop_id) { + case PROP_VIDEO_SINK: + GST_OBJECT_LOCK (play_sink); + g_value_set_object (value, play_sink->video_sink); + GST_OBJECT_UNLOCK (play_sink); + break; + case PROP_AUDIO_SINK: + GST_OBJECT_LOCK (play_sink); + g_value_set_object (value, play_sink->audio_sink); + GST_OBJECT_UNLOCK (play_sink); + break; + case PROP_VIS_PLUGIN: + GST_OBJECT_LOCK (play_sink); + g_value_set_object (value, play_sink->visualisation); + GST_OBJECT_UNLOCK (play_sink); + break; + case PROP_VOLUME: + GST_OBJECT_LOCK (play_sink); + g_value_set_double (value, play_sink->volume); + GST_OBJECT_UNLOCK (play_sink); + break; + case PROP_FRAME: + { + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +post_missing_element_message (GstPlaySink * playsink, const gchar * name) +{ + GstMessage *msg; + + msg = gst_missing_element_message_new (GST_ELEMENT_CAST (playsink), name); + gst_element_post_message (GST_ELEMENT_CAST (playsink), msg); +} + +static void +free_chain (GstPlayChain * chain) +{ + if (chain->bin) + gst_object_unref (chain->bin); + gst_object_unref (chain->playsink); + g_free (chain); +} + +static gboolean +add_chain (GstPlayChain * chain, gboolean add) +{ + if (chain->added == add) + return TRUE; + + if (add) + gst_bin_add (GST_BIN_CAST (chain->playsink), chain->bin); + else + gst_bin_remove (GST_BIN_CAST (chain->playsink), chain->bin); + + chain->added = add; + + return TRUE; +} + +static gboolean +activate_chain (GstPlayChain * chain, gboolean activate) +{ + if (chain->activated == activate) + return TRUE; + + if (activate) + gst_element_set_state (chain->bin, GST_STATE_PAUSED); + else + gst_element_set_state (chain->bin, GST_STATE_NULL); + + chain->activated = activate; + + return TRUE; +} + +/* make the element (bin) that contains the elements needed to perform + * video display. + * + * +------------------------------------------------+ + * | vbin | + * | +----------+ +----------+ +---------+ | + * | |colorspace| |videoscale| |videosink| | + * | +-sink src-sink src-sink | | + * | | +----------+ +----------+ +---------+ | + * sink-+ | + * +------------------------------------------------+ + * + */ +static GstPlayChain * +gen_video_chain (GstPlaySink * play_sink) +{ + GstPlayVideoChain *chain; + GstBin *bin; + GstPad *pad; + + chain = g_new0 (GstPlayVideoChain, 1); + chain->chain.playsink = gst_object_ref (play_sink); + + if (play_sink->video_sink) { + chain->sink = play_sink->video_sink; + } else { + chain->sink = gst_element_factory_make ("autovideosink", "videosink"); + if (chain->sink == NULL) { + chain->sink = gst_element_factory_make ("xvimagesink", "videosink"); + } + if (chain->sink == NULL) + goto no_sinks; + } + + /* create a bin to hold objects, as we create them we add them to this bin so + * that when something goes wrong we only need to unref the bin */ + chain->chain.bin = gst_bin_new ("vbin"); + bin = GST_BIN_CAST (chain->chain.bin); + gst_object_ref (bin); + gst_object_sink (bin); + gst_bin_add (bin, chain->sink); + + + chain->conv = gst_element_factory_make ("ffmpegcolorspace", "vconv"); + if (chain->conv == NULL) + goto no_colorspace; + gst_bin_add (bin, chain->conv); + + /* decouple decoder from sink, this improves playback quite a lot since the + * decoder can continue while the sink blocks for synchronisation. We don't + * need a lot of buffers as this consumes a lot of memory and we don't want + * too little because else we would be context switching too quickly. */ + chain->queue = gst_element_factory_make ("queue", "vqueue"); + g_object_set (G_OBJECT (chain->queue), "max-size-buffers", 3, + "max-size-bytes", 0, "max-size-time", (gint64) 0, NULL); + gst_bin_add (bin, chain->queue); + + chain->scale = gst_element_factory_make ("videoscale", "vscale"); + if (chain->scale == NULL) + goto no_videoscale; + gst_bin_add (bin, chain->scale); + + gst_element_link_pads (chain->conv, "src", chain->queue, "sink"); + gst_element_link_pads (chain->queue, "src", chain->scale, "sink"); + /* be more careful with the pad from the custom sink element, it might not + * be named 'sink' */ + if (!gst_element_link_pads (chain->scale, "src", chain->sink, NULL)) + goto link_failed; + + pad = gst_element_get_pad (chain->conv, "sink"); + chain->chain.sinkpad = gst_ghost_pad_new ("sink", pad); + gst_object_unref (pad); + gst_element_add_pad (chain->chain.bin, chain->chain.sinkpad); + + return (GstPlayChain *) chain; + + /* ERRORS */ +no_sinks: + { + post_missing_element_message (play_sink, "autovideosink"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Both autovideosink and xvimagesink elements are missing.")), + (NULL)); + free_chain ((GstPlayChain *) chain); + return NULL; + } +no_colorspace: + { + post_missing_element_message (play_sink, "ffmpegcolorspace"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "ffmpegcolorspace"), (NULL)); + free_chain ((GstPlayChain *) chain); + return NULL; + } + +no_videoscale: + { + post_missing_element_message (play_sink, "videoscale"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "videoscale"), ("possibly a liboil version mismatch?")); + free_chain ((GstPlayChain *) chain); + return NULL; + } +link_failed: + { + GST_ELEMENT_ERROR (play_sink, CORE, PAD, + (NULL), ("Failed to configure the video sink.")); + free_chain ((GstPlayChain *) chain); + return NULL; + } +} + +#if 0 +/* make an element for playback of video with subtitles embedded. + * + * +--------------------------------------------------+ + * | tbin +-------------+ | + * | +-----+ | textoverlay | +------+ | + * | | csp | +--video_sink | | vbin | | + * video_sink-sink src+ +-text_sink src-sink | | + * | +-----+ | +-------------+ +------+ | + * text_sink-------------+ | + * +--------------------------------------------------+ + * + * If there is no subtitle renderer this function will simply return the + * videosink without the text_sink pad. + */ +static GstElement * +gen_text_element (GstPlaySink * play_sink) +{ + GstElement *element, *csp, *overlay, *vbin; + GstPad *pad; + + /* Create the video rendering bin, error is posted when this fails. */ + vbin = gen_video_element (play_sink); + if (!vbin) + return NULL; + + /* Text overlay */ + overlay = gst_element_factory_make ("textoverlay", "overlay"); + + /* If no overlay return the video bin without subtitle support. */ + if (!overlay) + goto no_overlay; + + /* Create our bin */ + element = gst_bin_new ("textbin"); + + /* Set some parameters */ + g_object_set (G_OBJECT (overlay), + "halign", "center", "valign", "bottom", NULL); + if (play_sink->font_desc) { + g_object_set (G_OBJECT (overlay), "font-desc", play_sink->font_desc, NULL); + } + + /* Take a ref */ + play_sink->textoverlay_element = GST_ELEMENT_CAST (gst_object_ref (overlay)); + + /* we know this will succeed, as the video bin already created one before */ + csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp"); + + /* Add our elements */ + gst_bin_add_many (GST_BIN_CAST (element), csp, overlay, vbin, NULL); + + /* Link */ + gst_element_link_pads (csp, "src", overlay, "video_sink"); + gst_element_link_pads (overlay, "src", vbin, "sink"); + + /* Add ghost pads on the subtitle bin */ + pad = gst_element_get_pad (overlay, "text_sink"); + gst_element_add_pad (element, gst_ghost_pad_new ("text_sink", pad)); + gst_object_unref (pad); + + pad = gst_element_get_pad (csp, "sink"); + gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad)); + gst_object_unref (pad); + + /* Set state to READY */ + gst_element_set_state (element, GST_STATE_READY); + + return element; + + /* ERRORS */ +no_overlay: + { + post_missing_element_message (play_sink, "textoverlay"); + GST_WARNING_OBJECT (play_sink, + "No overlay (pango) element, subtitles disabled"); + return vbin; + } +} +#endif + + +/* make the chain that contains the elements needed to perform + * audio playback. + * + * +-------------------------------------------------------------+ + * | abin | + * | +---------+ +----------+ +---------+ +---------+ | + * | |audioconv| |audioscale| | volume | |audiosink| | + * | +-sink src-sink src-sink src-sink | | + * | | +---------+ +----------+ +---------+ +---------+ | + * sink-+ | + * +-------------------------------------------------------------+ + */ +static GstPlayChain * +gen_audio_chain (GstPlaySink * play_sink) +{ + GstPlayAudioChain *chain; + GstBin *bin; + gboolean res; + GstPad *pad; + + chain = g_new0 (GstPlayAudioChain, 1); + chain->chain.playsink = gst_object_ref (play_sink); + + if (play_sink->audio_sink) { + chain->sink = play_sink->audio_sink; + } else { + chain->sink = gst_element_factory_make ("autoaudiosink", "audiosink"); + if (chain->sink == NULL) { + chain->sink = gst_element_factory_make ("alsasink", "audiosink"); + } + if (chain->sink == NULL) + goto no_sinks; + } + chain->chain.bin = gst_bin_new ("abin"); + bin = GST_BIN_CAST (chain->chain.bin); + gst_object_ref (bin); + gst_object_sink (bin); + gst_bin_add (bin, chain->sink); + + chain->conv = gst_element_factory_make ("audioconvert", "aconv"); + if (chain->conv == NULL) + goto no_audioconvert; + gst_bin_add (bin, chain->conv); + + chain->resample = gst_element_factory_make ("audioresample", "aresample"); + if (chain->resample == NULL) + goto no_audioresample; + gst_bin_add (bin, chain->resample); + + chain->volume = gst_element_factory_make ("volume", "volume"); + g_object_set (G_OBJECT (chain->volume), "volume", play_sink->volume, NULL); + gst_bin_add (bin, chain->volume); + + res = gst_element_link_pads (chain->conv, "src", chain->resample, "sink"); + res &= gst_element_link_pads (chain->resample, "src", chain->volume, "sink"); + res &= gst_element_link_pads (chain->volume, "src", chain->sink, NULL); + if (!res) + goto link_failed; + + pad = gst_element_get_pad (chain->conv, "sink"); + chain->chain.sinkpad = gst_ghost_pad_new ("sink", pad); + gst_object_unref (pad); + gst_element_add_pad (chain->chain.bin, chain->chain.sinkpad); + + return (GstPlayChain *) chain; + + /* ERRORS */ +no_sinks: + { + post_missing_element_message (play_sink, "autoaudiosink"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Both autoaudiosink and alsasink elements are missing.")), (NULL)); + free_chain ((GstPlayChain *) chain); + return NULL; + } +no_audioconvert: + { + post_missing_element_message (play_sink, "audioconvert"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "audioconvert"), ("possibly a liboil version mismatch?")); + free_chain ((GstPlayChain *) chain); + return NULL; + } + +no_audioresample: + { + post_missing_element_message (play_sink, "audioresample"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "audioresample"), ("possibly a liboil version mismatch?")); + free_chain ((GstPlayChain *) chain); + return NULL; + } +link_failed: + { + GST_ELEMENT_ERROR (play_sink, CORE, PAD, + (NULL), ("Failed to configure the audio sink.")); + free_chain ((GstPlayChain *) chain); + return NULL; + } +} + +#if 0 +/* make the element (bin) that contains the elements needed to perform + * visualisation ouput. The idea is to split the audio using tee, then + * sending the output to the regular audio bin and the other output to + * the vis plugin that transforms it into a video that is rendered with the + * normal video bin. The video and audio bins are run in threads to make sure + * they don't block eachother. + * + * +-----------------------------------------------------------------------+ + * | visbin | + * | +------+ +--------+ +----------------+ | + * | | tee | | aqueue | | abin ... | | + * | +-sink src-sink src-sink | | + * | | | | +--------+ +----------------+ | + * | | | | | + * | | | | +------+ +------------+ +------+ +-----------+ | + * | | | | |vqueue| | audioconv | | vis | | vbin ... | | + * | | | src-sink src-sink + samp src-sink src-sink | | + * | | | | +------+ +------------+ +------+ +-----------+ | + * | | | | | + * | | +------+ | + * sink-+ | + * +-----------------------------------------------------------------------+ + */ +static GstElement * +gen_vis_element (GstPlaySink * play_sink) +{ + gboolean res; + GstElement *element; + GstElement *tee; + GstElement *asink; + GstElement *vsink; + GstElement *conv; + GstElement *resamp; + GstElement *conv2; + GstElement *vis; + GstElement *vqueue, *aqueue; + GstPad *pad, *rpad; + + /* errors are already posted when these fail. */ + asink = gen_audio_element (play_sink); + if (!asink) + return NULL; + vsink = gen_video_element (play_sink); + if (!vsink) { + gst_object_unref (asink); + return NULL; + } + + element = gst_bin_new ("visbin"); + tee = gst_element_factory_make ("tee", "tee"); + + vqueue = gst_element_factory_make ("queue", "vqueue"); + aqueue = gst_element_factory_make ("queue", "aqueue"); + + gst_bin_add (GST_BIN_CAST (element), asink); + gst_bin_add (GST_BIN_CAST (element), vqueue); + gst_bin_add (GST_BIN_CAST (element), aqueue); + gst_bin_add (GST_BIN_CAST (element), vsink); + gst_bin_add (GST_BIN_CAST (element), tee); + + conv = gst_element_factory_make ("audioconvert", "aconv"); + if (conv == NULL) + goto no_audioconvert; + gst_bin_add (GST_BIN_CAST (element), conv); + + resamp = gst_element_factory_make ("audioresample", "aresamp"); + if (resamp == NULL) + goto no_audioresample; + gst_bin_add (GST_BIN_CAST (element), resamp); + + conv2 = gst_element_factory_make ("audioconvert", "aconv2"); + if (conv2 == NULL) + goto no_audioconvert; + gst_bin_add (GST_BIN_CAST (element), conv2); + + if (play_sink->visualisation) { + gst_object_ref (play_sink->visualisation); + vis = play_sink->visualisation; + } else { + vis = gst_element_factory_make ("goom", "vis"); + if (!vis) + goto no_goom; + } + gst_bin_add (GST_BIN_CAST (element), vis); + + res = gst_element_link_pads (vqueue, "src", conv, "sink"); + res &= gst_element_link_pads (conv, "src", resamp, "sink"); + res &= gst_element_link_pads (resamp, "src", conv2, "sink"); + res &= gst_element_link_pads (conv2, "src", vis, "sink"); + res &= gst_element_link_pads (vis, "src", vsink, "sink"); + if (!res) + goto link_failed; + + pad = gst_element_get_pad (aqueue, "sink"); + rpad = gst_element_get_request_pad (tee, "src%d"); + gst_pad_link (rpad, pad); + gst_object_unref (rpad); + gst_object_unref (pad); + gst_element_link_pads (aqueue, "src", asink, "sink"); + + pad = gst_element_get_pad (vqueue, "sink"); + rpad = gst_element_get_request_pad (tee, "src%d"); + gst_pad_link (rpad, pad); + gst_object_unref (rpad); + gst_object_unref (pad); + + pad = gst_element_get_pad (tee, "sink"); + gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad)); + gst_object_unref (pad); + + return element; + + /* ERRORS */ +no_audioconvert: + { + post_missing_element_message (play_sink, "audioconvert"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "audioconvert"), ("possibly a liboil version mismatch?")); + gst_object_unref (element); + return NULL; + } +no_audioresample: + { + post_missing_element_message (play_sink, "audioresample"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "audioresample"), (NULL)); + gst_object_unref (element); + return NULL; + } +no_goom: + { + post_missing_element_message (play_sink, "goom"); + GST_ELEMENT_ERROR (play_sink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "goom"), (NULL)); + gst_object_unref (element); + return NULL; + } +link_failed: + { + GST_ELEMENT_ERROR (play_sink, CORE, PAD, + (NULL), ("Failed to configure the visualisation element.")); + gst_object_unref (element); + return NULL; + } +} +#endif + +GstPlaySinkMode +gst_play_sink_get_mode (GstPlaySink * playsink) +{ + GstPlaySinkMode res; + + GST_OBJECT_LOCK (playsink); + res = playsink->mode; + GST_OBJECT_LOCK (playsink); + + return res; +} + +/* this function is called when all the request pads are requested and when we + * have to construct the final pipeline. + */ +gboolean +gst_play_sink_set_mode (GstPlaySink * playsink, GstPlaySinkMode mode) +{ + if (mode & GST_PLAY_SINK_MODE_AUDIO && playsink->audio_pad) { + if (!playsink->audiochain) + playsink->audiochain = gen_audio_chain (playsink); + add_chain (playsink->audiochain, TRUE); + activate_chain (playsink->audiochain, TRUE); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->audio_pad), + playsink->audiochain->sinkpad); + } else { + if (playsink->audiochain) { + add_chain (playsink->audiochain, FALSE); + activate_chain (playsink->audiochain, FALSE); + } + if (playsink->audio_pad) + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->audio_pad), NULL); + } + + if (mode & GST_PLAY_SINK_MODE_VIDEO && playsink->video_pad) { + if (!playsink->videochain) + playsink->videochain = gen_video_chain (playsink); + add_chain (playsink->videochain, TRUE); + activate_chain (playsink->videochain, TRUE); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad), + playsink->videochain->sinkpad); + } else { + if (playsink->videochain) { + add_chain (playsink->videochain, FALSE); + activate_chain (playsink->videochain, FALSE); + } + if (playsink->video_pad) + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad), NULL); + } + + playsink->mode = mode; + + return TRUE; +} + +GstPad * +gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type) +{ + GstPad *res = NULL; + gboolean created = FALSE; + + switch (type) { + case GST_PLAY_SINK_TYPE_AUDIO: + if (!playsink->audio_pad) { + playsink->audio_pad = + gst_ghost_pad_new_no_target ("audio_sink", GST_PAD_SINK); + created = TRUE; + } + res = playsink->audio_pad; + break; + case GST_PLAY_SINK_TYPE_VIDEO: + if (!playsink->video_pad) { + playsink->video_pad = + gst_ghost_pad_new_no_target ("video_sink", GST_PAD_SINK); + created = TRUE; + } + res = playsink->video_pad; + break; + case GST_PLAY_SINK_TYPE_TEXT: + if (!playsink->text_pad) { + playsink->text_pad = + gst_ghost_pad_new_no_target ("text_sink", GST_PAD_SINK); + created = TRUE; + } + res = playsink->text_pad; + break; + default: + res = NULL; + break; + } + if (created && res) { + gst_pad_set_active (res, TRUE); + gst_element_add_pad (GST_ELEMENT_CAST (playsink), res); + } + return res; +} + +void +gst_play_sink_release_pad (GstPlaySink * playsink, GstPad * pad) +{ + GstPad **res = NULL; + + if (pad == playsink->video_pad) { + res = &playsink->video_pad; + } else if (pad == playsink->audio_pad) { + res = &playsink->audio_pad; + } else if (pad == playsink->text_pad) { + res = &playsink->text_pad; + } + + if (*res) { + gst_pad_set_active (*res, FALSE); + gst_element_remove_pad (GST_ELEMENT_CAST (playsink), *res); + *res = NULL; + } +} + +/* Send an event to our sinks until one of them works; don't then send to the + * remaining sinks (unlike GstBin) + */ +static gboolean +gst_play_sink_send_event_to_sink (GstPlaySink * play_sink, GstEvent * event) +{ + gboolean res = TRUE; + + if (play_sink->audiochain) { + gst_event_ref (event); + if ((res = gst_element_send_event (play_sink->audiochain->bin, event))) { + GST_DEBUG_OBJECT (play_sink, "Sent event succesfully to audio sink"); + goto done; + } + GST_DEBUG_OBJECT (play_sink, "Event failed when sent to audio sink"); + } + if (play_sink->videochain) { + gst_event_ref (event); + if ((res = gst_element_send_event (play_sink->videochain->bin, event))) { + GST_DEBUG_OBJECT (play_sink, "Sent event succesfully to video sink"); + goto done; + } + GST_DEBUG_OBJECT (play_sink, "Event failed when sent to video sink"); + } +done: + gst_event_unref (event); + return res; +} + +/* We only want to send the event to a single sink (overriding GstBin's + * behaviour), but we want to keep GstPipeline's behaviour - wrapping seek + * events appropriately. So, this is a messy duplication of code. */ +static gboolean +gst_play_sink_send_event (GstElement * element, GstEvent * event) +{ + gboolean res = FALSE; + GstEventType event_type = GST_EVENT_TYPE (event); + + switch (event_type) { + case GST_EVENT_SEEK: + GST_DEBUG_OBJECT (element, "Sending seek event to a sink"); + res = gst_play_sink_send_event_to_sink (GST_PLAY_SINK (element), event); + break; + default: + res = parent_class->send_event (element, event); + break; + } + return res; +} + +static GstStateChangeReturn +gst_play_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstPlaySink *play_sink; + + play_sink = GST_PLAY_SINK (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* FIXME Release audio device when we implement that */ + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* remove sinks we added */ + break; + default: + break; + } + + return ret; +} diff --git a/gst/playback/gstplaysink.h b/gst/playback/gstplaysink.h new file mode 100644 index 0000000..e319e2c --- /dev/null +++ b/gst/playback/gstplaysink.h @@ -0,0 +1,74 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_PLAY_SINK_H__ +#define __GST_PLAY_SINK_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_PLAY_SINK \ + (gst_play_sink_get_type()) +#define GST_PLAY_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY_SINK, GstPlaySink)) +#define GST_PLAY_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAY_SINK, GstPlaySinkClass)) +#define GST_IS_PLAY_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY_SINK)) +#define GST_IS_PLAY_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAY_SINK)) + +/** + * GstPlaySinkMode: + * @GST_PLAY_SINK_MODE_VIDEO: + * @GST_PLAY_SINK_MODE_AUDIO: + * @GST_PLAY_SINK_MODE_TEXT: + * @GST_PLAY_SINK_MODE_VIS: + * + * Features to enable in the sink. + */ +typedef enum { + GST_PLAY_SINK_MODE_VIDEO = (1 << 0), + GST_PLAY_SINK_MODE_AUDIO = (1 << 1), + GST_PLAY_SINK_MODE_TEXT = (1 << 2), + GST_PLAY_SINK_MODE_VIS = (1 << 3) +} GstPlaySinkMode; + +typedef enum { + GST_PLAY_SINK_TYPE_AUDIO = 0, + GST_PLAY_SINK_TYPE_VIDEO = 1, + GST_PLAY_SINK_TYPE_TEXT = 2, + GST_PLAY_SINK_TYPE_LAST = 3 +} GstPlaySinkType; + +typedef struct _GstPlaySink GstPlaySink; +typedef struct _GstPlaySinkClass GstPlaySinkClass; + +GType gst_play_sink_get_type (void); + +GstPad * gst_play_sink_request_pad (GstPlaySink *playsink, GstPlaySinkType type); +void gst_play_sink_release_pad (GstPlaySink *playsink, GstPad *pad); + +GstPlaySinkMode gst_play_sink_get_mode (GstPlaySink *playsink); +gboolean gst_play_sink_set_mode (GstPlaySink *playsink, GstPlaySinkMode mode); + +G_END_DECLS + +#endif /* __GST_PLAY_SINK_H__ */ diff --git a/gst/playback/gstqueue2.c b/gst/playback/gstqueue2.c index 0064996..26838ac 100644 --- a/gst/playback/gstqueue2.c +++ b/gst/playback/gstqueue2.c @@ -1,7 +1,7 @@ /* GStreamer * Copyright (C) 1999,2000 Erik Walthinsen * 2003 Colin Walters - * 2000,2005,2007 Wim Taymans + * 2000,2005,2007 Wim Taymans * 2007 Thiago Sousa Santos * * gstqueue2.c: diff --git a/gst/playback/gststreaminfo.h b/gst/playback/gststreaminfo.h index 512176c..b4157ea 100644 --- a/gst/playback/gststreaminfo.h +++ b/gst/playback/gststreaminfo.h @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) <1999> Erik Walthinsen + * <2007> Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public diff --git a/gst/playback/gsturidecodebin.c b/gst/playback/gsturidecodebin.c index d54e14b..1cbf874 100644 --- a/gst/playback/gsturidecodebin.c +++ b/gst/playback/gsturidecodebin.c @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) <2007> Wim Taymans + * Copyright (C) <2007> Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public diff --git a/gst/playback/test.c b/gst/playback/test.c index 0ae17fe..0d36838 100644 --- a/gst/playback/test.c +++ b/gst/playback/test.c @@ -1,6 +1,6 @@ /* GStreamer * Copyright (C) <1999> Erik Walthinsen - * Copyright (C) <2006> Wim Taymans + * Copyright (C) <2007> Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public diff --git a/gst/playback/test7.c b/gst/playback/test7.c new file mode 100644 index 0000000..87ff8b0 --- /dev/null +++ b/gst/playback/test7.c @@ -0,0 +1,149 @@ +/* GStreamer + * Copyright (C) <2007> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include + +#define UPDATE_INTERVAL 500 + +static int arg_count; +static int max_count; + +static gboolean +update_scale (GstElement * element) +{ + gint64 duration = -1; + gint64 position = -1; + GstFormat format = GST_FORMAT_TIME; + gchar dur_str[32], pos_str[32]; + + if (gst_element_query_position (element, &format, &position) && + position != -1) { + g_snprintf (pos_str, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (position)); + } else { + g_snprintf (pos_str, 32, "-:--:--.---------"); + } + + if (gst_element_query_duration (element, &format, &duration) && + duration != -1) { + g_snprintf (dur_str, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); + } else { + g_snprintf (dur_str, 32, "-:--:--.---------"); + } + + g_print ("%s / %s\n", pos_str, dur_str); + + return TRUE; +} + +static void +warning_cb (GstBus * bus, GstMessage * msg, gpointer foo) +{ + GError *err = NULL; + gchar *dbg = NULL; + + gst_message_parse_warning (msg, &err, &dbg); + + g_printerr ("WARNING: %s (%s)\n", err->message, (dbg) ? dbg : "no details"); + + g_error_free (err); + g_free (dbg); +} + +static void +error_cb (GstBus * bus, GstMessage * msg, GMainLoop * main_loop) +{ + GError *err = NULL; + gchar *dbg = NULL; + + gst_message_parse_error (msg, &err, &dbg); + + g_printerr ("ERROR: %s (%s)\n", err->message, (dbg) ? dbg : "no details"); + + g_main_loop_quit (main_loop); + + g_error_free (err); + g_free (dbg); +} + +static void +eos_cb (GstBus * bus, GstMessage * msg, GMainLoop * main_loop) +{ + g_print ("EOS\n"); + g_main_loop_quit (main_loop); +} + +static void +about_to_finish_cb (GstElement * element, gchar * uri[]) +{ + if (arg_count < max_count) { + g_object_set (G_OBJECT (element), "next-uri", uri[arg_count], NULL); + arg_count++; + } +} + +gint +main (gint argc, gchar * argv[]) +{ + GstStateChangeReturn res; + GstElement *player; + GMainLoop *loop; + GstBus *bus; + + gst_init (&argc, &argv); + + loop = g_main_loop_new (NULL, TRUE); + + if (argc < 2) { + g_print ("usage: %s [ ... ]\n", argv[0]); + exit (-1); + } + + player = gst_element_factory_make ("playbin2", "player"); + g_assert (player); + + bus = gst_pipeline_get_bus (GST_PIPELINE (player)); + gst_bus_add_signal_watch (bus); + + g_signal_connect (bus, "message::eos", G_CALLBACK (eos_cb), loop); + g_signal_connect (bus, "message::error", G_CALLBACK (error_cb), loop); + g_signal_connect (bus, "message::warning", G_CALLBACK (warning_cb), NULL); + + g_object_set (G_OBJECT (player), "uri", argv[1], NULL); + + arg_count = 2; + max_count = argc; + g_signal_connect (player, "about-to-finish", G_CALLBACK (about_to_finish_cb), + argv); + + res = gst_element_set_state (player, GST_STATE_PLAYING); + if (res == GST_STATE_CHANGE_FAILURE) { + g_print ("could not play\n"); + return -1; + } + + g_timeout_add (UPDATE_INTERVAL, (GSourceFunc) update_scale, player); + + g_main_loop_run (loop); + + /* tidy up */ + gst_element_set_state (player, GST_STATE_NULL); + gst_object_unref (player); + gst_object_unref (bus); + + return 0; +} -- 2.7.4