Skip to content

Commit

Permalink
[tvOS] Add experimental runtime support for AVKit fullscreen
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=262814
rdar://116599008

Reviewed by Jer Noble.

Added experimental support for AVKit fullscreen on tvOS. Full support cannot be implemented until
new APIs are available (see rdar://116592344), but this provides a proof-of-concept implementation
by implementing the AVPlayerController interface expected by tvOS's AVPlayerViewController and
injecting a custom AVPlayerLayerView into AVPlayerViewController's view hierarchy.

This change also opts tvOS into iOS media controls layout traits and works around an issue related
to WKWebView visibility when AVPlayerViewController is modally presented on top of it.

* Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp:
(WebCore::MediaControlsHost::layoutTraitsClassName const):
* Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.h:
* Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.mm:
* Source/WebCore/PAL/pal/spi/cocoa/AVKitSPI.h:
* Source/WebCore/PAL/pal/spi/cocoa/FoundationSPI.h:
* Source/WebCore/platform/cocoa/WebAVPlayerLayer.h:
* Source/WebCore/platform/cocoa/WebAVPlayerLayer.mm:
(-[WebAVPlayerLayer legibleContentInsets]):
(-[WebAVPlayerLayer setLegibleContentInsets:]):
* Source/WebCore/platform/ios/VideoFullscreenInterfaceAVKit.mm:
(-[WebAVPlayerViewController initWithFullscreenInterface:]):
(-[WebAVPlayerViewController configurePlayerViewControllerWithFullscreenInterface:]):
* Source/WebCore/platform/ios/WebAVPlayerController.h:
* Source/WebCore/platform/ios/WebAVPlayerController.mm:
(-[WebAVPlayerController init]):
(-[WebAVPlayerController player]):
(-[WebAVPlayerController currentItem]):
(+[WebAVPlayerController keyPathsForValuesAffectingEffectiveRateNonZero]):
(-[WebAVPlayerController effectiveRateNonZero]):
(-[WebAVPlayerController forwardPlaybackEndTime]):
(-[WebAVPlayerController backwardPlaybackEndTime]):
(+[WebAVPlayerController keyPathsForValuesAffectingIsSeekingTV]):
(-[WebAVPlayerController isSeekingTV]):
(-[WebAVPlayerController hasStartAndEndDates]):
(-[WebAVPlayerController timeRangeSeekable]):
(-[WebAVPlayerController hasItem]):
(-[WebAVPlayerController isPlaybackLikelyToKeepUp]):
(-[WebAVPlayerController overrideForForwardPlaybackEndTime]):
(-[WebAVPlayerController overrideForReversePlaybackEndTime]):
(-[WebAVPlayerController timebaseRate]):
(-[WebAVPlayerController externalMetadata]):
(+[WebAVPlayerController keyPathsForValuesAffectingTimeControlStatus]):
(-[WebAVPlayerController timeControlStatus]):
(+[WebAVPlayerController keyPathsForValuesAffectingDisplayedDuration]):
(-[WebAVPlayerController displayedDuration]):
(-[WebAVPlayerController contentDurationCached]):
(-[WebAVPlayerController currentDisplayTime]):
(-[WebAVPlayerController currentOrEstimatedDate]):
(+[WebAVPlayerController keyPathsForValuesAffectingCurrentTime]):
(-[WebAVPlayerController currentTime]):
(-[WebAVPlayerController displayTimeRangeForNavigation]):
(-[WebAVPlayerController isAtMaxTime]):
(-[WebAVPlayerController isContentDurationIndefinite]):
(-[WebAVPlayerController timeRangeForNavigation]):
(-[WebAVPlayerController timeFromDisplayTime:]):
(-[WebAVPlayerController displayTimeFromTime:]):
(-[WebAVPlayerController activeRate]):
(-[WebAVPlayerController setActiveRate:]):
(-[WebAVPlayerController requestNavigateToTime:fromTime:reason:playWhenReady:permissionHandler:seekCompleti
(-[WebAVPlayerController seekToTime:seekReason:completionHandler:]):
(-[WebAVPlayerController requestSeekToTime:seekReason:permissionHandler:completionHandler:]):
(-[WebAVPlayerController requestPauseWithCompletion:]):
(-[WebAVPlayerController requestPlayWithCompletion:]):
(-[WebAVPlayerController requestSeekToTime:reason:playWhenReady:]):
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::viewDidLeaveWindow):

Canonical link: https://commits.webkit.org/269032@main
  • Loading branch information
aestes committed Oct 7, 2023
1 parent ff9aa20 commit 3febba3
Show file tree
Hide file tree
Showing 11 changed files with 355 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ String MediaControlsHost::layoutTraitsClassName() const
{
#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
return "MacOSLayoutTraits"_s;
#elif PLATFORM(IOS)
#elif PLATFORM(IOS) || PLATFORM(APPLETV)
return "IOSLayoutTraits"_s;
#elif PLATFORM(VISION)
return "VisionLayoutTraits"_s;
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ SOFT_LINK_FRAMEWORK_FOR_HEADER(PAL, AVFoundation)
// #define AVAssetCache PAL::getAVAssetCacheClass()
// because they make it difficult to use the class name in source code.

SOFT_LINK_CLASS_FOR_HEADER(PAL, AVAsset)
SOFT_LINK_CLASS_FOR_HEADER(PAL, AVAssetCache)
SOFT_LINK_CLASS_FOR_HEADER(PAL, AVAssetCollection)
SOFT_LINK_CLASS_FOR_HEADER(PAL, AVAssetExportSession)
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/PAL/pal/cocoa/AVFoundationSoftLink.mm
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ static BOOL justReturnsNO()
}
#endif

SOFT_LINK_CLASS_FOR_SOURCE_WITH_EXPORT(PAL, AVFoundation, AVAsset, PAL_EXPORT)
SOFT_LINK_CLASS_FOR_SOURCE_WITH_EXPORT(PAL, AVFoundation, AVAssetCache, PAL_EXPORT)
SOFT_LINK_CLASS_FOR_SOURCE_WITH_EXPORT(PAL, AVFoundation, AVAssetExportSession, PAL_EXPORT)
SOFT_LINK_CLASS_FOR_SOURCE_WITH_EXPORT(PAL, AVFoundation, AVAssetImageGenerator, PAL_EXPORT)
Expand Down
24 changes: 19 additions & 5 deletions Source/WebCore/PAL/pal/spi/cocoa/AVKitSPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -439,17 +439,17 @@ NS_ASSUME_NONNULL_BEGIN

#if USE(APPLE_INTERNAL_SDK)

typedef NS_ENUM(NSInteger, AVPlayerControllerStatus) {
AVPlayerControllerStatusUnknown = 0,
AVPlayerControllerStatusReadyToPlay = 2,
};

typedef NS_ENUM(NSInteger, AVPlayerControllerExternalPlaybackType) {
AVPlayerControllerExternalPlaybackTypeNone = 0,
AVPlayerControllerExternalPlaybackTypeAirPlay = 1,
AVPlayerControllerExternalPlaybackTypeTVOut = 2,
};

typedef NS_ENUM(NSInteger, AVPlayerControllerStatus) {
AVPlayerControllerStatusUnknown = 0,
AVPlayerControllerStatusReadyToPlay = 2,
};

@interface AVPlayerController : NSObject
@end

Expand All @@ -458,6 +458,15 @@ typedef NS_ENUM(NSInteger, AVPlayerControllerExternalPlaybackType) {

#endif // USE(APPLE_INTERNAL_SDK)

typedef NS_ENUM(NSInteger, AVPlayerControllerTimeControlStatus) {
AVPlayerControllerTimeControlStatusPaused,
AVPlayerControllerTimeControlStatusWaitingToPlayAtSpecifiedRate,
AVPlayerControllerTimeControlStatusPlaying
};

@interface AVTimeRange : NSObject
@end

@interface __AVPlayerLayerView (IPI)
@property (nonatomic, strong, nullable) AVPlayerController *playerController;
@property (nonatomic, readonly) AVPlayerLayer *playerLayer;
Expand All @@ -468,6 +477,11 @@ typedef NS_ENUM(NSInteger, AVPlayerControllerExternalPlaybackType) {
@property (nonatomic, strong) __AVPlayerLayerView *playerLayerView;
@end

@interface AVTimeRange (IPI)
- (instancetype)initWithCMTimeRange:(CMTimeRange)timeRange;
- (instancetype)initWithStartTime:(NSTimeInterval)startTime endTime:(NSTimeInterval)duration;
@end

NS_ASSUME_NONNULL_END

#endif // PLATFORM(APPLETV)
12 changes: 12 additions & 0 deletions Source/WebCore/PAL/pal/spi/cocoa/FoundationSPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@

#import <Foundation/Foundation.h>

#if USE(APPLE_INTERNAL_SDK)
#import <Foundation/NSGeometry.h>
#elif !PLATFORM(MAC) && !PLATFORM(MACCATALYST)
#define NSEDGEINSETS_DEFINED 1
typedef struct NS_SWIFT_SENDABLE NSEdgeInsets {
CGFloat top;
CGFloat left;
CGFloat bottom;
CGFloat right;
} NSEdgeInsets;
#endif

@interface NSTextCheckingResult ()
- (NSDictionary *)detail;
@end
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/platform/cocoa/WebAVPlayerLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "FloatRect.h"
#include <CoreGraphics/CGGeometry.h>
#include <QuartzCore/CALayer.h>
#include <pal/spi/cocoa/FoundationSPI.h>

OBJC_CLASS AVPlayerController;
OBJC_CLASS NSDictionary;
Expand All @@ -47,6 +48,7 @@ WEBCORE_EXPORT @interface WebAVPlayerLayer : CALayer
@property (nonatomic, retain, nonnull) CALayer *videoSublayer;
@property (nonatomic, copy, nullable) NSDictionary *pixelBufferAttributes;
@property CGSize videoDimensions;
@property (nonatomic) NSEdgeInsets legibleContentInsets;
- (WebCore::FloatRect)calculateTargetVideoFrame;
@end

Expand Down
11 changes: 11 additions & 0 deletions Source/WebCore/platform/cocoa/WebAVPlayerLayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ @implementation WebAVPlayerLayer {
RetainPtr<NSString> _videoGravity;
RetainPtr<NSString> _previousVideoGravity;
std::unique_ptr<WebAVPlayerLayerPresentationModelClient> _presentationModelClient;
NSEdgeInsets _legibleContentInsets;
#if !RELEASE_LOG_DISABLED
const void* _logIdentifier;
#endif
Expand Down Expand Up @@ -359,6 +360,16 @@ + (NSSet *)keyPathsForValuesAffectingVideoRect
return [NSSet setWithObjects:@"videoDimensions", @"videoGravity", nil];
}

- (NSEdgeInsets)legibleContentInsets
{
return _legibleContentInsets;
}

- (void)setLegibleContentInsets:(NSEdgeInsets)legibleContentInsets
{
_legibleContentInsets = legibleContentInsets;
}

@end

#if !RELEASE_LOG_DISABLED
Expand Down
40 changes: 37 additions & 3 deletions Source/WebCore/platform/ios/VideoFullscreenInterfaceAVKit.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#import <UIKit/UIWindow.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <pal/spi/cocoa/AVKitSPI.h>
#import <pal/spi/ios/UIKitSPI.h>
#import <wtf/RefPtr.h>
#import <wtf/RetainPtr.h>
Expand Down Expand Up @@ -264,7 +265,7 @@ - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPict
fullscreenInterface->prepareForPictureInPictureStopWithCompletionHandler(completionHandler);
}

#endif
#endif // HAVE(PIP_CONTROLLER)

@end

Expand Down Expand Up @@ -361,7 +362,7 @@ static void WebAVPictureInPictureContentViewController_dealloc(id aSelf, SEL)
return (WebAVPictureInPictureContentViewController *)[theClass alloc];
}

#endif
#endif // HAVE(PIP_CONTROLLER)

NS_ASSUME_NONNULL_BEGIN
@interface WebAVPlayerViewController : NSObject<AVPlayerViewControllerDelegate>
Expand Down Expand Up @@ -406,7 +407,10 @@ - (instancetype)initWithFullscreenInterface:(VideoFullscreenInterfaceAVKit *)int

OBJC_ALWAYS_LOG(OBJC_LOGIDENTIFIER);

#if !PLATFORM(APPLETV)
#if PLATFORM(APPLETV)
_avPlayerViewController = adoptNS([allocAVPlayerViewControllerInstance() init]);
[self configurePlayerViewControllerWithFullscreenInterface:interface];
#else
_avPlayerViewController = adoptNS([allocAVPlayerViewControllerInstance() initWithPlayerLayerView:interface->playerLayerView()]);
#endif
[_avPlayerViewController setModalPresentationStyle:UIModalPresentationOverFullScreen];
Expand All @@ -431,6 +435,36 @@ - (instancetype)initWithFullscreenInterface:(VideoFullscreenInterfaceAVKit *)int
return self;
}

#if PLATFORM(APPLETV)
- (void)configurePlayerViewControllerWithFullscreenInterface:(VideoFullscreenInterfaceAVKit *)interface
{
// FIXME (116592344): This is a proof-of-concept hack to work around lack support for a custom
// AVPlayerLayerView in tvOS's version of AVPlayerViewController. This will be replaced once
// proper API is available.

RELEASE_ASSERT([_avPlayerViewController view]);

[[_avPlayerViewController playerLayerView] removeFromSuperview];

WebAVPlayerLayerView *playerLayerView = interface->playerLayerView();
[_avPlayerViewController setPlayerLayerView:playerLayerView];

playerLayerView.pixelBufferAttributes = [_avPlayerViewController pixelBufferAttributes];
playerLayerView.playerController = (AVPlayerController *)interface->playerController();
playerLayerView.translatesAutoresizingMaskIntoConstraints = NO;
playerLayerView.playerLayer.videoGravity = [_avPlayerViewController videoGravity];

UIView *contentContainerView = [_avPlayerViewController view].subviews.firstObject;
[contentContainerView addSubview:playerLayerView];
[NSLayoutConstraint activateConstraints:@[
[playerLayerView.widthAnchor constraintEqualToAnchor:contentContainerView.widthAnchor],
[playerLayerView.heightAnchor constraintEqualToAnchor:contentContainerView.heightAnchor],
[playerLayerView.centerXAnchor constraintEqualToAnchor:contentContainerView.centerXAnchor],
[playerLayerView.centerYAnchor constraintEqualToAnchor:contentContainerView.centerYAnchor],
]];
}
#endif // PLATFORM(APPLETV)

- (void)dealloc
{
OBJC_ALWAYS_LOG(OBJC_LOGIDENTIFIER);
Expand Down
36 changes: 33 additions & 3 deletions Source/WebCore/platform/ios/WebAVPlayerController.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@

#import <pal/spi/cocoa/AVKitSPI.h>

NS_ASSUME_NONNULL_BEGIN

namespace WebCore {
class PlaybackSessionModel;
class PlaybackSessionInterfaceAVKit;
}

@class AVTimeRange;

@interface WebAVMediaSelectionOption : NSObject
- (instancetype)initWithMediaType:(AVMediaType)type displayName:(NSString *)displayName;

Expand All @@ -46,8 +50,8 @@ class PlaybackSessionInterfaceAVKit;
- (void)setAllowsPictureInPicture:(BOOL)allowsPictureInPicture;

@property (retain) AVPlayerController *playerControllerProxy;
@property (assign /*weak*/) WebCore::PlaybackSessionModel* delegate;
@property (assign /*weak*/) WebCore::PlaybackSessionInterfaceAVKit* playbackSessionInterface;
@property (assign, nullable /*weak*/) WebCore::PlaybackSessionModel* delegate;
@property (assign, nullable /*weak*/) WebCore::PlaybackSessionInterfaceAVKit* playbackSessionInterface;

@property (readonly) BOOL canScanForward;
@property BOOL canScanBackward;
Expand Down Expand Up @@ -109,9 +113,35 @@ class PlaybackSessionInterfaceAVKit;
- (void)setDefaultPlaybackRate:(double)defaultPlaybackRate fromJavaScript:(BOOL)fromJavaScript;
- (void)setRate:(double)rate fromJavaScript:(BOOL)fromJavaScript;

#if PLATFORM(APPLETV)
// FIXME (116592344): Remove these declarations once AVPlayerController API is available on tvOS.
@property (nonatomic, readonly, getter=isEffectiveRateNonZero) BOOL effectiveRateNonZero;
@property (nonatomic, readonly) CMTime forwardPlaybackEndTime;
@property (nonatomic, readonly) CMTime backwardPlaybackEndTime;
@property (nonatomic, readonly) BOOL isSeekingTV;
@property (nonatomic, readonly) BOOL hasStartAndEndDates;
@property (nonatomic, readonly, nullable) AVTimeRange *timeRangeSeekable;
@property (readonly, nullable) NSValue *overrideForForwardPlaybackEndTime;
@property (readonly, nullable) NSValue *overrideForReversePlaybackEndTime;
@property (readonly) double timebaseRate;
@property (readonly, nullable) NSArray *externalMetadata;
@property (readonly) BOOL isPlaybackLikelyToKeepUp;
@property (readonly) AVPlayerControllerTimeControlStatus timeControlStatus;
@property (readonly) NSTimeInterval displayedDuration;
@property (nonatomic, readonly) NSTimeInterval contentDurationCached;
@property (nonatomic, readonly) NSTimeInterval currentDisplayTime;
@property (nonatomic, readonly) NSDate *currentOrEstimatedDate;
@property (nonatomic, readonly) AVTimeRange *displayTimeRangeForNavigation;
@property (nonatomic, readonly) BOOL isContentDurationIndefinite;
@property (nonatomic, readonly) AVTimeRange *timeRangeForNavigation;
@property (nonatomic) float activeRate;
#endif // PLATFORM(APPLETV)

@end

Class webAVPlayerControllerClass();
RetainPtr<WebAVPlayerController> createWebAVPlayerController();

#endif
NS_ASSUME_NONNULL_END

#endif // PLATFORM(COCOA) && HAVE(AVKIT)
Loading

0 comments on commit 3febba3

Please sign in to comment.