[vlc-commits] [Git][videolan/vlc][master] 24 commits: macosx: Cache a dictionary of artist id and artist name in library model

Steve Lhomme (@robUx4) gitlab at videolan.org
Thu Nov 16 18:17:12 UTC 2023



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
5cdd08b0 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Cache a dictionary of artist id and artist name in library model

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

- - - - -
4f8c4b4f by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Cache album name for album id in library model

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

- - - - -
48b0ef2e by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Cache genre name with its id in VLCLibraryModel

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

- - - - -
97aee68c by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Rename detailString property to primaryDetailString property

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

- - - - -
74c94c74 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Lazily load and store genres for audio library groups

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

- - - - -
e06346ef by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Rename actionable detail properties to primary actionable details

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

- - - - -
13a1538c by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Add function to generate display string from genre array

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

- - - - -
a4cf3d69 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Add genreString property to artists and albums

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

- - - - -
1fa2686c by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Cache primary detail string after initial lazy load for VLCMediaLibraryMediaItem

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

- - - - -
30f7d9c7 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Generate contextual secondary detail string for VLCMediaLibraryMediaItems

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

- - - - -
818df221 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Add secondary label parameter to VLCMediaLibraryDummyItem

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

- - - - -
bff5b048 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Add secondaryActionableDetail properties to VLCMediaLibraryItemProtocol

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

- - - - -
ec3d6b57 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Fix crash for genreString when genres nil

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

- - - - -
32b52c4e by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Implement secondary actionable details in audio group types

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

- - - - -
91c0c0e6 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Implement secondary actionable detail in VLCMediaLibraryMediaItem

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

- - - - -
5f37f7d4 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Set null secondary detail on VLCMediaLibraryDummyItem

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

- - - - -
92d39f7d by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Add secondary detail button in VLCLibraryCollectionViewMediaItemSupplementaryDetailView XIB

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

- - - - -
d06a28ae by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Rename detail button to primary detail button in media item supplementary detail view

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

- - - - -
5883992f by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Implement support for secondary label in VLCLibraryCollectionViewMediaItemSupplementaryDetaiLView

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

- - - - -
31826b40 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Rename detail stuff to primary detail stuff in VLCLibraryCollectionViewAlbumSupplementaryDetailView

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

- - - - -
7993da80 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Add secondary detail action button to VLCLibraryCollectionViewAlbumSupplementaryDetailView XIB

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

- - - - -
f3dec496 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Implement backend for secondary detail button in VLCLibraryCollectionViewAlbumSupplementaryDetailView

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

- - - - -
fdca9b9b by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Properly truncate and elide text buttons in album and media item supplementary views

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

- - - - -
e0dee3c4 by Claudio Cambra at 2023-11-16T17:57:00+00:00
macosx: Add genre button to VLCLibraryAlbumTableCellView

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

- - - - -


19 changed files:

- modules/gui/macosx/UI/VLCLibraryAlbumTableCellView.xib
- modules/gui/macosx/UI/VLCLibraryCollectionViewAlbumSupplementaryDetailView.xib
- modules/gui/macosx/UI/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.xib
- modules/gui/macosx/library/VLCLibraryCollectionViewItem.m
- modules/gui/macosx/library/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.h
- modules/gui/macosx/library/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.m
- modules/gui/macosx/library/VLCLibraryDataTypes.h
- modules/gui/macosx/library/VLCLibraryDataTypes.m
- modules/gui/macosx/library/VLCLibraryHeroView.m
- modules/gui/macosx/library/VLCLibraryModel.h
- modules/gui/macosx/library/VLCLibraryModel.m
- modules/gui/macosx/library/VLCLibraryTableCellView.m
- modules/gui/macosx/library/audio-library/VLCLibraryAlbumTableCellView.h
- modules/gui/macosx/library/audio-library/VLCLibraryAlbumTableCellView.m
- modules/gui/macosx/library/audio-library/VLCLibraryAllAudioGroupsMediaLibraryItem.m
- modules/gui/macosx/library/audio-library/VLCLibraryAudioGroupHeaderView.m
- modules/gui/macosx/library/audio-library/VLCLibraryCollectionViewAlbumSupplementaryDetailView.h
- modules/gui/macosx/library/audio-library/VLCLibraryCollectionViewAlbumSupplementaryDetailView.m
- modules/gui/macosx/windows/mainwindow/VLCMainVideoViewControlsBar.m


Changes:

=====================================
modules/gui/macosx/UI/VLCLibraryAlbumTableCellView.xib
=====================================
@@ -35,25 +35,38 @@
                                 <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                             </textFieldCell>
                         </textField>
-                        <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kvo-td-z48">
+                        <button verticalHuggingPriority="750" horizontalCompressionResistancePriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kvo-td-z48">
                             <rect key="frame" x="144" y="386" width="92" height="19"/>
-                            <buttonCell key="cell" type="bevel" title="Album artist" bezelStyle="rounded" alignment="left" imageScaling="proportionallyDown" inset="2" id="WVL-LH-ZiA">
+                            <buttonCell key="cell" type="bevel" title="Album artist" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" truncatesLastVisibleLine="YES" imageScaling="proportionallyDown" inset="2" id="WVL-LH-ZiA">
                                 <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                 <font key="font" metaFont="systemSemibold" size="15"/>
                             </buttonCell>
                             <color key="contentTintColor" name="VLCAccentColor"/>
                             <connections>
-                                <action selector="detailAction:" target="c22-O7-iKe" id="Onc-S8-WGY"/>
+                                <action selector="primaryDetailAction:" target="c22-O7-iKe" id="Jo0-n2-tXc"/>
+                            </connections>
+                        </button>
+                        <button horizontalHuggingPriority="249" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="VRD-00-4LN">
+                            <rect key="frame" x="241" y="386" width="96" height="19"/>
+                            <buttonCell key="cell" type="bevel" title="Album genre" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" truncatesLastVisibleLine="YES" imageScaling="proportionallyDown" inset="2" id="GDe-oq-fGP">
+                                <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                <font key="font" metaFont="systemSemibold" size="15"/>
+                            </buttonCell>
+                            <color key="contentTintColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
+                            <connections>
+                                <action selector="secondaryDetailAction:" target="c22-O7-iKe" id="9UB-E4-P5J"/>
                             </connections>
                         </button>
                     </subviews>
                     <constraints>
+                        <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="VRD-00-4LN" secondAttribute="trailing" constant="20" id="1LW-wp-fJ6"/>
                         <constraint firstItem="lyR-U9-HKd" firstAttribute="top" secondItem="dd9-b1-XEf" secondAttribute="bottom" constant="5" id="36N-Cq-Zjf"/>
-                        <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kvo-td-z48" secondAttribute="trailing" constant="20" id="Wg0-Tn-f2L"/>
+                        <constraint firstItem="VRD-00-4LN" firstAttribute="centerY" secondItem="kvo-td-z48" secondAttribute="centerY" id="5Va-uv-a7Y"/>
+                        <constraint firstItem="VRD-00-4LN" firstAttribute="leading" secondItem="kvo-td-z48" secondAttribute="trailing" constant="5" id="QSz-5D-E9n"/>
                         <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="lyR-U9-HKd" secondAttribute="trailing" constant="20" id="dzL-hG-1nc"/>
                     </constraints>
                 </customView>
-                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xJW-ps-ycn">
+                <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xJW-ps-ycn">
                     <rect key="frame" x="142" y="410" width="104" height="20"/>
                     <textFieldCell key="cell" lineBreakMode="clipping" title="Album name" id="aCe-ia-0Ww">
                         <font key="font" metaFont="systemSemibold" size="17"/>
@@ -102,6 +115,7 @@
             <connections>
                 <outlet property="albumNameTextField" destination="xJW-ps-ycn" id="nX9-SH-RZA"/>
                 <outlet property="artistNameTextButton" destination="kvo-td-z48" id="U94-du-RqP"/>
+                <outlet property="genreNameTextButton" destination="VRD-00-4LN" id="fqy-F2-oit"/>
                 <outlet property="playInstantlyButton" destination="KVh-Zn-l7I" id="Ri1-YF-Fe2"/>
                 <outlet property="representedImageView" destination="Ydb-7n-5Cd" id="qBu-r5-jIY"/>
                 <outlet property="summaryTextField" destination="dd9-b1-XEf" id="ne9-oA-zPw"/>


=====================================
modules/gui/macosx/UI/VLCLibraryCollectionViewAlbumSupplementaryDetailView.xib
=====================================
@@ -29,7 +29,7 @@
                                 <stackView distribution="fillEqually" orientation="horizontal" alignment="top" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6Nc-Mb-wDa">
                                     <rect key="frame" x="0.0" y="0.0" width="237" height="28"/>
                                     <subviews>
-                                        <button verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ntd-VT-2KS">
+                                        <button imageHugsTitle="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ntd-VT-2KS">
                                             <rect key="frame" x="-6" y="-6" width="128" height="40"/>
                                             <buttonCell key="cell" type="push" title=" Play" bezelStyle="rounded" image="play.fill" catalog="system" imagePosition="left" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Rgc-Uk-A3h">
                                                 <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@@ -40,7 +40,7 @@
                                                 <action selector="playAction:" target="HAc-or-XD8" id="edJ-21-Ej1"/>
                                             </connections>
                                         </button>
-                                        <button verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="idL-3a-QaA">
+                                        <button imageHugsTitle="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="idL-3a-QaA">
                                             <rect key="frame" x="115" y="-6" width="128" height="40"/>
                                             <buttonCell key="cell" type="push" title=" Enqueue" bezelStyle="rounded" image="plus" catalog="system" imagePosition="left" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="igY-iu-P0N">
                                                 <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@@ -81,7 +81,7 @@
                         <stackView distribution="fill" orientation="vertical" alignment="leading" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bw7-QB-Ssc">
                             <rect key="frame" x="257" y="0.0" width="794" height="282"/>
                             <subviews>
