mirror of
https://github.com/Rafostar/clapper.git
synced 2025-12-24 14:06:26 +01:00
Merge pull request #594 from Rafostar/devel
Playlists handling changes and bugfixes
This commit is contained in:
@@ -1292,9 +1292,13 @@ clapper_app_window_constructed (GObject *object)
|
||||
#endif
|
||||
|
||||
#if CLAPPER_HAVE_DISCOVERER
|
||||
feature = CLAPPER_FEATURE (clapper_discoverer_new ());
|
||||
clapper_player_add_feature (player, feature);
|
||||
gst_object_unref (feature);
|
||||
if ((proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, "clapper-media-scanner"))) {
|
||||
gst_object_unref (proxy);
|
||||
} else {
|
||||
feature = CLAPPER_FEATURE (clapper_discoverer_new ());
|
||||
clapper_player_add_feature (player, feature);
|
||||
gst_object_unref (feature);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* FIXME: Allow setting sink/filter elements from prefs window
|
||||
|
||||
@@ -54,6 +54,21 @@ typedef enum
|
||||
CLAPPER_PLAYER_SEEK_METHOD_FAST,
|
||||
} ClapperPlayerSeekMethod;
|
||||
|
||||
/**
|
||||
* ClapperPlayerMessageDestination:
|
||||
* @CLAPPER_PLAYER_MESSAGE_DESTINATION_PLAYER: Messaging from application or reactable enhancers to the player itself.
|
||||
* @CLAPPER_PLAYER_MESSAGE_DESTINATION_REACTABLES: Messaging from application to the reactable enhancers.
|
||||
* @CLAPPER_PLAYER_MESSAGE_DESTINATION_APPLICATION: Messaging from reactable enhancers to the application.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
CLAPPER_PLAYER_MESSAGE_DESTINATION_PLAYER = 0,
|
||||
CLAPPER_PLAYER_MESSAGE_DESTINATION_REACTABLES,
|
||||
CLAPPER_PLAYER_MESSAGE_DESTINATION_APPLICATION,
|
||||
} ClapperPlayerMessageDestination;
|
||||
|
||||
/**
|
||||
* ClapperQueueProgressionMode:
|
||||
* @CLAPPER_QUEUE_PROGRESSION_NONE: Queue will not change current item after playback finishes.
|
||||
@@ -119,6 +134,8 @@ typedef enum
|
||||
* @CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT: Only run discovery on an item if it is not a currently selected item in [class@Clapper.Queue].
|
||||
* This mode is optimal when application always plays (or at least goes into paused) after selecting item from queue.
|
||||
* It will skip discovery of such items since they will be discovered by [class@Clapper.Player] anyway.
|
||||
*
|
||||
* Deprecated: 0.10: Use Media Scanner from `clapper-enhancers` repo instead.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
@@ -156,6 +173,8 @@ typedef enum
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_DURATION: Media item duration was updated.
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE: Media item timeline was updated.
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_TAGS: Media item tags were updated.
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_REDIRECT_URI: Media item redirect URI was updated.
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_CACHE_LOCATION: Media item cache location was updated.
|
||||
*
|
||||
* Flags informing which properties were updated within [class@Clapper.MediaItem].
|
||||
*
|
||||
@@ -167,6 +186,8 @@ typedef enum
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_DURATION = 1 << 1,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE = 1 << 2,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_TAGS = 1 << 3,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_REDIRECT_URI = 1 << 4,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_CACHE_LOCATION = 1 << 5,
|
||||
} ClapperReactableItemUpdatedFlags;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -34,7 +34,7 @@ G_GNUC_INTERNAL
|
||||
void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDiscovererInfo *info);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_media_item_update_from_item (ClapperMediaItem *item, ClapperMediaItem *other_item, ClapperPlayer *player);
|
||||
gboolean clapper_media_item_update_from_parsed_playlist (ClapperMediaItem *item, GListStore *playlist, GstObject *playlist_src, ClapperPlayer *player);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_media_item_set_duration (ClapperMediaItem *item, gdouble duration, ClapperAppBus *app_bus);
|
||||
|
||||
@@ -54,7 +54,8 @@ struct _ClapperMediaItem
|
||||
/* Whether using title from URI */
|
||||
gboolean title_is_parsed;
|
||||
|
||||
GSList *redirects;
|
||||
GType redirects_src_type;
|
||||
gchar *redirect_uri;
|
||||
gchar *cache_uri;
|
||||
|
||||
/* For shuffle */
|
||||
@@ -74,6 +75,7 @@ enum
|
||||
PROP_ID,
|
||||
PROP_URI,
|
||||
PROP_SUBURI,
|
||||
PROP_REDIRECT_URI,
|
||||
PROP_CACHE_LOCATION,
|
||||
PROP_TAGS,
|
||||
PROP_TITLE,
|
||||
@@ -202,12 +204,6 @@ clapper_media_item_get_id (ClapperMediaItem *self)
|
||||
return self->id;
|
||||
}
|
||||
|
||||
/* FIXME: 1.0:
|
||||
* Consider change to be transfer-full and just return latest data from redirects
|
||||
* list (alternatively expose redirect URI). This should make it possible to work
|
||||
* with enhancers that would benefit from knowledge about URI changes
|
||||
* (e.g "Recall" could read actual media instead of playlist file).
|
||||
*/
|
||||
/**
|
||||
* clapper_media_item_get_uri:
|
||||
* @item: a #ClapperMediaItem
|
||||
@@ -277,6 +273,55 @@ clapper_media_item_get_suburi (ClapperMediaItem *self)
|
||||
return suburi;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_media_item_get_redirect_uri:
|
||||
* @item: a #ClapperMediaItem
|
||||
*
|
||||
* Get permanent redirect URI of #ClapperMediaItem.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): a redirected URI of #ClapperMediaItem.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
gchar *
|
||||
clapper_media_item_get_redirect_uri (ClapperMediaItem *self)
|
||||
{
|
||||
gchar *redirect_uri;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
redirect_uri = g_strdup (self->redirect_uri);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return redirect_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_media_item_get_cache_location:
|
||||
* @item: a #ClapperMediaItem
|
||||
*
|
||||
* Get downloaded cache file location of #ClapperMediaItem.
|
||||
*
|
||||
* Returns: (transfer full) (type filename) (nullable): a cache file location of #ClapperMediaItem.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
gchar *
|
||||
clapper_media_item_get_cache_location (ClapperMediaItem *self)
|
||||
{
|
||||
gchar *cache_location = NULL;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
if (self->cache_uri)
|
||||
cache_location = g_filename_from_uri (self->cache_uri, NULL, NULL);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return cache_location;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_media_item_get_title:
|
||||
* @item: a #ClapperMediaItem
|
||||
@@ -681,49 +726,85 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco
|
||||
|
||||
/* XXX: Must be set from player thread */
|
||||
static inline gboolean
|
||||
clapper_media_item_set_redirect_uri (ClapperMediaItem *self, const gchar *redirect_uri)
|
||||
clapper_media_item_set_redirect_uri (ClapperMediaItem *self, const gchar *redirect_uri,
|
||||
GstObject *redirect_src)
|
||||
{
|
||||
/* Check if we did not already redirect into that URI (prevent endless loop) */
|
||||
if (!redirect_uri || g_slist_find_custom (self->redirects, redirect_uri, (GCompareFunc) strcmp))
|
||||
GType src_type;
|
||||
gboolean changed;
|
||||
|
||||
/* Safety checks */
|
||||
if (G_UNLIKELY (!redirect_uri)) {
|
||||
GST_ERROR_OBJECT (self, "Received redirect request without an URI set");
|
||||
return FALSE;
|
||||
}
|
||||
if (G_UNLIKELY (!redirect_src)) {
|
||||
GST_ERROR_OBJECT (self, "Received redirect request without source object set");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
self->redirects = g_slist_prepend (self->redirects, g_strdup (redirect_uri));
|
||||
GST_DEBUG_OBJECT (self, "Set redirect URI: \"%s\"", (gchar *) self->redirects->data);
|
||||
/* Only allow redirects determined by the same source type, otherwise
|
||||
* we would start mixing URIs when multiple sources message them */
|
||||
src_type = G_OBJECT_TYPE (redirect_src);
|
||||
|
||||
return TRUE;
|
||||
if (self->redirects_src_type == 0) {
|
||||
self->redirects_src_type = src_type;
|
||||
} else if (self->redirects_src_type != src_type) {
|
||||
GST_LOG_OBJECT (self, "Ignoring redirection from different source: %s",
|
||||
G_OBJECT_TYPE_NAME (redirect_src));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
changed = g_set_str (&self->redirect_uri, redirect_uri);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Set redirect URI: \"%s\", source: %s",
|
||||
redirect_uri, G_OBJECT_TYPE_NAME (redirect_src));
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_media_item_update_from_item (ClapperMediaItem *self, ClapperMediaItem *other_item,
|
||||
ClapperPlayer *player)
|
||||
clapper_media_item_update_from_parsed_playlist (ClapperMediaItem *self, GListStore *playlist,
|
||||
GstObject *playlist_src, ClapperPlayer *player)
|
||||
{
|
||||
gboolean title_changed = FALSE;
|
||||
ClapperMediaItem *other_item;
|
||||
const gchar *redirect_uri;
|
||||
gboolean success, title_changed = FALSE;
|
||||
|
||||
if (!clapper_media_item_set_redirect_uri (self, clapper_media_item_get_uri (other_item)))
|
||||
return FALSE;
|
||||
/* First playlist item URI becomes a redirect URI for item to be updated */
|
||||
other_item = g_list_model_get_item (G_LIST_MODEL (playlist), 0);
|
||||
redirect_uri = clapper_media_item_get_uri (other_item);
|
||||
|
||||
GST_OBJECT_LOCK (other_item);
|
||||
|
||||
if (other_item->tags)
|
||||
clapper_media_item_update_from_tag_list (self, other_item->tags, player);
|
||||
|
||||
/* Since its redirect now, we have to update title to describe new file instead of
|
||||
* being a playlist title. If other item had parsed title, it also means that tags
|
||||
* did not contain it, thus we have to manually update it and notify. */
|
||||
if (other_item->title_is_parsed) {
|
||||
GST_OBJECT_LOCK (self);
|
||||
title_changed = g_set_str (&self->title, other_item->title);
|
||||
self->title_is_parsed = TRUE;
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (other_item);
|
||||
|
||||
if (title_changed) {
|
||||
ClapperReactableItemUpdatedFlags flags = CLAPPER_REACTABLE_ITEM_UPDATED_TITLE;
|
||||
if ((success = clapper_media_item_set_redirect_uri (self, redirect_uri, playlist_src))) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
ClapperReactableItemUpdatedFlags flags = CLAPPER_REACTABLE_ITEM_UPDATED_REDIRECT_URI;
|
||||
|
||||
clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_TITLE]);
|
||||
/* Notify about URI change before other properties */
|
||||
clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self),
|
||||
param_specs[PROP_REDIRECT_URI]);
|
||||
|
||||
GST_OBJECT_LOCK (other_item);
|
||||
|
||||
if (other_item->tags)
|
||||
clapper_media_item_update_from_tag_list (self, other_item->tags, player);
|
||||
|
||||
/* Since its redirect now, we have to update title to describe new file instead of
|
||||
* being a playlist title. If other item had parsed title, it also means that tags
|
||||
* did not contain it, thus we have to manually update it and notify. */
|
||||
if (other_item->title_is_parsed) {
|
||||
GST_OBJECT_LOCK (self);
|
||||
title_changed = g_set_str (&self->title, other_item->title);
|
||||
self->title_is_parsed = TRUE;
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (other_item);
|
||||
|
||||
if (title_changed) {
|
||||
flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TITLE;
|
||||
clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_TITLE]);
|
||||
}
|
||||
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags);
|
||||
@@ -731,20 +812,51 @@ clapper_media_item_update_from_item (ClapperMediaItem *self, ClapperMediaItem *o
|
||||
clapper_features_manager_trigger_item_updated (features_manager, self);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
gst_object_unref (other_item);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/* XXX: Must be set from player thread or upon construction */
|
||||
void
|
||||
clapper_media_item_set_cache_location (ClapperMediaItem *self, const gchar *location)
|
||||
{
|
||||
g_clear_pointer (&self->cache_uri, g_free);
|
||||
gboolean changed;
|
||||
|
||||
if (location)
|
||||
self->cache_uri = g_filename_to_uri (location, NULL, NULL);
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Set cache URI: \"%s\"",
|
||||
GST_STR_NULL (self->cache_uri));
|
||||
/* Skip if both are NULL (no change - called during construction) */
|
||||
if ((changed = (self->cache_uri || location))) {
|
||||
g_clear_pointer (&self->cache_uri, g_free);
|
||||
|
||||
if (location)
|
||||
self->cache_uri = g_filename_to_uri (location, NULL, NULL);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Set cache URI: \"%s\"",
|
||||
GST_STR_NULL (self->cache_uri));
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (changed) {
|
||||
ClapperPlayer *player;
|
||||
|
||||
if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
|
||||
clapper_app_bus_post_prop_notify (player->app_bus,
|
||||
GST_OBJECT_CAST (self), param_specs[PROP_CACHE_LOCATION]);
|
||||
|
||||
if (player->reactables_manager) {
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_CACHE_LOCATION);
|
||||
}
|
||||
if ((features_manager = clapper_player_get_features_manager (player)))
|
||||
clapper_features_manager_trigger_item_updated (features_manager, self);
|
||||
|
||||
gst_object_unref (player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX: Can only be read from player thread.
|
||||
@@ -763,13 +875,10 @@ clapper_media_item_get_playback_uri (ClapperMediaItem *self)
|
||||
|
||||
if (exists)
|
||||
return self->cache_uri;
|
||||
|
||||
/* Do not test file existence next time */
|
||||
clapper_media_item_set_cache_location (self, NULL);
|
||||
}
|
||||
|
||||
if (self->redirects)
|
||||
return self->redirects->data;
|
||||
if (self->redirect_uri)
|
||||
return self->redirect_uri;
|
||||
|
||||
return self->uri;
|
||||
}
|
||||
@@ -814,7 +923,7 @@ clapper_media_item_constructed (GObject *object)
|
||||
self->uri = g_strdup ("file://");
|
||||
|
||||
self->title = clapper_utils_title_from_uri (self->uri);
|
||||
self->title_is_parsed = (self->title != NULL);
|
||||
self->title_is_parsed = TRUE;
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
}
|
||||
@@ -835,7 +944,7 @@ clapper_media_item_finalize (GObject *object)
|
||||
gst_object_unparent (GST_OBJECT_CAST (self->timeline));
|
||||
gst_object_unref (self->timeline);
|
||||
|
||||
g_slist_free_full (self->redirects, g_free);
|
||||
g_free (self->redirect_uri);
|
||||
g_free (self->cache_uri);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
@@ -879,6 +988,12 @@ clapper_media_item_get_property (GObject *object, guint prop_id,
|
||||
case PROP_SUBURI:
|
||||
g_value_take_string (value, clapper_media_item_get_suburi (self));
|
||||
break;
|
||||
case PROP_REDIRECT_URI:
|
||||
g_value_take_string (value, clapper_media_item_get_redirect_uri (self));
|
||||
break;
|
||||
case PROP_CACHE_LOCATION:
|
||||
g_value_take_string (value, clapper_media_item_get_cache_location (self));
|
||||
break;
|
||||
case PROP_TAGS:
|
||||
g_value_take_boxed (value, clapper_media_item_get_tags (self));
|
||||
break;
|
||||
@@ -940,16 +1055,40 @@ clapper_media_item_class_init (ClapperMediaItemClass *klass)
|
||||
NULL, NULL, NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperMediaItem:redirect-uri:
|
||||
*
|
||||
* Media permanent redirect URI.
|
||||
*
|
||||
* Changes when player determines a new redirect for given media item.
|
||||
* This will also happen when item URI leads to a playlist. Once playlist
|
||||
* is parsed, item is merged with the first item on that playlist and the
|
||||
* remaining items are appended to the playback queue after that item position.
|
||||
*
|
||||
* Once redirect URI in item is present, player will use that URI instead
|
||||
* of the default one. Cache location takes precedence over both URIs through.
|
||||
*/
|
||||
param_specs[PROP_REDIRECT_URI] = g_param_spec_string ("redirect-uri",
|
||||
NULL, NULL, NULL,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperMediaItem:cache-location:
|
||||
*
|
||||
* Media downloaded cache file location.
|
||||
*
|
||||
* This can be either set for newly created media items or
|
||||
* it will be updated after download is completed if
|
||||
* [property@Clapper.Player:download-enabled] is set.
|
||||
*
|
||||
* NOTE: This property was added in 0.8 as write at construct only.
|
||||
* It can also be read only since Clapper 0.10.
|
||||
*
|
||||
* Since: 0.8
|
||||
*/
|
||||
param_specs[PROP_CACHE_LOCATION] = g_param_spec_string ("cache-location",
|
||||
NULL, NULL, NULL,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperMediaItem:tags:
|
||||
|
||||
@@ -60,6 +60,12 @@ void clapper_media_item_set_suburi (ClapperMediaItem *item, const gchar *suburi)
|
||||
CLAPPER_API
|
||||
gchar * clapper_media_item_get_suburi (ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
gchar * clapper_media_item_get_redirect_uri (ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
gchar * clapper_media_item_get_cache_location (ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
gchar * clapper_media_item_get_title (ClapperMediaItem *item);
|
||||
|
||||
|
||||
@@ -49,4 +49,6 @@ void clapper_playbin_bus_post_current_item_change (GstBus *bus, ClapperMediaItem
|
||||
|
||||
void clapper_playbin_bus_post_item_suburi_change (GstBus *bus, ClapperMediaItem *item);
|
||||
|
||||
void clapper_playbin_bus_post_user_message (GstBus *bus, GstMessage *msg);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "clapper-stream-private.h"
|
||||
#include "clapper-stream-list-private.h"
|
||||
#include "gst/clapper-extractable-src-private.h"
|
||||
#include "gst/clapper-playlist-demux-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_playbin_bus_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
@@ -42,7 +43,8 @@ enum
|
||||
CLAPPER_PLAYBIN_BUS_STRUCTURE_RATE_CHANGE,
|
||||
CLAPPER_PLAYBIN_BUS_STRUCTURE_STREAM_CHANGE,
|
||||
CLAPPER_PLAYBIN_BUS_STRUCTURE_CURRENT_ITEM_CHANGE,
|
||||
CLAPPER_PLAYBIN_BUS_STRUCTURE_ITEM_SUBURI_CHANGE
|
||||
CLAPPER_PLAYBIN_BUS_STRUCTURE_ITEM_SUBURI_CHANGE,
|
||||
CLAPPER_PLAYBIN_BUS_STRUCTURE_USER_MESSAGE
|
||||
};
|
||||
|
||||
static ClapperBusQuark _structure_quarks[] = {
|
||||
@@ -54,6 +56,7 @@ static ClapperBusQuark _structure_quarks[] = {
|
||||
{"stream-change", 0},
|
||||
{"current-item-change", 0},
|
||||
{"item-suburi-change", 0},
|
||||
{"user-message", 0},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
@@ -325,7 +328,7 @@ clapper_playbin_bus_post_set_play_flag (GstBus *bus,
|
||||
ClapperPlayerPlayFlags flag, gboolean enabled)
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (SET_PLAY_FLAG),
|
||||
_FIELD_QUARK (FLAG), G_TYPE_FLAGS, flag,
|
||||
_FIELD_QUARK (FLAG), G_TYPE_UINT, flag,
|
||||
_FIELD_QUARK (VALUE), G_TYPE_BOOLEAN, enabled,
|
||||
NULL);
|
||||
gst_bus_post (bus, gst_message_new_application (NULL, structure));
|
||||
@@ -336,10 +339,10 @@ _handle_set_play_flag_msg (GstMessage *msg, const GstStructure *structure, Clapp
|
||||
{
|
||||
ClapperPlayerPlayFlags flag = 0;
|
||||
gboolean enabled, enable = FALSE;
|
||||
gint flags = 0;
|
||||
guint flags = 0;
|
||||
|
||||
gst_structure_id_get (structure,
|
||||
_FIELD_QUARK (FLAG), G_TYPE_FLAGS, &flag,
|
||||
_FIELD_QUARK (FLAG), G_TYPE_UINT, &flag,
|
||||
_FIELD_QUARK (VALUE), G_TYPE_BOOLEAN, &enable,
|
||||
NULL);
|
||||
|
||||
@@ -402,7 +405,7 @@ clapper_playbin_bus_post_seek (GstBus *bus, gdouble position, ClapperPlayerSeekM
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (SEEK),
|
||||
_FIELD_QUARK (POSITION), G_TYPE_INT64, (gint64) (position * GST_SECOND),
|
||||
_FIELD_QUARK (SEEK_METHOD), G_TYPE_ENUM, seek_method,
|
||||
_FIELD_QUARK (SEEK_METHOD), G_TYPE_INT, seek_method,
|
||||
NULL);
|
||||
gst_bus_post (bus, gst_message_new_application (NULL, structure));
|
||||
}
|
||||
@@ -422,7 +425,7 @@ _handle_seek_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer
|
||||
|
||||
gst_structure_id_get (structure,
|
||||
_FIELD_QUARK (POSITION), G_TYPE_INT64, &position,
|
||||
_FIELD_QUARK (SEEK_METHOD), G_TYPE_ENUM, &seek_method,
|
||||
_FIELD_QUARK (SEEK_METHOD), G_TYPE_INT, &seek_method,
|
||||
NULL);
|
||||
|
||||
/* If we are starting playback, do a seek after preroll */
|
||||
@@ -646,7 +649,7 @@ clapper_playbin_bus_post_current_item_change (GstBus *bus, ClapperMediaItem *cur
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (CURRENT_ITEM_CHANGE),
|
||||
_FIELD_QUARK (MEDIA_ITEM), CLAPPER_TYPE_MEDIA_ITEM, current_item,
|
||||
_FIELD_QUARK (ITEM_CHANGE_MODE), G_TYPE_ENUM, mode,
|
||||
_FIELD_QUARK (ITEM_CHANGE_MODE), G_TYPE_INT, mode,
|
||||
NULL);
|
||||
gst_bus_post (bus, gst_message_new_application (NULL, structure));
|
||||
}
|
||||
@@ -659,7 +662,7 @@ _handle_current_item_change_msg (GstMessage *msg, const GstStructure *structure,
|
||||
|
||||
gst_structure_id_get (structure,
|
||||
_FIELD_QUARK (MEDIA_ITEM), CLAPPER_TYPE_MEDIA_ITEM, ¤t_item,
|
||||
_FIELD_QUARK (ITEM_CHANGE_MODE), G_TYPE_ENUM, &mode,
|
||||
_FIELD_QUARK (ITEM_CHANGE_MODE), G_TYPE_INT, &mode,
|
||||
NULL);
|
||||
|
||||
player->pending_position = 0; // We store pending position for played item, so reset
|
||||
@@ -793,6 +796,100 @@ _handle_stream_change_msg (GstMessage *msg,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
clapper_playbin_bus_post_user_message (GstBus *bus, GstMessage *msg)
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id_empty (_STRUCTURE_QUARK (USER_MESSAGE));
|
||||
GValue value = G_VALUE_INIT;
|
||||
|
||||
g_value_init (&value, GST_TYPE_MESSAGE);
|
||||
g_value_take_boxed (&value, msg);
|
||||
|
||||
gst_structure_id_take_value (structure, _FIELD_QUARK (VALUE), &value);
|
||||
|
||||
gst_bus_post (bus, gst_message_new_application (NULL, structure));
|
||||
}
|
||||
|
||||
static inline void
|
||||
_on_playlist_parsed_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
{
|
||||
GstObject *src = GST_MESSAGE_SRC (msg);
|
||||
ClapperMediaItem *playlist_item = NULL;
|
||||
GListStore *playlist = NULL;
|
||||
const GstStructure *structure;
|
||||
guint n_items;
|
||||
|
||||
if (G_UNLIKELY (!src)) {
|
||||
GST_WARNING_OBJECT (player, "Ignoring playlist parsed message without a source");
|
||||
return;
|
||||
}
|
||||
|
||||
structure = gst_message_get_structure (msg);
|
||||
|
||||
/* If message contains item, use that.
|
||||
* Otherwise assume pending item was parsed. */
|
||||
if (gst_structure_has_field (structure, "item")) {
|
||||
gst_structure_get (structure,
|
||||
"item", CLAPPER_TYPE_MEDIA_ITEM, &playlist_item, NULL);
|
||||
} else if (CLAPPER_IS_PLAYLIST_DEMUX (src)) {
|
||||
GST_OBJECT_LOCK (player);
|
||||
|
||||
/* Playlist from demuxer is always parsed before playback starts */
|
||||
if (player->pending_item)
|
||||
playlist_item = gst_object_ref (player->pending_item);
|
||||
|
||||
GST_OBJECT_UNLOCK (player);
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (playlist_item == NULL)) {
|
||||
GST_WARNING_OBJECT (player, "Playlist parsed without media item set");
|
||||
return;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (player, "Received parsed playlist of %" GST_PTR_FORMAT
|
||||
"(%s)", playlist_item, clapper_media_item_get_uri (playlist_item));
|
||||
|
||||
gst_structure_get (structure,
|
||||
"playlist", G_TYPE_LIST_STORE, &playlist, NULL);
|
||||
|
||||
n_items = g_list_model_get_n_items (G_LIST_MODEL (playlist));
|
||||
|
||||
if (G_LIKELY (n_items > 0)) {
|
||||
gboolean updated;
|
||||
|
||||
/* Update redirect URI (must be done from player thread) */
|
||||
updated = clapper_media_item_update_from_parsed_playlist (playlist_item, playlist, src, player);
|
||||
|
||||
if (updated && n_items > 1) {
|
||||
/* Forward to append remaining items (must be done from main thread) */
|
||||
clapper_app_bus_post_insert_playlist (player->app_bus,
|
||||
GST_OBJECT_CAST (player),
|
||||
GST_OBJECT_CAST (playlist_item),
|
||||
G_OBJECT (playlist));
|
||||
}
|
||||
}
|
||||
|
||||
gst_object_unref (playlist_item);
|
||||
g_object_unref (playlist);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_handle_user_message_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer *player)
|
||||
{
|
||||
GstMessage *user_message = NULL;
|
||||
|
||||
gst_structure_id_get (structure,
|
||||
_FIELD_QUARK (VALUE), GST_TYPE_MESSAGE, &user_message,
|
||||
NULL);
|
||||
|
||||
GST_DEBUG_OBJECT (player, "Received user message: %" GST_PTR_FORMAT, user_message);
|
||||
|
||||
if (gst_message_has_name (user_message, "ClapperPlaylistParsed"))
|
||||
_on_playlist_parsed_msg (user_message, player);
|
||||
|
||||
gst_message_unref (user_message);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_handle_app_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
{
|
||||
@@ -813,6 +910,8 @@ _handle_app_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
_handle_current_item_change_msg (msg, structure, player);
|
||||
else if (quark == _STRUCTURE_QUARK (ITEM_SUBURI_CHANGE))
|
||||
_handle_item_suburi_change_msg (msg, structure, player);
|
||||
else if (quark == _STRUCTURE_QUARK (USER_MESSAGE))
|
||||
_handle_user_message_msg (msg, structure, player);
|
||||
}
|
||||
|
||||
static inline void
|
||||
@@ -832,70 +931,7 @@ _handle_element_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
g_free (name);
|
||||
g_free (details);
|
||||
} else if (gst_message_has_name (msg, "ClapperPlaylistParsed")) {
|
||||
ClapperMediaItem *playlist_item = NULL;
|
||||
GListStore *playlist = NULL;
|
||||
const GstStructure *structure = gst_message_get_structure (msg);
|
||||
guint n_items;
|
||||
|
||||
/* If message contains item, use that.
|
||||
* Otherwise assume pending item was parsed. */
|
||||
if (gst_structure_has_field (structure, "item")) {
|
||||
gst_structure_get (structure,
|
||||
"item", CLAPPER_TYPE_MEDIA_ITEM, &playlist_item, NULL);
|
||||
} else {
|
||||
GST_OBJECT_LOCK (player);
|
||||
|
||||
/* Playlist is always parsed before playback starts */
|
||||
if (player->pending_item)
|
||||
playlist_item = gst_object_ref (player->pending_item);
|
||||
|
||||
GST_OBJECT_UNLOCK (player);
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (playlist_item == NULL)) {
|
||||
GST_WARNING_OBJECT (player, "Playlist parsed without media item set");
|
||||
return;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (player, "Received parsed playlist of %" GST_PTR_FORMAT
|
||||
"(%s)", playlist_item, clapper_media_item_get_uri (playlist_item));
|
||||
|
||||
gst_structure_get (structure,
|
||||
"playlist", G_TYPE_LIST_STORE, &playlist, NULL);
|
||||
|
||||
n_items = g_list_model_get_n_items (G_LIST_MODEL (playlist));
|
||||
|
||||
if (G_LIKELY (n_items > 0)) {
|
||||
ClapperMediaItem *active_item = g_list_model_get_item (G_LIST_MODEL (playlist), 0);
|
||||
gboolean updated;
|
||||
|
||||
/* Update redirect URI (must be done from player thread) */
|
||||
updated = clapper_media_item_update_from_item (playlist_item, active_item, player);
|
||||
gst_object_unref (active_item);
|
||||
|
||||
if (!updated) {
|
||||
GstMessage *msg;
|
||||
GError *error;
|
||||
|
||||
error = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
|
||||
"Detected infinite redirection in playlist");
|
||||
msg = gst_message_new_error (GST_OBJECT (player), error, NULL);
|
||||
|
||||
_handle_error_msg (msg, player);
|
||||
|
||||
g_error_free (error);
|
||||
gst_message_unref (msg);
|
||||
} else if (n_items > 1) {
|
||||
/* Forward to append remaining items (must be done from main thread) */
|
||||
clapper_app_bus_post_insert_playlist (player->app_bus,
|
||||
GST_OBJECT_CAST (player),
|
||||
GST_OBJECT_CAST (playlist_item),
|
||||
G_OBJECT (playlist));
|
||||
}
|
||||
}
|
||||
|
||||
gst_object_unref (playlist_item);
|
||||
g_object_unref (playlist);
|
||||
_on_playlist_parsed_msg (msg, player);
|
||||
} else if (gst_message_has_name (msg, "GstCacheDownloadComplete")) {
|
||||
ClapperMediaItem *downloaded_item = NULL;
|
||||
const GstStructure *structure;
|
||||
@@ -921,6 +957,8 @@ _handle_element_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
location = gst_structure_get_string (structure, "location");
|
||||
signal_id = g_signal_lookup ("download-complete", CLAPPER_TYPE_PLAYER);
|
||||
|
||||
/* Set cache location before "download-complete" signal emit,
|
||||
* so it can also be read directly from item */
|
||||
GST_INFO_OBJECT (player, "Download of %" GST_PTR_FORMAT
|
||||
" complete: %s", downloaded_item, location);
|
||||
clapper_media_item_set_cache_location (downloaded_item, location);
|
||||
|
||||
@@ -2245,6 +2245,56 @@ clapper_player_make_pipeline_graph (ClapperPlayer *self, GstDebugGraphDetails de
|
||||
return gst_debug_bin_to_dot_data (GST_BIN (self->playbin), details);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_player_post_message:
|
||||
* @player: a #ClapperPlayer
|
||||
* @msg: (transfer full): a #GstMessage
|
||||
* @destination: a #ClapperPlayerMessageDestination
|
||||
*
|
||||
* Allows sending custom messages to the desired @destination.
|
||||
*
|
||||
* This functionality can be used for communication with enhancers implementing
|
||||
* [iface@Clapper.Reactable] interface. Useful for applications to send custom messages
|
||||
* to enhacers that can react to them and/or for enhancers development to send events
|
||||
* from them to the applications. It can also be used for sending specific messages
|
||||
* from application or enhancers to the player itself.
|
||||
*
|
||||
* Messages send to the application can be received by connecting a
|
||||
* [signal@Clapper.Player::message] signal handler. Inspection of message source
|
||||
* object can be done to determine who send given message.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_player_post_message (ClapperPlayer *self, GstMessage *msg,
|
||||
ClapperPlayerMessageDestination destination)
|
||||
{
|
||||
g_return_if_fail (CLAPPER_IS_PLAYER (self));
|
||||
g_return_if_fail (GST_IS_MESSAGE (msg));
|
||||
|
||||
switch (destination) {
|
||||
case CLAPPER_PLAYER_MESSAGE_DESTINATION_PLAYER:
|
||||
clapper_playbin_bus_post_user_message (self->bus, msg);
|
||||
return; // Message taken
|
||||
case CLAPPER_PLAYER_MESSAGE_DESTINATION_REACTABLES:
|
||||
if (self->reactables_manager) {
|
||||
clapper_reactables_manager_post_message (self->reactables_manager, msg);
|
||||
return; // Message taken
|
||||
}
|
||||
break;
|
||||
case CLAPPER_PLAYER_MESSAGE_DESTINATION_APPLICATION:
|
||||
clapper_app_bus_post_message_signal (self->app_bus,
|
||||
GST_OBJECT_CAST (self), signals[SIGNAL_MESSAGE], msg);
|
||||
break; // Above function does not take message (unref needed)
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
/* Unref when not taken */
|
||||
gst_message_unref (msg);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_player_thread_start (ClapperThreadedObject *threaded_object)
|
||||
{
|
||||
@@ -2962,6 +3012,9 @@ clapper_player_class_init (ClapperPlayerClass *klass)
|
||||
* be only emitted when progressive download buffering is enabled by
|
||||
* setting [property@Clapper.Player:download-enabled] property to %TRUE.
|
||||
*
|
||||
* Download cache file location can also be read directly from @item
|
||||
* through its [property@Clapper.MediaItem:cache-location] property.
|
||||
*
|
||||
* Since: 0.8
|
||||
*/
|
||||
signals[SIGNAL_DOWNLOAD_COMPLETE] = g_signal_new ("download-complete",
|
||||
|
||||
@@ -210,4 +210,7 @@ void clapper_player_add_feature (ClapperPlayer *player, ClapperFeature *feature)
|
||||
CLAPPER_API
|
||||
gchar * clapper_player_make_pipeline_graph (ClapperPlayer *player, GstDebugGraphDetails details);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_player_post_message (ClapperPlayer *player, GstMessage *msg, ClapperPlayerMessageDestination destination);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -206,6 +206,17 @@ struct _ClapperReactableInterface
|
||||
*/
|
||||
void (* queue_progression_changed) (ClapperReactable *reactable, ClapperQueueProgressionMode mode);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::message_received:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @msg: a #GstMessage
|
||||
*
|
||||
* Custom message from user was received on reactables bus.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* message_received) (ClapperReactable *reactable, GstMessage *msg);
|
||||
|
||||
/*< private >*/
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "clapper-enums.h"
|
||||
#include "clapper-threaded-object.h"
|
||||
#include "clapper-enhancer-proxy.h"
|
||||
@@ -36,6 +38,9 @@ void clapper_reactables_manager_initialize (void);
|
||||
G_GNUC_INTERNAL
|
||||
ClapperReactablesManager * clapper_reactables_manager_new (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_post_message (ClapperReactablesManager *manager, GstMessage *msg);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_configure_take_config (ClapperReactablesManager *manager, ClapperEnhancerProxy *proxy, GstStructure *config);
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ enum
|
||||
{
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_CONFIGURE = 0,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_EVENT,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_USER_MESSAGE,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_VALUE,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_EXTRA_VALUE
|
||||
};
|
||||
@@ -85,6 +86,7 @@ enum
|
||||
static ClapperBusQuark _quarks[] = {
|
||||
{"configure", 0},
|
||||
{"event", 0},
|
||||
{"user-message", 0},
|
||||
{"value", 0},
|
||||
{"extra-value", 0},
|
||||
{NULL, 0}
|
||||
@@ -308,6 +310,23 @@ clapper_reactables_manager_handle_event (ClapperReactablesManager *self, const G
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
clapper_reactables_manager_handle_user_message (ClapperReactablesManager *self, const GstStructure *structure)
|
||||
{
|
||||
const GValue *value = gst_structure_id_get_value (structure, _QUARK (VALUE));
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->array->len; ++i) {
|
||||
ClapperReactableManagerData *data = g_ptr_array_index (self->array, i);
|
||||
ClapperReactableInterface *reactable_iface = CLAPPER_REACTABLE_GET_IFACE (data->reactable);
|
||||
|
||||
if (reactable_iface->message_received) {
|
||||
reactable_iface->message_received (data->reactable,
|
||||
GST_MESSAGE_CAST (g_value_get_boxed (value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
@@ -324,6 +343,8 @@ _bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSE
|
||||
clapper_reactables_manager_handle_event (self, structure);
|
||||
} else if (quark == _QUARK (CONFIGURE)) {
|
||||
clapper_reactables_manager_handle_configure (self, structure);
|
||||
} else if (quark == _QUARK (USER_MESSAGE)) {
|
||||
clapper_reactables_manager_handle_user_message (self, structure);
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Received invalid quark on reactables bus!");
|
||||
}
|
||||
@@ -365,6 +386,21 @@ clapper_reactables_manager_new (void)
|
||||
return reactables_manager;
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_post_message (ClapperReactablesManager *self, GstMessage *msg)
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id_empty (_QUARK (USER_MESSAGE));
|
||||
GValue value = G_VALUE_INIT;
|
||||
|
||||
g_value_init (&value, GST_TYPE_MESSAGE);
|
||||
g_value_take_boxed (&value, msg);
|
||||
|
||||
gst_structure_id_take_value (structure, _QUARK (VALUE), &value);
|
||||
|
||||
gst_bus_post (self->bus, gst_message_new_application (
|
||||
GST_OBJECT_CAST (self), structure));
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_configure_take_config (ClapperReactablesManager *self,
|
||||
ClapperEnhancerProxy *proxy, GstStructure *config)
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
*
|
||||
* Use [const@Clapper.HAVE_DISCOVERER] macro to check if Clapper API
|
||||
* was compiled with this feature.
|
||||
*
|
||||
* Deprecated: 0.10: Use Media Scanner from `clapper-enhancers` repo instead.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
@@ -369,6 +371,8 @@ clapper_discoverer_unprepare (ClapperFeature *feature)
|
||||
* Creates a new #ClapperDiscoverer instance.
|
||||
*
|
||||
* Returns: (transfer full): a new #ClapperDiscoverer instance.
|
||||
*
|
||||
* Deprecated: 0.10: Use Media Scanner from `clapper-enhancers` repo instead.
|
||||
*/
|
||||
ClapperDiscoverer *
|
||||
clapper_discoverer_new (void)
|
||||
@@ -385,6 +389,8 @@ clapper_discoverer_new (void)
|
||||
* @mode: a #ClapperDiscovererDiscoveryMode
|
||||
*
|
||||
* Set the [enum@Clapper.DiscovererDiscoveryMode] of @discoverer.
|
||||
*
|
||||
* Deprecated: 0.10: Use Media Scanner from `clapper-enhancers` repo instead.
|
||||
*/
|
||||
void
|
||||
clapper_discoverer_set_discovery_mode (ClapperDiscoverer *self, ClapperDiscovererDiscoveryMode mode)
|
||||
@@ -409,6 +415,8 @@ clapper_discoverer_set_discovery_mode (ClapperDiscoverer *self, ClapperDiscovere
|
||||
* Get the [enum@Clapper.DiscovererDiscoveryMode] of @discoverer.
|
||||
*
|
||||
* Returns: a currently set #ClapperDiscovererDiscoveryMode.
|
||||
*
|
||||
* Deprecated: 0.10: Use Media Scanner from `clapper-enhancers` repo instead.
|
||||
*/
|
||||
ClapperDiscovererDiscoveryMode
|
||||
clapper_discoverer_get_discovery_mode (ClapperDiscoverer *self)
|
||||
@@ -498,6 +506,8 @@ clapper_discoverer_class_init (ClapperDiscovererClass *klass)
|
||||
* ClapperDiscoverer:discovery-mode:
|
||||
*
|
||||
* Discoverer discovery mode.
|
||||
*
|
||||
* Deprecated: 0.10: Use Media Scanner from `clapper-enhancers` repo instead.
|
||||
*/
|
||||
param_specs[PROP_DISCOVERY_MODE] = g_param_spec_enum ("discovery-mode",
|
||||
NULL, NULL, CLAPPER_TYPE_DISCOVERER_DISCOVERY_MODE, DEFAULT_DISCOVERY_MODE,
|
||||
|
||||
@@ -33,16 +33,16 @@ G_BEGIN_DECLS
|
||||
#define CLAPPER_TYPE_DISCOVERER (clapper_discoverer_get_type())
|
||||
#define CLAPPER_DISCOVERER_CAST(obj) ((ClapperDiscoverer *)(obj))
|
||||
|
||||
CLAPPER_API
|
||||
CLAPPER_DEPRECATED
|
||||
G_DECLARE_FINAL_TYPE (ClapperDiscoverer, clapper_discoverer, CLAPPER, DISCOVERER, ClapperFeature)
|
||||
|
||||
CLAPPER_API
|
||||
CLAPPER_DEPRECATED
|
||||
ClapperDiscoverer * clapper_discoverer_new (void);
|
||||
|
||||
CLAPPER_API
|
||||
CLAPPER_DEPRECATED
|
||||
void clapper_discoverer_set_discovery_mode (ClapperDiscoverer *discoverer, ClapperDiscovererDiscoveryMode mode);
|
||||
|
||||
CLAPPER_API
|
||||
CLAPPER_DEPRECATED
|
||||
ClapperDiscovererDiscoveryMode clapper_discoverer_get_discovery_mode (ClapperDiscoverer *discoverer);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -60,6 +60,8 @@ typedef struct
|
||||
GError **error;
|
||||
} ClapperEnhancerDirectorData;
|
||||
|
||||
static GMutex cleanup_lock;
|
||||
|
||||
static gpointer
|
||||
clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data)
|
||||
{
|
||||
@@ -356,6 +358,11 @@ _cache_cleanup_func (ClapperEnhancerDirector *self)
|
||||
const gchar *data;
|
||||
gint64 since_cleanup, epoch_now, epoch_last = 0;
|
||||
|
||||
if (!g_mutex_trylock (&cleanup_lock)) {
|
||||
GST_LOG_OBJECT (self, "Cache cleanup is already running");
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
date = g_date_time_new_now_utc ();
|
||||
epoch_now = g_date_time_to_unix (date);
|
||||
g_date_time_unref (date);
|
||||
@@ -418,6 +425,7 @@ _cache_cleanup_func (ClapperEnhancerDirector *self)
|
||||
CLAPPER_TIME_FORMAT " ago", CLAPPER_TIME_ARGS (since_cleanup));
|
||||
}
|
||||
|
||||
g_mutex_unlock (&cleanup_lock);
|
||||
g_free (filename);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
#define URI_LIST_MEDIA_TYPE "text/uri-list"
|
||||
#define DATA_CHUNK_SIZE 4096
|
||||
|
||||
#define NTH_REDIRECT_STRUCTURE_NAME "ClapperQueryNthRedirect"
|
||||
#define NTH_REDIRECT_FIELD "nth-redirect"
|
||||
#define MAX_REDIRECTS 10
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_playlist_demux_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
@@ -309,16 +313,32 @@ _filter_playlistables (ClapperPlaylistDemux *self, GstCaps *caps, ClapperEnhance
|
||||
static inline gboolean
|
||||
_handle_playlist (ClapperPlaylistDemux *self, GListStore *playlist, GCancellable *cancellable)
|
||||
{
|
||||
ClapperMediaItem *item = g_list_model_get_item (G_LIST_MODEL (playlist), 0);
|
||||
ClapperMediaItem *item;
|
||||
GstStructure *structure;
|
||||
const gchar *uri;
|
||||
gboolean success;
|
||||
|
||||
if (g_cancellable_is_cancelled (cancellable)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("Playlist parsing was cancelled"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
item = g_list_model_get_item (G_LIST_MODEL (playlist), 0);
|
||||
|
||||
if (G_UNLIKELY (item == NULL)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("This playlist appears to be empty"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Post playlist before setting an URI, so it arrives
|
||||
* before eventual error (e.g. non-existing file) */
|
||||
structure = gst_structure_new ("ClapperPlaylistParsed",
|
||||
"playlist", G_TYPE_LIST_STORE, playlist, NULL);
|
||||
gst_element_post_message (GST_ELEMENT_CAST (self),
|
||||
gst_message_new_element (GST_OBJECT_CAST (self), structure));
|
||||
|
||||
uri = clapper_media_item_get_uri (item);
|
||||
success = clapper_uri_base_demux_set_uri (CLAPPER_URI_BASE_DEMUX_CAST (self), uri, NULL);
|
||||
gst_object_unref (item);
|
||||
@@ -329,15 +349,51 @@ _handle_playlist (ClapperPlaylistDemux *self, GListStore *playlist, GCancellable
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!g_cancellable_is_cancelled (cancellable)) {
|
||||
GstStructure *structure = gst_structure_new ("ClapperPlaylistParsed",
|
||||
"playlist", G_TYPE_LIST_STORE, playlist, NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gst_element_post_message (GST_ELEMENT_CAST (self),
|
||||
gst_message_new_element (GST_OBJECT_CAST (self), structure));
|
||||
static void
|
||||
_query_parse_nth_redirect (GstQuery *query, guint *nth_redirect)
|
||||
{
|
||||
const GstStructure *structure = gst_query_get_structure (query);
|
||||
*nth_redirect = g_value_get_uint (gst_structure_get_value (structure, NTH_REDIRECT_FIELD));
|
||||
}
|
||||
|
||||
static void
|
||||
_query_set_nth_redirect (GstQuery *query, guint nth_redirect)
|
||||
{
|
||||
GstStructure *structure = gst_query_writable_structure (query);
|
||||
gst_structure_set (structure, NTH_REDIRECT_FIELD, G_TYPE_UINT, nth_redirect, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_playlist_demux_handle_custom_query (ClapperUriBaseDemux *uri_bd, GstQuery *query)
|
||||
{
|
||||
ClapperPlaylistDemux *self = CLAPPER_PLAYLIST_DEMUX_CAST (uri_bd);
|
||||
const GstStructure *structure = gst_query_get_structure (query);
|
||||
|
||||
if (gst_structure_has_name (structure, NTH_REDIRECT_STRUCTURE_NAME)) {
|
||||
GstPad *sink_pad;
|
||||
|
||||
GST_LOG_OBJECT (self, "Received custom query: " NTH_REDIRECT_STRUCTURE_NAME);
|
||||
|
||||
sink_pad = gst_element_get_static_pad (GST_ELEMENT_CAST (self), "sink");
|
||||
gst_pad_peer_query (sink_pad, query);
|
||||
gst_object_unref (sink_pad);
|
||||
|
||||
if (G_LIKELY (gst_query_is_writable (query))) {
|
||||
guint nth_redirect = 0;
|
||||
|
||||
_query_parse_nth_redirect (query, &nth_redirect);
|
||||
_query_set_nth_redirect (query, ++nth_redirect);
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Unwritable custom query: " NTH_REDIRECT_STRUCTURE_NAME);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@@ -347,9 +403,11 @@ clapper_playlist_demux_process_buffer (ClapperUriBaseDemux *uri_bd,
|
||||
ClapperPlaylistDemux *self = CLAPPER_PLAYLIST_DEMUX_CAST (uri_bd);
|
||||
GstPad *sink_pad;
|
||||
GstQuery *query;
|
||||
GstStructure *query_structure;
|
||||
GUri *uri = NULL;
|
||||
GListStore *playlist;
|
||||
GError *error = NULL;
|
||||
guint nth_redirect = 0;
|
||||
gboolean handled;
|
||||
|
||||
sink_pad = gst_element_get_static_pad (GST_ELEMENT_CAST (self), "sink");
|
||||
@@ -367,11 +425,28 @@ clapper_playlist_demux_process_buffer (ClapperUriBaseDemux *uri_bd,
|
||||
}
|
||||
}
|
||||
|
||||
gst_query_unref (query);
|
||||
|
||||
query_structure = gst_structure_new (NTH_REDIRECT_STRUCTURE_NAME,
|
||||
NTH_REDIRECT_FIELD, G_TYPE_UINT, 0, NULL);
|
||||
query = gst_query_new_custom (GST_QUERY_CUSTOM, query_structure);
|
||||
|
||||
if (gst_pad_peer_query (sink_pad, query))
|
||||
_query_parse_nth_redirect (query, &nth_redirect);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Current number of redirects: %u", nth_redirect);
|
||||
|
||||
gst_query_unref (query);
|
||||
gst_object_unref (sink_pad);
|
||||
|
||||
if (G_UNLIKELY (uri == NULL)) {
|
||||
GST_ERROR_OBJECT (self, "Could not query source URI");
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
||||
("Could not query source URI"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
if (G_UNLIKELY (nth_redirect > MAX_REDIRECTS)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
||||
("Too many nested playlists"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -498,6 +573,7 @@ clapper_playlist_demux_class_init (ClapperPlaylistDemuxClass *klass)
|
||||
gobject_class->finalize = clapper_playlist_demux_finalize;
|
||||
|
||||
clapperuribd_class->handle_caps = clapper_playlist_demux_handle_caps;
|
||||
clapperuribd_class->handle_custom_query = clapper_playlist_demux_handle_custom_query;
|
||||
clapperuribd_class->process_buffer = clapper_playlist_demux_process_buffer;
|
||||
|
||||
param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies",
|
||||
|
||||
@@ -41,6 +41,8 @@ struct _ClapperUriBaseDemuxClass
|
||||
void (* handle_caps) (ClapperUriBaseDemux *uri_bd, GstCaps *caps);
|
||||
|
||||
void (* handle_custom_event) (ClapperUriBaseDemux *uri_bd, GstEvent *event);
|
||||
|
||||
gboolean (* handle_custom_query) (ClapperUriBaseDemux *uri_bd, GstQuery *query);
|
||||
};
|
||||
|
||||
gboolean clapper_uri_base_demux_set_uri (ClapperUriBaseDemux *uri_bd, const gchar *uri, const gchar *blacklisted_el);
|
||||
|
||||
@@ -189,6 +189,20 @@ _make_handler_for_uri (ClapperUriBaseDemux *self, const gchar *uri, const gchar
|
||||
return element;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_src_pad_query_func (GstPad *pad, GstObject *parent, GstQuery *query)
|
||||
{
|
||||
if (GST_QUERY_TYPE (query) == GST_QUERY_CUSTOM) {
|
||||
ClapperUriBaseDemux *self = CLAPPER_URI_BASE_DEMUX_CAST (parent);
|
||||
ClapperUriBaseDemuxClass *uri_bd_class = CLAPPER_URI_BASE_DEMUX_GET_CLASS (self);
|
||||
|
||||
if (uri_bd_class->handle_custom_query && uri_bd_class->handle_custom_query (self, query))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return gst_pad_query_default (pad, parent, query);
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_uri_base_demux_set_uri (ClapperUriBaseDemux *self, const gchar *uri, const gchar *blacklisted_el)
|
||||
{
|
||||
@@ -254,6 +268,7 @@ clapper_uri_base_demux_set_uri (ClapperUriBaseDemux *self, const gchar *uri, con
|
||||
|
||||
src_ghostpad = gst_ghost_pad_new_from_template ("src", priv->typefind_src,
|
||||
gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "src"));
|
||||
gst_pad_set_query_function (src_ghostpad, (GstPadQueryFunction) _src_pad_query_func);
|
||||
|
||||
gst_pad_set_active (src_ghostpad, TRUE);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user