From f67d5bef2e79545e3b2f1fdbe70a906459f4972a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Sun, 28 Apr 2024 20:59:44 +0200 Subject: [PATCH] clapper: Add media caching via download to local storage The aim here is to stream an online video/audio while also at the same time download/cache it to disk (excluding adaptive content). After download is complete, further playback and seeking are done using the locally cached file. This functionality uses GStreamer "downloadbuffer" element. Player will emit a signal with a local download location after it completes, so application will know where downloaded file for media item is stored in case it wants to reuse it in the future. It is up to application to set download dir and later manage downloaded content in it, removing files its not going to use on next application run and any incomplete downloads. --- src/lib/clapper/clapper-app-bus-private.h | 2 + src/lib/clapper/clapper-app-bus.c | 37 +++ src/lib/clapper/clapper-media-item-private.h | 6 + src/lib/clapper/clapper-media-item.c | 39 +++ src/lib/clapper/clapper-playbin-bus.c | 18 ++ src/lib/clapper/clapper-player-private.h | 2 + src/lib/clapper/clapper-player.c | 242 ++++++++++++++++++- src/lib/clapper/clapper-player.h | 8 + 8 files changed, 350 insertions(+), 4 deletions(-) diff --git a/src/lib/clapper/clapper-app-bus-private.h b/src/lib/clapper/clapper-app-bus-private.h index b64ac862..8291c3f9 100644 --- a/src/lib/clapper/clapper-app-bus-private.h +++ b/src/lib/clapper/clapper-app-bus-private.h @@ -47,6 +47,8 @@ void clapper_app_bus_post_refresh_timeline (ClapperAppBus *app_bus, GstObject *s void clapper_app_bus_post_simple_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id); +void clapper_app_bus_post_object_desc_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GstObject *object, const gchar *desc); + void clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, const gchar *desc, const gchar *details); void clapper_app_bus_post_error_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GError *error, const gchar *debug_info); diff --git a/src/lib/clapper/clapper-app-bus.c b/src/lib/clapper/clapper-app-bus.c index ab505b35..8e19dde5 100644 --- a/src/lib/clapper/clapper-app-bus.c +++ b/src/lib/clapper/clapper-app-bus.c @@ -43,6 +43,7 @@ enum CLAPPER_APP_BUS_STRUCTURE_REFRESH_STREAMS, CLAPPER_APP_BUS_STRUCTURE_REFRESH_TIMELINE, CLAPPER_APP_BUS_STRUCTURE_SIMPLE_SIGNAL, + CLAPPER_APP_BUS_STRUCTURE_OBJECT_DESC_SIGNAL, CLAPPER_APP_BUS_STRUCTURE_DESC_WITH_DETAILS_SIGNAL, CLAPPER_APP_BUS_STRUCTURE_ERROR_SIGNAL }; @@ -53,6 +54,7 @@ static ClapperBusQuark _structure_quarks[] = { {"refresh-streams", 0}, {"refresh-timeline", 0}, {"simple-signal", 0}, + {"object-desc-signal", 0}, {"desc-with-details-signal", 0}, {"error-signal", 0}, {NULL, 0} @@ -63,6 +65,7 @@ enum CLAPPER_APP_BUS_FIELD_UNKNOWN = 0, CLAPPER_APP_BUS_FIELD_PSPEC, CLAPPER_APP_BUS_FIELD_SIGNAL_ID, + CLAPPER_APP_BUS_FIELD_OBJECT, CLAPPER_APP_BUS_FIELD_DESC, CLAPPER_APP_BUS_FIELD_DETAILS, CLAPPER_APP_BUS_FIELD_ERROR, @@ -73,6 +76,7 @@ static ClapperBusQuark _field_quarks[] = { {"unknown", 0}, {"pspec", 0}, {"signal-id", 0}, + {"object", 0}, {"desc", 0}, {"details", 0}, {"error", 0}, @@ -177,6 +181,37 @@ _handle_simple_signal_msg (GstMessage *msg, const GstStructure *structure) g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0); } +void +clapper_app_bus_post_object_desc_signal (ClapperAppBus *self, + GstObject *src, guint signal_id, + GstObject *object, const gchar *desc) +{ + GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (OBJECT_DESC_SIGNAL), + _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id, + _FIELD_QUARK (OBJECT), GST_TYPE_OBJECT, object, + _FIELD_QUARK (DESC), G_TYPE_STRING, desc, + NULL); + gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); +} + +static inline void +_handle_object_desc_signal_msg (GstMessage *msg, const GstStructure *structure) +{ + guint signal_id = 0; + GstObject *object = NULL; + gchar *desc = NULL; + + gst_structure_id_get (structure, + _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id, + _FIELD_QUARK (OBJECT), GST_TYPE_OBJECT, &object, + _FIELD_QUARK (DESC), G_TYPE_STRING, &desc, + NULL); + g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0, object, desc); + + gst_object_unref (object); + g_free (desc); +} + void clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *self, GstObject *src, guint signal_id, @@ -253,6 +288,8 @@ clapper_app_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G _handle_refresh_timeline_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (SIMPLE_SIGNAL)) _handle_simple_signal_msg (msg, structure); + else if (quark == _STRUCTURE_QUARK (OBJECT_DESC_SIGNAL)) + _handle_object_desc_signal_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (ERROR_SIGNAL)) _handle_error_signal_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL)) diff --git a/src/lib/clapper/clapper-media-item-private.h b/src/lib/clapper/clapper-media-item-private.h index ca1faf55..2ddc5bea 100644 --- a/src/lib/clapper/clapper-media-item-private.h +++ b/src/lib/clapper/clapper-media-item-private.h @@ -37,6 +37,12 @@ void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, Gst G_GNUC_INTERNAL gboolean clapper_media_item_set_duration (ClapperMediaItem *item, gdouble duration, ClapperAppBus *app_bus); +G_GNUC_INTERNAL +void clapper_media_item_set_cache_location (ClapperMediaItem *item, const gchar *location); + +G_GNUC_INTERNAL +const gchar * clapper_media_item_get_playback_uri (ClapperMediaItem *item); + G_GNUC_INTERNAL void clapper_media_item_set_used (ClapperMediaItem *item, gboolean used); diff --git a/src/lib/clapper/clapper-media-item.c b/src/lib/clapper/clapper-media-item.c index a83caea1..16c149f6 100644 --- a/src/lib/clapper/clapper-media-item.c +++ b/src/lib/clapper/clapper-media-item.c @@ -50,6 +50,8 @@ struct _ClapperMediaItem gchar *container_format; gdouble duration; + gchar *cache_uri; + /* For shuffle */ gboolean used; }; @@ -450,6 +452,41 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco gst_object_unref (player); } +/* XXX: Must be set from player thread */ +inline void +clapper_media_item_set_cache_location (ClapperMediaItem *self, const gchar *location) +{ + g_free (self->cache_uri); + self->cache_uri = g_filename_to_uri (location, NULL, NULL); + GST_DEBUG_OBJECT (self, "Set cache URI: \"%s\"", self->cache_uri); +} + +/* XXX: Can only be read from player thread. + * Returns cache URI if available, item URI otherwise. */ +inline const gchar * +clapper_media_item_get_playback_uri (ClapperMediaItem *self) +{ + if (self->cache_uri) { + GFile *file = g_file_new_for_uri (self->cache_uri); + gboolean exists; + + /* It is an app error if it removes files in non-stopped state, + * and this function is only called when starting playback */ + exists = g_file_query_exists (file, NULL); + g_object_unref (file); + + if (exists) + return self->cache_uri; + + /* Do not test file existence next time */ + GST_DEBUG_OBJECT (self, "Cleared cache URI for non-existing file: \"%s\"", + self->cache_uri); + g_clear_pointer (&self->cache_uri, g_free); + } + + return self->uri; +} + void clapper_media_item_set_used (ClapperMediaItem *self, gboolean used) { @@ -505,6 +542,8 @@ clapper_media_item_finalize (GObject *object) gst_object_unparent (GST_OBJECT_CAST (self->timeline)); gst_object_unref (self->timeline); + g_free (self->cache_uri); + G_OBJECT_CLASS (parent_class)->finalize (object); } diff --git a/src/lib/clapper/clapper-playbin-bus.c b/src/lib/clapper/clapper-playbin-bus.c index 691cbf19..33819c5d 100644 --- a/src/lib/clapper/clapper-playbin-bus.c +++ b/src/lib/clapper/clapper-playbin-bus.c @@ -827,6 +827,24 @@ _handle_element_msg (GstMessage *msg, ClapperPlayer *player) g_free (name); g_free (details); + } else if (gst_message_has_name (msg, "GstCacheDownloadComplete")) { + const GstStructure *structure; + const gchar *location; + guint signal_id; + + if (G_UNLIKELY (player->played_item == NULL)) + return; + + structure = gst_message_get_structure (msg); + location = gst_structure_get_string (structure, "location"); + signal_id = g_signal_lookup ("download-complete", CLAPPER_TYPE_PLAYER); + + GST_INFO_OBJECT (player, "Download complete: %s", location); + clapper_media_item_set_cache_location (player->played_item, location); + + clapper_app_bus_post_object_desc_signal (player->app_bus, + GST_OBJECT_CAST (player), signal_id, + GST_OBJECT_CAST (player->played_item), location); } } diff --git a/src/lib/clapper/clapper-player-private.h b/src/lib/clapper/clapper-player-private.h index 0512cbbe..61e4528b 100644 --- a/src/lib/clapper/clapper-player-private.h +++ b/src/lib/clapper/clapper-player-private.h @@ -95,6 +95,8 @@ struct _ClapperPlayer gboolean video_enabled; gboolean audio_enabled; gboolean subtitles_enabled; + gchar *download_dir; + gboolean download_enabled; gdouble audio_offset; gdouble subtitle_offset; }; diff --git a/src/lib/clapper/clapper-player.c b/src/lib/clapper/clapper-player.c index 4d7fdc49..ed8e5604 100644 --- a/src/lib/clapper/clapper-player.c +++ b/src/lib/clapper/clapper-player.c @@ -43,7 +43,7 @@ #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-queue-private.h" -#include "clapper-media-item.h" +#include "clapper-media-item-private.h" #include "clapper-stream-list-private.h" #include "clapper-stream-private.h" #include "clapper-video-stream-private.h" @@ -61,6 +61,7 @@ #define DEFAULT_VIDEO_ENABLED TRUE #define DEFAULT_AUDIO_ENABLED TRUE #define DEFAULT_SUBTITLES_ENABLED TRUE +#define DEFAULT_DOWNLOAD_ENABLED FALSE #define GST_CAT_DEFAULT clapper_player_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -90,6 +91,8 @@ enum PROP_VIDEO_ENABLED, PROP_AUDIO_ENABLED, PROP_SUBTITLES_ENABLED, + PROP_DOWNLOAD_DIR, + PROP_DOWNLOAD_ENABLED, PROP_AUDIO_OFFSET, PROP_SUBTITLE_OFFSET, PROP_SUBTITLE_FONT_DESC, @@ -99,6 +102,7 @@ enum enum { SIGNAL_SEEK_DONE, + SIGNAL_DOWNLOAD_COMPLETE, SIGNAL_MISSING_PLUGIN, SIGNAL_WARNING, SIGNAL_ERROR, @@ -278,14 +282,15 @@ void clapper_player_handle_playbin_flags_changed (ClapperPlayer *self, const GValue *value) { gint flags; - gboolean video_enabled, audio_enabled, subtitles_enabled; - gboolean video_changed, audio_changed, subtitles_changed; + gboolean video_enabled, audio_enabled, subtitles_enabled, download_enabled; + gboolean video_changed, audio_changed, subtitles_changed, download_changed; flags = g_value_get_flags (value); video_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_VIDEO) == CLAPPER_PLAYER_PLAY_FLAG_VIDEO); audio_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_AUDIO) == CLAPPER_PLAYER_PLAY_FLAG_AUDIO); subtitles_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_TEXT) == CLAPPER_PLAYER_PLAY_FLAG_TEXT); + download_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD) == CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD); GST_OBJECT_LOCK (self); @@ -295,6 +300,8 @@ clapper_player_handle_playbin_flags_changed (ClapperPlayer *self, const GValue * self->audio_enabled = audio_enabled; if ((subtitles_changed = self->subtitles_enabled != subtitles_enabled)) self->subtitles_enabled = subtitles_enabled; + if ((download_changed = self->download_enabled != download_enabled)) + self->download_enabled = download_enabled; GST_OBJECT_UNLOCK (self); @@ -313,6 +320,11 @@ clapper_player_handle_playbin_flags_changed (ClapperPlayer *self, const GValue * clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_SUBTITLES_ENABLED]); } + if (download_changed) { + GST_INFO_OBJECT (self, "Download enabled: %s", (download_enabled) ? "yes" : "no"); + clapper_app_bus_post_prop_notify (self->app_bus, + GST_OBJECT_CAST (self), param_specs[PROP_DOWNLOAD_ENABLED]); + } } void @@ -438,7 +450,7 @@ clapper_player_set_pending_item (ClapperPlayer *self, ClapperMediaItem *pending_ /* Might be NULL (e.g. after queue is cleared) */ if (pending_item) { - uri = clapper_media_item_get_uri (pending_item); + uri = clapper_media_item_get_playback_uri (pending_item); suburi = clapper_media_item_get_suburi (pending_item); } @@ -725,6 +737,50 @@ clapper_player_reset (ClapperPlayer *self, gboolean pending_dispose) } } +static inline gchar * +_make_download_template (ClapperPlayer *self) +{ + gchar *download_template = NULL; + + GST_OBJECT_LOCK (self); + + if (self->download_enabled && self->download_dir) { + if (g_mkdir_with_parents (self->download_dir, 0755) == 0) { + download_template = g_build_filename (self->download_dir, "XXXXXX", NULL); + } else { + GST_ERROR_OBJECT (self, "Could not create download dir: \"%s\"", self->download_dir); + } + } + + GST_OBJECT_UNLOCK (self); + + return download_template; +} + +static void +_element_setup_cb (GstElement *playbin, GstElement *element, ClapperPlayer *self) +{ + GstElementFactory *factory = gst_element_get_factory (element); + + if (G_UNLIKELY (factory == NULL)) + return; + + GST_INFO_OBJECT (self, "Element setup: %s", GST_OBJECT_NAME (factory)); + + if (strcmp (GST_OBJECT_NAME (factory), "downloadbuffer") == 0) { + gchar *download_template; + + /* Only set props if we have download template */ + if ((download_template = _make_download_template (self))) { + g_object_set (element, + "temp-template", download_template, + "temp-remove", FALSE, + NULL); + g_free (download_template); + } + } +} + static void _about_to_finish_cb (GstElement *playbin, ClapperPlayer *self) { @@ -1505,6 +1561,106 @@ clapper_player_get_subtitles_enabled (ClapperPlayer *self) return enabled; } +/** + * clapper_player_set_download_dir: + * @player: a #ClapperPlayer + * @path: (type filename): the path of a directory to use for media downloads + * + * Set a directory that @player will use to store downloads. + * + * See [property@Clapper.Player:download-enabled] description for more + * info how this works. + * + * Since: 0.8 + */ +void +clapper_player_set_download_dir (ClapperPlayer *self, const gchar *path) +{ + gboolean changed; + + g_return_if_fail (CLAPPER_IS_PLAYER (self)); + g_return_if_fail (path != NULL); + + GST_OBJECT_LOCK (self); + changed = g_set_str (&self->download_dir, path); + GST_OBJECT_UNLOCK (self); + + if (changed) { + GST_INFO_OBJECT (self, "Current download dir: %s", path); + clapper_app_bus_post_prop_notify (self->app_bus, + GST_OBJECT_CAST (self), param_specs[PROP_DOWNLOAD_DIR]); + } +} + +/** + * clapper_player_get_download_dir: + * @player: a #ClapperPlayer + * + * Get path to a directory set for media downloads. + * + * Returns: (type filename) (transfer full) (nullable): the path of a directory + * set for media downloads or %NULL if no directory was set yet. + * + * Since: 0.8 + */ +gchar * +clapper_player_get_download_dir (ClapperPlayer *self) +{ + gchar *download_dir; + + g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); + + GST_OBJECT_LOCK (self); + download_dir = g_strdup (self->download_dir); + GST_OBJECT_UNLOCK (self); + + return download_dir; +} + +/** + * clapper_player_set_download_enabled: + * @player: a #ClapperPlayer + * @enabled: whether enabled + * + * Set whether player should attempt progressive download buffering. + * + * For this to actually work a [property@Clapper.Player:download-dir] + * must also be set. + * + * Since: 0.8 + */ +void +clapper_player_set_download_enabled (ClapperPlayer *self, gboolean enabled) +{ + g_return_if_fail (CLAPPER_IS_PLAYER (self)); + + clapper_playbin_bus_post_set_play_flag (self->bus, CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD, enabled); +} + +/** + * clapper_player_get_download_enabled: + * @player: a #ClapperPlayer + * + * Get whether progressive download buffering is enabled. + * + * Returns: %TRUE if enabled, %FALSE otherwise. + * + * Since: 0.8 + */ +gboolean +clapper_player_get_download_enabled (ClapperPlayer *self) +{ + gboolean enabled; + + g_return_val_if_fail (CLAPPER_IS_PLAYER (self), FALSE); + + GST_OBJECT_LOCK (self); + enabled = self->download_enabled; + GST_OBJECT_UNLOCK (self); + + return enabled; +} + /** * clapper_player_set_audio_offset: * @player: a #ClapperPlayer @@ -1793,6 +1949,7 @@ clapper_player_thread_start (ClapperThreadedObject *threaded_object) for (i = 0; playbin_watchlist[i]; ++i) gst_element_add_property_notify_watch (self->playbin, playbin_watchlist[i], TRUE); + g_signal_connect (self->playbin, "element-setup", G_CALLBACK (_element_setup_cb), self); g_signal_connect (self->playbin, "about-to-finish", G_CALLBACK (_about_to_finish_cb), self); if (!self->use_playbin3) { @@ -1866,6 +2023,7 @@ clapper_player_init (ClapperPlayer *self) self->video_enabled = DEFAULT_VIDEO_ENABLED; self->audio_enabled = DEFAULT_AUDIO_ENABLED; self->subtitles_enabled = DEFAULT_SUBTITLES_ENABLED; + self->download_enabled = DEFAULT_DOWNLOAD_ENABLED; } static void @@ -1924,6 +2082,8 @@ clapper_player_finalize (GObject *object) gst_clear_object (&self->pending_item); gst_clear_object (&self->played_item); + g_free (self->download_dir); + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -1991,6 +2151,12 @@ clapper_player_get_property (GObject *object, guint prop_id, case PROP_SUBTITLES_ENABLED: g_value_set_boolean (value, clapper_player_get_subtitles_enabled (self)); break; + case PROP_DOWNLOAD_DIR: + g_value_take_string (value, clapper_player_get_download_dir (self)); + break; + case PROP_DOWNLOAD_ENABLED: + g_value_set_boolean (value, clapper_player_get_download_enabled (self)); + break; case PROP_AUDIO_OFFSET: g_value_set_double (value, clapper_player_get_audio_offset (self)); break; @@ -2046,6 +2212,12 @@ clapper_player_set_property (GObject *object, guint prop_id, case PROP_SUBTITLES_ENABLED: clapper_player_set_subtitles_enabled (self, g_value_get_boolean (value)); break; + case PROP_DOWNLOAD_DIR: + clapper_player_set_download_dir (self, g_value_get_string (value)); + break; + case PROP_DOWNLOAD_ENABLED: + clapper_player_set_download_enabled (self, g_value_get_boolean (value)); + break; case PROP_AUDIO_OFFSET: clapper_player_set_audio_offset (self, g_value_get_double (value)); break; @@ -2251,6 +2423,52 @@ clapper_player_class_init (ClapperPlayerClass *klass) NULL, NULL, DEFAULT_SUBTITLES_ENABLED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * ClapperPlayer:download-dir: + * + * A directory that @player will use to download network content + * when [property@Clapper.Player:download-enabled] is set to %TRUE. + * + * If directory at @path does not exist, it will be automatically created. + * + * Since: 0.8 + */ + param_specs[PROP_DOWNLOAD_DIR] = g_param_spec_string ("download-dir", + NULL, NULL, NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperPlayer:download-enabled: + * + * Whether progressive download buffering is enabled. + * + * If progressive download is enabled and [property@Clapper.Player:download-dir] + * is set, streamed network content will be cached to the disk space instead + * of memory whenever possible. This allows for faster seeking through + * currently played media. + * + * Not every type of content is download applicable. Mainly applies to + * web content that does not use adaptive streaming. + * + * Once data that media item URI points to is fully downloaded, player + * will emit [signal@Clapper.Player::download-complete] signal with a + * location of downloaded file. + * + * Playing again the exact same [class@Clapper.MediaItem] object that was + * previously fully downloaded will cause player to automatically use that + * cached file if it still exists, avoiding any further network requests. + * + * Please note that player will not delete nor manage downloaded content. + * It is up to application to cleanup data in created cache directory + * (e.g. before app exits), in order to remove any downloads that app + * is not going to use next time it is run and incomplete ones. + * + * Since: 0.8 + */ + param_specs[PROP_DOWNLOAD_ENABLED] = g_param_spec_boolean ("download-enabled", + NULL, NULL, DEFAULT_DOWNLOAD_ENABLED, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** * ClapperPlayer:audio-offset: * @@ -2288,6 +2506,22 @@ clapper_player_class_init (ClapperPlayerClass *klass) G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + /** + * ClapperPlayer::download-complete: + * @player: a #ClapperPlayer + * @item: a #ClapperMediaItem + * @location: (type filename): a path to downloaded file + * + * Media was fully downloaded to local cache directory. This signal will + * be only emitted when progressive download buffering is enabled by + * setting [property@Clapper.Player:download-enabled] property to %TRUE. + * + * Since: 0.8 + */ + signals[SIGNAL_DOWNLOAD_COMPLETE] = g_signal_new ("download-complete", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + 0, NULL, NULL, NULL, G_TYPE_NONE, 2, CLAPPER_TYPE_MEDIA_ITEM, G_TYPE_STRING); + /** * ClapperPlayer::missing-plugin: * @player: a #ClapperPlayer diff --git a/src/lib/clapper/clapper-player.h b/src/lib/clapper/clapper-player.h index 993eb1d1..d5b62264 100644 --- a/src/lib/clapper/clapper-player.h +++ b/src/lib/clapper/clapper-player.h @@ -101,6 +101,14 @@ void clapper_player_set_subtitles_enabled (ClapperPlayer *player, gboolean enabl gboolean clapper_player_get_subtitles_enabled (ClapperPlayer *player); +void clapper_player_set_download_dir (ClapperPlayer *player, const gchar *path); + +gchar * clapper_player_get_download_dir (ClapperPlayer *player); + +void clapper_player_set_download_enabled (ClapperPlayer *player, gboolean enabled); + +gboolean clapper_player_get_download_enabled (ClapperPlayer *player); + void clapper_player_set_audio_offset (ClapperPlayer *player, gdouble offset); gdouble clapper_player_get_audio_offset (ClapperPlayer *player);