[vlc-commits] [Git][videolan/vlc][master] 16 commits: medialibrary: Add functions to retrieve favorites from media library

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Sat Jul 12 15:11:39 UTC 2025



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
4763efc4 by Claudio Cambra at 2025-07-12T14:49:06+00:00
medialibrary: Add functions to retrieve favorites from media library

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
c31d50c5 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Implement media library favorites arrays in library model

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
a4ddea20 by Claudio Cambra at 2025-07-12T14:49:06+00:00
medialibrary: Send event when favorites change

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
3778130e by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Add VLCLibraryFavoritesViewController

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
30f01ec4 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Add VLCLibraryFavoritesDataSource

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
78a72612 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Handle favorites change event in library model

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
a8b00d0a by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Add persistent view mode preferences for favorites

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
5ea5e9f2 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Implement basic loading of items into favorites data source

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
b2f94e53 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Implement conformance to collection view data source in favorites data source

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
c6b70ddb by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Implement master/detail table view conformance in favorites data source

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
75d9d4f8 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Configure and present collection view (or placeholder view) for favorites view controller

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
9888c5c1 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Add favorites library segment

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
f3a40bbb by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Configure and setup table view for favorites library

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
2d6186d9 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Implement presenting library item for table view in favorites vc

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
60f89431 by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Emit notification on displayed collection change in favorites data source

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -
8d0a5ccc by Claudio Cambra at 2025-07-12T14:49:06+00:00
macosx: Allow creating dummy media library items with media items

Signed-off-by: Claudio Cambra <developer at claudiocambra.com>

- - - - -


16 changed files:

- extras/package/macosx/VLC.xcodeproj/project.pbxproj
- include/vlc_media_library.h
- modules/gui/macosx/Makefile.am
- modules/gui/macosx/library/VLCLibraryDataTypes.h
- modules/gui/macosx/library/VLCLibraryDataTypes.m
- modules/gui/macosx/library/VLCLibraryModel.h
- modules/gui/macosx/library/VLCLibraryModel.m
- modules/gui/macosx/library/VLCLibrarySegment.h
- modules/gui/macosx/library/VLCLibrarySegment.m
- modules/gui/macosx/library/VLCLibraryWindowPersistentPreferences.h
- modules/gui/macosx/library/VLCLibraryWindowPersistentPreferences.m
- + modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesDataSource.h
- + modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesDataSource.m
- + modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesViewController.h
- + modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesViewController.m
- modules/misc/medialibrary/medialibrary.cpp


Changes:

=====================================
extras/package/macosx/VLC.xcodeproj/project.pbxproj
=====================================
@@ -157,6 +157,8 @@
 		53D0213D2CC25520003C008F /* VLCPlayerTitle.m in Sources */ = {isa = PBXBuildFile; fileRef = 53D0213C2CC25520003C008F /* VLCPlayerTitle.m */; };
 		53D21A1F2BB465600085C71B /* VLCLibraryWindowPlayQueueSidebarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53D21A1E2BB465600085C71B /* VLCLibraryWindowPlayQueueSidebarViewController.m */; };
 		53D8ED9A2B583AAF00142EAD /* VLCLibraryModelChangeDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53D8ED992B583AAF00142EAD /* VLCLibraryModelChangeDelegate.m */; };
+		53E511282E1AD14A0048CEFC /* VLCLibraryFavoritesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E511272E1AD14A0048CEFC /* VLCLibraryFavoritesViewController.m */; };
+		53E5112B2E1ADA9F0048CEFC /* VLCLibraryFavoritesDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E5112A2E1ADA9F0048CEFC /* VLCLibraryFavoritesDataSource.m */; };
 		53ED472329C74D1F00795DB1 /* VLCLibraryAudioTableViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472229C74D1F00795DB1 /* VLCLibraryAudioTableViewDelegate.m */; };
 		53ED472629C78FE700795DB1 /* VLCLibraryAudioGroupTableViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472529C78FE700795DB1 /* VLCLibraryAudioGroupTableViewDelegate.m */; };
 		53ED472B29C8FF9D00795DB1 /* VLCLibraryAlbumTracksTableViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472A29C8FF9D00795DB1 /* VLCLibraryAlbumTracksTableViewDelegate.m */; };
@@ -489,6 +491,10 @@
 		53D6664E2B6B82940042C03D /* VLCLibraryTableViewDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryTableViewDataSource.h; sourceTree = "<group>"; };
 		53D8ED982B583AAF00142EAD /* VLCLibraryModelChangeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryModelChangeDelegate.h; sourceTree = "<group>"; };
 		53D8ED992B583AAF00142EAD /* VLCLibraryModelChangeDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCLibraryModelChangeDelegate.m; sourceTree = "<group>"; };
+		53E511262E1AD14A0048CEFC /* VLCLibraryFavoritesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryFavoritesViewController.h; sourceTree = "<group>"; };
+		53E511272E1AD14A0048CEFC /* VLCLibraryFavoritesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCLibraryFavoritesViewController.m; sourceTree = "<group>"; };
+		53E511292E1ADA9F0048CEFC /* VLCLibraryFavoritesDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryFavoritesDataSource.h; sourceTree = "<group>"; };
+		53E5112A2E1ADA9F0048CEFC /* VLCLibraryFavoritesDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCLibraryFavoritesDataSource.m; sourceTree = "<group>"; };
 		53ED472129C74D1F00795DB1 /* VLCLibraryAudioTableViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryAudioTableViewDelegate.h; sourceTree = "<group>"; };
 		53ED472229C74D1F00795DB1 /* VLCLibraryAudioTableViewDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCLibraryAudioTableViewDelegate.m; sourceTree = "<group>"; };
 		53ED472429C78FE700795DB1 /* VLCLibraryAudioGroupTableViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryAudioGroupTableViewDelegate.h; sourceTree = "<group>"; };
@@ -1372,6 +1378,7 @@
 			isa = PBXGroup;
 			children = (
 				5325C5742930026600B2B63A /* audio-library */,
+				53E511252E1ACB6C0048CEFC /* favorites-library */,
 				537BD6832C59212A00446ED0 /* groups-library */,
 				5350E4EB2B1B210E00F276CB /* home-library */,
 				7DFBDCB8226CED3700B700A5 /* media-source */,
@@ -1731,6 +1738,17 @@
 			path = "video-library";
 			sourceTree = "<group>";
 		};
+		53E511252E1ACB6C0048CEFC /* favorites-library */ = {
+			isa = PBXGroup;
+			children = (
+				53E511262E1AD14A0048CEFC /* VLCLibraryFavoritesViewController.h */,
+				53E511272E1AD14A0048CEFC /* VLCLibraryFavoritesViewController.m */,
+				53E511292E1ADA9F0048CEFC /* VLCLibraryFavoritesDataSource.h */,
+				53E5112A2E1ADA9F0048CEFC /* VLCLibraryFavoritesDataSource.m */,
+			);
+			path = "favorites-library";
+			sourceTree = "<group>";
+		};
 		6B8224481E4D2B0500833BE1 /* Template Images */ = {
 			isa = PBXGroup;
 			children = (
@@ -2436,6 +2454,7 @@
 				1C3113D31E508C6900D4DD76 /* VLCResumeDialogController.m in Sources */,
 				7D2FFA40227B8A5B0085D649 /* VLCLinearProgressIndicator.m in Sources */,
 				538DC4E32A6B69B50082DECD /* VLCLibraryAudioGroupHeaderView.m in Sources */,
+				53E5112B2E1ADA9F0048CEFC /* VLCLibraryFavoritesDataSource.m in Sources */,
 				7DFBDCB1226A518400B700A5 /* VLCLibraryMenuController.m in Sources */,
 				536283F8291146BC00640C15 /* VLCLibrarySongTableCellView.m in Sources */,
 				53A8F9D12A7E1E6300BC11BF /* VLCLibraryPlaylistViewController.m in Sources */,
@@ -2449,6 +2468,7 @@
 				1C3113D91E508C6900D4DD76 /* VLCSimplePrefsController.m in Sources */,
 				7DFBDCC4226E445500B700A5 /* VLCMediaSourceBaseDataSource.m in Sources */,
 				6B2EFC601F2819F700F3C0EA /* VLCVolumeSlider.m in Sources */,
+				53E511282E1AD14A0048CEFC /* VLCLibraryFavoritesViewController.m in Sources */,
 				53F399802AC6D6B400B86241 /* VLCLibraryHomeViewVideoCarouselContainerView.m in Sources */,
 				7DE2F0472282D5D10040DD0A /* VLCLibraryTableCellView.m in Sources */,
 				53C1EF8C2B466B13001AEEF5 /* VLCLibraryHomeViewStackViewController.m in Sources */,


=====================================
include/vlc_media_library.h
=====================================
@@ -551,6 +551,24 @@ enum vlc_ml_list_queries
     VLC_ML_LIST_FOLDER_MEDIA,     /**< arg1: folder id; arg2 (out): vlc_ml_media_list_t**                               */
     VLC_ML_COUNT_FOLDER_MEDIA,    /**< arg1: folder id; arg2 (out): size_t*                                             */
 
+    /* Favorites listings */
+    VLC_ML_LIST_FAVORITE_MEDIA,   /**< arg1 (out): vlc_ml_media_list_t**                                                */
+    VLC_ML_COUNT_FAVORITE_MEDIA,  /**< arg1 (out): size_t*                                                              */
+    VLC_ML_LIST_FAVORITE_VIDEOS,  /**< arg1 (out): vlc_ml_media_list_t**                                                */
+    VLC_ML_COUNT_FAVORITE_VIDEOS, /**< arg1 (out): size_t*                                                              */
+    VLC_ML_LIST_FAVORITE_AUDIOS,  /**< arg1 (out): vlc_ml_media_list_t**                                                */
+    VLC_ML_COUNT_FAVORITE_AUDIOS, /**< arg1 (out): size_t*                                                              */
+    VLC_ML_LIST_FAVORITE_ALBUMS,  /**< arg1 (out): vlc_ml_album_list_t**                                                */
+    VLC_ML_COUNT_FAVORITE_ALBUMS, /**< arg1 (out): size_t*                                                              */
+    VLC_ML_LIST_FAVORITE_ARTISTS, /**< arg1 (out): vlc_ml_artist_list_t**                                               */
+    VLC_ML_COUNT_FAVORITE_ARTISTS,/**< arg1 (out): size_t*                                                              */
+    VLC_ML_LIST_FAVORITE_GENRES,  /**< arg1 (out): vlc_ml_genre_list_t**                                                */
+    VLC_ML_COUNT_FAVORITE_GENRES, /**< arg1 (out): size_t*                                                              */
+    VLC_ML_LIST_FAVORITE_PLAYLISTS,/**< arg1 (out): vlc_ml_playlist_list_t**                                            */
+    VLC_ML_COUNT_FAVORITE_PLAYLISTS,/**< arg1 (out): size_t*                                                            */
+    VLC_ML_LIST_FAVORITE_FOLDERS, /**< arg1 (out): vlc_ml_folder_list_t**                                               */
+    VLC_ML_COUNT_FAVORITE_FOLDERS,/**< arg1 (out): size_t*                                                              */
+
     /* Children entities listing */
     VLC_ML_LIST_MEDIA_OF,         /**< arg1: parent entity type; arg2: parent entity id; arg3(out): ml_media_list_t**   */
     VLC_ML_COUNT_MEDIA_OF,        /**< arg1: parent entity type; arg2: parent entity id; arg3(out): size_t*             */
@@ -813,6 +831,12 @@ enum vlc_ml_event_type
      * Sent when an application requested rescan starts being processed.
      */
     VLC_ML_EVENT_RESCAN_STARTED,
+    /**
+     * Sent when favorites list changes. This includes when entities are
+     * marked/unmarked as favorites.
+     * The entity type and id are stored in vlc_ml_event_t::favorites_changed
+     */
+    VLC_ML_EVENT_FAVORITES_CHANGED,
 };
 
 typedef struct vlc_ml_event_t
@@ -885,6 +909,12 @@ typedef struct vlc_ml_event_t
         {
             vlc_ml_history_type_t history_type;
         } history_changed;
+        struct
+        {
+            int64_t i_entity_id;
+            int i_entity_type; /**< One of VLC_ML_PARENT_* values */
+            bool b_favorite; /**< true if marked as favorite, false if unmarked */
+        } favorites_changed;
     };
 } vlc_ml_event_t;
 
