Skip to content

Commit

Permalink
macos: gui: Add Picture in Picture feature
Browse files Browse the repository at this point in the history
This adds system picture in picture support using private AVKit's PIPViewController.

A new button to enter picture in picture mode is added in the player controls overlay.
  • Loading branch information
umxprime-vlabs authored and fkuehne committed Aug 18, 2024
1 parent e981199 commit f697fef
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 21 deletions.
10 changes: 10 additions & 0 deletions extras/package/macosx/VLC.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@
6BF56C3D1FCF00AF004A411A /* audiotoolbox_midi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audiotoolbox_midi.c; path = ../../../modules/codec/audiotoolbox_midi.c; sourceTree = "<group>"; };
6BF5C5021EFE66EF008A9C12 /* VLCHUDTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCHUDTableView.h; sourceTree = "<group>"; };
6BF5C5031EFE66EF008A9C12 /* VLCHUDTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCHUDTableView.m; sourceTree = "<group>"; };
6CEA55642C6BA1BE00CCC2E7 /* PIPSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PIPSPI.h; sourceTree = "<group>"; };
7D0A387820CBCC4D00D4BF3B /* videotoolbox.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = videotoolbox.c; path = ../../../modules/codec/videotoolbox.c; sourceTree = "<group>"; };
7D0F5A992264EB410009C48A /* VLCHotkeysController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VLCHotkeysController.h; sourceTree = "<group>"; };
7D0F5A9A2264EB410009C48A /* VLCHotkeysController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VLCHotkeysController.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -990,6 +991,7 @@
1C1ED5032204A99400811EC0 /* panels */,
1C1ED5142205A96600811EC0 /* playlist */,
1C1ED5102204B06700811EC0 /* preferences */,
6CEA55632C6BA14300CCC2E7 /* private */,
1C1ED5062204AB7C00811EC0 /* views */,
1C1ED5072204AC5900811EC0 /* windows */,
);
Expand Down Expand Up @@ -1789,6 +1791,14 @@
name = codec;
sourceTree = "<group>";
};
6CEA55632C6BA14300CCC2E7 /* private */ = {
isa = PBXGroup;
children = (
6CEA55642C6BA1BE00CCC2E7 /* PIPSPI.h */,
);
path = private;
sourceTree = "<group>";
};
7D0A387620CBCC2F00D4BF3B /* aout */ = {
isa = PBXGroup;
children = (
Expand Down
1 change: 1 addition & 0 deletions modules/gui/macosx/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ xib_verbose__0 = $(xib_verbose_0)

libmacosx_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) -fobjc-exceptions -fobjc-arc -I$(srcdir)/gui/macosx
libmacosx_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(guidir)' \
-Wl,-framework,AVKit \
-Wl,-framework,AVFoundation \
-Wl,-framework,Cocoa \
-Wl,-framework,CoreAudio \
Expand Down
67 changes: 49 additions & 18 deletions modules/gui/macosx/UI/VLCMainVideoView.xib

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions modules/gui/macosx/playlist/VLCPlayerController.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ extern NSString *VLCPlayerTrackSelectionChanged;
*/
extern NSString *VLCPlayerFullscreenChanged;

extern NSString *VLCPlayerPictureInPictureChanged;

/**
* Listen to VLCPlayerListOfVideoOutputThreadsChanged to be notified when a video output thread was added or removed
* @note the affected player object will be the object of the notification
Expand Down Expand Up @@ -806,6 +808,9 @@ extern const CGFloat VLCVolumeDefault;
*/
- (void)toggleFullscreen;


- (void)togglePictureInPicture;

/**
* indicates whether video is displaed in wallpaper mode or shall to
* @note listen to VLCPlayerWallpaperModeChanged to be notified about changes to this property
Expand Down
7 changes: 7 additions & 0 deletions modules/gui/macosx/playlist/VLCPlayerController.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
NSString *VLCPlayerTrackListChanged = @"VLCPlayerTrackListChanged";
NSString *VLCPlayerTrackSelectionChanged = @"VLCPlayerTrackSelectionChanged";
NSString *VLCPlayerFullscreenChanged = @"VLCPlayerFullscreenChanged";
NSString *VLCPlayerPictureInPictureChanged = @"VLCPlayerPictureInPictureChanged";
NSString *VLCPlayerWallpaperModeChanged = @"VLCPlayerWallpaperModeChanged";
NSString *VLCPlayerListOfVideoOutputThreadsChanged = @"VLCPlayerListOfVideoOutputThreadsChanged";
NSString *VLCPlayerVolumeChanged = @"VLCPlayerVolumeChanged";
Expand Down Expand Up @@ -1611,6 +1612,12 @@ - (void)toggleFullscreen
vlc_player_vout_SetFullscreen(_p_player, !_fullscreen);
}

- (void)togglePictureInPicture
{
[_defaultNotificationCenter postNotificationName:VLCPlayerPictureInPictureChanged
object:self];
}

- (void)wallpaperModeChanged:(BOOL)wallpaperModeValue
{
_wallpaperMode = wallpaperModeValue;
Expand Down
50 changes: 50 additions & 0 deletions modules/gui/macosx/private/PIPSPI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*****************************************************************************
* PIPSPI.h: Picture in Picture private API
*****************************************************************************
* Copyright (C) 2024 VLC authors and VideoLAN
*
* Authors: Maxime Chapelet <umxprime at videolabs dot io>
*
* 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.
*****************************************************************************/

NS_ASSUME_NONNULL_BEGIN

@protocol PIPViewControllerDelegate;

@interface PIPViewController : NSViewController

@property (nonatomic, weak, nullable) id<PIPViewControllerDelegate> delegate;
@property (nonatomic, weak, nullable) NSWindow *replacementWindow;
@property (nonatomic) NSRect replacementRect;
@property (nonatomic) bool playing;
@property (nonatomic) bool userCanResize;
@property (nonatomic) NSSize aspectRatio;

- (void)presentViewControllerAsPictureInPicture:(NSViewController *)viewController;

@end

@protocol PIPViewControllerDelegate <NSObject>
@optional
- (BOOL)pipShouldClose:(PIPViewController *)pip;
- (void)pipWillClose:(PIPViewController *)pip;
- (void)pipDidClose:(PIPViewController *)pip;
- (void)pipActionPlay:(PIPViewController *)pip;
- (void)pipActionPause:(PIPViewController *)pip;
- (void)pipActionStop:(PIPViewController *)pip;
@end

NS_ASSUME_NONNULL_END
4 changes: 4 additions & 0 deletions modules/gui/macosx/windows/controlsbar/VLCControlsBarCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
@property (readwrite, strong) IBOutlet NSButton *fullscreenButton;
@property (readwrite, strong) IBOutlet NSLayoutConstraint *fullscreenButtonWidthConstraint;

@property (readwrite, strong) IBOutlet NSButton *pipButton;
@property (readwrite, strong) IBOutlet NSLayoutConstraint *pipButtonWidthConstraint;

@property (readwrite, strong) IBOutlet VLCBottomBarView *bottomBarView;

@property (readonly) BOOL nativeFullscreenMode;
Expand All @@ -73,6 +76,7 @@
- (IBAction)timeSliderAction:(id)sender;
- (IBAction)volumeAction:(id)sender;
- (IBAction)fullscreen:(id)sender;
- (IBAction)onPipButtonClick:(id)sender;

- (void)update;
- (void)updateMuteVolumeButtonImage;
Expand Down
13 changes: 12 additions & 1 deletion modules/gui/macosx/windows/controlsbar/VLCControlsBarCommon.m
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/*****************************************************************************
* VLCControlsBarCommon.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2012-2019 VLC authors and VideoLAN
* Copyright (C) 2024 VLC authors and VideoLAN
*
* Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
* David Fuhrmann <david dot fuhrmann at googlemail dot com>
* Maxime Chapelet <umxprime at videolabs dot io>
*
* 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
Expand Down Expand Up @@ -213,6 +214,11 @@ - (void)awakeFromNib
[_artworkImageView setCropsImagesToRoundedCorners:YES];
[_artworkImageView setImage:[NSImage imageNamed:@"noart"]];
[_artworkImageView setContentGravity:VLCImageViewContentGravityResize];

if (!NSClassFromString(@"PIPViewController")) {
self.pipButtonWidthConstraint.constant = 0;
self.pipButton.hidden = YES;
}

// Update verything post-init
[self update];
Expand Down Expand Up @@ -352,6 +358,11 @@ - (IBAction)fullscreen:(id)sender
[_playerController toggleFullscreen];
}

- (IBAction)onPipButtonClick:(id)sender
{
[_playerController togglePictureInPicture];
}

#pragma mark -
#pragma mark Updaters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
NS_ASSUME_NONNULL_BEGIN

@interface VLCMainVideoViewController : NSViewController
@property (readwrite, strong) IBOutlet NSView *voutContainingView;

@property (readwrite, strong) IBOutlet VLCVoutView *voutView;
@property (readwrite, strong) IBOutlet NSBox *mainControlsView;
Expand Down
121 changes: 119 additions & 2 deletions modules/gui/macosx/windows/video/VLCMainVideoViewController.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/*****************************************************************************
* VLCMainVideoViewController.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2023 VLC authors and VideoLAN
* Copyright (C) 2024 VLC authors and VideoLAN
*
* Authors: Claudio Cambra <[email protected]>
* Maxime Chapelet <umxprime at videolabs dot io>
*
* 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
Expand Down Expand Up @@ -47,11 +48,44 @@

#import <vlc_common.h>

@interface VLCMainVideoViewController()
#import "private/PIPSPI.h"

@interface PIPVoutViewController : NSViewController
@end

@implementation PIPVoutViewController

- (void)setView:(NSView *)view {
[super setView:view];
}

- (void)viewDidLoad {
[super viewDidLoad];
}

- (void)viewWillAppear {
[super viewWillAppear];

if (self.view.superview) {
[self.view.superview.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
[self.view.superview.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
[self.view.superview.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
[self.view.superview.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;
}
}

- (void)viewDidAppear {
[super viewDidAppear];
}
@end

@interface VLCMainVideoViewController() <PIPViewControllerDelegate>
{
NSTimer *_hideControlsTimer;
NSLayoutConstraint *_returnButtonBottomConstraint;
NSLayoutConstraint *_playlistButtonBottomConstraint;
PIPViewController *_pipViewController;
PIPVoutViewController *_voutViewController;

BOOL _isFadingIn;
}
Expand Down Expand Up @@ -86,6 +120,15 @@ - (instancetype)init
selector:@selector(shouldShowControls:)
name:VLCVideoWindowShouldShowFullscreenController
object:nil];
[notificationCenter addObserver:self
selector:@selector(pictureInPictureChanged:)
name:VLCPlayerPictureInPictureChanged
object:nil];

Class PIPViewControllerClass = NSClassFromString(@"PIPViewController");
_pipViewController = [[PIPViewControllerClass alloc] init];
_pipViewController.delegate = self;
_pipViewController.userCanResize = true;
}
return self;
}
Expand Down Expand Up @@ -456,6 +499,37 @@ - (void)updateLibraryControls
[_overlayView setNeedsDisplay:YES];
}

- (void)pictureInPictureChanged:(VLCPlayerController *)playerController {
if (_voutViewController)
return;
[self.view.window orderOut:self.view.window];
_voutViewController = [PIPVoutViewController new];
_voutViewController.view = _voutView;
VLCPlayerController * const controller =
VLCMain.sharedInstance.playlistController.playerController;
_pipViewController.playing = controller.playerState == VLC_PLAYER_STATE_PLAYING;

VLCInputItem *item = controller.currentMedia;
input_item_t * const p_input = item.vlcInputItem;
vlc_mutex_lock(&p_input->lock);
const struct input_item_es *item_es;
vlc_vector_foreach_ref(item_es, &p_input->es_vec)
{
if (item_es->es.i_cat != VIDEO_ES)
continue;
const video_format_t *fmt = &item_es->es.video;
unsigned int width = fmt->i_visible_width;
unsigned int height = fmt->i_visible_height;
if (fmt->i_sar_num && fmt->i_sar_den)
height = (height * fmt->i_sar_den) / fmt->i_sar_num;
_pipViewController.aspectRatio = CGSizeMake(width, height);
break;
}
vlc_mutex_unlock(&p_input->lock);
_pipViewController.title = self.view.window.title;
[_pipViewController presentViewControllerAsPictureInPicture:_voutViewController];
}

- (IBAction)togglePlaylist:(id)sender
{
VLCLibraryWindow * const libraryWindow = (VLCLibraryWindow*)self.view.window;
Expand All @@ -471,5 +545,48 @@ - (IBAction)returnToLibrary:(id)sender
[libraryWindow disableVideoPlaybackAppearance];
}
}
#pragma mark - PIPViewControllerDelegate

- (BOOL)pipShouldClose:(PIPViewController *)pip {
return YES;
}

- (void)pipWillClose:(PIPViewController *)pip {
[_voutView removeFromSuperview];
[_voutContainingView addSubview:_voutView];
[_voutContainingView.topAnchor constraintEqualToAnchor:_voutView.topAnchor].active = YES;
[_voutContainingView.bottomAnchor constraintEqualToAnchor:_voutView.bottomAnchor].active = YES;
[_voutContainingView.leftAnchor constraintEqualToAnchor:_voutView.leftAnchor].active = YES;
[_voutContainingView.rightAnchor constraintEqualToAnchor:_voutView.rightAnchor].active = YES;
_voutViewController = nil;
pip.replacementWindow = self.view.window;
pip.replacementRect = self.voutContainingView.frame;
}

- (void)pipDidClose:(PIPViewController *)pip {
[self.view.window orderFront:self.view.window];
}

- (void)pipActionPlay:(PIPViewController *)pip {
VLCPlayerController * const controller =
VLCMain.sharedInstance.playlistController.playerController;
if (controller.playerState == VLC_PLAYER_STATE_PAUSED) {
[controller resume];
} else {
[controller start];
}
}

- (void)pipActionStop:(PIPViewController *)pip {
VLCPlayerController * const controller =
VLCMain.sharedInstance.playlistController.playerController;
[controller pause];
}

- (void)pipActionPause:(PIPViewController *)pip {
VLCPlayerController * const controller =
VLCMain.sharedInstance.playlistController.playerController;
[controller pause];
}

@end

0 comments on commit f697fef

Please sign in to comment.