mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 16:31:58 +02:00
The Free Software Foundation is now remote-only, and no longer has a street address. Updated license notice text is from https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html#SEC4.
1317 lines
37 KiB
C
1317 lines
37 KiB
C
/* Clapper Playback Library
|
|
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see
|
|
* <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* ClapperQueue:
|
|
*
|
|
* A queue of media to be played.
|
|
*/
|
|
|
|
#include <gio/gio.h>
|
|
|
|
#include "clapper-queue-private.h"
|
|
#include "clapper-media-item-private.h"
|
|
#include "clapper-player-private.h"
|
|
#include "clapper-playbin-bus-private.h"
|
|
#include "clapper-reactables-manager-private.h"
|
|
#include "clapper-features-manager-private.h"
|
|
|
|
#define CLAPPER_QUEUE_GET_REC_LOCK(obj) (&CLAPPER_QUEUE_CAST(obj)->rec_lock)
|
|
#define CLAPPER_QUEUE_REC_LOCK(obj) g_rec_mutex_lock (CLAPPER_QUEUE_GET_REC_LOCK(obj))
|
|
#define CLAPPER_QUEUE_REC_UNLOCK(obj) g_rec_mutex_unlock (CLAPPER_QUEUE_GET_REC_LOCK(obj))
|
|
|
|
#define DEFAULT_PROGRESSION_MODE CLAPPER_QUEUE_PROGRESSION_NONE
|
|
#define DEFAULT_GAPLESS FALSE
|
|
#define DEFAULT_INSTANT FALSE
|
|
|
|
#define GST_CAT_DEFAULT clapper_queue_debug
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
|
|
|
struct _ClapperQueue
|
|
{
|
|
GstObject parent;
|
|
|
|
GRecMutex rec_lock;
|
|
|
|
GPtrArray *items;
|
|
ClapperMediaItem *current_item;
|
|
guint current_index;
|
|
|
|
ClapperQueueProgressionMode progression_mode;
|
|
gboolean gapless;
|
|
gboolean instant;
|
|
|
|
/* Avoid scenario when "gapless" prop is changed
|
|
* between "about-to-finish" and "EOS" */
|
|
gboolean handled_gapless;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CURRENT_ITEM,
|
|
PROP_CURRENT_INDEX,
|
|
PROP_N_ITEMS,
|
|
PROP_PROGRESSION_MODE,
|
|
PROP_GAPLESS,
|
|
PROP_INSTANT,
|
|
PROP_LAST
|
|
};
|
|
|
|
static GType
|
|
clapper_queue_list_model_get_item_type (GListModel *model)
|
|
{
|
|
return CLAPPER_TYPE_MEDIA_ITEM;
|
|
}
|
|
|
|
static guint
|
|
clapper_queue_list_model_get_n_items (GListModel *model)
|
|
{
|
|
ClapperQueue *self = CLAPPER_QUEUE_CAST (model);
|
|
guint n_items;
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
n_items = self->items->len;
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return n_items;
|
|
}
|
|
|
|
static gpointer
|
|
clapper_queue_list_model_get_item (GListModel *model, guint index)
|
|
{
|
|
ClapperQueue *self = CLAPPER_QUEUE_CAST (model);
|
|
ClapperMediaItem *item = NULL;
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
if (G_LIKELY (index < self->items->len)) {
|
|
GST_LOG_OBJECT (self, "Reading queue item: %u", index);
|
|
item = g_object_ref (g_ptr_array_index (self->items, index));
|
|
}
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return item;
|
|
}
|
|
|
|
static void
|
|
clapper_queue_list_model_iface_init (GListModelInterface *iface)
|
|
{
|
|
iface->get_item_type = clapper_queue_list_model_get_item_type;
|
|
iface->get_n_items = clapper_queue_list_model_get_n_items;
|
|
iface->get_item = clapper_queue_list_model_get_item;
|
|
}
|
|
|
|
#define parent_class clapper_queue_parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (ClapperQueue, clapper_queue, GST_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_queue_list_model_iface_init));
|
|
|
|
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
|
|
|
static void
|
|
_announce_model_update (ClapperQueue *self, guint index, guint removed, guint added,
|
|
ClapperMediaItem *changed_item)
|
|
{
|
|
GST_DEBUG_OBJECT (self, "Announcing model update, index: %u, removed: %u, added: %u",
|
|
index, removed, added);
|
|
|
|
/* We handle reposition separately */
|
|
if (removed != added) {
|
|
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
|
|
|
if (player) {
|
|
gboolean have_features = clapper_player_get_have_features (player);
|
|
|
|
if (added == 1) { // addition
|
|
if (player->reactables_manager)
|
|
clapper_reactables_manager_trigger_queue_item_added (player->reactables_manager, changed_item, index);
|
|
if (have_features)
|
|
clapper_features_manager_trigger_queue_item_added (player->features_manager, changed_item, index);
|
|
} else if (removed == 1) { // removal
|
|
if (player->reactables_manager)
|
|
clapper_reactables_manager_trigger_queue_item_removed (player->reactables_manager, changed_item, index);
|
|
if (have_features)
|
|
clapper_features_manager_trigger_queue_item_removed (player->features_manager, changed_item, index);
|
|
} else if (removed > 1 && added == 0) { // queue cleared
|
|
if (player->reactables_manager)
|
|
clapper_reactables_manager_trigger_queue_cleared (player->reactables_manager);
|
|
if (have_features)
|
|
clapper_features_manager_trigger_queue_cleared (player->features_manager);
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
gst_clear_object (&player);
|
|
}
|
|
|
|
g_list_model_items_changed (G_LIST_MODEL (self), index, removed, added);
|
|
|
|
if (removed != added)
|
|
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_ITEMS]);
|
|
}
|
|
|
|
static void
|
|
_announce_reposition (ClapperQueue *self, guint before, guint after)
|
|
{
|
|
ClapperPlayer *player;
|
|
|
|
GST_DEBUG_OBJECT (self, "Announcing item reposition: %u -> %u", before, after);
|
|
|
|
if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) {
|
|
if (player->reactables_manager)
|
|
clapper_reactables_manager_trigger_queue_item_repositioned (player->reactables_manager, before, after);
|
|
if (clapper_player_get_have_features (player))
|
|
clapper_features_manager_trigger_queue_item_repositioned (player->features_manager, before, after);
|
|
|
|
gst_object_unref (player);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Notify about current index change. This is needed only if some items
|
|
* are added/removed before current selection, otherwise if selection
|
|
* also changes use _announce_current_item_and_index_change() instead.
|
|
*/
|
|
static void
|
|
_announce_current_index_change (ClapperQueue *self)
|
|
{
|
|
gboolean is_main_thread = g_main_context_is_owner (g_main_context_default ());
|
|
|
|
GST_DEBUG_OBJECT (self, "Announcing current index change from %smain thread, now: %u",
|
|
(is_main_thread) ? "" : "non-", self->current_index);
|
|
|
|
if (is_main_thread) {
|
|
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_INDEX]);
|
|
} else {
|
|
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
|
|
|
if (G_LIKELY (player != NULL)) {
|
|
clapper_app_bus_post_prop_notify (player->app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]);
|
|
|
|
gst_object_unref (player);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Notify about both current item and its index changes.
|
|
* Needs to be called while holding CLAPPER_QUEUE_REC_LOCK.
|
|
*/
|
|
static void
|
|
_announce_current_item_and_index_change (ClapperQueue *self)
|
|
{
|
|
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
|
gboolean instant, is_main_thread;
|
|
|
|
if (G_UNLIKELY (player == NULL))
|
|
return;
|
|
|
|
is_main_thread = g_main_context_is_owner (g_main_context_default ());
|
|
|
|
GST_DEBUG_OBJECT (self, "Announcing current item change from %smain thread,"
|
|
" now: %" GST_PTR_FORMAT " (index: %u)",
|
|
(is_main_thread) ? "" : "non-", self->current_item, self->current_index);
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
instant = self->instant;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
clapper_playbin_bus_post_current_item_change (player->bus, self->current_item,
|
|
(instant) ? CLAPPER_QUEUE_ITEM_CHANGE_INSTANT : CLAPPER_QUEUE_ITEM_CHANGE_NORMAL);
|
|
|
|
if (is_main_thread) {
|
|
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_ITEM]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_INDEX]);
|
|
} else {
|
|
clapper_app_bus_post_prop_notify (player->app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_ITEM]);
|
|
clapper_app_bus_post_prop_notify (player->app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]);
|
|
}
|
|
|
|
gst_object_unref (player);
|
|
}
|
|
|
|
static inline gboolean
|
|
_replace_current_item_unlocked (ClapperQueue *self, ClapperMediaItem *item, guint index)
|
|
{
|
|
if (gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (item))) {
|
|
self->current_index = index;
|
|
|
|
if (self->current_item)
|
|
clapper_media_item_set_used (self->current_item, TRUE);
|
|
|
|
GST_TRACE_OBJECT (self, "Current item replaced, now: %" GST_PTR_FORMAT, self->current_item);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
_reset_shuffle_unlocked (ClapperQueue *self)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < self->items->len; ++i) {
|
|
ClapperMediaItem *item = g_ptr_array_index (self->items, i);
|
|
clapper_media_item_set_used (item, FALSE);
|
|
}
|
|
}
|
|
|
|
static ClapperMediaItem *
|
|
_get_next_item_unlocked (ClapperQueue *self, ClapperQueueProgressionMode mode)
|
|
{
|
|
ClapperMediaItem *next_item = NULL;
|
|
|
|
GST_DEBUG_OBJECT (self, "Handling progression mode: %u", mode);
|
|
|
|
if (self->current_index == CLAPPER_QUEUE_INVALID_POSITION) {
|
|
GST_DEBUG_OBJECT (self, "No current item, can not advance");
|
|
return NULL;
|
|
}
|
|
|
|
switch (mode) {
|
|
case CLAPPER_QUEUE_PROGRESSION_NONE:
|
|
break;
|
|
case CLAPPER_QUEUE_PROGRESSION_CAROUSEL:
|
|
next_item = g_ptr_array_index (self->items, 0);
|
|
G_GNUC_FALLTHROUGH;
|
|
case CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE:
|
|
if (self->current_index + 1 < self->items->len)
|
|
next_item = g_ptr_array_index (self->items, self->current_index + 1);
|
|
break;
|
|
case CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM:
|
|
next_item = self->current_item;
|
|
break;
|
|
case CLAPPER_QUEUE_PROGRESSION_SHUFFLE:{
|
|
GList *unused = NULL;
|
|
GRand *rand = g_rand_new ();
|
|
guint i;
|
|
|
|
for (i = 0; i < self->items->len; ++i) {
|
|
ClapperMediaItem *item = g_ptr_array_index (self->items, i);
|
|
|
|
if (!clapper_media_item_get_used (item))
|
|
unused = g_list_append (unused, item);
|
|
}
|
|
|
|
if (unused) {
|
|
next_item = g_list_nth_data (unused,
|
|
g_rand_int_range (rand, 0, g_list_length (unused)));
|
|
g_list_free (unused);
|
|
} else {
|
|
_reset_shuffle_unlocked (self);
|
|
next_item = g_ptr_array_index (self->items,
|
|
g_rand_int_range (rand, 0, self->items->len));
|
|
}
|
|
|
|
g_rand_free (rand);
|
|
break;
|
|
}
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
if (next_item)
|
|
gst_object_ref (next_item);
|
|
|
|
return next_item;
|
|
}
|
|
|
|
/*
|
|
* For gapless we need to manually replace current item in queue when it starts
|
|
* playing and emit notify about change, this function will do that if necessary
|
|
*/
|
|
void
|
|
clapper_queue_handle_played_item_changed (ClapperQueue *self, ClapperMediaItem *played_item,
|
|
ClapperAppBus *app_bus)
|
|
{
|
|
guint index = 0;
|
|
gboolean changed = FALSE;
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
/* Item is often the same here (when selected from queue),
|
|
* so compare pointers first to avoid iterating queue */
|
|
if (played_item != self->current_item
|
|
&& g_ptr_array_find (self->items, played_item, &index))
|
|
changed = _replace_current_item_unlocked (self, played_item, index);
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
if (changed) {
|
|
clapper_app_bus_post_prop_notify (app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_ITEM]);
|
|
clapper_app_bus_post_prop_notify (app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]);
|
|
}
|
|
}
|
|
|
|
void
|
|
clapper_queue_handle_about_to_finish (ClapperQueue *self, ClapperPlayer *player)
|
|
{
|
|
ClapperMediaItem *next_item;
|
|
ClapperQueueProgressionMode progression_mode;
|
|
|
|
GST_INFO_OBJECT (self, "Handling \"about-to-finish\"");
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
if (!(self->handled_gapless = self->gapless)) {
|
|
GST_OBJECT_UNLOCK (self);
|
|
return;
|
|
}
|
|
progression_mode = self->progression_mode;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
next_item = _get_next_item_unlocked (self, progression_mode);
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
if (next_item) {
|
|
clapper_player_set_pending_item (player, next_item, CLAPPER_QUEUE_ITEM_CHANGE_GAPLESS);
|
|
gst_object_unref (next_item);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
clapper_queue_handle_eos (ClapperQueue *self, ClapperPlayer *player)
|
|
{
|
|
ClapperMediaItem *next_item = NULL;
|
|
ClapperQueueProgressionMode progression_mode;
|
|
gboolean handled_eos = FALSE;
|
|
|
|
/* On gapless "about-to-finish" selects next item instead and
|
|
* we can reach EOS only if there was either nothing to select or
|
|
* some playback error ocurred */
|
|
|
|
GST_INFO_OBJECT (self, "Handling EOS");
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
if (self->handled_gapless) {
|
|
self->handled_gapless = FALSE; // reset
|
|
GST_OBJECT_UNLOCK (self);
|
|
return FALSE;
|
|
}
|
|
progression_mode = self->progression_mode;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
if ((next_item = _get_next_item_unlocked (self, progression_mode))) {
|
|
if (next_item == self->current_item)
|
|
clapper_player_seek (player, 0);
|
|
else
|
|
clapper_queue_select_item (self, next_item);
|
|
|
|
handled_eos = TRUE;
|
|
gst_object_unref (next_item);
|
|
}
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return handled_eos;
|
|
}
|
|
|
|
/*
|
|
* clapper_queue_new:
|
|
*
|
|
* Returns: (transfer full): a new #ClapperQueue instance
|
|
*/
|
|
ClapperQueue *
|
|
clapper_queue_new (void)
|
|
{
|
|
ClapperQueue *queue;
|
|
|
|
queue = g_object_new (CLAPPER_TYPE_QUEUE, NULL);
|
|
gst_object_ref_sink (queue);
|
|
|
|
return queue;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_add_item:
|
|
* @queue: a #ClapperQueue
|
|
* @item: a #ClapperMediaItem
|
|
*
|
|
* Add another #ClapperMediaItem to the end of queue.
|
|
*
|
|
* If item is already in queue, this function will do nothing,
|
|
* so it is safe to call multiple times if unsure.
|
|
*/
|
|
void
|
|
clapper_queue_add_item (ClapperQueue *self, ClapperMediaItem *item)
|
|
{
|
|
clapper_queue_insert_item (self, item, -1);
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_insert_item:
|
|
* @queue: a #ClapperQueue
|
|
* @item: a #ClapperMediaItem
|
|
* @index: the index to place @item in queue, -1 to append
|
|
*
|
|
* Insert another #ClapperMediaItem at @index position to the queue.
|
|
*
|
|
* If item is already in queue, this function will do nothing,
|
|
* so it is safe to call multiple times if unsure.
|
|
*/
|
|
void
|
|
clapper_queue_insert_item (ClapperQueue *self, ClapperMediaItem *item, gint index)
|
|
{
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (self));
|
|
g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item));
|
|
g_return_if_fail (index >= -1);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
if (!g_ptr_array_find (self->items, item, NULL)) {
|
|
guint prev_length = self->items->len;
|
|
|
|
g_ptr_array_insert (self->items, index, gst_object_ref (item));
|
|
gst_object_set_parent (GST_OBJECT_CAST (item), GST_OBJECT_CAST (self));
|
|
|
|
/* In append we inserted at array length */
|
|
if (index < 0)
|
|
index = prev_length;
|
|
|
|
_announce_model_update (self, index, 0, 1, item);
|
|
|
|
/* If has selection and inserting before it */
|
|
if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION
|
|
&& (guint) index <= self->current_index) {
|
|
self->current_index++;
|
|
_announce_current_index_change (self);
|
|
} else if (prev_length == 0 && _replace_current_item_unlocked (self, item, 0)) {
|
|
/* If queue was empty, auto select first item and announce it */
|
|
_announce_current_item_and_index_change (self);
|
|
} else if (self->current_index == prev_length - 1
|
|
&& clapper_queue_get_progression_mode (self) == CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE) {
|
|
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
|
gboolean after_eos = (gboolean) g_atomic_int_get (&player->eos);
|
|
|
|
/* In consecutive progression automatically select next item
|
|
* if we were after EOS of last queue item */
|
|
if (after_eos && _replace_current_item_unlocked (self, item, index))
|
|
_announce_current_item_and_index_change (self);
|
|
|
|
gst_object_unref (player);
|
|
}
|
|
}
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_reposition_item:
|
|
* @queue: a #ClapperQueue
|
|
* @item: a #ClapperMediaItem
|
|
* @index: the index to place @item in queue, -1 to place at the end
|
|
*
|
|
* Change position of one #ClapperMediaItem within the queue.
|
|
*
|
|
* Note that the @index is the new position you expect item to be
|
|
* after whole reposition operation is finished.
|
|
*
|
|
* If item is not in the queue, this function will do nothing.
|
|
*/
|
|
void
|
|
clapper_queue_reposition_item (ClapperQueue *self, ClapperMediaItem *item, gint index)
|
|
{
|
|
guint index_old = 0;
|
|
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (self));
|
|
g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item));
|
|
g_return_if_fail (index >= -1);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
if (g_ptr_array_find (self->items, item, &index_old)) {
|
|
ClapperMediaItem *removed_item;
|
|
guint index_new, start_index, end_index, n_changed;
|
|
|
|
index_new = (index < 0)
|
|
? self->items->len - 1
|
|
: (guint) index;
|
|
|
|
GST_DEBUG_OBJECT (self, "Reposition item %u -> %u, is_current: %s",
|
|
index_old, index_new, (item == self->current_item) ? "yes" : "no");
|
|
|
|
removed_item = g_ptr_array_steal_index (self->items, index_old);
|
|
g_ptr_array_insert (self->items, index_new, removed_item);
|
|
|
|
_announce_reposition (self, index_old, index_new);
|
|
|
|
if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION) {
|
|
guint before = self->current_index;
|
|
|
|
if (index_old > self->current_index && index_new <= self->current_index)
|
|
self->current_index++; // Moved before current item
|
|
else if (index_old < self->current_index && index_new >= self->current_index)
|
|
self->current_index--; // Moved after current item
|
|
else if (index_old == self->current_index)
|
|
self->current_index = index_new; // Moved current item
|
|
|
|
if (self->current_index != before)
|
|
_announce_current_index_change (self);
|
|
}
|
|
|
|
start_index = MIN (index_old, index_new);
|
|
end_index = MAX (index_old, index_new);
|
|
n_changed = end_index - start_index + 1;
|
|
|
|
_announce_model_update (self, start_index, n_changed, n_changed, item);
|
|
}
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_remove_item:
|
|
* @queue: a #ClapperQueue
|
|
* @item: a #ClapperMediaItem
|
|
*
|
|
* Removes #ClapperMediaItem from the queue.
|
|
*
|
|
* If item either was never in the queue or was removed from
|
|
* it earlier, this function will do nothing, so it is safe
|
|
* to call multiple times if unsure.
|
|
*/
|
|
void
|
|
clapper_queue_remove_item (ClapperQueue *self, ClapperMediaItem *item)
|
|
{
|
|
guint index = 0;
|
|
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (self));
|
|
g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item));
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
if (g_ptr_array_find (self->items, item, &index))
|
|
clapper_queue_remove_index (self, index);
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_remove_index:
|
|
* @queue: a #ClapperQueue
|
|
* @index: an item index
|
|
*
|
|
* Removes #ClapperMediaItem at @index from the queue.
|
|
*/
|
|
void
|
|
clapper_queue_remove_index (ClapperQueue *self, guint index)
|
|
{
|
|
ClapperMediaItem *item = clapper_queue_steal_index (self, index);
|
|
gst_clear_object (&item);
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_steal_index:
|
|
* @queue: a #ClapperQueue
|
|
* @index: an item index
|
|
*
|
|
* Removes #ClapperMediaItem at @index from the queue.
|
|
*
|
|
* Returns: (transfer full) (nullable): The removed #ClapperMediaItem at @index.
|
|
*/
|
|
ClapperMediaItem *
|
|
clapper_queue_steal_index (ClapperQueue *self, guint index)
|
|
{
|
|
ClapperMediaItem *removed_item = NULL;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), NULL);
|
|
g_return_val_if_fail (index != CLAPPER_QUEUE_INVALID_POSITION, NULL);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
if (index < self->items->len) {
|
|
if (index == self->current_index
|
|
&& _replace_current_item_unlocked (self, NULL, CLAPPER_QUEUE_INVALID_POSITION)) {
|
|
_announce_current_item_and_index_change (self);
|
|
} else if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION
|
|
&& index < self->current_index) {
|
|
/* If has selection and removed before it */
|
|
self->current_index--;
|
|
_announce_current_index_change (self);
|
|
}
|
|
|
|
removed_item = g_ptr_array_steal_index (self->items, index);
|
|
gst_object_unparent (GST_OBJECT_CAST (removed_item));
|
|
|
|
_announce_model_update (self, index, 1, 0, removed_item);
|
|
}
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return removed_item;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_clear:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Removes all media items from the queue.
|
|
*
|
|
* If queue is empty, this function will do nothing,
|
|
* so it is safe to call multiple times if unsure.
|
|
*/
|
|
void
|
|
clapper_queue_clear (ClapperQueue *self)
|
|
{
|
|
guint n_items;
|
|
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (self));
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
n_items = self->items->len;
|
|
|
|
if (n_items > 0) {
|
|
if (_replace_current_item_unlocked (self, NULL, CLAPPER_QUEUE_INVALID_POSITION))
|
|
_announce_current_item_and_index_change (self);
|
|
|
|
g_ptr_array_remove_range (self->items, 0, n_items);
|
|
_announce_model_update (self, 0, n_items, 0, NULL);
|
|
}
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_select_item:
|
|
* @queue: a #ClapperQueue
|
|
* @item: (nullable): a #ClapperMediaItem or %NULL to unselect
|
|
*
|
|
* Selects #ClapperMediaItem from @queue as current one or
|
|
* unselects currently selected item when @item is %NULL.
|
|
*
|
|
* Returns: %TRUE if item could be selected/unselected,
|
|
* %FALSE if it was not in the queue.
|
|
*/
|
|
gboolean
|
|
clapper_queue_select_item (ClapperQueue *self, ClapperMediaItem *item)
|
|
{
|
|
gboolean success = FALSE;
|
|
guint index = 0;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
g_return_val_if_fail (item == NULL || CLAPPER_IS_MEDIA_ITEM (item), FALSE);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
if (!item)
|
|
success = clapper_queue_select_index (self, CLAPPER_QUEUE_INVALID_POSITION);
|
|
else if (g_ptr_array_find (self->items, item, &index))
|
|
success = clapper_queue_select_index (self, index);
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_select_index:
|
|
* @queue: a #ClapperQueue
|
|
* @index: an item index or [const@Clapper.QUEUE_INVALID_POSITION] to unselect
|
|
*
|
|
* Selects #ClapperMediaItem at @index from @queue as current one or
|
|
* unselects currently selected index when @index is [const@Clapper.QUEUE_INVALID_POSITION].
|
|
*
|
|
* Returns: %TRUE if item at @index could be selected/unselected,
|
|
* %FALSE if index was out of queue range.
|
|
*/
|
|
gboolean
|
|
clapper_queue_select_index (ClapperQueue *self, guint index)
|
|
{
|
|
ClapperMediaItem *item = NULL;
|
|
gboolean success;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
if (index != CLAPPER_QUEUE_INVALID_POSITION && index < self->items->len)
|
|
item = g_ptr_array_index (self->items, index);
|
|
if ((success = (index == CLAPPER_QUEUE_INVALID_POSITION
|
|
|| index < self->items->len))) {
|
|
if (_replace_current_item_unlocked (self, item, index))
|
|
_announce_current_item_and_index_change (self);
|
|
}
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_select_next_item:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Selects next #ClapperMediaItem from @queue for playback.
|
|
*
|
|
* Note that this will try to select next item in the order
|
|
* of the queue, regardless of [enum@Clapper.QueueProgressionMode] set.
|
|
*
|
|
* Returns: %TRUE if there was another media item in queue, %FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
clapper_queue_select_next_item (ClapperQueue *self)
|
|
{
|
|
gboolean success = FALSE;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION
|
|
&& self->current_index < self->items->len - 1) {
|
|
GST_DEBUG_OBJECT (self, "Selecting next queue item");
|
|
success = clapper_queue_select_index (self, self->current_index + 1);
|
|
}
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_select_previous_item:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Selects previous #ClapperMediaItem from @queue for playback.
|
|
*
|
|
* Note that this will try to select previous item in the order
|
|
* of the queue, regardless of [enum@Clapper.QueueProgressionMode] set.
|
|
*
|
|
* Returns: %TRUE if there was previous media item in queue, %FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
clapper_queue_select_previous_item (ClapperQueue *self)
|
|
{
|
|
gboolean success = FALSE;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION
|
|
&& self->current_index > 0) {
|
|
GST_DEBUG_OBJECT (self, "Selecting previous queue item");
|
|
success = clapper_queue_select_index (self, self->current_index - 1);
|
|
}
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_get_item: (skip)
|
|
* @queue: a #ClapperQueue
|
|
* @index: an item index
|
|
*
|
|
* Get the #ClapperMediaItem at index.
|
|
*
|
|
* This behaves the same as [method@Gio.ListModel.get_item], and is here
|
|
* for code uniformity and convenience to avoid type casting by user.
|
|
*
|
|
* This function is not available in bindings as they already
|
|
* inherit `get_item()` method from [iface@Gio.ListModel] interface.
|
|
*
|
|
* Returns: (transfer full) (nullable): The #ClapperMediaItem at @index.
|
|
*/
|
|
ClapperMediaItem *
|
|
clapper_queue_get_item (ClapperQueue *self, guint index)
|
|
{
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), NULL);
|
|
|
|
return g_list_model_get_item (G_LIST_MODEL (self), index);
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_get_current_item:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Get the currently selected #ClapperMediaItem.
|
|
*
|
|
* Returns: (transfer full) (nullable): The current #ClapperMediaItem.
|
|
*/
|
|
ClapperMediaItem *
|
|
clapper_queue_get_current_item (ClapperQueue *self)
|
|
{
|
|
ClapperMediaItem *item = NULL;
|
|
|
|
/* XXX: For updating media item during playback we should
|
|
* use `player->played_item` instead to not be racy when
|
|
* changing and updating current item at the same time */
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), NULL);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
if (self->current_item)
|
|
item = gst_object_ref (self->current_item);
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_get_current_index:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Get index of the currently selected #ClapperMediaItem.
|
|
*
|
|
* Returns: Current item index or [const@Clapper.QUEUE_INVALID_POSITION]
|
|
* when nothing is selected.
|
|
*/
|
|
guint
|
|
clapper_queue_get_current_index (ClapperQueue *self)
|
|
{
|
|
guint index;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), CLAPPER_QUEUE_INVALID_POSITION);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
index = self->current_index;
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_item_is_current:
|
|
* @queue: a #ClapperQueue
|
|
* @item: a #ClapperMediaItem to check
|
|
*
|
|
* Checks if given #ClapperMediaItem is currently selected.
|
|
*
|
|
* Returns: %TRUE if @item is a current media item, %FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
clapper_queue_item_is_current (ClapperQueue *self, ClapperMediaItem *item)
|
|
{
|
|
gboolean is_current;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (item), FALSE);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
is_current = (item == self->current_item);
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return is_current;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_find_item:
|
|
* @queue: a #ClapperQueue
|
|
* @item: a #ClapperMediaItem to search for
|
|
* @index: (optional) (out): return location for the index of
|
|
* the element, if found
|
|
*
|
|
* Get the index of #ClapperMediaItem within #ClapperQueue.
|
|
*
|
|
* Returns: %TRUE if @item is one of the elements of queue.
|
|
*/
|
|
gboolean
|
|
clapper_queue_find_item (ClapperQueue *self, ClapperMediaItem *item, guint *index)
|
|
{
|
|
gboolean found;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (item), FALSE);
|
|
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
found = g_ptr_array_find (self->items, item, index);
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_get_n_items: (skip)
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Get the number of items in #ClapperQueue.
|
|
*
|
|
* This behaves the same as [method@Gio.ListModel.get_n_items], and is here
|
|
* for code uniformity and convenience to avoid type casting by user.
|
|
*
|
|
* This function is not available in bindings as they already
|
|
* inherit get_n_items() method from #GListModel interface.
|
|
*
|
|
* Returns: The number of items in #ClapperQueue.
|
|
*/
|
|
guint
|
|
clapper_queue_get_n_items (ClapperQueue *self)
|
|
{
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), 0);
|
|
|
|
return g_list_model_get_n_items (G_LIST_MODEL (self));
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_set_progression_mode:
|
|
* @queue: a #ClapperQueue
|
|
* @mode: a #ClapperQueueProgressionMode
|
|
*
|
|
* Set the #ClapperQueueProgressionMode of the #ClapperQueue.
|
|
*
|
|
* Changing the mode set will alter next item selection at the
|
|
* end of playback. For possible values and their descriptions,
|
|
* see #ClapperQueueProgressionMode documentation.
|
|
*/
|
|
void
|
|
clapper_queue_set_progression_mode (ClapperQueue *self, ClapperQueueProgressionMode mode)
|
|
{
|
|
gboolean changed;
|
|
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (self));
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
if ((changed = self->progression_mode != mode))
|
|
self->progression_mode = mode;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
if (changed) {
|
|
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
|
|
|
/* Start shuffle from the current item, allowing
|
|
* reselecting past items already used without it */
|
|
if (mode == CLAPPER_QUEUE_PROGRESSION_SHUFFLE) {
|
|
CLAPPER_QUEUE_REC_LOCK (self);
|
|
|
|
_reset_shuffle_unlocked (self);
|
|
if (self->current_item)
|
|
clapper_media_item_set_used (self->current_item, TRUE);
|
|
|
|
CLAPPER_QUEUE_REC_UNLOCK (self);
|
|
}
|
|
|
|
clapper_app_bus_post_prop_notify (player->app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_PROGRESSION_MODE]);
|
|
if (player->reactables_manager)
|
|
clapper_reactables_manager_trigger_queue_progression_changed (player->reactables_manager, mode);
|
|
if (clapper_player_get_have_features (player))
|
|
clapper_features_manager_trigger_queue_progression_changed (player->features_manager, mode);
|
|
|
|
gst_object_unref (player);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_get_progression_mode:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Get the #ClapperQueueProgressionMode of the #ClapperQueue.
|
|
*
|
|
* Returns: a currently set #ClapperQueueProgressionMode.
|
|
*/
|
|
ClapperQueueProgressionMode
|
|
clapper_queue_get_progression_mode (ClapperQueue *self)
|
|
{
|
|
ClapperQueueProgressionMode mode;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), DEFAULT_PROGRESSION_MODE);
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
mode = self->progression_mode;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
return mode;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_set_gapless:
|
|
* @queue: a #ClapperQueue
|
|
* @gapless: %TRUE to enable, %FALSE otherwise.
|
|
*
|
|
* Set #ClapperQueue progression to be gapless.
|
|
*
|
|
* Gapless playback will try to re-use as much as possible of underlying
|
|
* GStreamer elements when #ClapperQueue progresses, removing any
|
|
* potential gap in the data.
|
|
*
|
|
* Enabling this option mostly makes sense when used together with
|
|
* [property@Clapper.Queue:progression-mode] property set to
|
|
* [enum@Clapper.QueueProgressionMode.CONSECUTIVE].
|
|
*
|
|
* NOTE: This feature within GStreamer is rather new and
|
|
* might still cause playback issues. Disabled by default.
|
|
*/
|
|
void
|
|
clapper_queue_set_gapless (ClapperQueue *self, gboolean gapless)
|
|
{
|
|
gboolean changed;
|
|
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (self));
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
if ((changed = self->gapless != gapless))
|
|
self->gapless = gapless;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
if (changed) {
|
|
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
|
|
|
clapper_app_bus_post_prop_notify (player->app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_GAPLESS]);
|
|
|
|
gst_object_unref (player);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_get_gapless:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Get if #ClapperQueue is set to use gapless progression.
|
|
*
|
|
* Returns: %TRUE if enabled, %FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
clapper_queue_get_gapless (ClapperQueue *self)
|
|
{
|
|
gboolean gapless;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
gapless = self->gapless;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
return gapless;
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_set_instant:
|
|
* @queue: a #ClapperQueue
|
|
* @instant: %TRUE to enable, %FALSE otherwise.
|
|
*
|
|
* Set #ClapperQueue media item changes to be instant.
|
|
*
|
|
* Instant will try to re-use as much as possible of underlying
|
|
* GStreamer elements when #ClapperMediaItem is selected, allowing
|
|
* media item change requests to be faster.
|
|
*
|
|
* NOTE: This feature within GStreamer is rather new and
|
|
* might still cause playback issues. Disabled by default.
|
|
*/
|
|
void
|
|
clapper_queue_set_instant (ClapperQueue *self, gboolean instant)
|
|
{
|
|
gboolean changed;
|
|
|
|
g_return_if_fail (CLAPPER_IS_QUEUE (self));
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
if ((changed = self->instant != instant))
|
|
self->instant = instant;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
if (changed) {
|
|
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
|
|
|
clapper_app_bus_post_prop_notify (player->app_bus,
|
|
GST_OBJECT_CAST (self), param_specs[PROP_INSTANT]);
|
|
|
|
gst_object_unref (player);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clapper_queue_get_instant:
|
|
* @queue: a #ClapperQueue
|
|
*
|
|
* Get if #ClapperQueue is set to use instant media item changes.
|
|
*
|
|
* Returns: %TRUE if enabled, %FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
clapper_queue_get_instant (ClapperQueue *self)
|
|
{
|
|
gboolean instant;
|
|
|
|
g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE);
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
instant = self->instant;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
return instant;
|
|
}
|
|
|
|
static void
|
|
_item_remove_func (ClapperMediaItem *item)
|
|
{
|
|
gst_object_unparent (GST_OBJECT_CAST (item));
|
|
gst_object_unref (item);
|
|
}
|
|
|
|
static void
|
|
clapper_queue_init (ClapperQueue *self)
|
|
{
|
|
g_rec_mutex_init (&self->rec_lock);
|
|
|
|
self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) _item_remove_func);
|
|
|
|
self->current_index = CLAPPER_QUEUE_INVALID_POSITION;
|
|
self->progression_mode = DEFAULT_PROGRESSION_MODE;
|
|
self->gapless = DEFAULT_GAPLESS;
|
|
self->instant = DEFAULT_INSTANT;
|
|
}
|
|
|
|
static void
|
|
clapper_queue_finalize (GObject *object)
|
|
{
|
|
ClapperQueue *self = CLAPPER_QUEUE_CAST (object);
|
|
|
|
GST_TRACE_OBJECT (self, "Finalize");
|
|
|
|
g_rec_mutex_clear (&self->rec_lock);
|
|
|
|
gst_clear_object (&self->current_item);
|
|
g_ptr_array_unref (self->items);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
clapper_queue_get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
ClapperQueue *self = CLAPPER_QUEUE_CAST (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_CURRENT_ITEM:
|
|
g_value_take_object (value, clapper_queue_get_current_item (self));
|
|
break;
|
|
case PROP_CURRENT_INDEX:
|
|
g_value_set_uint (value, clapper_queue_get_current_index (self));
|
|
break;
|
|
case PROP_N_ITEMS:
|
|
g_value_set_uint (value, clapper_queue_get_n_items (self));
|
|
break;
|
|
case PROP_PROGRESSION_MODE:
|
|
g_value_set_enum (value, clapper_queue_get_progression_mode (self));
|
|
break;
|
|
case PROP_GAPLESS:
|
|
g_value_set_boolean (value, clapper_queue_get_gapless (self));
|
|
break;
|
|
case PROP_INSTANT:
|
|
g_value_set_boolean (value, clapper_queue_get_instant (self));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clapper_queue_set_property (GObject *object, guint prop_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
ClapperQueue *self = CLAPPER_QUEUE_CAST (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_CURRENT_INDEX:
|
|
clapper_queue_select_index (self, g_value_get_uint (value));
|
|
break;
|
|
case PROP_PROGRESSION_MODE:
|
|
clapper_queue_set_progression_mode (self, g_value_get_enum (value));
|
|
break;
|
|
case PROP_GAPLESS:
|
|
clapper_queue_set_gapless (self, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_INSTANT:
|
|
clapper_queue_set_instant (self, g_value_get_boolean (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clapper_queue_class_init (ClapperQueueClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperqueue", 0,
|
|
"Clapper Queue");
|
|
|
|
gobject_class->get_property = clapper_queue_get_property;
|
|
gobject_class->set_property = clapper_queue_set_property;
|
|
gobject_class->finalize = clapper_queue_finalize;
|
|
|
|
/**
|
|
* ClapperQueue:current-item:
|
|
*
|
|
* Currently selected media item for playback.
|
|
*/
|
|
param_specs[PROP_CURRENT_ITEM] = g_param_spec_object ("current-item",
|
|
NULL, NULL, CLAPPER_TYPE_MEDIA_ITEM,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* ClapperQueue:current-index:
|
|
*
|
|
* Index of currently selected media item for playback.
|
|
*/
|
|
param_specs[PROP_CURRENT_INDEX] = g_param_spec_uint ("current-index",
|
|
NULL, NULL, 0, G_MAXUINT, CLAPPER_QUEUE_INVALID_POSITION,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* ClapperQueue:n-items:
|
|
*
|
|
* Number of media items in the queue.
|
|
*/
|
|
param_specs[PROP_N_ITEMS] = g_param_spec_uint ("n-items",
|
|
NULL, NULL, 0, G_MAXUINT, 0,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* ClapperQueue:progression-mode:
|
|
*
|
|
* Queue progression mode.
|
|
*/
|
|
param_specs[PROP_PROGRESSION_MODE] = g_param_spec_enum ("progression-mode",
|
|
NULL, NULL, CLAPPER_TYPE_QUEUE_PROGRESSION_MODE, DEFAULT_PROGRESSION_MODE,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* ClapperQueue:gapless:
|
|
*
|
|
* Use gapless progression.
|
|
*/
|
|
param_specs[PROP_GAPLESS] = g_param_spec_boolean ("gapless",
|
|
NULL, NULL, DEFAULT_GAPLESS,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* ClapperQueue:instant:
|
|
*
|
|
* Use instant media item changes.
|
|
*/
|
|
param_specs[PROP_INSTANT] = g_param_spec_boolean ("instant",
|
|
NULL, NULL, DEFAULT_INSTANT,
|
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
|
}
|