If you’ve tried to make a video full screen in a Mac Catalyst app (for example Apple’s Developer app), you may have noticed that the video only maximises to the size of the window.
As can be seen in the iPad screenshot, it’s not just macOS. On iOS videos are also restricted to the size of the window.
However on macOS, we do want to be able to play video full screen, often because we have multiple monitors and want to dedicate one to displaying video.
Fortunately, with the help of some private APIs we can maximise the window at the same time as the video, so we can have a true “full screen” experience.
Maximising the Window
By maximising the window at the same time as the AVPlayerViewController, we can get the desired behaviour. The video is playing full screen, but you can still switch to other windows using Mission Control.
As the video shows, this approach is far from perfect, there’s some stuttering and the animations look crap. But it’s a step in the right direction!
I’ve implemented the resizing in two ways:
- Switching the video to full screen resizes the window
- Maximising the window, triggers the video to go full screen
Resizing the window can be done using some documented AppKit APIs, but going full screen with the AVPlayerController requires some private APIs. These are the APIs used:
- NSWindowWillEnterFullScreenNotification / NSWindowWillExitFullScreenNotification notifications to monitor window resizes
- playerViewController(_:willBeginFullScreenPresentationWithAnimationCoordinator:) to monitor player resizes
- NSWindow.toggleFullScreen to resize the window
- _transitionToFullScreen / _transitionFromFullScreen private APIs to resize the AVPlayerViewController
There’s multiple ways of implementing this, you can add a new AppKit bundle to your Catalyst UIKit app to call these APIs. Or you can use NSSelector and performSelector with strings to call these APIs. But I prefer using the Dynamic library.
The Dynamic library is a very convenient way of calling AppKit methods from Catalyst. It’s basically a wrapper around the NSSelector and performSelector methods, using Swift’s @dynamicMemberLookup and @dynamicCallable methods.
For all code, see the Github project.