-                                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="nCe-dY-YMM">
+                                <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="nCe-dY-YMM">
                                     <rect key="frame" x="-2" y="256" width="124" height="26"/>
                                     <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                                     <textFieldCell key="cell" lineBreakMode="truncatingTail" title="Album name" id="6RM-x8-Y4y">
@@ -94,18 +94,29 @@
                                     <rect key="frame" x="0.0" y="203" width="794" height="48"/>
                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
                                     <subviews>
-                                        <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="o9Q-0s-xRU">
-                                            <rect key="frame" x="0.0" y="0.0" width="154" height="48"/>
-                                            <buttonCell key="cell" type="bevel" title="Album detail string" bezelStyle="rounded" alignment="left" imageScaling="proportionallyDown" inset="2" id="SqJ-99-uiI">
+                                        <button clipsToBounds="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="o9Q-0s-xRU">
+                                            <rect key="frame" x="0.0" y="0.0" width="220" height="48"/>
+                                            <buttonCell key="cell" type="bevel" title="Album primary detail string" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" truncatesLastVisibleLine="YES" imageScaling="proportionallyDown" inset="2" id="SqJ-99-uiI">
                                                 <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                                 <font key="font" metaFont="systemSemibold" size="17"/>
                                             </buttonCell>
                                             <color key="contentTintColor" name="VLCAccentColor"/>
                                             <connections>
-                                                <action selector="detailAction:" target="HAc-or-XD8" id="gEr-Qz-9KQ"/>
+                                                <action selector="primaryDetailAction:" target="HAc-or-XD8" id="Afg-sr-HiT"/>
                                             </connections>
                                         </button>
-                                        <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QuO-3G-BMT">
+                                        <button verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="jNq-xn-yIp">
+                                            <rect key="frame" x="225" y="0.0" width="242" height="48"/>
+                                            <buttonCell key="cell" type="bevel" title="Album secondary detail string" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" truncatesLastVisibleLine="YES" imageScaling="proportionallyDown" inset="2" id="3xV-LQ-O7O">
+                                                <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                                <font key="font" metaFont="systemSemibold" size="17"/>
+                                            </buttonCell>
+                                            <color key="contentTintColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
+                                            <connections>
+                                                <action selector="secondaryDetailAction:" target="HAc-or-XD8" id="cDV-Wr-lQD"/>
+                                            </connections>
+                                        </button>
+                                        <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QuO-3G-BMT">
                                             <rect key="frame" x="687" y="16" width="94" height="16"/>
                                             <textFieldCell key="cell" lineBreakMode="clipping" title="Year · Duration" id="JBg-wo-ZeE">
                                                 <font key="font" metaFont="system"/>
@@ -115,30 +126,33 @@
                                         </textField>
                                     </subviews>
                                     <constraints>
+                                        <constraint firstItem="jNq-xn-yIp" firstAttribute="leading" secondItem="o9Q-0s-xRU" secondAttribute="trailing" constant="5" id="5IX-1t-Psn"/>
+                                        <constraint firstItem="jNq-xn-yIp" firstAttribute="trailing" relation="lessThanOrEqual" secondItem="QuO-3G-BMT" secondAttribute="leading" constant="-5" id="88t-Jp-57R"/>
                                         <constraint firstItem="o9Q-0s-xRU" firstAttribute="top" secondItem="SvO-zd-2zo" secondAttribute="top" id="QuO-Ev-i98"/>
-                                        <constraint firstItem="o9Q-0s-xRU" firstAttribute="trailing" relation="lessThanOrEqual" secondItem="QuO-3G-BMT" secondAttribute="leading" id="R8I-I6-uGg"/>
                                         <constraint firstItem="QuO-3G-BMT" firstAttribute="centerY" secondItem="SvO-zd-2zo" secondAttribute="centerY" id="TX6-cF-dnh"/>
                                         <constraint firstAttribute="trailing" secondItem="QuO-3G-BMT" secondAttribute="trailing" constant="15" id="Wfo-47-OGd"/>
+                                        <constraint firstItem="jNq-xn-yIp" firstAttribute="top" secondItem="SvO-zd-2zo" secondAttribute="top" id="eBH-UW-wtK"/>
                                         <constraint firstAttribute="bottom" secondItem="o9Q-0s-xRU" secondAttribute="bottom" id="lvh-r5-QzT"/>
+                                        <constraint firstAttribute="bottom" secondItem="jNq-xn-yIp" secondAttribute="bottom" id="msf-KD-aoJ"/>
                                         <constraint firstItem="o9Q-0s-xRU" firstAttribute="leading" secondItem="SvO-zd-2zo" secondAttribute="leading" id="nYc-fo-IyN"/>
                                     </constraints>
                                 </customView>
                                 <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="52" horizontalPageScroll="10" verticalLineScroll="52" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" id="9ZS-oy-iP9" customClass="VLCSubScrollView">
-                                    <rect key="frame" x="0.0" y="0.0" width="701" height="198"/>
+                                    <rect key="frame" x="0.0" y="0.0" width="699" height="198"/>
                                     <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
                                     <clipView key="contentView" drawsBackground="NO" id="3V4-tX-owM">
-                                        <rect key="frame" x="0.0" y="0.0" width="701" height="198"/>
+                                        <rect key="frame" x="0.0" y="0.0" width="699" height="198"/>
                                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                         <subviews>
                                             <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" tableStyle="fullWidth" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="50" viewBased="YES" id="eEJ-WA-0aM" customClass="VLCLibraryTableView">
-                                                <rect key="frame" x="0.0" y="0.0" width="701" height="188"/>
+                                                <rect key="frame" x="0.0" y="0.0" width="699" height="188"/>
                                                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                                 <size key="intercellSpacing" width="3" height="2"/>
                                                 <color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
                                                 <tableViewGridLines key="gridStyleMask" horizontal="YES"/>
                                                 <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
                                                 <tableColumns>
-                                                    <tableColumn editable="NO" width="689" minWidth="10" maxWidth="1000000000000" id="tVn-dP-rPg">
+                                                    <tableColumn editable="NO" width="687" minWidth="10" maxWidth="1000000000000" id="tVn-dP-rPg">
                                                         <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
                                                             <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
                                                             <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
@@ -151,7 +165,7 @@
                                                         <tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
                                                         <prototypeCellViews>
                                                             <tableCellView id="vmz-MH-Uum">
-                                                                <rect key="frame" x="1" y="1" width="698" height="36"/>
+                                                                <rect key="frame" x="1" y="1" width="696" height="36"/>
                                                                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                                             </tableCellView>
                                                         </prototypeCellViews>
@@ -213,7 +227,8 @@
             </constraints>
             <connections>
                 <outlet property="albumArtworkImageView" destination="xZd-Hk-h2M" id="J8l-V9-P06"/>
-                <outlet property="albumDetailsTextButton" destination="o9Q-0s-xRU" id="vQv-Q6-HdR"/>
+                <outlet property="albumPrimaryDetailTextButton" destination="o9Q-0s-xRU" id="At9-hH-CJa"/>
+                <outlet property="albumSecondaryDetailTextButton" destination="jNq-xn-yIp" id="ESO-jC-EfW"/>
                 <outlet property="albumTitleTextField" destination="nCe-dY-YMM" id="h3l-p0-w3e"/>
                 <outlet property="albumTracksTableView" destination="eEJ-WA-0aM" id="l8k-M9-a8e"/>
                 <outlet property="albumYearAndDurationTextField" destination="QuO-3G-BMT" id="y34-rD-uR"/>


=====================================
modules/gui/macosx/UI/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.xib
=====================================
@@ -29,8 +29,8 @@
                                 <stackView distribution="fillEqually" orientation="horizontal" alignment="top" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" verticalHuggingPriority="1000" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6Nc-Mb-wDa">
                                     <rect key="frame" x="0.0" y="0.0" width="180" height="30"/>
                                     <subviews>
-                                        <button verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ntd-VT-2KS">
-                                            <rect key="frame" x="-6" y="-4" width="93" height="40"/>
+                                        <button imageHugsTitle="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ntd-VT-2KS">
+                                            <rect key="frame" x="-6" y="-4" width="94" height="40"/>
                                             <buttonCell key="cell" type="push" title=" Play" bezelStyle="rounded" image="play.fill" catalog="system" imagePosition="left" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Rgc-Uk-A3h">
                                                 <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                                 <font key="font" metaFont="system"/>
@@ -40,8 +40,8 @@
                                                 <action selector="playAction:" target="HAc-or-XD8" id="edJ-21-Ej1"/>
                                             </connections>
                                         </button>
-                                        <button verticalHuggingPriority="750" imageHugsTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="idL-3a-QaA">
-                                            <rect key="frame" x="80" y="-4" width="106" height="40"/>
+                                        <button imageHugsTitle="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="idL-3a-QaA">
+                                            <rect key="frame" x="81" y="-4" width="105" height="40"/>
                                             <buttonCell key="cell" type="push" title=" Enqueue" bezelStyle="rounded" image="plus" catalog="system" imagePosition="left" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="igY-iu-P0N">
                                                 <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                                 <font key="font" metaFont="system"/>
@@ -82,7 +82,7 @@
                         <stackView distribution="fillEqually" orientation="vertical" alignment="leading" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" horizontalCompressionResistancePriority="250" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bw7-QB-Ssc">
                             <rect key="frame" x="200" y="115" width="877" height="110"/>
                             <subviews>
-                                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nCe-dY-YMM">
+                                <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nCe-dY-YMM">
                                     <rect key="frame" x="-2" y="84" width="167" height="26"/>
                                     <textFieldCell key="cell" lineBreakMode="truncatingTail" title="Media item name" id="6RM-x8-Y4y">
                                         <font key="font" textStyle="title1" name=".SFNS-Regular"/>