@@ -2011,6 +2041,153 @@ static inline int vlc_ml_playlist_set_favorite( vlc_medialibrary_t *p_ml, int64_
     return vlc_ml_control( p_ml, VLC_ML_PLAYLIST_SET_FAVORITE, i_playlist_id, (int)b_favorite );
 }
 
+//-------------------------------------------------------------------------------------------------
+// Favorites
+
+static inline vlc_ml_media_list_t* vlc_ml_list_favorite_media( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_media_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_MEDIA, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_media( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_MEDIA, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
+static inline vlc_ml_media_list_t* vlc_ml_list_favorite_videos( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_media_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_VIDEOS, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_videos( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_VIDEOS, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
+static inline vlc_ml_media_list_t* vlc_ml_list_favorite_audios( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_media_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_AUDIOS, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_audios( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_AUDIOS, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
+static inline vlc_ml_album_list_t* vlc_ml_list_favorite_albums( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_album_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_ALBUMS, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_albums( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_ALBUMS, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
+static inline vlc_ml_artist_list_t* vlc_ml_list_favorite_artists( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_artist_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_ARTISTS, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_artists( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_ARTISTS, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
+static inline vlc_ml_genre_list_t* vlc_ml_list_favorite_genres( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_genre_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_GENRES, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_genres( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_GENRES, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
+static inline vlc_ml_playlist_list_t* vlc_ml_list_favorite_playlists( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_playlist_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_PLAYLISTS, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_playlists( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_PLAYLISTS, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
+static inline vlc_ml_folder_list_t* vlc_ml_list_favorite_folders( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    vlc_ml_folder_list_t* res;
+    if ( vlc_ml_list( p_ml, VLC_ML_LIST_FAVORITE_FOLDERS, params, &res ) != VLC_SUCCESS )
+        return NULL;
+    return res;
+}
+
+static inline size_t vlc_ml_count_favorite_folders( vlc_medialibrary_t* p_ml, const vlc_ml_query_params_t* params )
+{
+    vlc_assert( p_ml != NULL );
+    size_t count;
+    if ( vlc_ml_list( p_ml, VLC_ML_COUNT_FAVORITE_FOLDERS, params, &count ) != VLC_SUCCESS )
+        return 0;
+    return count;
+}
+
 #ifdef __cplusplus
 }
 #endif /* C++ */


=====================================
modules/gui/macosx/Makefile.am
=====================================
@@ -289,6 +289,10 @@ libmacosx_plugin_la_SOURCES = \
 	gui/macosx/library/audio-library/VLCLibrarySongTableCellView.m \
 	gui/macosx/library/audio-library/VLCLibrarySongsTableViewSongPlayingTableCellView.h \
 	gui/macosx/library/audio-library/VLCLibrarySongsTableViewSongPlayingTableCellView.m \
+	gui/macosx/library/favorites-library/VLCLibraryFavoritesDataSource.h \
+	gui/macosx/library/favorites-library/VLCLibraryFavoritesDataSource.m \
+	gui/macosx/library/favorites-library/VLCLibraryFavoritesViewController.h \
+	gui/macosx/library/favorites-library/VLCLibraryFavoritesViewController.m \
 	gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.h \
 	gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.m \
 	gui/macosx/library/media-source/VLCMediaSource.h \


=====================================
modules/gui/macosx/library/VLCLibraryDataTypes.h
=====================================
@@ -357,6 +357,9 @@ typedef NS_ENUM(NSUInteger, VLCMediaLibraryParentGroupType) {
               withPrimaryDetailString:(nullable NSString *)primaryDetailString
             withSecondaryDetailString:(nullable NSString *)secondaryDetailString;
 
+- (instancetype)initWithDisplayString:(NSString *)displayString
+                       withMediaItems:(NSArray<VLCMediaLibraryMediaItem *> *)mediaItems;      
+
 @end
 
 NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/library/VLCLibraryDataTypes.m
=====================================
@@ -1695,22 +1695,37 @@ static NSString *genreArrayDisplayString(NSArray<VLCMediaLibraryGenre *> * const
     return self;
 }
 
+- (instancetype)initWithDisplayString:(NSString *)displayString
+                      withMediaItems:(NSArray<VLCMediaLibraryMediaItem *> *)mediaItems
+{
+
+    self = [self initWithDisplayString:displayString
+               withPrimaryDetailString:[NSString stringWithFormat:@"%lu items", (unsigned long)mediaItems.count]
+              withSecondaryDetailString:@""];
+    if (self) {
+        _mediaItems = mediaItems;
+        _firstMediaItem = mediaItems.firstObject;
+    }
+    return self;
+}
+
 - (void)moveToTrash
 {
-    [self doesNotRecognizeSelector:_cmd];
-    return;
+    [self iterateMediaItemsWithBlock:^(VLCMediaLibraryMediaItem * const item) {
+        [item moveToTrash];
+    }];
 }
 
 - (void)revealInFinder
 {
-    [self doesNotRecognizeSelector:_cmd];
-    return;
+    [self.mediaItems.firstObject revealInFinder];
 }
 
 - (void)iterateMediaItemsWithBlock:(nonnull void (^)(VLCMediaLibraryMediaItem * _Nonnull))mediaItemBlock
 {
-    [self doesNotRecognizeSelector:_cmd];
-    return;
+     for (VLCMediaLibraryMediaItem * const childItem in self.mediaItems) {
+        mediaItemBlock(childItem);
+    }
 }
 
 @end


=====================================
modules/gui/macosx/library/VLCLibraryModel.h
=====================================
@@ -36,6 +36,11 @@ extern NSString * const VLCLibraryModelMediaItemThumbnailGenerated;
 
 extern NSString * const VLCLibraryModelAudioMediaListReset;
 extern NSString * const VLCLibraryModelVideoMediaListReset;
+extern NSString * const VLCLibraryModelFavoriteAudioMediaListReset;
+extern NSString * const VLCLibraryModelFavoriteVideoMediaListReset;
+extern NSString * const VLCLibraryModelFavoriteAlbumsListReset;
+extern NSString * const VLCLibraryModelFavoriteArtistsListReset;
+extern NSString * const VLCLibraryModelFavoriteGenresListReset;
 extern NSString * const VLCLibraryModelRecentsMediaListReset;
 extern NSString * const VLCLibraryModelRecentAudioMediaListReset;
 extern NSString * const VLCLibraryModelListOfShowsReset;
@@ -120,6 +125,22 @@ extern NSString * const VLCLibraryModelDiscoveryFailed;
 - (void)sortByCriteria:(enum vlc_ml_sorting_criteria_t)sortCriteria
          andDescending:(bool)descending;
 
+// Favorites support
+ at property (readonly) size_t numberOfFavoriteAudioMedia;
+ at property (readonly) NSArray <VLCMediaLibraryMediaItem *> *listOfFavoriteAudioMedia;
+
+ at property (readonly) size_t numberOfFavoriteVideoMedia;
+ at property (readonly) NSArray <VLCMediaLibraryMediaItem *> *listOfFavoriteVideoMedia;
+
+ at property (readonly) size_t numberOfFavoriteAlbums;
+ at property (readonly) NSArray <VLCMediaLibraryAlbum *> *listOfFavoriteAlbums;
+
+ at property (readonly) size_t numberOfFavoriteArtists;
+ at property (readonly) NSArray <VLCMediaLibraryArtist *> *listOfFavoriteArtists;
+
+ at property (readonly) size_t numberOfFavoriteGenres;
+ at property (readonly) NSArray <VLCMediaLibraryGenre *> *listOfFavoriteGenres;
+
 @end
 
 NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/library/VLCLibraryModel.m
=====================================
@@ -37,6 +37,11 @@ NSString * const VLCLibraryModelMediaItemThumbnailGenerated = @"VLCLibraryModelM
 
 NSString * const VLCLibraryModelAudioMediaListReset = @"VLCLibraryModelAudioMediaListReset";
 NSString * const VLCLibraryModelVideoMediaListReset = @"VLCLibraryModelVideoMediaListReset";
+NSString * const VLCLibraryModelFavoriteAudioMediaListReset = @"VLCLibraryModelFavoriteAudioMediaListReset";
+NSString * const VLCLibraryModelFavoriteVideoMediaListReset = @"VLCLibraryModelFavoriteVideoMediaListReset";
+NSString * const VLCLibraryModelFavoriteAlbumsListReset = @"VLCLibraryModelFavoriteAlbumsListReset";
+NSString * const VLCLibraryModelFavoriteArtistsListReset = @"VLCLibraryModelFavoriteArtistsListReset";
+NSString * const VLCLibraryModelFavoriteGenresListReset = @"VLCLibraryModelFavoriteGenresListReset";
 NSString * const VLCLibraryModelRecentsMediaListReset = @"VLCLibraryModelRecentsMediaListReset";
 NSString * const VLCLibraryModelRecentAudioMediaListReset = @"VLCLibraryModelRecentAudioMediaListReset";
 NSString * const VLCLibraryModelListOfShowsReset = @"VLCLibraryModelListOfShowsReset";
@@ -218,6 +223,17 @@ static void libraryCallback(void *p_data, const vlc_ml_event_t *p_event)
             [libraryModel resetCachedListOfRecentMedia];
             [libraryModel resetCachedListOfRecentAudioMedia];
             break;
+        case VLC_ML_EVENT_FAVORITES_CHANGED:
+        {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteAudioMediaListReset object:libraryModel];
+                [NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteVideoMediaListReset object:libraryModel];
+                [NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteAlbumsListReset object:libraryModel];
+                [NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteArtistsListReset object:libraryModel];
+                [NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryModelFavoriteGenresListReset object:libraryModel];
+            });
+            break;
+        }
         case VLC_ML_EVENT_DISCOVERY_STARTED:
             dispatch_async(dispatch_get_main_queue(), ^{
                 NSNotificationCenter * const defaultCenter = NSNotificationCenter.defaultCenter;
@@ -1334,4 +1350,117 @@ static void libraryCallback(void *p_data, const vlc_ml_event_t *p_event)
     });
 }
 
+#pragma mark - Favorites Support
+
+- (NSArray<VLCMediaLibraryMediaItem *> *)listOfFavoriteAudioMedia
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    vlc_ml_media_list_t * const p_media_list = vlc_ml_list_favorite_audios(_p_mediaLibrary, &queryParams);
+    NSArray * const mediaArray = [NSArray arrayFromVlcMediaList:p_media_list];
+    if (mediaArray == nil) {
+        return @[];
+    }
+    vlc_ml_media_list_release(p_media_list);
+    return mediaArray;
+}
+
+- (NSArray<VLCMediaLibraryMediaItem *> *)listOfFavoriteVideoMedia
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    vlc_ml_media_list_t * const p_media_list = vlc_ml_list_favorite_videos(_p_mediaLibrary, &queryParams);
+    NSArray * const mediaArray = [NSArray arrayFromVlcMediaList:p_media_list];
+    if (mediaArray == nil) {
+        return @[];
+    }
+    vlc_ml_media_list_release(p_media_list);
+    return mediaArray;
+}
+
+- (NSArray<VLCMediaLibraryAlbum *> *)listOfFavoriteAlbums
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    vlc_ml_album_list_t * const p_album_list = vlc_ml_list_favorite_albums(_p_mediaLibrary, &queryParams);
+    if (p_album_list == NULL) {
+        return @[];
+    }
+    
+    NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:p_album_list->i_nb_items];
+    for (size_t x = 0; x < p_album_list->i_nb_items; x++) {
+        VLCMediaLibraryAlbum * const album = [[VLCMediaLibraryAlbum alloc] initWithAlbum:&p_album_list->p_items[x]];
+        if (album != nil) {
+            [mutableArray addObject:album];
+        }
+    }
+    vlc_ml_album_list_release(p_album_list);
+    return mutableArray.copy;
+}
+
+- (NSArray<VLCMediaLibraryArtist *> *)listOfFavoriteArtists
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    vlc_ml_artist_list_t * const p_artist_list = vlc_ml_list_favorite_artists(_p_mediaLibrary, &queryParams);
+    if (p_artist_list == NULL) {
+        return @[];
+    }
+    
+    NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:p_artist_list->i_nb_items];
+    for (size_t x = 0; x < p_artist_list->i_nb_items; x++) {
+        VLCMediaLibraryArtist * const artist = [[VLCMediaLibraryArtist alloc] initWithArtist:&p_artist_list->p_items[x]];
+        if (artist != nil) {
+            [mutableArray addObject:artist];
+        }
+    }
+    vlc_ml_artist_list_release(p_artist_list);
+    return mutableArray.copy;
+}
+
+- (NSArray<VLCMediaLibraryGenre *> *)listOfFavoriteGenres
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    vlc_ml_genre_list_t * const p_genre_list = vlc_ml_list_favorite_genres(_p_mediaLibrary, &queryParams);
+    if (p_genre_list == NULL) {
+        return @[];
+    }
+    
+    NSMutableArray * const mutableArray = [[NSMutableArray alloc] initWithCapacity:p_genre_list->i_nb_items];
+    for (size_t x = 0; x < p_genre_list->i_nb_items; x++) {
+        VLCMediaLibraryGenre * const genre = [[VLCMediaLibraryGenre alloc] initWithGenre:&p_genre_list->p_items[x]];
+        if (genre != nil) {
+            [mutableArray addObject:genre];
+        }
+    }
+    vlc_ml_genre_list_release(p_genre_list);
+    return mutableArray.copy;
+}
+
+- (size_t)numberOfFavoriteAudioMedia
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    return vlc_ml_count_favorite_audios(_p_mediaLibrary, &queryParams);
+}
+
+- (size_t)numberOfFavoriteVideoMedia
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    return vlc_ml_count_favorite_videos(_p_mediaLibrary, &queryParams);
+}
+
+- (size_t)numberOfFavoriteAlbums
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    return vlc_ml_count_favorite_albums(_p_mediaLibrary, &queryParams);
+}
+
+- (size_t)numberOfFavoriteArtists
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    return vlc_ml_count_favorite_artists(_p_mediaLibrary, &queryParams);
+}
+
+- (size_t)numberOfFavoriteGenres
+{
+    const vlc_ml_query_params_t queryParams = [self queryParams];
+    return vlc_ml_count_favorite_genres(_p_mediaLibrary, &queryParams);
+}
+
 @end


