[vlc-commits] [Git][videolan/vlc][master] 9 commits: macos: dispatch media source reloads to the main queue
Steve Lhomme (@robUx4)
gitlab at videolan.org
Mon Aug 17 05:00:30 UTC 2026
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
635733f2 by Serhii Bykov at 2026-08-17T04:32:00+00:00
macos: dispatch media source reloads to the main queue
# Conflicts:
# modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
- - - - -
3e7566aa by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Add preparse meta-info to media source notification emission
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
f98f3779 by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Notify on in-tree preparse end
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
7e67bae8 by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Check specifically for suffix in status notifier view notifications
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
74262be8 by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Handle preparse state on a per-item basis
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
0c9afb5a by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Add childCountForInputNode to media source types
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
9d428cfd by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Acquire input node child counts on separate dispatch queue (and cache them)
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
c26e70b8 by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Only dump child cache and reload data when preparse change notification does not target currently-viewed folder
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
7ceece7e by Claudio Cambra at 2026-08-17T04:32:00+00:00
macosx: Fix infinite load on empty removable storages
Signed-off-by: Claudio Cambra <developer at claudiocambra.com>
- - - - -
6 changed files:
- modules/gui/macosx/library/media-source/VLCLocalMediaSource.m
- modules/gui/macosx/library/media-source/VLCMediaSource.h
- modules/gui/macosx/library/media-source/VLCMediaSource.m
- modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
- modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
- modules/gui/macosx/views/VLCStatusNotifierView.m
Changes:
=====================================
modules/gui/macosx/library/media-source/VLCLocalMediaSource.m
=====================================
@@ -28,6 +28,35 @@
@implementation VLCLocalMediaSource
+- (nullable NSNumber *)childCountForInputNode:(VLCInputNode *)inputNode
+ error:(NSError * _Nullable * _Nullable)error
+{
+ NSURL * const nodeURL = [NSURL URLWithString:inputNode.inputItem.MRL];
+ if (!nodeURL.isFileURL) {
+ return [super childCountForInputNode:inputNode error:error];
+ }
+
+ NSArray<NSURL *> * const children =
+ [NSFileManager.defaultManager contentsOfDirectoryAtURL:nodeURL
+ includingPropertiesForKeys:@[NSURLIsDirectoryKey]
+ options:NSDirectoryEnumerationSkipsHiddenFiles |
+ NSDirectoryEnumerationSkipsSubdirectoryDescendants
+ error:error];
+ if (children == nil) {
+ return nil;
+ }
+
+ NSUInteger childCount = 0;
+ for (NSURL * const childURL in children) {
+ NSNumber *isDirectory = nil;
+ [childURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
+ if (isDirectory.boolValue || input_item_Playable(childURL.absoluteString.UTF8String)) {
+ ++childCount;
+ }
+ }
+ return @(childCount);
+}
+
- (nullable id<VLCMediaSourceNodeObservation>)observeInputNode:(VLCInputNode *)inputNode
onChange:(void (^)(VLCMediaSourceNodeChange change))changeHandler
{
=====================================
modules/gui/macosx/library/media-source/VLCMediaSource.h
=====================================
@@ -37,6 +37,8 @@ extern NSString *VLCMediaSourceChildrenAdded;
extern NSString *VLCMediaSourceChildrenRemoved;
extern NSString *VLCMediaSourcePreparsingStarted;
extern NSString *VLCMediaSourcePreparsingEnded;
+extern NSString * const VLCMediaSourcePreparseInputItemKey;
+extern NSString * const VLCMediaSourcePreparseStatusKey;
@interface VLCMediaSource : NSObject
@@ -49,6 +51,9 @@ extern NSString *VLCMediaSourcePreparsingEnded;
andPreparser:(vlc_preparser_t *)p_preparser;
- (nullable NSError *)preparseInputNodeWithinTree:(VLCInputNode *)inputNode;
+/// Returns nil without an error when this media source cannot count children directly.
+- (nullable NSNumber *)childCountForInputNode:(VLCInputNode *)inputNode
+ error:(NSError * _Nullable * _Nullable)error;
- (void)clearChildNodesForNode:(input_item_node_t*)inputNode;
- (nullable NSError *)generateChildNodesForDirectoryNode:(VLCInputNode *)directoryInputNode
withUrl:(NSURL *)directoryUrl;
=====================================
modules/gui/macosx/library/media-source/VLCMediaSource.m
=====================================
@@ -40,6 +40,8 @@ NSString *VLCMediaSourceChildrenAdded = @"VLCMediaSourceChildrenAdded";
NSString *VLCMediaSourceChildrenRemoved = @"VLCMediaSourceChildrenRemoved";
NSString *VLCMediaSourcePreparsingStarted = @"VLCMediaSourcePreparsingStarted";
NSString *VLCMediaSourcePreparsingEnded = @"VLCMediaSourcePreparsingEnded";
+NSString * const VLCMediaSourcePreparseInputItemKey = @"VLCMediaSourcePreparseInputItemKey";
+NSString * const VLCMediaSourcePreparseStatusKey = @"VLCMediaSourcePreparseStatusKey";
static void cb_children_reset(vlc_media_tree_t * __unused p_tree,
input_item_node_t * __unused p_node,
@@ -79,14 +81,22 @@ static void cb_children_removed(vlc_media_tree_t * __unused p_tree,
}
static void cb_preparse_ended(vlc_media_tree_t * __unused p_tree,
- input_item_node_t * __unused p_node,
- int __unused status,
+ input_item_node_t *p_node,
+ int status,
void *p_data)
{
+ VLCMediaSource * const mediaSource = (__bridge VLCMediaSource *)p_data;
+ VLCInputItem * const inputItem = p_node->p_item == NULL
+ ? nil
+ : [[VLCInputItem alloc] initWithInputItem:p_node->p_item];
+ NSDictionary * const userInfo = inputItem == nil
+ ? @{ VLCMediaSourcePreparseStatusKey: @(status) }
+ : @{ VLCMediaSourcePreparseInputItemKey: inputItem,
+ VLCMediaSourcePreparseStatusKey: @(status) };
dispatch_async(dispatch_get_main_queue(), ^{
- VLCMediaSource *mediaSource = (__bridge VLCMediaSource *)p_data;
[NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingEnded
- object:mediaSource];
+ object:mediaSource
+ userInfo:userInfo];
});
}
@@ -325,17 +335,47 @@ static const char *const remoteBrowseDescription = "Remote Browse";
return nil;
}
- [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingStarted
- object:self];
+ NSDictionary * const preparseUserInfo = @{
+ VLCMediaSourcePreparseInputItemKey: inputNode.inputItem
+ };
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingStarted
+ object:self
+ userInfo:preparseUserInfo];
+ });
- if (inputNode.inputItem.inputType == ITEM_TYPE_DIRECTORY &&
+ const enum input_item_type_e inputType = inputNode.inputItem.inputType;
+ if ((inputType == ITEM_TYPE_DIRECTORY || inputType == ITEM_TYPE_DISC) &&
[inputNode.inputItem.MRL hasPrefix:@"file://"]) {
NSURL *dirUrl = [NSURL URLWithString:inputNode.inputItem.MRL];
return [self generateChildNodesForDirectoryNode:inputNode withUrl:dirUrl];
}
- vlc_media_tree_Preparse(_p_mediaSource->tree, _p_preparser,
- inputNode.inputItem.vlcInputItem);
+ vlc_preparser_req * const request =
+ vlc_media_tree_Preparse(_p_mediaSource->tree,
+ _p_preparser,
+ inputNode.inputItem.vlcInputItem);
+ if (request == NULL) {
+ NSDictionary * const failedPreparseUserInfo = @{
+ VLCMediaSourcePreparseInputItemKey: inputNode.inputItem,
+ VLCMediaSourcePreparseStatusKey: @(VLC_ENOMEM)
+ };
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingEnded
+ object:self
+ userInfo:failedPreparseUserInfo];
+ });
+ return [NSError errorWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil];
+ }
+ return nil;
+}
+
+- (nullable NSNumber *)childCountForInputNode:(VLCInputNode * const __unused)inputNode
+ error:(NSError * _Nullable * _Nullable)error
+{
+ if (error != NULL) {
+ *error = nil;
+ }
return nil;
}
@@ -470,8 +510,15 @@ static const char *const remoteBrowseDescription = "Remote Browse";
if (self.didFinishGeneratingChildNodesForNodeHandler) {
self.didFinishGeneratingChildNodesForNodeHandler(directoryNode);
}
- [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingEnded
- object:self];
+ NSDictionary * const userInfo = @{
+ VLCMediaSourcePreparseInputItemKey: directoryInputNode.inputItem,
+ VLCMediaSourcePreparseStatusKey: @(VLC_EGENERIC)
+ };
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingEnded
+ object:self
+ userInfo:userInfo];
+ });
return error;
}
@@ -524,8 +571,15 @@ static const char *const remoteBrowseDescription = "Remote Browse";
if (self.didFinishGeneratingChildNodesForNodeHandler) {
self.didFinishGeneratingChildNodesForNodeHandler(directoryNode);
}
- [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingEnded
- object:self];
+ NSDictionary * const userInfo = @{
+ VLCMediaSourcePreparseInputItemKey: directoryInputNode.inputItem,
+ VLCMediaSourcePreparseStatusKey: @(VLC_SUCCESS)
+ };
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourcePreparsingEnded
+ object:self
+ userInfo:userInfo];
+ });
}
return nil;
=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceBaseDataSource.m
=====================================
@@ -758,8 +758,10 @@ referenceSizeForHeaderInSection:(NSInteger)section
_lanDeviceSnapshot = [self snapshotByUpdatingSource:source];
}
}
-
- [self reloadData];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self reloadData];
+ });
}
- (void)reloadData
=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
=====================================
@@ -55,11 +55,22 @@ NSString * const VLCMediaSourceDataSourceLoadingEnded = @"VLCMediaSourceDataSour
}
@property (readwrite) dispatch_source_t observedPathDispatchSource;
- at property (readwrite) BOOL preparseOngoing;
@property (readwrite, strong, nullable, nonatomic) id<VLCMediaSourceNodeObservation> nodeObservation;
+ at property (readwrite, strong) NSMutableSet<NSValue *> *preparingInputItemIdentifiers;
+ at property (readwrite, strong) NSMutableSet<NSValue *> *preparedInputItemIdentifiers;
+ at property (readwrite, strong) NSMutableSet<NSValue *> *unavailableInputItemIdentifiers;
+ at property (readwrite, strong) NSMutableDictionary<NSValue *, NSNumber *> *childCountsByInputItemIdentifier;
+ at property (readwrite) dispatch_queue_t childCountQueue;
@end
+static NSValue * _Nullable inputItemIdentifier(VLCInputItem * _Nullable const inputItem)
+{
+ return inputItem.vlcInputItem == NULL
+ ? nil
+ : [NSValue valueWithPointer:inputItem.vlcInputItem];
+}
+
@implementation VLCMediaSourceDataSource
- (instancetype)initWithParentBaseDataSource:(VLCMediaSourceBaseDataSource *)parentBaseDataSource
@@ -67,6 +78,14 @@ NSString * const VLCMediaSourceDataSourceLoadingEnded = @"VLCMediaSourceDataSour
self = [super init];
if (self) {
self.parentBaseDataSource = parentBaseDataSource;
+ self.preparingInputItemIdentifiers = NSMutableSet.set;
+ self.preparedInputItemIdentifiers = NSMutableSet.set;
+ self.unavailableInputItemIdentifiers = NSMutableSet.set;
+ self.childCountsByInputItemIdentifier = NSMutableDictionary.dictionary;
+ dispatch_queue_attr_t const childCountQueueAttributes =
+ dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
+ self.childCountQueue = dispatch_queue_create("org.videolan.vlc.media-source-child-count",
+ childCountQueueAttributes);
NSNotificationCenter * const notificationCenter = NSNotificationCenter.defaultCenter;
[notificationCenter addObserver:self
selector:@selector(mediaSourceChildrenChanged:)
@@ -100,7 +119,80 @@ NSString * const VLCMediaSourceDataSourceLoadingEnded = @"VLCMediaSourceDataSour
- (void)preparseStateChanged:(NSNotification *)notification
{
- self.preparseOngoing = notification.name != VLCMediaSourcePreparsingEnded;
+ if (notification.object != self.displayedMediaSource)
+ return;
+
+ VLCInputItem * const inputItem = notification.userInfo[VLCMediaSourcePreparseInputItemKey];
+ NSValue * const identifier = inputItemIdentifier(inputItem);
+ if (identifier == nil) {
+ return;
+ }
+
+ const BOOL preparseStarted = [notification.name isEqualToString:VLCMediaSourcePreparsingStarted];
+ if (preparseStarted) {
+ [self.preparingInputItemIdentifiers addObject:identifier];
+ } else {
+ [self.preparingInputItemIdentifiers removeObject:identifier];
+
+ NSNumber * const status = notification.userInfo[VLCMediaSourcePreparseStatusKey];
+ if (status == nil || status.intValue == VLC_SUCCESS) {
+ [self.preparedInputItemIdentifiers addObject:identifier];
+ [self.unavailableInputItemIdentifiers removeObject:identifier];
+ } else {
+ [self.unavailableInputItemIdentifiers addObject:identifier];
+ [self.preparedInputItemIdentifiers removeObject:identifier];
+ }
+ }
+
+ NSValue * const displayedNodeIdentifier = inputItemIdentifier(self.nodeToDisplay.inputItem);
+ if (![identifier isEqual:displayedNodeIdentifier]) {
+ if (!preparseStarted) {
+ [self reloadCountForInputItemIdentifier:identifier];
+ }
+ return;
+ }
+
+ if (!preparseStarted) {
+ [self.nodeToDisplay clearChildrenCache];
+ [self reloadData];
+ }
+
+ NSString * const loadingNotificationName = preparseStarted
+ ? VLCMediaSourceDataSourceLoadingStarted
+ : VLCMediaSourceDataSourceLoadingEnded;
+ [NSNotificationCenter.defaultCenter postNotificationName:loadingNotificationName
+ object:self];
+}
+
+- (void)reloadCountForInputItemIdentifier:(NSValue *)identifier
+{
+ NSArray<VLCInputNode *> * const children = self.nodeToDisplay.children;
+ const NSUInteger row = [children indexOfObjectPassingTest:^BOOL(VLCInputNode * const childNode,
+ NSUInteger __unused idx,
+ BOOL * const __unused stop) {
+ return [inputItemIdentifier(childNode.inputItem) isEqual:identifier];
+ }];
+ if (row == NSNotFound) {
+ return;
+ }
+
+ [children[row] clearChildrenCache];
+
+ const NSInteger countColumn = [self.tableView columnWithIdentifier:@"VLCMediaSourceTableCountColumn"];
+ if (self.tableView.hidden || countColumn == -1) {
+ return;
+ }
+
+ [self.tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row]
+ columnIndexes:[NSIndexSet indexSetWithIndex:countColumn]];
+}
+
+- (void)clearDisplayedNodeChildrenCaches
+{
+ [self.nodeToDisplay clearChildrenCache];
+ for (VLCInputNode * const childNode in self.nodeToDisplay.children) {
+ [childNode clearChildrenCache];
+ }
}
- (void)mediaSourceChildrenChanged:(NSNotification *)notification
@@ -108,13 +200,8 @@ NSString * const VLCMediaSourceDataSourceLoadingEnded = @"VLCMediaSourceDataSour
if (notification.object != self.displayedMediaSource)
return;
dispatch_async(dispatch_get_main_queue(), ^{
+ [self clearDisplayedNodeChildrenCaches];
[self reloadData];
- if (self.hasDisplayedItems ||
- [notification.name isEqualToString:VLCMediaSourcePreparsingEnded]) {
- [NSNotificationCenter.defaultCenter
- postNotificationName:VLCMediaSourceDataSourceLoadingEnded
- object:self];
- }
});
}
@@ -144,13 +231,13 @@ NSString * const VLCMediaSourceDataSourceLoadingEnded = @"VLCMediaSourceDataSour
}];
[self reloadData];
- if (self.preparseOngoing) {
- [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourceDataSourceLoadingStarted
- object:self];
- } else {
- [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourceDataSourceLoadingEnded
- object:self];
- }
+ NSValue * const identifier = inputItemIdentifier(nodeToDisplay.inputItem);
+ NSString * const loadingNotificationName =
+ [self.preparingInputItemIdentifiers containsObject:identifier]
+ ? VLCMediaSourceDataSourceLoadingStarted
+ : VLCMediaSourceDataSourceLoadingEnded;
+ [NSNotificationCenter.defaultCenter postNotificationName:loadingNotificationName
+ object:self];
}
- (BOOL)hasDisplayedItems
@@ -295,26 +382,43 @@ NSString * const VLCMediaSourceDataSourceLoadingEnded = @"VLCMediaSourceDataSour
NSAssert(cellView, @"Cell view should not be nil");
if ([tableColumn.identifier isEqualToString:@"VLCMediaSourceTableCountColumn"]) {
- if (inputNode.numberOfChildren == 0) {
+ NSValue * const identifier = inputItemIdentifier(inputNode.inputItem);
+ const int numberOfChildren = inputNode.numberOfChildren;
+ NSNumber * const cachedChildCount = self.childCountsByInputItemIdentifier[identifier];
+ if ([self.unavailableInputItemIdentifiers containsObject:identifier]) {
+ cellView.textField.stringValue = NSTR("Unavailable");
+ } else if (numberOfChildren > 0 || [self.preparedInputItemIdentifiers containsObject:identifier]) {
+ cellView.textField.stringValue =
+ [NSString stringWithFormat:@"%i items", numberOfChildren];
+ } else if (cachedChildCount != nil) {
+ cellView.textField.stringValue =
+ [NSString stringWithFormat:@"%li items", cachedChildCount.integerValue];
+ } else {
cellView.textField.stringValue = NSTR("Loading…");
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
- NSError * const error =
- [self.displayedMediaSource preparseInputNodeWithinTree:inputNode];
- if (error) {
+ if (![self.preparingInputItemIdentifiers containsObject:identifier]) {
+ [self.preparingInputItemIdentifiers addObject:identifier];
+ VLCMediaSource * const mediaSource = self.displayedMediaSource;
+ dispatch_async(self.childCountQueue, ^{
+ NSError *error = nil;
+ NSNumber * const childCount = [mediaSource childCountForInputNode:inputNode
+ error:&error];
+ if (childCount == nil && error == nil) {
+ [mediaSource preparseInputNodeWithinTree:inputNode];
+ return;
+ }
+
dispatch_async(dispatch_get_main_queue(), ^{
- cellView.textField.stringValue = NSTR("Unavailable");
+ [self.preparingInputItemIdentifiers removeObject:identifier];
+ if (error != nil) {
+ [self.unavailableInputItemIdentifiers addObject:identifier];
+ } else if (childCount != nil) {
+ [self.unavailableInputItemIdentifiers removeObject:identifier];
+ self.childCountsByInputItemIdentifier[identifier] = childCount;
+ }
+ [self reloadCountForInputItemIdentifier:identifier];
});
- return;
- }
-
- dispatch_async(dispatch_get_main_queue(), ^{
- cellView.textField.stringValue =
- [NSString stringWithFormat:@"%i items", inputNode.numberOfChildren];
});
- });
- } else {
- cellView.textField.stringValue =
- [NSString stringWithFormat:@"%i items", inputNode.numberOfChildren];
+ }
}
} else if ([tableColumn.identifier isEqualToString:@"VLCMediaSourceTableKindColumn"]) {
NSString *typeName = NSTR("Unknown");
=====================================
modules/gui/macosx/views/VLCStatusNotifierView.m
=====================================
@@ -152,14 +152,15 @@ NSString * const VLCMessageTimeoutTimerUserInfoMessageKey = @"VLCMessageTimeoutT
[self displayFinishLoad];
[self presentTransientMessage:self.discoveryFailedMessage];
[self removeMessage:self.discoveringMediaMessage];
- } else if ([notificationName containsString:VLCLongNotificationNameStartSuffix] && ![self.longNotifications containsObject:notificationName]) {
+ } else if ([notificationName hasSuffix:VLCLongNotificationNameStartSuffix] &&
+ ![self.longNotifications containsObject:notificationName]) {
if (self.longNotifications.count == 0) {
[self addMessage:self.loadingLibraryItemsMessage];
[self removeMessage:self.libraryItemsLoadedMessage];
[self displayStartLoad];
}
[self.longNotifications addObject:notificationName];
- } else if ([notificationName containsString:VLCLongNotificationNameFinishSuffix]) {
+ } else if ([notificationName hasSuffix:VLCLongNotificationNameFinishSuffix]) {
NSString * const loadingNotification =
[notificationName stringByReplacingOccurrencesOfString:VLCLongNotificationNameFinishSuffix withString:VLCLongNotificationNameStartSuffix];
[self.longNotifications removeObject:loadingNotification];
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/09d04c78ac62e92b95c23aeb2af25bbbcd7da051...7ceece7e7a66c378777ae35c7a924f47470420d9
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/09d04c78ac62e92b95c23aeb2af25bbbcd7da051...7ceece7e7a66c378777ae35c7a924f47470420d9
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