mirror of
				https://github.com/Rafostar/clapper.git
				synced 2025-10-30 18:05:40 +01:00 
			
		
		
		
	Merge pull request #438 from Rafostar/download-cache
clapper: Add media caching via download to local storage
This commit is contained in:
		
							
								
								
									
										57
									
								
								examples/clapper-gtk/download_cache/python/example.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										57
									
								
								examples/clapper-gtk/download_cache/python/example.py
									
									
									
									
									
										Executable file
									
								
							| @@ -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) | ||||||
| @@ -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_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_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); | void clapper_app_bus_post_error_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GError *error, const gchar *debug_info); | ||||||
|   | |||||||
| @@ -43,6 +43,7 @@ enum | |||||||
|   CLAPPER_APP_BUS_STRUCTURE_REFRESH_STREAMS, |   CLAPPER_APP_BUS_STRUCTURE_REFRESH_STREAMS, | ||||||
|   CLAPPER_APP_BUS_STRUCTURE_REFRESH_TIMELINE, |   CLAPPER_APP_BUS_STRUCTURE_REFRESH_TIMELINE, | ||||||
|   CLAPPER_APP_BUS_STRUCTURE_SIMPLE_SIGNAL, |   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_DESC_WITH_DETAILS_SIGNAL, | ||||||
|   CLAPPER_APP_BUS_STRUCTURE_ERROR_SIGNAL |   CLAPPER_APP_BUS_STRUCTURE_ERROR_SIGNAL | ||||||
| }; | }; | ||||||
| @@ -53,6 +54,7 @@ static ClapperBusQuark _structure_quarks[] = { | |||||||
|   {"refresh-streams", 0}, |   {"refresh-streams", 0}, | ||||||
|   {"refresh-timeline", 0}, |   {"refresh-timeline", 0}, | ||||||
|   {"simple-signal", 0}, |   {"simple-signal", 0}, | ||||||
|  |   {"object-desc-signal", 0}, | ||||||
|   {"desc-with-details-signal", 0}, |   {"desc-with-details-signal", 0}, | ||||||
|   {"error-signal", 0}, |   {"error-signal", 0}, | ||||||
|   {NULL, 0} |   {NULL, 0} | ||||||
| @@ -63,6 +65,7 @@ enum | |||||||
|   CLAPPER_APP_BUS_FIELD_UNKNOWN = 0, |   CLAPPER_APP_BUS_FIELD_UNKNOWN = 0, | ||||||
|   CLAPPER_APP_BUS_FIELD_PSPEC, |   CLAPPER_APP_BUS_FIELD_PSPEC, | ||||||
|   CLAPPER_APP_BUS_FIELD_SIGNAL_ID, |   CLAPPER_APP_BUS_FIELD_SIGNAL_ID, | ||||||
|  |   CLAPPER_APP_BUS_FIELD_OBJECT, | ||||||
|   CLAPPER_APP_BUS_FIELD_DESC, |   CLAPPER_APP_BUS_FIELD_DESC, | ||||||
|   CLAPPER_APP_BUS_FIELD_DETAILS, |   CLAPPER_APP_BUS_FIELD_DETAILS, | ||||||
|   CLAPPER_APP_BUS_FIELD_ERROR, |   CLAPPER_APP_BUS_FIELD_ERROR, | ||||||
| @@ -73,6 +76,7 @@ static ClapperBusQuark _field_quarks[] = { | |||||||
|   {"unknown", 0}, |   {"unknown", 0}, | ||||||
|   {"pspec", 0}, |   {"pspec", 0}, | ||||||
|   {"signal-id", 0}, |   {"signal-id", 0}, | ||||||
|  |   {"object", 0}, | ||||||
|   {"desc", 0}, |   {"desc", 0}, | ||||||
|   {"details", 0}, |   {"details", 0}, | ||||||
|   {"error", 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); |   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 | void | ||||||
| clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *self, | clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *self, | ||||||
|     GstObject *src, guint signal_id, |     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); |       _handle_refresh_timeline_msg (msg, structure); | ||||||
|     else if (quark == _STRUCTURE_QUARK (SIMPLE_SIGNAL)) |     else if (quark == _STRUCTURE_QUARK (SIMPLE_SIGNAL)) | ||||||
|       _handle_simple_signal_msg (msg, structure); |       _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)) |     else if (quark == _STRUCTURE_QUARK (ERROR_SIGNAL)) | ||||||
|       _handle_error_signal_msg (msg, structure); |       _handle_error_signal_msg (msg, structure); | ||||||
|     else if (quark == _STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL)) |     else if (quark == _STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL)) | ||||||
|   | |||||||
| @@ -37,6 +37,12 @@ void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, Gst | |||||||
| G_GNUC_INTERNAL | G_GNUC_INTERNAL | ||||||
| gboolean clapper_media_item_set_duration (ClapperMediaItem *item, gdouble duration, ClapperAppBus *app_bus); | 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 | G_GNUC_INTERNAL | ||||||
| void clapper_media_item_set_used (ClapperMediaItem *item, gboolean used); | void clapper_media_item_set_used (ClapperMediaItem *item, gboolean used); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,6 +50,8 @@ struct _ClapperMediaItem | |||||||
|   gchar *container_format; |   gchar *container_format; | ||||||
|   gdouble duration; |   gdouble duration; | ||||||
|  |  | ||||||
|  |   gchar *cache_uri; | ||||||
|  |  | ||||||
|   /* For shuffle */ |   /* For shuffle */ | ||||||
|   gboolean used; |   gboolean used; | ||||||
| }; | }; | ||||||
| @@ -450,6 +452,41 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco | |||||||
|   gst_object_unref (player); |   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 | void | ||||||
| clapper_media_item_set_used (ClapperMediaItem *self, gboolean used) | 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_unparent (GST_OBJECT_CAST (self->timeline)); | ||||||
|   gst_object_unref (self->timeline); |   gst_object_unref (self->timeline); | ||||||
|  |  | ||||||
|  |   g_free (self->cache_uri); | ||||||
|  |  | ||||||
|   G_OBJECT_CLASS (parent_class)->finalize (object); |   G_OBJECT_CLASS (parent_class)->finalize (object); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -827,6 +827,24 @@ _handle_element_msg (GstMessage *msg, ClapperPlayer *player) | |||||||
|  |  | ||||||
|     g_free (name); |     g_free (name); | ||||||
|     g_free (details); |     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); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -95,6 +95,8 @@ struct _ClapperPlayer | |||||||
|   gboolean video_enabled; |   gboolean video_enabled; | ||||||
|   gboolean audio_enabled; |   gboolean audio_enabled; | ||||||
|   gboolean subtitles_enabled; |   gboolean subtitles_enabled; | ||||||
|  |   gchar *download_dir; | ||||||
|  |   gboolean download_enabled; | ||||||
|   gdouble audio_offset; |   gdouble audio_offset; | ||||||
|   gdouble subtitle_offset; |   gdouble subtitle_offset; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ | |||||||
| #include "clapper-playbin-bus-private.h" | #include "clapper-playbin-bus-private.h" | ||||||
| #include "clapper-app-bus-private.h" | #include "clapper-app-bus-private.h" | ||||||
| #include "clapper-queue-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-list-private.h" | ||||||
| #include "clapper-stream-private.h" | #include "clapper-stream-private.h" | ||||||
| #include "clapper-video-stream-private.h" | #include "clapper-video-stream-private.h" | ||||||
| @@ -61,6 +61,7 @@ | |||||||
| #define DEFAULT_VIDEO_ENABLED TRUE | #define DEFAULT_VIDEO_ENABLED TRUE | ||||||
| #define DEFAULT_AUDIO_ENABLED TRUE | #define DEFAULT_AUDIO_ENABLED TRUE | ||||||
| #define DEFAULT_SUBTITLES_ENABLED TRUE | #define DEFAULT_SUBTITLES_ENABLED TRUE | ||||||
|  | #define DEFAULT_DOWNLOAD_ENABLED FALSE | ||||||
|  |  | ||||||
| #define GST_CAT_DEFAULT clapper_player_debug | #define GST_CAT_DEFAULT clapper_player_debug | ||||||
| GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); | GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); | ||||||
| @@ -90,6 +91,8 @@ enum | |||||||
|   PROP_VIDEO_ENABLED, |   PROP_VIDEO_ENABLED, | ||||||
|   PROP_AUDIO_ENABLED, |   PROP_AUDIO_ENABLED, | ||||||
|   PROP_SUBTITLES_ENABLED, |   PROP_SUBTITLES_ENABLED, | ||||||
|  |   PROP_DOWNLOAD_DIR, | ||||||
|  |   PROP_DOWNLOAD_ENABLED, | ||||||
|   PROP_AUDIO_OFFSET, |   PROP_AUDIO_OFFSET, | ||||||
|   PROP_SUBTITLE_OFFSET, |   PROP_SUBTITLE_OFFSET, | ||||||
|   PROP_SUBTITLE_FONT_DESC, |   PROP_SUBTITLE_FONT_DESC, | ||||||
| @@ -99,6 +102,7 @@ enum | |||||||
| enum | enum | ||||||
| { | { | ||||||
|   SIGNAL_SEEK_DONE, |   SIGNAL_SEEK_DONE, | ||||||
|  |   SIGNAL_DOWNLOAD_COMPLETE, | ||||||
|   SIGNAL_MISSING_PLUGIN, |   SIGNAL_MISSING_PLUGIN, | ||||||
|   SIGNAL_WARNING, |   SIGNAL_WARNING, | ||||||
|   SIGNAL_ERROR, |   SIGNAL_ERROR, | ||||||
| @@ -278,14 +282,15 @@ void | |||||||
| clapper_player_handle_playbin_flags_changed (ClapperPlayer *self, const GValue *value) | clapper_player_handle_playbin_flags_changed (ClapperPlayer *self, const GValue *value) | ||||||
| { | { | ||||||
|   gint flags; |   gint flags; | ||||||
|   gboolean video_enabled, audio_enabled, subtitles_enabled; |   gboolean video_enabled, audio_enabled, subtitles_enabled, download_enabled; | ||||||
|   gboolean video_changed, audio_changed, subtitles_changed; |   gboolean video_changed, audio_changed, subtitles_changed, download_changed; | ||||||
|  |  | ||||||
|   flags = g_value_get_flags (value); |   flags = g_value_get_flags (value); | ||||||
|  |  | ||||||
|   video_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_VIDEO) == CLAPPER_PLAYER_PLAY_FLAG_VIDEO); |   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); |   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); |   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); |   GST_OBJECT_LOCK (self); | ||||||
|  |  | ||||||
| @@ -295,6 +300,8 @@ clapper_player_handle_playbin_flags_changed (ClapperPlayer *self, const GValue * | |||||||
|     self->audio_enabled = audio_enabled; |     self->audio_enabled = audio_enabled; | ||||||
|   if ((subtitles_changed = self->subtitles_enabled != subtitles_enabled)) |   if ((subtitles_changed = self->subtitles_enabled != subtitles_enabled)) | ||||||
|     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); |   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, |     clapper_app_bus_post_prop_notify (self->app_bus, | ||||||
|         GST_OBJECT_CAST (self), param_specs[PROP_SUBTITLES_ENABLED]); |         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 | void | ||||||
| @@ -438,7 +450,7 @@ clapper_player_set_pending_item (ClapperPlayer *self, ClapperMediaItem *pending_ | |||||||
|  |  | ||||||
|   /* Might be NULL (e.g. after queue is cleared) */ |   /* Might be NULL (e.g. after queue is cleared) */ | ||||||
|   if (pending_item) { |   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); |     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 | static void | ||||||
| _about_to_finish_cb (GstElement *playbin, ClapperPlayer *self) | _about_to_finish_cb (GstElement *playbin, ClapperPlayer *self) | ||||||
| { | { | ||||||
| @@ -1505,6 +1561,106 @@ clapper_player_get_subtitles_enabled (ClapperPlayer *self) | |||||||
|   return enabled; |   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: |  * clapper_player_set_audio_offset: | ||||||
|  * @player: a #ClapperPlayer |  * @player: a #ClapperPlayer | ||||||
| @@ -1793,6 +1949,7 @@ clapper_player_thread_start (ClapperThreadedObject *threaded_object) | |||||||
|   for (i = 0; playbin_watchlist[i]; ++i) |   for (i = 0; playbin_watchlist[i]; ++i) | ||||||
|     gst_element_add_property_notify_watch (self->playbin, playbin_watchlist[i], TRUE); |     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); |   g_signal_connect (self->playbin, "about-to-finish", G_CALLBACK (_about_to_finish_cb), self); | ||||||
|  |  | ||||||
|   if (!self->use_playbin3) { |   if (!self->use_playbin3) { | ||||||
| @@ -1866,6 +2023,7 @@ clapper_player_init (ClapperPlayer *self) | |||||||
|   self->video_enabled = DEFAULT_VIDEO_ENABLED; |   self->video_enabled = DEFAULT_VIDEO_ENABLED; | ||||||
|   self->audio_enabled = DEFAULT_AUDIO_ENABLED; |   self->audio_enabled = DEFAULT_AUDIO_ENABLED; | ||||||
|   self->subtitles_enabled = DEFAULT_SUBTITLES_ENABLED; |   self->subtitles_enabled = DEFAULT_SUBTITLES_ENABLED; | ||||||
|  |   self->download_enabled = DEFAULT_DOWNLOAD_ENABLED; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void | static void | ||||||
| @@ -1924,6 +2082,8 @@ clapper_player_finalize (GObject *object) | |||||||
|   gst_clear_object (&self->pending_item); |   gst_clear_object (&self->pending_item); | ||||||
|   gst_clear_object (&self->played_item); |   gst_clear_object (&self->played_item); | ||||||
|  |  | ||||||
|  |   g_free (self->download_dir); | ||||||
|  |  | ||||||
|   G_OBJECT_CLASS (parent_class)->finalize (object); |   G_OBJECT_CLASS (parent_class)->finalize (object); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1991,6 +2151,12 @@ clapper_player_get_property (GObject *object, guint prop_id, | |||||||
|     case PROP_SUBTITLES_ENABLED: |     case PROP_SUBTITLES_ENABLED: | ||||||
|       g_value_set_boolean (value, clapper_player_get_subtitles_enabled (self)); |       g_value_set_boolean (value, clapper_player_get_subtitles_enabled (self)); | ||||||
|       break; |       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: |     case PROP_AUDIO_OFFSET: | ||||||
|       g_value_set_double (value, clapper_player_get_audio_offset (self)); |       g_value_set_double (value, clapper_player_get_audio_offset (self)); | ||||||
|       break; |       break; | ||||||
| @@ -2046,6 +2212,12 @@ clapper_player_set_property (GObject *object, guint prop_id, | |||||||
|     case PROP_SUBTITLES_ENABLED: |     case PROP_SUBTITLES_ENABLED: | ||||||
|       clapper_player_set_subtitles_enabled (self, g_value_get_boolean (value)); |       clapper_player_set_subtitles_enabled (self, g_value_get_boolean (value)); | ||||||
|       break; |       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: |     case PROP_AUDIO_OFFSET: | ||||||
|       clapper_player_set_audio_offset (self, g_value_get_double (value)); |       clapper_player_set_audio_offset (self, g_value_get_double (value)); | ||||||
|       break; |       break; | ||||||
| @@ -2251,6 +2423,52 @@ clapper_player_class_init (ClapperPlayerClass *klass) | |||||||
|       NULL, NULL, DEFAULT_SUBTITLES_ENABLED, |       NULL, NULL, DEFAULT_SUBTITLES_ENABLED, | ||||||
|       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |       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: |    * 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, |       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); |       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: |    * ClapperPlayer::missing-plugin: | ||||||
|    * @player: a #ClapperPlayer |    * @player: a #ClapperPlayer | ||||||
|   | |||||||
| @@ -101,6 +101,14 @@ void clapper_player_set_subtitles_enabled (ClapperPlayer *player, gboolean enabl | |||||||
|  |  | ||||||
| gboolean clapper_player_get_subtitles_enabled (ClapperPlayer *player); | 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); | void clapper_player_set_audio_offset (ClapperPlayer *player, gdouble offset); | ||||||
|  |  | ||||||
| gdouble clapper_player_get_audio_offset (ClapperPlayer *player); | gdouble clapper_player_get_audio_offset (ClapperPlayer *player); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user