=====================================
modules/gui/macosx/library/VLCLibrarySegment.h
=====================================
@@ -33,6 +33,7 @@ extern NSString * const VLCLibraryBookmarkedLocationsChanged;
 typedef NS_ENUM(NSInteger, VLCLibrarySegmentType) {
     VLCLibraryLowSentinelSegment = -1,
     VLCLibraryHomeSegmentType,
+    VLCLibraryFavoritesSegmentType,
     VLCLibraryHeaderSegmentType,
     VLCLibraryVideoSegmentType,
     VLCLibraryShowsVideoSubSegmentType,


=====================================
modules/gui/macosx/library/VLCLibrarySegment.m
=====================================
@@ -35,6 +35,8 @@
 
 #import "library/audio-library/VLCLibraryAudioViewController.h"
 
+#import "library/favorites-library/VLCLibraryFavoritesViewController.h"
+
 #import "library/groups-library/VLCLibraryGroupsViewController.h"
 
 #import "library/home-library/VLCLibraryHomeViewController.h"
@@ -163,6 +165,44 @@ NSArray<NSString *> *defaultBookmarkedLocations()
 
 @end
 
+// MARK: - VLCLibraryFavoritesSegment
+
+ at interface VLCLibraryFavoritesSegment : VLCLibrarySegment
+ at end
+
+ at implementation VLCLibraryFavoritesSegment
+
+- (instancetype)init
+{
+    self = [super initWithSegmentType:VLCLibraryFavoritesSegmentType];
+    if (self) {
+        self.internalDisplayString = _NS("Favorites");
+        if (@available(macOS 11.0, *)) {
+            self.internalDisplayImage = [NSImage imageWithSystemSymbolName:@"heart"
+                                                  accessibilityDescription:@"Favorites icon"];
+        } else {
+            self.internalDisplayImage = [NSImage imageNamed:@"bw-home"];
+            self.internalDisplayImage.template = YES;
+        }
+        self.internalLibraryViewControllerClass = VLCLibraryFavoritesViewController.class;
+        self.internalLibraryViewControllerCreator = ^{
+            return [[VLCLibraryFavoritesViewController alloc] initWithLibraryWindow:VLCMain.sharedInstance.libraryWindow];
+        };
+        self.internalLibraryViewPresenter = ^(VLCLibraryAbstractSegmentViewController * const controller) {
+            [(VLCLibraryFavoritesViewController *)controller presentFavoritesView];
+        };
+        self.internalSaveViewModePreference = ^(const NSInteger viewMode) {
+            VLCLibraryWindowPersistentPreferences.sharedInstance.favoritesLibraryViewMode = viewMode;
+        };
+        self.internalGetViewModePreference = ^{
+            return VLCLibraryWindowPersistentPreferences.sharedInstance.favoritesLibraryViewMode;
+        };
+        self.internalToolbarDisplayFlags = standardLibraryViewToolbarDisplayFlags;
+    }
+    return self;
+}
+
+ at end
 
 // MARK: - Video library view segments
 
@@ -815,6 +855,7 @@ NSArray<NSString *> *defaultBookmarkedLocations()
     return @[
         [[VLCLibraryHomeSegment alloc] init],
         [[VLCLibraryHeaderSegment alloc] initWithDisplayString:_NS("Library")],
+        [[VLCLibraryFavoritesSegment alloc] init],
         [[VLCLibraryVideoSegment alloc] init],
         [[VLCLibraryMusicSegment alloc] init],
         [[VLCLibraryPlaylistSegment alloc] init],
@@ -830,6 +871,8 @@ NSArray<NSString *> *defaultBookmarkedLocations()
     switch (segmentType) {
         case VLCLibraryHomeSegmentType:
             return [[VLCLibraryHomeSegment alloc] init];
+        case VLCLibraryFavoritesSegmentType:
+            return [[VLCLibraryFavoritesSegment alloc] init];
         case VLCLibraryVideoSegmentType:
             return [[VLCLibraryVideoSegment alloc] init];
         case VLCLibraryShowsVideoSubSegmentType:


=====================================
modules/gui/macosx/library/VLCLibraryWindowPersistentPreferences.h
=====================================
@@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property (class, readonly) VLCLibraryWindowPersistentPreferences *sharedInstance;
 
 @property (readwrite, nonatomic) VLCLibraryViewModeSegment homeLibraryViewMode;
+ at property (readwrite, nonatomic) VLCLibraryViewModeSegment favoritesLibraryViewMode;
 @property (readwrite, nonatomic) VLCLibraryViewModeSegment videoLibraryViewMode;
 @property (readwrite, nonatomic) VLCLibraryViewModeSegment showsLibraryViewMode;
 @property (readwrite, nonatomic) VLCLibraryViewModeSegment albumLibraryViewMode;


=====================================
modules/gui/macosx/library/VLCLibraryWindowPersistentPreferences.m
=====================================
@@ -25,6 +25,7 @@
 NSString * const VLCLibraryWindowPreferencePrefix = @"VLCLibraryWindow";
 
 NSString * const VLCLibraryHomeLibraryViewModePreferenceKey = @"HomeLibraryViewMode";
+NSString * const VLCLibraryFavoritesLibraryViewModePreferenceKey = @"FavoritesLibraryViewMode";
 NSString * const VLCLibraryVideoLibraryViewModePreferenceKey = @"VideoLibraryViewMode";
 NSString * const VLCLibraryShowsLibraryViewModePreferenceKey = @"ShowsLibraryViewMode";
 NSString * const VLCLibraryAlbumLibraryViewModePreferenceKey = @"AlbumLibraryViewMode";
@@ -97,6 +98,17 @@ static VLCLibraryWindowPersistentPreferences *sharedInstance = nil;
                                               value:homeLibraryViewMode];
 }
 
+- (VLCLibraryViewModeSegment)favoritesLibraryViewMode
+{
+    return [self libraryViewModePreferenceWithKey:VLCLibraryFavoritesLibraryViewModePreferenceKey];
+}
+
+- (void)setFavoritesLibraryViewMode:(VLCLibraryViewModeSegment)favoritesLibraryViewMode
+{
+    [self setLibraryWindowViewModePreferenceWithKey:VLCLibraryFavoritesLibraryViewModePreferenceKey
+                                              value:favoritesLibraryViewMode];
+}
+
 - (VLCLibraryViewModeSegment)videoLibraryViewMode
 {
     return [self libraryViewModePreferenceWithKey:VLCLibraryVideoLibraryViewModePreferenceKey];


=====================================
modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesDataSource.h
=====================================
@@ -0,0 +1,55 @@
+/*****************************************************************************
+ * VLCLibraryFavoritesDataSource.h: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ *****************************************************************************/
+
+#import <Cocoa/Cocoa.h>
+
+#import "library/VLCLibraryCollectionViewDataSource.h"
+#import "library/VLCLibraryMasterDetailViewTableViewDataSource.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+ at class VLCLibraryModel;
+
+extern NSString * const VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification;
+
+typedef NS_ENUM(NSUInteger, VLCLibraryFavoritesSection) {
+    VLCLibraryFavoritesSectionVideoMedia = 0,
+    VLCLibraryFavoritesSectionAudioMedia,
+    VLCLibraryFavoritesSectionAlbums,
+    VLCLibraryFavoritesSectionArtists,
+    VLCLibraryFavoritesSectionGenres,
+    VLCLibraryFavoritesSectionCount
+};
+
+ at interface VLCLibraryFavoritesDataSource : NSObject <VLCLibraryCollectionViewDataSource, VLCLibraryMasterDetailViewTableViewDataSource>
+
+ at property (readwrite, weak) VLCLibraryModel *libraryModel;
+ at property (readwrite, weak) NSCollectionView *collectionView;
+ at property (readwrite, weak) NSTableView *masterTableView;
+ at property (readwrite, weak) NSTableView *detailTableView;
+
+- (void)reloadData;
+- (NSInteger)rowForLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem;
+
+ at end
+
+NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesDataSource.m
=====================================
@@ -0,0 +1,450 @@
+/*****************************************************************************
+ * VLCLibraryFavoritesDataSource.m: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ *****************************************************************************/
+
+#import "VLCLibraryFavoritesDataSource.h"
+
+#import "library/VLCLibraryCollectionViewFlowLayout.h"
+#import "library/VLCLibraryCollectionViewItem.h"
+#import "library/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.h"
+#import "library/VLCLibraryCollectionViewSupplementaryElementView.h"
+#import "library/VLCLibraryModel.h"
+#import "library/VLCLibraryDataTypes.h"
+#import "library/VLCLibraryRepresentedItem.h"
+#import "library/VLCLibraryTableCellView.h"
+
+#import "views/VLCImageView.h"
+
+#import "main/CompatibilityFixes.h"
+#import "main/VLCMain.h"
+
+#import "extensions/NSIndexSet+VLCAdditions.h"
+#import "extensions/NSString+Helpers.h"
+#import "extensions/NSPasteboardItem+VLCAdditions.h"
+
+NSString * const VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification = @"VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification";
+
+ at interface VLCLibraryFavoritesDataSource ()
+{
+    NSArray *_favoriteVideoMediaArray;
+    NSArray *_favoriteAudioMediaArray;
+    NSArray *_favoriteAlbumsArray;
+    NSArray *_favoriteArtistsArray;
+    NSArray *_favoriteGenresArray;
+    VLCLibraryCollectionViewFlowLayout *_collectionViewFlowLayout;
+    NSArray<NSNumber *> *_visibleSectionMapping; // Maps visible sections to VLCLibraryFavoritesSection values
+}
+
+ at end
+
+ at implementation VLCLibraryFavoritesDataSource
+
+- (instancetype)init
+{
+    self = [super init];
+    if (self) {
+        [self connect];
+    }
+    return self;
+}
+
+- (NSArray *)arrayForSection:(VLCLibraryFavoritesSection)section
+{
+    switch (section) {
+        case VLCLibraryFavoritesSectionVideoMedia:
+            return _favoriteVideoMediaArray;
+        case VLCLibraryFavoritesSectionAudioMedia:
+            return _favoriteAudioMediaArray;
+        case VLCLibraryFavoritesSectionAlbums:
+            return _favoriteAlbumsArray;
+        case VLCLibraryFavoritesSectionArtists:
+            return _favoriteArtistsArray;
+        case VLCLibraryFavoritesSectionGenres:
+            return _favoriteGenresArray;
+        default:
+            return @[];
+    }
+}
+
+- (NSString *)titleForSection:(VLCLibraryFavoritesSection)section
+{
+    switch (section) {
+        case VLCLibraryFavoritesSectionVideoMedia:
+            return _NS("Favorite Videos");
+        case VLCLibraryFavoritesSectionAudioMedia:
+            return _NS("Favorite Music");
+        case VLCLibraryFavoritesSectionAlbums:
+            return _NS("Favorite Albums");
+        case VLCLibraryFavoritesSectionArtists:
+            return _NS("Favorite Artists");
+        case VLCLibraryFavoritesSectionGenres:
+            return _NS("Favorite Genres");
+        default:
+            return @"";
+    }
+}
+
+- (VLCMediaLibraryParentGroupType)parentTypeForSection:(VLCLibraryFavoritesSection)section
+{
+    switch (section) {
+        case VLCLibraryFavoritesSectionVideoMedia:
+            return VLCMediaLibraryParentGroupTypeVideoLibrary;
+        case VLCLibraryFavoritesSectionAudioMedia:
+            return VLCMediaLibraryParentGroupTypeAudioLibrary;
+        case VLCLibraryFavoritesSectionAlbums:
+            return VLCMediaLibraryParentGroupTypeAlbum;
+        case VLCLibraryFavoritesSectionArtists:
+            return VLCMediaLibraryParentGroupTypeArtist;
+        case VLCLibraryFavoritesSectionGenres:
+            return VLCMediaLibraryParentGroupTypeGenre;
+        default:
+            return VLCMediaLibraryParentGroupTypeUnknown;
+    }
+}
+
+- (VLCLibraryFavoritesSection)sectionForVisibleIndex:(NSInteger)visibleIndex
+{
+    if (visibleIndex < 0 || visibleIndex >= _visibleSectionMapping.count) {
+        return VLCLibraryFavoritesSectionCount; // Invalid
+    }
+    return (VLCLibraryFavoritesSection)[_visibleSectionMapping[visibleIndex] integerValue];
+}
+
+- (NSInteger)visibleIndexForSection:(VLCLibraryFavoritesSection)section
+{
+    NSNumber * const sectionNumber = @(section);
+    NSUInteger index = [_visibleSectionMapping indexOfObject:sectionNumber];
+    return index == NSNotFound ? -1 : (NSInteger)index;
+}
+
+- (void)updateVisibleSectionMapping
+{
+    NSMutableArray<NSNumber *> * const visibleSections = [NSMutableArray array];
+    
+    for (NSUInteger i = 0; i < VLCLibraryFavoritesSectionCount; i++) {
+        NSArray * const sectionArray = [self arrayForSection:i];
+        if (sectionArray.count > 0) {
+            [visibleSections addObject:@(i)];
+        }
+    }
+    
+    _visibleSectionMapping = [visibleSections copy];
+}
+
+- (NSUInteger)indexOfMediaItem:(const NSUInteger)libraryId inArray:(NSArray const *)array
+{
+    return [array indexOfObjectPassingTest:^BOOL(id<VLCMediaLibraryItemProtocol> const findMediaItem, const NSUInteger idx, BOOL * const stop) {
+        NSAssert(findMediaItem != nil, @"Collection should not contain nil media items");
+        return findMediaItem.libraryID == libraryId;
+    }];
+}
+
+- (id<VLCMediaLibraryItemProtocol>)createGroupDescriptorForSection:(VLCLibraryFavoritesSection)section
+{
+    return [[VLCMediaLibraryDummyItem alloc] initWithDisplayString:[self titleForSection:section]
+                                                    withMediaItems:[self arrayForSection:section]];
+}
+
+#pragma mark - Notification handlers
+
+- (void)libraryModelFavoriteVideoMediaListReset:(NSNotification * const)notification
+{
+    [self reloadData];
+}
+
+- (void)libraryModelFavoriteAudioMediaListReset:(NSNotification * const)notification
+{
+    [self reloadData];
+}
+
+- (void)libraryModelFavoriteAlbumsListReset:(NSNotification * const)notification
+{
+    [self reloadData];
+}
+
+- (void)libraryModelFavoriteArtistsListReset:(NSNotification * const)notification
+{
+    [self reloadData];
+}
+
+- (void)libraryModelFavoriteGenresListReset:(NSNotification * const)notification
+{
+    [self reloadData];
+}
+
+#pragma mark - VLCLibraryDataSource
+
+- (void)connect
+{
+    NSNotificationCenter * const notificationCenter = NSNotificationCenter.defaultCenter;
+
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelFavoriteVideoMediaListReset:)
+                               name:VLCLibraryModelFavoriteVideoMediaListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelFavoriteAudioMediaListReset:)
+                               name:VLCLibraryModelFavoriteAudioMediaListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelFavoriteAlbumsListReset:)
+                               name:VLCLibraryModelFavoriteAlbumsListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelFavoriteArtistsListReset:)
+                               name:VLCLibraryModelFavoriteArtistsListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelFavoriteGenresListReset:)
+                               name:VLCLibraryModelFavoriteGenresListReset
+                             object:nil];
+
+    [self reloadData];
+}
+
+- (void)disconnect
+{
+    [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+- (void)reloadData
+{
+    if (!_libraryModel) {
+        return;
+    }
+
+    [_collectionViewFlowLayout resetLayout];
+
+    _favoriteVideoMediaArray = [self.libraryModel listOfFavoriteVideoMedia];
+    _favoriteAudioMediaArray = [self.libraryModel listOfFavoriteAudioMedia];
+    _favoriteAlbumsArray = [self.libraryModel listOfFavoriteAlbums];
+    _favoriteArtistsArray = [self.libraryModel listOfFavoriteArtists];
+    _favoriteGenresArray = [self.libraryModel listOfFavoriteGenres];
+
+    [self updateVisibleSectionMapping];
+
+    if (self.masterTableView.dataSource == self) {
+        [self.masterTableView reloadData];
+    }
+    if (self.detailTableView.dataSource == self) {
+        [self.detailTableView reloadData];
+    }
+    if (self.collectionView.dataSource == self) {
+        [self.collectionView reloadData];
+    }
+    
+    [NSNotificationCenter.defaultCenter postNotificationName:VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification
+                                                      object:self
+                                                    userInfo:nil];
+}
+
+#pragma mark - NSTableViewDataSource (Master-Detail View)
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
+{
+    if (tableView == self.masterTableView) {
+        return _visibleSectionMapping.count;
+    } else if (tableView == self.detailTableView && self.masterTableView.selectedRow > -1) {
+        const VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:self.masterTableView.selectedRow];
+        return [self arrayForSection:section].count;
+    }
+    
+    return 0;
+}
+
+- (id<NSPasteboardWriting>)tableView:(NSTableView *)tableView pasteboardWriterForRow:(NSInteger)row
+{
+    const id<VLCMediaLibraryItemProtocol> libraryItem = [self libraryItemAtRow:row forTableView:tableView];
+    return [NSPasteboardItem pasteboardItemWithLibraryItem:libraryItem];
+}
+
+- (id<VLCMediaLibraryItemProtocol>)libraryItemAtRow:(NSInteger)row
+                                       forTableView:(NSTableView *)tableView
+{
+    if (tableView == self.masterTableView) {
+        // For master table, return a group descriptor object
+        if (row >= 0 && row < _visibleSectionMapping.count) {
+            const VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:row];
+            return [self createGroupDescriptorForSection:section];
+        }
+    } else if (tableView == self.detailTableView && self.masterTableView.selectedRow > -1) {
+        VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:self.masterTableView.selectedRow];
+        NSArray * const sectionArray = [self arrayForSection:section];
+        if (row >= 0 && row < sectionArray.count) {
+            return sectionArray[row];
+        }
+    }
+    
+    return nil;
+}
+
+- (NSInteger)rowForLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
+{
+    if (libraryItem == nil) {
+        return NSNotFound;
+    }
+    
+    // Search through all visible sections
+    for (NSNumber * const sectionNumber in _visibleSectionMapping) {
+        const VLCLibraryFavoritesSection section = (VLCLibraryFavoritesSection)[sectionNumber integerValue];
+        NSArray * const sectionArray = [self arrayForSection:section];
+        const NSInteger index = [self indexOfMediaItem:libraryItem.libraryID inArray:sectionArray];
+        if (index != NSNotFound) {
+            return index;
+        }
+    }
+    
+    return NSNotFound;
+}
+
+- (VLCMediaLibraryParentGroupType)currentParentType
+{
+    if (self.masterTableView.selectedRow > -1) {
+        const VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:self.masterTableView.selectedRow];
+        return [self parentTypeForSection:section];
+    }
+    return VLCMediaLibraryParentGroupTypeVideoLibrary; // Default fallback
+}
+
+#pragma mark - NSCollectionViewDataSource
+
+- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView
+{
+    return _visibleSectionMapping.count;
+}
+
+- (NSInteger)collectionView:(NSCollectionView *)collectionView
+     numberOfItemsInSection:(NSInteger)section
+{
+    const VLCLibraryFavoritesSection favoritesSection = [self sectionForVisibleIndex:section];
+    return [self arrayForSection:favoritesSection].count;
+}
+
+- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
+     itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath
+{
+    VLCLibraryCollectionViewItem * const viewItem =
+        [collectionView makeItemWithIdentifier:VLCLibraryCellIdentifier forIndexPath:indexPath];
+    
+    const VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:indexPath.section];
+    const VLCMediaLibraryParentGroupType parentType = [self parentTypeForSection:section];
+    const id<VLCMediaLibraryItemProtocol> item =
+        [self libraryItemAtIndexPath:indexPath forCollectionView:collectionView];
+    
+    VLCLibraryRepresentedItem * const representedItem =
+        [[VLCLibraryRepresentedItem alloc] initWithItem:item parentType:parentType];
+    viewItem.representedItem = representedItem;
+    return viewItem;
+}
+
+- (NSView *)collectionView:(NSCollectionView *)collectionView
+viewForSupplementaryElementOfKind:(NSCollectionViewSupplementaryElementKind)kind
+               atIndexPath:(NSIndexPath *)indexPath
+{
+    if([kind isEqualToString:NSCollectionElementKindSectionHeader]) {
+        VLCLibraryCollectionViewSupplementaryElementView * const sectionHeadingView =
+            [collectionView makeSupplementaryViewOfKind:kind
+                                         withIdentifier:VLCLibrarySupplementaryElementViewIdentifier
+                                           forIndexPath:indexPath];
+        
+        const VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:indexPath.section];
+        sectionHeadingView.stringValue = [self titleForSection:section];
+        return sectionHeadingView;
+
+    } else if ([kind isEqualToString:VLCLibraryCollectionViewMediaItemSupplementaryDetailViewKind]) {
+        VLCLibraryCollectionViewMediaItemSupplementaryDetailView * const mediaItemSupplementaryDetailView = 
+            [collectionView makeSupplementaryViewOfKind:kind 
+                                         withIdentifier:VLCLibraryCollectionViewMediaItemSupplementaryDetailViewKind 
+                                           forIndexPath:indexPath];
+        
+        const id<VLCMediaLibraryItemProtocol> item = [self libraryItemAtIndexPath:indexPath forCollectionView:collectionView];
+        VLCLibraryRepresentedItem * const representedItem = [[VLCLibraryRepresentedItem alloc] initWithItem:item parentType:self.currentParentType];
+
+        mediaItemSupplementaryDetailView.representedItem = representedItem;
+        mediaItemSupplementaryDetailView.selectedItem = [collectionView itemAtIndexPath:indexPath];
+        return mediaItemSupplementaryDetailView;
+    }
+
+    return nil;
+}
+
+#pragma mark - VLCLibraryCollectionViewDataSource
+
+- (id<VLCMediaLibraryItemProtocol>)libraryItemAtIndexPath:(NSIndexPath *)indexPath
+                                        forCollectionView:(NSCollectionView *)collectionView
+{
+    const VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:indexPath.section];
+    NSArray * const sectionArray = [self arrayForSection:section];
+    
+    if (indexPath.item >= 0 && indexPath.item < sectionArray.count) {
+        return sectionArray[indexPath.item];
+    }
+    
+    return nil;
+}
+
+- (NSIndexPath *)indexPathForLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
+{
+    if (libraryItem == nil) {
+        return nil;
+    }
+    
+    // Search through all visible sections
+    for (NSUInteger visibleIndex = 0; visibleIndex < _visibleSectionMapping.count; visibleIndex++) {
+        const VLCLibraryFavoritesSection section = (VLCLibraryFavoritesSection)[_visibleSectionMapping[visibleIndex] integerValue];
+        NSArray * const sectionArray = [self arrayForSection:section];
+        const NSInteger itemIndex = [self indexOfMediaItem:libraryItem.libraryID inArray:sectionArray];
+        if (itemIndex != NSNotFound) {
+            return [NSIndexPath indexPathForItem:itemIndex inSection:visibleIndex];
+        }
+    }
+    
+    return nil;
+}
+
+- (NSArray<VLCLibraryRepresentedItem *> *)representedItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths
+                                                     forCollectionView:(NSCollectionView *)collectionView
+{
+    NSMutableArray<VLCLibraryRepresentedItem *> * const representedItems =
+        [NSMutableArray arrayWithCapacity:indexPaths.count];
+
+    for (NSIndexPath * const indexPath in indexPaths) {
+        const VLCLibraryFavoritesSection section = [self sectionForVisibleIndex:indexPath.section];
+        const VLCMediaLibraryParentGroupType parentType = [self parentTypeForSection:section];
+        const id<VLCMediaLibraryItemProtocol> libraryItem =
+            [self libraryItemAtIndexPath:indexPath forCollectionView:collectionView];
+        
+        if (libraryItem) {
+            VLCLibraryRepresentedItem * const representedItem =
+                [[VLCLibraryRepresentedItem alloc] initWithItem:libraryItem parentType:parentType];
+            [representedItems addObject:representedItem];
+        }
+    }
+
+    return representedItems;
+}
+
+- (NSString *)supplementaryDetailViewKind
+{
+    return VLCLibraryCollectionViewMediaItemSupplementaryDetailViewKind;
+}
+
+ at end