@@ -90,18 +90,42 @@
                                         <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                     </textFieldCell>
                                 </textField>
-                                <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="S5i-gd-zkt">
-                                    <rect key="frame" x="0.0" y="63" width="146" height="16"/>
-                                    <buttonCell key="cell" type="bevel" title="Media item detail string" bezelStyle="rounded" alignment="left" imageScaling="proportionallyDown" inset="2" id="zkC-9p-xFn">
-                                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                                        <font key="font" metaFont="system"/>
-                                    </buttonCell>
-                                    <color key="contentTintColor" name="VLCAccentColor"/>
-                                    <connections>
-                                        <action selector="detailAction:" target="HAc-or-XD8" id="JiQ-Ss-fTO"/>
-                                    </connections>
-                                </button>
-                                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QuO-3G-BMT">
+                                <stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JzZ-K9-xaP">
+                                    <rect key="frame" x="0.0" y="63" width="417" height="16"/>
+                                    <subviews>
+                                        <button verticalHuggingPriority="750" horizontalCompressionResistancePriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="S5i-gd-zkt">
+                                            <rect key="frame" x="0.0" y="0.0" width="196" height="16"/>
+                                            <buttonCell key="cell" type="bevel" title="Media item primary detail string" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" truncatesLastVisibleLine="YES" imageScaling="proportionallyDown" inset="2" id="zkC-9p-xFn">
+                                                <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                                <font key="font" metaFont="system"/>
+                                            </buttonCell>
+                                            <color key="contentTintColor" name="VLCAccentColor"/>
+                                            <connections>
+                                                <action selector="primaryDetailAction:" target="HAc-or-XD8" id="bFl-ro-arq"/>
+                                            </connections>
+                                        </button>
+                                        <button verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="9CH-03-Iu3">
+                                            <rect key="frame" x="204" y="0.0" width="213" height="16"/>
+                                            <buttonCell key="cell" type="bevel" title="Media item secondary detail string" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" truncatesLastVisibleLine="YES" imageScaling="proportionallyDown" inset="2" id="wPH-tu-Uw1">
+                                                <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                                <font key="font" metaFont="system"/>
+                                            </buttonCell>
+                                            <color key="contentTintColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
+                                            <connections>
+                                                <action selector="secondaryDetailAction:" target="HAc-or-XD8" id="IAX-VG-Kpd"/>
+                                            </connections>
+                                        </button>
+                                    </subviews>
+                                    <visibilityPriorities>
+                                        <integer value="1000"/>
+                                        <integer value="1000"/>
+                                    </visibilityPriorities>
+                                    <customSpacing>
+                                        <real value="3.4028234663852886e+38"/>
+                                        <real value="3.4028234663852886e+38"/>
+                                    </customSpacing>
+                                </stackView>
+                                <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QuO-3G-BMT">
                                     <rect key="frame" x="-2" y="42" width="94" height="16"/>
                                     <textFieldCell key="cell" lineBreakMode="clipping" title="Year · Duration" id="JBg-wo-ZeE">
                                         <font key="font" metaFont="system"/>
@@ -112,7 +136,7 @@
                                 <stackView distribution="fill" orientation="horizontal" alignment="top" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fiJ-9f-ecF">
                                     <rect key="frame" x="0.0" y="21" width="106" height="16"/>
                                     <subviews>
-                                        <textField horizontalHuggingPriority="251" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="NZk-WW-Uay">
+                                        <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="NZk-WW-Uay">
                                             <rect key="frame" x="-2" y="0.0" width="72" height="16"/>
                                             <textFieldCell key="cell" lineBreakMode="clipping" title="File Name:" id="CyQ-Ao-mup">
                                                 <font key="font" metaFont="systemBold"/>
@@ -120,7 +144,7 @@
                                                 <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                             </textFieldCell>
                                         </textField>
-                                        <textField verticalHuggingPriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="VxL-BW-3eh">
+                                        <textField focusRingType="none" verticalHuggingPriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="VxL-BW-3eh">
                                             <rect key="frame" x="71" y="0.0" width="37" height="16"/>
                                             <textFieldCell key="cell" title="Label" id="Pzf-s3-rHC">
                                                 <font key="font" usesAppearanceFont="YES"/>
@@ -141,7 +165,7 @@
                                 <stackView distribution="fill" orientation="horizontal" alignment="top" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="B2d-Zo-xAO">
                                     <rect key="frame" x="0.0" y="0.0" width="72" height="16"/>
                                     <subviews>
-                                        <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="hEf-79-49a">
+                                        <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="hEf-79-49a">
                                             <rect key="frame" x="-2" y="0.0" width="38" height="16"/>
                                             <textFieldCell key="cell" lineBreakMode="clipping" title="Path:" id="a9t-Zw-74U">
                                                 <font key="font" metaFont="systemBold"/>
@@ -149,8 +173,8 @@
                                                 <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                             </textFieldCell>
                                         </textField>
-                                        <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7gY-3s-Kay">
-                                            <rect key="frame" x="38" y="0.0" width="37" height="16"/>
+                                        <textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7gY-3s-Kay">
+                                            <rect key="frame" x="37" y="0.0" width="37" height="16"/>
                                             <textFieldCell key="cell" title="Label" id="3XB-SJ-5ia">
                                                 <font key="font" usesAppearanceFont="YES"/>
                                                 <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -213,9 +237,10 @@
             </constraints>
             <connections>
                 <outlet property="mediaItemArtworkImageView" destination="xZd-Hk-h2M" id="J8l-V9-P06"/>
-                <outlet property="mediaItemDetailButton" destination="S5i-gd-zkt" id="SsE-11-GwE"/>
                 <outlet property="mediaItemFileNameTextField" destination="VxL-BW-3eh" id="fiL-3N-m3P"/>
                 <outlet property="mediaItemPathTextField" destination="7gY-3s-Kay" id="P4t-h1-m3P"/>
+                <outlet property="mediaItemPrimaryDetailButton" destination="S5i-gd-zkt" id="ssB-Jg-jcf"/>
+                <outlet property="mediaItemSecondaryDetailButton" destination="9CH-03-Iu3" id="J8O-hB-tHb"/>
                 <outlet property="mediaItemTitleTextField" destination="nCe-dY-YMM" id="h3l-p0-w3e"/>
                 <outlet property="mediaItemYearAndDurationTextField" destination="QuO-3G-BMT" id="y34-rD-uR"/>
                 <outlet property="playMediaItemButton" destination="ntd-VT-2KS" id="LJV-KL-cK4"/>


=====================================
modules/gui/macosx/library/VLCLibraryCollectionViewItem.m
=====================================
@@ -214,7 +214,7 @@ const CGFloat VLCLibraryCollectionViewItemMaximumDisplayedProgress = 0.95;
 
     const id<VLCMediaLibraryItemProtocol> actualItem = self.representedItem.item;
     _mediaTitleTextField.stringValue = actualItem.displayString;
