[vlc-devel] [PATCH 07/12] core: playlist: implement sorting
Romain Vimont
rom1v at videolabs.io
Thu Oct 11 23:14:45 CEST 2018
Expose a function to sort the items in the playlist by a list of
criteria. A criterion is composed of a key (title, duration, etc.) and
an order (ascending or descending).
---
include/vlc_fixups.h | 4 +
include/vlc_playlist_new.h | 39 +++++
src/libvlccore.sym | 1 +
src/playlist/playlist.c | 304 +++++++++++++++++++++++++++++++++++++
4 files changed, 348 insertions(+)
diff --git a/include/vlc_fixups.h b/include/vlc_fixups.h
index 037f67da31..6b15381d38 100644
--- a/include/vlc_fixups.h
+++ b/include/vlc_fixups.h
@@ -112,6 +112,10 @@ typedef struct
# include <dirent.h>
#endif
+#ifdef _WIN32
+# define qsort_r qsort_s
+#endif
+
#ifdef __cplusplus
# define VLC_NOTHROW throw ()
extern "C" {
diff --git a/include/vlc_playlist_new.h b/include/vlc_playlist_new.h
index 03956a48b4..ea5c908c4c 100644
--- a/include/vlc_playlist_new.h
+++ b/include/vlc_playlist_new.h
@@ -126,6 +126,33 @@ enum vlc_playlist_playback_order
VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM,
};
+enum vlc_playlist_sort_key
+{
+ VLC_PLAYLIST_SORT_KEY_TITLE,
+ VLC_PLAYLIST_SORT_KEY_DURATION,
+ VLC_PLAYLIST_SORT_KEY_ARTIST,
+ VLC_PLAYLIST_SORT_KEY_ALBUM,
+ VLC_PLAYLIST_SORT_KEY_ALBUM_ARTIST,
+ VLC_PLAYLIST_SORT_KEY_GENRE,
+ VLC_PLAYLIST_SORT_KEY_DATE,
+ VLC_PLAYLIST_SORT_KEY_TRACK_NUMBER,
+ VLC_PLAYLIST_SORT_KEY_DISC_NUMBER,
+ VLC_PLAYLIST_SORT_KEY_URL,
+ VLC_PLAYLIST_SORT_KEY_RATING,
+};
+
+enum vlc_playlist_sort_order
+{
+ VLC_PLAYLIST_SORT_ORDER_ASCENDING,
+ VLC_PLAYLIST_SORT_ORDER_DESCENDING,
+};
+
+struct vlc_playlist_sort_criterion
+{
+ enum vlc_playlist_sort_key key;
+ enum vlc_playlist_sort_order order;
+};
+
/**
* Playlist callbacks.
*
@@ -579,6 +606,18 @@ vlc_playlist_RequestRemove(vlc_playlist_t *playlist,
VLC_API void
vlc_playlist_Shuffle(vlc_playlist_t *playlist);
+/**
+ * Sort the playlist by a list of criteria.
+ *
+ * \param playlist the playlist, locked
+ * \param criteria the sort criteria (in order)
+ * \param count the number of criteria
+ */
+VLC_API void
+vlc_playlist_Sort(vlc_playlist_t *playlist,
+ const struct vlc_playlist_sort_criterion criteria[],
+ size_t count);
+
/**
* Return the index of a given item.
*
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 77a87e7f05..45119d83d6 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -877,6 +877,7 @@ vlc_playlist_RequestInsert
vlc_playlist_RequestMove
vlc_playlist_RequestRemove
vlc_playlist_Shuffle
+vlc_playlist_Sort
vlc_playlist_IndexOf
vlc_playlist_IndexOfMedia
vlc_playlist_GetPlaybackRepeat
diff --git a/src/playlist/playlist.c b/src/playlist/playlist.c
index 118e3f8721..a4c08b028e 100644
--- a/src/playlist/playlist.c
+++ b/src/playlist/playlist.c
@@ -939,6 +939,202 @@ vlc_playlist_Shuffle(vlc_playlist_t *playlist)
vlc_playlist_state_NotifyChanges(playlist, &state);
}
+struct sort_request
+{
+ const struct vlc_playlist_sort_criterion *criteria;
+ size_t count;
+};
+
+static inline int
+CompareStrings(const char *a, const char *b)
+{
+ if (a && b)
+ return strcasecmp(a, b);
+ if (!a && !b)
+ return 0;
+ return a ? 1 : -1;
+}
+
+static inline int
+CompareIntegers(const char *a, const char *b)
+{
+ if (a && b)
+ {
+ long long aa = atoll(a);
+ long long bb = atoll(b);
+
+ if (aa < bb)
+ return -1;
+ if (aa > bb)
+ return 1;
+ return 0;
+ }
+
+ if (!a && !b)
+ return 0;
+ return a ? 1 : -1;
+}
+
+static inline const char *
+MediaGetTitleOrName(input_item_t *media)
+{
+ const char *ret = media->psz_name;
+ if (media->p_meta)
+ {
+ const char *title = input_item_GetMetaLocked(media, vlc_meta_Title);
+ if (!EMPTY_STR(title))
+ ret = title;
+ }
+ return ret;
+}
+
+static inline vlc_tick_t MediaGetDuration(input_item_t *media)
+{
+ if (media->i_duration == INPUT_DURATION_INDEFINITE)
+ return 0;
+ if (media->i_duration == INPUT_DURATION_UNSET)
+ return 0;
+ return media->i_duration;
+}
+
+static inline int
+MediaCompareTitlesOrNames(input_item_t *lhs, input_item_t *rhs)
+{
+ const char *a = MediaGetTitleOrName(lhs);
+ const char *b = MediaGetTitleOrName(rhs);
+
+ return CompareStrings(a, b);
+}
+
+static inline int
+MediaCompareDurations(input_item_t *lhs, input_item_t *rhs)
+{
+ vlc_tick_t a = MediaGetDuration(lhs);
+ vlc_tick_t b = MediaGetDuration(rhs);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ return 0;
+}
+
+static inline int
+MediaCompareMetaStrings(input_item_t *lhs, input_item_t *rhs,
+ vlc_meta_type_t meta_key)
+{
+ const char *a = input_item_GetMetaLocked(lhs, meta_key);
+ const char *b = input_item_GetMetaLocked(rhs, meta_key);
+ return CompareStrings(a, b);
+}
+
+static inline int
+MediaCompareMetaIntegers(input_item_t *lhs, input_item_t *rhs,
+ vlc_meta_type_t meta_key)
+{
+ const char *a = input_item_GetMetaLocked(lhs, meta_key);
+ const char *b = input_item_GetMetaLocked(rhs, meta_key);
+ return CompareIntegers(a, b);
+}
+
+static inline int
+CompareItemsByKey(const vlc_playlist_item_t *a, const vlc_playlist_item_t *b,
+ enum vlc_playlist_sort_key key)
+{
+ switch (key)
+ {
+ case VLC_PLAYLIST_SORT_KEY_TITLE:
+ return MediaCompareTitlesOrNames(a->media, b->media);
+ case VLC_PLAYLIST_SORT_KEY_DURATION:
+ return MediaCompareDurations(a->media, b->media);
+ case VLC_PLAYLIST_SORT_KEY_ARTIST:
+ return MediaCompareMetaStrings(a->media, b->media, vlc_meta_Artist);
+ case VLC_PLAYLIST_SORT_KEY_ALBUM:
+ return MediaCompareMetaStrings(a->media, b->media, vlc_meta_Album);
+ case VLC_PLAYLIST_SORT_KEY_ALBUM_ARTIST:
+ return MediaCompareMetaStrings(a->media, b->media,
+ vlc_meta_AlbumArtist);
+ case VLC_PLAYLIST_SORT_KEY_GENRE:
+ return MediaCompareMetaStrings(a->media, b->media, vlc_meta_Genre);
+ case VLC_PLAYLIST_SORT_KEY_DATE:
+ return MediaCompareMetaIntegers(a->media, b->media, vlc_meta_Date);
+ case VLC_PLAYLIST_SORT_KEY_TRACK_NUMBER:
+ return MediaCompareMetaIntegers(a->media, b->media,
+ vlc_meta_TrackNumber);
+ case VLC_PLAYLIST_SORT_KEY_DISC_NUMBER:
+ return MediaCompareMetaIntegers(a->media, b->media,
+ vlc_meta_DiscNumber);
+ case VLC_PLAYLIST_SORT_KEY_URL:
+ return MediaCompareMetaStrings(a->media, b->media, vlc_meta_URL);
+ case VLC_PLAYLIST_SORT_KEY_RATING:
+ return MediaCompareMetaIntegers(a->media, b->media,
+ vlc_meta_Rating);
+ default:
+ assert(!"Unknown sort key");
+ }
+}
+
+static int
+compare_items(const void *lhs, const void *rhs, void *userdata)
+{
+ struct sort_request *req = userdata;
+ const vlc_playlist_item_t *a = *(const vlc_playlist_item_t **) lhs;
+ const vlc_playlist_item_t *b = *(const vlc_playlist_item_t **) rhs;
+
+ for (size_t i = 0; i < req->count; ++i)
+ {
+ const struct vlc_playlist_sort_criterion *criterion = &req->criteria[i];
+ int ret = CompareItemsByKey(a, b, criterion->key);
+ if (ret)
+ {
+ if (criterion->order == VLC_PLAYLIST_SORT_ORDER_DESCENDING)
+ /* do not return -ret, it's undefined if ret == INT_MIN */
+ return ret > 0 ? -1 : 1;
+ return ret;
+ }
+ }
+ return 0;
+}
+
+void
+vlc_playlist_Sort(vlc_playlist_t *playlist,
+ const struct vlc_playlist_sort_criterion criteria[],
+ size_t count)
+{
+ assert(count > 0);
+ PlaylistAssertLocked(playlist);
+
+ input_item_t *current_media = PlaylistGetMedia(playlist, playlist->current);
+
+ struct sort_request req = { criteria, count };
+
+ /* lock all media to read protected fields during comparison */
+ for (size_t i = 0; i < playlist->items.size; ++i)
+ vlc_mutex_lock(&playlist->items.data[i]->media->lock);
+
+ qsort_r(playlist->items.data, playlist->items.size,
+ sizeof(*playlist->items.data), compare_items, &req);
+
+ /* unlock all media */
+ for (size_t i = playlist->items.size; i != 0; --i)
+ vlc_mutex_unlock(&playlist->items.data[i - 1]->media->lock);
+
+ struct vlc_playlist_state state;
+ if (current_media)
+ {
+ /* the current position have changed after the shuffle */
+ vlc_playlist_state_Save(playlist, &state);
+ playlist->current = vlc_playlist_IndexOfMedia(playlist, current_media);
+ playlist->has_prev = PlaylistHasPrev(playlist);
+ playlist->has_next = PlaylistHasNext(playlist);
+ }
+
+ PlaylistNotify(playlist, on_items_reset, playlist->items.data,
+ playlist->items.size);
+ if (current_media)
+ vlc_playlist_state_NotifyChanges(playlist, &state);
+}
+
ssize_t
vlc_playlist_IndexOf(vlc_playlist_t *playlist, const vlc_playlist_item_t *item)
{
@@ -3420,6 +3616,113 @@ test_shuffle(void)
vlc_playlist_Delete(playlist);
}
+static void
+test_sort(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ media[0] = CreateDummyMedia(4); media[0]->i_duration = 42;
+ media[1] = CreateDummyMedia(1); media[1]->i_duration = 5;
+ media[2] = CreateDummyMedia(6); media[2]->i_duration = 100;
+ media[3] = CreateDummyMedia(2); media[3]->i_duration = 1;
+ media[4] = CreateDummyMedia(1); media[4]->i_duration = 8;
+ media[5] = CreateDummyMedia(4); media[5]->i_duration = 23;
+ media[6] = CreateDummyMedia(3); media[6]->i_duration = 60;
+ media[7] = CreateDummyMedia(3); media[7]->i_duration = 40;
+ media[8] = CreateDummyMedia(0); media[8]->i_duration = 42;
+ media[9] = CreateDummyMedia(5); media[9]->i_duration = 42;
+
+ /* 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);
+
+ /* on_items_reset is called once during AddListener() */
+ callback_ctx_reset(&ctx);
+
+ playlist->current = 0;
+ playlist->has_prev = false;
+ playlist->has_next = true;
+
+ struct vlc_playlist_sort_criterion criteria1[] = {
+ { VLC_PLAYLIST_SORT_KEY_TITLE, VLC_PLAYLIST_SORT_ORDER_ASCENDING },
+ { VLC_PLAYLIST_SORT_KEY_DURATION, VLC_PLAYLIST_SORT_ORDER_ASCENDING },
+ };
+ vlc_playlist_Sort(playlist, criteria1, 2);
+
+ EXPECT_AT(0, 8);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 4);
+ EXPECT_AT(3, 3);
+ EXPECT_AT(4, 7);
+ EXPECT_AT(5, 6);
+ EXPECT_AT(6, 5);
+ EXPECT_AT(7, 0);
+ EXPECT_AT(8, 9);
+ EXPECT_AT(9, 2);
+
+ ssize_t index = vlc_playlist_IndexOfMedia(playlist, media[0]);
+ assert(index == 7);
+ assert(playlist->current == 7);
+
+ assert(ctx.vec_items_reset.size == 1);
+ assert(ctx.vec_items_reset.data[0].count == 10);
+ assert(ctx.vec_items_reset.data[0].state.playlist_size == 10);
+ assert(ctx.vec_items_reset.data[0].state.current == 7);
+ 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 == 7);
+
+ 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);
+
+ struct vlc_playlist_sort_criterion criteria2[] = {
+ { VLC_PLAYLIST_SORT_KEY_DURATION, VLC_PLAYLIST_SORT_ORDER_DESCENDING },
+ { VLC_PLAYLIST_SORT_KEY_TITLE, VLC_PLAYLIST_SORT_ORDER_ASCENDING },
+ };
+
+ vlc_playlist_Sort(playlist, criteria2, 2);
+
+ EXPECT_AT(0, 2);
+ EXPECT_AT(1, 6);
+ EXPECT_AT(2, 8);
+ EXPECT_AT(3, 0);
+ EXPECT_AT(4, 9);
+ EXPECT_AT(5, 7);
+ EXPECT_AT(6, 5);
+ EXPECT_AT(7, 4);
+ EXPECT_AT(8, 1);
+ EXPECT_AT(9, 3);
+
+ assert(ctx.vec_items_reset.size == 1);
+ assert(ctx.vec_items_reset.data[0].count == 10);
+ assert(ctx.vec_items_reset.data[0].state.playlist_size == 10);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
#undef EXPECT_AT
int main(void)
@@ -3452,6 +3755,7 @@ int main(void)
test_request_goto_adapt();
test_random();
test_shuffle();
+ test_sort();
return 0;
}
#endif /* TEST_PLAYLIST */
--
2.19.1
More information about the vlc-devel
mailing list