=====================================
modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesViewController.h
=====================================
@@ -0,0 +1,54 @@
+/*****************************************************************************
+ * VLCLibraryFavoritesViewController.h: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ *****************************************************************************/
+
+#import <Cocoa/Cocoa.h>
+
+#import "library/VLCLibraryAbstractMediaLibrarySegmentViewController.h"
+#import "library/VLCLibraryDataTypes.h"
+#import "library/VLCLibraryItemPresentingCapable.h"
+
+ at class VLCLibraryCollectionView;
+ at class VLCLibraryWindow;
+ at class VLCLibraryFavoritesDataSource;
+
+NS_ASSUME_NONNULL_BEGIN
+
+ at interface VLCLibraryFavoritesViewController : VLCLibraryAbstractMediaLibrarySegmentViewController<VLCLibraryItemPresentingCapable>
+
+ at property (readonly, weak) NSView *favoritesLibraryView;
+ at property (readonly, weak) NSSplitView *favoritesLibrarySplitView;
+ at property (readonly, weak) NSScrollView *favoritesLibraryCollectionViewScrollView;
+ at property (readonly, weak) VLCLibraryCollectionView *favoritesLibraryCollectionView;
+ at property (readonly, weak) NSScrollView *favoritesLibraryGroupSelectionTableViewScrollView;
+ at property (readonly, weak) NSTableView *favoritesLibraryGroupSelectionTableView;
+ at property (readonly, weak) NSScrollView *favoritesLibraryGroupsTableViewScrollView;
+ at property (readonly, weak) NSTableView *favoritesLibraryGroupsTableView;
+
+ at property (readwrite, strong) VLCLibraryFavoritesDataSource *libraryFavoritesDataSource;
+
+- (instancetype)initWithLibraryWindow:(VLCLibraryWindow *)libraryWindow;
+- (void)presentFavoritesView;
+- (void)presentLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem;
+
+ at end
+
+NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/library/favorites-library/VLCLibraryFavoritesViewController.m
=====================================
@@ -0,0 +1,363 @@
+/*****************************************************************************
+ * VLCLibraryFavoritesViewController.m MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * Authors: Claudio Cambra <developer at claudiocambra.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ *****************************************************************************/
+
+#import "VLCLibraryFavoritesViewController.h"
+
+#import "extensions/NSString+Helpers.h"
+#import "library/VLCLibraryCollectionView.h"
+#import "library/VLCLibraryCollectionViewDelegate.h"
+#import "library/VLCLibraryCollectionViewFlowLayout.h"
+#import "library/VLCLibraryCollectionViewItem.h"
+#import "library/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.h"
+#import "library/VLCLibraryCollectionViewSupplementaryElementView.h"
+#import "library/VLCLibraryController.h"
+#import "library/VLCLibraryModel.h"
+#import "library/VLCLibrarySegment.h"
+#import "library/VLCLibraryMasterDetailViewTableViewDelegate.h"
+#import "library/VLCLibraryTableCellView.h"
+#import "library/VLCLibraryTwoPaneSplitViewDelegate.h"
+#import "library/VLCLibraryUIUnits.h"
+#import "library/VLCLibraryWindow.h"
+#import "library/VLCLibraryWindowPersistentPreferences.h"
+#import "library/favorites-library/VLCLibraryFavoritesDataSource.h"
+#import "main/VLCMain.h"
+
+ at interface VLCLibraryFavoritesViewController ()
+{
+    VLCLibraryMasterDetailViewTableViewDelegate *_favoritesLibraryTableViewDelegate;
+    VLCLibraryTwoPaneSplitViewDelegate *_splitViewDelegate;
+    VLCLibraryCollectionViewDelegate *_collectionViewDelegate;
+    VLCLibraryCollectionViewFlowLayout *_collectionViewLayout;
+    NSArray<NSLayoutConstraint *> *_internalPlaceholderImageViewSizeConstraints;
+    
+    id<VLCMediaLibraryItemProtocol> _awaitingPresentingLibraryItem;
+}
+
+ at property (readwrite, weak) NSView *favoritesLibraryView;
+ at property (readwrite, weak) NSSplitView *favoritesLibrarySplitView;
+ at property (readwrite, weak) NSScrollView *favoritesLibraryCollectionViewScrollView;
+ at property (readwrite, weak) VLCLibraryCollectionView *favoritesLibraryCollectionView;
+ at property (readwrite, weak) NSScrollView *favoritesLibraryGroupSelectionTableViewScrollView;
+ at property (readwrite, weak) NSTableView *favoritesLibraryGroupSelectionTableView;
+ at property (readwrite, weak) NSScrollView *favoritesLibraryGroupsTableViewScrollView;
+ at property (readwrite, weak) NSTableView *favoritesLibraryGroupsTableView;
+
+ at end
+
+ at implementation VLCLibraryFavoritesViewController
+
+- (instancetype)initWithLibraryWindow:(VLCLibraryWindow *)libraryWindow
+{
+    self = [super initWithLibraryWindow:libraryWindow];
+    
+    if (self) {
+        _favoritesLibraryTableViewDelegate = [[VLCLibraryMasterDetailViewTableViewDelegate alloc] init];
+        _splitViewDelegate = [[VLCLibraryTwoPaneSplitViewDelegate alloc] init];
+        
+        [self setupPropertiesFromLibraryWindow:libraryWindow];
+        [self setupTableViews];
+        [self setupCollectionView];
+        [self setupFavoritesDataSource];
+        [self setupFavoritesPlaceholderView];
+        [self setupFavoritesLibraryViews];
+        [self setupNotifications];
+    }
+    
+    return self;
+}
+
+- (void)presentFavoritesView
+{
+    [self updatePresentedFavoritesView];
+}
+
+- (void)setupPropertiesFromLibraryWindow:(VLCLibraryWindow *)libraryWindow
+{
+    NSParameterAssert(libraryWindow);
+    _favoritesLibraryView = libraryWindow.videoLibraryView;
+    _favoritesLibrarySplitView = libraryWindow.videoLibrarySplitView;
+    _favoritesLibraryCollectionViewScrollView = libraryWindow.videoLibraryCollectionViewScrollView;
+    _favoritesLibraryCollectionView = libraryWindow.videoLibraryCollectionView;
+    _favoritesLibraryGroupSelectionTableViewScrollView = libraryWindow.videoLibraryGroupSelectionTableViewScrollView;
+    _favoritesLibraryGroupSelectionTableView = libraryWindow.videoLibraryGroupSelectionTableView;
+    _favoritesLibraryGroupsTableViewScrollView = libraryWindow.videoLibraryGroupsTableViewScrollView;
+    _favoritesLibraryGroupsTableView = libraryWindow.videoLibraryGroupsTableView;
+}
+
+- (void)setupTableViews
+{
+    self.favoritesLibrarySplitView.delegate = _splitViewDelegate;
+    [_splitViewDelegate resetDefaultSplitForSplitView:self.favoritesLibrarySplitView];
+
+    NSNib * const tableCellViewNib =
+        [[NSNib alloc] initWithNibNamed:NSStringFromClass(VLCLibraryTableCellView.class)
+                                 bundle:nil];
+    [self.favoritesLibraryGroupsTableView registerNib:tableCellViewNib
+                                        forIdentifier:@"VLCLibraryTableViewCellIdentifier"];
+    [self.favoritesLibraryGroupSelectionTableView registerNib:tableCellViewNib 
+                                                forIdentifier:@"VLCLibraryTableViewCellIdentifier"];
+}
+
+- (void)setupCollectionView
+{
+    _collectionViewLayout = [[VLCLibraryCollectionViewFlowLayout alloc] init];
+    
+    const CGFloat collectionItemSpacing = VLCLibraryUIUnits.collectionViewItemSpacing;
+    const NSEdgeInsets collectionViewSectionInset = VLCLibraryUIUnits.collectionViewSectionInsets;
+    _collectionViewLayout.headerReferenceSize = VLCLibraryCollectionViewSupplementaryElementView.defaultHeaderSize;
+    _collectionViewLayout.minimumLineSpacing = collectionItemSpacing;
+    _collectionViewLayout.minimumInteritemSpacing = collectionItemSpacing;
+    _collectionViewLayout.sectionInset = collectionViewSectionInset;
+    
+    NSCollectionView * const collectionView = self.favoritesLibraryCollectionView;
+    collectionView.collectionViewLayout = _collectionViewLayout;
+    
+    _collectionViewDelegate = [[VLCLibraryCollectionViewDelegate alloc] init];
+    collectionView.delegate = _collectionViewDelegate;
+    
+    [collectionView registerClass:VLCLibraryCollectionViewItem.class
+            forItemWithIdentifier:VLCLibraryCellIdentifier];
+    
+    [collectionView registerClass:VLCLibraryCollectionViewSupplementaryElementView.class
+       forSupplementaryViewOfKind:NSCollectionElementKindSectionHeader
+                   withIdentifier:VLCLibrarySupplementaryElementViewIdentifier];
+    
+    NSString * const mediaItemSupplementaryDetailViewString =
+        NSStringFromClass(VLCLibraryCollectionViewMediaItemSupplementaryDetailView.class);
+    NSNib * const mediaItemSupplementaryDetailViewNib =
+        [[NSNib alloc] initWithNibNamed:mediaItemSupplementaryDetailViewString bundle:nil];
+    
+    [collectionView registerNib:mediaItemSupplementaryDetailViewNib
+     forSupplementaryViewOfKind:VLCLibraryCollectionViewMediaItemSupplementaryDetailViewKind
+                 withIdentifier:VLCLibraryCollectionViewMediaItemSupplementaryDetailViewIdentifier];
+}
+
+- (void)setupFavoritesDataSource
+{
+    _libraryFavoritesDataSource = [[VLCLibraryFavoritesDataSource alloc] init];
+    self.libraryFavoritesDataSource.libraryModel = VLCMain.sharedInstance.libraryController.libraryModel;
+    self.libraryFavoritesDataSource.collectionView = self.favoritesLibraryCollectionView;
+    self.libraryFavoritesDataSource.masterTableView = self.favoritesLibraryGroupsTableView;
+    self.libraryFavoritesDataSource.detailTableView = self.favoritesLibraryGroupSelectionTableView;
+}
+
+- (void)setupFavoritesLibraryViews
+{
+    _favoritesLibraryGroupsTableView.rowHeight = VLCLibraryUIUnits.mediumTableViewRowHeight;
+    _favoritesLibraryGroupSelectionTableView.rowHeight = VLCLibraryUIUnits.mediumTableViewRowHeight;
+
+    const NSEdgeInsets defaultInsets = VLCLibraryUIUnits.libraryViewScrollViewContentInsets;
+    const NSEdgeInsets scrollerInsets = VLCLibraryUIUnits.libraryViewScrollViewScrollerInsets;
+
+    _favoritesLibraryCollectionViewScrollView.automaticallyAdjustsContentInsets = NO;
+    _favoritesLibraryCollectionViewScrollView.contentInsets = defaultInsets;
+    _favoritesLibraryCollectionViewScrollView.scrollerInsets = scrollerInsets;
+
+    _favoritesLibraryGroupsTableViewScrollView.automaticallyAdjustsContentInsets = NO;
+    _favoritesLibraryGroupsTableViewScrollView.contentInsets = defaultInsets;
+    _favoritesLibraryGroupsTableViewScrollView.scrollerInsets = scrollerInsets;
+    _favoritesLibraryGroupSelectionTableViewScrollView.automaticallyAdjustsContentInsets = NO;
+    _favoritesLibraryGroupSelectionTableViewScrollView.contentInsets = defaultInsets;
+    _favoritesLibraryGroupSelectionTableViewScrollView.scrollerInsets = scrollerInsets;
+}
+
+- (void)setupFavoritesPlaceholderView
+{
+    _internalPlaceholderImageViewSizeConstraints = @[
+        [NSLayoutConstraint constraintWithItem:self.placeholderImageView
+                                     attribute:NSLayoutAttributeWidth
+                                     relatedBy:NSLayoutRelationEqual
+                                        toItem:nil
+                                     attribute:NSLayoutAttributeNotAnAttribute
+                                    multiplier:0.f
+                                      constant:182.f],
+        [NSLayoutConstraint constraintWithItem:self.placeholderImageView
+                                     attribute:NSLayoutAttributeHeight
+                                     relatedBy:NSLayoutRelationEqual
+                                        toItem:nil
+                                     attribute:NSLayoutAttributeNotAnAttribute
+                                    multiplier:0.f
+                                      constant:114.f],
+    ];
+}
+
+- (void)setupNotifications
+{
+    NSNotificationCenter *notificationCenter = NSNotificationCenter.defaultCenter;
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelUpdated:)
+                               name:VLCLibraryModelFavoriteVideoMediaListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelUpdated:)
+                               name:VLCLibraryModelFavoriteAudioMediaListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelUpdated:)
+                               name:VLCLibraryModelFavoriteAlbumsListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelUpdated:)
+                               name:VLCLibraryModelFavoriteArtistsListReset
+                             object:nil];
+    [notificationCenter addObserver:self
+                           selector:@selector(libraryModelUpdated:)
+                               name:VLCLibraryModelFavoriteGenresListReset
+                             object:nil];
+}
+
+#pragma mark - VLCLibraryAbstractMediaLibrarySegmentViewController
+
+- (NSArray<NSLayoutConstraint *> *)placeholderImageViewSizeConstraints
+{
+    return _internalPlaceholderImageViewSizeConstraints;
+}
+
+- (id<VLCLibraryDataSource>)currentDataSource
+{
+    return self.libraryFavoritesDataSource;
+}
+
+#pragma mark - Public methods
+
+- (void)updatePresentedFavoritesView
+{
+    self.favoritesLibraryCollectionView.dataSource = self.libraryFavoritesDataSource;
+    
+    self.favoritesLibraryGroupsTableView.dataSource = self.libraryFavoritesDataSource;
+    self.favoritesLibraryGroupsTableView.target = self.libraryFavoritesDataSource;
+    self.favoritesLibraryGroupsTableView.delegate = _favoritesLibraryTableViewDelegate;
+
+    self.favoritesLibraryGroupSelectionTableView.dataSource = self.libraryFavoritesDataSource;
+    self.favoritesLibraryGroupSelectionTableView.target = self.libraryFavoritesDataSource;
+    self.favoritesLibraryGroupSelectionTableView.delegate = _favoritesLibraryTableViewDelegate;
+    
+    [self.libraryFavoritesDataSource reloadData];
+    
+    if ([self hasFavoriteItems]) {
+        const VLCLibraryViewModeSegment viewModeSegment = VLCLibraryWindowPersistentPreferences.sharedInstance.favoritesLibraryViewMode;
+        [self presentFavoritesLibraryView:viewModeSegment];
+    } else if (self.libraryFavoritesDataSource.libraryModel.filterString.length > 0) {
+        [self.libraryWindow displayNoResultsMessage];
+    } else {
+        [self presentPlaceholderFavoritesView];
+    }
+}
+
+- (BOOL)hasFavoriteItems
+{
+    VLCLibraryModel * const libraryModel = self.libraryFavoritesDataSource.libraryModel;
+    return libraryModel.numberOfFavoriteVideoMedia > 0 ||
+           libraryModel.numberOfFavoriteAudioMedia > 0 ||
+           libraryModel.numberOfFavoriteAlbums > 0 ||
+           libraryModel.numberOfFavoriteArtists > 0 ||
+           libraryModel.numberOfFavoriteGenres > 0;
+}
+
+- (void)presentFavoritesCollectionView
+{
+    [self.libraryWindow displayLibraryView:self.favoritesLibraryView];
+    self.favoritesLibraryCollectionViewScrollView.hidden = NO;
+}
+
+- (void)presentPlaceholderFavoritesView
+{
+    [self.libraryWindow displayLibraryPlaceholderViewWithImage:[NSImage imageNamed:@"placeholder-favorites"]
+                                              usingConstraints:self.placeholderImageViewSizeConstraints
+                                             displayingMessage:_NS("Your favorite items will appear here.\nMark items as favorites to see them in this view.")];
+}
+
+- (void)presentFavoritesLibraryView:(VLCLibraryViewModeSegment)viewModeSegment
+{
+    [self.libraryWindow displayLibraryView:self.favoritesLibraryView];
+    if (viewModeSegment == VLCLibraryGridViewModeSegment) {
+        self.favoritesLibrarySplitView.hidden = YES;
+        self.favoritesLibraryCollectionViewScrollView.hidden = NO;
+    } else if (viewModeSegment == VLCLibraryListViewModeSegment) {
+        self.favoritesLibrarySplitView.hidden = NO;
+        self.favoritesLibraryCollectionViewScrollView.hidden = YES;
+    } else {
+        NSAssert(false, @"View mode must be grid or list mode");
+    }
+}
+
+- (void)presentLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
+{
+    if (libraryItem == nil) {
+        return;
+    }
+
+    _awaitingPresentingLibraryItem = libraryItem;
+
+    const VLCLibraryViewModeSegment viewModeSegment = VLCLibraryWindowPersistentPreferences.sharedInstance.favoritesLibraryViewMode;
+
+    if (viewModeSegment == VLCLibraryGridViewModeSegment) {
+        [NSNotificationCenter.defaultCenter addObserver:self
+                                               selector:@selector(presentLibraryItemWaitForCollectionViewDataSourceFinished:)
+                                                   name:VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification
+                                                 object:self.libraryFavoritesDataSource];
+    } else if (viewModeSegment == VLCLibraryListViewModeSegment) {
+        [NSNotificationCenter.defaultCenter addObserver:self
+                                               selector:@selector(presentLibraryItemWaitForTableViewDataSourceFinished:)
+                                                   name:VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification
+                                                 object:self.libraryFavoritesDataSource];
+    } else {
+        NSAssert(false, @"View mode must be grid or list mode");
+    }
+}
+
+- (void)presentLibraryItemWaitForCollectionViewDataSourceFinished:(NSNotification *)notification
+{
+    [NSNotificationCenter.defaultCenter removeObserver:self
+                                                  name:VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification
+                                                object:self.libraryFavoritesDataSource];
+
+    _awaitingPresentingLibraryItem = nil;
+}
+
+- (void)presentLibraryItemWaitForTableViewDataSourceFinished:(NSNotification *)notification
+{
+    [NSNotificationCenter.defaultCenter removeObserver:self
+                                                  name:VLCLibraryFavoritesDataSourceDisplayedCollectionChangedNotification
+                                                object:self.libraryFavoritesDataSource];
+
+    const NSInteger rowForLibraryItem = [self.libraryFavoritesDataSource rowForLibraryItem:_awaitingPresentingLibraryItem];
+    if (rowForLibraryItem != NSNotFound) {
+        NSIndexSet * const indexSet = [NSIndexSet indexSetWithIndex:rowForLibraryItem];
+        [self.favoritesLibraryGroupsTableView selectRowIndexes:indexSet byExtendingSelection:NO];
+        [self.favoritesLibraryGroupsTableView scrollRowToVisible:rowForLibraryItem];
+    }
+
+    _awaitingPresentingLibraryItem = nil;
+}
+
+#pragma mark - Notification handlers
+
+- (void)libraryModelUpdated:(NSNotification *)notification
+{
+    NSParameterAssert(notification);
+    if (self.libraryWindow.librarySegmentType == VLCLibraryFavoritesSegmentType) {
+        [self updatePresentedFavoritesView];
+    }
+}
+
+ at end


