diff --git a/lib/gst/clapper/clapper.h b/lib/gst/clapper/clapper.h index ec3b5c40..54fb749e 100644 --- a/lib/gst/clapper/clapper.h +++ b/lib/gst/clapper/clapper.h @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include diff --git a/lib/gst/clapper/gstclapper-mpris.c b/lib/gst/clapper/gstclapper-mpris.c index fff485f0..f4d22afb 100644 --- a/lib/gst/clapper/gstclapper-mpris.c +++ b/lib/gst/clapper/gstclapper-mpris.c @@ -392,11 +392,17 @@ handle_open_uri_cb (GstClapperMprisMediaPlayer2Player * player_skeleton, gpointer user_data) { GstClapper *clapper = GST_CLAPPER (user_data); + GstClapperPlaylist *playlist; + GstClapperPlaylistItem *item; GST_DEBUG ("Handle OpenUri"); - /* FIXME: set one item playlist instead */ - gst_clapper_set_uri (clapper, uri); + playlist = gst_clapper_playlist_new (); + item = gst_clapper_playlist_item_new (uri); + + gst_clapper_playlist_append (playlist, item); + + gst_clapper_set_playlist (clapper, playlist); gst_clapper_mpris_media_player2_player_complete_open_uri (player_skeleton, invocation); return TRUE; diff --git a/lib/gst/clapper/gstclapper-playlist-item-private.h b/lib/gst/clapper/gstclapper-playlist-item-private.h new file mode 100644 index 00000000..e5ecf9fb --- /dev/null +++ b/lib/gst/clapper/gstclapper-playlist-item-private.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_PLAYLIST_ITEM_PRIVATE_H__ +#define __GST_CLAPPER_PLAYLIST_ITEM_PRIVATE_H__ + +#include "gstclapper-playlist.h" + +struct _GstClapperPlaylistItem +{ + GstObject parent; + + /* ID of the playlist this item belongs to */ + gchar *owner_uuid; + gint id; + + gchar *uri; + gchar *suburi; + gchar *custom_title; + + /* Signals */ + gulong activated_signal_id; +}; + +struct _GstClapperPlaylistItemClass +{ + GstObjectClass parent_class; +}; + +G_GNUC_INTERNAL +void gst_clapper_playlist_item_mark_added (GstClapperPlaylistItem *item, GstClapperPlaylist *playlist); + +#endif /* __GST_CLAPPER_PLAYLIST_ITEM_PRIVATE_H__ */ diff --git a/lib/gst/clapper/gstclapper-playlist-item.c b/lib/gst/clapper/gstclapper-playlist-item.c new file mode 100644 index 00000000..09f5cb56 --- /dev/null +++ b/lib/gst/clapper/gstclapper-playlist-item.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-playlist-item.h" +#include "gstclapper-playlist-item-private.h" +#include "gstclapper-playlist-private.h" + +enum +{ + PROP_0, + PROP_URI, + PROP_SUBURI, + PROP_CUSTOM_TITLE, + PROP_LAST +}; + +enum +{ + SIGNAL_ACTIVATED, + SIGNAL_LAST +}; + +#define parent_class gst_clapper_playlist_item_parent_class +G_DEFINE_TYPE (GstClapperPlaylistItem, gst_clapper_playlist_item, GST_TYPE_OBJECT); + +static guint signals[SIGNAL_LAST] = { 0, }; +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void gst_clapper_playlist_item_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_clapper_playlist_item_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_clapper_playlist_item_dispose (GObject * object); +static void gst_clapper_playlist_item_finalize (GObject * object); + +static void +gst_clapper_playlist_item_init (GstClapperPlaylistItem * self) +{ + self->owner_uuid = NULL; + self->id = -1; + + self->uri = NULL; + self->suburi = NULL; + self->custom_title = NULL; +} + +static void +gst_clapper_playlist_item_class_init (GstClapperPlaylistItemClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_clapper_playlist_item_set_property; + gobject_class->get_property = gst_clapper_playlist_item_get_property; + gobject_class->dispose = gst_clapper_playlist_item_dispose; + gobject_class->finalize = gst_clapper_playlist_item_finalize; + + param_specs[PROP_URI] = g_param_spec_string ("uri", + "URI", "Playlist Item URI", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", + "Subtitle URI", "Playlist Item Subtitle URI", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_CUSTOM_TITLE] = g_param_spec_string ("custom-title", + "Custom Title", "Playlist Item Custom Title", NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + signals[SIGNAL_ACTIVATED] = + g_signal_new ("activated", 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, G_TYPE_INVALID); +} + +static void +gst_clapper_playlist_item_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstClapperPlaylistItem *self = GST_CLAPPER_PLAYLIST_ITEM (object); + + switch (prop_id) { + case PROP_URI: + self->uri = g_value_dup_string (value); + break; + case PROP_SUBURI: + g_free (self->suburi); + self->suburi = g_value_dup_string (value); + break; + case PROP_CUSTOM_TITLE: + self->custom_title = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_playlist_item_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstClapperPlaylistItem *self = GST_CLAPPER_PLAYLIST_ITEM (object); + + switch (prop_id) { + case PROP_URI: + g_value_set_string (value, self->uri); + break; + case PROP_SUBURI: + g_value_set_string (value, self->suburi); + break; + case PROP_CUSTOM_TITLE: + g_value_set_string (value, self->custom_title); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_playlist_item_dispose (GObject * object) +{ + GstClapperPlaylistItem *self = GST_CLAPPER_PLAYLIST_ITEM (object); + + if (self->activated_signal_id) { + g_signal_handler_disconnect (self, self->activated_signal_id); + self->activated_signal_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_clapper_playlist_item_finalize (GObject * object) +{ + GstClapperPlaylistItem *self = GST_CLAPPER_PLAYLIST_ITEM (object); + + g_free (self->owner_uuid); + + g_free (self->uri); + g_free (self->suburi); + g_free (self->custom_title); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +item_activate_cb (GstClapperPlaylistItem * self, GParamSpec * pspec, + GstClapperPlaylist * playlist) +{ + gst_clapper_playlist_emit_item_activated (playlist, self); +} + +void +gst_clapper_playlist_item_mark_added (GstClapperPlaylistItem * self, + GstClapperPlaylist * playlist) +{ + GST_OBJECT_LOCK (self); + + self->owner_uuid = g_strdup (playlist->uuid); + self->id = playlist->id_count; + + self->activated_signal_id = g_signal_connect (self, "activated", + G_CALLBACK (item_activate_cb), playlist); + + GST_OBJECT_UNLOCK (self); +} + +/** + * gst_clapper_playlist_item_new: + * + * Creates a new #GstClapperPlaylistItem. + * + * Returns: (transfer full): a new #GstClapperPlaylistItem object. + */ +GstClapperPlaylistItem * +gst_clapper_playlist_item_new (const gchar * uri) +{ + return g_object_new (GST_TYPE_CLAPPER_PLAYLIST_ITEM, "uri", uri, NULL); +} + +/** + * gst_clapper_playlist_item_new_titled: + * @uri: An URI pointing to media + * @custom_title: A custom title for this item + * + * Creates a new #GstClapperPlaylistItem with a custom title. + * + * Normally item title is obtained from media info or local filename, + * use this function for online sources where media title cannot be + * determined or if you want to override original title for some reason. + * + * Returns: (transfer full): a new #GstClapperPlaylistItem object. + */ +GstClapperPlaylistItem * +gst_clapper_playlist_item_new_titled (const gchar * uri, + const gchar * custom_title) +{ + return g_object_new (GST_TYPE_CLAPPER_PLAYLIST_ITEM, "uri", uri, + "custom_title", custom_title, NULL); +} + +/** + * gst_clapper_playlist_item_copy: + * @item: #GstClapperPlaylistItem + * + * Duplicates a #GstClapperPlaylistItem. + * + * Duplicated items do not belong to any playlist. + * Use this function if you want to append the same + * media into another #GstClapperPlaylist instance. + * + * Returns: (transfer full): a new #GstClapperPlaylistItem object. + */ +GstClapperPlaylistItem * +gst_clapper_playlist_item_copy (GstClapperPlaylistItem * source) +{ + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST_ITEM (source), NULL); + + return g_object_new (GST_TYPE_CLAPPER_PLAYLIST_ITEM, "uri", source->uri, + "suburi", source->suburi, "custom-title", source->custom_title, NULL); +} + +/** + * gst_clapper_playlist_item_set_suburi: + * @item: #GstClapperPlaylistItem + * @suburi: subtitle URI + * + * Sets the external subtitle URI. + */ +void +gst_clapper_playlist_item_set_suburi (GstClapperPlaylistItem * self, + const gchar * suburi) +{ +/* TODO: When setting this property for an item that is currently active, + * it should be combined with a call to + * gst_clapper_set_subtitle_track_enabled(Clapper, TRUE), + * so the subtitles are actually rendered. + */ + g_return_if_fail (GST_IS_CLAPPER_PLAYLIST_ITEM (self)); + + g_object_set (self, "suburi", suburi, NULL); +} + +/** + * gst_clapper_playlist_item_activate: + * @item: #GstClapperPlaylistItem + * + * Activates the #GstClapperPlaylistItem. + */ +void +gst_clapper_playlist_item_activate (GstClapperPlaylistItem * self) +{ + g_return_if_fail (GST_IS_CLAPPER_PLAYLIST_ITEM (self)); + + g_signal_emit (self, signals[SIGNAL_ACTIVATED], 0); +} diff --git a/lib/gst/clapper/gstclapper-playlist-item.h b/lib/gst/clapper/gstclapper-playlist-item.h new file mode 100644 index 00000000..b90b437b --- /dev/null +++ b/lib/gst/clapper/gstclapper-playlist-item.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_PLAYLIST_ITEM_H__ +#define __GST_CLAPPER_PLAYLIST_ITEM_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GstClapperPlaylistItem GstClapperPlaylistItem; +typedef struct _GstClapperPlaylistItemClass GstClapperPlaylistItemClass; + +#define GST_TYPE_CLAPPER_PLAYLIST_ITEM (gst_clapper_playlist_item_get_type ()) +#define GST_IS_CLAPPER_PLAYLIST_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_PLAYLIST_ITEM)) +#define GST_IS_CLAPPER_PLAYLIST_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_PLAYLIST_ITEM)) +#define GST_CLAPPER_PLAYLIST_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_PLAYLIST_ITEM, GstClapperPlaylistItemClass)) +#define GST_CLAPPER_PLAYLIST_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_PLAYLIST_ITEM, GstClapperPlaylistItem)) +#define GST_CLAPPER_PLAYLIST_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_PLAYLIST_ITEM, GstClapperPlaylistItemClass)) +#define GST_CLAPPER_PLAYLIST_ITEM_CAST(obj) ((GstClapperPlaylistItem*)(obj)) + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstClapperPlaylistItem, gst_object_unref) +#endif + +GST_CLAPPER_API +GType gst_clapper_playlist_item_get_type (void); + +GST_CLAPPER_API +GstClapperPlaylistItem * gst_clapper_playlist_item_new (const gchar *uri); + +GST_CLAPPER_API +GstClapperPlaylistItem * gst_clapper_playlist_item_new_titled (const gchar *uri, const gchar *custom_title); + +GST_CLAPPER_API +GstClapperPlaylistItem * gst_clapper_playlist_item_copy (GstClapperPlaylistItem *item); + +GST_CLAPPER_API +void gst_clapper_playlist_item_set_suburi (GstClapperPlaylistItem *item, const gchar *suburi); + +GST_CLAPPER_API +void gst_clapper_playlist_item_activate (GstClapperPlaylistItem *item); + +G_END_DECLS + +#endif /* __GST_CLAPPER_PLAYLIST_ITEM_H__ */ diff --git a/lib/gst/clapper/gstclapper-playlist-private.h b/lib/gst/clapper/gstclapper-playlist-private.h new file mode 100644 index 00000000..2bbb1607 --- /dev/null +++ b/lib/gst/clapper/gstclapper-playlist-private.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_PLAYLIST_PRIVATE_H__ +#define __GST_CLAPPER_PLAYLIST_PRIVATE_H__ + +#include "gstclapper-playlist.h" + +struct _GstClapperPlaylist +{ + GstObject parent; + + gchar *uuid; + gint id_count; + GArray *items; + gint active_index; +}; + +struct _GstClapperPlaylistClass +{ + GstObjectClass parent_class; +}; + +G_GNUC_INTERNAL +void gst_clapper_playlist_emit_item_activated (GstClapperPlaylist *playlist, GstClapperPlaylistItem *item); + +#endif /* __GST_CLAPPER_PLAYLIST_PRIVATE_H__ */ diff --git a/lib/gst/clapper/gstclapper-playlist.c b/lib/gst/clapper/gstclapper-playlist.c new file mode 100644 index 00000000..566f96e8 --- /dev/null +++ b/lib/gst/clapper/gstclapper-playlist.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-playlist.h" +#include "gstclapper-playlist-private.h" +#include "gstclapper-playlist-item.h" +#include "gstclapper-playlist-item-private.h" + +enum +{ + SIGNAL_ITEM_ACTIVATED, + SIGNAL_LAST +}; + +#define parent_class gst_clapper_playlist_parent_class +G_DEFINE_TYPE (GstClapperPlaylist, gst_clapper_playlist, GST_TYPE_OBJECT); + +static guint signals[SIGNAL_LAST] = { 0, }; + +static void gst_clapper_playlist_dispose (GObject * object); +static void gst_clapper_playlist_finalize (GObject * object); + +static void +gst_clapper_playlist_init (GstClapperPlaylist * self) +{ + self->uuid = g_uuid_string_random (); + self->id_count = 0; + self->items = g_array_new (FALSE, FALSE, sizeof (GstClapperPlaylistItem)); + self->active_index = -1; + + g_array_set_clear_func (self->items, (GDestroyNotify) gst_object_unref); +} + +static void +gst_clapper_playlist_class_init (GstClapperPlaylistClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->dispose = gst_clapper_playlist_dispose; + gobject_class->finalize = gst_clapper_playlist_finalize; + + signals[SIGNAL_ITEM_ACTIVATED] = + g_signal_new ("item-activated", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLAPPER_PLAYLIST_ITEM); +} + +static void +gst_clapper_playlist_dispose (GObject * object) +{ + GstClapperPlaylist *self = GST_CLAPPER_PLAYLIST (object); + + /* FIXME: Need this for something? */ + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_clapper_playlist_finalize (GObject * object) +{ + GstClapperPlaylist *self = GST_CLAPPER_PLAYLIST (object); + + g_free (self->uuid); + g_array_unref (self->items); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +void +gst_clapper_playlist_emit_item_activated (GstClapperPlaylist * self, + GstClapperPlaylistItem * item) +{ + g_signal_emit (self, signals[SIGNAL_ITEM_ACTIVATED], 0, item); +} + +/** + * gst_clapper_playlist_new: + * + * Creates a new #GstClapperPlaylist. + * + * Returns: (transfer full): a new #GstClapperPlaylist instance. + */ +GstClapperPlaylist * +gst_clapper_playlist_new (void) +{ + return g_object_new (GST_TYPE_CLAPPER_PLAYLIST, NULL); +} + +/** + * gst_clapper_playlist_append: + * @playlist: #GstClapperPlaylist + * @item: #GstClapperPlaylistItem to append + * + * Adds a new #GstClapperPlaylistItem to the end of playlist. + * + * Returns: %TRUE if the item was added successfully. + */ +gboolean +gst_clapper_playlist_append (GstClapperPlaylist * self, GstClapperPlaylistItem * item) +{ + gboolean added = FALSE; + + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST (self), FALSE); + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST_ITEM (item), FALSE); + g_return_val_if_fail (item->owner_uuid == NULL, FALSE); + + GST_OBJECT_LOCK (self); + + added = g_array_append_val (self->items, item) != NULL; + if (added) { + gst_clapper_playlist_item_mark_added (item, self); + self->id_count++; + } + + GST_OBJECT_UNLOCK (self); + + return added; +} + +/** + * gst_clapper_playlist_get_length: + * @playlist: #GstClapperPlaylist + * + * Returns: Amount of items in playlist. + */ +guint +gst_clapper_playlist_get_length (GstClapperPlaylist * self) +{ + guint len; + + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST (self), 0); + + GST_OBJECT_LOCK (self); + len = self->items->len; + GST_OBJECT_UNLOCK (self); + + return len; +} + +/** + * gst_clapper_playlist_get_item_at_index: + * @playlist: #GstClapperPlaylist + * + * Returns: (transfer none): A #GstClapperPlaylistItem at given index. + */ +GstClapperPlaylistItem * +gst_clapper_playlist_get_item_at_index (GstClapperPlaylist * self, gint index) +{ + GstClapperPlaylistItem *item = NULL; + + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST (self), NULL); + + GST_OBJECT_LOCK (self); + + if (index < self->items->len) + goto out; + + item = &g_array_index (self->items, GstClapperPlaylistItem, index); + +out: + GST_OBJECT_UNLOCK (self); + + return item; +} + +/** + * gst_clapper_playlist_get_active_item: + * @playlist: #GstClapperPlaylist + * + * Returns: (transfer none): A #GstClapperPlaylistItem that is + * currently playing. + */ +GstClapperPlaylistItem * +gst_clapper_playlist_get_active_item (GstClapperPlaylist * self) +{ + gint active_index; + + GST_OBJECT_LOCK (self); + active_index = self->active_index; + GST_OBJECT_UNLOCK (self); + + return gst_clapper_playlist_get_item_at_index (self, active_index); +} + +/** + * gst_clapper_playlist_remove_item_at_index: + * @playlist: #GstClapperPlaylist + * @index: Index of #GstClapperPlaylistItem to remove + * + * Removes item at given index from playlist. + * + * Returns: %TRUE if the item was removed successfully. + */ +gboolean +gst_clapper_playlist_remove_item_at_index (GstClapperPlaylist * self, guint index) +{ + gboolean removed = FALSE; + + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST (self), FALSE); + + GST_OBJECT_LOCK (self); + + if (index >= self->items->len || index == self->active_index) + goto out; + + removed = g_array_remove_index (self->items, index) != NULL; + +out: + GST_OBJECT_UNLOCK (self); + + return removed; +} + +/** + * gst_clapper_playlist_remove_item: + * @playlist: #GstClapperPlaylist + * @item: #GstClapperPlaylistItem object to remove + * + * Removes given playlist item from playlist. + * + * Returns: %TRUE if the item was removed successfully. + */ +gboolean +gst_clapper_playlist_remove_item (GstClapperPlaylist * self, + GstClapperPlaylistItem * item) +{ + gint i; + gboolean removed = FALSE; + + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST (self), FALSE); + g_return_val_if_fail (GST_IS_CLAPPER_PLAYLIST_ITEM (item), FALSE); + + GST_OBJECT_LOCK (self); + + if (strcmp (self->uuid, item->owner_uuid) != 0) + goto out; + + for (i = 0; i < self->items->len; i++) { + GstClapperPlaylistItem *curr_item; + + curr_item = &g_array_index (self->items, GstClapperPlaylistItem, i); + if (!curr_item) + goto out; + + if (item->id == curr_item->id) { + removed = g_array_remove_index (self->items, i) != NULL; + break; + } + } + +out: + GST_OBJECT_UNLOCK (self); + + return removed; +} diff --git a/lib/gst/clapper/gstclapper-playlist.h b/lib/gst/clapper/gstclapper-playlist.h new file mode 100644 index 00000000..e1c40bc3 --- /dev/null +++ b/lib/gst/clapper/gstclapper-playlist.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_PLAYLIST_H__ +#define __GST_CLAPPER_PLAYLIST_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstClapperPlaylist GstClapperPlaylist; +typedef struct _GstClapperPlaylistClass GstClapperPlaylistClass; + +#define GST_TYPE_CLAPPER_PLAYLIST (gst_clapper_playlist_get_type ()) +#define GST_IS_CLAPPER_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_PLAYLIST)) +#define GST_IS_CLAPPER_PLAYLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_PLAYLIST)) +#define GST_CLAPPER_PLAYLIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_PLAYLIST, GstClapperPlaylistClass)) +#define GST_CLAPPER_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_PLAYLIST, GstClapperPlaylist)) +#define GST_CLAPPER_PLAYLIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_PLAYLIST, GstClapperPlaylistClass)) +#define GST_CLAPPER_PLAYLIST_CAST(obj) ((GstClapperPlaylist*)(obj)) + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstClapperPlaylist, g_object_unref) +#endif + +GST_CLAPPER_API +GType gst_clapper_playlist_get_type (void); + +GST_CLAPPER_API +GstClapperPlaylist * gst_clapper_playlist_new (void); + +GST_CLAPPER_API +gboolean gst_clapper_playlist_append (GstClapperPlaylist *playlist, GstClapperPlaylistItem *item); + +GST_CLAPPER_API +guint gst_clapper_playlist_get_length (GstClapperPlaylist *playlist); + +GST_CLAPPER_API +GstClapperPlaylistItem * + gst_clapper_playlist_get_item_at_index (GstClapperPlaylist *playlist, gint index); + +GST_CLAPPER_API +GstClapperPlaylistItem * + gst_clapper_playlist_get_active_item (GstClapperPlaylist *playlist); + +GST_CLAPPER_API +gboolean gst_clapper_playlist_remove_item_at_index (GstClapperPlaylist *playlist, guint index); + +GST_CLAPPER_API +gboolean gst_clapper_playlist_remove_item (GstClapperPlaylist *playlist, GstClapperPlaylistItem *item); + +G_END_DECLS + +#endif /* __GST_CLAPPER_PLAYLIST_H__ */ diff --git a/lib/gst/clapper/gstclapper.c b/lib/gst/clapper/gstclapper.c index c64b9379..7cdec72a 100644 --- a/lib/gst/clapper/gstclapper.c +++ b/lib/gst/clapper/gstclapper.c @@ -46,6 +46,7 @@ #include "gstclapper-signal-dispatcher-private.h" #include "gstclapper-video-renderer-private.h" #include "gstclapper-media-info-private.h" +#include "gstclapper-playlist-item-private.h" #include "gstclapper-mpris-private.h" GST_DEBUG_CATEGORY_STATIC (gst_clapper_debug); @@ -79,8 +80,7 @@ enum PROP_SIGNAL_DISPATCHER, PROP_MPRIS, PROP_STATE, - PROP_URI, - PROP_SUBURI, + PROP_PLAYLIST, PROP_POSITION, PROP_DURATION, PROP_MEDIA_INFO, @@ -134,6 +134,7 @@ struct _GstClapper gchar *uri; gchar *redirect_uri; gchar *suburi; + gchar *custom_title; GThread *thread; GMutex lock; @@ -188,6 +189,12 @@ struct _GstClapper gchar *audio_sid; gchar *subtitle_sid; gulong stream_notify_id; + + /* For playlist */ + GstClapperPlaylist *playlist; + GstClapperPlaylistItem *active_item; + gulong item_activated_id; + gulong suburi_notify_id; }; struct _GstClapperClass @@ -320,12 +327,8 @@ gst_clapper_class_init (GstClapperClass * klass) GST_TYPE_CLAPPER_STATE, DEFAULT_STATE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); - param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI", - DEFAULT_URI, G_PARAM_READWRITE | - G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); - - param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", "Subtitle URI", - "Current Subtitle URI", NULL, G_PARAM_READWRITE | + param_specs[PROP_PLAYLIST] = g_param_spec_string ("playlist", "Playlist", + "Current Playlist", NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_POSITION] = @@ -519,6 +522,8 @@ gst_clapper_finalize (GObject * object) g_object_unref (self->signal_dispatcher); if (self->mpris) g_object_unref (self->mpris); + if (self->playlist) + gst_object_unref (self->playlist); if (self->current_vis_element) gst_object_unref (self->current_vis_element); if (self->collection) @@ -567,35 +572,6 @@ uri_loaded_signal_data_free (UriLoadedSignalData * data) g_free (data); } -static gboolean -gst_clapper_set_uri_internal (gpointer user_data) -{ - GstClapper *self = user_data; - - gst_clapper_stop_internal (self, FALSE); - - g_mutex_lock (&self->lock); - GST_DEBUG_OBJECT (self, "Changing URI to '%s'", GST_STR_NULL (self->uri)); - g_object_set (self->playbin, "uri", self->uri, NULL); - g_object_set (self->playbin, "suburi", NULL, NULL); - self->can_start = TRUE; - - if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, - signals[SIGNAL_URI_LOADED], 0, NULL, NULL, NULL) != 0) { - UriLoadedSignalData *data = g_new (UriLoadedSignalData, 1); - - data->clapper = g_object_ref (self); - data->uri = g_strdup (self->uri); - gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, - uri_loaded_dispatch, data, - (GDestroyNotify) uri_loaded_signal_data_free); - } - - g_mutex_unlock (&self->lock); - - return G_SOURCE_REMOVE; -} - static gboolean gst_clapper_set_suburi_internal (gpointer user_data) { @@ -628,6 +604,100 @@ gst_clapper_set_suburi_internal (gpointer user_data) return G_SOURCE_REMOVE; } +static void +suburi_notify_cb (GstClapperPlaylistItem * item, GParamSpec * pspec, + GstClapper * self) +{ + g_mutex_lock (&self->lock); + g_free (self->suburi); + self->suburi = g_strdup (item->suburi); + GST_DEBUG_OBJECT (self, "Set suburi: %s", self->suburi); + g_mutex_unlock (&self->lock); + + g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT + 1, + gst_clapper_set_suburi_internal, self, NULL); +} + +static void +gst_clapper_set_playlist_item_locked (GstClapper * self, + GstClapperPlaylistItem * item) +{ + if (self->suburi_notify_id) + g_signal_handler_disconnect (self->active_item, self->suburi_notify_id); + + gst_object_replace ((GstObject **) & self->active_item, + (GstObject *) item); + + g_free (self->uri); + self->uri = g_strdup (self->active_item->uri); + + g_free (self->redirect_uri); + self->redirect_uri = NULL; + + g_free (self->suburi); + self->suburi = g_strdup (self->active_item->suburi); + + g_free (self->custom_title); + self->custom_title = g_strdup (self->active_item->custom_title); + + GST_DEBUG_OBJECT (self, "Changing URI to '%s'", + GST_STR_NULL (self->uri)); + g_object_set (self->playbin, "uri", self->uri, NULL); + + GST_DEBUG_OBJECT (self, "Changing SUBURI to '%s'", + GST_STR_NULL (self->suburi)); + g_object_set (self->playbin, "suburi", self->suburi, NULL); + + self->suburi_notify_id = g_signal_connect (self->active_item, + "notify::suburi", G_CALLBACK (suburi_notify_cb), self); + + self->can_start = TRUE; + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_URI_LOADED], 0, NULL, NULL, NULL) != 0) { + UriLoadedSignalData *data = g_new (UriLoadedSignalData, 1); + + data->clapper = g_object_ref (self); + data->uri = g_strdup (self->uri); + gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self, + uri_loaded_dispatch, data, + (GDestroyNotify) uri_loaded_signal_data_free); + } +} + +static gboolean +gst_clapper_set_playlist_internal (gpointer user_data) +{ + GstClapper *self = user_data; + GstClapperPlaylistItem *item; + + gst_clapper_stop_internal (self, FALSE); + + g_mutex_lock (&self->lock); + item = gst_clapper_playlist_get_item_at_index (self->playlist, 0); + if (!item) { + GST_DEBUG_OBJECT (self, "Set empty playlist"); + goto out; + } + gst_clapper_set_playlist_item_locked (self, item); + +out: + g_mutex_unlock (&self->lock); + + return G_SOURCE_REMOVE; +} + +static void +item_activated_cb (G_GNUC_UNUSED GstClapperPlaylist * playlist, + GstClapperPlaylistItem * item, gpointer user_data) +{ + GstClapper *self = GST_CLAPPER (user_data); + + g_mutex_lock (&self->lock); + gst_clapper_set_playlist_item_locked (self, item); + g_mutex_unlock (&self->lock); +} + static void gst_clapper_set_rate_internal (GstClapper * self) { @@ -664,35 +734,21 @@ gst_clapper_set_property (GObject * object, guint prop_id, case PROP_MPRIS: self->mpris = g_value_dup_object (value); break; - case PROP_URI:{ + case PROP_PLAYLIST: g_mutex_lock (&self->lock); - g_free (self->uri); - g_free (self->redirect_uri); - self->redirect_uri = NULL; - - g_free (self->suburi); - self->suburi = NULL; - - self->uri = g_value_dup_string (value); - GST_DEBUG_OBJECT (self, "Set uri=%s", self->uri); + if (self->playlist) { + if (self->item_activated_id) + g_signal_handler_disconnect (self->playlist, self->item_activated_id); + gst_object_unref (self->playlist); + } + self->playlist = g_value_dup_object (value); + self->item_activated_id = g_signal_connect (self->playlist, "item-activated", + G_CALLBACK (item_activated_cb), self); g_mutex_unlock (&self->lock); g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, - gst_clapper_set_uri_internal, self, NULL); + gst_clapper_set_playlist_internal, self, NULL); break; - } - case PROP_SUBURI:{ - g_mutex_lock (&self->lock); - g_free (self->suburi); - - self->suburi = g_value_dup_string (value); - GST_DEBUG_OBJECT (self, "Set suburi=%s", self->suburi); - g_mutex_unlock (&self->lock); - - g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT, - gst_clapper_set_suburi_internal, self, NULL); - break; - } case PROP_VOLUME: { GValue volume_linear = G_VALUE_INIT; gdouble volume = g_value_get_double (value); @@ -764,18 +820,6 @@ gst_clapper_get_property (GObject * object, guint prop_id, g_value_set_enum (value, self->app_state); g_mutex_unlock (&self->lock); break; - case PROP_URI: - g_mutex_lock (&self->lock); - g_value_set_string (value, self->uri); - g_mutex_unlock (&self->lock); - break; - case PROP_SUBURI: - g_mutex_lock (&self->lock); - g_value_set_string (value, self->suburi); - g_mutex_unlock (&self->lock); - GST_DEBUG_OBJECT (self, "Returning suburi=%s", - g_value_get_string (value)); - break; case PROP_POSITION:{ gint64 position = GST_CLOCK_TIME_NONE; @@ -785,12 +829,11 @@ gst_clapper_get_property (GObject * object, guint prop_id, GST_TIME_ARGS (g_value_get_uint64 (value))); break; } - case PROP_DURATION:{ + case PROP_DURATION: g_value_set_uint64 (value, self->cached_duration); GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT, GST_TIME_ARGS (g_value_get_uint64 (value))); break; - } case PROP_MEDIA_INFO:{ GstClapperMediaInfo *media_info = gst_clapper_get_media_info (self); g_value_take_object (value, media_info); @@ -837,20 +880,18 @@ gst_clapper_get_property (GObject * object, guint prop_id, case PROP_PIPELINE: g_value_set_object (value, self->playbin); break; - case PROP_VIDEO_MULTIVIEW_MODE:{ + case PROP_VIDEO_MULTIVIEW_MODE: g_object_get_property (G_OBJECT (self->playbin), "video-multiview-mode", value); GST_TRACE_OBJECT (self, "Return multiview mode=%d", g_value_get_enum (value)); break; - } - case PROP_VIDEO_MULTIVIEW_FLAGS:{ + case PROP_VIDEO_MULTIVIEW_FLAGS: g_object_get_property (G_OBJECT (self->playbin), "video-multiview-flags", value); GST_TRACE_OBJECT (self, "Return multiview flags=%x", g_value_get_flags (value)); break; - } case PROP_AUDIO_VIDEO_OFFSET: g_object_get_property (G_OBJECT (self->playbin), "av-offset", value); break; @@ -1804,11 +1845,13 @@ request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, static void media_info_update (GstClapper * self, GstClapperMediaInfo * info) { - /* Update title from new tags or leave the title from URI */ - gchar *tags_title = get_from_tags (self, info, get_title); - if (tags_title) { - g_free (info->title); - info->title = tags_title; + if (!self->custom_title) { + /* Update title from new tags or leave the title from URI */ + gchar *tags_title = get_from_tags (self, info, get_title); + if (tags_title) { + g_free (info->title); + info->title = tags_title; + } } g_free (info->container); @@ -2817,7 +2860,10 @@ gst_clapper_media_info_create (GstClapper * self) GST_TYPE_CLAPPER_SUBTITLE_INFO); } - media_info->title = get_from_tags (self, media_info, get_title); + if (self->custom_title) + media_info->title = g_strdup (self->custom_title); + if (!media_info->title) + media_info->title = get_from_tags (self, media_info, get_title); if (!media_info->title) media_info->title = get_title_from_uri (self->uri); @@ -3630,77 +3676,19 @@ gst_clapper_get_state (GstClapper * self) } /** - * gst_clapper_get_uri: + * gst_clapper_set_playlist: * @clapper: #GstClapper instance + * @playlist: #GstClapperPlaylist instance * - * Gets the URI of the currently-playing stream. - * - * Returns: (transfer full): a string containing the URI of the - * currently-playing stream. g_free() after usage. - */ -gchar * -gst_clapper_get_uri (GstClapper * self) -{ - gchar *val; - - g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_URI); - - g_object_get (self, "uri", &val, NULL); - - return val; -} - -/** - * gst_clapper_set_uri: - * @clapper: #GstClapper instance - * @uri: next URI to play. - * - * Sets the next URI to play. + * Sets the new #GstClapperPlaylist */ void -gst_clapper_set_uri (GstClapper * self, const gchar * val) +gst_clapper_set_playlist (GstClapper * self, GstClapperPlaylist * val) { g_return_if_fail (GST_IS_CLAPPER (self)); + g_return_if_fail (GST_IS_CLAPPER_PLAYLIST (val)); - g_object_set (self, "uri", val, NULL); -} - -/** - * gst_clapper_set_subtitle_uri: - * @clapper: #GstClapper instance - * @uri: subtitle URI - * - * Sets the external subtitle URI. This should be combined with a call to - * gst_clapper_set_subtitle_track_enabled(@clapper, TRUE) so the subtitles are actually - * rendered. - */ -void -gst_clapper_set_subtitle_uri (GstClapper * self, const gchar * suburi) -{ - g_return_if_fail (GST_IS_CLAPPER (self)); - - g_object_set (self, "suburi", suburi, NULL); -} - -/** - * gst_clapper_get_subtitle_uri: - * @clapper: #GstClapper instance - * - * current subtitle URI - * - * Returns: (transfer full): URI of the current external subtitle. - * g_free() after usage. - */ -gchar * -gst_clapper_get_subtitle_uri (GstClapper * self) -{ - gchar *val = NULL; - - g_return_val_if_fail (GST_IS_CLAPPER (self), NULL); - - g_object_get (self, "suburi", &val, NULL); - - return val; + g_object_set (self, "playlist", val, NULL); } /** diff --git a/lib/gst/clapper/gstclapper.h b/lib/gst/clapper/gstclapper.h index 35895109..c4d75f91 100644 --- a/lib/gst/clapper/gstclapper.h +++ b/lib/gst/clapper/gstclapper.h @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include G_BEGIN_DECLS @@ -193,16 +195,7 @@ GST_CLAPPER_API gdouble gst_clapper_get_rate (GstClapper *clapper); GST_CLAPPER_API -gchar * gst_clapper_get_uri (GstClapper *clapper); - -GST_CLAPPER_API -void gst_clapper_set_uri (GstClapper *clapper, const gchar *uri); - -GST_CLAPPER_API -gchar * gst_clapper_get_subtitle_uri (GstClapper *clapper); - -GST_CLAPPER_API -void gst_clapper_set_subtitle_uri (GstClapper *clapper, const gchar *uri); +void gst_clapper_set_playlist (GstClapper *clapper, GstClapperPlaylist *playlist); GST_CLAPPER_API GstClockTime gst_clapper_get_position (GstClapper *clapper); diff --git a/lib/gst/clapper/meson.build b/lib/gst/clapper/meson.build index 517fc672..ba2acdf3 100644 --- a/lib/gst/clapper/meson.build +++ b/lib/gst/clapper/meson.build @@ -8,6 +8,8 @@ gstclapper_sources = [ 'gstclapper-g-main-context-signal-dispatcher.c', 'gstclapper-video-overlay-video-renderer.c', 'gstclapper-visualization.c', + 'gstclapper-playlist.c', + 'gstclapper-playlist-item.c', 'gstclapper-mpris.c', 'gstclapper-gtk4-plugin.c', @@ -26,6 +28,8 @@ gstclapper_headers = [ 'gstclapper-g-main-context-signal-dispatcher.h', 'gstclapper-video-overlay-video-renderer.h', 'gstclapper-visualization.h', + 'gstclapper-playlist.h', + 'gstclapper-playlist-item.h', 'gstclapper-mpris.h', 'gstclapper-gtk4-plugin.h', ]