-    _secondaryInfoTextField.stringValue = actualItem.detailString;
+    _secondaryInfoTextField.stringValue = actualItem.primaryDetailString;
 
     [VLCLibraryImageCache thumbnailForLibraryItem:actualItem withCompletion:^(NSImage * const thumbnail) {
         self->_mediaImageView.image = thumbnail;


=====================================
modules/gui/macosx/library/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.h
=====================================
@@ -33,7 +33,8 @@ extern NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewMe
 @interface VLCLibraryCollectionViewMediaItemSupplementaryDetailView : VLCLibraryCollectionViewSupplementaryDetailView
 
 @property (readwrite, weak) IBOutlet NSTextField *mediaItemTitleTextField;
- at property (readwrite, weak) IBOutlet NSButton *mediaItemDetailButton;
+ at property (readwrite, weak) IBOutlet NSButton *mediaItemPrimaryDetailButton;
+ at property (readwrite, weak) IBOutlet NSButton *mediaItemSecondaryDetailButton;
 @property (readwrite, weak) IBOutlet NSTextField *mediaItemYearAndDurationTextField;
 @property (readwrite, weak) IBOutlet NSTextField *mediaItemFileNameTextField;
 @property (readwrite, weak) IBOutlet NSTextField *mediaItemPathTextField;


=====================================
modules/gui/macosx/library/VLCLibraryCollectionViewMediaItemSupplementaryDetailView.m
=====================================
@@ -48,10 +48,12 @@ NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewMediaItem
 - (void)awakeFromNib
 {
     _mediaItemTitleTextField.font = NSFont.VLCLibrarySubsectionHeaderFont;
-    _mediaItemDetailButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
+    _mediaItemPrimaryDetailButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
+    _mediaItemSecondaryDetailButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
 
     if (@available(macOS 10.14, *)) {
-        _mediaItemDetailButton.contentTintColor = NSColor.VLCAccentColor;
+        _mediaItemPrimaryDetailButton.contentTintColor = NSColor.VLCAccentColor;
+        _mediaItemSecondaryDetailButton.contentTintColor = NSColor.secondaryLabelColor;
     }
 
     if(@available(macOS 10.12.2, *)) {
@@ -90,17 +92,22 @@ NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewMediaItem
     NSAssert(actualItem != nil, @"represented item is not a media item", nil);
 
     _mediaItemTitleTextField.stringValue = actualItem.displayString;
-    _mediaItemDetailButton.title = actualItem.detailString;
+    _mediaItemPrimaryDetailButton.title = actualItem.primaryDetailString;
+    _mediaItemSecondaryDetailButton.title = actualItem.secondaryDetailString;
     _mediaItemYearAndDurationTextField.stringValue = [self formattedYearAndDurationString];
     _mediaItemFileNameTextField.stringValue = actualItem.inputItem.name;
     _mediaItemPathTextField.stringValue = actualItem.inputItem.decodedMRL;
 
-    const BOOL actionableDetail = actualItem.actionableDetail;
-    self.mediaItemDetailButton.enabled = actionableDetail;
+    const BOOL primaryActionableDetail = actualItem.primaryActionableDetail;
+    const BOOL secondaryActionableDetail = actualItem.secondaryActionableDetail;
+    self.mediaItemPrimaryDetailButton.enabled = primaryActionableDetail;
+    self.mediaItemSecondaryDetailButton.enabled = secondaryActionableDetail;
     if (@available(macOS 10.14, *)) {
-        self.mediaItemDetailButton.contentTintColor = actionableDetail ? NSColor.VLCAccentColor : NSColor.secondaryLabelColor;
+        self.mediaItemPrimaryDetailButton.contentTintColor = primaryActionableDetail ? NSColor.VLCAccentColor : NSColor.secondaryLabelColor;
+        self.mediaItemSecondaryDetailButton.contentTintColor = secondaryActionableDetail ? NSColor.secondaryLabelColor : NSColor.tertiaryLabelColor;
     }
-    self.mediaItemDetailButton.action = @selector(detailAction:);
+    self.mediaItemPrimaryDetailButton.action = @selector(primaryDetailAction:);
+    self.mediaItemSecondaryDetailButton.action = @selector(secondaryDetailAction:);
 
     [VLCLibraryImageCache thumbnailForLibraryItem:actualItem withCompletion:^(NSImage * const thumbnail) {
         self->_mediaItemArtworkImageView.image = thumbnail;
@@ -117,15 +124,27 @@ NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewMediaItem
     [self.representedItem queue];
 }
 
-- (IBAction)detailAction:(id)sender
+- (IBAction)primaryDetailAction:(id)sender
 {
     VLCMediaLibraryMediaItem * const actualItem = self.representedItem.item;
-    if (actualItem == nil || !actualItem.actionableDetail) {
+    if (actualItem == nil || !actualItem.primaryActionableDetail) {
         return;
     }
 
     VLCLibraryWindow * const libraryWindow = VLCMain.sharedInstance.libraryWindow;
-    const id<VLCMediaLibraryItemProtocol> libraryItem = actualItem.actionableDetailLibraryItem;
+    const id<VLCMediaLibraryItemProtocol> libraryItem = actualItem.primaryActionableDetailLibraryItem;
+    [libraryWindow presentLibraryItem:libraryItem];
+}
+
+- (IBAction)secondaryDetailAction:(id)sender
+{
+    VLCMediaLibraryMediaItem * const actualItem = self.representedItem.item;
+    if (actualItem == nil || !actualItem.secondaryActionableDetail) {
+        return;
+    }
+
+    VLCLibraryWindow * const libraryWindow = VLCMain.sharedInstance.libraryWindow;
+    const id<VLCMediaLibraryItemProtocol> libraryItem = actualItem.secondaryActionableDetailLibraryItem;
     [libraryWindow presentLibraryItem:libraryItem];
 }
 


=====================================
modules/gui/macosx/library/VLCLibraryDataTypes.h
=====================================
@@ -31,6 +31,7 @@ extern NSString * const VLCMediaLibraryMediaItemUTI;
 @class VLCMediaLibraryMediaItem;
 @class VLCMediaLibraryAlbum;
 @class VLCMediaLibraryArtist;
+ at class VLCMediaLibraryGenre;
 @class VLCInputItem;
 
 extern const CGFloat VLCMediaLibrary4KWidth;
@@ -131,18 +132,21 @@ typedef NS_ENUM(NSUInteger, VLCMediaLibraryParentGroupType) {
 @property (readonly) BOOL smallArtworkGenerated;
 @property (readonly) NSString *smallArtworkMRL;
 @property (readonly) NSString *displayString;
- at property (readonly) NSString *detailString;
+ at property (readonly) NSString *primaryDetailString;
+ at property (readonly) NSString *secondaryDetailString;
 @property (readonly) NSString *durationString;
 @property (readonly) VLCMediaLibraryMediaItem *firstMediaItem;
 // Media items should be delivered album-wise. If it is required for derivative
 // types to provide media items in a different grouping or order, use methods
-// or properties specific to the type (e.g. like in VLCMeidaLibraryGenre)
+// or properties specific to the type (e.g. like in VLCMediaLibraryGenre)
 @property (readonly) NSArray<VLCMediaLibraryMediaItem *> *mediaItems;
-// If the info in detailString contains a library object that can be used for nav
-// We lazy load the actionable library item so avoid using the property until we
-// actually need to, resort to `actionableDetail` to know if there is one instead
- at property (readonly) BOOL actionableDetail;
- at property (readonly) id<VLCMediaLibraryItemProtocol> actionableDetailLibraryItem;
+// If the info in detailString contains a library object that can be used for nav,
+// we lazy load the actionable library item to avoid using the property until we
+// actually need to -- resort to `actionableDetail` to know if there is one instead
+ at property (readonly) BOOL primaryActionableDetail;
+ at property (readonly) id<VLCMediaLibraryItemProtocol> primaryActionableDetailLibraryItem;
+ at property (readonly) BOOL secondaryActionableDetail;
+ at property (readonly) id<VLCMediaLibraryItemProtocol> secondaryActionableDetailLibraryItem;
 
 - (void)iterateMediaItemsWithBlock:(void (^)(VLCMediaLibraryMediaItem*))mediaItemBlock;
 
@@ -153,8 +157,9 @@ typedef NS_ENUM(NSUInteger, VLCMediaLibraryParentGroupType) {
 @protocol VLCMediaLibraryAudioGroupProtocol <VLCMediaLibraryItemProtocol>
 
 @property (readonly) unsigned int numberOfTracks;
- at property (readonly) NSArray <VLCMediaLibraryArtist *> *artists;
- at property (readonly) NSArray <VLCMediaLibraryAlbum *> *albums;
+ at property (readonly) NSArray<VLCMediaLibraryArtist *> *artists;
+ at property (readonly) NSArray<VLCMediaLibraryAlbum *> *albums;
+ at property (readonly) NSArray<VLCMediaLibraryGenre *> *genres;
 @property (readonly) VLCMediaLibraryParentGroupType matchingParentType;
 
 - (void)iterateMediaItemsWithBlock:(void (^)(VLCMediaLibraryMediaItem*))mediaItemBlock;
@@ -179,6 +184,7 @@ typedef NS_ENUM(NSUInteger, VLCMediaLibraryParentGroupType) {
 - (instancetype)initWithArtist:(struct vlc_ml_artist_t *)p_artist;
 
 @property (readonly) NSString *name;
+ at property (readonly) NSString *genreString; // Lazy loaded for performance
 @property (readonly) NSString *shortBiography;
 @property (readonly) NSString *musicBrainzID;
 @property (readonly) unsigned int numberOfAlbums;
@@ -193,6 +199,7 @@ typedef NS_ENUM(NSUInteger, VLCMediaLibraryParentGroupType) {
 @property (readonly) NSString *title;
 @property (readonly) NSString *summary;
 @property (readonly) NSString *artistName;
+ at property (readonly) NSString *genreString; // Lazy loaded for performance
 @property (readonly) int64_t artistID;
 @property (readonly) unsigned int duration;
 @property (readonly) unsigned int year;
@@ -277,8 +284,9 @@ typedef NS_ENUM(NSUInteger, VLCMediaLibraryParentGroupType) {
 
 @interface VLCMediaLibraryDummyItem : NSObject<VLCMediaLibraryItemProtocol>
 
-- (instancetype)initWithDisplayString:(NSString*)displayString
-                     withDetailString:(NSString*)detailString;
+- (instancetype)initWithDisplayString:(NSString *)displayString
+              withPrimaryDetailString:(nullable NSString *)primaryDetailString
+            withSecondaryDetailString:(nullable NSString *)secondaryDetailString;
 
 @end
 


=====================================
modules/gui/macosx/library/VLCLibraryDataTypes.m
=====================================
@@ -113,6 +113,25 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return [mutableArray copy];
 }
 
+static NSString *genreArrayDisplayString(NSArray<VLCMediaLibraryGenre *> * const genres)
+{
+    const NSUInteger genreCount = genres.count;
+    if (genreCount == 0) {
+        return @"";
+    }
+
+    VLCMediaLibraryGenre * const firstGenre = genres.firstObject;
+    if (genreCount == 1) {
+        return firstGenre.name;
+    }
+
+    VLCMediaLibraryGenre * const secondGenre = [genres objectAtIndex:1];
+    return [NSString stringWithFormat:_NS("%@, %@, and %lli other genres"),
+                     firstGenre.name,
+                     secondGenre.name,
+                     genreCount - 2];
+}
+
 @implementation VLCMediaLibraryFile
 
 - (instancetype)initWithFile:(struct vlc_ml_file_t *)p_file
@@ -259,10 +278,12 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 
 @property (readwrite, assign) int64_t libraryID;
 @property (readwrite, assign) BOOL smallArtworkGenerated;
- at property (readwrite, assign) BOOL actionableDetail;
+ at property (readwrite, assign) BOOL primaryActionableDetail;
+ at property (readwrite, assign) BOOL secondaryActionableDetail;
 @property (readwrite, atomic, strong) NSString *smallArtworkMRL;
 @property (readwrite, atomic, strong) NSString *displayString;
- at property (readwrite, atomic, strong) NSString *detailString;
+ at property (readwrite, atomic, strong) NSString *primaryDetailString;
+ at property (readwrite, atomic, strong) NSString *secondaryDetailString;
 @property (readwrite, atomic, strong) NSString *durationString;
 
 @end
@@ -281,7 +302,13 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return nil;
 }
 
-- (id<VLCMediaLibraryItemProtocol>)actionableDetailLibraryItem
+- (id<VLCMediaLibraryItemProtocol>)primaryActionableDetailLibraryItem
+{
+    [self doesNotRecognizeSelector:_cmd];
+    return nil;
+}
+
+- (id<VLCMediaLibraryItemProtocol>)secondaryActionableDetailLibraryItem
 {
     [self doesNotRecognizeSelector:_cmd];
     return nil;
@@ -304,8 +331,42 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 
 @end
 
+ at interface VLCAbstractMediaLibraryAudioGroup ()
+{
+    NSArray<VLCMediaLibraryGenre *> *_genres;
+}
+ at end
+
 @implementation VLCAbstractMediaLibraryAudioGroup
 
+- (NSArray<VLCMediaLibraryGenre *> *)genres
+{
+    if (_genres == nil) {
+        NSMutableSet<NSNumber *> * const mutableGenreIDs = NSMutableSet.set;
+        NSMutableArray<VLCMediaLibraryGenre *> * const mutableGenres = NSMutableArray.array;
+
+        for (VLCMediaLibraryMediaItem * const mediaItem in self.mediaItems) {
+            const int64_t genreID = mediaItem.genreID;
+            NSNumber * const numberGenreID = @(mediaItem.genreID);
+            if ([mutableGenreIDs containsObject:numberGenreID]) {
+                continue;
+            }
+
+            VLCMediaLibraryGenre * const genre = [VLCMediaLibraryGenre genreWithID:genreID];
+            if (genre == nil) {
+                continue;
+            }
+
+            [mutableGenreIDs addObject:numberGenreID];
+            [mutableGenres addObject:genre];
+        }
+
+        _genres = mutableGenres.copy;
+    }
+
+    return _genres;
+}
+
 - (NSArray<VLCMediaLibraryArtist *> *)artists
 {
     [self doesNotRecognizeSelector:_cmd];
@@ -360,6 +421,12 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 
 @end
 
+ at interface VLCMediaLibraryArtist ()
+{
+    NSString *_genreString;
+}
+ at end
+
 @implementation VLCMediaLibraryArtist
 
 @synthesize numberOfTracks = _numberOfTracks;
@@ -385,7 +452,8 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
         self.libraryID = p_artist->i_id;
         self.smallArtworkGenerated = p_artist->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl != NULL;
         self.smallArtworkMRL = self.smallArtworkGenerated ? toNSStr(p_artist->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl) : nil;
-        self.actionableDetail = NO;
+        self.primaryActionableDetail = NO;
+        self.secondaryActionableDetail = YES;
 
         _name = toNSStr(p_artist->psz_name);
         if ([_name isEqualToString:@""]) {
@@ -405,11 +473,16 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return _name;
 }
 
-- (NSString *)detailString
+- (NSString *)primaryDetailString
 {
     return [self durationString];
 }
 
+- (NSString *)secondaryDetailString
+{
+    return self.genreString;
+}
+
 - (NSString *)durationString
 {
     NSString *countMetadataString;
@@ -427,6 +500,15 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return countMetadataString;
 }
 
+- (NSString *)genreString
+{
+    if (_genreString == nil || [_genreString isEqualToString:@""]) {
+        _genreString = self.genres == nil ? @"" : genreArrayDisplayString(self.genres);
+    }
+
+    return _genreString;
+}
+
 - (NSArray<VLCMediaLibraryArtist *> *)artists
 {
     return @[self];
@@ -447,6 +529,11 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return VLCMediaLibraryParentGroupTypeArtist;
 }
 
+- (id<VLCMediaLibraryItemProtocol>)secondaryActionableDetailLibraryItem
+{
+    return self.genres.firstObject;
+}
+
 - (void)iterateMediaItemsWithBlock:(void (^)(VLCMediaLibraryMediaItem*))mediaItemBlock;
 {
     for(VLCMediaLibraryAlbum* album in self.albums) {
@@ -456,10 +543,16 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 
 @end
 
+ at interface VLCMediaLibraryAlbum ()
+{
+    NSString *_genreString;
+}
+ at end
+
 @implementation VLCMediaLibraryAlbum
 
 @synthesize numberOfTracks = _numberOfTracks;
- at synthesize actionableDetailLibraryItem = _actionableDetailLibraryItem;
+ at synthesize primaryActionableDetailLibraryItem = _primaryActionableDetailLibraryItem;
 
 + (nullable instancetype)albumWithID:(int64_t)albumID
 {
@@ -482,7 +575,8 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
         self.libraryID = p_album->i_id;
         self.smallArtworkGenerated = p_album->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl != NULL;
         self.smallArtworkMRL = self.smallArtworkGenerated ? toNSStr(p_album->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl) : nil;
-        self.actionableDetail = YES;
+        self.primaryActionableDetail = YES;
+        self.secondaryActionableDetail = YES;
 
         _title = toNSStr(p_album->psz_title);
         if ([_title isEqualToString:@""]) {
@@ -504,11 +598,25 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return _title;
 }
 
-- (NSString *)detailString
+- (NSString *)primaryDetailString
 {
     return _artistName;
 }
 
+- (NSString *)secondaryDetailString
+{
+    return self.genreString;
+}
+
+- (NSString *)genreString
+{
+    if (_genreString == nil || [_genreString isEqualToString:@""]) {
+        _genreString = genreArrayDisplayString(self.genres);
+    }
+
+    return _genreString;
+}
+
 - (NSString *)durationString
 {
     return [NSString stringWithTime:_duration / VLCMediaLibraryMediaItemDurationDenominator];
@@ -529,18 +637,23 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return fetchMediaItemsForLibraryItem(vlc_ml_list_album_tracks, self.libraryID);
 }
 
-- (id<VLCMediaLibraryItemProtocol>)actionableDetailLibraryItem
+- (VLCMediaLibraryParentGroupType)matchingParentType
+{
+    return VLCMediaLibraryParentGroupTypeAlbum;
+}
+
+- (id<VLCMediaLibraryItemProtocol>)primaryActionableDetailLibraryItem
 {
-    if (_actionableDetailLibraryItem == nil) {
-        _actionableDetailLibraryItem = [VLCMediaLibraryArtist artistWithID:self.artistID];
+    if (_primaryActionableDetailLibraryItem == nil) {
+        _primaryActionableDetailLibraryItem = [VLCMediaLibraryArtist artistWithID:self.artistID];
     }
 
-    return _actionableDetailLibraryItem;
+    return _primaryActionableDetailLibraryItem;
 }
 
-- (VLCMediaLibraryParentGroupType)matchingParentType
+- (id<VLCMediaLibraryItemProtocol>)secondaryActionableDetailLibraryItem
 {
-    return VLCMediaLibraryParentGroupTypeAlbum;
+    return self.genres.firstObject;
 }
 
 - (void)iterateMediaItemsWithBlock:(void (^)(VLCMediaLibraryMediaItem*))mediaItemBlock
@@ -563,7 +676,8 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
         self.libraryID = p_genre->i_id;
         self.smallArtworkGenerated = p_genre->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl != NULL;
         self.smallArtworkMRL = self.smallArtworkGenerated ? toNSStr(p_genre->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl) : nil;
-        self.actionableDetail = NO;
+        self.primaryActionableDetail = NO;
+        self.secondaryActionableDetail = NO;
 
         _name = toNSStr(p_genre->psz_name);
         if ([_name isEqualToString:@""]) {
@@ -594,7 +708,7 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return _name;
 }
 
-- (NSString *)detailString
+- (NSString *)primaryDetailString
 {
     return [self durationString];
 }
@@ -618,6 +732,12 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return fetchArtistsForLibraryItem(vlc_ml_list_genre_artists, self.libraryID);
 }
 
+- (NSArray<VLCMediaLibraryGenre *> *)genres
+{
+    // Overloading superclass behaviour
+    return @[self];
+}
+
 - (NSArray<VLCMediaLibraryMediaItem *> *)mediaItems
 {
     return fetchMediaItemsForLibraryItem(vlc_ml_list_genre_tracks, self.libraryID);
@@ -658,6 +778,10 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 @end
 
 @interface VLCMediaLibraryMediaItem ()
+{
+    NSString *_primaryDetailString;
+    NSString *_secondaryDetailString;
+}
 
 @property (readwrite, assign) vlc_medialibrary_t *p_mediaLibrary;
 
@@ -665,7 +789,8 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 
 @implementation VLCMediaLibraryMediaItem
 
- at synthesize actionableDetailLibraryItem = _actionableDetailLibraryItem;
+ at synthesize primaryActionableDetailLibraryItem = _primaryActionableDetailLibraryItem;
+ at synthesize secondaryActionableDetailLibraryItem = _secondaryActionableDetailLibraryItem;
 
 #pragma mark - initialization
 
@@ -725,7 +850,10 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
         self.libraryID = p_mediaItem->i_id;
         self.smallArtworkGenerated = p_mediaItem->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl != NULL;
         self.smallArtworkMRL = self.smallArtworkGenerated ? toNSStr(p_mediaItem->thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl) : nil;
-        self.actionableDetail = p_mediaItem->i_subtype == VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK;
+
+        const BOOL isAlbumTrack = p_mediaItem->i_subtype == VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK;
+        self.primaryActionableDetail = isAlbumTrack;
+        self.secondaryActionableDetail = isAlbumTrack;
 
         _p_mediaLibrary = p_mediaLibrary;
         _mediaType = p_mediaItem->i_type;
@@ -903,29 +1031,61 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return _title;
 }
 
-- (NSString *)detailString
+- (nullable NSString *)contextualPrimaryDetailString
 {
-   if (_mediaSubType == VLC_ML_MEDIA_SUBTYPE_SHOW_EPISODE) {
-        VLCInputItem *inputItem = [self inputItem];
-        if (inputItem) {
-            NSString *showName = inputItem.showName;
-            return showName.length > 0 ? showName : [self durationString];
-        }
-   } else if (_mediaSubType == VLC_ML_MEDIA_SUBTYPE_MOVIE) {
-        VLCInputItem *inputItem = [self inputItem];
-        if (inputItem) {
-            NSString *directorString = inputItem.director;
-            return directorString.length > 0 ? directorString : [self durationString];
-        }
-    } else if (_mediaSubType == VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK) {
-        VLCMediaLibraryArtist *artist = [VLCMediaLibraryArtist artistWithID:_artistID];
-        if (artist) {
-            NSString *artistName = artist.name;
-            return artistName.length > 0 ? artistName : [self durationString];
-        }
+    switch (self.mediaSubType) {
+    case VLC_ML_MEDIA_SUBTYPE_SHOW_EPISODE:
+        return self.inputItem.showName;
+    case VLC_ML_MEDIA_SUBTYPE_MOVIE:
+        return self.inputItem.director;
+    case VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK:
+    {
+        VLCMediaLibraryArtist * const artist = [VLCMediaLibraryArtist artistWithID:_artistID];
+        return artist.name;
     }
+    default:
+        return nil;
+    }
+}
 
-    return [self durationString];
+- (NSString *)primaryDetailString
+{
+    if (_primaryDetailString == nil || [_primaryDetailString isEqualToString:@""]) {
+        NSString * const contextualString = [self contextualPrimaryDetailString];
+        const BOOL validContextualString = contextualString != nil && contextualString.length > 0;
+        _primaryDetailString = validContextualString ? contextualString : self.durationString;
+    }
+
+    return _primaryDetailString;
+}
+
+- (nullable NSString *)contextualSecondaryDetailString
+{
+    switch (self.mediaSubType) {
+    case VLC_ML_MEDIA_SUBTYPE_SHOW_EPISODE:
+    {
+        VLCInputItem * const inputItem = self.inputItem;
+        return [NSString stringWithFormat:_NS("Season %u, Episode %u"),
+                         inputItem.season,
+                         inputItem.episode];
+    }
+    case VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK:
+    {
+        VLCMediaLibraryGenre * const genre = [VLCMediaLibraryGenre genreWithID:self.genreID];
+        return genreArrayDisplayString(@[genre]);
+    }
+    default:
+        return self.inputItem.date;
+    }
+}
+
+- (NSString *)secondaryDetailString
+{
+    if (_secondaryDetailString == nil || [_secondaryDetailString isEqualToString:@""]) {
+        _secondaryDetailString = [self contextualSecondaryDetailString];
+    }
+
+    return _secondaryDetailString;
 }
 
 - (NSString *)durationString
@@ -955,13 +1115,22 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
     return @[self];
 }
 
-- (id<VLCMediaLibraryItemProtocol>)actionableDetailLibraryItem
+- (id<VLCMediaLibraryItemProtocol>)primaryActionableDetailLibraryItem
+{
+    if (_primaryActionableDetailLibraryItem == nil && self.mediaSubType == VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK) {
+        _primaryActionableDetailLibraryItem = [VLCMediaLibraryAlbum albumWithID:self.albumID];
+    }
+
+    return _primaryActionableDetailLibraryItem;
+}
+
+- (id<VLCMediaLibraryItemProtocol>)secondaryActionableDetailLibraryItem
 {
-    if (_mediaSubType == VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK && _actionableDetailLibraryItem == nil) {
-        _actionableDetailLibraryItem = [VLCMediaLibraryAlbum albumWithID:self.albumID];
+    if (_secondaryActionableDetailLibraryItem == nil && self.mediaSubType == VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK) {
+        _secondaryActionableDetailLibraryItem = [VLCMediaLibraryGenre genreWithID:self.genreID];
     }
 
-    return _actionableDetailLibraryItem;
+    return _secondaryActionableDetailLibraryItem;
 }
 
 - (void)iterateMediaItemsWithBlock:(void (^)(VLCMediaLibraryMediaItem*))mediaItemBlock;
@@ -1236,7 +1405,8 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 
 @implementation VLCMediaLibraryDummyItem
 
- at synthesize detailString = _detailString;
+ at synthesize primaryDetailString = _primaryDetailString;
+ at synthesize secondaryDetailString = _secondaryDetailString;
 @synthesize displayString = _displayString;
 @synthesize durationString = _durationString;
 @synthesize firstMediaItem = _firstMediaItem;
@@ -1244,22 +1414,28 @@ static NSArray<VLCMediaLibraryArtist *> *fetchArtistsForLibraryItem(library_arti
 @synthesize libraryID = _libraryId;
 @synthesize smallArtworkGenerated = _smallArtworkGenerated;
 @synthesize smallArtworkMRL = _smallArtworkMRL;
- at synthesize actionableDetail = _actionableDetail;
- at synthesize actionableDetailLibraryItem = _actionableDetailLibraryItem;
+ at synthesize primaryActionableDetail = _primaryActionableDetail;
+ at synthesize primaryActionableDetailLibraryItem = _primaryActionableDetailLibraryItem;
+ at synthesize secondaryActionableDetail = _secondaryActionableDetail;
+ at synthesize secondaryActionableDetailLibraryItem = _secondaryActionableDetailLibraryItem;
 
-- (instancetype)initWithDisplayString:(NSString*)displayString
-                     withDetailString:(NSString*)detailString
+- (instancetype)initWithDisplayString:(NSString *)displayString
+              withPrimaryDetailString:(nullable NSString *)primaryDetailString
+            withSecondaryDetailString:(nullable NSString *)secondaryDetailString
 {
     self = [super init];
     if (self) {
         _displayString = displayString;
-        _detailString = detailString;
+        _primaryDetailString = primaryDetailString;
+        _secondaryDetailString = secondaryDetailString;
         _durationString = @"";
         _libraryId = -1;
         _smallArtworkGenerated = NO;
         _smallArtworkMRL = @"";
-        _actionableDetail = NO;
-        _actionableDetailLibraryItem = nil;
+        _primaryActionableDetail = NO;
+        _primaryActionableDetailLibraryItem = nil;
+        _secondaryActionableDetail = NO;
+        _secondaryActionableDetailLibraryItem = nil;
     }
     return self;
 }


=====================================
modules/gui/macosx/library/VLCLibraryHeroView.m
=====================================
@@ -62,7 +62,7 @@
     const id<VLCMediaLibraryItemProtocol> actualItem = self.representedItem.item;
     self.largeImageView.image = [VLCLibraryImageCache thumbnailForLibraryItem:actualItem];
     self.titleTextField.stringValue = actualItem.displayString;
-    self.detailTextField.stringValue = actualItem.detailString;
+    self.detailTextField.stringValue = actualItem.primaryDetailString;
 }
 
 - (void)setRepresentedItem:(VLCLibraryRepresentedItem *)representedItem


=====================================
modules/gui/macosx/library/VLCLibraryModel.h
=====================================
@@ -84,6 +84,10 @@ extern NSString * const VLCLibraryModelGenreUpdated;
 
 @property (readonly) NSArray <VLCMediaLibraryEntryPoint *> *listOfMonitoredFolders;
 
+ at property (readonly) NSDictionary<NSNumber *, NSString *> *albumDict;
+ at property (readonly) NSDictionary<NSNumber *, NSString *> *artistDict;
+ at property (readonly) NSDictionary<NSNumber *, NSString *> *genreDict;
+
 - (nullable NSArray<VLCMediaLibraryAlbum *> *)listAlbumsOfParentType:(const enum vlc_ml_parent_type)parentType forID:(int64_t)ID;
 - (NSArray<id<VLCMediaLibraryItemProtocol>> *)listOfLibraryItemsOfParentType:(const VLCMediaLibraryParentGroupType)parentType;
 - (NSArray<VLCMediaLibraryMediaItem *> *)listOfMediaItemsForParentType:(const VLCMediaLibraryParentGroupType)parentType;


=====================================
modules/gui/macosx/library/VLCLibraryModel.m
=====================================
@@ -299,17 +299,25 @@ static void libraryCallback(void *p_data, const vlc_ml_event_t *p_event)
 {
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
         const vlc_ml_query_params_t queryParams = [self queryParams];
-        vlc_ml_artist_list_t *p_artist_list = vlc_ml_list_artists(self->_p_mediaLibrary, &queryParams, NO);
-        NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:p_artist_list->i_nb_items];
-        for (size_t x = 0; x < p_artist_list->i_nb_items; x++) {
-            VLCMediaLibraryArtist *artist = [[VLCMediaLibraryArtist alloc] initWithArtist:&p_artist_list->p_items[x]];
+        vlc_ml_artist_list_t * const p_artist_list = vlc_ml_list_artists(self->_p_mediaLibrary, &queryParams, NO);
+        const size_t numberOfArtists = p_artist_list->i_nb_items;
+        NSMutableArray * const mutableArtistArray = [[NSMutableArray alloc] initWithCapacity:numberOfArtists];
+        NSMutableDictionary * const mutableArtistDict = [NSMutableDictionary dictionaryWithCapacity:numberOfArtists];
+
+        for (size_t x = 0; x < numberOfArtists; x++) {
+            VLCMediaLibraryArtist * const artist = [[VLCMediaLibraryArtist alloc] initWithArtist:&p_artist_list->p_items[x]];
+
             if (artist != nil) {
-                [mutableArray addObject:artist];
+                [mutableArtistArray addObject:artist];
+                [mutableArtistDict setObject:artist.name forKey:@(artist.libraryID)];
             }
         }
+
         vlc_ml_artist_list_release(p_artist_list);
+
         dispatch_async(dispatch_get_main_queue(), ^{
-            self.cachedArtists = [mutableArray copy];
+            self.cachedArtists = mutableArtistArray.copy;
+            self->_artistDict = mutableArtistDict.copy;
             [self->_defaultNotificationCenter postNotificationName:VLCLibraryModelArtistListUpdated object:self];
         });
     });
@@ -337,15 +345,22 @@ static void libraryCallback(void *p_data, const vlc_ml_event_t *p_event)
 {
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
         const vlc_ml_query_params_t queryParams = [self queryParams];
-        vlc_ml_album_list_t *p_album_list = vlc_ml_list_albums(self->_p_mediaLibrary, &queryParams);
-        NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:p_album_list->i_nb_items];
-        for (size_t x = 0; x < p_album_list->i_nb_items; x++) {
-            VLCMediaLibraryAlbum *album = [[VLCMediaLibraryAlbum alloc] initWithAlbum:&p_album_list->p_items[x]];
-            [mutableArray addObject:album];
+        vlc_ml_album_list_t * const p_album_list = vlc_ml_list_albums(self->_p_mediaLibrary, &queryParams);
+        const size_t numberOfAlbums = p_album_list->i_nb_items;
+        NSMutableArray * const mutableAlbumArray = [[NSMutableArray alloc] initWithCapacity:numberOfAlbums];
+        NSMutableDictionary * const mutableAlbumDict = [NSMutableDictionary dictionaryWithCapacity:numberOfAlbums];
+
+        for (size_t x = 0; x < numberOfAlbums; x++) {
+            VLCMediaLibraryAlbum * const album = [[VLCMediaLibraryAlbum alloc] initWithAlbum:&p_album_list->p_items[x]];
+            [mutableAlbumArray addObject:album];
+            [mutableAlbumDict setObject:album.title forKey:@(album.libraryID)];
         }
+
         vlc_ml_album_list_release(p_album_list);
+
         dispatch_async(dispatch_get_main_queue(), ^{
-            self.cachedAlbums = [mutableArray copy];
+            self.cachedAlbums = mutableAlbumArray.copy;
+            self->_albumDict = mutableAlbumDict.copy;
             [self->_defaultNotificationCenter postNotificationName:VLCLibraryModelAlbumListUpdated object:self];
         });
     });
@@ -373,15 +388,22 @@ static void libraryCallback(void *p_data, const vlc_ml_event_t *p_event)
 {
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
         const vlc_ml_query_params_t queryParams = [self queryParams];
-        vlc_ml_genre_list_t *p_genre_list = vlc_ml_list_genres(self->_p_mediaLibrary, &queryParams);
-        NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:p_genre_list->i_nb_items];
-        for (size_t x = 0; x < p_genre_list->i_nb_items; x++) {
-            VLCMediaLibraryGenre *genre = [[VLCMediaLibraryGenre alloc] initWithGenre:&p_genre_list->p_items[x]];
-            [mutableArray addObject:genre];
+        vlc_ml_genre_list_t * const p_genre_list = vlc_ml_list_genres(self->_p_mediaLibrary, &queryParams);
+        const size_t numberOfGenres = p_genre_list->i_nb_items;
+        NSMutableArray * const mutableGenreArray = [[NSMutableArray alloc] initWithCapacity:numberOfGenres];
+        NSMutableDictionary * const mutableGenreDict = [NSMutableDictionary dictionaryWithCapacity:numberOfGenres];
+
+        for (size_t x = 0; x < numberOfGenres; x++) {
+            VLCMediaLibraryGenre * const genre = [[VLCMediaLibraryGenre alloc] initWithGenre:&p_genre_list->p_items[x]];
+            [mutableGenreArray addObject:genre];
+            [mutableGenreDict setObject:genre.name forKey:@(genre.libraryID)];
         }
+
         vlc_ml_genre_list_release(p_genre_list);
+
         dispatch_async(dispatch_get_main_queue(), ^{
-            self.cachedGenres = [mutableArray copy];
+            self.cachedGenres = mutableGenreArray.copy;
+            self->_genreDict = mutableGenreDict.copy;
             [self->_defaultNotificationCenter postNotificationName:VLCLibraryModelGenreListUpdated object:self];
         });
     });


=====================================
modules/gui/macosx/library/VLCLibraryTableCellView.m
=====================================
@@ -83,11 +83,11 @@ NSString * const VLCLibraryTableCellViewIdentifier = @"VLCLibraryTableCellViewId
         self.representedImageView.image = thumbnail;
     }];
 
-    if(actualItem.detailString.length > 0) {
+    if(actualItem.primaryDetailString.length > 0) {
         self.primaryTitleTextField.hidden = NO;
         self.primaryTitleTextField.stringValue = actualItem.displayString;
         self.secondaryTitleTextField.hidden = NO;
-        self.secondaryTitleTextField.stringValue = actualItem.detailString;
+        self.secondaryTitleTextField.stringValue = actualItem.primaryDetailString;
     } else {
         self.singlePrimaryTitleTextField.hidden = NO;
         self.singlePrimaryTitleTextField.stringValue = actualItem.displayString;


=====================================
modules/gui/macosx/library/audio-library/VLCLibraryAlbumTableCellView.h
=====================================
@@ -43,6 +43,7 @@ extern NSString * const VLCLibraryAlbumTableCellTableViewColumnIdentifier;
 @property (readwrite, assign) IBOutlet VLCImageView *representedImageView;
 @property (readwrite, assign) IBOutlet NSTextField *albumNameTextField;
 @property (readwrite, assign) IBOutlet NSButton *artistNameTextButton;
+ at property (readwrite, assign) IBOutlet NSButton *genreNameTextButton;
 @property (readwrite, assign) IBOutlet NSTextField *summaryTextField;
 @property (readwrite, assign) IBOutlet NSTextField *yearTextField;
 @property (readwrite, assign) IBOutlet NSButton *playInstantlyButton;
@@ -51,7 +52,8 @@ extern NSString * const VLCLibraryAlbumTableCellTableViewColumnIdentifier;
 @property (readwrite, assign, nonatomic) VLCLibraryRepresentedItem *representedItem;
 
 - (IBAction)playInstantly:(id)sender;
-- (IBAction)detailAction:(id)sender;
+- (IBAction)primaryDetailAction:(id)sender;
+- (IBAction)secondaryDetailAction:(id)sender;
 
 @end
 


=====================================
modules/gui/macosx/library/audio-library/VLCLibraryAlbumTableCellView.m
=====================================
@@ -139,11 +139,14 @@ const CGFloat VLCLibraryAlbumTableCellViewDefaultHeight = 168.;
     [self setupTracksTableView];
     self.albumNameTextField.font = NSFont.VLCLibrarySubsectionHeaderFont;
     self.artistNameTextButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
-    self.artistNameTextButton.action = @selector(detailAction:);
+    self.genreNameTextButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
+    self.artistNameTextButton.action = @selector(primaryDetailAction:);
+    self.genreNameTextButton.action = @selector(secondaryDetailAction:);
     self.trackingView.viewToHide = self.playInstantlyButton;
 
     if (@available(macOS 10.14, *)) {
         self.artistNameTextButton.contentTintColor = NSColor.VLCAccentColor;
+        self.genreNameTextButton.contentTintColor = NSColor.secondaryLabelColor;
     }
 
     [self prepareForReuse];
@@ -207,6 +210,7 @@ const CGFloat VLCLibraryAlbumTableCellViewDefaultHeight = 168.;
     self.representedImageView.image = nil;
     self.albumNameTextField.stringValue = @"";
     self.artistNameTextButton.title = @"";
+    self.genreNameTextButton.title = @"";
     self.yearTextField.stringValue = @"";
     self.summaryTextField.stringValue = @"";
     self.yearTextField.hidden = NO;
@@ -214,6 +218,7 @@ const CGFloat VLCLibraryAlbumTableCellViewDefaultHeight = 168.;
 
     if (@available(macOS 10.14, *)) {
         self.artistNameTextButton.contentTintColor = NSColor.VLCAccentColor;
+        self.genreNameTextButton.contentTintColor = NSColor.secondaryLabelColor;
     }
 
     _tracksDataSource.representedAlbum = nil;
@@ -256,15 +261,27 @@ const CGFloat VLCLibraryAlbumTableCellViewDefaultHeight = 168.;
     [self.representedItem play];
 }
 
-- (void)detailAction:(id)sender
+- (void)primaryDetailAction:(id)sender
 {
     VLCMediaLibraryAlbum * const album = (VLCMediaLibraryAlbum *)self.representedItem.item;
-    if (album == nil || !album.actionableDetail) {
+    if (album == nil || !album.primaryActionableDetail) {
         return;
     }
 
     VLCLibraryWindow * const libraryWindow = VLCMain.sharedInstance.libraryWindow;
-    const id<VLCMediaLibraryItemProtocol> libraryItem = album.actionableDetailLibraryItem;
+    const id<VLCMediaLibraryItemProtocol> libraryItem = album.primaryActionableDetailLibraryItem;
+    [libraryWindow presentLibraryItem:libraryItem];
+}
+
+- (void)secondaryDetailAction:(id)sender
+{
+    VLCMediaLibraryAlbum * const album = (VLCMediaLibraryAlbum *)self.representedItem.item;
+    if (album == nil || !album.secondaryActionableDetail) {
+        return;
+    }
+
+    VLCLibraryWindow * const libraryWindow = VLCMain.sharedInstance.libraryWindow;
+    id<VLCMediaLibraryItemProtocol> libraryItem = album.secondaryActionableDetailLibraryItem;
     [libraryWindow presentLibraryItem:libraryItem];
 }
 
@@ -286,6 +303,7 @@ const CGFloat VLCLibraryAlbumTableCellViewDefaultHeight = 168.;
 
     self.albumNameTextField.stringValue = album.title;
     self.artistNameTextButton.title = album.artistName;
+    self.genreNameTextButton.title = album.genreString;
 
     if (album.year > 0) {
         self.yearTextField.intValue = album.year;
@@ -299,10 +317,13 @@ const CGFloat VLCLibraryAlbumTableCellViewDefaultHeight = 168.;
         self.summaryTextField.stringValue = album.durationString;
     }
 
-    const BOOL actionableDetail = album.actionableDetail;
-    self.artistNameTextButton.enabled = actionableDetail;
+    const BOOL primaryActionableDetail = album.primaryActionableDetail;
+    const BOOL secondaryActionableDetail = album.secondaryActionableDetail;
+    self.artistNameTextButton.enabled = primaryActionableDetail;
+    self.genreNameTextButton.enabled = secondaryActionableDetail;
     if (@available(macOS 10.14, *)) {
-        self.artistNameTextButton.contentTintColor = actionableDetail ? NSColor.VLCAccentColor : NSColor.secondaryLabelColor;
+        self.artistNameTextButton.contentTintColor = primaryActionableDetail ? NSColor.VLCAccentColor : NSColor.secondaryLabelColor;
+        self.genreNameTextButton.contentTintColor = secondaryActionableDetail ? NSColor.secondaryLabelColor : NSColor.tertiaryLabelColor;
     }
 
     [VLCLibraryImageCache thumbnailForLibraryItem:album withCompletion:^(NSImage * const thumbnail) {


=====================================
modules/gui/macosx/library/audio-library/VLCLibraryAllAudioGroupsMediaLibraryItem.m
=====================================
@@ -33,6 +33,7 @@
 
 @synthesize albums = _albums;
 @synthesize artists = _artists;
+ at synthesize genres = _genres;
 @synthesize numberOfTracks = _numberOfTracks;
 @synthesize mediaItems = _mediaItems;
 @synthesize matchingParentType = _matchingParentType;
@@ -54,9 +55,13 @@
     _numberOfTracks = _mediaItems.count;
 
     const NSUInteger numberOfAlbums = libraryModel.numberOfAlbums;
-    NSString * const detailString = [NSString stringWithFormat:_NS("%li albums, %li songs"), numberOfAlbums, _numberOfTracks];
+    NSString * const primaryDetailString = [NSString stringWithFormat:_NS("%li albums, %li songs"),
+                                                     numberOfAlbums,
+                                                     _numberOfTracks];
 
-    return [super initWithDisplayString:displayString withDetailString:detailString];
+    return [super initWithDisplayString:displayString
+                withPrimaryDetailString:primaryDetailString
+              withSecondaryDetailString:nil];
 }
 
 - (void)iterateMediaItemsWithBlock:(nonnull void (^)(VLCMediaLibraryMediaItem * _Nonnull))mediaItemBlock


=====================================
modules/gui/macosx/library/audio-library/VLCLibraryAudioGroupHeaderView.m
=====================================
@@ -59,7 +59,7 @@ NSString * const VLCLibraryAudioGroupHeaderViewIdentifier = @"VLCLibraryAudioGro
     }
 
     _titleTextField.stringValue = actualItem.displayString;
-    _detailTextField.stringValue = actualItem.detailString;
+    _detailTextField.stringValue = actualItem.primaryDetailString;
 }
 
 - (void)setRepresentedItem:(VLCLibraryRepresentedItem *)representedItem


=====================================
modules/gui/macosx/library/audio-library/VLCLibraryCollectionViewAlbumSupplementaryDetailView.h
=====================================
@@ -35,7 +35,8 @@ extern NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewAl
 @interface VLCLibraryCollectionViewAlbumSupplementaryDetailView : VLCLibraryCollectionViewSupplementaryDetailView
 
 @property (readwrite, weak) IBOutlet NSTextField *albumTitleTextField;
- at property (readwrite, weak) IBOutlet NSButton *albumDetailsTextButton;
+ at property (readwrite, weak) IBOutlet NSButton *albumPrimaryDetailTextButton;
+ at property (readwrite, weak) IBOutlet NSButton *albumSecondaryDetailTextButton;
 @property (readwrite, weak) IBOutlet NSTextField *albumYearAndDurationTextField;
 @property (readwrite, weak) IBOutlet VLCImageView *albumArtworkImageView;
 @property (readwrite, weak) IBOutlet NSTableView *albumTracksTableView;
@@ -43,7 +44,8 @@ extern NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewAl
 
 - (IBAction)playAction:(id)sender;
 - (IBAction)enqueueAction:(id)sender;
-- (IBAction)detailAction:(id)sender;
+- (IBAction)primaryDetailAction:(id)sender;
+- (IBAction)secondaryDetailAction:(id)sender;
 
 @end
 


=====================================
modules/gui/macosx/library/audio-library/VLCLibraryCollectionViewAlbumSupplementaryDetailView.m
=====================================
@@ -68,12 +68,15 @@ NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewAlbumSupp
     _albumTracksTableView.rowHeight = VLCLibraryTracksRowHeight;
 
     _albumTitleTextField.font = NSFont.VLCLibrarySubsectionHeaderFont;
-    _albumDetailsTextButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
+    self.albumPrimaryDetailTextButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
+    self.albumSecondaryDetailTextButton.font = NSFont.VLCLibrarySubsectionSubheaderFont;
 
-    _albumDetailsTextButton.action = @selector(detailAction:);
+    self.albumPrimaryDetailTextButton.action = @selector(primaryDetailAction:);
+    self.albumSecondaryDetailTextButton.action = @selector(secondaryDetailAction:);
 
     if (@available(macOS 10.14, *)) {
-        _albumDetailsTextButton.contentTintColor = NSColor.VLCAccentColor;
+        self.albumPrimaryDetailTextButton.contentTintColor = NSColor.VLCAccentColor;
+        self.albumSecondaryDetailTextButton.contentTintColor = NSColor.secondaryLabelColor;
     }
 
     if(@available(macOS 10.12.2, *)) {
@@ -110,13 +113,17 @@ NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewAlbumSupp
     NSAssert(album != nil, @"represented item is not an album", nil);
 
     _albumTitleTextField.stringValue = album.displayString;
-    _albumDetailsTextButton.title = album.artistName;
+    _albumPrimaryDetailTextButton.title = album.artistName;
+    _albumSecondaryDetailTextButton.title = album.genreString;
     _albumYearAndDurationTextField.stringValue = [NSString stringWithFormat:@"%u · %@", album.year, album.durationString];
 
-    const BOOL actionableDetail = album.actionableDetail;
-    self.albumDetailsTextButton.enabled = actionableDetail;
+    const BOOL primaryActionableDetail = album.primaryActionableDetail;
+    const BOOL secondaryActionableDetail = album.secondaryActionableDetail;
+    self.albumPrimaryDetailTextButton.enabled = primaryActionableDetail;
+    self.albumSecondaryDetailTextButton.enabled = secondaryActionableDetail;
     if (@available(macOS 10.14, *)) {
-        self.albumDetailsTextButton.contentTintColor = actionableDetail ? NSColor.VLCAccentColor : NSColor.secondaryLabelColor;
+        self.albumPrimaryDetailTextButton.contentTintColor = primaryActionableDetail ? NSColor.VLCAccentColor : NSColor.secondaryLabelColor;
+        self.albumSecondaryDetailTextButton.contentTintColor = secondaryActionableDetail ? NSColor.secondaryLabelColor : NSColor.tertiaryLabelColor;
     }
 
     [VLCLibraryImageCache thumbnailForLibraryItem:album withCompletion:^(NSImage * const thumbnail) {
@@ -143,15 +150,27 @@ NSCollectionViewSupplementaryElementKind const VLCLibraryCollectionViewAlbumSupp
     [self.representedItem queue];
 }
 
-- (IBAction)detailAction:(id)sender
+- (IBAction)primaryDetailAction:(id)sender
 {
-    VLCMediaLibraryAlbum * const album = (VLCMediaLibraryAlbum *)self.representedItem;
-    if (album == nil || !album.actionableDetail) {
+    VLCMediaLibraryAlbum * const album = (VLCMediaLibraryAlbum *)self.representedItem.item;
+    if (album == nil || !album.primaryActionableDetail) {
+        return;
+    }
+
+    VLCLibraryWindow * const libraryWindow = VLCMain.sharedInstance.libraryWindow;
+    const id<VLCMediaLibraryItemProtocol> libraryItem = album.primaryActionableDetailLibraryItem;
+    [libraryWindow presentLibraryItem:libraryItem];
+}
+
+- (IBAction)secondaryDetailAction:(id)sender
+{
+    VLCMediaLibraryAlbum * const album = (VLCMediaLibraryAlbum *)self.representedItem.item;
+    if (album == nil || !album.secondaryActionableDetail) {
         return;
     }
 
     VLCLibraryWindow * const libraryWindow = VLCMain.sharedInstance.libraryWindow;
-    id<VLCMediaLibraryItemProtocol> libraryItem = album.actionableDetailLibraryItem;
+    const id<VLCMediaLibraryItemProtocol> libraryItem = album.secondaryActionableDetailLibraryItem;
     [libraryWindow presentLibraryItem:libraryItem];
 }
 


=====================================
modules/gui/macosx/windows/mainwindow/VLCMainVideoViewControlsBar.m
=====================================
@@ -71,15 +71,15 @@
 
 - (void)updateDetailLabel:(NSNotification *)notification
 {
-    
+
     VLCMediaLibraryMediaItem * const mediaItem = [VLCMediaLibraryMediaItem mediaItemForURL:_playerController.URLOfCurrentMediaItem];
     if (!mediaItem) {
         return;
     }
 
-    _detailLabel.hidden = [mediaItem.detailString isEqualToString:@""] ||
-                          [mediaItem.detailString isEqualToString:mediaItem.durationString];
-    _detailLabel.stringValue = mediaItem.detailString;
+    _detailLabel.hidden = [mediaItem.primaryDetailString isEqualToString:@""] ||
+                          [mediaItem.primaryDetailString isEqualToString:mediaItem.durationString];
+    _detailLabel.stringValue = mediaItem.primaryDetailString;
 }
 
 - (IBAction)openBookmarks:(id)sender



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/55803d57ca9025bc8c8575972857218ef78681d0...e0dee3c4f8cac8781d7ab04f4603d712dcf68729

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/55803d57ca9025bc8c8575972857218ef78681d0...e0dee3c4f8cac8781d7ab04f4603d712dcf68729
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