=====================================
modules/misc/medialibrary/medialibrary.cpp
=====================================
@@ -793,6 +793,14 @@ int MediaLibrary::Control( int query, va_list args )
                 return VLC_EGENERIC;
             if ( folder->setFavorite( favorite ) == false )
                 return VLC_EGENERIC;
+            
+            vlc_ml_event_t ev;
+            ev.i_type = VLC_ML_EVENT_FAVORITES_CHANGED;
+            ev.favorites_changed.i_entity_id = folder->id();
+            ev.favorites_changed.i_entity_type = VLC_ML_PARENT_FOLDER;
+            ev.favorites_changed.b_favorite = favorite;
+            m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev );
+            
             return VLC_SUCCESS;
         }
         case VLC_ML_ARTIST_SET_FAVORITE:
@@ -804,6 +812,14 @@ int MediaLibrary::Control( int query, va_list args )
                 return VLC_EGENERIC;
             if ( artist->setFavorite( favorite ) == false )
                 return VLC_EGENERIC;
+            
+            vlc_ml_event_t ev;
+            ev.i_type = VLC_ML_EVENT_FAVORITES_CHANGED;
+            ev.favorites_changed.i_entity_id = artistId;
+            ev.favorites_changed.i_entity_type = VLC_ML_PARENT_ARTIST;
+            ev.favorites_changed.b_favorite = favorite;
+            m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev );
+            
             return VLC_SUCCESS;
         }
         case VLC_ML_ALBUM_SET_FAVORITE:
