[vlc-commits] [Git][videolan/vlc][master] vout: apple: Add Picture in Picture
Steve Lhomme (@robUx4)
gitlab at videolan.org
Sat Sep 21 11:01:42 UTC 2024
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
b8a97c62 by Maxime Chapelet at 2024-09-21T10:48:14+00:00
vout: apple: Add Picture in Picture
This adds a new module to handle picture in picture on iOS and tvOS.
Picture in picture is only enabled if the drawable ns-object conforms to the
new `VLCPictureInPictureDrawable` protocol.
`VLCPictureInPictureDrawable` protocol provide new apis to start/stop picture
in picture and various callbacks to handle user interactions from the picture
in picture window's overlay.
Implementing `VLCPictureInPictureDrawable.pictureInPictureReady` block is
mandatory to be notified once picture in picture initialization is done.
This block will pass a `id<VLCPictureInPictureWindowControlling>` object
allowing to start and stop picture in picture.
It will also provide a way to invalidate the state of the playback by calling
`invalidatePlaybackState`, forcing the picture in picture to fetch new media
infos like current time or media length from the
`VLCPictureInPictureDrawable.mediaController`.
This module uses Apple public APIs hence it won't work for macOS at this time.
Separate work will have to be done in order to provide support for picture in
picture private APIs and allow the use of picture in picture on macOS with
libvlc.
- - - - -
7 changed files:
- include/vlc/libvlc_media_player.h
- modules/video_output/Makefile.am
- + modules/video_output/apple/VLCDrawable.h
- + modules/video_output/apple/VLCPictureInPictureController.m
- modules/video_output/apple/VLCSampleBufferDisplay.m
- modules/video_output/apple/VLCVideoUIView.m
- + modules/video_output/apple/vlc_pip_controller.h
Changes:
=====================================
include/vlc/libvlc_media_player.h
=====================================
@@ -1,11 +1,12 @@
/*****************************************************************************
* libvlc_media_player.h: libvlc_media_player external API
*****************************************************************************
- * Copyright (C) 1998-2015 VLC authors and VideoLAN
+ * Copyright (C) 1998-2024 VLC authors and VideoLAN
*
* Authors: Clément Stenac <zorglub at videolan.org>
* Jean-Paul Saman <jpsaman at videolan.org>
* Pierre d'Herbemont <pdherbemont at videolan.org>
+ * 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 Lesser General Public License as published by
@@ -894,22 +895,52 @@ bool libvlc_video_set_output_callbacks( libvlc_media_player_t *mp,
void* opaque );
/**
- * Set the NSView handler where the media player should render its video output.
+ * Set the handler where the media player should display its video output.
*
- * Use the vout called "macosx".
- *
- * The drawable is an NSObject that follow the VLCVideoViewEmbedding
- * protocol:
+ * The drawable is an `NSObject` that require responding to two selectors
+ * like in this protocol:
*
* @code{.m}
- * @protocol VLCVideoViewEmbedding <NSObject>
- * - (void)addVoutSubview:(NSView *)view;
- * - (void)removeVoutSubview:(NSView *)view;
+ * @protocol VLCDrawable <NSObject>
+ * - (void)addSubview:(VLCView *)view;
+ * - (CGRect)bounds;
* @end
* @endcode
*
- * Or it can be an NSView object.
- *
+ * In this protocol `VLCView` type can either be a `UIView` or a `NSView` type
+ * class.
+ * VLCDrawable protocol conformance isn't mandatory but a drawable must respond
+ * to both `addSubview:` and `bounds` selectors.
+ *
+ * Additionally, a drawable can also conform to the `VLCPictureInPictureDrawable`
+ * protocol to allow picture in picture support :
+ *
+ * @code{.m}
+ * @protocol VLCPictureInPictureMediaControlling <NSObject>
+ * - (void)play;
+ * - (void)pause;
+ * - (void)seekBy:(int64_t)offset;
+ * - (int64_t)mediaLength;
+ * - (int64_t)mediaTime;
+ * - (BOOL)isMediaPlaying;
+ * @end
+ *
+ * @protocol VLCPictureInPictureWindowControlling <NSObject>
+ * - (void)startPictureInPicture;
+ * - (void)stopPictureInPicture;
+ * - (void)invalidatePlaybackState;
+ * @end
+ *
+ * @protocol VLCPictureInPictureDrawable <NSObject>
+ * - (id<VLCPictureInPictureMediaControlling>) mediaController;
+ * - (void (^)(id<VLCPictureInPictureWindowControlling>)) pictureInPictureReady;
+ * @end
+ * @endcode
+ *
+ * Be aware that full `VLCPictureInPictureDrawable` conformance is mandatory to
+ * enable picture in picture support and that time values in
+ * `VLCPictureInPictureMediaControlling` methods are expressed in milliseconds.
+ *
* If you want to use it along with Qt see the QMacCocoaViewContainer. Then
* the following code should work:
* @code{.mm}
@@ -924,8 +955,8 @@ bool libvlc_video_set_output_callbacks( libvlc_media_player_t *mp,
* You can find a live example in VLCVideoView in VLCKit.framework.
*
* \param p_mi the Media Player
- * \param drawable the drawable that is either an NSView or an object following
- * the VLCVideoViewEmbedding protocol.
+ * \param drawable the drawable that is either an NSView, a UIView or any
+ * NSObject responding to `addSubview:` and `bounds` selectors
*/
LIBVLC_API void libvlc_media_player_set_nsobject ( libvlc_media_player_t *p_mi, void * drawable );
=====================================
modules/video_output/Makefile.am
=====================================
@@ -88,6 +88,14 @@ vout_LTLIBRARIES += libsamplebufferdisplay_plugin.la
endif
endif
+if HAVE_IOS_OR_TVOS
+libpictureinpicturecontroller_plugin_la_SOURCES = video_output/apple/VLCPictureInPictureController.m
+libpictureinpicturecontroller_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) -fobjc-arc
+libpictureinpicturecontroller_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
+ -Wl,-framework,AVFoundation,-framework,Foundation,-framework,AVKit,-framework,UIKit
+vout_LTLIBRARIES += libpictureinpicturecontroller_plugin.la
+endif
+
libvout_ios_plugin_la_SOURCES = video_output/opengl/display.c $(OPENGL_VOUT_COMMONSOURCES)
libvout_ios_plugin_la_CFLAGS = $(AM_CFLAGS) $(OPENGL_COMMONCFLAGS) -DUSE_OPENGL_ES2
libvout_ios_plugin_la_LIBADD = libvlc_opengles.la
=====================================
modules/video_output/apple/VLCDrawable.h
=====================================
@@ -0,0 +1,110 @@
+/*****************************************************************************
+ * VLCDrawable.h :
+ *****************************************************************************
+ * Copyright (C) 2023-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 Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 <TargetConditionals.h>
+# if TARGET_OS_OSX
+# import <Cocoa/Cocoa.h>
+# define VLCView NSView
+# else
+# import <Foundation/Foundation.h>
+# import <UIKit/UIKit.h>
+# define VLCView UIView
+# endif
+
+/**
+ * Protocol used by the picture in picture to control playback or gather media
+ * infos
+ */
+ at protocol VLCPictureInPictureMediaControlling <NSObject>
+
+/// Called by picture in picture to play/resume the media
+- (void)play;
+
+/// Called by picture in picture to pause the media
+- (void)pause;
+
+/// Called by picture in picture to seek with time offset
+/// - Parameter offset: offset duration in milliseconds
+- (void)seekBy:(int64_t)offset;
+
+/// Called by picture in picture to get the current media duration
+/// - Returns Must return media duration in milliseconds
+- (int64_t)mediaLength;
+
+/// Called by picture in picture to get the current media duration
+/// - Returns Must return current media time in milliseconds
+- (int64_t)mediaTime;
+
+/// Called by picture in picture to get the media playback status
+/// - Returns Must return YES if media is playing, else return NO
+- (BOOL)isMediaPlaying;
+ at end
+
+/**
+ * Protocol used by the client to control picture in picture activation and
+ * state update
+ */
+ at protocol VLCPictureInPictureWindowControlling <NSObject>
+
+/// Call to present the display in picture in picture mode
+- (void)startPictureInPicture;
+
+/// Call to stop picture in picture
+- (void)stopPictureInPicture;
+
+/// Must be called each time media info is updated or playback state has changed
+- (void)invalidatePlaybackState;
+ at end
+
+/**
+ * Protocol that can be used by the client to conform an object to expected
+ * selectors for a video output display.
+ */
+ at protocol VLCDrawable <NSObject>
+
+/// Add a view to a view hierarchy
+/// - Parameter view: the display view to present, can be an UIView or a NSView
+- (void)addSubview:(VLCView *)view;
+
+/// Get the display view's parent's frame bounds
+/// - Returns Must return the display view's parent's frame bounds
+- (CGRect)bounds;
+ at end
+
+/**
+ * Protocol that can be used by the client to enable picture in picture for the
+ * video output display.
+ */
+ at protocol VLCPictureInPictureDrawable <NSObject>
+
+/// Get the object to let picture in picture control playback or gather media
+/// infos
+/// - Returns Must return the object that handles playback controls for
+/// picture in picture
+- (id<VLCPictureInPictureMediaControlling>) mediaController;
+
+/// Get the block that will be called once picture in picture is ready to be used
+/// - Returns Must return the block where picture in picture activation
+/// controller is passed
+- (void (^)(id<VLCPictureInPictureWindowControlling>)) pictureInPictureReady;
+ at end
=====================================
modules/video_output/apple/VLCPictureInPictureController.m
=====================================
@@ -0,0 +1,353 @@
+/*****************************************************************************
+ * VLCPictureInPictureController.m: Picture In Picture controller for iOS and
+ * tvOS
+ *****************************************************************************
+ * 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 Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_modules.h>
+
+#import <TargetConditionals.h>
+#import <Foundation/Foundation.h>
+#import <AVKit/AVKit.h>
+#import <AVFoundation/AVFoundation.h>
+#import "VLCDrawable.h"
+
+#include "vlc_pip_controller.h"
+
+API_AVAILABLE(ios(15.0), tvos(15.0), macosx(12.0))
+ at interface VLCPictureInPictureController: NSObject
+ <AVPictureInPictureSampleBufferPlaybackDelegate,
+ AVPictureInPictureControllerDelegate>
+
+ at property (nonatomic, readonly) NSObject *avPipController;
+ at property (nonatomic, readonly) pip_controller_t *pipcontroller;
+ at property (nonatomic, readonly, weak) id<VLCPictureInPictureDrawable> drawable;
+
+- (instancetype)initWithPipController:(pip_controller_t *)pipcontroller;
+- (void)invalidatePlaybackState;
+
+ at end
+
+ at implementation VLCPictureInPictureController
+
+- (instancetype)initWithPipController:(pip_controller_t *)pipcontroller {
+ self = [super init];
+ if (!self)
+ return nil;
+ _pipcontroller = pipcontroller;
+
+ id drawable = (__bridge id)var_InheritAddress (pipcontroller, "drawable-nsobject");
+
+ if (![drawable conformsToProtocol:@protocol(VLCPictureInPictureDrawable)])
+ return nil;
+
+ _drawable = drawable;
+
+ return self;
+}
+
+- (void)prepare:(AVSampleBufferDisplayLayer *)layer {
+ if (![AVPictureInPictureController isPictureInPictureSupported]) {
+ msg_Err(_pipcontroller, "Picture In Picture isn't supported");
+ return;
+ }
+
+ assert(layer);
+ AVPictureInPictureControllerContentSource *avPipSource;
+ avPipSource = [
+ [AVPictureInPictureControllerContentSource alloc]
+ initWithSampleBufferDisplayLayer:layer
+ playbackDelegate:self
+ ];
+ AVPictureInPictureController *avPipController = [
+ [AVPictureInPictureController alloc]
+ initWithContentSource:avPipSource
+ ];
+ avPipController.delegate = self;
+#if TARGET_OS_IOS
+ // Not sure if it's mandatory, its usefulness isn't obvious and
+ // documentation doesn't particularily helps
+ avPipController.canStartPictureInPictureAutomaticallyFromInline = YES;
+#endif
+ _avPipController = avPipController;
+
+
+ [_avPipController addObserver:self
+ forKeyPath:@"isPictureInPicturePossible"
+ options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew
+ context:NULL];
+}
+
+- (void)startPictureInPicture {
+ AVPictureInPictureController *avPipController =
+ (AVPictureInPictureController *)_avPipController;
+ [avPipController startPictureInPicture];
+}
+
+- (void)stopPictureInPicture {
+ AVPictureInPictureController *avPipController =
+ (AVPictureInPictureController *)_avPipController;
+ [avPipController stopPictureInPicture];
+}
+
+- (void)invalidatePlaybackState {
+ AVPictureInPictureController *avPipController =
+ (AVPictureInPictureController *)_avPipController;
+ [avPipController invalidatePlaybackState];
+}
+
+- (void)close {
+ AVPictureInPictureController *avPipController =
+ (AVPictureInPictureController *)_avPipController;
+ NSObject *observer = self;
+ dispatch_async(dispatch_get_main_queue(),^{
+ avPipController.contentSource = nil;
+ [avPipController removeObserver:observer forKeyPath:@"isPictureInPicturePossible"];
+ });
+ _avPipController = nil;
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
+ change:(NSDictionary<NSKeyValueChangeKey,id> *)change
+ context:(void *)context {
+ if (object==_avPipController) {
+ if ([keyPath isEqualToString:@"isPictureInPicturePossible"]) {
+ msg_Dbg(_pipcontroller, "isPictureInPicturePossible:%d", [change[NSKeyValueChangeNewKey] boolValue]);
+ }
+ } else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
+}
+
+#pragma mark - AVPictureInPictureSampleBufferPlaybackDelegate
+
+- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
+ didTransitionToRenderSize:(CMVideoDimensions)newRenderSize {
+
+}
+
+- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
+ setPlaying:(BOOL)playing {
+ assert(_pipcontroller->media_cbs);
+ void *mediaController = (__bridge void*)_drawable.mediaController;
+ bool isPlaying = _pipcontroller->media_cbs->is_media_playing(mediaController);
+ if(isPlaying && !playing)
+ _pipcontroller->media_cbs->pause(mediaController);
+ if(!isPlaying && playing)
+ _pipcontroller->media_cbs->play(mediaController);
+}
+
+- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
+ skipByInterval:(CMTime)skipInterval
+ completionHandler:(void (^)(void))completionHandler {
+ assert(_pipcontroller->media_cbs);
+ Float64 time_sec = CMTimeGetSeconds(skipInterval);
+
+ void *mediaController = (__bridge void*)_drawable.mediaController;
+ int64_t offset = time_sec * 1e3;
+ _pipcontroller->media_cbs->seek_by(offset, mediaController);
+ completionHandler();
+}
+
+- (BOOL)pictureInPictureControllerIsPlaybackPaused:(AVPictureInPictureController *)pictureInPictureController {
+ assert(_pipcontroller->media_cbs);
+ void *mediaController = (__bridge void*)_drawable.mediaController;
+ return ! _pipcontroller->media_cbs->is_media_playing(mediaController);
+}
+
+- (CMTimeRange)pictureInPictureControllerTimeRangeForPlayback:(AVPictureInPictureController *)pictureInPictureController {
+ assert(_pipcontroller->media_cbs);
+ //TODO: Handle media duration
+ void *mediaController = (__bridge void*)_drawable.mediaController;
+ const CMTimeRange live = CMTimeRangeMake(kCMTimeNegativeInfinity, kCMTimePositiveInfinity);
+
+ int64_t length = _pipcontroller->media_cbs->media_length(mediaController);
+ if (length == VLC_TICK_INVALID)
+ return live;
+
+ int64_t time = _pipcontroller->media_cbs->media_time(mediaController);
+
+ CFTimeInterval ca_now = CACurrentMediaTime();
+ CFTimeInterval time_sec = ((Float64)time) * 1e-3;
+ CFTimeInterval start = ca_now - time_sec;
+ CFTimeInterval duration = ((Float64)length) * 1e-3;
+ return CMTimeRangeMake(CMTimeMakeWithSeconds(start, 1000000), CMTimeMakeWithSeconds(duration, 1000000));
+}
+
+- (BOOL)pictureInPictureControllerShouldProhibitBackgroundAudioPlayback:(AVPictureInPictureController *)pictureInPictureController {
+ return NO;
+}
+
+#pragma mark - AVPictureInPictureControllerDelegate
+
+- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
+ /**
+ * Invalidation seems mandatory as
+ * pictureInPictureControllerTimeRangeForPlayback: isn't automatically
+ * called each time PiP is activated
+ */
+ [self invalidatePlaybackState];
+}
+
+ at end
+
+static void SetDisplayLayer( pip_controller_t *pipcontroller, void *layer) {
+ if (@available(macOS 12.0, iOS 15.0, tvos 15.0, *)) {
+ VLCPictureInPictureController *sys =
+ (__bridge VLCPictureInPictureController*)pipcontroller->p_sys;
+
+ AVSampleBufferDisplayLayer *displayLayer =
+ (__bridge AVSampleBufferDisplayLayer *)layer;
+
+ [sys prepare:displayLayer];
+ }
+}
+
+static void StartPictureInPicture( pip_controller_t *pipcontroller) {
+ if (@available(macOS 12.0, iOS 15.0, tvos 15.0, *)) {
+ VLCPictureInPictureController *sys =
+ (__bridge VLCPictureInPictureController*)pipcontroller->p_sys;
+
+ [sys startPictureInPicture];
+ }
+}
+
+static void StopPictureInPicture( pip_controller_t *pipcontroller) {
+ if (@available(macOS 12.0, iOS 15.0, tvos 15.0, *)) {
+ VLCPictureInPictureController *sys =
+ (__bridge VLCPictureInPictureController*)pipcontroller->p_sys;
+
+ [sys stopPictureInPicture];
+ }
+}
+
+static void InvalidatePictureInPicture( pip_controller_t *pipcontroller) {
+ if (@available(macOS 12.0, iOS 15.0, tvos 15.0, *)) {
+ VLCPictureInPictureController *sys =
+ (__bridge VLCPictureInPictureController*)pipcontroller->p_sys;
+
+ [sys invalidatePlaybackState];
+ }
+}
+
+static void PipControllerMediaPlay(void *opaque) {
+ id<VLCPictureInPictureMediaControlling> mediaController;
+ mediaController = (__bridge id<VLCPictureInPictureMediaControlling>)opaque;
+ [mediaController play];
+}
+
+static void PipControllerMediaPause(void *opaque) {
+ id<VLCPictureInPictureMediaControlling> mediaController;
+ mediaController = (__bridge id<VLCPictureInPictureMediaControlling>)opaque;
+ [mediaController pause];
+}
+
+static void PipControllerMediaSeekBy(vlc_tick_t time, void *opaque) {
+ id<VLCPictureInPictureMediaControlling> mediaController;
+ mediaController = (__bridge id<VLCPictureInPictureMediaControlling>)opaque;
+ [mediaController seekBy:time];
+}
+
+static vlc_tick_t PipControllerMediaGetLength(void *opaque) {
+ id<VLCPictureInPictureMediaControlling> mediaController;
+ mediaController = (__bridge id<VLCPictureInPictureMediaControlling>)opaque;
+ return [mediaController mediaLength];
+}
+
+static vlc_tick_t PipControllerMediaGetTime(void *opaque) {
+ id<VLCPictureInPictureMediaControlling> mediaController;
+ mediaController = (__bridge id<VLCPictureInPictureMediaControlling>)opaque;
+ return [mediaController mediaTime];
+}
+
+static bool PipControllerMediaIsPlaying(void *opaque) {
+ id<VLCPictureInPictureMediaControlling> mediaController;
+ mediaController = (__bridge id<VLCPictureInPictureMediaControlling>)opaque;
+ return [mediaController isMediaPlaying];
+}
+
+static int CloseController( pip_controller_t *pipcontroller )
+{
+ if (@available(macOS 12.0, iOS 15.0, tvos 15.0, *)) {
+ VLCPictureInPictureController *sys =
+ (__bridge_transfer VLCPictureInPictureController*)pipcontroller->p_sys;
+
+ [sys close];
+ }
+
+ return VLC_SUCCESS;
+}
+
+static int OpenController( pip_controller_t *pipcontroller )
+{
+ static const struct pip_controller_operations ops = {
+ SetDisplayLayer,
+ StartPictureInPicture,
+ StopPictureInPicture,
+ InvalidatePictureInPicture,
+ CloseController
+ };
+
+ pipcontroller->ops = &ops;
+
+ static const struct pip_controller_media_callbacks cbs = {
+ PipControllerMediaPlay,
+ PipControllerMediaPause,
+ PipControllerMediaSeekBy,
+ PipControllerMediaGetLength,
+ PipControllerMediaGetTime,
+ PipControllerMediaIsPlaying
+ };
+
+ pipcontroller->media_cbs = &cbs;
+
+ if (@available(macOS 12.0, iOS 15.0, tvos 15.0, *)) {
+ VLCPictureInPictureController *sys =
+ [[VLCPictureInPictureController alloc]
+ initWithPipController:pipcontroller];
+
+ if (sys == nil) {
+ return VLC_EGENERIC;
+ }
+
+ pipcontroller->p_sys = (__bridge_retained void*)sys;
+ } else {
+ return VLC_EGENERIC;
+ }
+
+ return VLC_SUCCESS;
+}
+
+/*
+ * Module descriptor
+ */
+vlc_module_begin()
+ set_description(N_("Picture in picture controller for Apple systems"))
+ set_subcategory(SUBCAT_VIDEO_VOUT)
+ set_callback(OpenController)
+ set_capability("pictureinpicture", 100)
+vlc_module_end()
=====================================
modules/video_output/apple/VLCSampleBufferDisplay.m
=====================================
@@ -2,7 +2,7 @@
* VLCSampleBufferDisplay.m: video output display using
* AVSampleBufferDisplayLayer on macOS
*****************************************************************************
- * Copyright (C) 2023 VLC authors and VideoLAN
+ * Copyright (C) 2023-2024 VLC authors and VideoLAN
*
* Authors: Maxime Chapelet <umxprime at videolabs dot io>
*
@@ -31,21 +31,15 @@
#include <vlc_plugin.h>
#include <vlc_vout_display.h>
#include <vlc_atomic.h>
+#include <vlc_modules.h>
-# import <TargetConditionals.h>
-# if TARGET_OS_OSX
-# import <Cocoa/Cocoa.h>
-# define VLCView NSView
-# else
-# import <Foundation/Foundation.h>
-# import <UIKit/UIKit.h>
-# define VLCView UIView
-# endif
+#import "VLCDrawable.h"
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
#include "../../codec/vt_utils.h"
+#include "vlc_pip_controller.h"
#import <VideoToolbox/VideoToolbox.h>
@@ -469,13 +463,8 @@ static void DeleteCVPXConverter( filter_t * p_converter )
vlc_object_delete(p_converter);
}
-/**
- * Protocol declaration that drawable-nsobject should follow
- */
- at protocol VLCOpenGLVideoViewEmbedding <NSObject>
-- (void)addVoutSubview:(VLCView *)view;
-- (void)removeVoutSubview:(VLCView *)view;
- at end
+static pip_controller_t * CreatePipController( vout_display_t *vd, void *cbs_opaque );
+static void DeletePipController( pip_controller_t * pipcontroller );
#pragma mark -
@class VLCSampleBufferSubpicture, VLCSampleBufferDisplay;
@@ -650,16 +639,26 @@ shouldInheritContentsScale:(CGFloat)newScale
#pragma mark -
- at interface VLCSampleBufferDisplay: NSObject {
+ at interface VLCSampleBufferDisplay: NSObject <VLCPictureInPictureWindowControlling>
+{
@public
+ vout_display_place_t place;
filter_t *converter;
}
- @property (nonatomic) id<VLCOpenGLVideoViewEmbedding> container;
+ @property (nonatomic, readonly, weak) VLCView *window;
+ @property (nonatomic, readonly, weak) id drawable;
+ @property (nonatomic, readonly) vout_display_t *vd;
@property (nonatomic) VLCSampleBufferDisplayView *displayView;
@property (nonatomic) AVSampleBufferDisplayLayer *displayLayer;
@property (nonatomic) VLCSampleBufferSubpictureView *spuView;
@property (nonatomic) VLCSampleBufferSubpicture *subpicture;
@property (nonatomic) id<VLCPixelBufferRotationContext> rotationContext;
+
+ @property (nonatomic, readonly) pip_controller_t *pipcontroller;
+
+ - (instancetype)init NS_UNAVAILABLE;
+ + (instancetype)new NS_UNAVAILABLE;
+ - (instancetype)initWithVoutDisplay:(vout_display_t *)vd;
@end
@implementation VLCSampleBufferDisplay
@@ -677,6 +676,126 @@ shouldInheritContentsScale:(CGFloat)newScale
return _rotationContext;
}
+
+- (instancetype)initWithVoutDisplay:(vout_display_t *)vd
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ if (vd->cfg->window->type != VLC_WINDOW_TYPE_NSOBJECT)
+ return nil;
+
+ VLCView *window = (__bridge VLCView *)vd->cfg->window->handle.nsobject;
+ if (!window) {
+ msg_Err(vd, "No window found!");
+ return nil;
+ }
+
+ _window = window;
+
+ _drawable = (__bridge id)var_InheritAddress (vd, "drawable-nsobject");
+
+ _pipcontroller = CreatePipController(vd, (__bridge void *)self);
+
+ _vd = vd;
+
+ return self;
+}
+
+- (void)preparePictureInPicture {
+ if ( !_pipcontroller)
+ return;
+
+ if ( _pipcontroller->ops->set_display_layer ) {
+ _pipcontroller->ops->set_display_layer(
+ _pipcontroller,
+ (__bridge void*)_displayView.displayLayer
+ );
+ }
+
+ if ( ![_drawable conformsToProtocol:@protocol(VLCPictureInPictureDrawable)] )
+ return;
+
+ id<VLCPictureInPictureDrawable> drawable = (id<VLCPictureInPictureDrawable>)_drawable;
+
+ drawable.pictureInPictureReady(self);
+}
+
+- (void)prepareDisplay {
+ @synchronized(_displayLayer) {
+ if (_displayLayer)
+ return;
+ }
+
+ VLCSampleBufferDisplay *sys = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (sys.displayView)
+ return;
+
+ VLCSampleBufferDisplayView *displayView;
+ VLCSampleBufferSubpictureView *spuView;
+ VLCView *window = sys.window;
+
+ displayView =
+ [[VLCSampleBufferDisplayView alloc] initWithVoutDisplay:sys.vd];
+ spuView = [VLCSampleBufferSubpictureView new];
+ [window addSubview:displayView];
+ [window addSubview:spuView];
+ [displayView setFrame:[window bounds]];
+ [spuView setFrame:[window bounds]];
+
+ vout_display_PlacePicture(
+ &sys->place, sys.vd->source, &sys.vd->cfg->display
+ );
+
+ sys.displayView = displayView;
+ sys.spuView = spuView;
+ @synchronized(sys.displayLayer) {
+ sys.displayLayer = displayView.displayLayer;
+ }
+ [sys preparePictureInPicture];
+ });
+}
+
+- (void)close {
+ VLCSampleBufferDisplay *sys = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [sys.displayView removeFromSuperview];
+ [sys.spuView removeFromSuperview];
+ });
+ DeletePipController(_pipcontroller);
+}
+
+#pragma mark - VLCDisplayPictureInPictureControlling
+
+- (void)startPictureInPicture {
+ if ( _pipcontroller
+ && _pipcontroller->ops->start_pip ) {
+ _pipcontroller->ops->start_pip(
+ _pipcontroller
+ );
+ }
+}
+
+- (void)stopPictureInPicture {
+ if ( _pipcontroller
+ && _pipcontroller->ops->stop_pip ) {
+ _pipcontroller->ops->stop_pip(
+ _pipcontroller
+ );
+ }
+}
+
+- (void)invalidatePlaybackState {
+ if ( _pipcontroller
+ && _pipcontroller->ops->invalidate_pip ) {
+ _pipcontroller->ops->invalidate_pip(
+ _pipcontroller
+ );
+ }
+}
+
@end
#pragma mark -
@@ -689,13 +808,7 @@ static void Close(vout_display_t *vd)
DeleteCVPXConverter(sys->converter);
- dispatch_async(dispatch_get_main_queue(), ^{
- if ([sys.container respondsToSelector:@selector(removeVoutSubview:)]) {
- [sys.container removeVoutSubview:sys.displayView];
- }
- [sys.displayView removeFromSuperview];
- [sys.spuView removeFromSuperview];
- });
+ [sys close];
}
static void RenderPicture(vout_display_t *vd, picture_t *pic, vlc_tick_t date) {
@@ -952,40 +1065,7 @@ static void PrepareDisplay (vout_display_t *vd) {
VLCSampleBufferDisplay *sys;
sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
- @synchronized(sys.displayLayer) {
- if (sys.displayLayer)
- return;
- }
-
- dispatch_async(dispatch_get_main_queue(), ^{
- if (sys.displayView)
- return;
- VLCSampleBufferDisplayView *displayView =
- [[VLCSampleBufferDisplayView alloc] initWithVoutDisplay:vd];
- VLCSampleBufferSubpictureView *spuView =
- [VLCSampleBufferSubpictureView new];
- id container = sys.container;
- //TODO: Is it still relevant ?
- if ([container respondsToSelector:@selector(addVoutSubview:)]) {
- [container addVoutSubview:displayView];
- [container addVoutSubview:spuView];
- } else if ([container isKindOfClass:[VLCView class]]) {
- VLCView *containerView = container;
- [containerView addSubview:displayView];
- [containerView addSubview:spuView];
- [displayView setFrame:containerView.bounds];
- [spuView setFrame:containerView.bounds];
- } else {
- displayView = nil;
- spuView = nil;
- }
-
- sys.displayView = displayView;
- sys.spuView = spuView;
- @synchronized(sys.displayLayer) {
- sys.displayLayer = displayView.displayLayer;
- }
- });
+ [sys prepareDisplay];
}
static void Prepare (vout_display_t *vd, picture_t *pic,
@@ -1023,6 +1103,41 @@ static int Control (vout_display_t *vd, int query)
return VLC_SUCCESS;
}
+static pip_controller_t * CreatePipController( vout_display_t *vd, void *cbs_opaque )
+{
+ pip_controller_t *pip_controller = vlc_object_create(vd, sizeof(pip_controller_t));
+
+ module_t **mods;
+ ssize_t total = vlc_module_match("pictureinpicture", NULL, false, &mods, NULL);
+ for (ssize_t i = 0; i < total; ++i)
+ {
+ int (*open)(pip_controller_t *) = vlc_module_map(vd->obj.logger, mods[i]);
+
+ if (open && open(pip_controller) == VLC_SUCCESS)
+ {
+ free(mods);
+ return pip_controller;
+ }
+ }
+
+ free(mods);
+ vlc_object_delete(pip_controller);
+ return NULL;
+}
+
+static void DeletePipController( pip_controller_t * pip_controller )
+{
+ if (pip_controller == NULL)
+ return;
+
+ if( pip_controller->ops->close )
+ {
+ pip_controller->ops->close(pip_controller);
+ }
+
+ vlc_object_delete(pip_controller);
+}
+
static int Open (vout_display_t *vd,
video_format_t *fmt, vlc_video_context *context)
{
@@ -1032,14 +1147,6 @@ static int Open (vout_display_t *vd,
return VLC_EGENERIC;
}
- if (vd->cfg->window->type != VLC_WINDOW_TYPE_NSOBJECT)
- return VLC_EGENERIC;
-
- VLCSampleBufferDisplay *sys = [VLCSampleBufferDisplay new];
- if (sys == nil) {
- return VLC_ENOMEM;
- }
-
// Display will only work with CVPX video context
filter_t *converter = NULL;
if (!vlc_video_context_GetPrivate(context, VLC_VIDEO_CONTEXT_CVPX)) {
@@ -1047,24 +1154,24 @@ static int Open (vout_display_t *vd,
if (!converter)
return VLC_EGENERIC;
}
- sys->converter = converter;
@autoreleasepool {
- id container = (__bridge id)vd->cfg->window->handle.nsobject;
- if (!container) {
- msg_Err(vd, "No drawable-nsobject found!");
+ VLCSampleBufferDisplay *sys =
+ [[VLCSampleBufferDisplay alloc] initWithVoutDisplay:vd];
+
+ if (sys == nil) {
DeleteCVPXConverter(converter);
- return VLC_EGENERIC;
+ return VLC_ENOMEM;
}
- sys.container = container;
+ sys->converter = converter;
vd->sys = (__bridge_retained void*)sys;
static const struct vlc_display_operations ops = {
Close, Prepare, Display, Control, NULL, NULL, NULL,
};
-
+
vd->ops = &ops;
static const vlc_fourcc_t subfmts[] = {
=====================================
modules/video_output/apple/VLCVideoUIView.m
=====================================
@@ -1,7 +1,7 @@
/*****************************************************************************
* VLCVideoUIView.m: iOS UIView vout window provider
*****************************************************************************
- * Copyright (C) 2001-2017 VLC authors and VideoLAN
+ * Copyright (C) 2001-2024 VLC authors and VideoLAN
* Copyright (C) 2020 Videolabs
*
* Authors: Pierre d'Herbemont <pdherbemont at videolan dot org>
@@ -11,6 +11,7 @@
* Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
* Eric Petit <titer at m0k.org>
* Alexandre Janniaux <ajanni at videolabs.io>
+ * 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 Lesser General Public License as published by
@@ -80,7 +81,7 @@
BOOL _resizing;
/* Parent view defined by libvlc_media_player_set_nsobject. */
- UIView *_viewContainer;
+ id _viewContainer;
/* Window observer for mouse-like events. */
UITapGestureRecognizer *_tapRecognizer;
@@ -135,7 +136,7 @@
initWithTarget:self action:@selector(tapRecognized:)];
}
- CGSize size = _viewContainer.bounds.size;
+ CGSize size = self.viewContainerBounds.size;
_width = size.width;
_height = size.height;
[self reportEvent:^{
@@ -145,11 +146,17 @@
return self;
}
+- (CGRect)viewContainerBounds {
+ if ([_viewContainer respondsToSelector:@selector(bounds)])
+ return (CGRect)[_viewContainer bounds];
+ return CGRectZero;
+}
+
- (BOOL)fetchViewContainer
{
@try {
/* get the object we will draw into */
- UIView *viewContainer = (__bridge UIView*)var_InheritAddress (_wnd, "drawable-nsobject");
+ id viewContainer = (__bridge id)var_InheritAddress (_wnd, "drawable-nsobject");
if (unlikely(viewContainer == nil)) {
msg_Err(_wnd, "provided view container is nil");
return NO;
@@ -160,8 +167,13 @@
return NO;
}
- if (![viewContainer isKindOfClass:[UIView class]]) {
- msg_Err(_wnd, "passed ObjC object not of class UIView");
+ if (unlikely(![viewContainer respondsToSelector:@selector(addSubview:)])) {
+ msg_Err(_wnd, "view container doesn't responds to addSubview:");
+ return NO;
+ }
+
+ if (unlikely(![viewContainer respondsToSelector:@selector(bounds)])) {
+ msg_Err(_wnd, "view container doesn't responds to bounds");
return NO;
}
@@ -173,7 +185,7 @@
* in the core for the display are happening. */
_viewContainer = viewContainer;
- self.frame = viewContainer.bounds;
+ self.frame = self.viewContainerBounds;
return YES;
} @catch (NSException *exception) {
=====================================
modules/video_output/apple/vlc_pip_controller.h
=====================================
@@ -0,0 +1,60 @@
+/*****************************************************************************
+ * vlc_pip_controller.h : picture in picture controller module 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 Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifndef VLC_PIP_CONTROLLER_H
+#define VLC_PIP_CONTROLLER_H 1
+
+#include <vlc_common.h>
+#include <vlc_tick.h>
+#include <vlc_player.h>
+
+typedef struct pip_controller_t pip_controller_t;
+
+struct pip_controller_operations {
+ void (*set_display_layer)(pip_controller_t *, void *);
+ void (*start_pip)(pip_controller_t *);
+ void (*stop_pip)(pip_controller_t *);
+ void (*invalidate_pip)(pip_controller_t *);
+ int (*close)(pip_controller_t *);
+};
+
+struct pip_controller_media_callbacks {
+ void (*play)(void* opaque);
+ void (*pause)(void* opaque);
+ void (*seek_by)(vlc_tick_t time, void* opaque);
+ vlc_tick_t (*media_length)(void* opaque);
+ vlc_tick_t (*media_time)(void* opaque);
+ bool (*is_media_playing)(void* opaque);
+};
+
+struct pip_controller_t
+{
+ struct vlc_object_t obj;
+
+ void *p_sys;
+
+ const struct pip_controller_operations *ops;
+ const struct pip_controller_media_callbacks *media_cbs;
+};
+
+#endif // VLC_PIP_CONTROLLER_H
\ No newline at end of file
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/b8a97c6274f6c39a51605d04c129121ec85c24e9
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/b8a97c6274f6c39a51605d04c129121ec85c24e9
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list