[vlc-commits] [Git][videolan/vlc][master] macosx: expose recently played network streams

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Tue May 26 11:36:40 UTC 2026



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
6c0693a3 by Felix Paul Kühne at 2026-05-26T13:18:25+02:00
macosx: expose recently played network streams

This fetches the recent networks streams from the media library and
fixes #18838. AppKit handles recent files only and refuses any
non-file URL.

- - - - -


9 changed files:

- extras/package/macosx/VLC.xcodeproj/project.pbxproj
- modules/gui/macosx/Makefile.am
- modules/gui/macosx/UI/MainMenu.xib
- modules/gui/macosx/menus/VLCMainMenu.h
- modules/gui/macosx/menus/VLCMainMenu.m
- + modules/gui/macosx/menus/VLCRecentStreamsMenuController.h
- + modules/gui/macosx/menus/VLCRecentStreamsMenuController.m
- modules/gui/macosx/playqueue/VLCPlayQueueController.m
- po/POTFILES.in


Changes:

=====================================
extras/package/macosx/VLC.xcodeproj/project.pbxproj
=====================================
@@ -221,6 +221,7 @@
 		7D66D4362200BC340040D04A /* VLCClickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D66D4352200BC340040D04A /* VLCClickerManager.m */; };
 		7D66D4392200C5B80040D04A /* VLCVideoFilterHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D66D4382200C5B80040D04A /* VLCVideoFilterHelper.m */; };
 		7D67318622C8F4060000AD40 /* VLCMediaSourceCollectionViewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D67318422C8F4050000AD40 /* VLCMediaSourceCollectionViewItem.m */; };
+		7D6CCA6F2FBE1BA400088B47 /* VLCRecentStreamsMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D6CCA6E2FBE1BA400088B47 /* VLCRecentStreamsMenuController.m */; };
 		7D713D322201AE350042BEB7 /* VLCLibraryWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D713D312201AE350042BEB7 /* VLCLibraryWindow.m */; };
 		7D713D362201DC640042BEB7 /* VLCLibraryWindow.xib in Sources */ = {isa = PBXBuildFile; fileRef = 7D713D332201BB130042BEB7 /* VLCLibraryWindow.xib */; };
 		7D903E0E22438F8F00917358 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 1C64E331223DB3B60046F895 /* error.c */; };
@@ -829,6 +830,8 @@
 		7D67318322C8F3F60000AD40 /* VLCMediaSourceCollectionViewItem.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = VLCMediaSourceCollectionViewItem.xib; sourceTree = "<group>"; };
 		7D67318422C8F4050000AD40 /* VLCMediaSourceCollectionViewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCMediaSourceCollectionViewItem.m; sourceTree = "<group>"; };
 		7D67318522C8F4050000AD40 /* VLCMediaSourceCollectionViewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCMediaSourceCollectionViewItem.h; sourceTree = "<group>"; };
+		7D6CCA6D2FBE1BA400088B47 /* VLCRecentStreamsMenuController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCRecentStreamsMenuController.h; sourceTree = "<group>"; };
+		7D6CCA6E2FBE1BA400088B47 /* VLCRecentStreamsMenuController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCRecentStreamsMenuController.m; sourceTree = "<group>"; };
 		7D713D302201AE350042BEB7 /* VLCLibraryWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCLibraryWindow.h; sourceTree = "<group>"; };
 		7D713D312201AE350042BEB7 /* VLCLibraryWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCLibraryWindow.m; sourceTree = "<group>"; };
 		7D713D332201BB130042BEB7 /* VLCLibraryWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VLCLibraryWindow.xib; sourceTree = "<group>"; };
@@ -1312,6 +1315,8 @@
 				7DB40D2920CBCEB500F63173 /* VLCMainMenu.m */,
 				7DB40D2C20CBCEC200F63173 /* VLCStatusBarIcon.h */,
 				7DB40D2B20CBCEC200F63173 /* VLCStatusBarIcon.m */,
+				7D6CCA6D2FBE1BA400088B47 /* VLCRecentStreamsMenuController.h */,
+				7D6CCA6E2FBE1BA400088B47 /* VLCRecentStreamsMenuController.m */,
 			);
 			path = menus;
 			sourceTree = "<group>";
@@ -2252,7 +2257,7 @@
 					};
 				};
 			};
-			buildConfigurationList = C2F2A6EA09588F1B00018C74 /* Build configuration list for PBXProject "vlc" */;
+			buildConfigurationList = C2F2A6EA09588F1B00018C74 /* Build configuration list for PBXProject "VLC" */;
 			compatibilityVersion = "Xcode 6.3";
 			developmentRegion = en;
 			hasScannedForEncodings = 1;