@@ -815,6 +831,14 @@ int MediaLibrary::Control( int query, va_list args )
                 return VLC_EGENERIC;
             if ( album->setFavorite( favorite ) == false )
                 return VLC_EGENERIC;
+            
+            vlc_ml_event_t ev;
+            ev.i_type = VLC_ML_EVENT_FAVORITES_CHANGED;
+            ev.favorites_changed.i_entity_id = albumId;
+            ev.favorites_changed.i_entity_type = VLC_ML_PARENT_ALBUM;
+            ev.favorites_changed.b_favorite = favorite;
+            m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev );
+            
             return VLC_SUCCESS;
         }
         case VLC_ML_GENRE_SET_FAVORITE:
@@ -826,6 +850,14 @@ int MediaLibrary::Control( int query, va_list args )
                 return VLC_EGENERIC;
             if ( genre->setFavorite( favorite ) == false)
                 return VLC_EGENERIC;
+            
+            vlc_ml_event_t ev;
+            ev.i_type = VLC_ML_EVENT_FAVORITES_CHANGED;
+            ev.favorites_changed.i_entity_id = genreId;
+            ev.favorites_changed.i_entity_type = VLC_ML_PARENT_GENRE;
+            ev.favorites_changed.b_favorite = favorite;
+            m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev );
+            
             return VLC_SUCCESS;
         }
         case VLC_ML_PLAYLIST_SET_FAVORITE:
