mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-20 15:27:07 +00:00
c881e0a505
Fix gst_event_new_seek call in gst-libs/gst/player/gstplayer.c If rate >= 0.0, then previous code doesn't set end of segment. So, the end of segment will be in place where previous seek put it. This is not neccesary end of media file (in case of reverse playback). So if we play video backward for some time and then switched to forward playing, we will get EOS somewhere in the middle of media file. This commit always sets end of segment, thus fixing this bug
4834 lines
131 KiB
C
4834 lines
131 KiB
C
/* 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
|
|
* @symbols:
|
|
* - GstPlayer
|
|
*
|
|
*/
|
|
|
|
/* 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
|
|
#define DEFAULT_SUBTITLE_VIDEO_OFFSET 0
|
|
|
|
/**
|
|
* gst_player_error_quark:
|
|
*/
|
|
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_SUBTITLE_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);
|
|
|
|
param_specs[PROP_SUBTITLE_VIDEO_OFFSET] =
|
|
g_param_spec_int64 ("subtitle-video-offset", "Text Video Offset",
|
|
"The synchronisation offset between text 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_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;
|
|
case PROP_SUBTITLE_VIDEO_OFFSET:
|
|
g_object_set_property (G_OBJECT (self->playbin), "text-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 = GST_CLOCK_TIME_NONE;
|
|
|
|
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;
|
|
case PROP_SUBTITLE_VIDEO_OFFSET:
|
|
g_object_get_property (G_OBJECT (self->playbin), "text-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 = GST_CLOCK_TIME_NONE;
|
|
|
|
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->max_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, ¤t, 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->playbin) {
|
|
g_error ("GstPlayer: 'playbin' element not found, please check your setup");
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* directly return if we're already stopped */
|
|
if (self->current_state <= GST_STATE_READY &&
|
|
self->target_state <= GST_STATE_READY)
|
|
return;
|
|
|
|
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_SET, GST_CLOCK_TIME_NONE);
|
|
} else {
|
|
s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
|
|
GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0), GST_SEEK_TYPE_SET, position);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* gst_player_get_subtitle_video_offset:
|
|
* @player: #GstPlayer instance
|
|
*
|
|
* Retrieve the current value of subtitle-video-offset property
|
|
*
|
|
* Returns: The current value of subtitle-video-offset in nanoseconds
|
|
*
|
|
* Since: 1.16
|
|
*/
|
|
gint64
|
|
gst_player_get_subtitle_video_offset (GstPlayer * self)
|
|
{
|
|
gint64 val = 0;
|
|
|
|
g_return_val_if_fail (GST_IS_PLAYER (self), DEFAULT_SUBTITLE_VIDEO_OFFSET);
|
|
|
|
g_object_get (self, "subtitle-video-offset", &val, NULL);
|
|
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* gst_player_set_subtitle_video_offset:
|
|
* @player: #GstPlayer instance
|
|
* @offset: #gint64 in nanoseconds
|
|
*
|
|
* Sets subtitle-video-offset property by value of @offset
|
|
*
|
|
* Since: 1.16
|
|
*/
|
|
void
|
|
gst_player_set_subtitle_video_offset (GstPlayer * self, gint64 offset)
|
|
{
|
|
g_return_if_fail (GST_IS_PLAYER (self));
|
|
|
|
g_object_set (self, "subtitle-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;
|
|
}
|