@@ -2402,6 +2407,7 @@
 				53903D3A29576ED500D0B308 /* VLCLibraryAudioGroupDataSource.m in Sources */,
 				1C31139A1E508C6900D4DD76 /* VLCBookmarksWindowController.m in Sources */,
 				6B0AB0F01F1AC8B3003A1B4E /* VLCPlaybackProgressSlider.m in Sources */,
+				7D6CCA6F2FBE1BA400088B47 /* VLCRecentStreamsMenuController.m in Sources */,
 				53741A5A2DCA7B4C00E112CD /* VLCSnowEffectView.m in Sources */,
 				7D28E6362275B4820098D30E /* NSColor+VLCAdditions.m in Sources */,
 				534D28B42DB2B3C0006869CD /* NSTableCellView+VLCAdditions.m in Sources */,
@@ -2954,7 +2960,7 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Default;
 		};
-		C2F2A6EA09588F1B00018C74 /* Build configuration list for PBXProject "vlc" */ = {
+		C2F2A6EA09588F1B00018C74 /* Build configuration list for PBXProject "VLC" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
 				C2F2A6EB09588F1B00018C74 /* Development */,


=====================================
modules/gui/macosx/Makefile.am
=====================================
@@ -338,6 +338,8 @@ libmacosx_plugin_la_SOURCES = \
 	gui/macosx/main/macosx.m \
 	gui/macosx/menus/VLCMainMenu.h \
 	gui/macosx/menus/VLCMainMenu.m \
+	gui/macosx/menus/VLCRecentStreamsMenuController.h \
+	gui/macosx/menus/VLCRecentStreamsMenuController.m \
 	gui/macosx/menus/VLCStatusBarIcon.h \
 	gui/macosx/menus/VLCStatusBarIcon.m \
 	gui/macosx/menus/renderers/VLCRendererDiscovery.h \


=====================================
modules/gui/macosx/UI/MainMenu.xib
=====================================
@@ -85,6 +85,8 @@
                 <outlet property="open_generic" destination="72" id="HZC-SG-Njq"/>
                 <outlet property="open_net" destination="446" id="mcx-C9-l0u"/>
                 <outlet property="open_recent" destination="4815" id="Hzh-wK-cxa"/>
+                <outlet property="recent_streams" destination="rstr-mi-0001" id="rstr-ol-0001"/>
+                <outlet property="recent_streamsMenu" destination="rstr-sm-0001" id="rstr-ol-0002"/>
                 <outlet property="pasteItem" destination="203" id="H04-s9-3uF"/>
                 <outlet property="play" destination="5142" id="B2I-n1-EOc"/>
                 <outlet property="playQueue" destination="1002" id="nhI-Gw-yev"/>
@@ -314,6 +316,10 @@
                                     </items>
                                 </menu>
                             </menuItem>
+                            <menuItem title="Recent Streams" id="rstr-mi-0001">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Recent Streams" id="rstr-sm-0001"/>
+                            </menuItem>
                             <menuItem isSeparatorItem="YES" id="3946">
                                 <modifierMask key="keyEquivalentModifierMask" command="YES"/>
                             </menuItem>


=====================================
modules/gui/macosx/menus/VLCMainMenu.h
=====================================
@@ -1,7 +1,7 @@
 /*****************************************************************************
  *MainMenu.h: MacOS X interface module
  *****************************************************************************
- *Copyright (C) 2011-2018 Felix Paul Kühne
+ *Copyright (C) 2011-2026 Felix Paul Kühne
  *
  *Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
  *
@@ -52,6 +52,8 @@
 @property (readwrite, weak) IBOutlet NSMenuItem *open_capture;
 @property (readwrite, weak) IBOutlet NSMenuItem *connect_to_server;
 @property (readwrite, weak) IBOutlet NSMenuItem *open_recent;
+ at property (readwrite, weak) IBOutlet NSMenuItem *recent_streams;
+ at property (readwrite, weak) IBOutlet NSMenu *recent_streamsMenu;
 @property (readwrite, weak) IBOutlet NSMenuItem *close_window;
 @property (readwrite, weak) IBOutlet NSMenuItem *convertandsave;
 @property (readwrite, weak) IBOutlet NSMenuItem *save_playlist;


=====================================
modules/gui/macosx/menus/VLCMainMenu.m
=====================================
@@ -1,7 +1,7 @@
 /*****************************************************************************
  *MainMenu.m: MacOS X interface module
  *****************************************************************************
- *Copyright (C) 2011-2019 Felix Paul Kühne
+ *Copyright (C) 2011-2026 Felix Paul Kühne
  *
  *Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
  *
@@ -32,6 +32,7 @@
 
 #import "main/VLCMain.h"
 
+#import "menus/VLCRecentStreamsMenuController.h"
 #import "menus/renderers/VLCRendererMenuController.h"
 
 #import "panels/VLCAudioEffectsWindowController.h"
@@ -107,6 +108,7 @@ typedef NS_ENUM(NSInteger, VLCObjectType) {
     VLCPlayerController *_playerController;
     VLCPlayQueueSortingMenuController *_playQueueSortingController;
     VLCInformationWindowController *_infoWindowController;
+    VLCRecentStreamsMenuController *_recentStreamsMenuController;
 
     __strong VLCTimeSelectionPanelController *_timeSelectionPanel;
     __strong VLCCustomCropArWindowController *_customARController;
@@ -158,6 +160,8 @@ typedef NS_ENUM(NSInteger, VLCObjectType) {
     _rendererMenuController.rendererMenu = _rendererMenu;
     _playQueueSortingController = [[VLCPlayQueueSortingMenuController alloc] init];
     _sortPlayQueue.submenu = _playQueueSortingController.playQueueSortingMenu;
+    _recentStreamsMenuController =
+        [[VLCRecentStreamsMenuController alloc] initWithSubmenu:_recent_streamsMenu];
 
     [self mediaItemChanged:nil];
     [self playbackStateChanged:nil];
@@ -353,6 +357,7 @@ typedef NS_ENUM(NSInteger, VLCObjectType) {
     [_open_capture setTitle: _NS("Open Capture Device...")];
     [_connect_to_server setTitle: _NS("Connect to Server...")];
     [_open_recent setTitle: _NS("Open Recent")];
+    [_recent_streams setTitle: _NS("Recent Streams")];
     [_close_window setTitle: _NS("Close Window")];
     [_convertandsave setTitle: _NS("Convert / Stream...")];
     [_save_playlist setTitle: _NS("Save Playlist...")];


=====================================
modules/gui/macosx/menus/VLCRecentStreamsMenuController.h
=====================================
@@ -0,0 +1,33 @@
+/*****************************************************************************
+ * VLCRecentStreamsMenuController.h: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2026 VLC authors and VideoLAN
+ *
+ * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import <Cocoa/Cocoa.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+ at interface VLCRecentStreamsMenuController : NSObject
+
+- (instancetype)initWithSubmenu:(NSMenu *)submenu;
+
+ at end
+
+NS_ASSUME_NONNULL_END


=====================================
modules/gui/macosx/menus/VLCRecentStreamsMenuController.m
=====================================
@@ -0,0 +1,148 @@
+/*****************************************************************************
+ * VLCRecentStreamsMenuController.m: MacOS X interface module
+ *****************************************************************************
+ * Copyright (C) 2026 VLC authors and VideoLAN
+ *
+ * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import "VLCRecentStreamsMenuController.h"
+#import "extensions/NSString+Helpers.h"
+#import "main/VLCMain.h"
+#import "playqueue/VLCPlayQueueController.h"
+#import "windows/VLCOpenInputMetadata.h"
+#import <vlc_media_library.h>
+
+ at interface VLCRecentStreamsMenuController ()
+{
+    vlc_medialibrary_t *_mediaLibrary;
+    vlc_ml_event_callback_t *_eventCallback;
+    NSMenu *_submenu;
+}
+
+- (void)rebuild;
+
+ at end
+
+static void recentStreamsLibraryCallback(void *p_data, const vlc_ml_event_t *p_event)
+{
+    if (p_event->i_type != VLC_ML_EVENT_HISTORY_CHANGED)
+        return;
+
+    VLCRecentStreamsMenuController *controller =
+        (__bridge VLCRecentStreamsMenuController *)p_data;
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [controller rebuild];
+    });
+}
+
+ at implementation VLCRecentStreamsMenuController
+
+- (instancetype)initWithSubmenu:(NSMenu *)submenu
+{
+    if (self = [super init]) {
+        _submenu = submenu;
+        _submenu.autoenablesItems = NO;
+
+        _mediaLibrary = vlc_ml_instance_get(getIntf());
+        if (_mediaLibrary != NULL) {
+            _eventCallback = vlc_ml_event_register_callback(_mediaLibrary,
+                                                            recentStreamsLibraryCallback,
+                                                            (__bridge void *)self);
+            [NSNotificationCenter.defaultCenter addObserver:self
+                                                   selector:@selector(applicationWillTerminate:)
+                                                       name:NSApplicationWillTerminateNotification
+                                                     object:nil];
+        }
+        [self rebuild];
+    }
+    return self;
+}
+
+- (void)applicationWillTerminate:(NSNotification *)notification
+{
+    if (_eventCallback != NULL) {
+        vlc_ml_event_unregister_callback(_mediaLibrary, _eventCallback);
+        _eventCallback = NULL;
+    }
+}
+
+- (void)dealloc
+{
+    [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+- (void)rebuild
+{
+    [_submenu removeAllItems];
+
+    if (_mediaLibrary == NULL) {
+        return;
+    }
+
+    BOOL hasEntries = NO;
+    vlc_ml_query_params_t params = vlc_ml_query_params_create();
+    params.i_nbResults = 30;
+    vlc_ml_media_list_t *list = vlc_ml_list_history(_mediaLibrary, &params, VLC_ML_HISTORY_TYPE_NETWORK);
+    if (list != NULL) {
+        for (size_t i = 0; i < list->i_nb_items; i++) {
+            const vlc_ml_media_t *ml_media = &list->p_items[i];
+            if (ml_media->p_files == NULL || ml_media->p_files->i_nb_items == 0)
+                continue;
+
+            NSString *mrlString = toNSStr(ml_media->p_files->p_items[0].psz_mrl);
+            NSString *title = toNSStr(ml_media->psz_title);
+            if (title.length == 0)
+                title = mrlString;
+
+            NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title
+                                                          action:@selector(openRecentStreamItem:)
+                                                   keyEquivalent:@""];
+            item.target = self;
+            item.representedObject = mrlString;
+            item.toolTip = mrlString;
+            [_submenu addItem:item];
+            hasEntries = YES;
+        }
+
+        if (hasEntries)
+            [_submenu addItem:[NSMenuItem separatorItem]];
+
+        vlc_ml_media_list_release(list);
+    }
+
+    NSMenuItem *clearItem = [[NSMenuItem alloc] initWithTitle:_NS("Clear Menu")
+                                                       action:@selector(clearRecentStreams:)
+                                                keyEquivalent:@""];
+    clearItem.target = self;
+    clearItem.enabled = hasEntries;
+    [_submenu addItem:clearItem];
+}
+
+- (void)openRecentStreamItem:(NSMenuItem *)sender
+{
+    VLCOpenInputMetadata *meta = [[VLCOpenInputMetadata alloc] init];
+    meta.MRLString = sender.representedObject;
+    [VLCMain.sharedInstance.playQueueController addPlayQueueItems:@[meta]];
+}
+
+- (void)clearRecentStreams:(id)sender
+{
+    vlc_ml_clear_history(_mediaLibrary, VLC_ML_HISTORY_TYPE_NETWORK);
+}
+
+ at end


=====================================
modules/gui/macosx/playqueue/VLCPlayQueueController.m
=====================================
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * VLCPlayQueueController.m: MacOS X interface module
  *****************************************************************************
- * Copyright (C) 2019-2025 VLC authors and VideoLAN
+ * Copyright (C) 2019-2026 VLC authors and VideoLAN
  *
  * Authors: Felix Paul Kühne <fkuehne # videolan -dot- org>
  *
@@ -693,6 +693,25 @@ static const struct vlc_playlist_callbacks playlist_callbacks = {
     if (url != nil && var_InheritBool(getIntf(), "macosx-recentitems"))
         [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:url];
 
+    /* Recent Streams menu */
+    if (url != nil && !url.isFileURL) {
+        vlc_medialibrary_t *ml = vlc_ml_instance_get(getIntf());
+        if (ml != NULL) {
+            const char *psz_uri = uri.UTF8String;
+            vlc_ml_media_t * ml_media = vlc_ml_get_media_by_mrl(ml, psz_uri);
+            if (ml_media == NULL) {
+                // create new entry if we don't have it yet
+                ml_media = vlc_ml_new_stream(ml, psz_uri);
+            }
+            if (ml_media != NULL) {
+                vlc_ml_media_set_played(ml, ml_media->i_id, true);
+                vlc_ml_media_release(ml_media);
+            }
+        } else {
+            msg_Dbg(p_intf, "ML unavailable, won't remember stream");
+        }
+    }
+
     return p_input;
 }
 


=====================================
po/POTFILES.in
=====================================
@@ -503,6 +503,8 @@ modules/gui/macosx/main/VLCMain.m
 modules/gui/macosx/main/macosx.m
 modules/gui/macosx/menus/VLCMainMenu.h
 modules/gui/macosx/menus/VLCMainMenu.m
+modules/gui/macosx/menus/VLCRecentStreamsMenuController.h
+modules/gui/macosx/menus/VLCRecentStreamsMenuController.m
 modules/gui/macosx/menus/VLCStatusBarIcon.h
 modules/gui/macosx/menus/VLCStatusBarIcon.m
 modules/gui/macosx/menus/renderers/VLCRendererDiscovery.h



View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/6c0693a32730db447dae010f16798a213a64baf4

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/6c0693a32730db447dae010f16798a213a64baf4
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list