[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