mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-29 13:11:06 +00:00
4e898a661a
Keep the pipeline paused when we detect download buffering. The user has to manually start the pipeline for now because we can't estimate when the buffering will finish or when we have underrun.
2894 lines
87 KiB
C
2894 lines
87 KiB
C
/* GStreamer
|
|
*
|
|
* seek.c: seeking sample application
|
|
*
|
|
* Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
|
|
* 2006 Stefan Kost <ensonic@users.sf.net>
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <math.h>
|
|
#include <glib.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gst/gst.h>
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_X
|
|
#include <gdk/gdkx.h>
|
|
#endif
|
|
#include <gst/interfaces/xoverlay.h>
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (seek_debug);
|
|
#define GST_CAT_DEFAULT (seek_debug)
|
|
|
|
/* configuration */
|
|
|
|
//#define SOURCE "filesrc"
|
|
#define SOURCE "gnomevfssrc"
|
|
|
|
#define ASINK "alsasink"
|
|
//#define ASINK "osssink"
|
|
|
|
#define VSINK "xvimagesink"
|
|
//#define VSINK "sdlvideosink"
|
|
//#define VSINK "ximagesink"
|
|
//#define VSINK "aasink"
|
|
//#define VSINK "cacasink"
|
|
|
|
#define FILL_INTERVAL 100
|
|
//#define UPDATE_INTERVAL 500
|
|
//#define UPDATE_INTERVAL 100
|
|
#define UPDATE_INTERVAL 10
|
|
|
|
/* number of milliseconds to play for after a seek */
|
|
#define SCRUB_TIME 100
|
|
|
|
/* timeout for gst_element_get_state() after a seek */
|
|
#define SEEK_TIMEOUT 40 * GST_MSECOND
|
|
|
|
#define DEFAULT_VIDEO_HEIGHT 300
|
|
|
|
|
|
static GList *seekable_pads = NULL;
|
|
static GList *rate_pads = NULL;
|
|
static GList *seekable_elements = NULL;
|
|
|
|
static gboolean accurate_seek = FALSE;
|
|
static gboolean keyframe_seek = FALSE;
|
|
static gboolean loop_seek = FALSE;
|
|
static gboolean flush_seek = TRUE;
|
|
static gboolean scrub = TRUE;
|
|
static gboolean play_scrub = FALSE;
|
|
static gboolean skip_seek = FALSE;
|
|
static gdouble rate = 1.0;
|
|
|
|
static GstElement *pipeline;
|
|
static gint pipeline_type;
|
|
static const gchar *pipeline_spec;
|
|
static gint64 position = -1;
|
|
static gint64 duration = -1;
|
|
static GtkAdjustment *adjustment;
|
|
static GtkWidget *hscale, *statusbar;
|
|
static guint status_id = 0;
|
|
static gboolean stats = FALSE;
|
|
static gboolean elem_seek = FALSE;
|
|
static gboolean verbose = FALSE;
|
|
|
|
static gboolean is_live = FALSE;
|
|
static gboolean buffering = FALSE;
|
|
static GstBufferingMode mode;
|
|
static gint64 buffering_left;
|
|
static GstState state = GST_STATE_NULL;
|
|
static guint update_id = 0;
|
|
static guint seek_timeout_id = 0;
|
|
static gulong changed_id;
|
|
static guint fill_id = 0;
|
|
|
|
static gint n_video = 0, n_audio = 0, n_text = 0;
|
|
static gboolean need_streams = TRUE;
|
|
static GtkWidget *video_combo, *audio_combo, *text_combo, *vis_combo;
|
|
static GtkWidget *vis_checkbox, *video_checkbox, *audio_checkbox;
|
|
static GtkWidget *text_checkbox, *mute_checkbox, *volume_spinbutton;
|
|
static GtkWidget *skip_checkbox, *video_window, *download_checkbox;
|
|
static GtkWidget *rate_spinbutton;
|
|
|
|
static GStaticMutex state_mutex = G_STATIC_MUTEX_INIT;
|
|
|
|
static GtkWidget *format_combo, *step_amount_spinbutton, *step_rate_spinbutton;
|
|
static GtkWidget *shuttle_checkbox, *step_button;
|
|
static GtkWidget *shuttle_hscale;
|
|
static GtkAdjustment *shuttle_adjustment;
|
|
|
|
static GList *paths = NULL, *l = NULL;
|
|
|
|
/* we keep an array of the visualisation entries so that we can easily switch
|
|
* with the combo box index. */
|
|
typedef struct
|
|
{
|
|
GstElementFactory *factory;
|
|
} VisEntry;
|
|
|
|
static GArray *vis_entries;
|
|
|
|
static void clear_streams (GstElement * pipeline);
|
|
static void volume_notify_cb (GstElement * pipeline, GParamSpec * arg,
|
|
gpointer user_dat);
|
|
|
|
/* pipeline construction */
|
|
|
|
typedef struct
|
|
{
|
|
const gchar *padname;
|
|
GstPad *target;
|
|
GstElement *bin;
|
|
}
|
|
dyn_link;
|
|
|
|
static GstElement *
|
|
gst_element_factory_make_or_warn (gchar * type, gchar * name)
|
|
{
|
|
GstElement *element = gst_element_factory_make (type, name);
|
|
|
|
if (!element) {
|
|
g_warning ("Failed to create element %s of type %s", name, type);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
static void
|
|
dynamic_link (GstPadTemplate * templ, GstPad * newpad, gpointer data)
|
|
{
|
|
gchar *padname;
|
|
dyn_link *connect = (dyn_link *) data;
|
|
|
|
padname = gst_pad_get_name (newpad);
|
|
|
|
if (connect->padname == NULL || !strcmp (padname, connect->padname)) {
|
|
if (connect->bin)
|
|
gst_bin_add (GST_BIN (pipeline), connect->bin);
|
|
gst_pad_link (newpad, connect->target);
|
|
|
|
//seekable_pads = g_list_prepend (seekable_pads, newpad);
|
|
rate_pads = g_list_prepend (rate_pads, newpad);
|
|
}
|
|
g_free (padname);
|
|
}
|
|
|
|
static void
|
|
setup_dynamic_link (GstElement * element, const gchar * padname,
|
|
GstPad * target, GstElement * bin)
|
|
{
|
|
dyn_link *connect;
|
|
|
|
connect = g_new0 (dyn_link, 1);
|
|
connect->padname = g_strdup (padname);
|
|
connect->target = target;
|
|
connect->bin = bin;
|
|
|
|
g_signal_connect (G_OBJECT (element), "pad-added", G_CALLBACK (dynamic_link),
|
|
connect);
|
|
}
|
|
|
|
static GstElement *
|
|
make_mod_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *src, *decoder, *audiosink;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
decoder = gst_element_factory_make_or_warn ("modplug", "decoder");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "sink");
|
|
//g_object_set (G_OBJECT (audiosink), "sync", FALSE, NULL);
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), decoder);
|
|
gst_bin_add (GST_BIN (pipeline), audiosink);
|
|
|
|
gst_element_link (src, decoder);
|
|
gst_element_link (decoder, audiosink);
|
|
|
|
seekable = gst_element_get_static_pad (decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (decoder, "sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_dv_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *src, *demux, *decoder, *audiosink, *videosink;
|
|
GstElement *a_queue, *v_queue;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
demux = gst_element_factory_make_or_warn ("dvdemux", "demuxer");
|
|
v_queue = gst_element_factory_make_or_warn ("queue", "v_queue");
|
|
decoder = gst_element_factory_make_or_warn ("ffdec_dvvideo", "decoder");
|
|
videosink = gst_element_factory_make_or_warn (VSINK, "v_sink");
|
|
a_queue = gst_element_factory_make_or_warn ("queue", "a_queue");
|
|
audiosink = gst_element_factory_make_or_warn ("alsasink", "a_sink");
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_bin_add (GST_BIN (pipeline), a_queue);
|
|
gst_bin_add (GST_BIN (pipeline), audiosink);
|
|
gst_bin_add (GST_BIN (pipeline), v_queue);
|
|
gst_bin_add (GST_BIN (pipeline), decoder);
|
|
gst_bin_add (GST_BIN (pipeline), videosink);
|
|
|
|
gst_element_link (src, demux);
|
|
gst_element_link (a_queue, audiosink);
|
|
gst_element_link (v_queue, decoder);
|
|
gst_element_link (decoder, videosink);
|
|
|
|
setup_dynamic_link (demux, "video", gst_element_get_static_pad (v_queue,
|
|
"sink"), NULL);
|
|
setup_dynamic_link (demux, "audio", gst_element_get_static_pad (a_queue,
|
|
"sink"), NULL);
|
|
|
|
seekable = gst_element_get_static_pad (decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_wav_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *src, *decoder, *audiosink;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
decoder = gst_element_factory_make_or_warn ("wavparse", "decoder");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "sink");
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), decoder);
|
|
gst_bin_add (GST_BIN (pipeline), audiosink);
|
|
|
|
gst_element_link (src, decoder);
|
|
|
|
setup_dynamic_link (decoder, "src", gst_element_get_static_pad (audiosink,
|
|
"sink"), NULL);
|
|
|
|
seekable_elements = g_list_prepend (seekable_elements, audiosink);
|
|
|
|
/* force element seeking on this pipeline */
|
|
elem_seek = TRUE;
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_flac_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *src, *decoder, *audiosink;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
decoder = gst_element_factory_make_or_warn ("flacdec", "decoder");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "sink");
|
|
g_object_set (G_OBJECT (audiosink), "sync", FALSE, NULL);
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), decoder);
|
|
gst_bin_add (GST_BIN (pipeline), audiosink);
|
|
|
|
gst_element_link (src, decoder);
|
|
gst_element_link (decoder, audiosink);
|
|
|
|
seekable = gst_element_get_static_pad (decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (decoder, "sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_sid_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *src, *decoder, *audiosink;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
decoder = gst_element_factory_make_or_warn ("siddec", "decoder");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "sink");
|
|
//g_object_set (G_OBJECT (audiosink), "sync", FALSE, NULL);
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), decoder);
|
|
gst_bin_add (GST_BIN (pipeline), audiosink);
|
|
|
|
gst_element_link (src, decoder);
|
|
gst_element_link (decoder, audiosink);
|
|
|
|
seekable = gst_element_get_static_pad (decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (decoder, "sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_parse_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *src, *parser, *fakesink;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
parser = gst_element_factory_make_or_warn ("mpegparse", "parse");
|
|
fakesink = gst_element_factory_make_or_warn ("fakesink", "sink");
|
|
g_object_set (G_OBJECT (fakesink), "silent", TRUE, NULL);
|
|
g_object_set (G_OBJECT (fakesink), "sync", TRUE, NULL);
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), parser);
|
|
gst_bin_add (GST_BIN (pipeline), fakesink);
|
|
|
|
gst_element_link (src, parser);
|
|
gst_element_link (parser, fakesink);
|
|
|
|
seekable = gst_element_get_static_pad (parser, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (parser, "sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_vorbis_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline, *audio_bin;
|
|
GstElement *src, *demux, *decoder, *convert, *audiosink;
|
|
GstPad *pad, *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
demux = gst_element_factory_make_or_warn ("oggdemux", "demux");
|
|
decoder = gst_element_factory_make_or_warn ("vorbisdec", "decoder");
|
|
convert = gst_element_factory_make_or_warn ("audioconvert", "convert");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "sink");
|
|
g_object_set (G_OBJECT (audiosink), "sync", TRUE, NULL);
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
audio_bin = gst_bin_new ("a_decoder_bin");
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_bin_add (GST_BIN (audio_bin), decoder);
|
|
gst_bin_add (GST_BIN (audio_bin), convert);
|
|
gst_bin_add (GST_BIN (audio_bin), audiosink);
|
|
gst_bin_add (GST_BIN (pipeline), audio_bin);
|
|
|
|
gst_element_link (src, demux);
|
|
gst_element_link (decoder, convert);
|
|
gst_element_link (convert, audiosink);
|
|
|
|
pad = gst_element_get_static_pad (decoder, "sink");
|
|
gst_element_add_pad (audio_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, NULL, gst_element_get_static_pad (audio_bin,
|
|
"sink"), NULL);
|
|
|
|
seekable = gst_element_get_static_pad (decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (decoder, "sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_theora_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline, *video_bin;
|
|
GstElement *src, *demux, *decoder, *convert, *videosink;
|
|
GstPad *pad, *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
demux = gst_element_factory_make_or_warn ("oggdemux", "demux");
|
|
decoder = gst_element_factory_make_or_warn ("theoradec", "decoder");
|
|
convert = gst_element_factory_make_or_warn ("ffmpegcolorspace", "convert");
|
|
videosink = gst_element_factory_make_or_warn (VSINK, "sink");
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
video_bin = gst_bin_new ("v_decoder_bin");
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_bin_add (GST_BIN (video_bin), decoder);
|
|
gst_bin_add (GST_BIN (video_bin), convert);
|
|
gst_bin_add (GST_BIN (video_bin), videosink);
|
|
gst_bin_add (GST_BIN (pipeline), video_bin);
|
|
|
|
gst_element_link (src, demux);
|
|
gst_element_link (decoder, convert);
|
|
gst_element_link (convert, videosink);
|
|
|
|
pad = gst_element_get_static_pad (decoder, "sink");
|
|
gst_element_add_pad (video_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, NULL, gst_element_get_static_pad (video_bin,
|
|
"sink"), NULL);
|
|
|
|
seekable = gst_element_get_static_pad (decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (decoder, "sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_vorbis_theora_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline, *audio_bin, *video_bin;
|
|
GstElement *src, *demux, *a_decoder, *a_convert, *v_decoder, *v_convert;
|
|
GstElement *audiosink, *videosink;
|
|
GstElement *a_queue, *v_queue, *v_scale;
|
|
GstPad *seekable;
|
|
GstPad *pad;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
demux = gst_element_factory_make_or_warn ("oggdemux", "demux");
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_element_link (src, demux);
|
|
|
|
audio_bin = gst_bin_new ("a_decoder_bin");
|
|
a_queue = gst_element_factory_make_or_warn ("queue", "a_queue");
|
|
a_decoder = gst_element_factory_make_or_warn ("vorbisdec", "a_dec");
|
|
a_convert = gst_element_factory_make_or_warn ("audioconvert", "a_convert");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "a_sink");
|
|
|
|
gst_bin_add (GST_BIN (pipeline), audio_bin);
|
|
|
|
gst_bin_add (GST_BIN (audio_bin), a_queue);
|
|
gst_bin_add (GST_BIN (audio_bin), a_decoder);
|
|
gst_bin_add (GST_BIN (audio_bin), a_convert);
|
|
gst_bin_add (GST_BIN (audio_bin), audiosink);
|
|
|
|
gst_element_link (a_queue, a_decoder);
|
|
gst_element_link (a_decoder, a_convert);
|
|
gst_element_link (a_convert, audiosink);
|
|
|
|
pad = gst_element_get_static_pad (a_queue, "sink");
|
|
gst_element_add_pad (audio_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, NULL, gst_element_get_static_pad (audio_bin,
|
|
"sink"), NULL);
|
|
|
|
video_bin = gst_bin_new ("v_decoder_bin");
|
|
v_queue = gst_element_factory_make_or_warn ("queue", "v_queue");
|
|
v_decoder = gst_element_factory_make_or_warn ("theoradec", "v_dec");
|
|
v_convert =
|
|
gst_element_factory_make_or_warn ("ffmpegcolorspace", "v_convert");
|
|
v_scale = gst_element_factory_make_or_warn ("videoscale", "v_scale");
|
|
videosink = gst_element_factory_make_or_warn (VSINK, "v_sink");
|
|
|
|
gst_bin_add (GST_BIN (pipeline), video_bin);
|
|
|
|
gst_bin_add (GST_BIN (video_bin), v_queue);
|
|
gst_bin_add (GST_BIN (video_bin), v_decoder);
|
|
gst_bin_add (GST_BIN (video_bin), v_convert);
|
|
gst_bin_add (GST_BIN (video_bin), v_scale);
|
|
gst_bin_add (GST_BIN (video_bin), videosink);
|
|
|
|
gst_element_link_many (v_queue, v_decoder, v_convert, v_scale, videosink,
|
|
NULL);
|
|
|
|
pad = gst_element_get_static_pad (v_queue, "sink");
|
|
gst_element_add_pad (video_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, NULL, gst_element_get_static_pad (video_bin,
|
|
"sink"), NULL);
|
|
|
|
seekable = gst_element_get_static_pad (a_decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (a_decoder,
|
|
"sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_avi_msmpeg4v3_mp3_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline, *audio_bin, *video_bin;
|
|
GstElement *src, *demux, *a_decoder, *a_convert, *v_decoder, *v_convert;
|
|
GstElement *audiosink, *videosink;
|
|
GstElement *a_queue, *v_queue;
|
|
GstPad *seekable, *pad;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
demux = gst_element_factory_make_or_warn ("avidemux", "demux");
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_element_link (src, demux);
|
|
|
|
audio_bin = gst_bin_new ("a_decoder_bin");
|
|
a_queue = gst_element_factory_make_or_warn ("queue", "a_queue");
|
|
a_decoder = gst_element_factory_make_or_warn ("mad", "a_dec");
|
|
a_convert = gst_element_factory_make_or_warn ("audioconvert", "a_convert");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "a_sink");
|
|
|
|
gst_bin_add (GST_BIN (audio_bin), a_queue);
|
|
gst_bin_add (GST_BIN (audio_bin), a_decoder);
|
|
gst_bin_add (GST_BIN (audio_bin), a_convert);
|
|
gst_bin_add (GST_BIN (audio_bin), audiosink);
|
|
|
|
gst_element_link (a_queue, a_decoder);
|
|
gst_element_link (a_decoder, a_convert);
|
|
gst_element_link (a_convert, audiosink);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), audio_bin);
|
|
|
|
pad = gst_element_get_static_pad (a_queue, "sink");
|
|
gst_element_add_pad (audio_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, NULL, gst_element_get_static_pad (audio_bin,
|
|
"sink"), NULL);
|
|
|
|
video_bin = gst_bin_new ("v_decoder_bin");
|
|
v_queue = gst_element_factory_make_or_warn ("queue", "v_queue");
|
|
v_decoder = gst_element_factory_make_or_warn ("ffdec_msmpeg4", "v_dec");
|
|
v_convert =
|
|
gst_element_factory_make_or_warn ("ffmpegcolorspace", "v_convert");
|
|
videosink = gst_element_factory_make_or_warn (VSINK, "v_sink");
|
|
|
|
gst_bin_add (GST_BIN (video_bin), v_queue);
|
|
gst_bin_add (GST_BIN (video_bin), v_decoder);
|
|
gst_bin_add (GST_BIN (video_bin), v_convert);
|
|
gst_bin_add (GST_BIN (video_bin), videosink);
|
|
|
|
gst_element_link_many (v_queue, v_decoder, v_convert, videosink, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), video_bin);
|
|
|
|
pad = gst_element_get_static_pad (v_queue, "sink");
|
|
gst_element_add_pad (video_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, NULL, gst_element_get_static_pad (video_bin,
|
|
"sink"), NULL);
|
|
|
|
seekable = gst_element_get_static_pad (a_decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (a_decoder,
|
|
"sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_mp3_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *src, *parser, *decoder, *audiosink, *queue;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
parser = gst_element_factory_make_or_warn ("mp3parse", "parse");
|
|
decoder = gst_element_factory_make_or_warn ("mad", "dec");
|
|
queue = gst_element_factory_make_or_warn ("queue", "queue");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "sink");
|
|
|
|
seekable_elements = g_list_prepend (seekable_elements, audiosink);
|
|
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
//g_object_set (G_OBJECT (audiosink), "fragment", 0x00180008, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), parser);
|
|
gst_bin_add (GST_BIN (pipeline), decoder);
|
|
gst_bin_add (GST_BIN (pipeline), queue);
|
|
gst_bin_add (GST_BIN (pipeline), audiosink);
|
|
|
|
gst_element_link (src, parser);
|
|
gst_element_link (parser, decoder);
|
|
gst_element_link (decoder, queue);
|
|
gst_element_link (queue, audiosink);
|
|
|
|
seekable = gst_element_get_static_pad (queue, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (decoder, "sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_avi_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline, *audio_bin, *video_bin;
|
|
GstElement *src, *demux, *a_decoder, *v_decoder, *audiosink, *videosink;
|
|
GstElement *a_queue = NULL, *v_queue = NULL;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
demux = gst_element_factory_make_or_warn ("avidemux", "demux");
|
|
seekable_elements = g_list_prepend (seekable_elements, demux);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_element_link (src, demux);
|
|
|
|
audio_bin = gst_bin_new ("a_decoder_bin");
|
|
a_decoder = gst_element_factory_make_or_warn ("mad", "a_dec");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "a_sink");
|
|
a_queue = gst_element_factory_make_or_warn ("queue", "a_queue");
|
|
gst_element_link (a_decoder, a_queue);
|
|
gst_element_link (a_queue, audiosink);
|
|
gst_bin_add (GST_BIN (audio_bin), a_decoder);
|
|
gst_bin_add (GST_BIN (audio_bin), a_queue);
|
|
gst_bin_add (GST_BIN (audio_bin), audiosink);
|
|
gst_element_set_state (audio_bin, GST_STATE_PAUSED);
|
|
|
|
setup_dynamic_link (demux, "audio_00", gst_element_get_static_pad (a_decoder,
|
|
"sink"), audio_bin);
|
|
|
|
seekable = gst_element_get_static_pad (a_queue, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (a_decoder,
|
|
"sink"));
|
|
|
|
video_bin = gst_bin_new ("v_decoder_bin");
|
|
v_decoder = gst_element_factory_make_or_warn ("ffmpegdecall", "v_dec");
|
|
videosink = gst_element_factory_make_or_warn (VSINK, "v_sink");
|
|
v_queue = gst_element_factory_make_or_warn ("queue", "v_queue");
|
|
gst_element_link (v_decoder, v_queue);
|
|
gst_element_link (v_queue, videosink);
|
|
gst_bin_add (GST_BIN (video_bin), v_decoder);
|
|
gst_bin_add (GST_BIN (video_bin), v_queue);
|
|
gst_bin_add (GST_BIN (video_bin), videosink);
|
|
|
|
gst_element_set_state (video_bin, GST_STATE_PAUSED);
|
|
|
|
setup_dynamic_link (demux, "video_00", gst_element_get_static_pad (v_decoder,
|
|
"sink"), video_bin);
|
|
|
|
seekable = gst_element_get_static_pad (v_queue, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (v_decoder,
|
|
"sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_mpeg_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline, *audio_bin, *video_bin;
|
|
GstElement *src, *demux, *a_decoder, *v_decoder, *v_filter;
|
|
GstElement *audiosink, *videosink;
|
|
GstElement *a_queue, *v_queue;
|
|
GstPad *seekable;
|
|
GstPad *pad;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
//demux = gst_element_factory_make_or_warn ("mpegdemux", "demux");
|
|
demux = gst_element_factory_make_or_warn ("flupsdemux", "demux");
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_element_link (src, demux);
|
|
|
|
audio_bin = gst_bin_new ("a_decoder_bin");
|
|
a_decoder = gst_element_factory_make_or_warn ("mad", "a_dec");
|
|
a_queue = gst_element_factory_make_or_warn ("queue", "a_queue");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "a_sink");
|
|
gst_bin_add (GST_BIN (audio_bin), a_decoder);
|
|
gst_bin_add (GST_BIN (audio_bin), a_queue);
|
|
gst_bin_add (GST_BIN (audio_bin), audiosink);
|
|
|
|
gst_element_link (a_decoder, a_queue);
|
|
gst_element_link (a_queue, audiosink);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), audio_bin);
|
|
|
|
pad = gst_element_get_static_pad (a_decoder, "sink");
|
|
gst_element_add_pad (audio_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, "audio_c0", gst_element_get_static_pad (audio_bin,
|
|
"sink"), NULL);
|
|
|
|
video_bin = gst_bin_new ("v_decoder_bin");
|
|
v_decoder = gst_element_factory_make_or_warn ("mpeg2dec", "v_dec");
|
|
v_queue = gst_element_factory_make_or_warn ("queue", "v_queue");
|
|
v_filter = gst_element_factory_make_or_warn ("ffmpegcolorspace", "v_filter");
|
|
videosink = gst_element_factory_make_or_warn (VSINK, "v_sink");
|
|
|
|
gst_bin_add (GST_BIN (video_bin), v_decoder);
|
|
gst_bin_add (GST_BIN (video_bin), v_queue);
|
|
gst_bin_add (GST_BIN (video_bin), v_filter);
|
|
gst_bin_add (GST_BIN (video_bin), videosink);
|
|
|
|
gst_element_link (v_decoder, v_queue);
|
|
gst_element_link (v_queue, v_filter);
|
|
gst_element_link (v_filter, videosink);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), video_bin);
|
|
|
|
pad = gst_element_get_static_pad (v_decoder, "sink");
|
|
gst_element_add_pad (video_bin, gst_ghost_pad_new ("sink", pad));
|
|
gst_object_unref (pad);
|
|
|
|
setup_dynamic_link (demux, "video_e0", gst_element_get_static_pad (video_bin,
|
|
"sink"), NULL);
|
|
|
|
seekable = gst_element_get_static_pad (v_filter, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (v_decoder,
|
|
"sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
make_mpegnt_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline, *audio_bin, *video_bin;
|
|
GstElement *src, *demux, *a_decoder, *v_decoder, *v_filter;
|
|
GstElement *audiosink, *videosink;
|
|
GstElement *a_queue;
|
|
GstPad *seekable;
|
|
|
|
pipeline = gst_pipeline_new ("app");
|
|
|
|
src = gst_element_factory_make_or_warn (SOURCE, "src");
|
|
g_object_set (G_OBJECT (src), "location", location, NULL);
|
|
|
|
demux = gst_element_factory_make_or_warn ("mpegdemux", "demux");
|
|
//g_object_set (G_OBJECT (demux), "sync", TRUE, NULL);
|
|
|
|
seekable_elements = g_list_prepend (seekable_elements, demux);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
gst_bin_add (GST_BIN (pipeline), demux);
|
|
gst_element_link (src, demux);
|
|
|
|
audio_bin = gst_bin_new ("a_decoder_bin");
|
|
a_decoder = gst_element_factory_make_or_warn ("mad", "a_dec");
|
|
a_queue = gst_element_factory_make_or_warn ("queue", "a_queue");
|
|
audiosink = gst_element_factory_make_or_warn (ASINK, "a_sink");
|
|
//g_object_set (G_OBJECT (audiosink), "fragment", 0x00180008, NULL);
|
|
g_object_set (G_OBJECT (audiosink), "sync", FALSE, NULL);
|
|
gst_element_link (a_decoder, a_queue);
|
|
gst_element_link (a_queue, audiosink);
|
|
gst_bin_add (GST_BIN (audio_bin), a_decoder);
|
|
gst_bin_add (GST_BIN (audio_bin), a_queue);
|
|
gst_bin_add (GST_BIN (audio_bin), audiosink);
|
|
|
|
setup_dynamic_link (demux, "audio_00", gst_element_get_static_pad (a_decoder,
|
|
"sink"), audio_bin);
|
|
|
|
seekable = gst_element_get_static_pad (a_queue, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (a_decoder,
|
|
"sink"));
|
|
|
|
video_bin = gst_bin_new ("v_decoder_bin");
|
|
v_decoder = gst_element_factory_make_or_warn ("mpeg2dec", "v_dec");
|
|
v_filter = gst_element_factory_make_or_warn ("ffmpegcolorspace", "v_filter");
|
|
videosink = gst_element_factory_make_or_warn (VSINK, "v_sink");
|
|
gst_element_link_many (v_decoder, v_filter, videosink, NULL);
|
|
|
|
gst_bin_add_many (GST_BIN (video_bin), v_decoder, v_filter, videosink, NULL);
|
|
|
|
setup_dynamic_link (demux, "video_00", gst_element_get_static_pad (v_decoder,
|
|
"sink"), video_bin);
|
|
|
|
seekable = gst_element_get_static_pad (v_decoder, "src");
|
|
seekable_pads = g_list_prepend (seekable_pads, seekable);
|
|
rate_pads = g_list_prepend (rate_pads, seekable);
|
|
rate_pads =
|
|
g_list_prepend (rate_pads, gst_element_get_static_pad (v_decoder,
|
|
"sink"));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static void
|
|
playerbin_set_uri (GstElement * player, const gchar * location)
|
|
{
|
|
gchar *uri;
|
|
|
|
/* Add "file://" prefix for convenience */
|
|
if (g_str_has_prefix (location, "/")) {
|
|
uri = g_strconcat ("file://", location, NULL);
|
|
g_object_set (G_OBJECT (player), "uri", uri, NULL);
|
|
g_free (uri);
|
|
} else {
|
|
g_object_set (G_OBJECT (player), "uri", location, NULL);
|
|
}
|
|
}
|
|
|
|
static GstElement *
|
|
construct_playerbin (const gchar * name, const gchar * location)
|
|
{
|
|
GstElement *player;
|
|
|
|
player = gst_element_factory_make (name, "player");
|
|
g_assert (player);
|
|
|
|
playerbin_set_uri (player, location);
|
|
|
|
seekable_elements = g_list_prepend (seekable_elements, player);
|
|
|
|
/* force element seeking on this pipeline */
|
|
elem_seek = TRUE;
|
|
|
|
return player;
|
|
}
|
|
|
|
static GstElement *
|
|
make_playerbin_pipeline (const gchar * location)
|
|
{
|
|
return construct_playerbin ("playbin", location);
|
|
}
|
|
|
|
static GstElement *
|
|
make_playerbin2_pipeline (const gchar * location)
|
|
{
|
|
GstElement *pipeline = construct_playerbin ("playbin2", location);
|
|
|
|
/* FIXME: this is not triggered, playbin2 is not forwarding it from the sink */
|
|
g_signal_connect (pipeline, "notify::volume", G_CALLBACK (volume_notify_cb),
|
|
NULL);
|
|
return pipeline;
|
|
}
|
|
|
|
#ifndef GST_DISABLE_PARSE
|
|
static GstElement *
|
|
make_parselaunch_pipeline (const gchar * description)
|
|
{
|
|
GstElement *pipeline;
|
|
GError *error = NULL;
|
|
|
|
pipeline = gst_parse_launch (description, &error);
|
|
|
|
seekable_elements = g_list_prepend (seekable_elements, pipeline);
|
|
|
|
elem_seek = TRUE;
|
|
|
|
return pipeline;
|
|
}
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
gchar *name;
|
|
GstElement *(*func) (const gchar * location);
|
|
}
|
|
Pipeline;
|
|
|
|
static Pipeline pipelines[] = {
|
|
{"mp3", make_mp3_pipeline},
|
|
{"avi", make_avi_pipeline},
|
|
{"mpeg1", make_mpeg_pipeline},
|
|
{"mpegparse", make_parse_pipeline},
|
|
{"vorbis", make_vorbis_pipeline},
|
|
{"theora", make_theora_pipeline},
|
|
{"ogg/v/t", make_vorbis_theora_pipeline},
|
|
{"avi/msmpeg4v3/mp3", make_avi_msmpeg4v3_mp3_pipeline},
|
|
{"sid", make_sid_pipeline},
|
|
{"flac", make_flac_pipeline},
|
|
{"wav", make_wav_pipeline},
|
|
{"mod", make_mod_pipeline},
|
|
{"dv", make_dv_pipeline},
|
|
{"mpeg1nothreads", make_mpegnt_pipeline},
|
|
{"playerbin", make_playerbin_pipeline},
|
|
#ifndef GST_DISABLE_PARSE
|
|
{"parse-launch", make_parselaunch_pipeline},
|
|
#endif
|
|
{"playerbin2", make_playerbin2_pipeline},
|
|
{NULL, NULL},
|
|
};
|
|
|
|
#define NUM_TYPES ((sizeof (pipelines) / sizeof (Pipeline)) - 1)
|
|
|
|
/* ui callbacks and helpers */
|
|
|
|
static gchar *
|
|
format_value (GtkScale * scale, gdouble value)
|
|
{
|
|
gint64 real;
|
|
gint64 seconds;
|
|
gint64 subseconds;
|
|
|
|
real = value * duration / 100;
|
|
seconds = (gint64) real / GST_SECOND;
|
|
subseconds = (gint64) real / (GST_SECOND / 100);
|
|
|
|
return g_strdup_printf ("%02" G_GINT64_FORMAT ":%02" G_GINT64_FORMAT ":%02"
|
|
G_GINT64_FORMAT, seconds / 60, seconds % 60, subseconds % 100);
|
|
}
|
|
|
|
|
|
static gchar *
|
|
shuttle_format_value (GtkScale * scale, gdouble value)
|
|
{
|
|
return g_strdup_printf ("%0.*g", gtk_scale_get_digits (scale), value);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
const gchar *name;
|
|
const GstFormat format;
|
|
}
|
|
seek_format;
|
|
|
|
static seek_format seek_formats[] = {
|
|
{"tim", GST_FORMAT_TIME},
|
|
{"byt", GST_FORMAT_BYTES},
|
|
{"buf", GST_FORMAT_BUFFERS},
|
|
{"def", GST_FORMAT_DEFAULT},
|
|
{NULL, 0},
|
|
};
|
|
|
|
G_GNUC_UNUSED static void
|
|
query_rates (void)
|
|
{
|
|
GList *walk = rate_pads;
|
|
|
|
while (walk) {
|
|
GstPad *pad = GST_PAD (walk->data);
|
|
gint i = 0;
|
|
|
|
g_print ("rate/sec %8.8s: ", GST_PAD_NAME (pad));
|
|
while (seek_formats[i].name) {
|
|
gint64 value;
|
|
GstFormat format;
|
|
|
|
format = seek_formats[i].format;
|
|
|
|
if (gst_pad_query_convert (pad, GST_FORMAT_TIME, GST_SECOND, &format,
|
|
&value)) {
|
|
g_print ("%s %13" G_GINT64_FORMAT " | ", seek_formats[i].name, value);
|
|
} else {
|
|
g_print ("%s %13.13s | ", seek_formats[i].name, "*NA*");
|
|
}
|
|
|
|
i++;
|
|
}
|
|
g_print (" %s:%s\n", GST_DEBUG_PAD_NAME (pad));
|
|
|
|
walk = g_list_next (walk);
|
|
}
|
|
}
|
|
|
|
G_GNUC_UNUSED static void
|
|
query_positions_elems (void)
|
|
{
|
|
GList *walk = seekable_elements;
|
|
|
|
while (walk) {
|
|
GstElement *element = GST_ELEMENT (walk->data);
|
|
gint i = 0;
|
|
|
|
g_print ("positions %8.8s: ", GST_ELEMENT_NAME (element));
|
|
while (seek_formats[i].name) {
|
|
gint64 position, total;
|
|
GstFormat format;
|
|
|
|
format = seek_formats[i].format;
|
|
|
|
if (gst_element_query_position (element, &format, &position) &&
|
|
gst_element_query_duration (element, &format, &total)) {
|
|
g_print ("%s %13" G_GINT64_FORMAT " / %13" G_GINT64_FORMAT " | ",
|
|
seek_formats[i].name, position, total);
|
|
} else {
|
|
g_print ("%s %13.13s / %13.13s | ", seek_formats[i].name, "*NA*",
|
|
"*NA*");
|
|
}
|
|
i++;
|
|
}
|
|
g_print (" %s\n", GST_ELEMENT_NAME (element));
|
|
|
|
walk = g_list_next (walk);
|
|
}
|
|
}
|
|
|
|
G_GNUC_UNUSED static void
|
|
query_positions_pads (void)
|
|
{
|
|
GList *walk = seekable_pads;
|
|
|
|
while (walk) {
|
|
GstPad *pad = GST_PAD (walk->data);
|
|
gint i = 0;
|
|
|
|
g_print ("positions %8.8s: ", GST_PAD_NAME (pad));
|
|
while (seek_formats[i].name) {
|
|
GstFormat format;
|
|
gint64 position, total;
|
|
|
|
format = seek_formats[i].format;
|
|
|
|
if (gst_pad_query_position (pad, &format, &position) &&
|
|
gst_pad_query_duration (pad, &format, &total)) {
|
|
g_print ("%s %13" G_GINT64_FORMAT " / %13" G_GINT64_FORMAT " | ",
|
|
seek_formats[i].name, position, total);
|
|
} else {
|
|
g_print ("%s %13.13s / %13.13s | ", seek_formats[i].name, "*NA*",
|
|
"*NA*");
|
|
}
|
|
|
|
i++;
|
|
}
|
|
g_print (" %s:%s\n", GST_DEBUG_PAD_NAME (pad));
|
|
|
|
walk = g_list_next (walk);
|
|
}
|
|
}
|
|
|
|
static gboolean start_seek (GtkWidget * widget, GdkEventButton * event,
|
|
gpointer user_data);
|
|
static gboolean stop_seek (GtkWidget * widget, GdkEventButton * event,
|
|
gpointer user_data);
|
|
static void seek_cb (GtkWidget * widget);
|
|
|
|
static void
|
|
set_scale (gdouble value)
|
|
{
|
|
g_signal_handlers_block_by_func (hscale, (void *) start_seek,
|
|
(void *) pipeline);
|
|
g_signal_handlers_block_by_func (hscale, (void *) stop_seek,
|
|
(void *) pipeline);
|
|
g_signal_handlers_block_by_func (hscale, (void *) seek_cb, (void *) pipeline);
|
|
gtk_adjustment_set_value (adjustment, value);
|
|
g_signal_handlers_unblock_by_func (hscale, (void *) start_seek,
|
|
(void *) pipeline);
|
|
g_signal_handlers_unblock_by_func (hscale, (void *) stop_seek,
|
|
(void *) pipeline);
|
|
g_signal_handlers_unblock_by_func (hscale, (void *) seek_cb,
|
|
(void *) pipeline);
|
|
gtk_widget_queue_draw (hscale);
|
|
}
|
|
|
|
static gboolean
|
|
update_fill (gpointer data)
|
|
{
|
|
if (elem_seek) {
|
|
if (seekable_elements) {
|
|
GstElement *element = GST_ELEMENT (seekable_elements->data);
|
|
GstQuery *query;
|
|
|
|
query = gst_query_new_buffering (GST_FORMAT_PERCENT);
|
|
if (gst_element_query (element, query)) {
|
|
gint64 start, stop;
|
|
GstFormat format;
|
|
gdouble fill;
|
|
gboolean busy;
|
|
gint percent;
|
|
|
|
gst_query_parse_buffering_percent (query, &busy, &percent);
|
|
gst_query_parse_buffering_range (query, &format, &start, &stop, NULL);
|
|
|
|
GST_DEBUG ("start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT,
|
|
start, stop);
|
|
|
|
if (stop != -1)
|
|
fill = 100.0 * stop / GST_FORMAT_PERCENT_MAX;
|
|
else
|
|
fill = 100.0;
|
|
|
|
gtk_range_set_fill_level (GTK_RANGE (hscale), fill);
|
|
}
|
|
gst_query_unref (query);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
update_scale (gpointer data)
|
|
{
|
|
GstFormat format = GST_FORMAT_TIME;
|
|
|
|
//position = 0;
|
|
//duration = 0;
|
|
|
|
if (elem_seek) {
|
|
if (seekable_elements) {
|
|
GstElement *element = GST_ELEMENT (seekable_elements->data);
|
|
|
|
gst_element_query_position (element, &format, &position);
|
|
gst_element_query_duration (element, &format, &duration);
|
|
}
|
|
} else {
|
|
if (seekable_pads) {
|
|
GstPad *pad = GST_PAD (seekable_pads->data);
|
|
|
|
gst_pad_query_position (pad, &format, &position);
|
|
gst_pad_query_duration (pad, &format, &duration);
|
|
}
|
|
}
|
|
|
|
if (stats) {
|
|
if (elem_seek) {
|
|
query_positions_elems ();
|
|
} else {
|
|
query_positions_pads ();
|
|
}
|
|
query_rates ();
|
|
}
|
|
|
|
if (position >= duration)
|
|
duration = position;
|
|
|
|
if (duration > 0) {
|
|
set_scale (position * 100.0 / duration);
|
|
}
|
|
|
|
/* FIXME: see make_playerbin2_pipeline() and volume_notify_cb() */
|
|
if (pipeline_type == 16) {
|
|
g_object_notify (G_OBJECT (pipeline), "volume");
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void do_seek (GtkWidget * widget);
|
|
static void connect_bus_signals (GstElement * pipeline);
|
|
static void set_update_scale (gboolean active);
|
|
static void set_update_fill (gboolean active);
|
|
|
|
static gboolean
|
|
end_scrub (GtkWidget * widget)
|
|
{
|
|
GST_DEBUG ("end scrub, PAUSE");
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
seek_timeout_id = 0;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
send_event (GstEvent * event)
|
|
{
|
|
gboolean res = FALSE;
|
|
|
|
if (!elem_seek) {
|
|
GList *walk = seekable_pads;
|
|
|
|
while (walk) {
|
|
GstPad *seekable = GST_PAD (walk->data);
|
|
|
|
GST_DEBUG ("send event on pad %s:%s", GST_DEBUG_PAD_NAME (seekable));
|
|
|
|
gst_event_ref (event);
|
|
res = gst_pad_send_event (seekable, event);
|
|
|
|
walk = g_list_next (walk);
|
|
}
|
|
} else {
|
|
GList *walk = seekable_elements;
|
|
|
|
while (walk) {
|
|
GstElement *seekable = GST_ELEMENT (walk->data);
|
|
|
|
GST_DEBUG ("send event on element %s", GST_ELEMENT_NAME (seekable));
|
|
|
|
gst_event_ref (event);
|
|
res = gst_element_send_event (seekable, event);
|
|
|
|
walk = g_list_next (walk);
|
|
}
|
|
}
|
|
gst_event_unref (event);
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
do_seek (GtkWidget * widget)
|
|
{
|
|
gint64 real;
|
|
gboolean res = FALSE;
|
|
GstEvent *s_event;
|
|
GstSeekFlags flags;
|
|
|
|
real = gtk_range_get_value (GTK_RANGE (widget)) * duration / 100;
|
|
|
|
flags = 0;
|
|
if (flush_seek)
|
|
flags |= GST_SEEK_FLAG_FLUSH;
|
|
if (accurate_seek)
|
|
flags |= GST_SEEK_FLAG_ACCURATE;
|
|
if (keyframe_seek)
|
|
flags |= GST_SEEK_FLAG_KEY_UNIT;
|
|
if (loop_seek)
|
|
flags |= GST_SEEK_FLAG_SEGMENT;
|
|
if (skip_seek)
|
|
flags |= GST_SEEK_FLAG_SKIP;
|
|
|
|
if (rate >= 0) {
|
|
s_event = gst_event_new_seek (rate,
|
|
GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, real, GST_SEEK_TYPE_SET,
|
|
GST_CLOCK_TIME_NONE);
|
|
GST_DEBUG ("seek with rate %lf to %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT,
|
|
rate, GST_TIME_ARGS (real), GST_TIME_ARGS (duration));
|
|
} else {
|
|
s_event = gst_event_new_seek (rate,
|
|
GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
|
|
GST_SEEK_TYPE_SET, real);
|
|
GST_DEBUG ("seek with rate %lf to %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT,
|
|
rate, GST_TIME_ARGS (0), GST_TIME_ARGS (real));
|
|
}
|
|
|
|
res = send_event (s_event);
|
|
|
|
if (res) {
|
|
if (flush_seek) {
|
|
gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, SEEK_TIMEOUT);
|
|
} else {
|
|
set_update_scale (TRUE);
|
|
}
|
|
} else {
|
|
g_print ("seek failed\n");
|
|
set_update_scale (TRUE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
seek_cb (GtkWidget * widget)
|
|
{
|
|
/* If the timer hasn't expired yet, then the pipeline is running */
|
|
if (play_scrub && seek_timeout_id != 0) {
|
|
GST_DEBUG ("do scrub seek, PAUSED");
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
}
|
|
|
|
GST_DEBUG ("do seek");
|
|
do_seek (widget);
|
|
|
|
if (play_scrub) {
|
|
GST_DEBUG ("do scrub seek, PLAYING");
|
|
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
|
|
|
if (seek_timeout_id == 0) {
|
|
seek_timeout_id =
|
|
g_timeout_add (SCRUB_TIME, (GSourceFunc) end_scrub, widget);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_update_fill (gboolean active)
|
|
{
|
|
GST_DEBUG ("fill scale is %d", active);
|
|
|
|
if (active) {
|
|
if (fill_id == 0) {
|
|
fill_id =
|
|
g_timeout_add (FILL_INTERVAL, (GtkFunction) update_fill, pipeline);
|
|
}
|
|
} else {
|
|
if (fill_id) {
|
|
g_source_remove (fill_id);
|
|
fill_id = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_update_scale (gboolean active)
|
|
{
|
|
|
|
GST_DEBUG ("update scale is %d", active);
|
|
|
|
if (active) {
|
|
if (update_id == 0) {
|
|
update_id =
|
|
g_timeout_add (UPDATE_INTERVAL, (GtkFunction) update_scale, pipeline);
|
|
}
|
|
} else {
|
|
if (update_id) {
|
|
g_source_remove (update_id);
|
|
update_id = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
start_seek (GtkWidget * widget, GdkEventButton * event, gpointer user_data)
|
|
{
|
|
if (event->type != GDK_BUTTON_PRESS)
|
|
return FALSE;
|
|
|
|
set_update_scale (FALSE);
|
|
|
|
if (state == GST_STATE_PLAYING && flush_seek && scrub) {
|
|
GST_DEBUG ("start scrub seek, PAUSE");
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
}
|
|
|
|
if (changed_id == 0 && flush_seek && scrub) {
|
|
changed_id = g_signal_connect (GTK_OBJECT (hscale),
|
|
"value_changed", G_CALLBACK (seek_cb), pipeline);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
stop_seek (GtkWidget * widget, GdkEventButton * event, gpointer user_data)
|
|
{
|
|
if (changed_id) {
|
|
g_signal_handler_disconnect (GTK_OBJECT (hscale), changed_id);
|
|
changed_id = 0;
|
|
}
|
|
|
|
if (!flush_seek || !scrub) {
|
|
GST_DEBUG ("do final seek");
|
|
do_seek (widget);
|
|
}
|
|
|
|
if (seek_timeout_id != 0) {
|
|
g_source_remove (seek_timeout_id);
|
|
seek_timeout_id = 0;
|
|
/* Still scrubbing, so the pipeline is playing, see if we need PAUSED
|
|
* instead. */
|
|
if (state == GST_STATE_PAUSED) {
|
|
GST_DEBUG ("stop scrub seek, PAUSED");
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
}
|
|
} else {
|
|
if (state == GST_STATE_PLAYING) {
|
|
GST_DEBUG ("stop scrub seek, PLAYING");
|
|
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
play_cb (GtkButton * button, gpointer data)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
|
|
if (state != GST_STATE_PLAYING) {
|
|
g_print ("PLAY pipeline\n");
|
|
gtk_statusbar_pop (GTK_STATUSBAR (statusbar), status_id);
|
|
|
|
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
|
switch (ret) {
|
|
case GST_STATE_CHANGE_FAILURE:
|
|
goto failed;
|
|
case GST_STATE_CHANGE_NO_PREROLL:
|
|
is_live = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
state = GST_STATE_PLAYING;
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Playing");
|
|
}
|
|
|
|
return;
|
|
|
|
failed:
|
|
{
|
|
g_print ("PLAY failed\n");
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Play failed");
|
|
}
|
|
}
|
|
|
|
static void
|
|
pause_cb (GtkButton * button, gpointer data)
|
|
{
|
|
g_static_mutex_lock (&state_mutex);
|
|
if (state != GST_STATE_PAUSED) {
|
|
GstStateChangeReturn ret;
|
|
|
|
gtk_statusbar_pop (GTK_STATUSBAR (statusbar), status_id);
|
|
g_print ("PAUSE pipeline\n");
|
|
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
switch (ret) {
|
|
case GST_STATE_CHANGE_FAILURE:
|
|
goto failed;
|
|
case GST_STATE_CHANGE_NO_PREROLL:
|
|
is_live = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
state = GST_STATE_PAUSED;
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Paused");
|
|
}
|
|
g_static_mutex_unlock (&state_mutex);
|
|
|
|
return;
|
|
|
|
failed:
|
|
{
|
|
g_static_mutex_unlock (&state_mutex);
|
|
g_print ("PAUSE failed\n");
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Pause failed");
|
|
}
|
|
}
|
|
|
|
static void
|
|
stop_cb (GtkButton * button, gpointer data)
|
|
{
|
|
if (state != GST_STATE_READY) {
|
|
GstStateChangeReturn ret;
|
|
|
|
g_print ("READY pipeline\n");
|
|
gtk_statusbar_pop (GTK_STATUSBAR (statusbar), status_id);
|
|
|
|
g_static_mutex_lock (&state_mutex);
|
|
ret = gst_element_set_state (pipeline, GST_STATE_READY);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
goto failed;
|
|
|
|
state = GST_STATE_READY;
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Stopped");
|
|
|
|
is_live = FALSE;
|
|
buffering = FALSE;
|
|
set_update_scale (FALSE);
|
|
set_scale (0.0);
|
|
set_update_fill (FALSE);
|
|
|
|
if (pipeline_type == 16)
|
|
clear_streams (pipeline);
|
|
g_static_mutex_unlock (&state_mutex);
|
|
|
|
#if 0
|
|
/* if one uses parse_launch, play, stop and play again it fails as all the
|
|
* pads after the demuxer can't be reconnected
|
|
*/
|
|
if (!strcmp (pipelines[pipeline_type].name, "parse-launch")) {
|
|
gst_element_set_state (pipeline, GST_STATE_NULL);
|
|
gst_object_unref (pipeline);
|
|
|
|
g_list_free (seekable_elements);
|
|
seekable_elements = NULL;
|
|
g_list_free (seekable_pads);
|
|
seekable_pads = NULL;
|
|
g_list_free (rate_pads);
|
|
rate_pads = NULL;
|
|
|
|
pipeline = pipelines[pipeline_type].func (pipeline_spec);
|
|
g_assert (pipeline);
|
|
gst_element_set_state (pipeline, GST_STATE_READY);
|
|
connect_bus_signals (pipeline);
|
|
}
|
|
#endif
|
|
}
|
|
return;
|
|
|
|
failed:
|
|
{
|
|
g_static_mutex_unlock (&state_mutex);
|
|
g_print ("STOP failed\n");
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Stop failed");
|
|
}
|
|
}
|
|
|
|
static void
|
|
accurate_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
accurate_seek = gtk_toggle_button_get_active (button);
|
|
}
|
|
|
|
static void
|
|
key_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
keyframe_seek = gtk_toggle_button_get_active (button);
|
|
}
|
|
|
|
static void
|
|
loop_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
loop_seek = gtk_toggle_button_get_active (button);
|
|
if (state == GST_STATE_PLAYING) {
|
|
do_seek (hscale);
|
|
}
|
|
}
|
|
|
|
static void
|
|
flush_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
flush_seek = gtk_toggle_button_get_active (button);
|
|
}
|
|
|
|
static void
|
|
scrub_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
scrub = gtk_toggle_button_get_active (button);
|
|
}
|
|
|
|
static void
|
|
play_scrub_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
play_scrub = gtk_toggle_button_get_active (button);
|
|
}
|
|
|
|
static void
|
|
skip_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
skip_seek = gtk_toggle_button_get_active (button);
|
|
if (state == GST_STATE_PLAYING) {
|
|
do_seek (hscale);
|
|
}
|
|
}
|
|
|
|
static void
|
|
rate_spinbutton_changed_cb (GtkSpinButton * button, GstPipeline * pipeline)
|
|
{
|
|
gboolean res = FALSE;
|
|
GstEvent *s_event;
|
|
GstSeekFlags flags;
|
|
|
|
rate = gtk_spin_button_get_value (button);
|
|
|
|
GST_DEBUG ("rate changed to %lf", rate);
|
|
|
|
flags = 0;
|
|
if (flush_seek)
|
|
flags |= GST_SEEK_FLAG_FLUSH;
|
|
if (loop_seek)
|
|
flags |= GST_SEEK_FLAG_SEGMENT;
|
|
if (accurate_seek)
|
|
flags |= GST_SEEK_FLAG_ACCURATE;
|
|
if (keyframe_seek)
|
|
flags |= GST_SEEK_FLAG_KEY_UNIT;
|
|
if (skip_seek)
|
|
flags |= GST_SEEK_FLAG_SKIP;
|
|
|
|
if (rate >= 0.0) {
|
|
s_event = gst_event_new_seek (rate,
|
|
GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, position,
|
|
GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
|
|
} else {
|
|
s_event = gst_event_new_seek (rate,
|
|
GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
|
|
GST_SEEK_TYPE_SET, position);
|
|
}
|
|
|
|
res = send_event (s_event);
|
|
|
|
if (res) {
|
|
if (flush_seek) {
|
|
gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, SEEK_TIMEOUT);
|
|
}
|
|
} else
|
|
g_print ("seek failed\n");
|
|
}
|
|
|
|
static void
|
|
update_flag (GstPipeline * pipeline, gint num, gboolean state)
|
|
{
|
|
gint flags;
|
|
|
|
g_object_get (pipeline, "flags", &flags, NULL);
|
|
if (state)
|
|
flags |= (1 << num);
|
|
else
|
|
flags &= ~(1 << num);
|
|
g_object_set (pipeline, "flags", flags, NULL);
|
|
}
|
|
|
|
static void
|
|
vis_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
gboolean state;
|
|
|
|
state = gtk_toggle_button_get_active (button);
|
|
update_flag (pipeline, 3, state);
|
|
gtk_widget_set_sensitive (vis_combo, state);
|
|
}
|
|
|
|
static void
|
|
audio_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
gboolean state;
|
|
|
|
state = gtk_toggle_button_get_active (button);
|
|
update_flag (pipeline, 1, state);
|
|
gtk_widget_set_sensitive (audio_combo, state);
|
|
}
|
|
|
|
static void
|
|
video_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
gboolean state;
|
|
|
|
state = gtk_toggle_button_get_active (button);
|
|
update_flag (pipeline, 0, state);
|
|
gtk_widget_set_sensitive (video_combo, state);
|
|
}
|
|
|
|
static void
|
|
text_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
gboolean state;
|
|
|
|
state = gtk_toggle_button_get_active (button);
|
|
update_flag (pipeline, 2, state);
|
|
gtk_widget_set_sensitive (text_combo, state);
|
|
}
|
|
|
|
static void
|
|
mute_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
gboolean mute;
|
|
|
|
mute = gtk_toggle_button_get_active (button);
|
|
g_object_set (pipeline, "mute", mute, NULL);
|
|
}
|
|
|
|
static void
|
|
download_toggle_cb (GtkToggleButton * button, GstPipeline * pipeline)
|
|
{
|
|
gboolean state;
|
|
|
|
state = gtk_toggle_button_get_active (button);
|
|
update_flag (pipeline, 7, state);
|
|
}
|
|
|
|
static void
|
|
clear_streams (GstElement * pipeline)
|
|
{
|
|
gint i;
|
|
|
|
/* remove previous info */
|
|
for (i = 0; i < n_video; i++)
|
|
gtk_combo_box_remove_text (GTK_COMBO_BOX (video_combo), 0);
|
|
for (i = 0; i < n_audio; i++)
|
|
gtk_combo_box_remove_text (GTK_COMBO_BOX (audio_combo), 0);
|
|
for (i = 0; i < n_text; i++)
|
|
gtk_combo_box_remove_text (GTK_COMBO_BOX (text_combo), 0);
|
|
|
|
n_audio = n_video = n_text = 0;
|
|
gtk_widget_set_sensitive (video_combo, FALSE);
|
|
gtk_widget_set_sensitive (audio_combo, FALSE);
|
|
gtk_widget_set_sensitive (text_combo, FALSE);
|
|
|
|
need_streams = TRUE;
|
|
}
|
|
|
|
static void
|
|
update_streams (GstPipeline * pipeline)
|
|
{
|
|
gint i;
|
|
|
|
if (pipeline_type == 16 && need_streams) {
|
|
GstTagList *tags;
|
|
gchar *name, *str;
|
|
gint active_idx;
|
|
gboolean state;
|
|
|
|
/* remove previous info */
|
|
clear_streams (GST_ELEMENT_CAST (pipeline));
|
|
|
|
/* here we get and update the different streams detected by playbin2 */
|
|
g_object_get (pipeline, "n-video", &n_video, NULL);
|
|
g_object_get (pipeline, "n-audio", &n_audio, NULL);
|
|
g_object_get (pipeline, "n-text", &n_text, NULL);
|
|
|
|
g_print ("video %d, audio %d, text %d\n", n_video, n_audio, n_text);
|
|
|
|
active_idx = 0;
|
|
for (i = 0; i < n_video; i++) {
|
|
g_signal_emit_by_name (pipeline, "get-video-tags", i, &tags);
|
|
if (tags) {
|
|
str = gst_structure_to_string ((GstStructure *) tags);
|
|
g_print ("video %d: %s\n", i, str);
|
|
g_free (str);
|
|
}
|
|
/* find good name for the label */
|
|
name = g_strdup_printf ("video %d", i + 1);
|
|
gtk_combo_box_append_text (GTK_COMBO_BOX (video_combo), name);
|
|
g_free (name);
|
|
}
|
|
state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (video_checkbox));
|
|
gtk_widget_set_sensitive (video_combo, state && n_video > 0);
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (video_combo), active_idx);
|
|
|
|
active_idx = 0;
|
|
for (i = 0; i < n_audio; i++) {
|
|
g_signal_emit_by_name (pipeline, "get-audio-tags", i, &tags);
|
|
if (tags) {
|
|
str = gst_structure_to_string ((GstStructure *) tags);
|
|
g_print ("audio %d: %s\n", i, str);
|
|
g_free (str);
|
|
}
|
|
/* find good name for the label */
|
|
name = g_strdup_printf ("audio %d", i + 1);
|
|
gtk_combo_box_append_text (GTK_COMBO_BOX (audio_combo), name);
|
|
g_free (name);
|
|
}
|
|
state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (audio_checkbox));
|
|
gtk_widget_set_sensitive (audio_combo, state && n_audio > 0);
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (audio_combo), active_idx);
|
|
|
|
active_idx = 0;
|
|
for (i = 0; i < n_text; i++) {
|
|
g_signal_emit_by_name (pipeline, "get-text-tags", i, &tags);
|
|
|
|
name = NULL;
|
|
if (tags) {
|
|
const GValue *value;
|
|
|
|
str = gst_structure_to_string ((GstStructure *) tags);
|
|
g_print ("text %d: %s\n", i, str);
|
|
g_free (str);
|
|
|
|
/* get the language code if we can */
|
|
value = gst_tag_list_get_value_index (tags, GST_TAG_LANGUAGE_CODE, 0);
|
|
if (value && G_VALUE_HOLDS_STRING (value)) {
|
|
name = g_strdup_printf ("text %s", g_value_get_string (value));
|
|
}
|
|
}
|
|
/* find good name for the label if we didn't use a tag */
|
|
if (name == NULL)
|
|
name = g_strdup_printf ("text %d", i + 1);
|
|
|
|
gtk_combo_box_append_text (GTK_COMBO_BOX (text_combo), name);
|
|
g_free (name);
|
|
}
|
|
state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (text_checkbox));
|
|
gtk_widget_set_sensitive (text_combo, state && n_text > 0);
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (text_combo), active_idx);
|
|
|
|
need_streams = FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
video_combo_cb (GtkComboBox * combo, GstPipeline * pipeline)
|
|
{
|
|
gint active;
|
|
|
|
active = gtk_combo_box_get_active (combo);
|
|
|
|
g_print ("setting current video track %d\n", active);
|
|
g_object_set (pipeline, "current-video", active, NULL);
|
|
}
|
|
|
|
static void
|
|
audio_combo_cb (GtkComboBox * combo, GstPipeline * pipeline)
|
|
{
|
|
gint active;
|
|
|
|
active = gtk_combo_box_get_active (combo);
|
|
|
|
g_print ("setting current audio track %d\n", active);
|
|
g_object_set (pipeline, "current-audio", active, NULL);
|
|
}
|
|
|
|
static void
|
|
text_combo_cb (GtkComboBox * combo, GstPipeline * pipeline)
|
|
{
|
|
gint active;
|
|
|
|
active = gtk_combo_box_get_active (combo);
|
|
|
|
g_print ("setting current text track %d\n", active);
|
|
g_object_set (pipeline, "current-text", active, NULL);
|
|
}
|
|
|
|
static gboolean
|
|
filter_features (GstPluginFeature * feature, gpointer data)
|
|
{
|
|
GstElementFactory *f;
|
|
|
|
if (!GST_IS_ELEMENT_FACTORY (feature))
|
|
return FALSE;
|
|
f = GST_ELEMENT_FACTORY (feature);
|
|
if (!g_strrstr (gst_element_factory_get_klass (f), "Visualization"))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
init_visualization_features (void)
|
|
{
|
|
GList *list, *walk;
|
|
|
|
vis_entries = g_array_new (FALSE, FALSE, sizeof (VisEntry));
|
|
|
|
list = gst_registry_feature_filter (gst_registry_get_default (),
|
|
filter_features, FALSE, NULL);
|
|
|
|
for (walk = list; walk; walk = g_list_next (walk)) {
|
|
VisEntry entry;
|
|
const gchar *name;
|
|
|
|
entry.factory = GST_ELEMENT_FACTORY (walk->data);
|
|
name = gst_element_factory_get_longname (entry.factory);
|
|
|
|
g_array_append_val (vis_entries, entry);
|
|
gtk_combo_box_append_text (GTK_COMBO_BOX (vis_combo), name);
|
|
}
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (vis_combo), 0);
|
|
}
|
|
|
|
static void
|
|
vis_combo_cb (GtkComboBox * combo, GstPipeline * pipeline)
|
|
{
|
|
guint index;
|
|
VisEntry *entry;
|
|
GstElement *element;
|
|
|
|
/* get the selected index and get the factory for this index */
|
|
index = gtk_combo_box_get_active (GTK_COMBO_BOX (vis_combo));
|
|
if (vis_entries->len > 0) {
|
|
entry = &g_array_index (vis_entries, VisEntry, index);
|
|
|
|
/* create an instance of the element from the factory */
|
|
element = gst_element_factory_create (entry->factory, NULL);
|
|
if (!element)
|
|
return;
|
|
|
|
/* set vis plugin for playbin2 */
|
|
g_object_set (pipeline, "vis-plugin", element, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
volume_spinbutton_changed_cb (GtkSpinButton * button, GstPipeline * pipeline)
|
|
{
|
|
gdouble volume;
|
|
|
|
volume = gtk_spin_button_get_value (button);
|
|
|
|
g_object_set (pipeline, "volume", volume, NULL);
|
|
}
|
|
|
|
static void
|
|
volume_notify_cb (GstElement * pipeline, GParamSpec * arg, gpointer user_dat)
|
|
{
|
|
gdouble cur_volume, new_volume;
|
|
|
|
g_object_get (pipeline, "volume", &new_volume, NULL);
|
|
cur_volume = gtk_spin_button_get_value (GTK_SPIN_BUTTON (volume_spinbutton));
|
|
if (fabs (cur_volume - new_volume) > 0.001) {
|
|
g_signal_handlers_block_by_func (volume_spinbutton,
|
|
volume_spinbutton_changed_cb, pipeline);
|
|
gtk_spin_button_set_value (GTK_SPIN_BUTTON (volume_spinbutton), new_volume);
|
|
g_signal_handlers_unblock_by_func (volume_spinbutton,
|
|
volume_spinbutton_changed_cb, pipeline);
|
|
}
|
|
}
|
|
|
|
static void
|
|
shot_cb (GtkButton * button, gpointer data)
|
|
{
|
|
GstBuffer *buffer;
|
|
GstCaps *caps;
|
|
|
|
/* convert to our desired format (RGB24) */
|
|
caps = gst_caps_new_simple ("video/x-raw-rgb",
|
|
"bpp", G_TYPE_INT, 24, "depth", G_TYPE_INT, 24,
|
|
/* Note: we don't ask for a specific width/height here, so that
|
|
* videoscale can adjust dimensions from a non-1/1 pixel aspect
|
|
* ratio to a 1/1 pixel-aspect-ratio */
|
|
"pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
|
|
"endianness", G_TYPE_INT, G_BIG_ENDIAN,
|
|
"red_mask", G_TYPE_INT, 0xff0000,
|
|
"green_mask", G_TYPE_INT, 0x00ff00,
|
|
"blue_mask", G_TYPE_INT, 0x0000ff, NULL);
|
|
|
|
/* convert the latest frame to the requested format */
|
|
g_signal_emit_by_name (pipeline, "convert-frame", caps, &buffer);
|
|
gst_caps_unref (caps);
|
|
|
|
if (buffer) {
|
|
GstCaps *caps;
|
|
GstStructure *s;
|
|
gboolean res;
|
|
gint width, height;
|
|
GdkPixbuf *pixbuf;
|
|
GError *error = NULL;
|
|
|
|
/* get the snapshot buffer format now. We set the caps on the appsink so
|
|
* that it can only be an rgb buffer. The only thing we have not specified
|
|
* on the caps is the height, which is dependant on the pixel-aspect-ratio
|
|
* of the source material */
|
|
caps = GST_BUFFER_CAPS (buffer);
|
|
if (!caps) {
|
|
g_warning ("could not get snapshot format\n");
|
|
goto done;
|
|
}
|
|
s = gst_caps_get_structure (caps, 0);
|
|
|
|
/* we need to get the final caps on the buffer to get the size */
|
|
res = gst_structure_get_int (s, "width", &width);
|
|
res |= gst_structure_get_int (s, "height", &height);
|
|
if (!res) {
|
|
g_warning ("could not get snapshot dimension\n");
|
|
goto done;
|
|
}
|
|
|
|
/* create pixmap from buffer and save, gstreamer video buffers have a stride
|
|
* that is rounded up to the nearest multiple of 4 */
|
|
pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buffer),
|
|
GDK_COLORSPACE_RGB, FALSE, 8, width, height,
|
|
GST_ROUND_UP_4 (width * 3), NULL, NULL);
|
|
|
|
/* save the pixbuf */
|
|
gdk_pixbuf_save (pixbuf, "snapshot.png", "png", &error, NULL);
|
|
|
|
done:
|
|
gst_buffer_unref (buffer);
|
|
}
|
|
}
|
|
|
|
/* called when the Step button is pressed */
|
|
static void
|
|
step_cb (GtkButton * button, gpointer data)
|
|
{
|
|
GstEvent *event;
|
|
GstFormat format;
|
|
guint64 amount;
|
|
gdouble rate;
|
|
gboolean flush, res;
|
|
gint active;
|
|
|
|
active = gtk_combo_box_get_active (GTK_COMBO_BOX (format_combo));
|
|
amount =
|
|
gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
|
|
(step_amount_spinbutton));
|
|
rate = gtk_spin_button_get_value (GTK_SPIN_BUTTON (step_rate_spinbutton));
|
|
flush = TRUE;
|
|
|
|
switch (active) {
|
|
case 0:
|
|
format = GST_FORMAT_BUFFERS;
|
|
break;
|
|
case 1:
|
|
format = GST_FORMAT_TIME;
|
|
amount *= GST_MSECOND;
|
|
break;
|
|
default:
|
|
format = GST_FORMAT_UNDEFINED;
|
|
break;
|
|
}
|
|
|
|
event = gst_event_new_step (format, amount, rate, flush, FALSE);
|
|
|
|
res = send_event (event);
|
|
}
|
|
|
|
static void
|
|
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
|
{
|
|
const GstStructure *s;
|
|
|
|
s = gst_message_get_structure (message);
|
|
g_print ("message from \"%s\" (%s): ",
|
|
GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message))),
|
|
gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
|
|
if (s) {
|
|
gchar *sstr;
|
|
|
|
sstr = gst_structure_to_string (s);
|
|
g_print ("%s\n", sstr);
|
|
g_free (sstr);
|
|
} else {
|
|
g_print ("no message details\n");
|
|
}
|
|
}
|
|
|
|
static gboolean shuttling = FALSE;
|
|
static gdouble shuttle_rate = 0.0;
|
|
static gdouble play_rate = 1.0;
|
|
|
|
static void
|
|
do_shuttle (GstElement * element)
|
|
{
|
|
guint64 duration;
|
|
|
|
if (shuttling)
|
|
duration = 40 * GST_MSECOND;
|
|
else
|
|
duration = -1;
|
|
|
|
gst_element_send_event (element,
|
|
gst_event_new_step (GST_FORMAT_TIME, duration, shuttle_rate, FALSE,
|
|
FALSE));
|
|
}
|
|
|
|
static void
|
|
msg_sync_step_done (GstBus * bus, GstMessage * message, GstElement * element)
|
|
{
|
|
GstFormat format;
|
|
guint64 amount;
|
|
gdouble rate;
|
|
gboolean flush;
|
|
gboolean intermediate;
|
|
guint64 duration;
|
|
gboolean eos;
|
|
|
|
gst_message_parse_step_done (message, &format, &amount, &rate, &flush,
|
|
&intermediate, &duration, &eos);
|
|
|
|
if (eos) {
|
|
g_print ("stepped till EOS\n");
|
|
return;
|
|
}
|
|
|
|
if (g_static_mutex_trylock (&state_mutex)) {
|
|
if (shuttling)
|
|
do_shuttle (element);
|
|
g_static_mutex_unlock (&state_mutex);
|
|
} else {
|
|
/* ignore step messages that come while we are doing a state change */
|
|
g_print ("state change is busy\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
shuttle_toggled (GtkToggleButton * button, GstElement * element)
|
|
{
|
|
gboolean active;
|
|
|
|
active = gtk_toggle_button_get_active (button);
|
|
|
|
if (active != shuttling) {
|
|
shuttling = active;
|
|
g_print ("shuttling %s\n", shuttling ? "active" : "inactive");
|
|
if (active) {
|
|
shuttle_rate = 0.0;
|
|
play_rate = 1.0;
|
|
pause_cb (NULL, NULL);
|
|
gst_element_get_state (element, NULL, NULL, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
shuttle_rate_switch (GstElement * element)
|
|
{
|
|
GstSeekFlags flags;
|
|
GstEvent *s_event;
|
|
gboolean res;
|
|
|
|
if (state == GST_STATE_PLAYING) {
|
|
/* pause when we need to */
|
|
pause_cb (NULL, NULL);
|
|
gst_element_get_state (element, NULL, NULL, -1);
|
|
}
|
|
|
|
if (play_rate == 1.0)
|
|
play_rate = -1.0;
|
|
else
|
|
play_rate = 1.0;
|
|
|
|
g_print ("rate changed to %lf %" GST_TIME_FORMAT "\n", play_rate,
|
|
GST_TIME_ARGS (position));
|
|
|
|
flags = GST_SEEK_FLAG_FLUSH;
|
|
flags |= GST_SEEK_FLAG_ACCURATE;
|
|
|
|
if (play_rate >= 0.0) {
|
|
s_event = gst_event_new_seek (play_rate,
|
|
GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, position,
|
|
GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
|
|
} else {
|
|
s_event = gst_event_new_seek (play_rate,
|
|
GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
|
|
GST_SEEK_TYPE_SET, position);
|
|
}
|
|
res = send_event (s_event);
|
|
if (res) {
|
|
gst_element_get_state (element, NULL, NULL, SEEK_TIMEOUT);
|
|
} else {
|
|
g_print ("seek failed\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
shuttle_value_changed (GtkRange * range, GstElement * element)
|
|
{
|
|
gdouble rate;
|
|
|
|
rate = gtk_adjustment_get_value (shuttle_adjustment);
|
|
|
|
if (rate == 0.0) {
|
|
g_print ("rate 0.0, pause\n");
|
|
pause_cb (NULL, NULL);
|
|
gst_element_get_state (element, NULL, NULL, -1);
|
|
} else {
|
|
g_print ("rate changed %0.3g\n", rate);
|
|
|
|
if ((rate < 0.0 && play_rate > 0.0) || (rate > 0.0 && play_rate < 0.0)) {
|
|
shuttle_rate_switch (element);
|
|
}
|
|
|
|
shuttle_rate = ABS (rate);
|
|
if (state != GST_STATE_PLAYING) {
|
|
do_shuttle (element);
|
|
play_cb (NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
msg_async_done (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
|
{
|
|
GST_DEBUG ("async done");
|
|
/* when we get ASYNC_DONE we can query position, duration and other
|
|
* properties */
|
|
update_scale (pipeline);
|
|
|
|
/* update the available streams */
|
|
update_streams (pipeline);
|
|
}
|
|
|
|
static void
|
|
msg_state_changed (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
|
{
|
|
const GstStructure *s;
|
|
|
|
s = gst_message_get_structure (message);
|
|
|
|
/* We only care about state changed on the pipeline */
|
|
if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (pipeline)) {
|
|
GstState old, new, pending;
|
|
|
|
gst_message_parse_state_changed (message, &old, &new, &pending);
|
|
|
|
/* When state of the pipeline changes to paused or playing we start updating scale */
|
|
if (new == GST_STATE_PLAYING) {
|
|
set_update_scale (TRUE);
|
|
} else {
|
|
set_update_scale (FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
msg_segment_done (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
|
{
|
|
GstEvent *s_event;
|
|
GstSeekFlags flags;
|
|
gboolean res;
|
|
GstFormat format;
|
|
|
|
GST_DEBUG ("position is %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
|
|
gst_message_parse_segment_done (message, &format, &position);
|
|
GST_DEBUG ("end of segment at %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
|
|
|
|
flags = 0;
|
|
/* in the segment-done callback we never flush as this would not make sense
|
|
* for seamless playback. */
|
|
if (loop_seek)
|
|
flags |= GST_SEEK_FLAG_SEGMENT;
|
|
if (skip_seek)
|
|
flags |= GST_SEEK_FLAG_SKIP;
|
|
|
|
s_event = gst_event_new_seek (rate,
|
|
GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0),
|
|
GST_SEEK_TYPE_SET, duration);
|
|
|
|
GST_DEBUG ("restart loop with rate %lf to 0 / %" GST_TIME_FORMAT,
|
|
rate, GST_TIME_ARGS (duration));
|
|
|
|
res = send_event (s_event);
|
|
if (!res)
|
|
g_print ("segment seek failed\n");
|
|
}
|
|
|
|
/* in stream buffering mode we PAUSE the pipeline until we receive a 100%
|
|
* message */
|
|
static void
|
|
do_stream_buffering (gint percent)
|
|
{
|
|
gchar *bufstr;
|
|
|
|
gtk_statusbar_pop (GTK_STATUSBAR (statusbar), status_id);
|
|
bufstr = g_strdup_printf ("Buffering...%d", percent);
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, bufstr);
|
|
g_free (bufstr);
|
|
|
|
if (percent == 100) {
|
|
/* a 100% message means buffering is done */
|
|
buffering = FALSE;
|
|
/* if the desired state is playing, go back */
|
|
if (state == GST_STATE_PLAYING) {
|
|
/* no state management needed for live pipelines */
|
|
if (!is_live) {
|
|
fprintf (stderr, "Done buffering, setting pipeline to PLAYING ...\n");
|
|
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
|
}
|
|
gtk_statusbar_pop (GTK_STATUSBAR (statusbar), status_id);
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Playing");
|
|
}
|
|
} else {
|
|
/* buffering busy */
|
|
if (buffering == FALSE && state == GST_STATE_PLAYING) {
|
|
/* we were not buffering but PLAYING, PAUSE the pipeline. */
|
|
if (!is_live) {
|
|
fprintf (stderr, "Buffering, setting pipeline to PAUSED ...\n");
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
}
|
|
}
|
|
buffering = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
do_download_buffering (gint percent)
|
|
{
|
|
if (!buffering && percent < 100) {
|
|
gchar *bufstr;
|
|
|
|
buffering = TRUE;
|
|
|
|
bufstr = g_strdup_printf ("Downloading...");
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, bufstr);
|
|
g_free (bufstr);
|
|
|
|
/* once we get a buffering message, we'll do the fill update */
|
|
set_update_fill (TRUE);
|
|
|
|
if (state == GST_STATE_PLAYING && !is_live) {
|
|
fprintf (stderr, "Downloading, setting pipeline to PAUSED ...\n");
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
/* user has to manually start the playback */
|
|
state = GST_STATE_PAUSED;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
msg_buffering (GstBus * bus, GstMessage * message, GstPipeline * data)
|
|
{
|
|
gint percent;
|
|
|
|
gst_message_parse_buffering (message, &percent);
|
|
|
|
/* get more stats */
|
|
gst_message_parse_buffering_stats (message, &mode, NULL, NULL,
|
|
&buffering_left);
|
|
|
|
switch (mode) {
|
|
case GST_BUFFERING_DOWNLOAD:
|
|
do_download_buffering (percent);
|
|
break;
|
|
case GST_BUFFERING_LIVE:
|
|
case GST_BUFFERING_TIMESHIFT:
|
|
case GST_BUFFERING_STREAM:
|
|
do_stream_buffering (percent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
msg_clock_lost (GstBus * bus, GstMessage * message, GstPipeline * data)
|
|
{
|
|
g_print ("clock lost! PAUSE and PLAY to select a new clock\n");
|
|
|
|
gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
|
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
|
}
|
|
|
|
#ifdef HAVE_X
|
|
|
|
static guint embed_xid = 0;
|
|
|
|
static GstBusSyncReply
|
|
bus_sync_handler (GstBus * bus, GstMessage * message, GstPipeline * data)
|
|
{
|
|
if ((GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) &&
|
|
gst_structure_has_name (message->structure, "prepare-xwindow-id")) {
|
|
GstElement *element = GST_ELEMENT (GST_MESSAGE_SRC (message));
|
|
|
|
g_print ("got prepare-xwindow-id\n");
|
|
if (!embed_xid) {
|
|
embed_xid = GDK_WINDOW_XID (GDK_WINDOW (video_window->window));
|
|
}
|
|
|
|
if (g_object_class_find_property (G_OBJECT_GET_CLASS (element),
|
|
"force-aspect-ratio")) {
|
|
g_object_set (element, "force-aspect-ratio", TRUE, NULL);
|
|
}
|
|
|
|
gst_x_overlay_set_xwindow_id (GST_X_OVERLAY (GST_MESSAGE_SRC (message)),
|
|
embed_xid);
|
|
}
|
|
return GST_BUS_PASS;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
handle_expose_cb (GtkWidget * widget, GdkEventExpose * event, gpointer data)
|
|
{
|
|
if (state < GST_STATE_PAUSED) {
|
|
gdk_draw_rectangle (widget->window, widget->style->black_gc, TRUE,
|
|
0, 0, widget->allocation.width, widget->allocation.height);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
msg_eos (GstBus * bus, GstMessage * message, GstPipeline * data)
|
|
{
|
|
message_received (bus, message, data);
|
|
|
|
/* Set new uri for playerbins and continue playback */
|
|
if (l && (pipeline_type == 14 || pipeline_type == 16)) {
|
|
stop_cb (NULL, NULL);
|
|
l = g_list_next (l);
|
|
if (l) {
|
|
playerbin_set_uri (GST_ELEMENT (data), l->data);
|
|
play_cb (NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
msg_step_done (GstBus * bus, GstMessage * message, GstPipeline * data)
|
|
{
|
|
if (!shuttling)
|
|
message_received (bus, message, data);
|
|
}
|
|
|
|
static void
|
|
connect_bus_signals (GstElement * pipeline)
|
|
{
|
|
GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
|
|
|
#ifdef HAVE_X
|
|
/* handle prepare-xwindow-id element message synchronously */
|
|
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) bus_sync_handler,
|
|
pipeline);
|
|
#endif
|
|
|
|
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
|
|
gst_bus_enable_sync_message_emission (bus);
|
|
|
|
g_signal_connect (bus, "message::state-changed",
|
|
(GCallback) msg_state_changed, pipeline);
|
|
g_signal_connect (bus, "message::segment-done", (GCallback) msg_segment_done,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::async-done", (GCallback) msg_async_done,
|
|
pipeline);
|
|
|
|
g_signal_connect (bus, "message::new-clock", (GCallback) message_received,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::clock-lost", (GCallback) msg_clock_lost,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::error", (GCallback) message_received,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::warning", (GCallback) message_received,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::eos", (GCallback) msg_eos, pipeline);
|
|
g_signal_connect (bus, "message::tag", (GCallback) message_received,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::element", (GCallback) message_received,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::segment-done", (GCallback) message_received,
|
|
pipeline);
|
|
g_signal_connect (bus, "message::buffering", (GCallback) msg_buffering,
|
|
pipeline);
|
|
// g_signal_connect (bus, "message::step-done", (GCallback) msg_step_done,
|
|
// pipeline);
|
|
g_signal_connect (bus, "message::step-start", (GCallback) msg_step_done,
|
|
pipeline);
|
|
g_signal_connect (bus, "sync-message::step-done",
|
|
(GCallback) msg_sync_step_done, pipeline);
|
|
|
|
gst_object_unref (bus);
|
|
}
|
|
|
|
/* Return GList of paths described in location string */
|
|
static GList *
|
|
handle_wildcards (const gchar * location)
|
|
{
|
|
GList *res = NULL;
|
|
gchar *path = g_path_get_dirname (location);
|
|
gchar *pattern = g_path_get_basename (location);
|
|
GPatternSpec *pspec = g_pattern_spec_new (pattern);
|
|
GDir *dir = g_dir_open (path, 0, NULL);
|
|
const gchar *name;
|
|
|
|
g_print ("matching %s from %s\n", pattern, path);
|
|
|
|
if (!dir) {
|
|
g_print ("opening directory %s failed\n", path);
|
|
goto out;
|
|
}
|
|
|
|
while ((name = g_dir_read_name (dir)) != NULL) {
|
|
if (g_pattern_match_string (pspec, name)) {
|
|
res = g_list_append (res, g_strjoin ("/", path, name, NULL));
|
|
g_print (" found clip %s\n", name);
|
|
}
|
|
}
|
|
|
|
g_dir_close (dir);
|
|
out:
|
|
g_pattern_spec_free (pspec);
|
|
g_free (pattern);
|
|
g_free (path);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
delete_event_cb (void)
|
|
{
|
|
stop_cb (NULL, NULL);
|
|
gtk_main_quit ();
|
|
}
|
|
|
|
static void
|
|
print_usage (int argc, char **argv)
|
|
{
|
|
gint i;
|
|
|
|
g_print ("usage: %s <type> <filename>\n", argv[0]);
|
|
g_print (" possible types:\n");
|
|
|
|
for (i = 0; i < NUM_TYPES; i++) {
|
|
g_print (" %d = %s\n", i, pipelines[i].name);
|
|
}
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GtkWidget *window, *hbox, *vbox, *panel, *expander, *pb2vbox, *boxes,
|
|
*flagtable, *boxes2, *step;
|
|
GtkWidget *play_button, *pause_button, *stop_button, *shot_button;
|
|
GtkWidget *accurate_checkbox, *key_checkbox, *loop_checkbox, *flush_checkbox;
|
|
GtkWidget *scrub_checkbox, *play_scrub_checkbox;
|
|
GtkWidget *rate_label, *volume_label;
|
|
GOptionEntry options[] = {
|
|
{"stats", 's', 0, G_OPTION_ARG_NONE, &stats,
|
|
"Show pad stats", NULL},
|
|
{"elem", 'e', 0, G_OPTION_ARG_NONE, &elem_seek,
|
|
"Seek on elements instead of pads", NULL},
|
|
{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
|
|
"Verbose properties", NULL},
|
|
{NULL}
|
|
};
|
|
GOptionContext *ctx;
|
|
GError *err = NULL;
|
|
|
|
if (!g_thread_supported ())
|
|
g_thread_init (NULL);
|
|
|
|
ctx = g_option_context_new ("- test seeking in gsteamer");
|
|
g_option_context_add_main_entries (ctx, options, NULL);
|
|
g_option_context_add_group (ctx, gst_init_get_option_group ());
|
|
g_option_context_add_group (ctx, gtk_get_option_group (TRUE));
|
|
|
|
if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
|
|
g_print ("Error initializing: %s\n", err->message);
|
|
exit (1);
|
|
}
|
|
|
|
GST_DEBUG_CATEGORY_INIT (seek_debug, "seek", 0, "seek example");
|
|
|
|
if (argc != 3) {
|
|
print_usage (argc, argv);
|
|
exit (-1);
|
|
}
|
|
|
|
pipeline_type = atoi (argv[1]);
|
|
|
|
if (pipeline_type < 0 || pipeline_type >= NUM_TYPES) {
|
|
print_usage (argc, argv);
|
|
exit (-1);
|
|
}
|
|
|
|
pipeline_spec = argv[2];
|
|
|
|
if (g_strrstr (pipeline_spec, "*") != NULL ||
|
|
g_strrstr (pipeline_spec, "?") != NULL) {
|
|
paths = handle_wildcards (pipeline_spec);
|
|
} else {
|
|
paths = g_list_prepend (paths, g_strdup (pipeline_spec));
|
|
}
|
|
|
|
if (!paths) {
|
|
g_print ("opening %s failed\n", pipeline_spec);
|
|
exit (-1);
|
|
}
|
|
|
|
l = paths;
|
|
|
|
pipeline = pipelines[pipeline_type].func ((gchar *) l->data);
|
|
g_assert (pipeline);
|
|
|
|
/* initialize gui elements ... */
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
video_window = gtk_drawing_area_new ();
|
|
g_signal_connect (G_OBJECT (video_window), "expose-event",
|
|
G_CALLBACK (handle_expose_cb), NULL);
|
|
gtk_widget_set_double_buffered (video_window, FALSE);
|
|
statusbar = gtk_statusbar_new ();
|
|
status_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar), "seek");
|
|
gtk_statusbar_push (GTK_STATUSBAR (statusbar), status_id, "Stopped");
|
|
hbox = gtk_hbox_new (FALSE, 0);
|
|
vbox = gtk_vbox_new (FALSE, 0);
|
|
flagtable = gtk_table_new (4, 2, FALSE);
|
|
gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
|
|
|
|
/* media controls */
|
|
play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
|
|
pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
|
|
stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
|
|
|
|
/* seek flags */
|
|
accurate_checkbox = gtk_check_button_new_with_label ("Accurate Seek");
|
|
key_checkbox = gtk_check_button_new_with_label ("Key-unit Seek");
|
|
loop_checkbox = gtk_check_button_new_with_label ("Loop");
|
|
flush_checkbox = gtk_check_button_new_with_label ("Flush");
|
|
scrub_checkbox = gtk_check_button_new_with_label ("Scrub");
|
|
play_scrub_checkbox = gtk_check_button_new_with_label ("Play Scrub");
|
|
skip_checkbox = gtk_check_button_new_with_label ("Play Skip");
|
|
rate_spinbutton = gtk_spin_button_new_with_range (-100, 100, 0.1);
|
|
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (rate_spinbutton), 3);
|
|
rate_label = gtk_label_new ("Rate");
|
|
|
|
gtk_widget_set_tooltip_text (accurate_checkbox,
|
|
"accurate position is requested, this might be considerably slower for some formats");
|
|
gtk_widget_set_tooltip_text (key_checkbox,
|
|
"seek to the nearest keyframe. This might be faster but less accurate");
|
|
gtk_widget_set_tooltip_text (loop_checkbox, "loop playback");
|
|
gtk_widget_set_tooltip_text (flush_checkbox, "flush pipeline after seeking");
|
|
gtk_widget_set_tooltip_text (rate_spinbutton, "define the playback rate, "
|
|
"negative value trigger reverse playback");
|
|
gtk_widget_set_tooltip_text (scrub_checkbox, "show images while seeking");
|
|
gtk_widget_set_tooltip_text (play_scrub_checkbox, "play video while seeking");
|
|
gtk_widget_set_tooltip_text (skip_checkbox,
|
|
"Skip frames while playing at high frame rates");
|
|
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (flush_checkbox), TRUE);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scrub_checkbox), TRUE);
|
|
|
|
gtk_spin_button_set_value (GTK_SPIN_BUTTON (rate_spinbutton), rate);
|
|
|
|
/* step expander */
|
|
{
|
|
GtkWidget *hbox;
|
|
|
|
step = gtk_expander_new ("step options");
|
|
hbox = gtk_hbox_new (FALSE, 0);
|
|
|
|
format_combo = gtk_combo_box_new_text ();
|
|
gtk_combo_box_append_text (GTK_COMBO_BOX (format_combo), "frames");
|
|
gtk_combo_box_append_text (GTK_COMBO_BOX (format_combo), "time (ms)");
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX (format_combo), 0);
|
|
gtk_box_pack_start (GTK_BOX (hbox), format_combo, FALSE, FALSE, 2);
|
|
|
|
step_amount_spinbutton = gtk_spin_button_new_with_range (1, 1000, 1);
|
|
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (step_amount_spinbutton), 0);
|
|
gtk_spin_button_set_value (GTK_SPIN_BUTTON (step_amount_spinbutton), 1.0);
|
|
gtk_box_pack_start (GTK_BOX (hbox), step_amount_spinbutton, FALSE, FALSE,
|
|
2);
|
|
|
|
step_rate_spinbutton = gtk_spin_button_new_with_range (0.0, 100, 0.1);
|
|
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (step_rate_spinbutton), 3);
|
|
gtk_spin_button_set_value (GTK_SPIN_BUTTON (step_rate_spinbutton), 1.0);
|
|
gtk_box_pack_start (GTK_BOX (hbox), step_rate_spinbutton, FALSE, FALSE, 2);
|
|
|
|
step_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_FORWARD);
|
|
gtk_button_set_label (GTK_BUTTON (step_button), "Step");
|
|
gtk_box_pack_start (GTK_BOX (hbox), step_button, FALSE, FALSE, 2);
|
|
|
|
g_signal_connect (G_OBJECT (step_button), "clicked", G_CALLBACK (step_cb),
|
|
pipeline);
|
|
|
|
/* shuttle scale */
|
|
shuttle_checkbox = gtk_check_button_new_with_label ("Shuttle");
|
|
gtk_box_pack_start (GTK_BOX (hbox), shuttle_checkbox, FALSE, FALSE, 2);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (shuttle_checkbox), FALSE);
|
|
g_signal_connect (GTK_OBJECT (shuttle_checkbox),
|
|
"toggled", G_CALLBACK (shuttle_toggled), pipeline);
|
|
|
|
shuttle_adjustment =
|
|
GTK_ADJUSTMENT (gtk_adjustment_new (0.0, -3.00, 4.0, 0.1, 1.0, 1.0));
|
|
shuttle_hscale = gtk_hscale_new (shuttle_adjustment);
|
|
gtk_scale_set_digits (GTK_SCALE (shuttle_hscale), 2);
|
|
gtk_scale_set_value_pos (GTK_SCALE (shuttle_hscale), GTK_POS_TOP);
|
|
gtk_range_set_update_policy (GTK_RANGE (shuttle_hscale),
|
|
GTK_UPDATE_CONTINUOUS);
|
|
g_signal_connect (GTK_OBJECT (shuttle_hscale), "value_changed",
|
|
G_CALLBACK (shuttle_value_changed), pipeline);
|
|
g_signal_connect (GTK_OBJECT (shuttle_hscale), "format_value",
|
|
G_CALLBACK (shuttle_format_value), pipeline);
|
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), shuttle_hscale, TRUE, TRUE, 2);
|
|
|
|
gtk_container_add (GTK_CONTAINER (step), hbox);
|
|
}
|
|
|
|
/* seek bar */
|
|
adjustment =
|
|
GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.00, 100.0, 0.1, 1.0, 1.0));
|
|
hscale = gtk_hscale_new (adjustment);
|
|
gtk_scale_set_digits (GTK_SCALE (hscale), 2);
|
|
gtk_scale_set_value_pos (GTK_SCALE (hscale), GTK_POS_RIGHT);
|
|
#if GTK_CHECK_VERSION(2,12,0)
|
|
gtk_range_set_show_fill_level (GTK_RANGE (hscale), TRUE);
|
|
gtk_range_set_fill_level (GTK_RANGE (hscale), 100.0);
|
|
#endif
|
|
gtk_range_set_update_policy (GTK_RANGE (hscale), GTK_UPDATE_CONTINUOUS);
|
|
|
|
g_signal_connect (GTK_OBJECT (hscale),
|
|
"button_press_event", G_CALLBACK (start_seek), pipeline);
|
|
g_signal_connect (GTK_OBJECT (hscale),
|
|
"button_release_event", G_CALLBACK (stop_seek), pipeline);
|
|
g_signal_connect (GTK_OBJECT (hscale),
|
|
"format_value", G_CALLBACK (format_value), pipeline);
|
|
|
|
if (pipeline_type == 16) {
|
|
/* the playbin2 panel controls for the video/audio/subtitle tracks */
|
|
panel = gtk_hbox_new (FALSE, 0);
|
|
video_combo = gtk_combo_box_new_text ();
|
|
audio_combo = gtk_combo_box_new_text ();
|
|
text_combo = gtk_combo_box_new_text ();
|
|
gtk_widget_set_sensitive (video_combo, FALSE);
|
|
gtk_widget_set_sensitive (audio_combo, FALSE);
|
|
gtk_widget_set_sensitive (text_combo, FALSE);
|
|
gtk_box_pack_start (GTK_BOX (panel), video_combo, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (panel), audio_combo, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (panel), text_combo, TRUE, TRUE, 2);
|
|
g_signal_connect (G_OBJECT (video_combo), "changed",
|
|
G_CALLBACK (video_combo_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (audio_combo), "changed",
|
|
G_CALLBACK (audio_combo_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (text_combo), "changed",
|
|
G_CALLBACK (text_combo_cb), pipeline);
|
|
/* playbin2 panel for flag checkboxes and volume/mute */
|
|
boxes = gtk_hbox_new (FALSE, 0);
|
|
vis_checkbox = gtk_check_button_new_with_label ("Vis");
|
|
video_checkbox = gtk_check_button_new_with_label ("Video");
|
|
audio_checkbox = gtk_check_button_new_with_label ("Audio");
|
|
text_checkbox = gtk_check_button_new_with_label ("Text");
|
|
mute_checkbox = gtk_check_button_new_with_label ("Mute");
|
|
download_checkbox = gtk_check_button_new_with_label ("Download");
|
|
volume_label = gtk_label_new ("Volume");
|
|
volume_spinbutton = gtk_spin_button_new_with_range (0, 10.0, 0.1);
|
|
gtk_spin_button_set_value (GTK_SPIN_BUTTON (volume_spinbutton), 1.0);
|
|
gtk_box_pack_start (GTK_BOX (boxes), video_checkbox, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes), audio_checkbox, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes), text_checkbox, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes), vis_checkbox, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes), mute_checkbox, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes), download_checkbox, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes), volume_label, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes), volume_spinbutton, TRUE, TRUE, 2);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (vis_checkbox), FALSE);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (audio_checkbox), TRUE);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (video_checkbox), TRUE);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (text_checkbox), TRUE);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (mute_checkbox), FALSE);
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (download_checkbox), FALSE);
|
|
g_signal_connect (G_OBJECT (vis_checkbox), "toggled",
|
|
G_CALLBACK (vis_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (audio_checkbox), "toggled",
|
|
G_CALLBACK (audio_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (video_checkbox), "toggled",
|
|
G_CALLBACK (video_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (text_checkbox), "toggled",
|
|
G_CALLBACK (text_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (mute_checkbox), "toggled",
|
|
G_CALLBACK (mute_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (download_checkbox), "toggled",
|
|
G_CALLBACK (download_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (volume_spinbutton), "value_changed",
|
|
G_CALLBACK (volume_spinbutton_changed_cb), pipeline);
|
|
/* playbin2 panel for snapshot */
|
|
boxes2 = gtk_hbox_new (FALSE, 0);
|
|
shot_button = gtk_button_new_from_stock (GTK_STOCK_SAVE);
|
|
gtk_widget_set_tooltip_text (shot_button,
|
|
"save a screenshot .png in the current directory");
|
|
g_signal_connect (G_OBJECT (shot_button), "clicked", G_CALLBACK (shot_cb),
|
|
pipeline);
|
|
vis_combo = gtk_combo_box_new_text ();
|
|
g_signal_connect (G_OBJECT (vis_combo), "changed",
|
|
G_CALLBACK (vis_combo_cb), pipeline);
|
|
gtk_widget_set_sensitive (vis_combo, FALSE);
|
|
gtk_box_pack_start (GTK_BOX (boxes2), shot_button, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (boxes2), vis_combo, TRUE, TRUE, 2);
|
|
|
|
/* fill the vis combo box and the array of factories */
|
|
init_visualization_features ();
|
|
} else {
|
|
panel = boxes = boxes2 = NULL;
|
|
}
|
|
|
|
/* do the packing stuff ... */
|
|
gtk_window_set_default_size (GTK_WINDOW (window), 250, 96);
|
|
/* FIXME: can we avoid this for audio only? */
|
|
gtk_widget_set_size_request (GTK_WIDGET (video_window), -1,
|
|
DEFAULT_VIDEO_HEIGHT);
|
|
gtk_container_add (GTK_CONTAINER (window), vbox);
|
|
gtk_box_pack_start (GTK_BOX (vbox), video_window, TRUE, TRUE, 2);
|
|
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (hbox), play_button, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (hbox), pause_button, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (hbox), stop_button, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (hbox), flagtable, FALSE, FALSE, 2);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), accurate_checkbox, 0, 1, 0,
|
|
1);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), flush_checkbox, 1, 2, 0, 1);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), loop_checkbox, 2, 3, 0, 1);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), key_checkbox, 0, 1, 1, 2);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), scrub_checkbox, 1, 2, 1, 2);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), play_scrub_checkbox, 2, 3,
|
|
1, 2);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), skip_checkbox, 3, 4, 0, 1);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), rate_label, 4, 5, 0, 1);
|
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), rate_spinbutton, 4, 5, 1,
|
|
2);
|
|
if (panel && boxes && boxes2) {
|
|
expander = gtk_expander_new ("playbin2 options");
|
|
pb2vbox = gtk_vbox_new (FALSE, 0);
|
|
gtk_box_pack_start (GTK_BOX (pb2vbox), panel, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (pb2vbox), boxes, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (pb2vbox), boxes2, FALSE, FALSE, 2);
|
|
gtk_container_add (GTK_CONTAINER (expander), pb2vbox);
|
|
gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 2);
|
|
}
|
|
gtk_box_pack_start (GTK_BOX (vbox), step, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (vbox), hscale, FALSE, FALSE, 2);
|
|
gtk_box_pack_start (GTK_BOX (vbox), statusbar, FALSE, FALSE, 2);
|
|
|
|
/* connect things ... */
|
|
g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb),
|
|
pipeline);
|
|
g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb),
|
|
pipeline);
|
|
g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb),
|
|
pipeline);
|
|
g_signal_connect (G_OBJECT (accurate_checkbox), "toggled",
|
|
G_CALLBACK (accurate_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (key_checkbox), "toggled",
|
|
G_CALLBACK (key_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (loop_checkbox), "toggled",
|
|
G_CALLBACK (loop_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (flush_checkbox), "toggled",
|
|
G_CALLBACK (flush_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (scrub_checkbox), "toggled",
|
|
G_CALLBACK (scrub_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (play_scrub_checkbox), "toggled",
|
|
G_CALLBACK (play_scrub_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (skip_checkbox), "toggled",
|
|
G_CALLBACK (skip_toggle_cb), pipeline);
|
|
g_signal_connect (G_OBJECT (rate_spinbutton), "value_changed",
|
|
G_CALLBACK (rate_spinbutton_changed_cb), pipeline);
|
|
|
|
g_signal_connect (G_OBJECT (window), "delete-event", delete_event_cb, NULL);
|
|
|
|
/* show the gui. */
|
|
gtk_widget_show_all (window);
|
|
|
|
if (verbose) {
|
|
g_signal_connect (pipeline, "deep_notify",
|
|
G_CALLBACK (gst_object_default_deep_notify), NULL);
|
|
}
|
|
|
|
connect_bus_signals (pipeline);
|
|
gtk_main ();
|
|
|
|
g_print ("NULL pipeline\n");
|
|
gst_element_set_state (pipeline, GST_STATE_NULL);
|
|
|
|
g_print ("free pipeline\n");
|
|
gst_object_unref (pipeline);
|
|
|
|
g_list_foreach (paths, (GFunc) g_free, NULL);
|
|
g_list_free (paths);
|
|
|
|
return 0;
|
|
}
|