@@ -837,6 +869,14 @@ int MediaLibrary::Control( int query, va_list args )
                 return VLC_EGENERIC;
             if ( playlist->setFavorite( favorite ) == false )
                 return VLC_EGENERIC;
+            
+            vlc_ml_event_t ev;
+            ev.i_type = VLC_ML_EVENT_FAVORITES_CHANGED;
+            ev.favorites_changed.i_entity_id = playlistId;
+            ev.favorites_changed.i_entity_type = VLC_ML_PARENT_PLAYLIST;
+            ev.favorites_changed.b_favorite = favorite;
+            m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev );
+            
             return VLC_SUCCESS;
         }
         default:
@@ -1247,6 +1287,246 @@ int MediaLibrary::List( int listQuery, const vlc_ml_query_params_t* params, va_l
             *( va_arg( args, size_t* ) ) = query ? query->count() : 0;
             break;
         }
+        case VLC_ML_LIST_FAVORITE_MEDIA:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IMedia> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchMedia( psz_pattern, &favParams );
+            else
+                query = m_ml->mediaFiles( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_media_list_t, vlc_ml_media_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_media_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_MEDIA:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IMedia> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchMedia( psz_pattern, &favParams );
+            else
+                query = m_ml->mediaFiles( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
+        case VLC_ML_LIST_FAVORITE_VIDEOS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IMedia> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchVideo( psz_pattern, &favParams );
+            else
+                query = m_ml->videoFiles( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_media_list_t, vlc_ml_media_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_media_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_VIDEOS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IMedia> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchVideo( psz_pattern, &favParams );
+            else
+                query = m_ml->videoFiles( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
+        case VLC_ML_LIST_FAVORITE_AUDIOS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IMedia> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchAudio( psz_pattern, &favParams );
+            else
+                query = m_ml->audioFiles( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_media_list_t, vlc_ml_media_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_media_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_AUDIOS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IMedia> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchAudio( psz_pattern, &favParams );
+            else
+                query = m_ml->audioFiles( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
+        case VLC_ML_LIST_FAVORITE_ALBUMS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IAlbum> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchAlbums( psz_pattern, &favParams );
+            else
+                query = m_ml->albums( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_album_list_t, vlc_ml_album_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_album_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_ALBUMS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IAlbum> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchAlbums( psz_pattern, &favParams );
+            else
+                query = m_ml->albums( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
+        case VLC_ML_LIST_FAVORITE_ARTISTS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IArtist> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchArtists( psz_pattern, medialibrary::ArtistIncluded::All, &favParams );
+            else
+                query = m_ml->artists( medialibrary::ArtistIncluded::All, &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_artist_list_t, vlc_ml_artist_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_artist_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_ARTISTS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IArtist> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchArtists( psz_pattern, medialibrary::ArtistIncluded::All, &favParams );
+            else
+                query = m_ml->artists( medialibrary::ArtistIncluded::All, &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
+        case VLC_ML_LIST_FAVORITE_GENRES:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IGenre> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchGenre( psz_pattern, &favParams );
+            else
+                query = m_ml->genres( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_genre_list_t,vlc_ml_genre_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_genre_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_GENRES:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IGenre> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchGenre( psz_pattern, &favParams );
+            else
+                query = m_ml->genres( &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
+        case VLC_ML_LIST_FAVORITE_PLAYLISTS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IPlaylist> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchPlaylists( psz_pattern, medialibrary::PlaylistType::All, &favParams );
+            else
+                query = m_ml->playlists( medialibrary::PlaylistType::All, &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_playlist_list_t, vlc_ml_playlist_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_playlist_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_PLAYLISTS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IPlaylist> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchPlaylists( psz_pattern, medialibrary::PlaylistType::All, &favParams );
+            else
+                query = m_ml->playlists( medialibrary::PlaylistType::All, &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
+        case VLC_ML_LIST_FAVORITE_FOLDERS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IFolder> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchFolders( psz_pattern, medialibrary::IMedia::Type::Unknown, &favParams );
+            else
+                query = m_ml->folders( medialibrary::IMedia::Type::Unknown, &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            auto res = ml_convert_list<vlc_ml_folder_list_t, vlc_ml_folder_t>(
+                        query->items( nbItems, offset ) );
+            *va_arg( args, vlc_ml_folder_list_t**) = res;
+            break;
+        }
+        case VLC_ML_COUNT_FAVORITE_FOLDERS:
+        {
+            medialibrary::QueryParameters favParams = paramsPtr ? *paramsPtr : medialibrary::QueryParameters{};
+            favParams.favoriteOnly = true;
+            medialibrary::Query<medialibrary::IFolder> query;
+            if ( psz_pattern != nullptr )
+                query = m_ml->searchFolders( psz_pattern, medialibrary::IMedia::Type::Unknown, &favParams );
+            else
+                query = m_ml->folders( medialibrary::IMedia::Type::Unknown, &favParams );
+            if ( query == nullptr )
+                return VLC_EGENERIC;
+            *va_arg( args, size_t* ) = query->count();
+            break;
+        }
         case VLC_ML_LIST_SUBFOLDERS:
         {
             const auto parent = m_ml->folder( va_arg( args, int64_t ) );
@@ -1640,6 +1920,14 @@ int MediaLibrary::controlMedia( int query, va_list args )
             bool favorite = va_arg( args, int );
             if ( m->setFavorite( favorite ) == false )
                 return VLC_EGENERIC;
+            
+            vlc_ml_event_t ev;
+            ev.i_type = VLC_ML_EVENT_FAVORITES_CHANGED;
+            ev.favorites_changed.i_entity_id = mediaId;
+            ev.favorites_changed.i_entity_type = VLC_ML_PARENT_UNKNOWN; // Media doesn't have a parent type in VLC_ML_PARENT_*
+            ev.favorites_changed.b_favorite = favorite;
+            m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev );
+            
             return VLC_SUCCESS;
         }
         case VLC_ML_MEDIA_ADD_BOOKMARK:



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/d913e5c0a5625f930c8ef8fc67aa2524637d973e...8d0a5ccc2a5b32d92827739a263477bf8504377b

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/d913e5c0a5625f930c8ef8fc67aa2524637d973e...8d0a5ccc2a5b32d92827739a263477bf8504377b
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list