[vlc-commits] [Git][videolan/vlc][master] macosx: show loading overlay in media source view while awaiting remote content
Felix Paul Kühne (@fkuehne)
gitlab at videolan.org
Fri Jun 26 17:02:36 UTC 2026
Felix Paul Kühne pushed to branch master at VideoLAN / VLC
Commits:
ecb38c9e by Serhii Bykov at 2026-06-26T18:51:47+02:00
macosx: show loading overlay in media source view while awaiting remote content
- - - - -
3 changed files:
- modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.m
- modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.h
- modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
Changes:
=====================================
modules/gui/macosx/library/media-source/VLCLibraryMediaSourceViewController.m
=====================================
@@ -29,8 +29,11 @@
#import "extensions/NSFont+VLCAdditions.h"
#import "extensions/NSString+Helpers.h"
#import "extensions/NSTextField+VLCAdditions.h"
+#import "extensions/NSView+VLCAdditions.h"
#import "extensions/NSWindow+VLCAdditions.h"
+#import "views/VLCLoadingOverlayView.h"
+
#import "library/VLCLibraryCollectionView.h"
#import "library/VLCLibraryCollectionViewFlowLayout.h"
#import "library/VLCLibraryCollectionViewItem.h"
@@ -43,6 +46,9 @@
#import "main/VLCMain.h"
@interface VLCLibraryMediaSourceViewController ()
+{
+ VLCLoadingOverlayView *_loadingOverlayView;
+}
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
@property (readonly) NSGlassEffectView *pathControlGlassEffectView API_AVAILABLE(macos(26.0));
@@ -52,6 +58,17 @@
@implementation VLCLibraryMediaSourceViewController
+// The shared mediaSourceView is the source of truth for the active overlay.
+- (nullable VLCLoadingOverlayView *)currentLoadingOverlayInMediaSourceView
+{
+ for (NSView * const subview in self.mediaSourceView.subviews) {
+ if ([subview isKindOfClass:VLCLoadingOverlayView.class]) {
+ return (VLCLoadingOverlayView *)subview;
+ }
+ }
+ return nil;
+}
+
- (instancetype)initWithLibraryWindow:(VLCLibraryWindow *)libraryWindow
{
self = [super initWithLibraryWindow:libraryWindow];
@@ -64,13 +81,21 @@
[self setupPlaceholderLabel];
NSNotificationCenter * const defaultCenter = NSNotificationCenter.defaultCenter;
- [defaultCenter addObserver:self
- selector:@selector(updatePlaceholderLabel:)
- name:VLCMediaSourceBaseDataSourceNodeChanged
+ [defaultCenter addObserver:self
+ selector:@selector(updatePlaceholderLabel:)
+ name:VLCMediaSourceBaseDataSourceNodeChanged
+ object:nil];
+ [defaultCenter addObserver:self
+ selector:@selector(updatePlaceholderLabel:)
+ name:VLCMediaSourceDataSourceNodeChanged
+ object:nil];
+ [defaultCenter addObserver:self
+ selector:@selector(mediaSourceLoadingStarted:)
+ name:VLCMediaSourceDataSourceLoadingStarted
object:nil];
- [defaultCenter addObserver:self
- selector:@selector(updatePlaceholderLabel:)
- name:VLCMediaSourceDataSourceNodeChanged
+ [defaultCenter addObserver:self
+ selector:@selector(mediaSourceLoadingEnded:)
+ name:VLCMediaSourceDataSourceLoadingEnded
object:nil];
}
return self;
@@ -213,7 +238,51 @@
- (void)updatePlaceholderLabel:(NSNotification *)notification
{
- self.browsePlaceholderLabel.hidden = self.baseDataSource.hasDisplayedItems;
+ _loadingOverlayView = [self currentLoadingOverlayInMediaSourceView];
+ const BOOL overlayPresent = _loadingOverlayView != nil;
+ self.browsePlaceholderLabel.hidden = self.baseDataSource.hasDisplayedItems || overlayPresent;
+}
+
+- (void)prepareLoadingOverlay
+{
+ _loadingOverlayView = [self currentLoadingOverlayInMediaSourceView];
+
+ if (_loadingOverlayView != nil)
+ return;
+ if (!_loadingOverlayView)
+ _loadingOverlayView = [[VLCLoadingOverlayView alloc] init];
+ _loadingOverlayView.translatesAutoresizingMaskIntoConstraints = NO;
+ _loadingOverlayView.wantsLayer = YES;
+ _loadingOverlayView.alphaValue = 0.0;
+ [self.mediaSourceView addSubview:_loadingOverlayView];
+ [_loadingOverlayView applyConstraintsToFillSuperview];
+ [_loadingOverlayView.indicator startAnimation:self];
+}
+
+- (void)mediaSourceLoadingStarted:(NSNotification *)notification
+{
+ _loadingOverlayView = [self currentLoadingOverlayInMediaSourceView];
+
+ [self prepareLoadingOverlay];
+ _loadingOverlayView.alphaValue = 1.0;
+ [self updatePlaceholderLabel:nil];
+}
+
+- (void)mediaSourceLoadingEnded:(NSNotification *)notification
+{
+ _loadingOverlayView = [self currentLoadingOverlayInMediaSourceView];
+
+ if (_loadingOverlayView == nil)
+ return;
+
+ _loadingOverlayView.alphaValue = 1.0;
+ [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
+ _loadingOverlayView.animator.alphaValue = 0.0;
+ } completionHandler:^{
+ [_loadingOverlayView removeFromSuperview];
+ [_loadingOverlayView.indicator stopAnimation:self];
+ [self updatePlaceholderLabel:nil];
+ }];
}
- (NSView *)pathControlContainerView
@@ -238,7 +307,14 @@
- (void)presentMediaSourceView:(VLCLibrarySegmentType)viewSegment
{
[self.libraryWindow displayLibraryView:self.mediaSourceView];
- _baseDataSource.mediaSourceMode = viewSegment == VLCLibraryBrowseSegmentType ? VLCMediaSourceModeLAN : VLCMediaSourceModeInternet;
+ const BOOL isStreams = viewSegment != VLCLibraryBrowseSegmentType;
+ _loadingOverlayView = [self currentLoadingOverlayInMediaSourceView];
+
+ if (!isStreams) {
+ [self mediaSourceLoadingEnded:nil];
+ }
+
+ _baseDataSource.mediaSourceMode = isStreams ? VLCMediaSourceModeInternet : VLCMediaSourceModeLAN;
[_baseDataSource reloadViews];
}
=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.h
=====================================
@@ -32,6 +32,8 @@ NS_ASSUME_NONNULL_BEGIN
@class VLCMediaSourceBaseDataSource;
extern NSString * const VLCMediaSourceDataSourceNodeChanged;
+extern NSString * const VLCMediaSourceDataSourceLoadingStarted;
+extern NSString * const VLCMediaSourceDataSourceLoadingEnded;
@interface VLCMediaSourceDataSource : NSObject <NSCollectionViewDataSource,
NSCollectionViewDelegate,
=====================================
modules/gui/macosx/library/media-source/VLCMediaSourceDataSource.m
=====================================
@@ -50,6 +50,8 @@
#import "views/VLCImageView.h"
NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourceNodeChanged";
+NSString * const VLCMediaSourceDataSourceLoadingStarted = @"VLCMediaSourceDataSourceLoadingStarted";
+NSString * const VLCMediaSourceDataSourceLoadingEnded = @"VLCMediaSourceDataSourceLoadingEnded";
@interface VLCMediaSourceDataSource()
{
@@ -99,6 +101,12 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
return;
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData];
+ if (self.hasDisplayedItems ||
+ [notification.name isEqualToString:VLCMediaSourcePreparsingEnded]) {
+ [NSNotificationCenter.defaultCenter
+ postNotificationName:VLCMediaSourceDataSourceLoadingEnded
+ object:self];
+ }
});
}
@@ -138,39 +146,48 @@ NSString * const VLCMediaSourceDataSourceNodeChanged = @"VLCMediaSourceDataSourc
if (self.parentBaseDataSource.mediaSourceMode == VLCMediaSourceModeLAN) {
NSURL * const nodeUrl = [NSURL URLWithString:nodeToDisplay.inputItem.MRL];
- if (!nodeUrl.isFileURL) {
- [self reloadData];
- return;
- }
+ if (nodeUrl.isFileURL) {
+ if (self.observedPathDispatchSource) {
+ dispatch_source_cancel(self.observedPathDispatchSource);
+ self.observedPathDispatchSource = nil;
+ }
- if (self.observedPathDispatchSource) {
- dispatch_source_cancel(self.observedPathDispatchSource);
- self.observedPathDispatchSource = nil;
+ const __weak typeof(self) weakSelf = self;
+ self.observedPathDispatchSource = [self observeLocalUrl:nodeUrl
+ forVnodeEvents:DISPATCH_VNODE_WRITE |
+ DISPATCH_VNODE_DELETE |
+ DISPATCH_VNODE_RENAME
+ withEventHandler:^{
+ const uintptr_t eventFlags =
+ dispatch_source_get_data(weakSelf.observedPathDispatchSource);
+ if (eventFlags & DISPATCH_VNODE_DELETE || eventFlags & DISPATCH_VNODE_RENAME) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [weakSelf.parentBaseDataSource homeButtonAction:weakSelf];
+ });
+ } else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [weakSelf.displayedMediaSource generateChildNodesForDirectoryNode:nodeToDisplay
+ withUrl:nodeUrl];
+ [weakSelf reloadData];
+ });
+ }
+ }];
}
-
- const __weak typeof(self) weakSelf = self;
- self.observedPathDispatchSource = [self observeLocalUrl:nodeUrl
- forVnodeEvents:DISPATCH_VNODE_WRITE |
- DISPATCH_VNODE_DELETE |
- DISPATCH_VNODE_RENAME
- withEventHandler:^{
- const uintptr_t eventFlags =
- dispatch_source_get_data(weakSelf.observedPathDispatchSource);
- if (eventFlags & DISPATCH_VNODE_DELETE || eventFlags & DISPATCH_VNODE_RENAME) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [weakSelf.parentBaseDataSource homeButtonAction:weakSelf];
- });
- } else {
- dispatch_async(dispatch_get_main_queue(), ^{
- [weakSelf.displayedMediaSource generateChildNodesForDirectoryNode:nodeToDisplay
- withUrl:nodeUrl];
- [weakSelf reloadData];
- });
- }
- }];
}
[self reloadData];
+ if (!self.hasDisplayedItems) {
+ [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourceDataSourceLoadingStarted
+ object:self];
+ } else {
+ [NSNotificationCenter.defaultCenter postNotificationName:VLCMediaSourceDataSourceLoadingEnded
+ object:self];
+ }
+}
+
+- (BOOL)hasDisplayedItems
+{
+ return _nodeToDisplay.numberOfChildren > 0;
}
- (void)setupViews
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/ecb38c9e0d2c8c1c408e26952b59292f67028796
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/ecb38c9e0d2c8c1c408e26952b59292f67028796
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