[vlc-devel] [PATCH 02/12] core: playlist: new playlist API
Romain Vimont
rom1v at videolabs.io
Thu Oct 11 23:14:40 CEST 2018
Add a new playlist API.
A playlist contains a simple list of items, and owns a player.
Callbacks are exposed so that clients (UI) are notified when items are
updated, insert, moved or removed. The playlist is responsible for the
playback order and repeat mode, and manages a cursor to the "current"
item.
---
include/vlc_playlist_new.h | 675 +++++++++++
src/Makefile.am | 7 +-
src/libvlccore.sym | 32 +
src/playlist/playlist.c | 2323 ++++++++++++++++++++++++++++++++++++
4 files changed, 3036 insertions(+), 1 deletion(-)
create mode 100644 include/vlc_playlist_new.h
create mode 100644 src/playlist/playlist.c
diff --git a/include/vlc_playlist_new.h b/include/vlc_playlist_new.h
new file mode 100644
index 0000000000..1e62bf89ed
--- /dev/null
+++ b/include/vlc_playlist_new.h
@@ -0,0 +1,675 @@
+/*****************************************************************************
+ * vlc_playlist_new.h
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_PLAYLIST_NEW_H_
+#define VLC_PLAYLIST_NEW_H_
+
+#include <vlc_common.h>
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+/**
+ * \defgroup playlist playlist
+ * \ingroup playlist
+ * @{
+ */
+
+/**
+ * A VLC playlist contains a list of "playlist items".
+ *
+ * Each playlist item contains exactly one media (input item). In the future,
+ * it might contain associated data.
+ *
+ * The API is intended to be simple, UI-friendly and allow for an
+ * implementation both correct (no race conditions) and performant for common
+ * use cases.
+ *
+ * UI frameworks typically use "list models" to provide a list of items to a
+ * list view component. A list model requires to implement functions to:
+ * - return the total number of items,
+ * - return the item at a given index.
+ *
+ * In addition, it must notify the view when changes occur when:
+ * - items are inserted (providing index and count),
+ * - items are removed (providing index and count),
+ * - items are moved (providing index, count, and target index),
+ * - items are updated (providing index and count),
+ * - the model is reset (the whole content should be considered changed).
+ *
+ * The API directly exposes what list models require.
+ *
+ * The core playlist may be modified from any thread, so it may not be used as
+ * a direct data source for a list model. In other word, the functions of a
+ * list model must not delegate the calls to the playlist. This would require
+ * locking the playlist individually for each call to get the count and
+ * retrieve each item (which is, in itself, not a good idea for UI
+ * responsiveness), and would not be sufficient to guarantee correctness: the
+ * playlist content could change between view calls so that a request to
+ * retrieve an item at a specific index could be invalid (which would break the
+ * list model expected behavior).
+ *
+ * As a consequence, the UI playlist should be considered as a remote
+ * out-of-sync view of the core playlist. This implies that the UI needs to
+ * keep a copy of the playlist content.
+ *
+ * Note that the copy must not limited to the list of playlist items (pointers)
+ * themselves, but also to the items content which is displayed and susceptible
+ * to change asynchronously (e.g. media metadata, like title or duration). The
+ * UI should never lock a media (input item) for rendering a playlist item;
+ * otherwise, the content could be changed (and exposed) before the list model
+ * notified the view of this change (which, again, would break the list model
+ * expected behavior).
+ *
+ * It is very important that the copy hold by the UI is only modified through
+ * the core playlist callbacks, to guarantee that the indexes notified are
+ * valid in the context of the list model. In other words, from the client, the
+ * playlist copy is a read-only "desynchronized" view of the core playlist.
+ *
+ * Moreover, the events triggered by the playlist must be kept in order until
+ * they are handled. The callbacks may be called from any thread, with lock
+ * held (in practice, the thread from which a change is requested). An UI will
+ * typically need to handle the events in the UI thread, so it will usually
+ * post the events in an even loop, to handle them from the UI thread. In that
+ * case, be careful to always post the events in the event loop, even if the
+ * current thread is already the UI thread, not to break the order of events.
+ *
+ * The playlist also handles the playback order and the repeat mode. It also
+ * manages a cursor to the "current" item, and expose whether a previous and
+ * next items (which depend on the playback order and repeat mode) are
+ * available.
+ */
+
+/* forward declarations */
+typedef struct input_item_t input_item_t;
+typedef struct vlc_player_t vlc_player_t;
+
+/* opaque types */
+typedef struct vlc_playlist vlc_playlist_t;
+typedef struct vlc_playlist_item vlc_playlist_item_t;
+typedef struct vlc_playlist_listener_id vlc_playlist_listener_id;
+
+enum vlc_playlist_playback_repeat
+{
+ VLC_PLAYLIST_PLAYBACK_REPEAT_NONE,
+ VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT,
+ VLC_PLAYLIST_PLAYBACK_REPEAT_ALL,
+};
+
+enum vlc_playlist_playback_order
+{
+ VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL,
+ VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM,
+};
+
+/**
+ * Playlist callbacks.
+ *
+ * A client may register a listener using vlc_playlist_AddListener() to listen
+ * playlist events.
+ *
+ * All callbacks are called with the playlist locked (see vlc_playlist_Lock()).
+ */
+struct vlc_playlist_callbacks
+{
+ /**
+ * Called when the whole content has changed (e.g. when the playlist has
+ * been cleared, shuffled or sorted).
+ *
+ * \param playlist the playlist
+ * \param items the whole new content of the playlist
+ * \param count the number of items
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_items_reset)(vlc_playlist_t *, vlc_playlist_item_t *const items[],
+ size_t count, void *userdata);
+
+ /**
+ * Called when items have been added to the playlist.
+ *
+ * \param playlist the playlist
+ * \param index the index of the insertion
+ * \param items the array of added items
+ * \param count the number of items added
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_items_added)(vlc_playlist_t *playlist, size_t index,
+ vlc_playlist_item_t *const items[], size_t count,
+ void *userdata);
+
+ /**
+ * Called when a slice of items have been moved.
+ *
+ * \param playlist the playlist
+ * \param index the index of the first moved item
+ * \param count the number of items moved
+ * \param target the new index of the moved slice
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_items_moved)(vlc_playlist_t *playlist, size_t index,
+ size_t count, size_t target, void *userdata);
+ /**
+ * Called when a slice of items have been removed from the playlist.
+ *
+ * \param playlist the playlist
+ * \param index the index of the first removed item
+ * \param items the array of removed items
+ * \param count the number of items removed
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_items_removed)(vlc_playlist_t *playlist, size_t index,
+ size_t count, void *userdata);
+
+ /**
+ * Called when an item has been updated via (pre-)parsing.
+ *
+ * \param playlist the playlist
+ * \param index the index of the first updated item
+ * \param items the array of updated items
+ * \param count the number of items updated
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_items_updated)(vlc_playlist_t *, size_t index,
+ vlc_playlist_item_t *const items[], size_t count,
+ void *userdata);
+
+ /**
+ * Called when the playback repeat mode has been changed.
+ *
+ * \param playlist the playlist
+ * \param repeat the new playback "repeat" mode
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_playback_repeat_changed)(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_repeat repeat,
+ void *userdata);
+
+ /**
+ * Called when the playback order mode has been changed.
+ *
+ * \param playlist the playlist
+ * \param rorder the new playback order
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_playback_order_changed)(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_order order,
+ void *userdata);
+
+ /**
+ * Called when the current item index has changed.
+ *
+ * Note that the current item index may have changed while the current item
+ * is still the same: it may have been moved.
+ *
+ * \param playlist the playlist
+ * \param index the new current index (-1 if there is no current item)
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_current_index_changed)(vlc_playlist_t *playlist, ssize_t index,
+ void *userdata);
+
+ /**
+ * Called when the "has previous item" property has changed.
+ *
+ * This is typically useful to update any "previous" button in the UI.
+ *
+ * \param playlist the playlist
+ * \param has_prev true if there is a previous item, false otherwise
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_has_prev_changed)(vlc_playlist_t *, bool has_prev,
+ void *userdata);
+
+ /**
+ * Called when the "has next item" property has changed.
+ *
+ * This is typically useful to update any "next" button in the UI.
+ *
+ * \param playlist the playlist
+ * \param has_next true if there is a next item, false otherwise
+ * \param userdata userdata provided to AddListener()
+ */
+ void (*on_has_next_changed)(vlc_playlist_t *, bool has_next,
+ void *userdata);
+};
+
+/* Playlist items */
+
+/**
+ * Hold a playlist item.
+ *
+ * Increment the refcount of the playlist item.
+ */
+VLC_API void
+vlc_playlist_item_Hold(vlc_playlist_item_t *);
+
+/**
+ * Release a playlist item.
+ *
+ * Decrement the refcount of the playlist item, and destroy it if necessary.
+ */
+VLC_API void
+vlc_playlist_item_Release(vlc_playlist_item_t *);
+
+/**
+ * Return the media associated to the playlist item.
+ */
+VLC_API input_item_t *
+vlc_playlist_item_GetMedia(vlc_playlist_item_t *);
+
+/* Playlist */
+
+/**
+ * Create a new playlist.
+ *
+ * \param parent a VLC object
+ * \return a pointer to a valid playlist instance, or NULL if an error occurred
+ */
+VLC_API VLC_USED vlc_playlist_t *
+vlc_playlist_New(vlc_object_t *parent);
+
+/**
+ * Delete a playlist.
+ *
+ * All playlist items are released, and listeners are removed and destroyed.
+ */
+VLC_API void
+vlc_playlist_Delete(vlc_playlist_t *);
+
+/**
+ * Lock the playlist/player.
+ *
+ * The playlist and its player share the same lock, to avoid lock-order
+ * inversion issues.
+ *
+ * \warning Do not forget that the playlist and player lock are the same (or
+ * you could lock twice the same and deadlock).
+ *
+ * Almost all playlist functions must be called with lock held (check their
+ * description).
+ *
+ * The lock is not recursive.
+ */
+VLC_API void
+vlc_playlist_Lock(vlc_playlist_t *);
+
+/**
+ * Unlock the playlist/player.
+ */
+VLC_API void
+vlc_playlist_Unlock(vlc_playlist_t *);
+
+/**
+ * Add a playlist listener.
+ *
+ * Return an opaque listener identifier, to be passed to
+ * vlc_player_RemoveListener().
+ *
+ * \param playlist the playlist
+ * \param cbs the callbacks (must be valid until the listener is removed)
+ * \param userdata userdata provided as a parameter in callbacks
+ * \return a listener identifier, or NULL if an error occurred
+ */
+VLC_API VLC_USED vlc_playlist_listener_id *
+vlc_playlist_AddListener(vlc_playlist_t *playlist,
+ const struct vlc_playlist_callbacks *cbs,
+ void *userdata);
+
+/**
+ * Remove a player listener.
+ *
+ * \param playlist the playlist
+ * \param id the listener identifier returned by
+ * vlc_playlist_AddListener()
+ */
+VLC_API void
+vlc_playlist_RemoveListener(vlc_playlist_t *, vlc_playlist_listener_id *);
+
+/**
+ * Return the number of items.
+ *
+ * \param playlist the playlist, locked
+ */
+VLC_API size_t
+vlc_playlist_Count(vlc_playlist_t *playlist);
+
+/**
+ * Return the item at a given index.
+ *
+ * The index must be in range (less than vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \param index the index
+ * \return the playlist item
+ */
+VLC_API vlc_playlist_item_t *
+vlc_playlist_Get(vlc_playlist_t *playlist, size_t index);
+
+/**
+ * Clear the playlist.
+ *
+ * \param playlist the playlist, locked
+ */
+VLC_API void
+vlc_playlist_Clear(vlc_playlist_t *playlist);
+
+/**
+ * Insert a list of media at a given index.
+ *
+ * The index must be in range (less than or equal to vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \index index the index where the media are to be inserted
+ * \param media the array of media to insert
+ * \param count the number of media to insert
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_Insert(vlc_playlist_t *playlist, size_t index,
+ input_item_t *const media[], size_t count);
+
+/**
+ * Insert a media at a given index.
+ *
+ * The index must be in range (less than or equal to vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \index index the index where the media is to be inserted
+ * \param media the media to insert
+ * \return VLC_SUCCESS on success, another value on error
+ */
+static inline int
+vlc_playlist_InsertOne(vlc_playlist_t *playlist, size_t index,
+ input_item_t *media)
+{
+ return vlc_playlist_Insert(playlist, index, &media, 1);
+}
+
+/**
+ * Add a list of media at the end of the playlist.
+ *
+ * \param playlist the playlist, locked
+ * \param media the array of media to append
+ * \param count the number of media to append
+ * \return VLC_SUCCESS on success, another value on error
+ */
+static inline int
+vlc_playlist_Append(vlc_playlist_t *playlist, input_item_t *const media[],
+ size_t count)
+{
+ size_t size = vlc_playlist_Count(playlist);
+ return vlc_playlist_Insert(playlist, size, media, count);
+}
+
+/**
+ * Add a media at the end of the playlist.
+ *
+ * \param playlist the playlist, locked
+ * \param media the media to append
+ * \return VLC_SUCCESS on success, another value on error
+ */
+static inline int
+vlc_playlist_AppendOne(vlc_playlist_t *playlist, input_item_t *media)
+{
+ return vlc_playlist_Append(playlist, &media, 1);
+}
+
+/**
+ * Move a slice of items to a given target index.
+ *
+ * The slice and the target must be in range (both index+count and target+count
+ * less than or equal to vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \param index the index of the first item to move
+ * \param count the number of items to move
+ * \param target the new index of the moved slice
+ */
+VLC_API void
+vlc_playlist_Move(vlc_playlist_t *playlist, size_t index, size_t count,
+ size_t target);
+
+/**
+ * Move an item to a given target index.
+ *
+ * The index and the target must be in range (index less than, and target less
+ * than or equal to, vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \param index the index of the item to move
+ * \param target the new index of the moved item
+ */
+static inline void
+vlc_playlist_MoveOne(vlc_playlist_t *playlist, size_t index, size_t target)
+{
+ vlc_playlist_Move(playlist, index, 1, target);
+}
+
+/**
+ * Remove a slice of items at a given index.
+ *
+ * The slice must be in range (index+count less than or equal to
+ * vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \param index the index of the first item to remove
+ * \param count the number of items to remove
+ */
+VLC_API void
+vlc_playlist_Remove(vlc_playlist_t *playlist, size_t index, size_t count);
+
+/**
+ * Remove an item at a given index.
+ *
+ * The index must be in range (less than vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \param index the index of the item to remove
+ */
+static inline void
+vlc_playlist_RemoveOne(vlc_playlist_t *playlist, size_t index)
+{
+ vlc_playlist_Remove(playlist, index, 1);
+}
+
+/**
+ * Return the index of a given item.
+ *
+ * \param playlist the playlist, locked
+ * \param item the item to locate
+ * \return the index of the item (-1 if not found)
+ */
+VLC_API ssize_t
+vlc_playlist_IndexOf(vlc_playlist_t *playlist, const vlc_playlist_item_t *item);
+
+/**
+ * Return the index of a given media.
+ *
+ * \param playlist the playlist, locked
+ * \param media the media to locate
+ * \return the index of the playlist item containing the media (-1 if not found)
+ */
+VLC_API ssize_t
+vlc_playlist_IndexOfMedia(vlc_playlist_t *playlist, const input_item_t *media);
+
+/**
+ * Return the playback "repeat" mode.
+ *
+ * \param playlist the playlist, locked
+ * \return the playback "repeat" mode
+ */
+VLC_API enum vlc_playlist_playback_repeat
+vlc_playlist_GetPlaybackRepeat(vlc_playlist_t *playlist);
+
+/**
+ * Return the playback order.
+ *
+ * \param playlist the playlist, locked
+ * \return the playback order
+ */
+VLC_API enum vlc_playlist_playback_order
+vlc_playlist_GetPlaybackOrder(vlc_playlist_t *);
+
+/**
+ * Change the playback "repeat" mode.
+ *
+ * \param playlist the playlist, locked
+ * \param repeat the new playback "repeat" mode
+ */
+VLC_API void
+vlc_playlist_SetPlaybackRepeat(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_repeat repeat);
+
+/**
+ * Change the playback order
+ *
+ * \param playlist the playlist, locked
+ * \param repeat the new playback order
+ */
+VLC_API void
+vlc_playlist_SetPlaybackOrder(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_order order);
+
+/**
+ * Return the index of the current item.
+ *
+ * \param playlist the playlist, locked
+ * \return the index of the current item, -1 if none.
+ */
+VLC_API ssize_t
+vlc_playlist_GetCurrentIndex(vlc_playlist_t *playlist);
+
+/**
+ * Indicate whether a previous item is available.
+ *
+ * \param playlist the playlist, locked
+ * \retval true if a previous item is available
+ * \retval false if no previous item is available
+ */
+VLC_API bool
+vlc_playlist_HasPrev(vlc_playlist_t *playlist);
+
+/**
+ * Indicate whether a next item is available.
+ *
+ * \param playlist the playlist, locked
+ * \retval true if a next item is available
+ * \retval false if no next item is available
+ */
+VLC_API bool
+vlc_playlist_HasNext(vlc_playlist_t *playlist);
+
+/**
+ * Go to the previous item.
+ *
+ * Return VLC_EGENERIC if vlc_playlist_HasPrev() returns false.
+ *
+ * \param playlist the playlist, locked
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_Prev(vlc_playlist_t *playlist);
+
+/**
+ * Go to the next item.
+ *
+ * Return VLC_EGENERIC if vlc_playlist_HasNext() returns false.
+ *
+ * \param playlist the playlist, locked
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_Next(vlc_playlist_t *playlist);
+
+/**
+ * Go to a given index.
+ *
+ * the index must be -1 or in range (less than vlc_playlist_Count()).
+ *
+ * \param playlist the playlist, locked
+ * \param index the index to go to (-1 to none)
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_GoTo(vlc_playlist_t *playlist, ssize_t index);
+
+/**
+ * Return the player owned by the playlist.
+ *
+ * \param playlist the playlist (not necessarily locked)
+ * \return the player
+ */
+VLC_API vlc_player_t *
+vlc_playlist_GetPlayer(vlc_playlist_t *playlist);
+
+/**
+ * Start the player.
+ *
+ * \param playlist the playlist, locked
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_Start(vlc_playlist_t *playlist);
+
+/**
+ * Stop the player.
+ *
+ * \param playlist the playlist, locked
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API void
+vlc_playlist_Stop(vlc_playlist_t *playlist);
+
+/**
+ * Pause the player.
+ *
+ * \param playlist the playlist, locked
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API void
+vlc_playlist_Pause(vlc_playlist_t *playlist);
+
+/**
+ * Resume the player.
+ *
+ * \param playlist the playlist, locked
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API void
+vlc_playlist_Resume(vlc_playlist_t *playlist);
+
+/**
+ * Preparse a media, and expand it in the playlist on subitems added.
+ *
+ * \param playlist the playlist (not necessarily locked)
+ * \param libvlc the libvlc instance
+ * \param media the media to preparse
+ */
+VLC_API void
+vlc_playlist_Preparse(vlc_playlist_t *playlist, libvlc_int_t *libvlc,
+ input_item_t *media);
+
+/** @} */
+# ifdef __cplusplus
+}
+# endif
+
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 30c06abcf6..2a98636901 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -78,6 +78,7 @@ pluginsinclude_HEADERS = \
../include/vlc_picture_pool.h \
../include/vlc_player.h \
../include/vlc_playlist.h \
+ ../include/vlc_playlist_new.h \
../include/vlc_plugin.h \
../include/vlc_probe.h \
../include/vlc_rand.h \
@@ -223,6 +224,7 @@ libvlccore_la_SOURCES = \
playlist/loadsave.c \
playlist/tree.c \
playlist/item.c \
+ playlist/playlist.c \
playlist/search.c \
playlist/services_discovery.c \
playlist/renderer.c \
@@ -536,7 +538,8 @@ check_PROGRAMS = \
test_mrl_helpers \
test_arrays \
test_vector \
- test_shared_data_ptr
+ test_shared_data_ptr \
+ test_playlist
TESTS = $(check_PROGRAMS) check_symbols
@@ -561,6 +564,8 @@ test_mrl_helpers_SOURCES = test/mrl_helpers.c
test_arrays_SOURCES = test/arrays.c
test_vector_SOURCES = test/vector.c
test_shared_data_ptr_SOURCES = test/shared_data_ptr.cpp
+test_playlist_SOURCES = playlist/playlist.c
+test_playlist_CFLAGS = -DTEST_PLAYLIST
AM_LDFLAGS = -no-install
LDADD = libvlccore.la \
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index a7df3bd87b..577a24a002 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -858,4 +858,36 @@ vlc_player_track_Delete
vlc_player_track_Dup
vlc_player_vout_IsFullscreen
vlc_player_vout_SetFullscreen
+vlc_playlist_item_Hold
+vlc_playlist_item_Release
+vlc_playlist_item_GetMedia
+vlc_playlist_New
+vlc_playlist_Delete
+vlc_playlist_Lock
+vlc_playlist_Unlock
+vlc_playlist_AddListener
+vlc_playlist_RemoveListener
+vlc_playlist_Count
+vlc_playlist_Get
+vlc_playlist_Clear
+vlc_playlist_Insert
+vlc_playlist_Move
+vlc_playlist_Remove
+vlc_playlist_IndexOf
+vlc_playlist_IndexOfMedia
+vlc_playlist_GetPlaybackRepeat
+vlc_playlist_GetPlaybackOrder
+vlc_playlist_SetPlaybackRepeat
+vlc_playlist_SetPlaybackOrder
+vlc_playlist_GetCurrentIndex
+vlc_playlist_HasPrev
+vlc_playlist_HasNext
+vlc_playlist_Prev
+vlc_playlist_Next
+vlc_playlist_GoTo
vlc_playlist_GetPlayer
+vlc_playlist_Start
+vlc_playlist_Stop
+vlc_playlist_Pause
+vlc_playlist_Resume
+vlc_playlist_Preparse
diff --git a/src/playlist/playlist.c b/src/playlist/playlist.c
new file mode 100644
index 0000000000..957a3c99b8
--- /dev/null
+++ b/src/playlist/playlist.c
@@ -0,0 +1,2323 @@
+/*****************************************************************************
+ * playlist.c
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_playlist_new.h>
+
+#include <assert.h>
+#include <vlc_common.h>
+#include <vlc_atomic.h>
+#include <vlc_input_item.h>
+#include <vlc_list.h>
+#include <vlc_threads.h>
+#include <vlc_vector.h>
+#include "input/player.h"
+#include "libvlc.h" // for vlc_MetadataRequest()
+
+#ifdef TEST_PLAYLIST
+/* disable vlc_assert_locked in tests since the symbol is not exported */
+# define vlc_assert_locked(m) VLC_UNUSED(m)
+# define vlc_player_assert_locked(p) VLC_UNUSED(p);
+/* mock the player in tests */
+# define vlc_player_New(a,b,c) (VLC_UNUSED(a), VLC_UNUSED(b), malloc(1))
+# define vlc_player_Delete(p) free(p)
+# define vlc_player_Lock(p) VLC_UNUSED(p)
+# define vlc_player_Unlock(p) VLC_UNUSED(p)
+# define vlc_player_AddListener(a,b,c) (VLC_UNUSED(b), malloc(1))
+# define vlc_player_RemoveListener(a,b) free(b)
+# define vlc_player_SetCurrentMedia(a,b) (VLC_UNUSED(b), VLC_SUCCESS)
+# define vlc_player_InvalidateNextMedia(p) VLC_UNUSED(p)
+#endif /* TEST_PLAYLIST */
+
+struct vlc_playlist_listener_id
+{
+ const struct vlc_playlist_callbacks *cbs;
+ void *userdata;
+ struct vlc_list node; /**< node of vlc_playlist.listeners */
+};
+
+#define vlc_playlist_listener_foreach(listener, playlist) \
+ vlc_list_foreach(listener, &(playlist)->listeners, node)
+
+struct vlc_playlist_item
+{
+ input_item_t *media;
+ vlc_atomic_rc_t rc;
+};
+
+static vlc_playlist_item_t *
+vlc_playlist_item_New(input_item_t *media)
+{
+ vlc_playlist_item_t *item = malloc(sizeof(*item));
+ if (unlikely(!item))
+ return NULL;
+
+ vlc_atomic_rc_init(&item->rc);
+ item->media = media;
+ input_item_Hold(media);
+ return item;
+}
+
+void
+vlc_playlist_item_Hold(vlc_playlist_item_t *item)
+{
+ vlc_atomic_rc_inc(&item->rc);
+}
+
+void
+vlc_playlist_item_Release(vlc_playlist_item_t *item)
+{
+ if (vlc_atomic_rc_dec(&item->rc))
+ {
+ input_item_Release(item->media);
+ free(item);
+ }
+}
+
+input_item_t *
+vlc_playlist_item_GetMedia(vlc_playlist_item_t *item)
+{
+ return item->media;
+}
+
+typedef struct VLC_VECTOR(vlc_playlist_item_t *) playlist_item_vector_t;
+
+struct vlc_playlist
+{
+ vlc_player_t *player;
+ /* all remaining fields are protected by the lock of the player */
+ struct vlc_player_listener_id *player_listener;
+ playlist_item_vector_t items;
+ ssize_t current;
+ bool has_prev;
+ bool has_next;
+ struct vlc_list listeners; /**< list of vlc_playlist_listener_id.node */
+ enum vlc_playlist_playback_repeat repeat;
+ enum vlc_playlist_playback_order order;
+};
+
+static inline void
+PlaylistAssertLocked(vlc_playlist_t *playlist)
+{
+ vlc_player_assert_locked(playlist->player);
+}
+
+#define PlaylistNotifyListener(playlist, listener, event, ...) \
+do { \
+ if (listener->cbs->event) \
+ listener->cbs->event(playlist, ##__VA_ARGS__, listener->userdata); \
+} while (0)
+
+#define PlaylistNotify(playlist, event, ...) \
+do { \
+ PlaylistAssertLocked(playlist); \
+ vlc_playlist_listener_id *listener; \
+ vlc_playlist_listener_foreach(listener, playlist) \
+ PlaylistNotifyListener(playlist, listener, event, ##__VA_ARGS__); \
+} while(0)
+
+/* Helper to notify several changes at once */
+struct vlc_playlist_state {
+ ssize_t current;
+ bool has_prev;
+ bool has_next;
+};
+
+static void
+vlc_playlist_state_Save(vlc_playlist_t *playlist,
+ struct vlc_playlist_state *state)
+{
+ state->current = playlist->current;
+ state->has_prev = playlist->has_prev;
+ state->has_next = playlist->has_next;
+}
+
+static void
+vlc_playlist_state_NotifyChanges(vlc_playlist_t *playlist,
+ struct vlc_playlist_state *saved_state)
+{
+ if (saved_state->current != playlist->current)
+ PlaylistNotify(playlist, on_current_index_changed, playlist->current);
+ if (saved_state->has_prev != playlist->has_prev)
+ PlaylistNotify(playlist, on_has_prev_changed, playlist->has_prev);
+ if (saved_state->has_next != playlist->has_next)
+ PlaylistNotify(playlist, on_has_next_changed, playlist->has_next);
+}
+
+static inline bool
+PlaylistHasItemUpdatedListeners(vlc_playlist_t *playlist)
+{
+ vlc_playlist_listener_id *listener;
+ vlc_playlist_listener_foreach(listener, playlist)
+ if (listener->cbs->on_items_updated)
+ return true;
+ return false;
+}
+
+static inline void
+NotifyMediaUpdated(vlc_playlist_t *playlist, input_item_t *media)
+{
+ PlaylistAssertLocked(playlist);
+ if (!PlaylistHasItemUpdatedListeners(playlist))
+ /* no need to find the index if there are no listeners */
+ return;
+
+ ssize_t index;
+ if (playlist->current != -1 &&
+ playlist->items.data[playlist->current]->media == media)
+ /* the player typically sends events for the current item, so we can
+ * often avoid to search */
+ index = playlist->current;
+ else
+ {
+ /* linear search */
+ index = vlc_playlist_IndexOfMedia(playlist, media);
+ if (index == -1)
+ return;
+ }
+ PlaylistNotify(playlist, on_items_updated, index,
+ &playlist->items.data[index], 1);
+}
+
+static inline vlc_playlist_item_t *
+PlaylistGetItem(vlc_playlist_t *playlist, ssize_t index)
+{
+ PlaylistAssertLocked(playlist);
+
+ if (index == -1)
+ return NULL;
+
+ return playlist->items.data[index];
+}
+
+static inline input_item_t *
+PlaylistGetMedia(vlc_playlist_t *playlist, ssize_t index)
+{
+ vlc_playlist_item_t *item = PlaylistGetItem(playlist, index);
+
+ if (!item)
+ return NULL;
+
+ return item->media;
+}
+
+static inline void
+PlaylistClear(vlc_playlist_t *playlist)
+{
+ vlc_playlist_item_t *item;
+ vlc_vector_foreach(item, &playlist->items)
+ vlc_playlist_item_Release(item);
+ vlc_vector_clear(&playlist->items);
+}
+
+static inline int
+PlaylistSetCurrentMedia(vlc_playlist_t *playlist, ssize_t index)
+{
+ PlaylistAssertLocked(playlist);
+
+ vlc_playlist_item_t *item = PlaylistGetItem(playlist, index);
+ input_item_t *media = item ? item->media : NULL;
+ return vlc_player_SetCurrentMedia(playlist->player, media);
+}
+
+static inline bool
+PlaylistNormalOrderHasPrev(vlc_playlist_t *playlist)
+{
+ if (playlist->current == -1)
+ return false;
+
+ if (playlist->repeat == VLC_PLAYLIST_PLAYBACK_REPEAT_ALL)
+ return true;
+
+ return playlist->current > 0;
+}
+
+static inline size_t
+PlaylistNormalOrderGetPrevIndex(vlc_playlist_t *playlist)
+{
+ switch (playlist->repeat)
+ {
+ case VLC_PLAYLIST_PLAYBACK_REPEAT_NONE:
+ case VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT:
+ return playlist->current - 1;
+ case VLC_PLAYLIST_PLAYBACK_REPEAT_ALL:
+ if (playlist->current == 0)
+ return playlist->items.size - 1;
+ return playlist->current + 1;
+ default:
+ vlc_assert_unreachable();
+ }
+}
+
+static inline bool
+PlaylistNormalOrderHasNext(vlc_playlist_t *playlist)
+{
+ if (playlist->repeat == VLC_PLAYLIST_PLAYBACK_REPEAT_ALL)
+ return true;
+
+ /* also works if current == -1 or playlist->items.size == 0 */
+ return playlist->current < (ssize_t) playlist->items.size - 1;
+}
+
+static inline size_t
+PlaylistNormalOrderGetNextIndex(vlc_playlist_t *playlist)
+{
+ switch (playlist->repeat)
+ {
+ case VLC_PLAYLIST_PLAYBACK_REPEAT_NONE:
+ case VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT:
+ if (playlist->current >= (ssize_t) playlist->items.size - 1)
+ return -1;
+ return playlist->current + 1;
+ case VLC_PLAYLIST_PLAYBACK_REPEAT_ALL:
+ if (playlist->items.size == 0)
+ return -1;
+ return (playlist->current + 1) % playlist->items.size;
+ default:
+ vlc_assert_unreachable();
+ }
+}
+
+static inline bool
+PlaylistRandomOrderHasPrev(vlc_playlist_t *playlist)
+{
+ VLC_UNUSED(playlist);
+ /* TODO */
+ return false;
+}
+
+static inline size_t
+PlaylistRandomOrderGetPrevIndex(vlc_playlist_t *playlist)
+{
+ VLC_UNUSED(playlist);
+ /* TODO */
+ return -0;
+}
+
+static inline bool
+PlaylistRandomOrderHasNext(vlc_playlist_t *playlist)
+{
+ VLC_UNUSED(playlist);
+ /* TODO */
+ return false;
+}
+
+static inline size_t
+PlaylistRandomOrderGetNextIndex(vlc_playlist_t *playlist)
+{
+ VLC_UNUSED(playlist);
+ /* TODO */
+ return 0;
+}
+
+static size_t
+PlaylistGetPrevIndex(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ switch (playlist->order)
+ {
+ case VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL:
+ return PlaylistNormalOrderGetPrevIndex(playlist);
+ case VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM:
+ return PlaylistRandomOrderGetPrevIndex(playlist);
+ default:
+ vlc_assert_unreachable();
+ }
+}
+
+static size_t
+PlaylistGetNextIndex(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ switch (playlist->order)
+ {
+ case VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL:
+ return PlaylistNormalOrderGetNextIndex(playlist);
+ case VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM:
+ return PlaylistRandomOrderGetNextIndex(playlist);
+ default:
+ vlc_assert_unreachable();
+ }
+}
+
+static bool
+PlaylistHasPrev(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ switch (playlist->order)
+ {
+ case VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL:
+ return PlaylistNormalOrderHasPrev(playlist);
+ case VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM:
+ return PlaylistRandomOrderHasPrev(playlist);
+ default:
+ vlc_assert_unreachable();
+ }
+}
+
+static bool
+PlaylistHasNext(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ switch (playlist->order)
+ {
+ case VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL:
+ return PlaylistNormalOrderHasNext(playlist);
+ case VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM:
+ return PlaylistRandomOrderHasNext(playlist);
+ default:
+ vlc_assert_unreachable();
+ }
+}
+
+static void
+PlaylistPlaybackOrderChanged(vlc_playlist_t *playlist)
+{
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ PlaylistNotify(playlist, on_playback_order_changed, playlist->order);
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
+static void
+PlaylistPlaybackRepeatChanged(vlc_playlist_t *playlist)
+{
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ PlaylistNotify(playlist, on_playback_repeat_changed, playlist->repeat);
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
+static void
+PlaylistItemsReset(vlc_playlist_t *playlist)
+{
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ playlist->current = -1;
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ PlaylistNotify(playlist, on_items_reset, playlist->items.data,
+ playlist->items.size);
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
+static void
+PlaylistItemsInserted(vlc_playlist_t *playlist, size_t index, size_t count)
+{
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ if (playlist->current >= (ssize_t) index)
+ playlist->current += count;
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ vlc_player_InvalidateNextMedia(playlist->player);
+
+ vlc_playlist_item_t **items = &playlist->items.data[index];
+ PlaylistNotify(playlist, on_items_added, index, items, count);
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
+static void
+PlaylistItemsMoved(vlc_playlist_t *playlist, size_t index, size_t count,
+ size_t target)
+{
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ if (playlist->current != -1) {
+ size_t current = (size_t) playlist->current;
+ if (current >= index && current < target + count)
+ {
+ if (current < index + count)
+ /* current item belongs the moved block */
+ playlist->current += target - index;
+ else
+ /* current item was shifted due to the moved block */
+ playlist->current -= count;
+ }
+ }
+
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ vlc_player_InvalidateNextMedia(playlist->player);
+
+ PlaylistNotify(playlist, on_items_moved, index, count, target);
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
+static void
+PlaylistItemsRemoved(vlc_playlist_t *playlist, size_t index, size_t count)
+{
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ bool invalidate_next_media = true;
+ if (playlist->current != -1) {
+ size_t current = (size_t) playlist->current;
+ if (current >= index && current < index + count) {
+ /* current item has been removed */
+ if (index + count < playlist->items.size) {
+ /* select the first item after the removed block */
+ playlist->current = index;
+ } else {
+ /* no more items */
+ playlist->current = -1;
+ }
+ /* change current playback */
+ PlaylistSetCurrentMedia(playlist, playlist->current);
+ /* we changed the current media, this already resets the next */
+ invalidate_next_media = false;
+ } else if (current >= index + count) {
+ playlist->current -= count;
+ }
+ }
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ PlaylistNotify(playlist, on_items_removed, index, count);
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+
+ if (invalidate_next_media)
+ vlc_player_InvalidateNextMedia(playlist->player);
+}
+
+static void
+PlaylistSetCurrentIndex(vlc_playlist_t *playlist, ssize_t index)
+{
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ playlist->current = index;
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
+static void
+ChildrenToPlaylistItems(playlist_item_vector_t *dest, input_item_node_t *node)
+{
+ for (int i = 0; i < node->i_children; ++i)
+ {
+ input_item_node_t *child = node->pp_children[i];
+ vlc_playlist_item_t *item = vlc_playlist_item_New(child->p_item);
+ if (item)
+ vlc_vector_push(dest, item);
+ ChildrenToPlaylistItems(dest, child);
+ }
+}
+
+static bool
+ExpandItem(vlc_playlist_t *playlist, size_t index, input_item_node_t *node)
+{
+ vlc_playlist_RemoveOne(playlist, index);
+
+ playlist_item_vector_t flatten = VLC_VECTOR_INITIALIZER;
+ ChildrenToPlaylistItems(&flatten, node);
+
+ if (vlc_vector_insert_all(&playlist->items, index, flatten.data,
+ flatten.size))
+ PlaylistItemsInserted(playlist, index, flatten.size);
+
+ vlc_vector_destroy(&flatten);
+ return true;
+}
+
+static void
+ExpandItemFromNode(vlc_playlist_t *playlist, input_item_node_t *subitems)
+{
+ input_item_t *media = subitems->p_item;
+ ssize_t index = vlc_playlist_IndexOfMedia(playlist, media);
+ if (index != -1)
+ /* replace the item by its flatten subtree */
+ ExpandItem(playlist, index, subitems);
+}
+
+/* called when the current media has changed _automatically_ (not on
+ * SetCurrentItem) */
+static void
+player_on_current_media_changed(vlc_player_t *player, input_item_t *new_media,
+ void *userdata)
+{
+ VLC_UNUSED(player);
+ vlc_playlist_t *playlist = userdata;
+
+ /* the playlist and the player share the lock */
+ PlaylistAssertLocked(playlist);
+
+ input_item_t *media = PlaylistGetMedia(playlist, playlist->current);
+ if (new_media == media)
+ /* nothing to do */
+ return;
+
+ ssize_t index = new_media ? vlc_playlist_IndexOfMedia(playlist, new_media)
+ : -1;
+
+ struct vlc_playlist_state state;
+ vlc_playlist_state_Save(playlist, &state);
+
+ playlist->current = index;
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
+static ssize_t
+PlaylistGetNextMediaIndex(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ if (playlist->repeat == VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT)
+ return playlist->current;
+ if (!PlaylistHasNext(playlist))
+ return -1;
+ return PlaylistGetNextIndex(playlist);
+}
+
+static input_item_t *
+player_get_next_media(vlc_player_t *player, void *userdata)
+{
+ VLC_UNUSED(player);
+ vlc_playlist_t *playlist = userdata;
+
+ /* the playlist and the player share the lock */
+ PlaylistAssertLocked(playlist);
+
+ ssize_t index = PlaylistGetNextMediaIndex(playlist);
+ if (index == -1)
+ return NULL;
+
+ input_item_t *media = playlist->items.data[index]->media;
+ input_item_Hold(media);
+ return media;
+}
+
+static void
+on_player_media_meta_changed(vlc_player_t *player, input_item_t *media,
+ void *userdata)
+{
+ VLC_UNUSED(player);
+ vlc_playlist_t *playlist = userdata;
+
+ /* the playlist and the player share the lock */
+ PlaylistAssertLocked(playlist);
+
+ NotifyMediaUpdated(playlist, media);
+}
+
+static void
+on_player_media_length_changed(vlc_player_t *player, vlc_tick_t new_length,
+ void *userdata)
+{
+ VLC_UNUSED(player);
+ VLC_UNUSED(new_length);
+ vlc_playlist_t *playlist = userdata;
+
+ /* the playlist and the player share the lock */
+ PlaylistAssertLocked(playlist);
+
+ input_item_t *media = vlc_player_GetCurrentMedia(player);
+ assert(media);
+
+ NotifyMediaUpdated(playlist, media);
+}
+
+static void
+on_player_subitems_detected(vlc_player_t *player, input_item_node_t *subitems,
+ void *userdata)
+{
+ VLC_UNUSED(player);
+ vlc_playlist_t *playlist = userdata;
+ ExpandItemFromNode(playlist, subitems);
+}
+
+static const struct vlc_player_media_provider player_media_provider = {
+ .get_next = player_get_next_media,
+};
+
+static const struct vlc_player_cbs player_callbacks = {
+ .on_current_media_changed = player_on_current_media_changed,
+ .on_media_meta_changed = on_player_media_meta_changed,
+ .on_length_changed = on_player_media_length_changed,
+ .on_subitems_changed = on_player_subitems_detected,
+};
+
+vlc_playlist_t *
+vlc_playlist_New(vlc_object_t *parent)
+{
+ vlc_playlist_t *playlist = malloc(sizeof(*playlist));
+ if (unlikely(!playlist))
+ return NULL;
+
+ playlist->player = vlc_player_New(parent, &player_media_provider, playlist);
+ if (unlikely(!playlist->player))
+ {
+ free(playlist);
+ return NULL;
+ }
+
+ vlc_player_Lock(playlist->player);
+ /* the playlist and the player share the lock */
+ PlaylistAssertLocked(playlist);
+ playlist->player_listener = vlc_player_AddListener(playlist->player,
+ &player_callbacks,
+ playlist);
+ vlc_player_Unlock(playlist->player);
+ if (unlikely(!playlist->player_listener))
+ {
+ vlc_player_Delete(playlist->player);
+ free(playlist);
+ return NULL;
+ }
+
+ vlc_vector_init(&playlist->items);
+ playlist->current = -1;
+ playlist->has_prev = false;
+ playlist->has_next = false;
+ vlc_list_init(&playlist->listeners);
+ playlist->repeat = VLC_PLAYLIST_PLAYBACK_REPEAT_NONE;
+ playlist->order = VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL;
+
+ return playlist;
+}
+
+void
+vlc_playlist_Delete(vlc_playlist_t *playlist)
+{
+ assert(vlc_list_is_empty(&playlist->listeners));
+
+ vlc_player_Lock(playlist->player);
+ vlc_player_RemoveListener(playlist->player, playlist->player_listener);
+ vlc_player_Unlock(playlist->player);
+
+ vlc_player_Delete(playlist->player);
+
+ PlaylistClear(playlist);
+
+ free(playlist);
+}
+
+void
+vlc_playlist_Lock(vlc_playlist_t *playlist)
+{
+ vlc_player_Lock(playlist->player);
+}
+
+void
+vlc_playlist_Unlock(vlc_playlist_t *playlist)
+{
+ vlc_player_Unlock(playlist->player);
+}
+
+vlc_playlist_listener_id *
+vlc_playlist_AddListener(vlc_playlist_t *playlist,
+ const struct vlc_playlist_callbacks *cbs,
+ void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+
+ vlc_playlist_listener_id *listener = malloc(sizeof(*listener));
+ if (unlikely(!listener))
+ return NULL;
+
+ listener->cbs = cbs;
+ listener->userdata = userdata;
+ vlc_list_append(&listener->node, &playlist->listeners);
+
+ return listener;
+}
+
+void
+vlc_playlist_RemoveListener(vlc_playlist_t *playlist,
+ vlc_playlist_listener_id *listener)
+{
+ PlaylistAssertLocked(playlist);
+
+ vlc_list_remove(&listener->node);
+ free(listener);
+}
+
+size_t
+vlc_playlist_Count(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ return playlist->items.size;
+}
+
+vlc_playlist_item_t *
+vlc_playlist_Get(vlc_playlist_t *playlist, size_t index)
+{
+ PlaylistAssertLocked(playlist);
+ return playlist->items.data[index];
+}
+
+void
+vlc_playlist_Clear(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+
+ PlaylistClear(playlist);
+ int ret = vlc_player_SetCurrentMedia(playlist->player, NULL);
+ VLC_UNUSED(ret); /* what could we do? */
+
+ PlaylistItemsReset(playlist);
+}
+
+static int
+PlaylistMediaToItems(input_item_t *const media[], size_t count,
+ vlc_playlist_item_t *items[])
+{
+ size_t i;
+ for (i = 0; i < count; ++i)
+ {
+ items[i] = vlc_playlist_item_New(media[i]);
+ if (unlikely(!items[i]))
+ break;
+ }
+ if (i < count)
+ {
+ /* allocation failure, release partial items */
+ while (i)
+ vlc_playlist_item_Release(items[--i]);
+ return VLC_ENOMEM;
+ }
+ return VLC_SUCCESS;
+}
+
+int
+vlc_playlist_Insert(vlc_playlist_t *playlist, size_t index,
+ input_item_t *const media[], size_t count)
+{
+ PlaylistAssertLocked(playlist);
+ assert(index <= playlist->items.size);
+
+ /* make space in the vector */
+ if (!vlc_vector_insert_hole(&playlist->items, index, count))
+ return VLC_ENOMEM;
+
+ /* create playlist items in place */
+ int ret = PlaylistMediaToItems(media, count, &playlist->items.data[index]);
+ if (ret != VLC_SUCCESS)
+ {
+ /* we were optimistic, it failed, restore the vector state */
+ vlc_vector_remove_slice(&playlist->items, index, count);
+ return ret;
+ }
+
+ PlaylistItemsInserted(playlist, index, count);
+
+ return VLC_SUCCESS;
+}
+
+void
+vlc_playlist_Move(vlc_playlist_t *playlist, size_t index, size_t count,
+ size_t target)
+{
+ PlaylistAssertLocked(playlist);
+ assert(index + count <= playlist->items.size);
+ assert(target + count <= playlist->items.size);
+
+ vlc_vector_move_slice(&playlist->items, index, count, target);
+
+ PlaylistItemsMoved(playlist, index, count, target);
+}
+
+void
+vlc_playlist_Remove(vlc_playlist_t *playlist, size_t index, size_t count)
+{
+ PlaylistAssertLocked(playlist);
+ assert(index < playlist->items.size);
+
+ for (size_t i = 0; i < count; ++i)
+ vlc_playlist_item_Release(playlist->items.data[index + i]);
+
+ vlc_vector_remove_slice(&playlist->items, index, count);
+
+ PlaylistItemsRemoved(playlist, index, count);
+}
+
+ssize_t
+vlc_playlist_IndexOf(vlc_playlist_t *playlist, const vlc_playlist_item_t *item)
+{
+ PlaylistAssertLocked(playlist);
+
+ ssize_t index;
+ vlc_vector_index_of(&playlist->items, item, &index);
+ return index;
+}
+
+ssize_t
+vlc_playlist_IndexOfMedia(vlc_playlist_t *playlist, const input_item_t *media)
+{
+ PlaylistAssertLocked(playlist);
+
+ playlist_item_vector_t *items = &playlist->items;
+ for (size_t i = 0; i < items->size; ++i)
+ if (items->data[i]->media == media)
+ return i;
+ return -1;
+}
+
+enum vlc_playlist_playback_repeat
+vlc_playlist_GetPlaybackRepeat(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ return playlist->repeat;
+}
+
+enum vlc_playlist_playback_order
+vlc_playlist_GetPlaybackOrder(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ return playlist->order;
+}
+
+void
+vlc_playlist_SetPlaybackRepeat(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_repeat repeat)
+{
+ PlaylistAssertLocked(playlist);
+
+ if (playlist->repeat == repeat)
+ return;
+
+ playlist->repeat = repeat;
+ PlaylistPlaybackRepeatChanged(playlist);
+}
+
+void
+vlc_playlist_SetPlaybackOrder(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_order order)
+{
+ PlaylistAssertLocked(playlist);
+
+ if (playlist->order == order)
+ return;
+
+ playlist->order = order;
+ PlaylistPlaybackOrderChanged(playlist);
+}
+
+ssize_t
+vlc_playlist_GetCurrentIndex(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ return playlist->current;
+}
+
+bool
+vlc_playlist_HasPrev(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ return playlist->has_prev;
+}
+
+bool
+vlc_playlist_HasNext(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+ return playlist->has_next;
+}
+
+int
+vlc_playlist_Prev(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+
+ if (!PlaylistHasPrev(playlist))
+ return VLC_EGENERIC;
+
+ ssize_t index = PlaylistGetPrevIndex(playlist);
+ assert(index != -1);
+
+ int ret = PlaylistSetCurrentMedia(playlist, index);
+ if (ret != VLC_SUCCESS)
+ return ret;
+
+ PlaylistSetCurrentIndex(playlist, index);
+ return VLC_SUCCESS;
+}
+
+int
+vlc_playlist_Next(vlc_playlist_t *playlist)
+{
+ PlaylistAssertLocked(playlist);
+
+ if (!PlaylistHasNext(playlist))
+ return VLC_EGENERIC;
+
+ ssize_t index = PlaylistGetNextIndex(playlist);
+ assert(index != -1);
+
+ int ret = PlaylistSetCurrentMedia(playlist, index);
+ if (ret != VLC_SUCCESS)
+ return ret;
+
+ PlaylistSetCurrentIndex(playlist, index);
+ return VLC_SUCCESS;
+}
+
+int
+vlc_playlist_GoTo(vlc_playlist_t *playlist, ssize_t index)
+{
+ PlaylistAssertLocked(playlist);
+ assert(index == -1 || (size_t) index < playlist->items.size);
+
+ int ret = PlaylistSetCurrentMedia(playlist, index);
+ if (ret != VLC_SUCCESS)
+ return ret;
+
+ PlaylistSetCurrentIndex(playlist, index);
+ return VLC_SUCCESS;
+}
+
+vlc_player_t *
+vlc_playlist_GetPlayer(vlc_playlist_t *playlist)
+{
+ return playlist->player;
+}
+
+int
+vlc_playlist_Start(vlc_playlist_t *playlist)
+{
+ return vlc_player_Start(playlist->player);
+}
+
+void
+vlc_playlist_Stop(vlc_playlist_t *playlist)
+{
+ vlc_player_Stop(playlist->player);
+}
+
+void
+vlc_playlist_Pause(vlc_playlist_t *playlist)
+{
+ vlc_player_Pause(playlist->player);
+}
+
+void
+vlc_playlist_Resume(vlc_playlist_t *playlist)
+{
+ vlc_player_Resume(playlist->player);
+}
+
+static void
+on_subtree_added(input_item_t *media, input_item_node_t *subtree,
+ void *userdata)
+{
+ VLC_UNUSED(media); /* retrieved by subtree->p_item */
+ vlc_playlist_t *playlist = userdata;
+
+ vlc_playlist_Lock(playlist);
+ ExpandItemFromNode(playlist, subtree);
+ vlc_playlist_Unlock(playlist);
+}
+
+static const input_preparser_callbacks_t input_preparser_callbacks = {
+ .on_subtree_added = on_subtree_added,
+};
+
+void
+vlc_playlist_Preparse(vlc_playlist_t *playlist, libvlc_int_t *libvlc,
+ input_item_t *input)
+{
+#ifdef TEST_PLAYLIST
+ VLC_UNUSED(playlist);
+ VLC_UNUSED(libvlc);
+ VLC_UNUSED(input);
+ VLC_UNUSED(input_preparser_callbacks);
+#else
+ /* vlc_MetadataRequest is not exported */
+ vlc_MetadataRequest(libvlc, input, META_REQUEST_OPTION_NONE,
+ &input_preparser_callbacks, playlist, -1, NULL);
+#endif
+}
+
+
+
+
+
+
+/******************************************************************************
+ * TESTS *
+ ******************************************************************************/
+
+#ifdef TEST_PLAYLIST
+/* the playlist lock is the one of the player */
+# define vlc_playlist_Lock(p) VLC_UNUSED(p);
+# define vlc_playlist_Unlock(p) VLC_UNUSED(p);
+
+static input_item_t *
+CreateDummyMedia(int num)
+{
+ char *url;
+ char *name;
+
+ int res = asprintf(&url, "vlc://item-%d", num);
+ if (res == -1)
+ return NULL;
+
+ res = asprintf(&name, "item-%d", num);
+ if (res == -1)
+ return NULL;
+
+ input_item_t *media = input_item_New(url, name);
+ free(url);
+ free(name);
+ return media;
+}
+
+static void
+CreateDummyMediaArray(input_item_t *out[], size_t count)
+{
+ for (size_t i = 0; i < count; ++i)
+ {
+ out[i] = CreateDummyMedia(i);
+ assert(out[i]);
+ }
+}
+
+static void
+DestroyMediaArray(input_item_t *const array[], size_t count)
+{
+ for (size_t i = 0; i < count; ++i)
+ input_item_Release(array[i]);
+}
+
+#define EXPECT_AT(index, id) \
+ assert(vlc_playlist_Get(playlist, index)->media == media[id])
+
+static void
+test_append(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* append one by one */
+ for (int i = 0; i < 5; ++i)
+ {
+ int ret = vlc_playlist_AppendOne(playlist, media[i]);
+ assert(ret == VLC_SUCCESS);
+ }
+
+ /* append several at once */
+ int ret = vlc_playlist_Append(playlist, &media[5], 5);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 10);
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 2);
+ EXPECT_AT(3, 3);
+ EXPECT_AT(4, 4);
+ EXPECT_AT(5, 5);
+ EXPECT_AT(6, 6);
+ EXPECT_AT(7, 7);
+ EXPECT_AT(8, 8);
+ EXPECT_AT(9, 9);
+
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_insert(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[15];
+ CreateDummyMediaArray(media, 15);
+
+ /* initial playlist with 5 items */
+ int ret = vlc_playlist_Append(playlist, media, 5);
+ assert(ret == VLC_SUCCESS);
+
+ /* insert one by one */
+ for (int i = 0; i < 5; ++i)
+ {
+ ret = vlc_playlist_InsertOne(playlist, 2, media[i + 5]);
+ assert(ret == VLC_SUCCESS);
+ }
+
+ /* insert several at once */
+ ret = vlc_playlist_Insert(playlist, 6, &media[10], 5);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 15);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+
+ EXPECT_AT(2, 9);
+ EXPECT_AT(3, 8);
+ EXPECT_AT(4, 7);
+ EXPECT_AT(5, 6);
+
+ EXPECT_AT(6, 10);
+ EXPECT_AT(7, 11);
+ EXPECT_AT(8, 12);
+ EXPECT_AT(9, 13);
+ EXPECT_AT(10, 14);
+
+ EXPECT_AT(11, 5);
+ EXPECT_AT(12, 2);
+ EXPECT_AT(13, 3);
+ EXPECT_AT(14, 4);
+
+ DestroyMediaArray(media, 15);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_move(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ /* move slice {3, 4, 5, 6} so that its new position is 5 */
+ vlc_playlist_Move(playlist, 3, 4, 5);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 2);
+ EXPECT_AT(3, 7);
+ EXPECT_AT(4, 8);
+ EXPECT_AT(5, 3);
+ EXPECT_AT(6, 4);
+ EXPECT_AT(7, 5);
+ EXPECT_AT(8, 6);
+ EXPECT_AT(9, 9);
+
+ /* move it back to its original position */
+ vlc_playlist_Move(playlist, 5, 4, 3);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 2);
+ EXPECT_AT(3, 3);
+ EXPECT_AT(4, 4);
+ EXPECT_AT(5, 5);
+ EXPECT_AT(6, 6);
+ EXPECT_AT(7, 7);
+ EXPECT_AT(8, 8);
+ EXPECT_AT(9, 9);
+
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_remove(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ /* remove one by one */
+ for (int i = 0; i < 3; ++i)
+ vlc_playlist_RemoveOne(playlist, 2);
+
+ /* remove several at once */
+ vlc_playlist_Remove(playlist, 3, 2);
+
+ assert(vlc_playlist_Count(playlist) == 5);
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 5);
+ EXPECT_AT(3, 8);
+ EXPECT_AT(4, 9);
+
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_clear(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 10);
+ vlc_playlist_Clear(playlist);
+ assert(vlc_playlist_Count(playlist) == 0);
+
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_expand_item(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[16];
+ CreateDummyMediaArray(media, 16);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ /* create a subtree for item 8 with 4 children */
+ input_item_t *item_to_expand = playlist->items.data[8]->media;
+ input_item_node_t *root = input_item_node_Create(item_to_expand);
+ for (int i = 0; i < 4; ++i)
+ {
+ input_item_node_t *node = input_item_node_AppendItem(root,
+ media[i + 10]);
+ assert(node);
+ }
+
+ /* on the 3rd children, add 2 grand-children */
+ input_item_node_t *parent = root->pp_children[2];
+ for (int i = 0; i < 2; ++i)
+ {
+ input_item_node_t *node = input_item_node_AppendItem(parent,
+ media[i + 14]);
+ assert(node);
+ }
+
+ bool ok = ExpandItem(playlist, 8, root);
+ assert(ok);
+ assert(vlc_playlist_Count(playlist) == 15);
+ EXPECT_AT(7, 7);
+
+ EXPECT_AT(8, 10);
+ EXPECT_AT(9, 11);
+ EXPECT_AT(10, 12);
+
+ EXPECT_AT(11, 14);
+ EXPECT_AT(12, 15);
+
+ EXPECT_AT(13, 13);
+
+ EXPECT_AT(14, 9);
+
+ input_item_node_Delete(root);
+ DestroyMediaArray(media, 16);
+ vlc_playlist_Delete(playlist);
+}
+
+struct playlist_state
+{
+ size_t playlist_size;
+ ssize_t current;
+ bool has_prev;
+ bool has_next;
+};
+
+static void
+playlist_state_init(struct playlist_state *state, vlc_playlist_t *playlist)
+{
+ state->playlist_size = vlc_playlist_Count(playlist);
+ state->current = vlc_playlist_GetCurrentIndex(playlist);
+ state->has_prev = vlc_playlist_HasPrev(playlist);
+ state->has_next = vlc_playlist_HasNext(playlist);
+}
+
+struct items_reset_report
+{
+ size_t count;
+ struct playlist_state state;
+};
+
+struct items_added_report
+{
+ size_t index;
+ size_t count;
+ struct playlist_state state;
+};
+
+struct items_moved_report
+{
+ size_t index;
+ size_t count;
+ size_t target;
+ struct playlist_state state;
+};
+
+struct items_removed_report
+{
+ size_t index;
+ size_t count;
+ struct playlist_state state;
+};
+
+struct playback_repeat_changed_report
+{
+ enum vlc_playlist_playback_repeat repeat;
+};
+
+struct playback_order_changed_report
+{
+ enum vlc_playlist_playback_order order;
+};
+
+struct current_index_changed_report
+{
+ ssize_t current;
+};
+
+struct has_prev_changed_report
+{
+ bool has_prev;
+};
+
+struct has_next_changed_report
+{
+ bool has_next;
+};
+
+struct callback_ctx
+{
+ struct VLC_VECTOR(struct items_reset_report) vec_items_reset;
+ struct VLC_VECTOR(struct items_added_report) vec_items_added;
+ struct VLC_VECTOR(struct items_moved_report) vec_items_moved;
+ struct VLC_VECTOR(struct items_removed_report) vec_items_removed;
+ struct VLC_VECTOR(struct playback_order_changed_report)
+ vec_playback_order_changed;
+ struct VLC_VECTOR(struct playback_repeat_changed_report)
+ vec_playback_repeat_changed;
+ struct VLC_VECTOR(struct current_index_changed_report)
+ vec_current_index_changed;
+ struct VLC_VECTOR(struct has_prev_changed_report) vec_has_prev_changed;
+ struct VLC_VECTOR(struct has_next_changed_report) vec_has_next_changed;
+};
+
+#define CALLBACK_CTX_INITIALIZER \
+{ \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+ VLC_VECTOR_INITIALIZER, \
+}
+
+static inline void
+callback_ctx_reset(struct callback_ctx *ctx)
+{
+ vlc_vector_clear(&ctx->vec_items_reset);
+ vlc_vector_clear(&ctx->vec_items_added);
+ vlc_vector_clear(&ctx->vec_items_moved);
+ vlc_vector_clear(&ctx->vec_items_removed);
+ vlc_vector_clear(&ctx->vec_playback_repeat_changed);
+ vlc_vector_clear(&ctx->vec_playback_order_changed);
+ vlc_vector_clear(&ctx->vec_current_index_changed);
+ vlc_vector_clear(&ctx->vec_has_prev_changed);
+ vlc_vector_clear(&ctx->vec_has_next_changed);
+};
+
+static inline void
+callback_ctx_destroy(struct callback_ctx *ctx)
+{
+ vlc_vector_destroy(&ctx->vec_items_reset);
+ vlc_vector_destroy(&ctx->vec_items_added);
+ vlc_vector_destroy(&ctx->vec_items_moved);
+ vlc_vector_destroy(&ctx->vec_items_removed);
+ vlc_vector_destroy(&ctx->vec_playback_repeat_changed);
+ vlc_vector_destroy(&ctx->vec_playback_order_changed);
+ vlc_vector_destroy(&ctx->vec_current_index_changed);
+ vlc_vector_destroy(&ctx->vec_has_prev_changed);
+ vlc_vector_destroy(&ctx->vec_has_next_changed);
+};
+
+static void
+callback_on_items_reset(vlc_playlist_t *playlist,
+ vlc_playlist_item_t *const items[], size_t count,
+ void *userdata)
+{
+ VLC_UNUSED(items);
+ PlaylistAssertLocked(playlist);
+
+ struct callback_ctx *ctx = userdata;
+
+ struct items_reset_report report;
+ report.count = count;
+ playlist_state_init(&report.state, playlist);
+ vlc_vector_push(&ctx->vec_items_reset, report);
+}
+
+static void
+callback_on_items_added(vlc_playlist_t *playlist, size_t index,
+ vlc_playlist_item_t *const items[], size_t count,
+ void *userdata)
+{
+ VLC_UNUSED(items);
+ PlaylistAssertLocked(playlist);
+
+ struct callback_ctx *ctx = userdata;
+
+ struct items_added_report report;
+ report.index = index;
+ report.count = count;
+ playlist_state_init(&report.state, playlist);
+ vlc_vector_push(&ctx->vec_items_added, report);
+}
+
+static void
+callback_on_items_moved(vlc_playlist_t *playlist, size_t index, size_t count,
+ size_t target, void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+
+ struct callback_ctx *ctx = userdata;
+
+ struct items_moved_report report;
+ report.index = index;
+ report.count = count;
+ report.target = target;
+ playlist_state_init(&report.state, playlist);
+ vlc_vector_push(&ctx->vec_items_moved, report);
+}
+
+static void
+callback_on_items_removed(vlc_playlist_t *playlist, size_t index, size_t count,
+ void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+
+ struct callback_ctx *ctx = userdata;
+
+ struct items_removed_report report;
+ report.index = index;
+ report.count = count;
+ playlist_state_init(&report.state, playlist);
+ vlc_vector_push(&ctx->vec_items_removed, report);
+}
+
+static void
+callback_on_playback_repeat_changed(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_repeat repeat,
+ void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+ struct callback_ctx *ctx = userdata;
+
+ struct playback_repeat_changed_report report;
+ report.repeat = repeat;
+ vlc_vector_push(&ctx->vec_playback_repeat_changed, report);
+}
+
+static void
+callback_on_playback_order_changed(vlc_playlist_t *playlist,
+ enum vlc_playlist_playback_order order,
+ void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+ struct callback_ctx *ctx = userdata;
+
+ struct playback_order_changed_report report;
+ report.order = order;
+ vlc_vector_push(&ctx->vec_playback_order_changed, report);
+}
+
+static void
+callback_on_current_index_changed(vlc_playlist_t *playlist, ssize_t index,
+ void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+ struct callback_ctx *ctx = userdata;
+
+ struct current_index_changed_report report;
+ report.current = index;
+ vlc_vector_push(&ctx->vec_current_index_changed, report);
+}
+
+static void
+callback_on_has_prev_changed(vlc_playlist_t *playlist, bool has_prev,
+ void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+ struct callback_ctx *ctx = userdata;
+
+ struct has_prev_changed_report report;
+ report.has_prev = has_prev;
+ vlc_vector_push(&ctx->vec_has_prev_changed, report);
+}
+
+static void
+callback_on_has_next_changed(vlc_playlist_t *playlist, bool has_next,
+ void *userdata)
+{
+ PlaylistAssertLocked(playlist);
+ struct callback_ctx *ctx = userdata;
+
+ struct has_next_changed_report report;
+ report.has_next = has_next;
+ vlc_vector_push(&ctx->vec_has_next_changed, report);
+}
+
+static void
+test_items_added_callbacks(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_added = callback_on_items_added,
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ int ret = vlc_playlist_AppendOne(playlist, media[0]);
+ assert(ret == VLC_SUCCESS);
+
+ /* the callbacks must be called with *all* values up to date */
+ assert(ctx.vec_items_added.size == 1);
+ assert(ctx.vec_items_added.data[0].index == 0);
+ assert(ctx.vec_items_added.data[0].count == 1);
+ assert(ctx.vec_items_added.data[0].state.playlist_size == 1);
+ assert(ctx.vec_items_added.data[0].state.current == -1);
+ assert(!ctx.vec_items_added.data[0].state.has_prev);
+ assert(ctx.vec_items_added.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 0);
+
+ assert(ctx.vec_has_prev_changed.size == 0);
+
+ assert(ctx.vec_has_next_changed.size == 1);
+ assert(ctx.vec_has_next_changed.data[0].has_next);
+
+ callback_ctx_reset(&ctx);
+
+ /* set the only item as current */
+ playlist->current = 0;
+ playlist->has_prev = false;
+ playlist->has_next = false;
+
+ /* insert before the current item */
+ ret = vlc_playlist_Insert(playlist, 0, &media[1], 4);
+ assert(ret == VLC_SUCCESS);
+
+ assert(ctx.vec_items_added.size == 1);
+ assert(ctx.vec_items_added.data[0].index == 0);
+ assert(ctx.vec_items_added.data[0].count == 4);
+ assert(ctx.vec_items_added.data[0].state.playlist_size == 5);
+ assert(ctx.vec_items_added.data[0].state.current == 4); /* shifted */
+ assert(ctx.vec_items_added.data[0].state.has_prev);
+ assert(!ctx.vec_items_added.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 4);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_reset(&ctx);
+
+ /* append (after the current item) */
+ ret = vlc_playlist_Append(playlist, &media[5], 5);
+ assert(ret == VLC_SUCCESS);
+
+ assert(ctx.vec_items_added.size == 1);
+ assert(ctx.vec_items_added.data[0].index == 5);
+ assert(ctx.vec_items_added.data[0].count == 5);
+ assert(ctx.vec_items_added.data[0].state.playlist_size == 10);
+ assert(ctx.vec_items_added.data[0].state.current == 4);
+ assert(ctx.vec_items_added.data[0].state.has_prev);
+ assert(ctx.vec_items_added.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 0);
+
+ assert(ctx.vec_has_prev_changed.size == 0);
+
+ assert(ctx.vec_has_next_changed.size == 1);
+ assert(ctx.vec_has_next_changed.data[0].has_next);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_items_moved_callbacks(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_moved = callback_on_items_moved,
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_Move(playlist, 2, 3, 5);
+
+ assert(ctx.vec_items_moved.size == 1);
+ assert(ctx.vec_items_moved.data[0].index == 2);
+ assert(ctx.vec_items_moved.data[0].count == 3);
+ assert(ctx.vec_items_moved.data[0].target == 5);
+ assert(ctx.vec_items_moved.data[0].state.playlist_size == 10);
+ assert(ctx.vec_items_moved.data[0].state.current == -1);
+ assert(!ctx.vec_items_moved.data[0].state.has_prev);
+ assert(ctx.vec_items_moved.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 0);
+ assert(ctx.vec_has_prev_changed.size == 0);
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ playlist->current = 3;
+ playlist->has_prev = true;
+ playlist->has_next = true;
+
+ callback_ctx_reset(&ctx);
+
+ /* the current index belongs to the moved slice */
+ vlc_playlist_Move(playlist, 1, 3, 5);
+
+ assert(ctx.vec_items_moved.size == 1);
+ assert(ctx.vec_items_moved.data[0].index == 1);
+ assert(ctx.vec_items_moved.data[0].count == 3);
+ assert(ctx.vec_items_moved.data[0].target == 5);
+ assert(ctx.vec_items_moved.data[0].state.playlist_size == 10);
+ assert(ctx.vec_items_moved.data[0].state.current == 7);
+ assert(ctx.vec_items_moved.data[0].state.has_prev);
+ assert(ctx.vec_items_moved.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 7);
+
+ assert(ctx.vec_has_prev_changed.size == 0);
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_reset(&ctx);
+
+ /* as a result of this move, the current item (7) will be at index 0 */
+ vlc_playlist_Move(playlist, 0, 7, 1);
+
+ assert(ctx.vec_items_moved.size == 1);
+ assert(ctx.vec_items_moved.data[0].index == 0);
+ assert(ctx.vec_items_moved.data[0].count == 7);
+ assert(ctx.vec_items_moved.data[0].target == 1);
+ assert(ctx.vec_items_moved.data[0].state.playlist_size == 10);
+ assert(ctx.vec_items_moved.data[0].state.current == 0);
+ assert(!ctx.vec_items_moved.data[0].state.has_prev);
+ assert(ctx.vec_items_moved.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 0);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(!ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_items_removed_callbacks(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_removed = callback_on_items_removed,
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_RemoveOne(playlist, 4);
+
+ assert(ctx.vec_items_removed.size == 1);
+ assert(ctx.vec_items_removed.data[0].index == 4);
+ assert(ctx.vec_items_removed.data[0].count == 1);
+ assert(ctx.vec_items_removed.data[0].state.playlist_size == 9);
+ assert(ctx.vec_items_removed.data[0].state.current == -1);
+ assert(!ctx.vec_items_removed.data[0].state.has_prev);
+ assert(ctx.vec_items_removed.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 0);
+ assert(ctx.vec_has_prev_changed.size == 0);
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ playlist->current = 7;
+ playlist->has_prev = true;
+ playlist->has_next = true;
+
+ callback_ctx_reset(&ctx);
+
+ /* remove items before the current */
+ vlc_playlist_Remove(playlist, 2, 4);
+
+ assert(ctx.vec_items_removed.size == 1);
+ assert(ctx.vec_items_removed.data[0].index == 2);
+ assert(ctx.vec_items_removed.data[0].count == 4);
+ assert(ctx.vec_items_removed.data[0].state.playlist_size == 5);
+ assert(ctx.vec_items_removed.data[0].state.current == 3); /* shifted */
+ assert(ctx.vec_items_removed.data[0].state.has_prev);
+ assert(ctx.vec_items_removed.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 3);
+
+ assert(ctx.vec_has_prev_changed.size == 0);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_reset(&ctx);
+
+ /* remove the remaining items (without Clear) */
+ vlc_playlist_Remove(playlist, 0, 5);
+
+ assert(ctx.vec_items_removed.size == 1);
+ assert(ctx.vec_items_removed.data[0].index == 0);
+ assert(ctx.vec_items_removed.data[0].count == 5);
+ assert(ctx.vec_items_removed.data[0].state.playlist_size == 0);
+ assert(ctx.vec_items_removed.data[0].state.current == -1);
+ assert(!ctx.vec_items_removed.data[0].state.has_prev);
+ assert(!ctx.vec_items_removed.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == -1);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(!ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 1);
+ assert(!ctx.vec_has_next_changed.data[0].has_next);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_items_reset_callbacks(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_reset = callback_on_items_reset,
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ callback_ctx_reset(&ctx);
+
+ playlist->current = 9; /* last item */
+ playlist->has_prev = true;
+ playlist->has_next = false;
+
+ vlc_playlist_Clear(playlist);
+
+ assert(ctx.vec_items_reset.size == 1);
+ assert(ctx.vec_items_reset.data[0].count == 0);
+ assert(ctx.vec_items_reset.data[0].state.playlist_size == 0);
+ assert(ctx.vec_items_reset.data[0].state.current == -1);
+ assert(!ctx.vec_items_reset.data[0].state.has_prev);
+ assert(!ctx.vec_items_reset.data[0].state.has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == -1);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(!ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_playback_repeat_changed_callbacks(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ playlist->repeat = VLC_PLAYLIST_PLAYBACK_REPEAT_NONE;
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_playback_repeat_changed = callback_on_playback_repeat_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_SetPlaybackRepeat(playlist, VLC_PLAYLIST_PLAYBACK_REPEAT_ALL);
+
+ assert(vlc_playlist_GetPlaybackRepeat(playlist) ==
+ VLC_PLAYLIST_PLAYBACK_REPEAT_ALL);
+
+ assert(ctx.vec_playback_repeat_changed.size == 1);
+ assert(ctx.vec_playback_repeat_changed.data[0].repeat ==
+ VLC_PLAYLIST_PLAYBACK_REPEAT_ALL);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_playback_order_changed_callbacks(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ playlist->order = VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL;
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_playback_order_changed = callback_on_playback_order_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_SetPlaybackOrder(playlist, VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM);
+
+ assert(vlc_playlist_GetPlaybackOrder(playlist) ==
+ VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM);
+
+ assert(ctx.vec_playback_order_changed.size == 1);
+ assert(ctx.vec_playback_order_changed.data[0].order ==
+ VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_index_of(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 9 items (1 is not added) */
+ int ret = vlc_playlist_Append(playlist, media, 9);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_IndexOfMedia(playlist, media[4]) == 4);
+ /* only items 0 to 8 were added */
+ assert(vlc_playlist_IndexOfMedia(playlist, media[9]) == -1);
+
+ vlc_playlist_item_t *item = vlc_playlist_Get(playlist, 4);
+ assert(vlc_playlist_IndexOf(playlist, item) == 4);
+
+ vlc_playlist_item_Hold(item);
+ vlc_playlist_RemoveOne(playlist, 4);
+ assert(vlc_playlist_IndexOf(playlist, item) == -1);
+ vlc_playlist_item_Release(item);
+
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_prev(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[4];
+ CreateDummyMediaArray(media, 4);
+
+ /* initial playlist with 3 items */
+ int ret = vlc_playlist_Append(playlist, media, 3);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ playlist->current = 2; /* last item */
+ playlist->has_prev = true;
+ playlist->has_next = false;
+
+ /* go to the previous item (at index 1) */
+ assert(vlc_playlist_HasPrev(playlist));
+ ret = vlc_playlist_Prev(playlist);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 1);
+ assert(playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 1);
+
+ assert(ctx.vec_has_prev_changed.size == 0);
+
+ assert(ctx.vec_has_next_changed.size == 1);
+ assert(ctx.vec_has_next_changed.data[0].has_next);
+
+ callback_ctx_reset(&ctx);
+
+ /* go to the previous item (at index 0) */
+ assert(vlc_playlist_HasPrev(playlist));
+ ret = vlc_playlist_Prev(playlist);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 0);
+ assert(!playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 0);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(!ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ /* no more previous item */
+ assert(!vlc_playlist_HasPrev(playlist));
+
+ /* returns an error, but does not crash */
+ assert(vlc_playlist_Prev(playlist) == VLC_EGENERIC);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 4);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_next(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[3];
+ CreateDummyMediaArray(media, 3);
+
+ /* initial playlist with 3 items */
+ int ret = vlc_playlist_Append(playlist, media, 3);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ playlist->current = 0; /* first item */
+ playlist->has_prev = false;
+ playlist->has_next = true;
+
+ /* go to the next item (at index 1) */
+ assert(vlc_playlist_HasNext(playlist));
+ ret = vlc_playlist_Next(playlist);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 1);
+ assert(playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 1);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_reset(&ctx);
+
+ /* go to the next item (at index 2) */
+ assert(vlc_playlist_HasNext(playlist));
+ ret = vlc_playlist_Next(playlist);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 2);
+ assert(playlist->has_prev);
+ assert(!playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 2);
+
+ assert(ctx.vec_has_prev_changed.size == 0);
+
+ assert(ctx.vec_has_next_changed.size == 1);
+ assert(!ctx.vec_has_next_changed.data[0].has_next);
+
+ /* no more next item */
+ assert(!vlc_playlist_HasNext(playlist));
+
+ /* returns an error, but does not crash */
+ assert(vlc_playlist_Next(playlist) == VLC_EGENERIC);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 3);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_goto(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ /* go to an item in the middle */
+ ret = vlc_playlist_GoTo(playlist, 4);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 4);
+ assert(playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 4);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_reset(&ctx);
+
+ /* go to the same item */
+ ret = vlc_playlist_GoTo(playlist, 4);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 4);
+ assert(playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 0);
+ assert(ctx.vec_has_prev_changed.size == 0);
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_reset(&ctx);
+
+ /* go to the first item */
+ ret = vlc_playlist_GoTo(playlist, 0);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 0);
+ assert(!playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 0);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(!ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_reset(&ctx);
+
+ /* go to the last item */
+ ret = vlc_playlist_GoTo(playlist, 9);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 9);
+ assert(playlist->has_prev);
+ assert(!playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 9);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 1);
+ assert(!ctx.vec_has_next_changed.data[0].has_next);
+
+ callback_ctx_reset(&ctx);
+
+ /* deselect current */
+ ret = vlc_playlist_GoTo(playlist, -1);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == -1);
+ assert(!playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == -1);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(!ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 1);
+ assert(ctx.vec_has_next_changed.data[0].has_next);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+#undef EXPECT_AT
+
+int main(void)
+{
+ test_append();
+ test_insert();
+ test_move();
+ test_remove();
+ test_clear();
+ test_expand_item();
+ test_items_added_callbacks();
+ test_items_moved_callbacks();
+ test_items_removed_callbacks();
+ test_items_reset_callbacks();
+ test_playback_repeat_changed_callbacks();
+ test_playback_order_changed_callbacks();
+ test_index_of();
+ test_prev();
+ test_next();
+ test_goto();
+ return 0;
+}
+#endif /* TEST_PLAYLIST */
--
2.19.1
More information about the vlc-devel
mailing list