blob: 5ab9e03e32931112c64e08a74ecb2d0d93cd9efe [file] [log] [blame]
/* GStreamer
*
* Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
* Copyright (C) 2015 Brijesh Singh <brijesh.ksingh@gmail.com>
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gstplayer
* @title: GstPlayer
* @short_description: Player
*
*/
/* TODO:
*
* - Equalizer
* - Gapless playback
* - Frame stepping
* - Subtitle font, connection speed
* - Deinterlacing
* - Buffering control (-> progressive downloading)
* - Playlist/queue object
* - Custom video sink (e.g. embed in GL scene)
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstplayer.h"
#include "gstplayer-signal-dispatcher-private.h"
#include "gstplayer-video-renderer-private.h"
#include "gstplayer-media-info-private.h"
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/video/colorbalance.h>
#include <gst/tag/tag.h>
#include <gst/pbutils/descriptions.h>
#include <string.h>
GST_DEBUG_CATEGORY_STATIC (gst_player_debug);
#define GST_CAT_DEFAULT gst_player_debug
#define DEFAULT_URI NULL
#define DEFAULT_POSITION GST_CLOCK_TIME_NONE
#define DEFAULT_DURATION GST_CLOCK_TIME_NONE
#define DEFAULT_VOLUME 1.0
#define DEFAULT_MUTE FALSE
#define DEFAULT_RATE 1.0
#define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100
#define DEFAULT_AUDIO_VIDEO_OFFSET 0
GQuark
gst_player_error_quark (void)
{
return g_quark_from_static_string ("gst-player-error-quark");
}
static GQuark QUARK_CONFIG;
/* Keep ConfigQuarkId and _config_quark_strings ordered and synced */
typedef enum
{
CONFIG_QUARK_USER_AGENT = 0,
CONFIG_QUARK_POSITION_INTERVAL_UPDATE,
CONFIG_QUARK_ACCURATE_SEEK,
CONFIG_QUARK_MAX
} ConfigQuarkId;
static const gchar *_config_quark_strings[] = {
"user-agent",
"position-interval-update",
"accurate-seek",
};
GQuark _config_quark_table[CONFIG_QUARK_MAX];
#define CONFIG_QUARK(q) _config_quark_table[CONFIG_QUARK_##q]
enum
{
PROP_0,
PROP_VIDEO_RENDERER,
PROP_SIGNAL_DISPATCHER,
PROP_URI,
PROP_SUBURI,
PROP_POSITION,
PROP_DURATION,
PROP_MEDIA_INFO,
PROP_CURRENT_AUDIO_TRACK,
PROP_CURRENT_VIDEO_TRACK,
PROP_CURRENT_SUBTITLE_TRACK,
PROP_VOLUME,
PROP_MUTE,
PROP_RATE,
PROP_PIPELINE,
PROP_VIDEO_MULTIVIEW_MODE,
PROP_VIDEO_MULTIVIEW_FLAGS,
PROP_AUDIO_VIDEO_OFFSET,
PROP_LAST
};
enum
{
SIGNAL_URI_LOADED,
SIGNAL_POSITION_UPDATED,
SIGNAL_DURATION_CHANGED,
SIGNAL_STATE_CHANGED,
SIGNAL_BUFFERING,
SIGNAL_END_OF_STREAM,
SIGNAL_ERROR,
SIGNAL_WARNING,
SIGNAL_VIDEO_DIMENSIONS_CHANGED,
SIGNAL_MEDIA_INFO_UPDATED,
SIGNAL_VOLUME_CHANGED,
SIGNAL_MUTE_CHANGED,
SIGNAL_SEEK_DONE,
SIGNAL_LAST
};
enum
{
GST_PLAY_FLAG_VIDEO = (1 << 0),
GST_PLAY_FLAG_AUDIO = (1 << 1),
GST_PLAY_FLAG_SUBTITLE = (1 << 2),
GST_PLAY_FLAG_VIS = (1 << 3)
};
struct _GstPlayer
{
GstObject parent;
GstPlayerVideoRenderer *video_renderer;
GstPlayerSignalDispatcher *signal_dispatcher;
gchar *uri;
gchar *redirect_uri;
gchar *suburi;
GThread *thread;
GMutex lock;
GCond cond;
GMainContext *context;
GMainLoop *loop;
GstElement *playbin;
GstBus *bus;
GstState target_state, current_state;
gboolean is_live, is_eos;
GSource *tick_source, *ready_timeout_source;
GstClockTime cached_duration;
gdouble rate;
GstPlayerState app_state;
gint buffering;
GstTagList *global_tags;
GstPlayerMediaInfo *media_info;
GstElement *current_vis_element;
GstStructure *config;
/* Protected by lock */
gboolean seek_pending; /* Only set from main context */
GstClockTime last_seek_time; /* Only set from main context */
GSource *seek_source;
GstClockTime seek_position;
/* If TRUE, all signals are inhibited except the
* state-changed:GST_PLAYER_STATE_STOPPED/PAUSED. This ensures that no signal
* is emitted after gst_player_stop/pause() has been called by the user. */
gboolean inhibit_sigs;
/* For playbin3 */
gboolean use_playbin3;
GstStreamCollection *collection;
gchar *video_sid;
gchar *audio_sid;
gchar *subtitle_sid;
gulong stream_notify_id;
};
struct _GstPlayerClass
{
GstObjectClass parent_class;
};
#define parent_class gst_player_parent_class
G_DEFINE_TYPE (GstPlayer, gst_player, GST_TYPE_OBJECT);
static guint signals[SIGNAL_LAST] = { 0, };
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void gst_player_dispose (GObject * object);
static void gst_player_finalize (GObject * object);
static void gst_player_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_player_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_player_constructed (GObject * object);
static gpointer gst_player_main (gpointer data);
static void gst_player_seek_internal_locked (GstPlayer * self);
static void gst_player_stop_internal (GstPlayer * self, gboolean transient);
static gboolean gst_player_pause_internal (gpointer user_data);
static gboolean gst_player_play_internal (gpointer user_data);
static gboolean gst_player_seek_internal (gpointer user_data);
static void gst_player_set_rate_internal (GstPlayer * self);
static void change_state (GstPlayer * self, GstPlayerState state);
static GstPlayerMediaInfo *gst_player_media_info_create (GstPlayer * self);
static void gst_player_streams_info_create (GstPlayer * self,
GstPlayerMediaInfo * media_info, const gchar * prop, GType type);
static void gst_player_stream_info_update (GstPlayer * self,
GstPlayerStreamInfo * s);
static void gst_player_stream_info_update_tags_and_caps (GstPlayer * self,
GstPlayerStreamInfo * s);
static GstPlayerStreamInfo *gst_player_stream_info_find (GstPlayerMediaInfo *
media_info, GType type, gint stream_index);
static GstPlayerStreamInfo *gst_player_stream_info_get_current (GstPlayer *
self, const gchar * prop, GType type);
static void gst_player_video_info_update (GstPlayer * self,
GstPlayerStreamInfo * stream_info);
static void gst_player_audio_info_update (GstPlayer * self,
GstPlayerStreamInfo * stream_info);
static void gst_player_subtitle_info_update (GstPlayer * self,
GstPlayerStreamInfo * stream_info);
/* For playbin3 */
static void gst_player_streams_info_create_from_collection (GstPlayer * self,
GstPlayerMediaInfo * media_info, GstStreamCollection * collection);
static void gst_player_stream_info_update_from_stream (GstPlayer * self,
GstPlayerStreamInfo * s, GstStream * stream);
static GstPlayerStreamInfo *gst_player_stream_info_find_from_stream_id
(GstPlayerMediaInfo * media_info, const gchar * stream_id);
static GstPlayerStreamInfo *gst_player_stream_info_get_current_from_stream_id
(GstPlayer * self, const gchar * stream_id, GType type);
static void stream_notify_cb (GstStreamCollection * collection,
GstStream * stream, GParamSpec * pspec, GstPlayer * self);
static void emit_media_info_updated_signal (GstPlayer * self);
static void *get_title (GstTagList * tags);
static void *get_container_format (GstTagList * tags);
static void *get_from_tags (GstPlayer * self, GstPlayerMediaInfo * media_info,
void *(*func) (GstTagList *));
static void *get_cover_sample (GstTagList * tags);
static void remove_seek_source (GstPlayer * self);
static void
gst_player_init (GstPlayer * self)
{
GST_TRACE_OBJECT (self, "Initializing");
self = gst_player_get_instance_private (self);
g_mutex_init (&self->lock);
g_cond_init (&self->cond);
self->context = g_main_context_new ();
self->loop = g_main_loop_new (self->context, FALSE);
/* *INDENT-OFF* */
self->config = gst_structure_new_id (QUARK_CONFIG,
CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, DEFAULT_POSITION_UPDATE_INTERVAL_MS,
CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, FALSE,
NULL);
/* *INDENT-ON* */
self->seek_pending = FALSE;
self->seek_position = GST_CLOCK_TIME_NONE;
self->last_seek_time = GST_CLOCK_TIME_NONE;
self->inhibit_sigs = FALSE;
GST_TRACE_OBJECT (self, "Initialized");
}
static void
config_quark_initialize (void)
{
gint i;
QUARK_CONFIG = g_quark_from_static_string ("player-config");
if (G_N_ELEMENTS (_config_quark_strings) != CONFIG_QUARK_MAX)
g_warning ("the quark table is not consistent! %d != %d",
(int) G_N_ELEMENTS (_config_quark_strings), CONFIG_QUARK_MAX);
for (i = 0; i < CONFIG_QUARK_MAX; i++) {
_config_quark_table[i] =
g_quark_from_static_string (_config_quark_strings[i]);
}
}
static void
gst_player_class_init (GstPlayerClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
gobject_class->set_property = gst_player_set_property;
gobject_class->get_property = gst_player_get_property;
gobject_class->dispose = gst_player_dispose;
gobject_class->finalize = gst_player_finalize;
gobject_class->constructed = gst_player_constructed;
param_specs[PROP_VIDEO_RENDERER] =
g_param_spec_object ("video-renderer",
"Video Renderer", "Video renderer to use for rendering videos",
GST_TYPE_PLAYER_VIDEO_RENDERER,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
param_specs[PROP_SIGNAL_DISPATCHER] =
g_param_spec_object ("signal-dispatcher",
"Signal Dispatcher", "Dispatcher for the signals to e.g. event loops",
GST_TYPE_PLAYER_SIGNAL_DISPATCHER,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI",
DEFAULT_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", "Subtitle URI",
"Current Subtitle URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_POSITION] =
g_param_spec_uint64 ("position", "Position", "Current Position",
0, G_MAXUINT64, DEFAULT_POSITION,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_MEDIA_INFO] =
g_param_spec_object ("media-info", "Media Info",
"Current media information", GST_TYPE_PLAYER_MEDIA_INFO,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_CURRENT_AUDIO_TRACK] =
g_param_spec_object ("current-audio-track", "Current Audio Track",
"Current audio track information", GST_TYPE_PLAYER_AUDIO_INFO,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_CURRENT_VIDEO_TRACK] =
g_param_spec_object ("current-video-track", "Current Video Track",
"Current video track information", GST_TYPE_PLAYER_VIDEO_INFO,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_CURRENT_SUBTITLE_TRACK] =
g_param_spec_object ("current-subtitle-track", "Current Subtitle Track",
"Current audio subtitle information", GST_TYPE_PLAYER_SUBTITLE_INFO,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_DURATION] =
g_param_spec_uint64 ("duration", "Duration", "Duration",
0, G_MAXUINT64, DEFAULT_DURATION,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_VOLUME] =
g_param_spec_double ("volume", "Volume", "Volume",
0, 10.0, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_MUTE] =
g_param_spec_boolean ("mute", "Mute", "Mute",
DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_PIPELINE] =
g_param_spec_object ("pipeline", "Pipeline",
"GStreamer pipeline that is used",
GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_RATE] =
g_param_spec_double ("rate", "rate", "Playback rate",
-64.0, 64.0, DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_VIDEO_MULTIVIEW_MODE] =
g_param_spec_enum ("video-multiview-mode",
"Multiview Mode Override",
"Re-interpret a video stream as one of several frame-packed stereoscopic modes.",
GST_TYPE_VIDEO_MULTIVIEW_FRAME_PACKING,
GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_VIDEO_MULTIVIEW_FLAGS] =
g_param_spec_flags ("video-multiview-flags",
"Multiview Flags Override",
"Override details of the multiview frame layout",
GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_AUDIO_VIDEO_OFFSET] =
g_param_spec_int64 ("audio-video-offset", "Audio Video Offset",
"The synchronisation offset between audio and video in nanoseconds",
G_MININT64, G_MAXINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
signals[SIGNAL_URI_LOADED] =
g_signal_new ("uri-loaded", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
signals[SIGNAL_POSITION_UPDATED] =
g_signal_new ("position-updated", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME);
signals[SIGNAL_DURATION_CHANGED] =
g_signal_new ("duration-changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME);
signals[SIGNAL_STATE_CHANGED] =
g_signal_new ("state-changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_PLAYER_STATE);
signals[SIGNAL_BUFFERING] =
g_signal_new ("buffering", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT);
signals[SIGNAL_END_OF_STREAM] =
g_signal_new ("end-of-stream", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID);
signals[SIGNAL_ERROR] =
g_signal_new ("error", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR);
signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED] =
g_signal_new ("video-dimensions-changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
signals[SIGNAL_MEDIA_INFO_UPDATED] =
g_signal_new ("media-info-updated", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_PLAYER_MEDIA_INFO);
signals[SIGNAL_VOLUME_CHANGED] =
g_signal_new ("volume-changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID);
signals[SIGNAL_MUTE_CHANGED] =
g_signal_new ("mute-changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID);
signals[SIGNAL_WARNING] =
g_signal_new ("warning", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR);
signals[SIGNAL_SEEK_DONE] =
g_signal_new ("seek-done", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME);
config_quark_initialize ();
}
static void
gst_player_dispose (GObject * object)
{
GstPlayer *self = GST_PLAYER (object);
GST_TRACE_OBJECT (self, "Stopping main thread");
if (self->loop) {
g_main_loop_quit (self->loop);
if (self->thread != g_thread_self ())
g_thread_join (self->thread);
else
g_thread_unref (self->thread);
self->thread = NULL;
g_main_loop_unref (self->loop);
self->loop = NULL;
g_main_context_unref (self->context);
self->context = NULL;
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_player_finalize (GObject * object)
{
GstPlayer *self = GST_PLAYER (object);
GST_TRACE_OBJECT (self, "Finalizing");
g_free (self->uri);
g_free (self->redirect_uri);
g_free (self->suburi);
g_free (self->video_sid);
g_free (self->audio_sid);
g_free (self->subtitle_sid);
if (self->global_tags)
gst_tag_list_unref (self->global_tags);
if (self->video_renderer)
g_object_unref (self->video_renderer);
if (self->signal_dispatcher)
g_object_unref (self->signal_dispatcher);
if (self->current_vis_element)
gst_object_unref (self->current_vis_element);
if (self->config)
gst_structure_free (self->config);
if (self->collection)
gst_object_unref (self->collection);
g_mutex_clear (&self->lock);
g_cond_clear (&self->cond);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_player_constructed (GObject * object)
{
GstPlayer *self = GST_PLAYER (object);
GST_TRACE_OBJECT (self, "Constructed");
g_mutex_lock (&self->lock);
self->thread = g_thread_new ("GstPlayer", gst_player_main, self);
while (!self->loop || !g_main_loop_is_running (self->loop))
g_cond_wait (&self->cond, &self->lock);
g_mutex_unlock (&self->lock);
G_OBJECT_CLASS (parent_class)->constructed (object);
}
typedef struct
{
GstPlayer *player;
gchar *uri;
} UriLoadedSignalData;
static void
uri_loaded_dispatch (gpointer user_data)
{
UriLoadedSignalData *data = user_data;
g_signal_emit (data->player, signals[SIGNAL_URI_LOADED], 0, data->uri);
}
static void
uri_loaded_signal_data_free (UriLoadedSignalData * data)
{
g_object_unref (data->player);
g_free (data->uri);
g_free (data);
}
static gboolean
gst_player_set_uri_internal (gpointer user_data)
{
GstPlayer *self = user_data;
gst_player_stop_internal (self, FALSE);
g_mutex_lock (&self->lock);
GST_DEBUG_OBJECT (self, "Changing URI to '%s'", GST_STR_NULL (self->uri));
g_object_set (self->playbin, "uri", self->uri, NULL);
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_URI_LOADED], 0, NULL, NULL, NULL) != 0) {
UriLoadedSignalData *data = g_new (UriLoadedSignalData, 1);
data->player = g_object_ref (self);
data->uri = g_strdup (self->uri);
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
uri_loaded_dispatch, data,
(GDestroyNotify) uri_loaded_signal_data_free);
}
g_object_set (self->playbin, "suburi", NULL, NULL);
g_mutex_unlock (&self->lock);
return G_SOURCE_REMOVE;
}
static gboolean
gst_player_set_suburi_internal (gpointer user_data)
{
GstPlayer *self = user_data;
GstClockTime position;
GstState target_state;
/* save the state and position */
target_state = self->target_state;
position = gst_player_get_position (self);
gst_player_stop_internal (self, TRUE);
g_mutex_lock (&self->lock);
GST_DEBUG_OBJECT (self, "Changing SUBURI to '%s'",
GST_STR_NULL (self->suburi));
g_object_set (self->playbin, "suburi", self->suburi, NULL);
g_object_set (self->playbin, "uri",
self->redirect_uri ? self->redirect_uri : self->uri, NULL);
g_mutex_unlock (&self->lock);
/* restore state and position */
if (position != GST_CLOCK_TIME_NONE)
gst_player_seek (self, position);
if (target_state == GST_STATE_PAUSED)
gst_player_pause_internal (self);
else if (target_state == GST_STATE_PLAYING)
gst_player_play_internal (self);
return G_SOURCE_REMOVE;
}
static void
gst_player_set_rate_internal (GstPlayer * self)
{
self->seek_position = gst_player_get_position (self);
/* If there is no seek being dispatch to the main context currently do that,
* otherwise we just updated the rate so that it will be taken by
* the seek handler from the main context instead of the old one.
*/
if (!self->seek_source) {
/* If no seek is pending then create new seek source */
if (!self->seek_pending) {
self->seek_source = g_idle_source_new ();
g_source_set_callback (self->seek_source,
(GSourceFunc) gst_player_seek_internal, self, NULL);
g_source_attach (self->seek_source, self->context);
}
}
}
static void
gst_player_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstPlayer *self = GST_PLAYER (object);
switch (prop_id) {
case PROP_VIDEO_RENDERER:
self->video_renderer = g_value_dup_object (value);
break;
case PROP_SIGNAL_DISPATCHER:
self->signal_dispatcher = g_value_dup_object (value);
break;
case PROP_URI:{
g_mutex_lock (&self->lock);
g_free (self->uri);
g_free (self->redirect_uri);
self->redirect_uri = NULL;
g_free (self->suburi);
self->suburi = NULL;
self->uri = g_value_dup_string (value);
GST_DEBUG_OBJECT (self, "Set uri=%s", self->uri);
g_mutex_unlock (&self->lock);
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
gst_player_set_uri_internal, self, NULL);
break;
}
case PROP_SUBURI:{
g_mutex_lock (&self->lock);
g_free (self->suburi);
self->suburi = g_value_dup_string (value);
GST_DEBUG_OBJECT (self, "Set suburi=%s", self->suburi);
g_mutex_unlock (&self->lock);
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
gst_player_set_suburi_internal, self, NULL);
break;
}
case PROP_VOLUME:
GST_DEBUG_OBJECT (self, "Set volume=%lf", g_value_get_double (value));
g_object_set_property (G_OBJECT (self->playbin), "volume", value);
break;
case PROP_RATE:
g_mutex_lock (&self->lock);
self->rate = g_value_get_double (value);
GST_DEBUG_OBJECT (self, "Set rate=%lf", g_value_get_double (value));
gst_player_set_rate_internal (self);
g_mutex_unlock (&self->lock);
break;
case PROP_MUTE:
GST_DEBUG_OBJECT (self, "Set mute=%d", g_value_get_boolean (value));
g_object_set_property (G_OBJECT (self->playbin), "mute", value);
break;
case PROP_VIDEO_MULTIVIEW_MODE:
GST_DEBUG_OBJECT (self, "Set multiview mode=%u",
g_value_get_enum (value));
g_object_set_property (G_OBJECT (self->playbin), "video-multiview-mode",
value);
break;
case PROP_VIDEO_MULTIVIEW_FLAGS:
GST_DEBUG_OBJECT (self, "Set multiview flags=%x",
g_value_get_flags (value));
g_object_set_property (G_OBJECT (self->playbin), "video-multiview-flags",
value);
break;
case PROP_AUDIO_VIDEO_OFFSET:
g_object_set_property (G_OBJECT (self->playbin), "av-offset", value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_player_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstPlayer *self = GST_PLAYER (object);
switch (prop_id) {
case PROP_URI:
g_mutex_lock (&self->lock);
g_value_set_string (value, self->uri);
g_mutex_unlock (&self->lock);
break;
case PROP_SUBURI:
g_mutex_lock (&self->lock);
g_value_set_string (value, self->suburi);
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (self, "Returning suburi=%s",
g_value_get_string (value));
break;
case PROP_POSITION:{
gint64 position = 0;
gst_element_query_position (self->playbin, GST_FORMAT_TIME, &position);
g_value_set_uint64 (value, position);
GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT,
GST_TIME_ARGS (g_value_get_uint64 (value)));
break;
}
case PROP_DURATION:{
g_value_set_uint64 (value, self->cached_duration);
GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT,
GST_TIME_ARGS (g_value_get_uint64 (value)));
break;
}
case PROP_MEDIA_INFO:{
GstPlayerMediaInfo *media_info = gst_player_get_media_info (self);
g_value_take_object (value, media_info);
break;
}
case PROP_CURRENT_AUDIO_TRACK:{
GstPlayerAudioInfo *audio_info =
gst_player_get_current_audio_track (self);
g_value_take_object (value, audio_info);
break;
}
case PROP_CURRENT_VIDEO_TRACK:{
GstPlayerVideoInfo *video_info =
gst_player_get_current_video_track (self);
g_value_take_object (value, video_info);
break;
}
case PROP_CURRENT_SUBTITLE_TRACK:{
GstPlayerSubtitleInfo *subtitle_info =
gst_player_get_current_subtitle_track (self);
g_value_take_object (value, subtitle_info);
break;
}
case PROP_VOLUME:
g_object_get_property (G_OBJECT (self->playbin), "volume", value);
GST_TRACE_OBJECT (self, "Returning volume=%lf",
g_value_get_double (value));
break;
case PROP_RATE:
g_mutex_lock (&self->lock);
g_value_set_double (value, self->rate);
g_mutex_unlock (&self->lock);
break;
case PROP_MUTE:
g_object_get_property (G_OBJECT (self->playbin), "mute", value);
GST_TRACE_OBJECT (self, "Returning mute=%d", g_value_get_boolean (value));
break;
case PROP_PIPELINE:
g_value_set_object (value, self->playbin);
break;
case PROP_VIDEO_MULTIVIEW_MODE:{
g_object_get_property (G_OBJECT (self->playbin), "video-multiview-mode",
value);
GST_TRACE_OBJECT (self, "Return multiview mode=%d",
g_value_get_enum (value));
break;
}
case PROP_VIDEO_MULTIVIEW_FLAGS:{
g_object_get_property (G_OBJECT (self->playbin), "video-multiview-flags",
value);
GST_TRACE_OBJECT (self, "Return multiview flags=%x",
g_value_get_flags (value));
break;
}
case PROP_AUDIO_VIDEO_OFFSET:
g_object_get_property (G_OBJECT (self->playbin), "av-offset", value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
main_loop_running_cb (gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GST_TRACE_OBJECT (self, "Main loop running now");
g_mutex_lock (&self->lock);
g_cond_signal (&self->cond);
g_mutex_unlock (&self->lock);
return G_SOURCE_REMOVE;
}
typedef struct
{
GstPlayer *player;
GstPlayerState state;
} StateChangedSignalData;
static void
state_changed_dispatch (gpointer user_data)
{
StateChangedSignalData *data = user_data;
if (data->player->inhibit_sigs && data->state != GST_PLAYER_STATE_STOPPED
&& data->state != GST_PLAYER_STATE_PAUSED)
return;
g_signal_emit (data->player, signals[SIGNAL_STATE_CHANGED], 0, data->state);
}
static void
state_changed_signal_data_free (StateChangedSignalData * data)
{
g_object_unref (data->player);
g_free (data);
}
static void
change_state (GstPlayer * self, GstPlayerState state)
{
if (state == self->app_state)
return;
GST_DEBUG_OBJECT (self, "Changing app state from %s to %s",
gst_player_state_get_name (self->app_state),
gst_player_state_get_name (state));
self->app_state = state;
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_STATE_CHANGED], 0, NULL, NULL, NULL) != 0) {
StateChangedSignalData *data = g_new (StateChangedSignalData, 1);
data->player = g_object_ref (self);
data->state = state;
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
state_changed_dispatch, data,
(GDestroyNotify) state_changed_signal_data_free);
}
}
typedef struct
{
GstPlayer *player;
GstClockTime position;
} PositionUpdatedSignalData;
static void
position_updated_dispatch (gpointer user_data)
{
PositionUpdatedSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
if (data->player->target_state >= GST_STATE_PAUSED) {
g_signal_emit (data->player, signals[SIGNAL_POSITION_UPDATED], 0,
data->position);
g_object_notify_by_pspec (G_OBJECT (data->player),
param_specs[PROP_POSITION]);
}
}
static void
position_updated_signal_data_free (PositionUpdatedSignalData * data)
{
g_object_unref (data->player);
g_free (data);
}
static gboolean
tick_cb (gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
gint64 position;
if (self->target_state >= GST_STATE_PAUSED
&& gst_element_query_position (self->playbin, GST_FORMAT_TIME,
&position)) {
GST_LOG_OBJECT (self, "Position %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_POSITION_UPDATED], 0, NULL, NULL, NULL) != 0) {
PositionUpdatedSignalData *data = g_new (PositionUpdatedSignalData, 1);
data->player = g_object_ref (self);
data->position = position;
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
position_updated_dispatch, data,
(GDestroyNotify) position_updated_signal_data_free);
}
}
return G_SOURCE_CONTINUE;
}
static void
add_tick_source (GstPlayer * self)
{
guint position_update_interval_ms;
if (self->tick_source)
return;
position_update_interval_ms =
gst_player_config_get_position_update_interval (self->config);
if (!position_update_interval_ms)
return;
self->tick_source = g_timeout_source_new (position_update_interval_ms);
g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL);
g_source_attach (self->tick_source, self->context);
}
static void
remove_tick_source (GstPlayer * self)
{
if (!self->tick_source)
return;
g_source_destroy (self->tick_source);
g_source_unref (self->tick_source);
self->tick_source = NULL;
}
static gboolean
ready_timeout_cb (gpointer user_data)
{
GstPlayer *self = user_data;
if (self->target_state <= GST_STATE_READY) {
GST_DEBUG_OBJECT (self, "Setting pipeline to NULL state");
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_NULL;
gst_element_set_state (self->playbin, GST_STATE_NULL);
}
return G_SOURCE_REMOVE;
}
static void
add_ready_timeout_source (GstPlayer * self)
{
if (self->ready_timeout_source)
return;
self->ready_timeout_source = g_timeout_source_new_seconds (60);
g_source_set_callback (self->ready_timeout_source,
(GSourceFunc) ready_timeout_cb, self, NULL);
g_source_attach (self->ready_timeout_source, self->context);
}
static void
remove_ready_timeout_source (GstPlayer * self)
{
if (!self->ready_timeout_source)
return;
g_source_destroy (self->ready_timeout_source);
g_source_unref (self->ready_timeout_source);
self->ready_timeout_source = NULL;
}
typedef struct
{
GstPlayer *player;
GError *err;
} ErrorSignalData;
static void
error_dispatch (gpointer user_data)
{
ErrorSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
g_signal_emit (data->player, signals[SIGNAL_ERROR], 0, data->err);
}
static void
free_error_signal_data (ErrorSignalData * data)
{
g_object_unref (data->player);
g_clear_error (&data->err);
g_free (data);
}
static void
emit_error (GstPlayer * self, GError * err)
{
GST_ERROR_OBJECT (self, "Error: %s (%s, %d)", err->message,
g_quark_to_string (err->domain), err->code);
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_ERROR], 0, NULL, NULL, NULL) != 0) {
ErrorSignalData *data = g_new (ErrorSignalData, 1);
data->player = g_object_ref (self);
data->err = g_error_copy (err);
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
error_dispatch, data, (GDestroyNotify) free_error_signal_data);
}
g_error_free (err);
remove_tick_source (self);
remove_ready_timeout_source (self);
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_NULL;
self->is_live = FALSE;
self->is_eos = FALSE;
gst_element_set_state (self->playbin, GST_STATE_NULL);
change_state (self, GST_PLAYER_STATE_STOPPED);
self->buffering = 100;
g_mutex_lock (&self->lock);
if (self->media_info) {
g_object_unref (self->media_info);
self->media_info = NULL;
}
if (self->global_tags) {
gst_tag_list_unref (self->global_tags);
self->global_tags = NULL;
}
self->seek_pending = FALSE;
remove_seek_source (self);
self->seek_position = GST_CLOCK_TIME_NONE;
self->last_seek_time = GST_CLOCK_TIME_NONE;
g_mutex_unlock (&self->lock);
}
static void
dump_dot_file (GstPlayer * self, const gchar * name)
{
gchar *full_name;
full_name = g_strdup_printf ("gst-player.%p.%s", self, name);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->playbin),
GST_DEBUG_GRAPH_SHOW_ALL, full_name);
g_free (full_name);
}
typedef struct
{
GstPlayer *player;
GError *err;
} WarningSignalData;
static void
warning_dispatch (gpointer user_data)
{
WarningSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
g_signal_emit (data->player, signals[SIGNAL_WARNING], 0, data->err);
}
static void
free_warning_signal_data (WarningSignalData * data)
{
g_object_unref (data->player);
g_clear_error (&data->err);
g_free (data);
}
static void
emit_warning (GstPlayer * self, GError * err)
{
GST_ERROR_OBJECT (self, "Warning: %s (%s, %d)", err->message,
g_quark_to_string (err->domain), err->code);
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_WARNING], 0, NULL, NULL, NULL) != 0) {
WarningSignalData *data = g_new (WarningSignalData, 1);
data->player = g_object_ref (self);
data->err = g_error_copy (err);
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
warning_dispatch, data, (GDestroyNotify) free_warning_signal_data);
}
g_error_free (err);
}
static void
error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GError *err, *player_err;
gchar *name, *debug, *message, *full_message;
dump_dot_file (self, "error");
gst_message_parse_error (msg, &err, &debug);
name = gst_object_get_path_string (msg->src);
message = gst_error_get_message (err->domain, err->code);
if (debug)
full_message =
g_strdup_printf ("Error from element %s: %s\n%s\n%s", name, message,
err->message, debug);
else
full_message =
g_strdup_printf ("Error from element %s: %s\n%s", name, message,
err->message);
GST_ERROR_OBJECT (self, "ERROR: from element %s: %s\n", name, err->message);
if (debug != NULL)
GST_ERROR_OBJECT (self, "Additional debug info:\n%s\n", debug);
player_err =
g_error_new_literal (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
full_message);
emit_error (self, player_err);
g_clear_error (&err);
g_free (debug);
g_free (name);
g_free (full_message);
g_free (message);
}
static void
warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GError *err, *player_err;
gchar *name, *debug, *message, *full_message;
dump_dot_file (self, "warning");
gst_message_parse_warning (msg, &err, &debug);
name = gst_object_get_path_string (msg->src);
message = gst_error_get_message (err->domain, err->code);
if (debug)
full_message =
g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message,
err->message, debug);
else
full_message =
g_strdup_printf ("Warning from element %s: %s\n%s", name, message,
err->message);
GST_WARNING_OBJECT (self, "WARNING: from element %s: %s\n", name,
err->message);
if (debug != NULL)
GST_WARNING_OBJECT (self, "Additional debug info:\n%s\n", debug);
player_err =
g_error_new_literal (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
full_message);
emit_warning (self, player_err);
g_clear_error (&err);
g_free (debug);
g_free (name);
g_free (full_message);
g_free (message);
}
static void
eos_dispatch (gpointer user_data)
{
GstPlayer *player = user_data;
if (player->inhibit_sigs)
return;
g_signal_emit (player, signals[SIGNAL_END_OF_STREAM], 0);
}
static void
eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GST_DEBUG_OBJECT (self, "End of stream");
tick_cb (self);
remove_tick_source (self);
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_END_OF_STREAM], 0, NULL, NULL, NULL) != 0) {
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
eos_dispatch, g_object_ref (self), (GDestroyNotify) g_object_unref);
}
change_state (self, GST_PLAYER_STATE_STOPPED);
self->buffering = 100;
self->is_eos = TRUE;
}
typedef struct
{
GstPlayer *player;
gint percent;
} BufferingSignalData;
static void
buffering_dispatch (gpointer user_data)
{
BufferingSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
if (data->player->target_state >= GST_STATE_PAUSED) {
g_signal_emit (data->player, signals[SIGNAL_BUFFERING], 0, data->percent);
}
}
static void
buffering_signal_data_free (BufferingSignalData * data)
{
g_object_unref (data->player);
g_free (data);
}
static void
buffering_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
gint percent;
if (self->target_state < GST_STATE_PAUSED)
return;
if (self->is_live)
return;
gst_message_parse_buffering (msg, &percent);
GST_LOG_OBJECT (self, "Buffering %d%%", percent);
if (percent < 100 && self->target_state >= GST_STATE_PAUSED) {
GstStateChangeReturn state_ret;
GST_DEBUG_OBJECT (self, "Waiting for buffering to finish");
state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
if (state_ret == GST_STATE_CHANGE_FAILURE) {
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to handle buffering"));
return;
}
change_state (self, GST_PLAYER_STATE_BUFFERING);
}
if (self->buffering != percent) {
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_BUFFERING], 0, NULL, NULL, NULL) != 0) {
BufferingSignalData *data = g_new (BufferingSignalData, 1);
data->player = g_object_ref (self);
data->percent = percent;
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
buffering_dispatch, data,
(GDestroyNotify) buffering_signal_data_free);
}
self->buffering = percent;
}
g_mutex_lock (&self->lock);
if (percent == 100 && (self->seek_position != GST_CLOCK_TIME_NONE ||
self->seek_pending)) {
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (self, "Buffering finished - seek pending");
} else if (percent == 100 && self->target_state >= GST_STATE_PLAYING
&& self->current_state >= GST_STATE_PAUSED) {
GstStateChangeReturn state_ret;
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (self, "Buffering finished - going to PLAYING");
state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
/* Application state change is happening when the state change happened */
if (state_ret == GST_STATE_CHANGE_FAILURE)
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to handle buffering"));
} else if (percent == 100 && self->target_state >= GST_STATE_PAUSED) {
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (self, "Buffering finished - staying PAUSED");
change_state (self, GST_PLAYER_STATE_PAUSED);
} else {
g_mutex_unlock (&self->lock);
}
}
static void
clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstStateChangeReturn state_ret;
GST_DEBUG_OBJECT (self, "Clock lost");
if (self->target_state >= GST_STATE_PLAYING) {
state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
if (state_ret != GST_STATE_CHANGE_FAILURE)
state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
if (state_ret == GST_STATE_CHANGE_FAILURE)
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to handle clock loss"));
}
}
typedef struct
{
GstPlayer *player;
gint width, height;
} VideoDimensionsChangedSignalData;
static void
video_dimensions_changed_dispatch (gpointer user_data)
{
VideoDimensionsChangedSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
if (data->player->target_state >= GST_STATE_PAUSED) {
g_signal_emit (data->player, signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0,
data->width, data->height);
}
}
static void
video_dimensions_changed_signal_data_free (VideoDimensionsChangedSignalData *
data)
{
g_object_unref (data->player);
g_free (data);
}
static void
check_video_dimensions_changed (GstPlayer * self)
{
GstElement *video_sink;
GstPad *video_sink_pad;
GstCaps *caps;
GstVideoInfo info;
gint width = 0, height = 0;
g_object_get (self->playbin, "video-sink", &video_sink, NULL);
if (!video_sink)
goto out;
video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
if (!video_sink_pad) {
gst_object_unref (video_sink);
goto out;
}
caps = gst_pad_get_current_caps (video_sink_pad);
if (caps) {
if (gst_video_info_from_caps (&info, caps)) {
info.width = info.width * info.par_n / info.par_d;
GST_DEBUG_OBJECT (self, "Video dimensions changed: %dx%d", info.width,
info.height);
width = info.width;
height = info.height;
}
gst_caps_unref (caps);
}
gst_object_unref (video_sink_pad);
gst_object_unref (video_sink);
out:
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED], 0, NULL, NULL, NULL) != 0) {
VideoDimensionsChangedSignalData *data =
g_new (VideoDimensionsChangedSignalData, 1);
data->player = g_object_ref (self);
data->width = width;
data->height = height;
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
video_dimensions_changed_dispatch, data,
(GDestroyNotify) video_dimensions_changed_signal_data_free);
}
}
static void
notify_caps_cb (G_GNUC_UNUSED GObject * object,
G_GNUC_UNUSED GParamSpec * pspec, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
check_video_dimensions_changed (self);
}
typedef struct
{
GstPlayer *player;
GstClockTime duration;
} DurationChangedSignalData;
static void
duration_changed_dispatch (gpointer user_data)
{
DurationChangedSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
if (data->player->target_state >= GST_STATE_PAUSED) {
g_signal_emit (data->player, signals[SIGNAL_DURATION_CHANGED], 0,
data->duration);
g_object_notify_by_pspec (G_OBJECT (data->player),
param_specs[PROP_DURATION]);
}
}
static void
duration_changed_signal_data_free (DurationChangedSignalData * data)
{
g_object_unref (data->player);
g_free (data);
}
static void
emit_duration_changed (GstPlayer * self, GstClockTime duration)
{
gboolean updated = FALSE;
if (self->cached_duration == duration)
return;
GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT,
GST_TIME_ARGS (duration));
self->cached_duration = duration;
g_mutex_lock (&self->lock);
if (self->media_info) {
self->media_info->duration = duration;
updated = TRUE;
}
g_mutex_unlock (&self->lock);
if (updated) {
emit_media_info_updated_signal (self);
}
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_DURATION_CHANGED], 0, NULL, NULL, NULL) != 0) {
DurationChangedSignalData *data = g_new (DurationChangedSignalData, 1);
data->player = g_object_ref (self);
data->duration = duration;
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
duration_changed_dispatch, data,
(GDestroyNotify) duration_changed_signal_data_free);
}
}
typedef struct
{
GstPlayer *player;
GstClockTime position;
} SeekDoneSignalData;
static void
seek_done_dispatch (gpointer user_data)
{
SeekDoneSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
g_signal_emit (data->player, signals[SIGNAL_SEEK_DONE], 0, data->position);
}
static void
seek_done_signal_data_free (SeekDoneSignalData * data)
{
g_object_unref (data->player);
g_free (data);
}
static void
emit_seek_done (GstPlayer * self)
{
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_SEEK_DONE], 0, NULL, NULL, NULL) != 0) {
SeekDoneSignalData *data = g_new (SeekDoneSignalData, 1);
data->player = g_object_ref (self);
data->position = gst_player_get_position (self);
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
seek_done_dispatch, data, (GDestroyNotify) seek_done_signal_data_free);
}
}
static void
state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->playbin)) {
gchar *transition_name;
GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state),
gst_element_state_get_name (pending_state));
transition_name = g_strdup_printf ("%s_%s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
dump_dot_file (self, transition_name);
g_free (transition_name);
self->current_state = new_state;
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED
&& pending_state == GST_STATE_VOID_PENDING) {
GstElement *video_sink;
GstPad *video_sink_pad;
gint64 duration = -1;
GST_DEBUG_OBJECT (self, "Initial PAUSED - pre-rolled");
g_mutex_lock (&self->lock);
if (self->media_info)
g_object_unref (self->media_info);
self->media_info = gst_player_media_info_create (self);
g_mutex_unlock (&self->lock);
emit_media_info_updated_signal (self);
g_object_get (self->playbin, "video-sink", &video_sink, NULL);
if (video_sink) {
video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
if (video_sink_pad) {
g_signal_connect (video_sink_pad, "notify::caps",
(GCallback) notify_caps_cb, self);
gst_object_unref (video_sink_pad);
}
gst_object_unref (video_sink);
}
check_video_dimensions_changed (self);
if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME,
&duration)) {
emit_duration_changed (self, duration);
} else {
self->cached_duration = GST_CLOCK_TIME_NONE;
}
}
if (new_state == GST_STATE_PAUSED
&& pending_state == GST_STATE_VOID_PENDING) {
remove_tick_source (self);
g_mutex_lock (&self->lock);
if (self->seek_pending) {
self->seek_pending = FALSE;
if (!self->media_info->seekable) {
GST_DEBUG_OBJECT (self, "Media is not seekable");
remove_seek_source (self);
self->seek_position = GST_CLOCK_TIME_NONE;
self->last_seek_time = GST_CLOCK_TIME_NONE;
} else if (self->seek_source) {
GST_DEBUG_OBJECT (self, "Seek finished but new seek is pending");
gst_player_seek_internal_locked (self);
} else {
GST_DEBUG_OBJECT (self, "Seek finished");
emit_seek_done (self);
}
}
if (self->seek_position != GST_CLOCK_TIME_NONE) {
GST_DEBUG_OBJECT (self, "Seeking now that we reached PAUSED state");
gst_player_seek_internal_locked (self);
g_mutex_unlock (&self->lock);
} else if (!self->seek_pending) {
g_mutex_unlock (&self->lock);
tick_cb (self);
if (self->target_state >= GST_STATE_PLAYING && self->buffering == 100) {
GstStateChangeReturn state_ret;
state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
if (state_ret == GST_STATE_CHANGE_FAILURE)
emit_error (self, g_error_new (GST_PLAYER_ERROR,
GST_PLAYER_ERROR_FAILED, "Failed to play"));
} else if (self->buffering == 100) {
change_state (self, GST_PLAYER_STATE_PAUSED);
}
} else {
g_mutex_unlock (&self->lock);
}
} else if (new_state == GST_STATE_PLAYING
&& pending_state == GST_STATE_VOID_PENDING) {
/* If no seek is currently pending, add the tick source. This can happen
* if we seeked already but the state-change message was still queued up */
if (!self->seek_pending) {
add_tick_source (self);
change_state (self, GST_PLAYER_STATE_PLAYING);
}
} else if (new_state == GST_STATE_READY && old_state > GST_STATE_READY) {
change_state (self, GST_PLAYER_STATE_STOPPED);
} else {
/* Otherwise we neither reached PLAYING nor PAUSED, so must
* wait for something to happen... i.e. are BUFFERING now */
change_state (self, GST_PLAYER_STATE_BUFFERING);
}
}
}
static void
duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
gint64 duration;
if (gst_element_query_duration (self->playbin, GST_FORMAT_TIME, &duration)) {
emit_duration_changed (self, duration);
}
}
static void
latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GST_DEBUG_OBJECT (self, "Latency changed");
gst_bin_recalculate_latency (GST_BIN (self->playbin));
}
static void
request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstState state;
GstStateChangeReturn state_ret;
gst_message_parse_request_state (msg, &state);
GST_DEBUG_OBJECT (self, "State %s requested",
gst_element_state_get_name (state));
self->target_state = state;
state_ret = gst_element_set_state (self->playbin, state);
if (state_ret == GST_STATE_CHANGE_FAILURE)
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to change to requested state %s",
gst_element_state_get_name (state)));
}
static void
media_info_update (GstPlayer * self, GstPlayerMediaInfo * info)
{
g_free (info->title);
info->title = get_from_tags (self, info, get_title);
g_free (info->container);
info->container = get_from_tags (self, info, get_container_format);
if (info->image_sample)
gst_sample_unref (info->image_sample);
info->image_sample = get_from_tags (self, info, get_cover_sample);
GST_DEBUG_OBJECT (self, "title: %s, container: %s "
"image_sample: %p", info->title, info->container, info->image_sample);
}
static void
tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstTagList *tags = NULL;
gst_message_parse_tag (msg, &tags);
GST_DEBUG_OBJECT (self, "received %s tags",
gst_tag_list_get_scope (tags) ==
GST_TAG_SCOPE_GLOBAL ? "global" : "stream");
if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL) {
g_mutex_lock (&self->lock);
if (self->media_info) {
if (self->media_info->tags)
gst_tag_list_unref (self->media_info->tags);
self->media_info->tags = gst_tag_list_ref (tags);
media_info_update (self, self->media_info);
g_mutex_unlock (&self->lock);
emit_media_info_updated_signal (self);
} else {
if (self->global_tags)
gst_tag_list_unref (self->global_tags);
self->global_tags = gst_tag_list_ref (tags);
g_mutex_unlock (&self->lock);
}
}
gst_tag_list_unref (tags);
}
static void
element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
const GstStructure *s;
s = gst_message_get_structure (msg);
if (gst_structure_has_name (s, "redirect")) {
const gchar *new_location;
new_location = gst_structure_get_string (s, "new-location");
if (!new_location) {
const GValue *locations_list, *location_val;
guint i, size;
locations_list = gst_structure_get_value (s, "locations");
size = gst_value_list_get_size (locations_list);
for (i = 0; i < size; ++i) {
const GstStructure *location_s;
location_val = gst_value_list_get_value (locations_list, i);
if (!GST_VALUE_HOLDS_STRUCTURE (location_val))
continue;
location_s = (const GstStructure *) g_value_get_boxed (location_val);
if (!gst_structure_has_name (location_s, "redirect"))
continue;
new_location = gst_structure_get_string (location_s, "new-location");
if (new_location)
break;
}
}
if (new_location) {
GstState target_state;
GST_DEBUG_OBJECT (self, "Redirect to '%s'", new_location);
/* Remember target state and restore after setting the URI */
target_state = self->target_state;
gst_player_stop_internal (self, TRUE);
g_mutex_lock (&self->lock);
g_free (self->redirect_uri);
self->redirect_uri = g_strdup (new_location);
g_object_set (self->playbin, "uri", self->redirect_uri, NULL);
g_mutex_unlock (&self->lock);
if (target_state == GST_STATE_PAUSED)
gst_player_pause_internal (self);
else if (target_state == GST_STATE_PLAYING)
gst_player_play_internal (self);
}
}
}
/* Must be called with lock */
static gboolean
update_stream_collection (GstPlayer * self, GstStreamCollection * collection)
{
if (self->collection && self->collection == collection)
return FALSE;
if (self->collection && self->stream_notify_id)
g_signal_handler_disconnect (self->collection, self->stream_notify_id);
gst_object_replace ((GstObject **) & self->collection,
(GstObject *) collection);
if (self->media_info) {
gst_object_unref (self->media_info);
self->media_info = gst_player_media_info_create (self);
}
self->stream_notify_id =
g_signal_connect (self->collection, "stream-notify",
G_CALLBACK (stream_notify_cb), self);
return TRUE;
}
static void
stream_collection_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstStreamCollection *collection = NULL;
gboolean updated = FALSE;
gst_message_parse_stream_collection (msg, &collection);
if (!collection)
return;
g_mutex_lock (&self->lock);
updated = update_stream_collection (self, collection);
gst_object_unref (collection);
g_mutex_unlock (&self->lock);
if (self->media_info && updated)
emit_media_info_updated_signal (self);
}
static void
streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstStreamCollection *collection = NULL;
gboolean updated = FALSE;
guint i, len;
gst_message_parse_streams_selected (msg, &collection);
if (!collection)
return;
g_mutex_lock (&self->lock);
updated = update_stream_collection (self, collection);
gst_object_unref (collection);
g_free (self->video_sid);
g_free (self->audio_sid);
g_free (self->subtitle_sid);
self->video_sid = NULL;
self->audio_sid = NULL;
self->subtitle_sid = NULL;
len = gst_message_streams_selected_get_size (msg);
for (i = 0; i < len; i++) {
GstStream *stream;
GstStreamType stream_type;
const gchar *stream_id;
gchar **current_sid;
stream = gst_message_streams_selected_get_stream (msg, i);
stream_type = gst_stream_get_stream_type (stream);
stream_id = gst_stream_get_stream_id (stream);
if (stream_type & GST_STREAM_TYPE_AUDIO)
current_sid = &self->audio_sid;
else if (stream_type & GST_STREAM_TYPE_VIDEO)
current_sid = &self->video_sid;
else if (stream_type & GST_STREAM_TYPE_TEXT)
current_sid = &self->subtitle_sid;
else {
GST_WARNING_OBJECT (self,
"Unknown stream-id %s with type 0x%x", stream_id, stream_type);
continue;
}
if (G_UNLIKELY (*current_sid)) {
GST_FIXME_OBJECT (self,
"Multiple streams are selected for type %s, choose the first one",
gst_stream_type_get_name (stream_type));
continue;
}
*current_sid = g_strdup (stream_id);
}
g_mutex_unlock (&self->lock);
if (self->media_info && updated)
emit_media_info_updated_signal (self);
}
static void
player_set_flag (GstPlayer * self, gint pos)
{
gint flags;
g_object_get (self->playbin, "flags", &flags, NULL);
flags |= pos;
g_object_set (self->playbin, "flags", flags, NULL);
GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
}
static void
player_clear_flag (GstPlayer * self, gint pos)
{
gint flags;
g_object_get (self->playbin, "flags", &flags, NULL);
flags &= ~pos;
g_object_set (self->playbin, "flags", flags, NULL);
GST_DEBUG_OBJECT (self, "setting flags=%#x", flags);
}
typedef struct
{
GstPlayer *player;
GstPlayerMediaInfo *info;
} MediaInfoUpdatedSignalData;
static void
media_info_updated_dispatch (gpointer user_data)
{
MediaInfoUpdatedSignalData *data = user_data;
if (data->player->inhibit_sigs)
return;
if (data->player->target_state >= GST_STATE_PAUSED) {
g_signal_emit (data->player, signals[SIGNAL_MEDIA_INFO_UPDATED], 0,
data->info);
}
}
static void
free_media_info_updated_signal_data (MediaInfoUpdatedSignalData * data)
{
g_object_unref (data->player);
g_object_unref (data->info);
g_free (data);
}
/*
* emit_media_info_updated_signal:
*
* create a new copy of self->media_info object and emits the newly created
* copy to user application. The newly created media_info will be unref'ed
* as part of signal finalize method.
*/
static void
emit_media_info_updated_signal (GstPlayer * self)
{
MediaInfoUpdatedSignalData *data = g_new (MediaInfoUpdatedSignalData, 1);
data->player = g_object_ref (self);
g_mutex_lock (&self->lock);
data->info = gst_player_media_info_copy (self->media_info);
g_mutex_unlock (&self->lock);
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
media_info_updated_dispatch, data,
(GDestroyNotify) free_media_info_updated_signal_data);
}
static GstCaps *
get_caps (GstPlayer * self, gint stream_index, GType type)
{
GstPad *pad = NULL;
GstCaps *caps = NULL;
if (type == GST_TYPE_PLAYER_VIDEO_INFO)
g_signal_emit_by_name (G_OBJECT (self->playbin),
"get-video-pad", stream_index, &pad);
else if (type == GST_TYPE_PLAYER_AUDIO_INFO)
g_signal_emit_by_name (G_OBJECT (self->playbin),
"get-audio-pad", stream_index, &pad);
else
g_signal_emit_by_name (G_OBJECT (self->playbin),
"get-text-pad", stream_index, &pad);
if (pad) {
caps = gst_pad_get_current_caps (pad);
gst_object_unref (pad);
}
return caps;
}
static void
gst_player_subtitle_info_update (GstPlayer * self,
GstPlayerStreamInfo * stream_info)
{
GstPlayerSubtitleInfo *info = (GstPlayerSubtitleInfo *) stream_info;
if (stream_info->tags) {
/* free the old language info */
g_free (info->language);
info->language = NULL;
/* First try to get the language full name from tag, if name is not
* available then try language code. If we find the language code
* then use gstreamer api to translate code to full name.
*/
gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
&info->language);
if (!info->language) {
gchar *lang_code = NULL;
gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
&lang_code);
if (lang_code) {
info->language = g_strdup (gst_tag_get_language_name (lang_code));
g_free (lang_code);
}
}
/* If we are still failed to find language name then check if external
* subtitle is loaded and compare the stream index between current sub
* stream index with our stream index and if matches then declare it as
* external subtitle and use the filename.
*/
if (!info->language) {
gint text_index = -1;
gchar *suburi = NULL;
g_object_get (G_OBJECT (self->playbin), "current-suburi", &suburi, NULL);
if (suburi) {
if (self->use_playbin3) {
if (g_str_equal (self->subtitle_sid, stream_info->stream_id))
info->language = g_path_get_basename (suburi);
} else {
g_object_get (G_OBJECT (self->playbin), "current-text", &text_index,
NULL);
if (text_index == gst_player_stream_info_get_index (stream_info))
info->language = g_path_get_basename (suburi);
}
g_free (suburi);
}
}
} else {
g_free (info->language);
info->language = NULL;
}
GST_DEBUG_OBJECT (self, "language=%s", info->language);
}
static void
gst_player_video_info_update (GstPlayer * self,
GstPlayerStreamInfo * stream_info)
{
GstPlayerVideoInfo *info = (GstPlayerVideoInfo *) stream_info;
if (stream_info->caps) {
GstStructure *s;
s = gst_caps_get_structure (stream_info->caps, 0);
if (s) {
gint width, height;
gint fps_n, fps_d;
gint par_n, par_d;
if (gst_structure_get_int (s, "width", &width))
info->width = width;
else
info->width = -1;
if (gst_structure_get_int (s, "height", &height))
info->height = height;
else
info->height = -1;
if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) {
info->framerate_num = fps_n;
info->framerate_denom = fps_d;
} else {
info->framerate_num = 0;
info->framerate_denom = 1;
}
if (gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d)) {
info->par_num = par_n;
info->par_denom = par_d;
} else {
info->par_num = 1;
info->par_denom = 1;
}
}
} else {
info->width = info->height = -1;
info->par_num = info->par_denom = 1;
info->framerate_num = 0;
info->framerate_denom = 1;
}
if (stream_info->tags) {
guint bitrate, max_bitrate;
if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
info->bitrate = bitrate;
else
info->bitrate = -1;
if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
&max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
GST_TAG_NOMINAL_BITRATE, &max_bitrate))
info->max_bitrate = max_bitrate;
else
info->max_bitrate = -1;
} else {
info->bitrate = info->max_bitrate = -1;
}
GST_DEBUG_OBJECT (self, "width=%d height=%d fps=%.2f par=%d:%d "
"bitrate=%d max_bitrate=%d", info->width, info->height,
(gdouble) info->framerate_num / info->framerate_denom,
info->par_num, info->par_denom, info->bitrate, info->max_bitrate);
}
static void
gst_player_audio_info_update (GstPlayer * self,
GstPlayerStreamInfo * stream_info)
{
GstPlayerAudioInfo *info = (GstPlayerAudioInfo *) stream_info;
if (stream_info->caps) {
GstStructure *s;
s = gst_caps_get_structure (stream_info->caps, 0);
if (s) {
gint rate, channels;
if (gst_structure_get_int (s, "rate", &rate))
info->sample_rate = rate;
else
info->sample_rate = -1;
if (gst_structure_get_int (s, "channels", &channels))
info->channels = channels;
else
info->channels = 0;
}
} else {
info->sample_rate = -1;
info->channels = 0;
}
if (stream_info->tags) {
guint bitrate, max_bitrate;
if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_BITRATE, &bitrate))
info->bitrate = bitrate;
else
info->bitrate = -1;
if (gst_tag_list_get_uint (stream_info->tags, GST_TAG_MAXIMUM_BITRATE,
&max_bitrate) || gst_tag_list_get_uint (stream_info->tags,
GST_TAG_NOMINAL_BITRATE, &max_bitrate))
info->max_bitrate = max_bitrate;
else
info->max_bitrate = -1;
/* if we have old language the free it */
g_free (info->language);
info->language = NULL;
/* First try to get the language full name from tag, if name is not
* available then try language code. If we find the language code
* then use gstreamer api to translate code to full name.
*/
gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_NAME,
&info->language);
if (!info->language) {
gchar *lang_code = NULL;
gst_tag_list_get_string (stream_info->tags, GST_TAG_LANGUAGE_CODE,
&lang_code);
if (lang_code) {
info->language = g_strdup (gst_tag_get_language_name (lang_code));
g_free (lang_code);
}
}
} else {
g_free (info->language);
info->language = NULL;
info->max_bitrate = info->bitrate = -1;
}
GST_DEBUG_OBJECT (self, "language=%s rate=%d channels=%d bitrate=%d "
"max_bitrate=%d", info->language, info->sample_rate, info->channels,
info->bitrate, info->bitrate);
}
static GstPlayerStreamInfo *
gst_player_stream_info_find (GstPlayerMediaInfo * media_info,
GType type, gint stream_index)
{
GList *list, *l;
GstPlayerStreamInfo *info = NULL;
if (!media_info)
return NULL;
list = gst_player_media_info_get_stream_list (media_info);
for (l = list; l != NULL; l = l->next) {
info = (GstPlayerStreamInfo *) l->data;
if ((G_OBJECT_TYPE (info) == type) && (info->stream_index == stream_index)) {
return info;
}
}
return NULL;
}
static GstPlayerStreamInfo *
gst_player_stream_info_find_from_stream_id (GstPlayerMediaInfo * media_info,
const gchar * stream_id)
{
GList *list, *l;
GstPlayerStreamInfo *info = NULL;
if (!media_info)
return NULL;
list = gst_player_media_info_get_stream_list (media_info);
for (l = list; l != NULL; l = l->next) {
info = (GstPlayerStreamInfo *) l->data;
if (g_str_equal (info->stream_id, stream_id)) {
return info;
}
}
return NULL;
}
static gboolean
is_track_enabled (GstPlayer * self, gint pos)
{
gint flags;
g_object_get (G_OBJECT (self->playbin), "flags", &flags, NULL);
if ((flags & pos))
return TRUE;
return FALSE;
}
static GstPlayerStreamInfo *
gst_player_stream_info_get_current (GstPlayer * self, const gchar * prop,
GType type)
{
gint current;
GstPlayerStreamInfo *info;
if (!self->media_info)
return NULL;
g_object_get (G_OBJECT (self->playbin), prop, &current, NULL);
g_mutex_lock (&self->lock);
info = gst_player_stream_info_find (self->media_info, type, current);
if (info)
info = gst_player_stream_info_copy (info);
g_mutex_unlock (&self->lock);
return info;
}
static GstPlayerStreamInfo *
gst_player_stream_info_get_current_from_stream_id (GstPlayer * self,
const gchar * stream_id, GType type)
{
GstPlayerStreamInfo *info;
if (!self->media_info || !stream_id)
return NULL;
g_mutex_lock (&self->lock);
info =
gst_player_stream_info_find_from_stream_id (self->media_info, stream_id);
if (info && G_OBJECT_TYPE (info) == type)
info = gst_player_stream_info_copy (info);
else
info = NULL;
g_mutex_unlock (&self->lock);
return info;
}
static void
stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
GParamSpec * pspec, GstPlayer * self)
{
GstPlayerStreamInfo *info;
const gchar *stream_id;
gboolean emit_signal = FALSE;
if (!self->media_info)
return;
if (G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_CAPS &&
G_PARAM_SPEC_VALUE_TYPE (pspec) != GST_TYPE_TAG_LIST)
return;
stream_id = gst_stream_get_stream_id (stream);
g_mutex_lock (&self->lock);
info =
gst_player_stream_info_find_from_stream_id (self->media_info, stream_id);
if (info) {
gst_player_stream_info_update_from_stream (self, info, stream);
emit_signal = TRUE;
}
g_mutex_unlock (&self->lock);
if (emit_signal)
emit_media_info_updated_signal (self);
}
static void
gst_player_stream_info_update (GstPlayer * self, GstPlayerStreamInfo * s)
{
if (GST_IS_PLAYER_VIDEO_INFO (s))
gst_player_video_info_update (self, s);
else if (GST_IS_PLAYER_AUDIO_INFO (s))
gst_player_audio_info_update (self, s);
else
gst_player_subtitle_info_update (self, s);
}
static gchar *
stream_info_get_codec (GstPlayerStreamInfo * s)
{
const gchar *type;
GstTagList *tags;
gchar *codec = NULL;
if (GST_IS_PLAYER_VIDEO_INFO (s))
type = GST_TAG_VIDEO_CODEC;
else if (GST_IS_PLAYER_AUDIO_INFO (s))
type = GST_TAG_AUDIO_CODEC;
else
type = GST_TAG_SUBTITLE_CODEC;
tags = gst_player_stream_info_get_tags (s);
if (tags) {
gst_tag_list_get_string (tags, type, &codec);
if (!codec)
gst_tag_list_get_string (tags, GST_TAG_CODEC, &codec);
}
if (!codec) {
GstCaps *caps;
caps = gst_player_stream_info_get_caps (s);
if (caps) {
codec = gst_pb_utils_get_codec_description (caps);
}
}
return codec;
}
static void
gst_player_stream_info_update_tags_and_caps (GstPlayer * self,
GstPlayerStreamInfo * s)
{
GstTagList *tags;
gint stream_index;
stream_index = gst_player_stream_info_get_index (s);
if (GST_IS_PLAYER_VIDEO_INFO (s))
g_signal_emit_by_name (self->playbin, "get-video-tags",
stream_index, &tags);
else if (GST_IS_PLAYER_AUDIO_INFO (s))
g_signal_emit_by_name (self->playbin, "get-audio-tags",
stream_index, &tags);
else
g_signal_emit_by_name (self->playbin, "get-text-tags", stream_index, &tags);
if (s->tags)
gst_tag_list_unref (s->tags);
s->tags = tags;
if (s->caps)
gst_caps_unref (s->caps);
s->caps = get_caps (self, stream_index, G_OBJECT_TYPE (s));
g_free (s->codec);
s->codec = stream_info_get_codec (s);
GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
gst_player_stream_info_get_stream_type (s), stream_index,
s->tags, s->caps);
gst_player_stream_info_update (self, s);
}
static void
gst_player_streams_info_create (GstPlayer * self,
GstPlayerMediaInfo * media_info, const gchar * prop, GType type)
{
gint i;
gint total = -1;
GstPlayerStreamInfo *s;
if (!media_info)
return;
g_object_get (G_OBJECT (self->playbin), prop, &total, NULL);
GST_DEBUG_OBJECT (self, "%s: %d", prop, total);
for (i = 0; i < total; i++) {
/* check if stream already exist in the list */
s = gst_player_stream_info_find (media_info, type, i);
if (!s) {
/* create a new stream info instance */
s = gst_player_stream_info_new (i, type);
/* add the object in stream list */
media_info->stream_list = g_list_append (media_info->stream_list, s);
/* based on type, add the object in its corresponding stream_ list */
if (GST_IS_PLAYER_AUDIO_INFO (s))
media_info->audio_stream_list = g_list_append
(media_info->audio_stream_list, s);
else if (GST_IS_PLAYER_VIDEO_INFO (s))
media_info->video_stream_list = g_list_append
(media_info->video_stream_list, s);
else
media_info->subtitle_stream_list = g_list_append
(media_info->subtitle_stream_list, s);
GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
gst_player_stream_info_get_stream_type (s), i);
}
gst_player_stream_info_update_tags_and_caps (self, s);
}
}
static void
gst_player_stream_info_update_from_stream (GstPlayer * self,
GstPlayerStreamInfo * s, GstStream * stream)
{
if (s->tags)
gst_tag_list_unref (s->tags);
s->tags = gst_stream_get_tags (stream);
if (s->caps)
gst_caps_unref (s->caps);
s->caps = gst_stream_get_caps (stream);
g_free (s->codec);
s->codec = stream_info_get_codec (s);
GST_DEBUG_OBJECT (self, "%s index: %d tags: %p caps: %p",
gst_player_stream_info_get_stream_type (s), s->stream_index,
s->tags, s->caps);
gst_player_stream_info_update (self, s);
}
static void
gst_player_streams_info_create_from_collection (GstPlayer * self,
GstPlayerMediaInfo * media_info, GstStreamCollection * collection)
{
guint i;
guint total;
GstPlayerStreamInfo *s;
guint n_audio = 0;
guint n_video = 0;
guint n_text = 0;
if (!media_info || !collection)
return;
total = gst_stream_collection_get_size (collection);
for (i = 0; i < total; i++) {
GstStream *stream = gst_stream_collection_get_stream (collection, i);
GstStreamType stream_type = gst_stream_get_stream_type (stream);
const gchar *stream_id = gst_stream_get_stream_id (stream);
if (stream_type & GST_STREAM_TYPE_AUDIO) {
s = gst_player_stream_info_new (n_audio, GST_TYPE_PLAYER_AUDIO_INFO);
n_audio++;
} else if (stream_type & GST_STREAM_TYPE_VIDEO) {
s = gst_player_stream_info_new (n_video, GST_TYPE_PLAYER_VIDEO_INFO);
n_video++;
} else if (stream_type & GST_STREAM_TYPE_TEXT) {
s = gst_player_stream_info_new (n_text, GST_TYPE_PLAYER_SUBTITLE_INFO);
n_text++;
} else {
GST_DEBUG_OBJECT (self, "Unknown type stream %d", i);
continue;
}
s->stream_id = g_strdup (stream_id);
/* add the object in stream list */
media_info->stream_list = g_list_append (media_info->stream_list, s);
/* based on type, add the object in its corresponding stream_ list */
if (GST_IS_PLAYER_AUDIO_INFO (s))
media_info->audio_stream_list = g_list_append
(media_info->audio_stream_list, s);
else if (GST_IS_PLAYER_VIDEO_INFO (s))
media_info->video_stream_list = g_list_append
(media_info->video_stream_list, s);
else
media_info->subtitle_stream_list = g_list_append
(media_info->subtitle_stream_list, s);
GST_DEBUG_OBJECT (self, "create %s stream stream_index: %d",
gst_player_stream_info_get_stream_type (s), s->stream_index);
gst_player_stream_info_update_from_stream (self, s, stream);
}
}
static void
video_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
g_mutex_lock (&self->lock);
gst_player_streams_info_create (self, self->media_info,
"n-video", GST_TYPE_PLAYER_VIDEO_INFO);
g_mutex_unlock (&self->lock);
}
static void
audio_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
g_mutex_lock (&self->lock);
gst_player_streams_info_create (self, self->media_info,
"n-audio", GST_TYPE_PLAYER_AUDIO_INFO);
g_mutex_unlock (&self->lock);
}
static void
subtitle_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
g_mutex_lock (&self->lock);
gst_player_streams_info_create (self, self->media_info,
"n-text", GST_TYPE_PLAYER_SUBTITLE_INFO);
g_mutex_unlock (&self->lock);
}
static void *
get_title (GstTagList * tags)
{
gchar *title = NULL;
gst_tag_list_get_string (tags, GST_TAG_TITLE, &title);
if (!title)
gst_tag_list_get_string (tags, GST_TAG_TITLE_SORTNAME, &title);
return title;
}
static void *
get_container_format (GstTagList * tags)
{
gchar *container = NULL;
gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &container);
/* TODO: If container is not available then maybe consider
* parsing caps or file extension to guess the container format.
*/
return container;
}
static void *
get_from_tags (GstPlayer * self, GstPlayerMediaInfo * media_info,
void *(*func) (GstTagList *))
{
GList *l;
void *ret = NULL;
if (media_info->tags) {
ret = func (media_info->tags);
if (ret)
return ret;
}
/* if global tag does not exit then try video and audio streams */
GST_DEBUG_OBJECT (self, "trying video tags");
for (l = gst_player_media_info_get_video_streams (media_info); l != NULL;
l = l->next) {
GstTagList *tags;
tags = gst_player_stream_info_get_tags ((GstPlayerStreamInfo *) l->data);
if (tags)
ret = func (tags);
if (ret)
return ret;
}
GST_DEBUG_OBJECT (self, "trying audio tags");
for (l = gst_player_media_info_get_audio_streams (media_info); l != NULL;
l = l->next) {
GstTagList *tags;
tags = gst_player_stream_info_get_tags ((GstPlayerStreamInfo *) l->data);
if (tags)
ret = func (tags);
if (ret)
return ret;
}
GST_DEBUG_OBJECT (self, "failed to get the information from tags");
return NULL;
}
static void *
get_cover_sample (GstTagList * tags)
{
GstSample *cover_sample = NULL;
gst_tag_list_get_sample (tags, GST_TAG_IMAGE, &cover_sample);
if (!cover_sample)
gst_tag_list_get_sample (tags, GST_TAG_PREVIEW_IMAGE, &cover_sample);
return cover_sample;
}
static GstPlayerMediaInfo *
gst_player_media_info_create (GstPlayer * self)
{
GstPlayerMediaInfo *media_info;
GstQuery *query;
GST_DEBUG_OBJECT (self, "begin");
media_info = gst_player_media_info_new (self->uri);
media_info->duration = gst_player_get_duration (self);
media_info->tags = self->global_tags;
media_info->is_live = self->is_live;
self->global_tags = NULL;
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (self->playbin, query))
gst_query_parse_seeking (query, NULL, &media_info->seekable, NULL, NULL);
gst_query_unref (query);
if (self->use_playbin3 && self->collection) {
gst_player_streams_info_create_from_collection (self, media_info,
self->collection);
} else {
/* create audio/video/sub streams */
gst_player_streams_info_create (self, media_info, "n-video",
GST_TYPE_PLAYER_VIDEO_INFO);
gst_player_streams_info_create (self, media_info, "n-audio",
GST_TYPE_PLAYER_AUDIO_INFO);
gst_player_streams_info_create (self, media_info, "n-text",
GST_TYPE_PLAYER_SUBTITLE_INFO);
}
media_info->title = get_from_tags (self, media_info, get_title);
media_info->container =
get_from_tags (self, media_info, get_container_format);
media_info->image_sample = get_from_tags (self, media_info, get_cover_sample);
GST_DEBUG_OBJECT (self, "uri: %s title: %s duration: %" GST_TIME_FORMAT
" seekable: %s live: %s container: %s image_sample %p",
media_info->uri, media_info->title, GST_TIME_ARGS (media_info->duration),
media_info->seekable ? "yes" : "no", media_info->is_live ? "yes" : "no",
media_info->container, media_info->image_sample);
GST_DEBUG_OBJECT (self, "end");
return media_info;
}
static void
tags_changed_cb (GstPlayer * self, gint stream_index, GType type)
{
GstPlayerStreamInfo *s;
if (!self->media_info)
return;
/* update the stream information */
g_mutex_lock (&self->lock);
s = gst_player_stream_info_find (self->media_info, type, stream_index);
gst_player_stream_info_update_tags_and_caps (self, s);
g_mutex_unlock (&self->lock);
emit_media_info_updated_signal (self);
}
static void
video_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
gpointer user_data)
{
tags_changed_cb (GST_PLAYER (user_data), stream_index,
GST_TYPE_PLAYER_VIDEO_INFO);
}
static void
audio_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
gpointer user_data)
{
tags_changed_cb (GST_PLAYER (user_data), stream_index,
GST_TYPE_PLAYER_AUDIO_INFO);
}
static void
subtitle_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
gpointer user_data)
{
tags_changed_cb (GST_PLAYER (user_data), stream_index,
GST_TYPE_PLAYER_SUBTITLE_INFO);
}
static void
volume_changed_dispatch (gpointer user_data)
{
GstPlayer *player = user_data;
if (player->inhibit_sigs)
return;
g_signal_emit (player, signals[SIGNAL_VOLUME_CHANGED], 0);
g_object_notify_by_pspec (G_OBJECT (player), param_specs[PROP_VOLUME]);
}
static void
volume_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
GstPlayer * self)
{
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_VOLUME_CHANGED], 0, NULL, NULL, NULL) != 0) {
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
volume_changed_dispatch, g_object_ref (self),
(GDestroyNotify) g_object_unref);
}
}
static void
mute_changed_dispatch (gpointer user_data)
{
GstPlayer *player = user_data;
if (player->inhibit_sigs)
return;
g_signal_emit (player, signals[SIGNAL_MUTE_CHANGED], 0);
g_object_notify_by_pspec (G_OBJECT (player), param_specs[PROP_MUTE]);
}
static void
mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
GstPlayer * self)
{
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_MUTE_CHANGED], 0, NULL, NULL, NULL) != 0) {
gst_player_signal_dispatcher_dispatch (self->signal_dispatcher, self,
mute_changed_dispatch, g_object_ref (self),
(GDestroyNotify) g_object_unref);
}
}
static void
source_setup_cb (GstElement * playbin, GstElement * source, GstPlayer * self)
{
gchar *user_agent;
user_agent = gst_player_config_get_user_agent (self->config);
if (user_agent) {
GParamSpec *prop;
prop = g_object_class_find_property (G_OBJECT_GET_CLASS (source),
"user-agent");
if (prop && prop->value_type == G_TYPE_STRING) {
GST_INFO_OBJECT (self, "Setting source user-agent: %s", user_agent);
g_object_set (source, "user-agent", user_agent, NULL);
}
g_free (user_agent);
}
}
static gpointer
gst_player_main (gpointer data)
{
GstPlayer *self = GST_PLAYER (data);
GstBus *bus;
GSource *source;
GSource *bus_source;
GstElement *scaletempo;
const gchar *env;
GST_TRACE_OBJECT (self, "Starting main thread");
g_main_context_push_thread_default (self->context);
source = g_idle_source_new ();
g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self,
NULL);
g_source_attach (source, self->context);
g_source_unref (source);
env = g_getenv ("GST_PLAYER_USE_PLAYBIN3");
if (env && g_str_has_prefix (env, "1"))
self->use_playbin3 = TRUE;
if (self->use_playbin3) {
GST_DEBUG_OBJECT (self, "playbin3 enabled");
self->playbin = gst_element_factory_make ("playbin3", "playbin3");
} else
self->playbin = gst_element_factory_make ("playbin", "playbin");
if (self->video_renderer) {
GstElement *video_sink =
gst_player_video_renderer_create_video_sink (self->video_renderer,
self);
if (video_sink)
g_object_set (self->playbin, "video-sink", video_sink, NULL);
}
scaletempo = gst_element_factory_make ("scaletempo", NULL);
if (scaletempo) {
g_object_set (self->playbin, "audio-filter", scaletempo, NULL);
} else {
g_warning ("GstPlayer: scaletempo element not available. Audio pitch "
"will not be preserved during trick modes");
}
self->bus = bus = gst_element_get_bus (self->playbin);
bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, self->context);
g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb),
self);
g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb),
self);
g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self);
g_signal_connect (G_OBJECT (bus), "message::state-changed",
G_CALLBACK (state_changed_cb), self);
g_signal_connect (G_OBJECT (bus), "message::buffering",
G_CALLBACK (buffering_cb), self);
g_signal_connect (G_OBJECT (bus), "message::clock-lost",
G_CALLBACK (clock_lost_cb), self);
g_signal_connect (G_OBJECT (bus), "message::duration-changed",
G_CALLBACK (duration_changed_cb), self);
g_signal_connect (G_OBJECT (bus), "message::latency",
G_CALLBACK (latency_cb), self);
g_signal_connect (G_OBJECT (bus), "message::request-state",
G_CALLBACK (request_state_cb), self);
g_signal_connect (G_OBJECT (bus), "message::element",
G_CALLBACK (element_cb), self);
g_signal_connect (G_OBJECT (bus), "message::tag", G_CALLBACK (tags_cb), self);
if (self->use_playbin3) {
g_signal_connect (G_OBJECT (bus), "message::stream-collection",
G_CALLBACK (stream_collection_cb), self);
g_signal_connect (G_OBJECT (bus), "message::streams-selected",
G_CALLBACK (streams_selected_cb), self);
} else {
g_signal_connect (self->playbin, "video-changed",
G_CALLBACK (video_changed_cb), self);
g_signal_connect (self->playbin, "audio-changed",
G_CALLBACK (audio_changed_cb), self);
g_signal_connect (self->playbin, "text-changed",
G_CALLBACK (subtitle_changed_cb), self);
g_signal_connect (self->playbin, "video-tags-changed",
G_CALLBACK (video_tags_changed_cb), self);
g_signal_connect (self->playbin, "audio-tags-changed",
G_CALLBACK (audio_tags_changed_cb), self);
g_signal_connect (self->playbin, "text-tags-changed",
G_CALLBACK (subtitle_tags_changed_cb), self);
}
g_signal_connect (self->playbin, "notify::volume",
G_CALLBACK (volume_notify_cb), self);
g_signal_connect (self->playbin, "notify::mute",
G_CALLBACK (mute_notify_cb), self);
g_signal_connect (self->playbin, "source-setup",
G_CALLBACK (source_setup_cb), self);
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_NULL;
change_state (self, GST_PLAYER_STATE_STOPPED);
self->buffering = 100;
self->is_eos = FALSE;
self->is_live = FALSE;
self->rate = 1.0;
GST_TRACE_OBJECT (self, "Starting main loop");
g_main_loop_run (self->loop);
GST_TRACE_OBJECT (self, "Stopped main loop");
g_source_destroy (bus_source);
g_source_unref (bus_source);
gst_object_unref (bus);
remove_tick_source (self);
remove_ready_timeout_source (self);
g_mutex_lock (&self->lock);
if (self->media_info) {
g_object_unref (self->media_info);
self->media_info = NULL;
}
remove_seek_source (self);
g_mutex_unlock (&self->lock);
g_main_context_pop_thread_default (self->context);
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_NULL;
if (self->playbin) {
gst_element_set_state (self->playbin, GST_STATE_NULL);
gst_object_unref (self->playbin);
self->playbin = NULL;
}
GST_TRACE_OBJECT (self, "Stopped main thread");
return NULL;
}
static gpointer
gst_player_init_once (G_GNUC_UNUSED gpointer user_data)
{
gst_init (NULL, NULL);
GST_DEBUG_CATEGORY_INIT (gst_player_debug, "gst-player", 0, "GstPlayer");
gst_player_error_quark ();
return NULL;
}
/**
* gst_player_new:
* @video_renderer: (transfer full) (allow-none): GstPlayerVideoRenderer to use
* @signal_dispatcher: (transfer full) (allow-none): GstPlayerSignalDispatcher to use
*
* Creates a new #GstPlayer instance that uses @signal_dispatcher to dispatch
* signals to some event loop system, or emits signals directly if NULL is
* passed. See gst_player_g_main_context_signal_dispatcher_new().
*
* Video is going to be rendered by @video_renderer, or if %NULL is provided
* no special video set up will be done and some default handling will be
* performed.
*
* Returns: (transfer full): a new #GstPlayer instance
*/
GstPlayer *
gst_player_new (GstPlayerVideoRenderer * video_renderer,
GstPlayerSignalDispatcher * signal_dispatcher)
{
static GOnce once = G_ONCE_INIT;
GstPlayer *self;
g_once (&once, gst_player_init_once, NULL);
self =
g_object_new (GST_TYPE_PLAYER, "video-renderer", video_renderer,
"signal-dispatcher", signal_dispatcher, NULL);
gst_object_ref_sink (self);
if (video_renderer)
g_object_unref (video_renderer);
if (signal_dispatcher)
g_object_unref (signal_dispatcher);
return self;
}
static gboolean
gst_player_play_internal (gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstStateChangeReturn state_ret;
GST_DEBUG_OBJECT (self, "Play");
g_mutex_lock (&self->lock);
if (!self->uri) {
g_mutex_unlock (&self->lock);
return G_SOURCE_REMOVE;
}
g_mutex_unlock (&self->lock);
remove_ready_timeout_source (self);
self->target_state = GST_STATE_PLAYING;
if (self->current_state < GST_STATE_PAUSED)
change_state (self, GST_PLAYER_STATE_BUFFERING);
if (self->current_state >= GST_STATE_PAUSED && !self->is_eos
&& self->buffering >= 100 && !(self->seek_position != GST_CLOCK_TIME_NONE
|| self->seek_pending)) {
state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
} else {
state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
}
if (state_ret == GST_STATE_CHANGE_FAILURE) {
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to play"));
return G_SOURCE_REMOVE;
} else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
self->is_live = TRUE;
GST_DEBUG_OBJECT (self, "Pipeline is live");
}
if (self->is_eos) {
gboolean ret;
GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
self->is_eos = FALSE;
ret =
gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH, 0);
if (!ret) {
GST_ERROR_OBJECT (self, "Seek to beginning failed");
gst_player_stop_internal (self, TRUE);
gst_player_play_internal (self);
}
}
return G_SOURCE_REMOVE;
}
/**
* gst_player_play:
* @player: #GstPlayer instance
*
* Request to play the loaded stream.
*/
void
gst_player_play (GstPlayer * self)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_mutex_lock (&self->lock);
self->inhibit_sigs = FALSE;
g_mutex_unlock (&self->lock);
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
gst_player_play_internal, self, NULL);
}
static gboolean
gst_player_pause_internal (gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
GstStateChangeReturn state_ret;
GST_DEBUG_OBJECT (self, "Pause");
g_mutex_lock (&self->lock);
if (!self->uri) {
g_mutex_unlock (&self->lock);
return G_SOURCE_REMOVE;
}
g_mutex_unlock (&self->lock);
tick_cb (self);
remove_tick_source (self);
remove_ready_timeout_source (self);
self->target_state = GST_STATE_PAUSED;
if (self->current_state < GST_STATE_PAUSED)
change_state (self, GST_PLAYER_STATE_BUFFERING);
state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
if (state_ret == GST_STATE_CHANGE_FAILURE) {
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to pause"));
return G_SOURCE_REMOVE;
} else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) {
self->is_live = TRUE;
GST_DEBUG_OBJECT (self, "Pipeline is live");
}
if (self->is_eos) {
gboolean ret;
GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
self->is_eos = FALSE;
ret =
gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH, 0);
if (!ret) {
GST_ERROR_OBJECT (self, "Seek to beginning failed");
gst_player_stop_internal (self, TRUE);
gst_player_pause_internal (self);
}
}
return G_SOURCE_REMOVE;
}
/**
* gst_player_pause:
* @player: #GstPlayer instance
*
* Pauses the current stream.
*/
void
gst_player_pause (GstPlayer * self)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_mutex_lock (&self->lock);
self->inhibit_sigs = FALSE;
g_mutex_unlock (&self->lock);
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
gst_player_pause_internal, self, NULL);
}
static void
gst_player_stop_internal (GstPlayer * self, gboolean transient)
{
GST_DEBUG_OBJECT (self, "Stop (transient %d)", transient);
tick_cb (self);
remove_tick_source (self);
add_ready_timeout_source (self);
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_READY;
self->is_live = FALSE;
self->is_eos = FALSE;
gst_bus_set_flushing (self->bus, TRUE);
gst_element_set_state (self->playbin, GST_STATE_READY);
gst_bus_set_flushing (self->bus, FALSE);
change_state (self, transient
&& self->app_state !=
GST_PLAYER_STATE_STOPPED ? GST_PLAYER_STATE_BUFFERING :
GST_PLAYER_STATE_STOPPED);
self->buffering = 100;
self->cached_duration = GST_CLOCK_TIME_NONE;
g_mutex_lock (&self->lock);
if (self->media_info) {
g_object_unref (self->media_info);
self->media_info = NULL;
}
if (self->global_tags) {
gst_tag_list_unref (self->global_tags);
self->global_tags = NULL;
}
self->seek_pending = FALSE;
remove_seek_source (self);
self->seek_position = GST_CLOCK_TIME_NONE;
self->last_seek_time = GST_CLOCK_TIME_NONE;
self->rate = 1.0;
if (self->collection) {
if (self->stream_notify_id)
g_signal_handler_disconnect (self->collection, self->stream_notify_id);
self->stream_notify_id = 0;
gst_object_unref (self->collection);
self->collection = NULL;
}
g_free (self->video_sid);
g_free (self->audio_sid);
g_free (self->subtitle_sid);
self->video_sid = NULL;
self->audio_sid = NULL;
self->subtitle_sid = NULL;
g_mutex_unlock (&self->lock);
}
static gboolean
gst_player_stop_internal_dispatch (gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
gst_player_stop_internal (self, FALSE);
return G_SOURCE_REMOVE;
}
/**
* gst_player_stop:
* @player: #GstPlayer instance
*
* Stops playing the current stream and resets to the first position
* in the stream.
*/
void
gst_player_stop (GstPlayer * self)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_mutex_lock (&self->lock);
self->inhibit_sigs = TRUE;
g_mutex_unlock (&self->lock);
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
gst_player_stop_internal_dispatch, self, NULL);
}
/* Must be called with lock from main context, releases lock! */
static void
gst_player_seek_internal_locked (GstPlayer * self)
{
gboolean ret;
GstClockTime position;
gdouble rate;
GstStateChangeReturn state_ret;
GstEvent *s_event;
GstSeekFlags flags = 0;
gboolean accurate = FALSE;
remove_seek_source (self);
/* Only seek in PAUSED */
if (self->current_state < GST_STATE_PAUSED) {
return;
} else if (self->current_state != GST_STATE_PAUSED) {
g_mutex_unlock (&self->lock);
state_ret = gst_element_set_state (self->playbin, GST_STATE_PAUSED);
if (state_ret == GST_STATE_CHANGE_FAILURE) {
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to seek"));
g_mutex_lock (&self->lock);
return;
}
g_mutex_lock (&self->lock);
return;
}
self->last_seek_time = gst_util_get_timestamp ();
position = self->seek_position;
self->seek_position = GST_CLOCK_TIME_NONE;
self->seek_pending = TRUE;
rate = self->rate;
g_mutex_unlock (&self->lock);
remove_tick_source (self);
self->is_eos = FALSE;
flags |= GST_SEEK_FLAG_FLUSH;
accurate = gst_player_config_get_seek_accurate (self->config);
if (accurate) {
flags |= GST_SEEK_FLAG_ACCURATE;
} else {
flags &= ~GST_SEEK_FLAG_ACCURATE;
}
if (rate != 1.0) {
flags |= GST_SEEK_FLAG_TRICKMODE;
}
if (rate >= 0.0) {
s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_NONE, 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);
}
GST_DEBUG_OBJECT (self, "Seek with rate %.2lf to %" GST_TIME_FORMAT,
rate, GST_TIME_ARGS (position));
ret = gst_element_send_event (self->playbin, s_event);
if (!ret)
emit_error (self, g_error_new (GST_PLAYER_ERROR, GST_PLAYER_ERROR_FAILED,
"Failed to seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (position)));
g_mutex_lock (&self->lock);
}
static gboolean
gst_player_seek_internal (gpointer user_data)
{
GstPlayer *self = GST_PLAYER (user_data);
g_mutex_lock (&self->lock);
gst_player_seek_internal_locked (self);
g_mutex_unlock (&self->lock);
return G_SOURCE_REMOVE;
}
/**
* gst_player_set_rate:
* @player: #GstPlayer instance
* @rate: playback rate
*
* Playback at specified rate
*/
void
gst_player_set_rate (GstPlayer * self, gdouble rate)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_return_if_fail (rate != 0.0);
g_object_set (self, "rate", rate, NULL);
}
/**
* gst_player_get_rate:
* @player: #GstPlayer instance
*
* Returns: current playback rate
*/
gdouble
gst_player_get_rate (GstPlayer * self)
{
gdouble val;
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_RATE);
g_object_get (self, "rate", &val, NULL);
return val;
}
/**
* gst_player_seek:
* @player: #GstPlayer instance
* @position: position to seek in nanoseconds
*
* Seeks the currently-playing stream to the absolute @position time
* in nanoseconds.
*/
void
gst_player_seek (GstPlayer * self, GstClockTime position)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_return_if_fail (GST_CLOCK_TIME_IS_VALID (position));
g_mutex_lock (&self->lock);
if (self->media_info && !self->media_info->seekable) {
GST_DEBUG_OBJECT (self, "Media is not seekable");
g_mutex_unlock (&self->lock);
return;
}
self->seek_position = position;
/* If there is no seek being dispatch to the main context currently do that,
* otherwise we just updated the seek position so that it will be taken by
* the seek handler from the main context instead of the old one.
*/
if (!self->seek_source) {
GstClockTime now = gst_util_get_timestamp ();
/* If no seek is pending or it was started more than 250 mseconds ago seek
* immediately, otherwise wait until the 250 mseconds have passed */
if (!self->seek_pending || (now - self->last_seek_time > 250 * GST_MSECOND)) {
self->seek_source = g_idle_source_new ();
g_source_set_callback (self->seek_source,
(GSourceFunc) gst_player_seek_internal, self, NULL);
GST_TRACE_OBJECT (self, "Dispatching seek to position %" GST_TIME_FORMAT,
GST_TIME_ARGS (position));
g_source_attach (self->seek_source, self->context);
} else {
guint delay = 250000 - (now - self->last_seek_time) / 1000;
/* Note that last_seek_time must be set to something at this point and
* it must be smaller than 250 mseconds */
self->seek_source = g_timeout_source_new (delay);
g_source_set_callback (self->seek_source,
(GSourceFunc) gst_player_seek_internal, self, NULL);
GST_TRACE_OBJECT (self,
"Delaying seek to position %" GST_TIME_FORMAT " by %u us",
GST_TIME_ARGS (position), delay);
g_source_attach (self->seek_source, self->context);
}
}
g_mutex_unlock (&self->lock);
}
static void
remove_seek_source (GstPlayer * self)
{
if (!self->seek_source)
return;
g_source_destroy (self->seek_source);
g_source_unref (self->seek_source);
self->seek_source = NULL;
}
/**
* gst_player_get_uri:
* @player: #GstPlayer instance
*
* Gets the URI of the currently-playing stream.
*
* Returns: (transfer full): a string containing the URI of the
* currently-playing stream. g_free() after usage.
*/
gchar *
gst_player_get_uri (GstPlayer * self)
{
gchar *val;
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_URI);
g_object_get (self, "uri", &val, NULL);
return val;
}
/**
* gst_player_set_uri:
* @player: #GstPlayer instance
* @uri: next URI to play.
*
* Sets the next URI to play.
*/
void
gst_player_set_uri (GstPlayer * self, const gchar * val)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_object_set (self, "uri", val, NULL);
}
/**
* gst_player_set_subtitle_uri:
* @player: #GstPlayer instance
* @uri: subtitle URI
*
* Sets the external subtitle URI. This should be combined with a call to
* gst_player_set_subtitle_track_enabled(@player, TRUE) so the subtitles are actually
* rendered.
*/
void
gst_player_set_subtitle_uri (GstPlayer * self, const gchar * suburi)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_object_set (self, "suburi", suburi, NULL);
}
/**
* gst_player_get_subtitle_uri:
* @player: #GstPlayer instance
*
* current subtitle URI
*
* Returns: (transfer full): URI of the current external subtitle.
* g_free() after usage.
*/
gchar *
gst_player_get_subtitle_uri (GstPlayer * self)
{
gchar *val = NULL;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
g_object_get (self, "suburi", &val, NULL);
return val;
}
/**
* gst_player_get_position:
* @player: #GstPlayer instance
*
* Returns: the absolute position time, in nanoseconds, of the
* currently-playing stream.
*/
GstClockTime
gst_player_get_position (GstPlayer * self)
{
GstClockTime val;
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_POSITION);
g_object_get (self, "position", &val, NULL);
return val;
}
/**
* gst_player_get_duration:
* @player: #GstPlayer instance
*
* Retrieves the duration of the media stream that self represents.
*
* Returns: the duration of the currently-playing media stream, in
* nanoseconds.
*/
GstClockTime
gst_player_get_duration (GstPlayer * self)
{
GstClockTime val;
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_DURATION);
g_object_get (self, "duration", &val, NULL);
return val;
}
/**
* gst_player_get_volume:
* @player: #GstPlayer instance
*
* Returns the current volume level, as a percentage between 0 and 1.
*
* Returns: the volume as percentage between 0 and 1.
*/
gdouble
gst_player_get_volume (GstPlayer * self)
{
gdouble val;
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_VOLUME);
g_object_get (self, "volume", &val, NULL);
return val;
}
/**
* gst_player_set_volume:
* @player: #GstPlayer instance
* @val: the new volume level, as a percentage between 0 and 1
*
* Sets the volume level of the stream as a percentage between 0 and 1.
*/
void
gst_player_set_volume (GstPlayer * self, gdouble val)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_object_set (self, "volume", val, NULL);
}
/**
* gst_player_get_mute:
* @player: #GstPlayer instance
*
* Returns: %TRUE if the currently-playing stream is muted.
*/
gboolean
gst_player_get_mute (GstPlayer * self)
{
gboolean val;
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_MUTE);
g_object_get (self, "mute", &val, NULL);
return val;
}
/**
* gst_player_set_mute:
* @player: #GstPlayer instance
* @val: Mute state the should be set
*
* %TRUE if the currently-playing stream should be muted.
*/
void
gst_player_set_mute (GstPlayer * self, gboolean val)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_object_set (self, "mute", val, NULL);
}
/**
* gst_player_get_pipeline:
* @player: #GstPlayer instance
*
* Returns: (transfer full): The internal playbin instance
*/
GstElement *
gst_player_get_pipeline (GstPlayer * self)
{
GstElement *val;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
g_object_get (self, "pipeline", &val, NULL);
return val;
}
/**
* gst_player_get_media_info:
* @player: #GstPlayer instance
*
* A Function to get the current media info #GstPlayerMediaInfo instance.
*
* Returns: (transfer full): media info instance.
*
* The caller should free it with g_object_unref()
*/
GstPlayerMediaInfo *
gst_player_get_media_info (GstPlayer * self)
{
GstPlayerMediaInfo *info;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
if (!self->media_info)
return NULL;
g_mutex_lock (&self->lock);
info = gst_player_media_info_copy (self->media_info);
g_mutex_unlock (&self->lock);
return info;
}
/**
* gst_player_get_current_audio_track:
* @player: #GstPlayer instance
*
* A Function to get current audio #GstPlayerAudioInfo instance.
*
* Returns: (transfer full): current audio track.
*
* The caller should free it with g_object_unref()
*/
GstPlayerAudioInfo *
gst_player_get_current_audio_track (GstPlayer * self)
{
GstPlayerAudioInfo *info;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
if (!is_track_enabled (self, GST_PLAY_FLAG_AUDIO))
return NULL;
if (self->use_playbin3) {
info = (GstPlayerAudioInfo *)
gst_player_stream_info_get_current_from_stream_id (self,
self->audio_sid, GST_TYPE_PLAYER_AUDIO_INFO);
} else {
info = (GstPlayerAudioInfo *) gst_player_stream_info_get_current (self,
"current-audio", GST_TYPE_PLAYER_AUDIO_INFO);
}
return info;
}
/**
* gst_player_get_current_video_track:
* @player: #GstPlayer instance
*
* A Function to get current video #GstPlayerVideoInfo instance.
*
* Returns: (transfer full): current video track.
*
* The caller should free it with g_object_unref()
*/
GstPlayerVideoInfo *
gst_player_get_current_video_track (GstPlayer * self)
{
GstPlayerVideoInfo *info;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
if (!is_track_enabled (self, GST_PLAY_FLAG_VIDEO))
return NULL;
if (self->use_playbin3) {
info = (GstPlayerVideoInfo *)
gst_player_stream_info_get_current_from_stream_id (self,
self->video_sid, GST_TYPE_PLAYER_VIDEO_INFO);
} else {
info = (GstPlayerVideoInfo *) gst_player_stream_info_get_current (self,
"current-video", GST_TYPE_PLAYER_VIDEO_INFO);
}
return info;
}
/**
* gst_player_get_current_subtitle_track:
* @player: #GstPlayer instance
*
* A Function to get current subtitle #GstPlayerSubtitleInfo instance.
*
* Returns: (transfer none): current subtitle track.
*
* The caller should free it with g_object_unref()
*/
GstPlayerSubtitleInfo *
gst_player_get_current_subtitle_track (GstPlayer * self)
{
GstPlayerSubtitleInfo *info;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
if (!is_track_enabled (self, GST_PLAY_FLAG_SUBTITLE))
return NULL;
if (self->use_playbin3) {
info = (GstPlayerSubtitleInfo *)
gst_player_stream_info_get_current_from_stream_id (self,
self->subtitle_sid, GST_TYPE_PLAYER_SUBTITLE_INFO);
} else {
info = (GstPlayerSubtitleInfo *) gst_player_stream_info_get_current (self,
"current-text", GST_TYPE_PLAYER_SUBTITLE_INFO);
}
return info;
}
/* Must be called with lock */
static gboolean
gst_player_select_streams (GstPlayer * self)
{
GList *stream_list = NULL;
gboolean ret = FALSE;
if (self->audio_sid)
stream_list = g_list_append (stream_list, g_strdup (self->audio_sid));
if (self->video_sid)
stream_list = g_list_append (stream_list, g_strdup (self->video_sid));
if (self->subtitle_sid)
stream_list = g_list_append (stream_list, g_strdup (self->subtitle_sid));
g_mutex_unlock (&self->lock);
if (stream_list) {
ret = gst_element_send_event (self->playbin,
gst_event_new_select_streams (stream_list));
g_list_free_full (stream_list, g_free);
} else {
GST_ERROR_OBJECT (self, "No available streams for select-streams");
}
g_mutex_lock (&self->lock);
return ret;
}
/**
* gst_player_set_audio_track:
* @player: #GstPlayer instance
* @stream_index: stream index
*
* Returns: %TRUE or %FALSE
*
* Sets the audio track @stream_idex.
*/
gboolean
gst_player_set_audio_track (GstPlayer * self, gint stream_index)
{
GstPlayerStreamInfo *info;
gboolean ret = TRUE;
g_return_val_if_fail (GST_IS_PLAYER (self), 0);
g_mutex_lock (&self->lock);
info = gst_player_stream_info_find (self->media_info,
GST_TYPE_PLAYER_AUDIO_INFO, stream_index);
g_mutex_unlock (&self->lock);
if (!info) {
GST_ERROR_OBJECT (self, "invalid audio stream index %d", stream_index);
return FALSE;
}
if (self->use_playbin3) {
g_mutex_lock (&self->lock);
g_free (self->audio_sid);
self->audio_sid = g_strdup (info->stream_id);
ret = gst_player_select_streams (self);
g_mutex_unlock (&self->lock);
} else {
g_object_set (G_OBJECT (self->playbin), "current-audio", stream_index,
NULL);
}
GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
return ret;
}
/**
* gst_player_set_video_track:
* @player: #GstPlayer instance
* @stream_index: stream index
*
* Returns: %TRUE or %FALSE
*
* Sets the video track @stream_index.
*/
gboolean
gst_player_set_video_track (GstPlayer * self, gint stream_index)
{
GstPlayerStreamInfo *info;
gboolean ret = TRUE;
g_return_val_if_fail (GST_IS_PLAYER (self), 0);
/* check if stream_index exist in our internal media_info list */
g_mutex_lock (&self->lock);
info = gst_player_stream_info_find (self->media_info,
GST_TYPE_PLAYER_VIDEO_INFO, stream_index);
g_mutex_unlock (&self->lock);
if (!info) {
GST_ERROR_OBJECT (self, "invalid video stream index %d", stream_index);
return FALSE;
}
if (self->use_playbin3) {
g_mutex_lock (&self->lock);
g_free (self->video_sid);
self->video_sid = g_strdup (info->stream_id);
ret = gst_player_select_streams (self);
g_mutex_unlock (&self->lock);
} else {
g_object_set (G_OBJECT (self->playbin), "current-video", stream_index,
NULL);
}
GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
return ret;
}
/**
* gst_player_set_subtitle_track:
* @player: #GstPlayer instance
* @stream_index: stream index
*
* Returns: %TRUE or %FALSE
*
* Sets the subtitle strack @stream_index.
*/
gboolean
gst_player_set_subtitle_track (GstPlayer * self, gint stream_index)
{
GstPlayerStreamInfo *info;
gboolean ret = TRUE;
g_return_val_if_fail (GST_IS_PLAYER (self), 0);
g_mutex_lock (&self->lock);
info = gst_player_stream_info_find (self->media_info,
GST_TYPE_PLAYER_SUBTITLE_INFO, stream_index);
g_mutex_unlock (&self->lock);
if (!info) {
GST_ERROR_OBJECT (self, "invalid subtitle stream index %d", stream_index);
return FALSE;
}
if (self->use_playbin3) {
g_mutex_lock (&self->lock);
g_free (self->subtitle_sid);
self->subtitle_sid = g_strdup (info->stream_id);
ret = gst_player_select_streams (self);
g_mutex_unlock (&self->lock);
} else {
g_object_set (G_OBJECT (self->playbin), "current-text", stream_index, NULL);
}
GST_DEBUG_OBJECT (self, "set stream index '%d'", stream_index);
return ret;
}
/**
* gst_player_set_audio_track_enabled:
* @player: #GstPlayer instance
* @enabled: TRUE or FALSE
*
* Enable or disable the current audio track.
*/
void
gst_player_set_audio_track_enabled (GstPlayer * self, gboolean enabled)
{
g_return_if_fail (GST_IS_PLAYER (self));
if (enabled)
player_set_flag (self, GST_PLAY_FLAG_AUDIO);
else
player_clear_flag (self, GST_PLAY_FLAG_AUDIO);
GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
}
/**
* gst_player_set_video_track_enabled:
* @player: #GstPlayer instance
* @enabled: TRUE or FALSE
*
* Enable or disable the current video track.
*/
void
gst_player_set_video_track_enabled (GstPlayer * self, gboolean enabled)
{
g_return_if_fail (GST_IS_PLAYER (self));
if (enabled)
player_set_flag (self, GST_PLAY_FLAG_VIDEO);
else
player_clear_flag (self, GST_PLAY_FLAG_VIDEO);
GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
}
/**
* gst_player_set_subtitle_track_enabled:
* @player: #GstPlayer instance
* @enabled: TRUE or FALSE
*
* Enable or disable the current subtitle track.
*/
void
gst_player_set_subtitle_track_enabled (GstPlayer * self, gboolean enabled)
{
g_return_if_fail (GST_IS_PLAYER (self));
if (enabled)
player_set_flag (self, GST_PLAY_FLAG_SUBTITLE);
else
player_clear_flag (self, GST_PLAY_FLAG_SUBTITLE);
GST_DEBUG_OBJECT (self, "track is '%s'", enabled ? "Enabled" : "Disabled");
}
/**
* gst_player_set_visualization:
* @player: #GstPlayer instance
* @name: visualization element obtained from
* #gst_player_visualizations_get()
*
* Returns: %TRUE if the visualizations was set correctly. Otherwise,
* %FALSE.
*/
gboolean
gst_player_set_visualization (GstPlayer * self, const gchar * name)
{
g_return_val_if_fail (GST_IS_PLAYER (self), FALSE);
g_mutex_lock (&self->lock);
if (self->current_vis_element) {
gst_object_unref (self->current_vis_element);
self->current_vis_element = NULL;
}
if (name) {
self->current_vis_element = gst_element_factory_make (name, NULL);
if (!self->current_vis_element)
goto error_no_element;
gst_object_ref_sink (self->current_vis_element);
}
g_object_set (self->playbin, "vis-plugin", self->current_vis_element, NULL);
g_mutex_unlock (&self->lock);
GST_DEBUG_OBJECT (self, "set vis-plugin to '%s'", name);
return TRUE;
error_no_element:
g_mutex_unlock (&self->lock);
GST_WARNING_OBJECT (self, "could not find visualization '%s'", name);
return FALSE;
}
/**
* gst_player_get_current_visualization:
* @player: #GstPlayer instance
*
* Returns: (transfer full): Name of the currently enabled visualization.
* g_free() after usage.
*/
gchar *
gst_player_get_current_visualization (GstPlayer * self)
{
gchar *name = NULL;
GstElement *vis_plugin = NULL;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
if (!is_track_enabled (self, GST_PLAY_FLAG_VIS))
return NULL;
g_object_get (self->playbin, "vis-plugin", &vis_plugin, NULL);
if (vis_plugin) {
GstElementFactory *factory = gst_element_get_factory (vis_plugin);
if (factory)
name = g_strdup (gst_plugin_feature_get_name (factory));
gst_object_unref (vis_plugin);
}
GST_DEBUG_OBJECT (self, "vis-plugin '%s' %p", name, vis_plugin);
return name;
}
/**
* gst_player_set_visualization_enabled:
* @player: #GstPlayer instance
* @enabled: TRUE or FALSE
*
* Enable or disable the visualization.
*/
void
gst_player_set_visualization_enabled (GstPlayer * self, gboolean enabled)
{
g_return_if_fail (GST_IS_PLAYER (self));
if (enabled)
player_set_flag (self, GST_PLAY_FLAG_VIS);
else
player_clear_flag (self, GST_PLAY_FLAG_VIS);
GST_DEBUG_OBJECT (self, "visualization is '%s'",
enabled ? "Enabled" : "Disabled");
}
struct CBChannelMap
{
const gchar *label; /* channel label name */
const gchar *name; /* get_name () */
};
static const struct CBChannelMap cb_channel_map[] = {
/* GST_PLAYER_COLOR_BALANCE_BRIGHTNESS */ {"BRIGHTNESS", "brightness"},
/* GST_PLAYER_COLOR_BALANCE_CONTRAST */ {"CONTRAST", "contrast"},
/* GST_PLAYER_COLOR_BALANCE_SATURATION */ {"SATURATION", "saturation"},
/* GST_PLAYER_COLOR_BALANCE_HUE */ {"HUE", "hue"},
};
static GstColorBalanceChannel *
gst_player_color_balance_find_channel (GstPlayer * self,
GstPlayerColorBalanceType type)
{
GstColorBalanceChannel *channel;
const GList *l, *channels;
if (type < GST_PLAYER_COLOR_BALANCE_BRIGHTNESS ||
type > GST_PLAYER_COLOR_BALANCE_HUE)
return NULL;
channels =
gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
for (l = channels; l; l = l->next) {
channel = l->data;
if (g_strrstr (channel->label, cb_channel_map[type].label))
return channel;
}
return NULL;
}
/**
* gst_player_has_color_balance:
* @player:#GstPlayer instance
*
* Checks whether the @player has color balance support available.
*
* Returns: %TRUE if @player has color balance support. Otherwise,
* %FALSE.
*/
gboolean
gst_player_has_color_balance (GstPlayer * self)
{
const GList *channels;
g_return_val_if_fail (GST_IS_PLAYER (self), FALSE);
if (!GST_IS_COLOR_BALANCE (self->playbin))
return FALSE;
channels =
gst_color_balance_list_channels (GST_COLOR_BALANCE (self->playbin));
return (channels != NULL);
}
/**
* gst_player_set_color_balance:
* @player: #GstPlayer instance
* @type: #GstPlayerColorBalanceType
* @value: The new value for the @type, ranged [0,1]
*
* Sets the current value of the indicated channel @type to the passed
* value.
*/
void
gst_player_set_color_balance (GstPlayer * self, GstPlayerColorBalanceType type,
gdouble value)
{
GstColorBalanceChannel *channel;
gdouble new_val;
g_return_if_fail (GST_IS_PLAYER (self));
g_return_if_fail (value >= 0.0 && value <= 1.0);
if (!GST_IS_COLOR_BALANCE (self->playbin))
return;
channel = gst_player_color_balance_find_channel (self, type);
if (!channel)
return;
value = CLAMP (value, 0.0, 1.0);
/* Convert to channel range */
new_val = channel->min_value + value * ((gdouble) channel->max_value -
(gdouble) channel->min_value);
gst_color_balance_set_value (GST_COLOR_BALANCE (self->playbin), channel,
new_val);
}
/**
* gst_player_get_color_balance:
* @player: #GstPlayer instance
* @type: #GstPlayerColorBalanceType
*
* Retrieve the current value of the indicated @type.
*
* Returns: The current value of @type, between [0,1]. In case of
* error -1 is returned.
*/
gdouble
gst_player_get_color_balance (GstPlayer * self, GstPlayerColorBalanceType type)
{
GstColorBalanceChannel *channel;
gint value;
g_return_val_if_fail (GST_IS_PLAYER (self), -1);
if (!GST_IS_COLOR_BALANCE (self->playbin))
return -1;
channel = gst_player_color_balance_find_channel (self, type);
if (!channel)
return -1;
value = gst_color_balance_get_value (GST_COLOR_BALANCE (self->playbin),
channel);
return ((gdouble) value -
(gdouble) channel->min_value) / ((gdouble) channel->max_value -
(gdouble) channel->min_value);
}
/**
* gst_player_get_multiview_mode:
* @player: #GstPlayer instance
*
* Retrieve the current value of the indicated @type.
*
* Returns: The current value of @type, Default: -1 "none"
*
* Since: 1.10
*/
GstVideoMultiviewFramePacking
gst_player_get_multiview_mode (GstPlayer * self)
{
GstVideoMultiviewFramePacking val = GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE;
g_return_val_if_fail (GST_IS_PLAYER (self),
GST_VIDEO_MULTIVIEW_FRAME_PACKING_NONE);
g_object_get (self, "video-multiview-mode", &val, NULL);
return val;
}
/**
* gst_player_set_multiview_mode:
* @player: #GstPlayer instance
* @mode: The new value for the @type
*
* Sets the current value of the indicated mode @type to the passed
* value.
*
* Since: 1.10
*/
void
gst_player_set_multiview_mode (GstPlayer * self,
GstVideoMultiviewFramePacking mode)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_object_set (self, "video-multiview-mode", mode, NULL);
}
/**
* gst_player_get_multiview_flags:
* @player: #GstPlayer instance
*
* Retrieve the current value of the indicated @type.
*
* Returns: The current value of @type, Default: 0x00000000 "none
*
* Since: 1.10
*/
GstVideoMultiviewFlags
gst_player_get_multiview_flags (GstPlayer * self)
{
GstVideoMultiviewFlags val = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
g_return_val_if_fail (GST_IS_PLAYER (self), val);
g_object_get (self, "video-multiview-flags", &val, NULL);
return val;
}
/**
* gst_player_set_multiview_flags:
* @player: #GstPlayer instance
* @flags: The new value for the @type
*
* Sets the current value of the indicated mode @type to the passed
* value.
*
* Since: 1.10
*/
void
gst_player_set_multiview_flags (GstPlayer * self, GstVideoMultiviewFlags flags)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_object_set (self, "video-multiview-flags", flags, NULL);
}
/**
* gst_player_get_audio_video_offset:
* @player: #GstPlayer instance
*
* Retrieve the current value of audio-video-offset property
*
* Returns: The current value of audio-video-offset in nanoseconds
*
* Since 1.10
*/
gint64
gst_player_get_audio_video_offset (GstPlayer * self)
{
gint64 val = 0;
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_AUDIO_VIDEO_OFFSET);
g_object_get (self, "audio-video-offset", &val, NULL);
return val;
}
/**
* gst_player_set_audio_video_offset:
* @player: #GstPlayer instance
* @offset: #gint64 in nanoseconds
*
* Sets audio-video-offset property by value of @offset
*
* Since 1.10
*/
void
gst_player_set_audio_video_offset (GstPlayer * self, gint64 offset)
{
g_return_if_fail (GST_IS_PLAYER (self));
g_object_set (self, "audio-video-offset", offset, NULL);
}
#define C_ENUM(v) ((gint) v)
#define C_FLAGS(v) ((guint) v)
GType
gst_player_color_balance_type_get_type (void)
{
static gsize id = 0;
static const GEnumValue values[] = {
{C_ENUM (GST_PLAYER_COLOR_BALANCE_HUE), "GST_PLAYER_COLOR_BALANCE_HUE",
"hue"},
{C_ENUM (GST_PLAYER_COLOR_BALANCE_BRIGHTNESS),
"GST_PLAYER_COLOR_BALANCE_BRIGHTNESS", "brightness"},
{C_ENUM (GST_PLAYER_COLOR_BALANCE_SATURATION),
"GST_PLAYER_COLOR_BALANCE_SATURATION", "saturation"},
{C_ENUM (GST_PLAYER_COLOR_BALANCE_CONTRAST),
"GST_PLAYER_COLOR_BALANCE_CONTRAST", "contrast"},
{0, NULL, NULL}
};
if (g_once_init_enter (&id)) {
GType tmp = g_enum_register_static ("GstPlayerColorBalanceType", values);
g_once_init_leave (&id, tmp);
}
return (GType) id;
}
/**
* gst_player_color_balance_type_get_name:
* @type: a #GstPlayerColorBalanceType
*
* Gets a string representing the given color balance type.
*
* Returns: (transfer none): a string with the name of the color
* balance type.
*/
const gchar *
gst_player_color_balance_type_get_name (GstPlayerColorBalanceType type)
{
g_return_val_if_fail (type >= GST_PLAYER_COLOR_BALANCE_BRIGHTNESS &&
type <= GST_PLAYER_COLOR_BALANCE_HUE, NULL);
return cb_channel_map[type].name;
}
GType
gst_player_state_get_type (void)
{
static gsize id = 0;
static const GEnumValue values[] = {
{C_ENUM (GST_PLAYER_STATE_STOPPED), "GST_PLAYER_STATE_STOPPED", "stopped"},
{C_ENUM (GST_PLAYER_STATE_BUFFERING), "GST_PLAYER_STATE_BUFFERING",
"buffering"},
{C_ENUM (GST_PLAYER_STATE_PAUSED), "GST_PLAYER_STATE_PAUSED", "paused"},
{C_ENUM (GST_PLAYER_STATE_PLAYING), "GST_PLAYER_STATE_PLAYING", "playing"},
{0, NULL, NULL}
};
if (g_once_init_enter (&id)) {
GType tmp = g_enum_register_static ("GstPlayerState", values);
g_once_init_leave (&id, tmp);
}
return (GType) id;
}
/**
* gst_player_state_get_name:
* @state: a #GstPlayerState
*
* Gets a string representing the given state.
*
* Returns: (transfer none): a string with the name of the state.
*/
const gchar *
gst_player_state_get_name (GstPlayerState state)
{
switch (state) {
case GST_PLAYER_STATE_STOPPED:
return "stopped";
case GST_PLAYER_STATE_BUFFERING:
return "buffering";
case GST_PLAYER_STATE_PAUSED:
return "paused";
case GST_PLAYER_STATE_PLAYING:
return "playing";
}
g_assert_not_reached ();
return NULL;
}
GType
gst_player_error_get_type (void)
{
static gsize id = 0;
static const GEnumValue values[] = {
{C_ENUM (GST_PLAYER_ERROR_FAILED), "GST_PLAYER_ERROR_FAILED", "failed"},
{0, NULL, NULL}
};
if (g_once_init_enter (&id)) {
GType tmp = g_enum_register_static ("GstPlayerError", values);
g_once_init_leave (&id, tmp);
}
return (GType) id;
}
/**
* gst_player_error_get_name:
* @error: a #GstPlayerError
*
* Gets a string representing the given error.
*
* Returns: (transfer none): a string with the given error.
*/
const gchar *
gst_player_error_get_name (GstPlayerError error)
{
switch (error) {
case GST_PLAYER_ERROR_FAILED:
return "failed";
}
g_assert_not_reached ();
return NULL;
}
/**
* gst_player_set_config:
* @player: #GstPlayer instance
* @config: (transfer full): a #GstStructure
*
* Set the configuration of the player. If the player is already configured, and
* the configuration haven't change, this function will return %TRUE. If the
* player is not in the GST_PLAYER_STATE_STOPPED, this method will return %FALSE
* and active configuration will remain.
*
* @config is a #GstStructure that contains the configuration parameters for
* the player.
*
* This function takes ownership of @config.
*
* Returns: %TRUE when the configuration could be set.
* Since 1.10
*/
gboolean
gst_player_set_config (GstPlayer * self, GstStructure * config)
{
g_return_val_if_fail (GST_IS_PLAYER (self), FALSE);
g_return_val_if_fail (config != NULL, FALSE);
g_mutex_lock (&self->lock);
if (self->app_state != GST_PLAYER_STATE_STOPPED) {
GST_INFO_OBJECT (self, "can't change config while player is %s",
gst_player_state_get_name (self->app_state));
g_mutex_unlock (&self->lock);
return FALSE;
}
if (self->config)
gst_structure_free (self->config);
self->config = config;
g_mutex_unlock (&self->lock);
return TRUE;
}
/**
* gst_player_get_config:
* @player: #GstPlayer instance
*
* Get a copy of the current configuration of the player. This configuration
* can either be modified and used for the gst_player_set_config() call
* or it must be freed after usage.
*
* Returns: (transfer full): a copy of the current configuration of @player. Use
* gst_structure_free() after usage or gst_player_set_config().
*
* Since 1.10
*/
GstStructure *
gst_player_get_config (GstPlayer * self)
{
GstStructure *ret;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
g_mutex_lock (&self->lock);
ret = gst_structure_copy (self->config);
g_mutex_unlock (&self->lock);
return ret;
}
/**
* gst_player_config_set_user_agent:
* @config: a #GstPlayer configuration
* @agent: the string to use as user agent
*
* Set the user agent to pass to the server if @player needs to connect
* to a server during playback. This is typically used when playing HTTP
* or RTSP streams.
*
* Since 1.10
*/
void
gst_player_config_set_user_agent (GstStructure * config, const gchar * agent)
{
g_return_if_fail (config != NULL);
g_return_if_fail (agent != NULL);
gst_structure_id_set (config,
CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, agent, NULL);
}
/**
* gst_player_config_get_user_agent:
* @config: a #GstPlayer configuration
*
* Return the user agent which has been configured using
* gst_player_config_set_user_agent() if any.
*
* Returns: (transfer full): the configured agent, or %NULL
* Since 1.10
*/
gchar *
gst_player_config_get_user_agent (const GstStructure * config)
{
gchar *agent = NULL;
g_return_val_if_fail (config != NULL, NULL);
gst_structure_id_get (config,
CONFIG_QUARK (USER_AGENT), G_TYPE_STRING, &agent, NULL);
return agent;
}
/**
* gst_player_config_set_position_update_interval:
* @config: a #GstPlayer configuration
* @interval: interval in ms
*
* set interval in milliseconds between two position-updated signals.
* pass 0 to stop updating the position.
* Since 1.10
*/
void
gst_player_config_set_position_update_interval (GstStructure * config,
guint interval)
{
g_return_if_fail (config != NULL);
g_return_if_fail (interval <= 10000);
gst_structure_id_set (config,
CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, interval, NULL);
}
/**
* gst_player_config_get_position_update_interval:
* @config: a #GstPlayer configuration
*
* Returns: current position update interval in milliseconds
*
* Since 1.10
*/
guint
gst_player_config_get_position_update_interval (const GstStructure * config)
{
guint interval = DEFAULT_POSITION_UPDATE_INTERVAL_MS;
g_return_val_if_fail (config != NULL, DEFAULT_POSITION_UPDATE_INTERVAL_MS);
gst_structure_id_get (config,
CONFIG_QUARK (POSITION_INTERVAL_UPDATE), G_TYPE_UINT, &interval, NULL);
return interval;
}
/**
* gst_player_config_set_seek_accurate:
* @config: a #GstPlayer configuration
* @accurate: accurate seek or not
*
* Enable or disable accurate seeking. When enabled, elements will try harder
* to seek as accurately as possible to the requested seek position. Generally
* it will be slower especially for formats that don't have any indexes or
* timestamp markers in the stream.
*
* If accurate seeking is disabled, elements will seek as close as the request
* position without slowing down seeking too much.
*
* Accurate seeking is disabled by default.
*
* Since: 1.12
*/
void
gst_player_config_set_seek_accurate (GstStructure * config, gboolean accurate)
{
g_return_if_fail (config != NULL);
gst_structure_id_set (config,
CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, accurate, NULL);
}
/**
* gst_player_config_get_seek_accurate:
* @config: a #GstPlayer configuration
*
* Returns: %TRUE if accurate seeking is enabled
*
* Since 1.12
*/
gboolean
gst_player_config_get_seek_accurate (const GstStructure * config)
{
gboolean accurate = FALSE;
g_return_val_if_fail (config != NULL, FALSE);
gst_structure_id_get (config,
CONFIG_QUARK (ACCURATE_SEEK), G_TYPE_BOOLEAN, &accurate, NULL);
return accurate;
}
/**
* gst_player_get_video_snapshot:
* @player: #GstPlayer instance
* @format: output format of the video snapshot
* @config: (allow-none): Additional configuration
*
* Get a snapshot of the currently selected video stream, if any. The format can be
* selected with @format and optional configuration is possible with @config
* Currently supported settings are:
* - width, height of type G_TYPE_INT
* - pixel-aspect-ratio of type GST_TYPE_FRACTION
* Except for GST_PLAYER_THUMBNAIL_RAW_NATIVE format, if no config is set, pixel-aspect-ratio would be 1/1
*
* Returns: (transfer full): Current video snapshot sample or %NULL on failure
*
* Since 1.12
*/
GstSample *
gst_player_get_video_snapshot (GstPlayer * self,
GstPlayerSnapshotFormat format, const GstStructure * config)
{
gint video_tracks = 0;
GstSample *sample = NULL;
GstCaps *caps = NULL;
gint width = -1;
gint height = -1;
gint par_n = 1;
gint par_d = 1;
g_return_val_if_fail (GST_IS_PLAYER (self), NULL);
g_object_get (self->playbin, "n-video", &video_tracks, NULL);
if (video_tracks == 0) {
GST_DEBUG_OBJECT (self, "total video track num is 0");
return NULL;
}
switch (format) {
case GST_PLAYER_THUMBNAIL_RAW_xRGB:
caps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "xRGB", NULL);
break;
case GST_PLAYER_THUMBNAIL_RAW_BGRx:
caps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "BGRx", NULL);
break;
case GST_PLAYER_THUMBNAIL_JPG:
caps = gst_caps_new_empty_simple ("image/jpeg");
break;
case GST_PLAYER_THUMBNAIL_PNG:
caps = gst_caps_new_empty_simple ("image/png");
break;
case GST_PLAYER_THUMBNAIL_RAW_NATIVE:
default:
caps = gst_caps_new_empty_simple ("video/x-raw");
break;
}
if (NULL != config) {
if (!gst_structure_get_int (config, "width", &width))
width = -1;
if (!gst_structure_get_int (config, "height", &height))
height = -1;
if (!gst_structure_get_fraction (config, "pixel-aspect-ratio", &par_n,
&par_d)) {
if (format != GST_PLAYER_THUMBNAIL_RAW_NATIVE) {
par_n = 1;
par_d = 1;
} else {
par_n = 0;
par_d = 0;
}
}
}
if (width > 0 && height > 0) {
gst_caps_set_simple (caps, "width", G_TYPE_INT, width,
"height", G_TYPE_INT, height, NULL);
}
if (format != GST_PLAYER_THUMBNAIL_RAW_NATIVE) {
gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
par_n, par_d, NULL);
} else if (NULL != config && par_n != 0 && par_d != 0) {
gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
par_n, par_d, NULL);
}
g_signal_emit_by_name (self->playbin, "convert-sample", caps, &sample);
gst_caps_unref (caps);
if (!sample) {
GST_WARNING_OBJECT (self, "Failed to retrieve or convert video frame");
return NULL;
}
return sample;
}