Skip to content
Snippets Groups Projects
VLCLibraryWindow.m 33.72 KiB
/*****************************************************************************
 * VLCLibraryWindow.m: MacOS X interface module
 *****************************************************************************
 * Copyright (C) 2019 VLC authors and VideoLAN
 *
 * Authors: Felix Paul Kühne <fkuehne # 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 "VLCLibraryWindow.h"

#import "VLCLibraryDataTypes.h"

#import "extensions/NSColor+VLCAdditions.h"
#import "extensions/NSImage+VLCAdditions.h"
#import "extensions/NSFont+VLCAdditions.h"
#import "extensions/NSString+Helpers.h"
#import "extensions/NSView+VLCAdditions.h"
#import "extensions/NSWindow+VLCAdditions.h"

#import "main/VLCMain.h"
#import "menus/VLCMainMenu.h"

#import "playlist/VLCPlayerController.h"
#import "playlist/VLCPlaylistController.h"

#import "library/VLCLibraryController.h"
#import "library/VLCLibraryCollectionViewItem.h"
#import "library/VLCLibraryCollectionViewSupplementaryElementView.h"
#import "library/VLCLibraryModel.h"
#import "library/VLCLibraryNavigationStack.h"
#import "library/VLCLibrarySegment.h"
#import "library/VLCLibrarySortingMenuController.h"
#import "library/VLCLibraryUIUnits.h"
#import "library/VLCLibraryWindowChaptersSidebarViewController.h"
#import "library/VLCLibraryWindowNavigationSidebarViewController.h"
#import "library/VLCLibraryWindowPersistentPreferences.h"
#import "library/VLCLibraryWindowSidebarRootViewController.h"
#import "library/VLCLibraryWindowSplitViewController.h"
#import "library/VLCLibraryWindowToolbarDelegate.h"

#import "library/groups-library/VLCLibraryGroupsViewController.h"

#import "library/home-library/VLCLibraryHomeViewController.h"

#import "library/video-library/VLCLibraryVideoDataSource.h"
#import "library/video-library/VLCLibraryVideoViewController.h"

#import "library/audio-library/VLCLibraryAlbumTableCellView.h"
#import "library/audio-library/VLCLibraryAudioViewController.h"
#import "library/audio-library/VLCLibraryAudioDataSource.h"

#import "library/playlist-library/VLCLibraryPlaylistViewController.h"

#import "media-source/VLCMediaSourceBaseDataSource.h"
#import "media-source/VLCLibraryMediaSourceViewController.h"
#import "views/VLCBottomBarView.h"
#import "views/VLCCustomWindowButton.h"
#import "views/VLCDragDropView.h"
#import "views/VLCLoadingOverlayView.h"
#import "views/VLCNoResultsLabel.h"
#import "views/VLCRoundedCornerTextField.h"

#import "windows/controlsbar/VLCControlsBarCommon.h"

#import "windows/video/VLCVoutView.h"
#import "windows/video/VLCVideoOutputProvider.h"
#import "windows/video/VLCMainVideoViewController.h"

#import "windows/VLCDetachedAudioWindow.h"
#import "windows/VLCOpenWindowController.h"
#import "windows/VLCOpenInputMetadata.h"

#import <vlc_common.h>
#import <vlc_configuration.h>
#import <vlc_media_library.h>
#import <vlc_url.h>

const CGFloat VLCLibraryWindowMinimalWidth = 604.;
const CGFloat VLCLibraryWindowMinimalHeight = 307.;
const NSUserInterfaceItemIdentifier VLCLibraryWindowIdentifier = @"VLCLibraryWindow";

@interface VLCLibraryWindow ()
{
    NSInteger _currentSelectedViewModeSegment;
    VLCVideoWindowCommon *_temporaryAudioDecorativeWindow;
}

@property NSTimer *searchInputTimer;

@end

static int ShowFullscreenController(vlc_object_t *p_this, const char *psz_variable,
                                    vlc_value_t old_val, vlc_value_t new_val, void *param)
{
    @autoreleasepool {
        dispatch_async(dispatch_get_main_queue(), ^{
            [NSNotificationCenter.defaultCenter postNotificationName:VLCVideoWindowShouldShowFullscreenController
                                                                object:nil];
        });

        return VLC_SUCCESS;
    }
}

static int ShowController(vlc_object_t *p_this, const char *psz_variable,
                          vlc_value_t old_val, vlc_value_t new_val, void *param)
{
    @autoreleasepool {
        dispatch_async(dispatch_get_main_queue(), ^{
            [NSNotificationCenter.defaultCenter postNotificationName:VLCWindowShouldShowController
                                                                object:nil];
        });

        return VLC_SUCCESS;
    }
}

static void addShadow(NSImageView *__unsafe_unretained imageView)
{
    NSShadow *buttonShadow = [[NSShadow alloc] init];

    buttonShadow.shadowBlurRadius = 15.0f;
    buttonShadow.shadowOffset = CGSizeMake(0.0f, -5.0f);
    buttonShadow.shadowColor = [NSColor blackColor];
    imageView.wantsLayer = YES;
    imageView.shadow = buttonShadow;
}

@implementation VLCLibraryWindow

- (void)awakeFromNib
{
    [super awakeFromNib];
    self.identifier = VLCLibraryWindowIdentifier;
    self.minSize = NSMakeSize(VLCLibraryWindowMinimalWidth, VLCLibraryWindowMinimalHeight);

    if(@available(macOS 10.12, *)) {
        self.tabbingMode = NSWindowTabbingModeDisallowed;
    }

    VLCMain *mainInstance = VLCMain.sharedInstance;
    _playlistController = [mainInstance playlistController];

    libvlc_int_t *libvlc = vlc_object_instance(getIntf());
    var_AddCallback(libvlc, "intf-toggle-fscontrol", ShowFullscreenController, (__bridge void *)self);
    var_AddCallback(libvlc, "intf-show", ShowController, (__bridge void *)self);

    _libraryTargetView = [[NSView alloc] init];

    self.navigationStack = [[VLCLibraryNavigationStack alloc] init];
    self.navigationStack.delegate = self;

    self.videoViewController.view.frame = self.mainSplitView.frame;
    self.videoViewController.view.hidden = YES;
    self.videoViewController.displayLibraryControls = YES;
    [self hideControlsBarImmediately];

    NSNotificationCenter *notificationCenter = NSNotificationCenter.defaultCenter;
    [notificationCenter addObserver:self
                           selector:@selector(shouldShowController:)
                               name:VLCWindowShouldShowController
                             object:nil];
    [notificationCenter addObserver:self
                           selector:@selector(playerStateChanged:)
                               name:VLCPlayerCurrentMediaItemChanged
                             object:nil];
    [notificationCenter addObserver:self
                           selector:@selector(playerStateChanged:)
                               name:VLCPlayerStateChanged
                             object:nil];

    _libraryMediaSourceViewController = [[VLCLibraryMediaSourceViewController alloc] initWithLibraryWindow:self];

    [self setViewForSelectedSegment];
    [self setupLoadingOverlayView];
}

- (void)dealloc
{
    [NSNotificationCenter.defaultCenter removeObserver:self];
    if (@available(macOS 10.14, *)) {
        [NSApplication.sharedApplication removeObserver:self forKeyPath:@"effectiveAppearance"];
    }

    libvlc_int_t *libvlc = vlc_object_instance(getIntf());
    var_DelCallback(libvlc, "intf-toggle-fscontrol", ShowFullscreenController, (__bridge void *)self);
    var_DelCallback(libvlc, "intf-show", ShowController, (__bridge void *)self);
}

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super encodeRestorableStateWithCoder:coder];
    [coder encodeInteger:_librarySegmentType forKey:@"macosx-library-selected-segment"];
}

- (void)setupLoadingOverlayView
{
    _loadingOverlayView = [[VLCLoadingOverlayView alloc] init];
    self.loadingOverlayView.translatesAutoresizingMaskIntoConstraints = NO;
    _loadingOverlayViewConstraints = @[
        [NSLayoutConstraint constraintWithItem:self.loadingOverlayView
                                     attribute:NSLayoutAttributeTop
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:self.libraryTargetView
                                     attribute:NSLayoutAttributeTop
                                    multiplier:1
                                      constant:0],
        [NSLayoutConstraint constraintWithItem:self.loadingOverlayView
                                     attribute:NSLayoutAttributeRight
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:self.libraryTargetView
                                     attribute:NSLayoutAttributeRight
                                    multiplier:1
                                      constant:0],
        [NSLayoutConstraint constraintWithItem:self.loadingOverlayView
                                     attribute:NSLayoutAttributeBottom
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:self.libraryTargetView
                                     attribute:NSLayoutAttributeBottom
                                    multiplier:1
                                      constant:0],
        [NSLayoutConstraint constraintWithItem:self.loadingOverlayView
                                     attribute:NSLayoutAttributeLeft
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:self.libraryTargetView
                                     attribute:NSLayoutAttributeLeft
                                    multiplier:1
                                      constant:0]
    ];
}

#pragma mark - misc. user interactions

- (void)updateGridVsListViewModeSegmentedControl
{
    VLCLibraryWindowPersistentPreferences * const preferences = VLCLibraryWindowPersistentPreferences.sharedInstance;

    switch (_librarySegmentType) {
    case VLCLibraryHomeSegment:
        _currentSelectedViewModeSegment = preferences.homeLibraryViewMode;
    case VLCLibraryVideoSegment:
        _currentSelectedViewModeSegment = preferences.videoLibraryViewMode;
        break;
    case VLCLibraryShowsVideoSubSegment:
        _currentSelectedViewModeSegment = preferences.showsLibraryViewMode;
        break;
    case VLCLibraryMusicSegment:
    case VLCLibraryArtistsMusicSubSegment:
        _currentSelectedViewModeSegment = preferences.artistLibraryViewMode;
        break;
    case VLCLibraryGenresMusicSubSegment:
        _currentSelectedViewModeSegment = preferences.genreLibraryViewMode;
        break;
    case VLCLibraryAlbumsMusicSubSegment:
        _currentSelectedViewModeSegment = preferences.albumLibraryViewMode;
        break;
    case VLCLibrarySongsMusicSubSegment:
        _currentSelectedViewModeSegment = preferences.songsLibraryViewMode;
        break;
    case VLCLibraryPlaylistsSegment:
        _currentSelectedViewModeSegment = preferences.playlistLibraryViewMode;
        break;
    case VLCLibraryPlaylistsMusicOnlyPlaylistsSubSegment:
        _currentSelectedViewModeSegment = preferences.musicOnlyPlaylistLibraryViewMode;
        break;
    case VLCLibraryPlaylistsVideoOnlyPlaylistsSubSegment:
        _currentSelectedViewModeSegment = preferences.videoOnlyPlaylistLibraryViewMode;
        break;
    case VLCLibraryBrowseSegment:
        _currentSelectedViewModeSegment = preferences.browseLibraryViewMode;
        break;
    case VLCLibraryStreamsSegment:
        _currentSelectedViewModeSegment = preferences.streamLibraryViewMode;
        break;
    case VLCLibraryGroupsSegment:
    case VLCLibraryGroupsGroupSubSegment:
        _currentSelectedViewModeSegment = preferences.groupsLibraryViewMode;
        break;
    default:
        break;
    }

    _gridVsListSegmentedControl.selectedSegment = _currentSelectedViewModeSegment;
}

- (void)setViewForSelectedSegment
{
    switch (_librarySegmentType) {
    case VLCLibraryHomeSegment:
        [self showHomeLibrary];
        break;
    case VLCLibraryVideoSegment:
        [self showVideoLibrary];
        break;
    case VLCLibraryShowsVideoSubSegment:
        [self showShowLibrary];
        break;
    case VLCLibraryMusicSegment:
    case VLCLibraryArtistsMusicSubSegment:
    case VLCLibraryAlbumsMusicSubSegment:
    case VLCLibrarySongsMusicSubSegment:
    case VLCLibraryGenresMusicSubSegment:
        [self showAudioLibrary];
        break;
    case VLCLibraryPlaylistsSegment:
        [self showPlaylistLibrary:VLC_ML_PLAYLIST_TYPE_ALL];
        break;
    case VLCLibraryPlaylistsMusicOnlyPlaylistsSubSegment:
        [self showPlaylistLibrary:VLC_ML_PLAYLIST_TYPE_AUDIO_ONLY];
        break;
    case VLCLibraryPlaylistsVideoOnlyPlaylistsSubSegment:
        [self showPlaylistLibrary:VLC_ML_PLAYLIST_TYPE_VIDEO_ONLY];
        break;
    case VLCLibraryBrowseSegment:
    case VLCLibraryBrowseBookmarkedLocationSubSegment:
    case VLCLibraryStreamsSegment:
        [self showMediaSourceLibrary];
        break;
    case VLCLibraryGroupsSegment:
    case VLCLibraryGroupsGroupSubSegment:
        [self showGroupsLibrary];
    default:
        break;
    }

    [self invalidateRestorableState];
}

- (void)setLibrarySegmentType:(NSInteger)segmentType
{
    if (segmentType == _librarySegmentType) {
        return;
    }
    _librarySegmentType = segmentType;
    [self setViewForSelectedSegment];
    [self updateGridVsListViewModeSegmentedControl];
}

- (IBAction)gridVsListSegmentedControlAction:(id)sender
{
    if (_gridVsListSegmentedControl.selectedSegment == _currentSelectedViewModeSegment) {
        return;
    }

    _currentSelectedViewModeSegment = _gridVsListSegmentedControl.selectedSegment;

    VLCLibraryWindowPersistentPreferences * const preferences = VLCLibraryWindowPersistentPreferences.sharedInstance;

    switch (_librarySegmentType) {
    case VLCLibraryHomeSegment:
        preferences.homeLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryVideoSegment:
        preferences.videoLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryShowsVideoSubSegment:
        preferences.showsLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryMusicSegment:
    case VLCLibraryArtistsMusicSubSegment:
        preferences.artistLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryGenresMusicSubSegment:
        preferences.genreLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryAlbumsMusicSubSegment:
        preferences.albumLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibrarySongsMusicSubSegment:
        preferences.songsLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryPlaylistsSegment:
        preferences.playlistLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryPlaylistsMusicOnlyPlaylistsSubSegment:
        preferences.musicOnlyPlaylistLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryPlaylistsVideoOnlyPlaylistsSubSegment:
        preferences.videoOnlyPlaylistLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryBrowseSegment:
    case VLCLibraryBrowseBookmarkedLocationSubSegment:
        preferences.browseLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryStreamsSegment:
        preferences.streamLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    case VLCLibraryGroupsSegment:
    case VLCLibraryGroupsGroupSubSegment:
        preferences.groupsLibraryViewMode = _currentSelectedViewModeSegment;
        break;
    default:
        break;
    }

    [self setViewForSelectedSegment];
}

- (void)showHomeLibrary
{
    // Only collection view mode
    [self.toolbarDelegate layoutForSegment:VLCLibraryHomeSegment];
    VLCLibraryHomeViewController * const lvc =
        [[VLCLibraryHomeViewController alloc] initWithLibraryWindow:self];
    [lvc presentHomeView];
    _librarySegmentViewController = lvc;
}

- (void)showVideoLibrary
{
    [self.toolbarDelegate layoutForSegment:VLCLibraryVideoSegment];
    VLCLibraryVideoViewController * const lvc =
        [[VLCLibraryVideoViewController alloc] initWithLibraryWindow:self];
    [lvc presentVideoView];
    _librarySegmentViewController = lvc;
}

- (void)showShowLibrary
{
    [self.toolbarDelegate layoutForSegment:VLCLibraryShowsVideoSubSegment];
    VLCLibraryVideoViewController * const lvc =
        [[VLCLibraryVideoViewController alloc] initWithLibraryWindow:self];
    [lvc presentShowsView];
    _librarySegmentViewController = lvc;
}

- (void)showAudioLibrary
{
    [self.toolbarDelegate layoutForSegment:VLCLibraryMusicSegment];
    VLCLibraryAudioViewController * const lvc =
        [[VLCLibraryAudioViewController alloc] initWithLibraryWindow:self];
    [lvc presentAudioView];
    _librarySegmentViewController = lvc;
}

- (void)showPlaylistLibrary:(enum vlc_ml_playlist_type_t)playlistType
{
    if (playlistType == VLC_ML_PLAYLIST_TYPE_AUDIO_ONLY) {
        [self.toolbarDelegate layoutForSegment:VLCLibraryPlaylistsMusicOnlyPlaylistsSubSegment];
    } else if (playlistType == VLC_ML_PLAYLIST_TYPE_VIDEO_ONLY) {
        [self.toolbarDelegate layoutForSegment:VLCLibraryPlaylistsVideoOnlyPlaylistsSubSegment];
    } else {
        [self.toolbarDelegate layoutForSegment:VLCLibraryPlaylistsSegment];
    }
    VLCLibraryPlaylistViewController * const lvc =
        [[VLCLibraryPlaylistViewController alloc] initWithLibraryWindow:self];
    [lvc presentPlaylistsViewForPlaylistType:playlistType];
    _librarySegmentViewController = lvc;
}

- (void)showMediaSourceLibrary
{
    [self.navigationStack clear];

    const VLCLibrarySegmentType segmentType = self.librarySegmentType;
    [self.toolbarDelegate layoutForSegment:segmentType];

    if (segmentType == VLCLibraryBrowseSegment) {
        [self.libraryMediaSourceViewController presentBrowseView];
    } else if (segmentType == VLCLibraryStreamsSegment) {
        [self.libraryMediaSourceViewController presentStreamsView];
    }
}

- (void)showGroupsLibrary
{
    [self.toolbarDelegate layoutForSegment:VLCLibraryGroupsSegment];
    VLCLibraryGroupsViewController * const lvc =
        [[VLCLibraryGroupsViewController alloc] initWithLibraryWindow:self];
    [lvc presentGroupsView];
    _librarySegmentViewController = lvc;
}
- (void)displayLibraryView:(NSView *)view
{
    view.translatesAutoresizingMaskIntoConstraints = NO;
    if ([self.libraryTargetView.subviews containsObject:self.loadingOverlayView]) {
        self.libraryTargetView.subviews = @[view, self.loadingOverlayView];
    } else {
        self.libraryTargetView.subviews = @[view];
    }

    [NSLayoutConstraint activateConstraints:@[
        [view.topAnchor constraintEqualToAnchor:self.libraryTargetView.topAnchor],
        [view.bottomAnchor constraintEqualToAnchor:self.libraryTargetView.bottomAnchor],
        [view.leftAnchor constraintEqualToAnchor:self.libraryTargetView.leftAnchor],
        [view.rightAnchor constraintEqualToAnchor:self.libraryTargetView.rightAnchor]
    ]];
}

- (void)displayLibraryPlaceholderViewWithImage:(NSImage *)image
                              usingConstraints:(NSArray<NSLayoutConstraint *> *)constraints
                             displayingMessage:(NSString *)message
{
    for (NSLayoutConstraint * const constraint in self.placeholderImageViewConstraints) {
        constraint.active = NO;
    }
    _placeholderImageViewConstraints = constraints;
    for (NSLayoutConstraint * const constraint in constraints) {
        constraint.active = YES;
    }

    [self displayLibraryView:self.emptyLibraryView];
    self.placeholderImageView.image = image;
    self.placeholderLabel.stringValue = message;
}

- (void)displayNoResultsMessage
{
    if (self.noResultsLabel == nil) {
        _noResultsLabel = [[VLCNoResultsLabel alloc] init];
        _noResultsLabel.translatesAutoresizingMaskIntoConstraints = NO;
    }
    
    if ([self.libraryTargetView.subviews containsObject:self.loadingOverlayView]) {
        self.libraryTargetView.subviews = @[self.noResultsLabel, self.loadingOverlayView];
    } else {
        self.libraryTargetView.subviews = @[_noResultsLabel];
    }

    [NSLayoutConstraint activateConstraints:@[
        [self.noResultsLabel.centerXAnchor constraintEqualToAnchor:self.libraryTargetView.centerXAnchor],
        [self.noResultsLabel.centerYAnchor constraintEqualToAnchor:self.libraryTargetView.centerYAnchor]
    ]];
}

- (void)presentAudioLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
{
    [self showAudioLibrary];
    [(VLCLibraryAudioViewController *)self.librarySegmentViewController presentLibraryItem:libraryItem];
}

- (void)presentVideoLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
{
    [self showVideoLibrary];
    [(VLCLibraryVideoViewController *)self.librarySegmentViewController presentLibraryItem:libraryItem];
}

- (void)presentGroupLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
{
    [self showGroupsLibrary];
    [(VLCLibraryAudioViewController *)self.librarySegmentViewController presentLibraryItem:libraryItem];
}

- (void)presentLibraryItem:(id<VLCMediaLibraryItemProtocol>)libraryItem
{
    const BOOL isAudioGroup = [libraryItem isKindOfClass:VLCMediaLibraryAlbum.class] ||
                              [libraryItem isKindOfClass:VLCMediaLibraryArtist.class] ||
                              [libraryItem isKindOfClass:VLCMediaLibraryGenre.class];

    if (isAudioGroup) {
        [self presentAudioLibraryItem:libraryItem];
        return;
    } else if ([libraryItem isKindOfClass:VLCMediaLibraryGroup.class]) {
        [self presentGroupLibraryItem:libraryItem];
        return;
    }

    VLCMediaLibraryMediaItem * const mediaItem = (VLCMediaLibraryMediaItem *)libraryItem;
    const BOOL validMediaItem = mediaItem != nil;
    if (validMediaItem && mediaItem.mediaType == VLC_ML_MEDIA_TYPE_AUDIO) {
        [self presentAudioLibraryItem:libraryItem];
        return;
    } else if (validMediaItem && mediaItem.mediaType == VLC_ML_MEDIA_TYPE_VIDEO) {
        [self presentVideoLibraryItem:libraryItem];
        return;
    }

    NSLog(@"Unknown kind of library item provided, cannot present library view for it: %@", libraryItem.displayString);
}

- (void)goToLocalFolderMrl:(NSString *)mrl
{
    [self goToBrowseSection:self];
    [self.libraryMediaSourceViewController presentLocalFolderMrl:mrl];
}

- (IBAction)sortLibrary:(id)sender
{
    if (!_librarySortingMenuController) {
        _librarySortingMenuController = [[VLCLibrarySortingMenuController alloc] init];
    }
    [NSMenu popUpContextMenu:_librarySortingMenuController.librarySortingMenu withEvent:[NSApp currentEvent] forView:sender];
}

- (void)stopSearchTimer
{
    [self.searchInputTimer invalidate];
    self.searchInputTimer = nil;
}

- (IBAction)filterLibrary:(id)sender
{
    [self stopSearchTimer];
    self.searchInputTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
                                                            target:self
                                                           selector:@selector(updateFilterString)
                                                           userInfo:nil
                                                            repeats:NO];
}

- (void)updateFilterString
{
    [VLCMain.sharedInstance.libraryController filterByString:_librarySearchField.stringValue];
}

- (void)clearFilterString
{
    [self stopSearchTimer];
    _librarySearchField.stringValue = @"";
    [self updateFilterString];
}
- (BOOL)handlePasteBoardFromDragSession:(NSPasteboard *)paste
{
    id propertyList = [paste propertyListForType:NSFilenamesPboardType];
    if (propertyList == nil) {
        return NO;
    }

    NSArray *values = [propertyList sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
    NSUInteger valueCount = [values count];
    if (valueCount > 0) {
        NSMutableArray *metadataArray = [NSMutableArray arrayWithCapacity:valueCount];

        for (NSString *filepath in values) {
            VLCOpenInputMetadata *inputMetadata;

            inputMetadata = [VLCOpenInputMetadata inputMetaWithPath:filepath];
            if (!inputMetadata)
                continue;

            [metadataArray addObject:inputMetadata];
        }
        [_playlistController addPlaylistItems:metadataArray];

        return YES;
    }

    return NO;
}

- (IBAction)goToBrowseSection:(id)sender
{
    [self.splitViewController.navSidebarViewController selectSegment:VLCLibraryBrowseSegment];
}

- (IBAction)backwardsNavigationAction:(id)sender
{
    self.videoViewController.view.hidden ? [_navigationStack backwards] : [self disableVideoPlaybackAppearance];
}

- (IBAction)forwardsNavigationAction:(id)sender
{
    [_navigationStack forwards];
}

#pragma mark - video output controlling

- (void)setHasActiveVideo:(BOOL)hasActiveVideo
{
    [super setHasActiveVideo:hasActiveVideo];
    if (hasActiveVideo) {
        [self enableVideoPlaybackAppearance];
    } else if (!self.videoViewController.view.hidden) {
        // If we are switching to audio media then keep the active main video view open
        NSURL * const currentMediaUrl = _playlistController.playerController.URLOfCurrentMediaItem;
        VLCMediaLibraryMediaItem * const mediaItem = [VLCMediaLibraryMediaItem mediaItemForURL:currentMediaUrl];
        const BOOL decorativeViewVisible = mediaItem != nil && mediaItem.mediaType == VLC_ML_MEDIA_TYPE_AUDIO;

        if (!decorativeViewVisible) {
            [self disableVideoPlaybackAppearance];
        }
    } else {
        [self disableVideoPlaybackAppearance];
    }
}

- (void)playerStateChanged:(NSNotification *)notification
{
    if (_playlistController.playerController.playerState == VLC_PLAYER_STATE_STOPPED) {
        [self hideControlsBar];
        return;
    }

    if (self.videoViewController.view.isHidden) {
        [self showControlsBar];
    }
}

// This handles reopening the video view when the user has closed it.
- (void)reopenVideoView
{
    [self enableVideoPlaybackAppearance];
}

- (void)hideControlsBarImmediately
{
    self.controlsBarHeightConstraint.constant = 0;
}

- (void)hideControlsBar
{
    [NSAnimationContext runAnimationGroup:^(NSAnimationContext * const context) {
        context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
        context.duration = VLCLibraryUIUnits.controlsFadeAnimationDuration;
        self.controlsBarHeightConstraint.animator.constant = 0;
    } completionHandler:nil];
}

- (void)showControlsBarImmediately
{
    self.controlsBarHeightConstraint.constant = VLCLibraryUIUnits.libraryWindowControlsBarHeight;
}

- (void)showControlsBar
{
    [NSAnimationContext runAnimationGroup:^(NSAnimationContext * const context) {
        context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
        context.duration = VLCLibraryUIUnits.controlsFadeAnimationDuration;
        self.controlsBarHeightConstraint.animator.constant = VLCLibraryUIUnits.libraryWindowControlsBarHeight;
    } completionHandler:nil];
}

- (void)presentExternalWindows
{
    VLCVideoOutputProvider * const voutProvider = VLCMain.sharedInstance.voutProvider;
    NSArray<NSWindow *> * const voutWindows = voutProvider.voutWindows.allValues;

    if (voutWindows.count == 0 && self.playerController.videoTracks.count == 0) {
        // If we have no video windows in the video provider but are being asked to present a window
        // then we are dealing with an audio item and the user wants to see the decorative artwork
        // window for said audio
        [VLCMain.sharedInstance.detachedAudioWindow makeKeyAndOrderFront:self];
        return;
    }

    for (NSWindow * const window in voutWindows) {
        [window makeKeyAndOrderFront:self];
    }
}

- (void)presentVideoView
{
    for (NSView *subview in _libraryTargetView.subviews) {
        [subview removeFromSuperview];
    }

    NSLog(@"Presenting video view in main library window.");

    NSView *videoView = self.videoViewController.view;
    videoView.translatesAutoresizingMaskIntoConstraints = NO;
    videoView.hidden = NO;

    [_libraryTargetView addSubview:videoView];
    NSDictionary *dict = NSDictionaryOfVariableBindings(videoView);
    [_libraryTargetView addConstraints:@[
        [NSLayoutConstraint constraintWithItem:videoView
                                     attribute:NSLayoutAttributeTop
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:_libraryTargetView
                                     attribute:NSLayoutAttributeTop
                                    multiplier:1.
                                      constant:0.],
        [NSLayoutConstraint constraintWithItem:videoView
                                     attribute:NSLayoutAttributeBottom
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:_libraryTargetView
                                     attribute:NSLayoutAttributeBottom
                                    multiplier:1.
                                      constant:0.],
        [NSLayoutConstraint constraintWithItem:videoView
                                     attribute:NSLayoutAttributeLeft
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:_libraryTargetView
                                     attribute:NSLayoutAttributeLeft
                                    multiplier:1.
                                      constant:0.],
        [NSLayoutConstraint constraintWithItem:videoView
                                     attribute:NSLayoutAttributeRight
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:_libraryTargetView
                                     attribute:NSLayoutAttributeRight
                                    multiplier:1.
                                      constant:0.]
    ]];
}

- (void)enableVideoPlaybackAppearance
{
    const BOOL isEmbedded = var_InheritBool(getIntf(), "embedded-video");
    if (!isEmbedded) {
        [self presentExternalWindows];
        return;
    }

    [self presentVideoView];
    [self enableVideoTitleBarMode];
    [self hideControlsBarImmediately];
    [self.videoViewController showControls];

    self.splitViewController.multifunctionSidebarViewController.mainVideoModeEnabled = YES;

    [self.librarySegmentViewController disconnect];
}

- (void)disableVideoPlaybackAppearance
{
    [self makeFirstResponder:self.splitViewController.multifunctionSidebarViewController.view];
    [VLCMain.sharedInstance.voutProvider updateWindowLevelForHelperWindows:NSNormalWindowLevel];

    // restore alpha value to 1 for the case that macosx-opaqueness is set to < 1
    self.alphaValue = 1.0;
    self.videoViewController.view.hidden = YES;
    [self setViewForSelectedSegment];
    [self disableVideoTitleBarMode];
    [self showControlsBarImmediately];
    self.splitViewController.multifunctionSidebarViewController.mainVideoModeEnabled = NO;
}

- (void)showLoadingOverlay
{
    if ([self.libraryTargetView.subviews containsObject:self.loadingOverlayView]) {
        return;
    }

    self.loadingOverlayView.wantsLayer = YES;
    self.loadingOverlayView.alphaValue = 0.0;

    NSArray * const views = [self.libraryTargetView.subviews arrayByAddingObject:self.loadingOverlayView];
    self.libraryTargetView.subviews = views;
    [self.libraryTargetView addConstraints:self.loadingOverlayViewConstraints];

    [NSAnimationContext runAnimationGroup:^(NSAnimationContext * const context) {
        context.duration = 0.5;
        self.loadingOverlayView.animator.alphaValue = 1.0;
    } completionHandler:nil];
    [self.loadingOverlayView.indicator startAnimation:self];

}

- (void)hideLoadingOverlay
{
    if (![self.libraryTargetView.subviews containsObject:self.loadingOverlayView]) {
        return;
    }

    self.loadingOverlayView.wantsLayer = YES;
    self.loadingOverlayView.alphaValue = 1.0;

    [NSAnimationContext runAnimationGroup:^(NSAnimationContext * const context) {
        context.duration = 1.0;
        self.loadingOverlayView.animator.alphaValue = 0.0;
    } completionHandler:^{
        [self.libraryTargetView removeConstraints:self.loadingOverlayViewConstraints];
        NSMutableArray * const views = self.libraryTargetView.subviews.mutableCopy;
        [views removeObject:self.loadingOverlayView];
        self.libraryTargetView.subviews = views.copy;
        [self.loadingOverlayView.indicator stopAnimation:self];
    }];
}

- (void)mouseMoved:(NSEvent *)o_event
{
    if (!self.videoViewController.view.hidden) {
        NSPoint mouseLocation = [o_event locationInWindow];
        NSView *videoView = self.videoViewController.view;
        NSRect videoViewRect = [videoView convertRect:videoView.frame toView:self.contentView];

        if ([self.contentView mouse:mouseLocation inRect:videoViewRect]) {
            [NSNotificationCenter.defaultCenter postNotificationName:VLCVideoWindowShouldShowFullscreenController
                                                                object:self];
        }
    }

    [super mouseMoved:o_event];
}

#pragma mark -
#pragma mark respond to core events

- (void)shouldShowController:(NSNotification *)aNotification
{
    [self makeKeyAndOrderFront:nil];

    if (self.videoViewController.view.isHidden) {
        [self showControlsBar];
        NSView *standardWindowButtonsSuperView = [self standardWindowButton:NSWindowCloseButton].superview;
        standardWindowButtonsSuperView.hidden = NO;
    }
}

- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
    [super windowWillEnterFullScreen:notification];

    if (!self.videoViewController.view.hidden) {
        [self hideControlsBar];
    }
}

- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
    [super windowDidEnterFullScreen:notification];
    if (!self.videoViewController.view.hidden) {
        [self showControlsBar];
    }
}

@end