diff --git a/src/bin/clapper-app/clapper-app-window.c b/src/bin/clapper-app/clapper-app-window.c index 04145119..f8fa4350 100644 --- a/src/bin/clapper-app/clapper-app-window.c +++ b/src/bin/clapper-app/clapper-app-window.c @@ -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 diff --git a/src/lib/clapper/clapper-enums.h b/src/lib/clapper/clapper-enums.h index 6f398bbb..12f12984 100644 --- a/src/lib/clapper/clapper-enums.h +++ b/src/lib/clapper/clapper-enums.h @@ -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 diff --git a/src/lib/clapper/clapper-media-item-private.h b/src/lib/clapper/clapper-media-item-private.h index d1666355..c48467ff 100644 --- a/src/lib/clapper/clapper-media-item-private.h +++ b/src/lib/clapper/clapper-media-item-private.h @@ -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); diff --git a/src/lib/clapper/clapper-media-item.c b/src/lib/clapper/clapper-media-item.c index ea89529b..cc36909c 100644 --- a/src/lib/clapper/clapper-media-item.c +++ b/src/lib/clapper/clapper-media-item.c @@ -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: diff --git a/src/lib/clapper/clapper-media-item.h b/src/lib/clapper/clapper-media-item.h index 8c8733a7..1c77a807 100644 --- a/src/lib/clapper/clapper-media-item.h +++ b/src/lib/clapper/clapper-media-item.h @@ -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); diff --git a/src/lib/clapper/clapper-playbin-bus-private.h b/src/lib/clapper/clapper-playbin-bus-private.h index 2e858c5b..b06bed1f 100644 --- a/src/lib/clapper/clapper-playbin-bus-private.h +++ b/src/lib/clapper/clapper-playbin-bus-private.h @@ -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 diff --git a/src/lib/clapper/clapper-playbin-bus.c b/src/lib/clapper/clapper-playbin-bus.c index 706d080b..1ee4aa08 100644 --- a/src/lib/clapper/clapper-playbin-bus.c +++ b/src/lib/clapper/clapper-playbin-bus.c @@ -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); diff --git a/src/lib/clapper/clapper-player.c b/src/lib/clapper/clapper-player.c index 39dee096..7fbf8a21 100644 --- a/src/lib/clapper/clapper-player.c +++ b/src/lib/clapper/clapper-player.c @@ -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", diff --git a/src/lib/clapper/clapper-player.h b/src/lib/clapper/clapper-player.h index 0e69b566..45fc31d2 100644 --- a/src/lib/clapper/clapper-player.h +++ b/src/lib/clapper/clapper-player.h @@ -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 diff --git a/src/lib/clapper/clapper-reactable.h b/src/lib/clapper/clapper-reactable.h index 8a376465..5b321889 100644 --- a/src/lib/clapper/clapper-reactable.h +++ b/src/lib/clapper/clapper-reactable.h @@ -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]; }; diff --git a/src/lib/clapper/clapper-reactables-manager-private.h b/src/lib/clapper/clapper-reactables-manager-private.h index d9e27586..cf0d837e 100644 --- a/src/lib/clapper/clapper-reactables-manager-private.h +++ b/src/lib/clapper/clapper-reactables-manager-private.h @@ -18,6 +18,8 @@ #pragma once +#include + #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); diff --git a/src/lib/clapper/clapper-reactables-manager.c b/src/lib/clapper/clapper-reactables-manager.c index faccfb66..8b08c297 100644 --- a/src/lib/clapper/clapper-reactables-manager.c +++ b/src/lib/clapper/clapper-reactables-manager.c @@ -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) diff --git a/src/lib/clapper/features/discoverer/clapper-discoverer.c b/src/lib/clapper/features/discoverer/clapper-discoverer.c index c9497434..19881894 100644 --- a/src/lib/clapper/features/discoverer/clapper-discoverer.c +++ b/src/lib/clapper/features/discoverer/clapper-discoverer.c @@ -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 @@ -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, diff --git a/src/lib/clapper/features/discoverer/clapper-discoverer.h b/src/lib/clapper/features/discoverer/clapper-discoverer.h index f3bbdcd7..2055c9e5 100644 --- a/src/lib/clapper/features/discoverer/clapper-discoverer.h +++ b/src/lib/clapper/features/discoverer/clapper-discoverer.h @@ -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 diff --git a/src/lib/clapper/gst/clapper-enhancer-director.c b/src/lib/clapper/gst/clapper-enhancer-director.c index c8f3ac7d..1f71cd31 100644 --- a/src/lib/clapper/gst/clapper-enhancer-director.c +++ b/src/lib/clapper/gst/clapper-enhancer-director.c @@ -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; diff --git a/src/lib/clapper/gst/clapper-playlist-demux.c b/src/lib/clapper/gst/clapper-playlist-demux.c index fb123240..0c7afac4 100644 --- a/src/lib/clapper/gst/clapper-playlist-demux.c +++ b/src/lib/clapper/gst/clapper-playlist-demux.c @@ -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", diff --git a/src/lib/clapper/gst/clapper-uri-base-demux-private.h b/src/lib/clapper/gst/clapper-uri-base-demux-private.h index 3237c419..169b91e3 100644 --- a/src/lib/clapper/gst/clapper-uri-base-demux-private.h +++ b/src/lib/clapper/gst/clapper-uri-base-demux-private.h @@ -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); diff --git a/src/lib/clapper/gst/clapper-uri-base-demux.c b/src/lib/clapper/gst/clapper-uri-base-demux.c index 45bcdd97..b6baf2f1 100644 --- a/src/lib/clapper/gst/clapper-uri-base-demux.c +++ b/src/lib/clapper/gst/clapper-uri-base-demux.c @@ -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);