diff --git a/examples/clapper-gtk/download_cache/python/example.py b/examples/clapper-gtk/download_cache/python/example.py new file mode 100755 index 00000000..a542d825 --- /dev/null +++ b/examples/clapper-gtk/download_cache/python/example.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import gi +gi.require_version('Adw', '1') +gi.require_version('Clapper', '0.0') +gi.require_version('ClapperGtk', '0.0') +gi.require_version('GLib', '2.0') +gi.require_version('Gtk', '4.0') +from gi.repository import Adw, Clapper, ClapperGtk, GLib, Gtk +import shutil + +Clapper.init(None) + +download_dir = GLib.build_filenamev([GLib.get_user_cache_dir(), "example_download_dir", None]) +print('Using ceche directory: {0}'.format(download_dir)) + +def on_download_complete(player, item, location): + # Media downloaded. Data from this file is still used for current playback (including seeking). + print('Download complete: {0} => {1}'.format(item.props.uri, location)) + +def on_activate(app): + win = Gtk.ApplicationWindow(application=app, default_width=640, default_height=396) + video = ClapperGtk.Video() + controls = ClapperGtk.SimpleControls(fullscreenable=False) + + # Enable local storage caching and monitor it + video.props.player.set_download_dir(download_dir) + video.props.player.set_download_enabled(True) + video.props.player.connect('download-complete', on_download_complete) + + # Configure playback + video.props.player.props.queue.set_progression_mode(Clapper.QueueProgressionMode.CAROUSEL) + video.props.player.set_autoplay(True) + + # Create and add media for playback + item_1 = Clapper.MediaItem(uri='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4') + item_2 = Clapper.MediaItem(uri='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4') + video.props.player.props.queue.add_item(item_1) + video.props.player.props.queue.add_item(item_2) + + # Assemble window + video.add_fading_overlay(controls) + win.set_child(video) + win.present() + +# Create a new application +app = Adw.Application(application_id='com.example.ClapperDownloadCache') +app.connect('activate', on_activate) + +# Run the application +app.run(None) + +# Finally app should cleanup before exit. Possibly moving data to +# another dir if it wants to use it on next run and deleting what's +# left (so any unfinished downloads will also be removed). +print('Cleanup') +shutil.rmtree(download_dir) 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);