Spracherkennung für: .m vermutete Sprache: Cobol {Cobol[100] ABAP[69] Hlasm[69]} [Methode: maximale Elemente, drei Dimensionen]
/*****************************************************************************
* VLCVideoUIView.m: iOS UIView vout window provider
*****************************************************************************
* Copyright (C) 2001-2024 VLC authors and VideoLAN
* Copyright (C) 2020 Videolabs
*
* Authors: Pierre d'Herbemont <pdherbemont at videolan dot org>
* Felix Paul Kühne <fkuehne at videolan dot org>
* David Fuhrmann <david dot fuhrmann at googlemail dot com>
* Rémi Denis-Courmont
* Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
* Eric Petit <titer@m0k.org>
* Alexandre Janniaux <ajanni@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
* 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.
*****************************************************************************/
/**
* @file VLCVideoUIView.m
* @brief UIView-based vlc_window_t provider
*
* This UIView window provider mostly handles resizing constraints from parent
* views and provides event forwarding to VLC. It is usable for any kind of
* subview and in particular can be used to implement a CAEAGLLayer in a
* vlc_gl_t provider as well as a CAMetalLayer, or other CALayer based video
* output in general.
*
* In particular, UI event will be forwarded to the core without the display
* lock thanks to this implementation, but vout display implementation will
* need to let the event pass through to this UIView.
*
* Note that this module is asynchronous with the usual VLC execution flow:
* except during Open(), where a status code is needed and synchronization
* must be done with the main thread, everything is forwarded to and
* asynchronously executed by the main thread. In particular, the closing
* of this module must be done asynchronously to not require the main thread
* to run, and the hosting application will need to drain the main thread
* dispatch queue. For iOS, it basically means nothing more than running the
* usual UIApplicationMain.
*/
#import <TargetConditionals.h>
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <dlfcn.h>
#ifdef HAVE_CONFIG_H
# import "config.h"
#endif
#import <vlc_common.h>
#import <vlc_threads.h>
#import <vlc_plugin.h>
#import <vlc_dialog.h>
#import <vlc_mouse.h>
#import <vlc_window.h>
@interface VLCVideoUIView : UIView {
/* VLC window object, set to NULL under _mutex lock when closing. */
vlc_window_t *_wnd;
vlc_mutex_t _mutex;
vlc_mutex_t _size_mutex;
unsigned _requested_width;
unsigned _requested_height;
unsigned _width;
unsigned _height;
BOOL _resizing;
/* Parent view defined by libvlc_media_player_set_nsobject. */
id _viewContainer;
/* Window observer for mouse-like events. */
UITapGestureRecognizer *_tapRecognizer;
/* Window state */
BOOL _enabled;
int _subviews;
dispatch_queue_t _eventq;
}
- (id)initWithWindow:(vlc_window_t *)wnd;
- (BOOL)fetchViewContainer;
- (void)detachFromParent;
- (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer;
- (void)enable;
- (void)disable;
@end
/*****************************************************************************
* Our UIView object
*****************************************************************************/
@implementation VLCVideoUIView
- (id)initWithWindow:(vlc_window_t *)wnd
{
_wnd = wnd;
_enabled = NO;
_subviews = 0;
vlc_mutex_init(&_size_mutex);
_requested_height = 0;
_requested_width = 0;
_resizing = NO;
self = [super initWithFrame:CGRectMake(0., 0., 320., 240.)];
if (!self)
return nil;
_eventq = dispatch_queue_create("vlc_eventq", DISPATCH_QUEUE_SERIAL);
vlc_mutex_init(&_mutex);
/* The window is controlled by the host application through the UIView
* sizing mechanisms. */
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if (![self fetchViewContainer])
return nil;
/* add tap gesture recognizer for DVD menus and stuff */
if (var_InheritBool( wnd, "mouse-events" ) == true) {
_tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(tapRecognized:)];
}
CGSize size = self.viewContainerBounds.size;
_width = size.width;
_height = size.height;
[self reportEvent:^{
vlc_window_ReportSize(_wnd, size.width, size.height);
}];
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 */
id viewContainer = (__bridge id)var_InheritAddress (_wnd, "drawable-nsobject");
if (unlikely(viewContainer == nil)) {
msg_Err(_wnd, "provided view container is nil");
return NO;
}
if (unlikely(![viewContainer respondsToSelector:@selector(isKindOfClass:)])) {
msg_Err(_wnd, "void pointer not an ObjC object");
return NO;
}
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;
}
/* We need to store the view container because we'll add our view
* only when a subview (hopefully able to handle the rendering) will
* get created. The main reason for this is that we cannot report
* events from the window until the display is opened, otherwise a
* race condition involving locking both the main thread and the lock
* in the core for the display are happening. */
_viewContainer = viewContainer;
self.frame = self.viewContainerBounds;
return YES;
} @catch (NSException *exception) {
msg_Err(_wnd, "Handling the view container failed due to an Obj-C exception (%s, %s", [exception.name UTF8String], [exception.reason UTF8String]);
return NO;
}
}
- (void)reportEventAsync:(void(^)())eventBlock
{
dispatch_async(_eventq, eventBlock);
}
- (void)reportEvent:(void(^)())eventBlock
{
CFStringRef mode = CFSTR("org.videolan.vlccore.window");
CFRunLoopRef runloop = CFRunLoopGetCurrent();
/* Callback hell right below, we need to execute the call
* to CFRunLoopStop inside the CFRunLoopRunInMode context
* since the CFRunLoopRunInMode might have already returned
* otherwise, which means more callback wrapping. */
CFRunLoopPerformBlock(runloop, mode, ^{
/* Execute the event in a different thread, we don't
* want to block the main CFRunLoop since the vout
* display module typically needs it to Open(). */
dispatch_async(_eventq, ^{
(eventBlock)();
CFRunLoopPerformBlock(runloop, mode, ^{
/* Signal that we can end the ReportEvent call */
CFRunLoopStop(runloop);
});
CFRunLoopWakeUp(runloop);
});
});
/* Above and here, the CFRunLoopWakeUp call is necessary to
* signal to the event loop that it will need to process the
* blocks. They don't act like CFRunLoopSource so they won't
* wake up the loop otherwise. */
CFRunLoopWakeUp(runloop);
for (;;)
{
/* We need a timeout here, otherwise the CFRunLoopInMode
* call will check the events (if woken up), and since
* we might have no event, it would return a timeout
* result code, and loop again, creating a busy loop.
* INFINITY is more than enough, and we'll interrupt
* anyway. */
CFRunLoopRunResult ret = CFRunLoopRunInMode(mode, INFINITY, YES);
/* Usual CFRunLoop are typically checking result code
* like kCFRunLoopRunFinished too, but we really want
* to receive the Stop signal from above to leave the
* loop in the correct state. */
if (ret == kCFRunLoopRunStopped)
break;
}
}
- (void)detachFromParent
{
/* We need to lock because we consider that _wnd might be destroyed
* after this function returns, typically as it will be called in the
* Close() operation which preceed the vlc_window_t destruction in
* the core. */
vlc_mutex_lock(&_mutex);
/* The UIView must not be attached before releasing. Disable() is doing
* exactly this asynchronously in the main thread so ensure it was called
* here before detaching from the parent. */
_wnd = NULL;
vlc_mutex_unlock(&_mutex);
}
/**
* We track whether we currently have a child view, which will be able
* to do the actual rendering. Depending on how many children view we had
* and whether we are in enabled state or not, we add or remove the view
* from/to the parent UIView.
* This is needed because we cannot emit resize event from the main
* thread as long as the display is not created, since the display will
* need the main thread too. It would non-deterministically deadlock
* otherwise.
*/
- (void)didAddSubview:(UIView*)subview
{
assert(_enabled);
_subviews++;
if (_subviews == 1)
[_viewContainer addSubview:self];
VLC_UNUSED(subview);
}
- (void)willRemoveSubview:(UIView*)subview
{
_subviews--;
if (_enabled && _subviews == 0)
[self removeFromSuperview];
VLC_UNUSED(subview);
}
/**
* Vout window operations implementation, which are expected to be run on
* the main thread only. Core C wrappers below must typically use
* dispatch_async with dispatch_get_main_queue() to call them.
*
* The addition of the UIView to the parent UIView might happen later
* if there's no subview attached yet.
*/
- (void)enable
{
if (_enabled)
return;
assert(_subviews == 0);
_enabled = YES;
/**
* Given -[UIView addGestureRecognizer:] can raise an exception if
* tapRecognizer is nil and given tapRecognizer can be nil if
* "mouse-events" var == false, then add tapRecognizer to the view only if
* it's not nil
*/
if (_tapRecognizer != nil) {
[self addGestureRecognizer:_tapRecognizer];
}
_tapRecognizer.cancelsTouchesInView = NO;
}
- (void)disable
{
if (!_enabled)
return;
_enabled = NO;
assert(_subviews == 0);
[self removeFromSuperview];
[_tapRecognizer.view removeGestureRecognizer:_tapRecognizer];
}
/**
* Window state tracking and reporting
*/
- (void)didMoveToWindow
{
#if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
self.contentScaleFactor = self.window.screen.scale;
#endif
}
- (void)layoutSubviews
{
[self reshape];
}
- (void)reshape
{
assert([NSThread isMainThread]);
CGSize viewSize = [self bounds].size;
CGFloat scaleFactor = self.contentScaleFactor;
/* We need to lock to ensure _wnd is still valid, see detachFromParent. */
vlc_mutex_lock(&_size_mutex);
_requested_width = viewSize.width * scaleFactor;
_requested_height = viewSize.height * scaleFactor;
if (_resizing == NO) {
_resizing = YES;
[self reportEventAsync:^{
unsigned w, h;
vlc_mutex_lock(&_size_mutex);
while (_requested_width != _width ||
_requested_height != _height) {
w = _requested_width;
h = _requested_height;
vlc_mutex_unlock(&_size_mutex);
vlc_mutex_lock(&_mutex);
if (_wnd != NULL)
vlc_window_ReportSize(_wnd, w, h);
vlc_mutex_unlock(&_mutex);
vlc_mutex_lock(&_size_mutex);
_width = w;
_height = h;
}
_resizing = NO;
vlc_mutex_unlock(&_size_mutex);
}];
}
vlc_mutex_unlock(&_size_mutex);
}
- (void)tapRecognized:(UITapGestureRecognizer *)tapRecognizer
{
UIGestureRecognizerState state = [tapRecognizer state];
CGPoint touchPoint = [tapRecognizer locationInView:self];
CGFloat scaleFactor = self.contentScaleFactor;
/* We need to lock to ensure _wnd is still valid, see detachFromParent. */
vlc_mutex_lock(&_mutex);
if (_wnd == NULL)
{
vlc_mutex_unlock(&_mutex);
return;
}
vlc_window_ReportMouseMoved(_wnd,
(int)touchPoint.x * scaleFactor, (int)touchPoint.y * scaleFactor);
vlc_window_ReportMousePressed(_wnd, MOUSE_BUTTON_LEFT);
vlc_window_ReportMouseReleased(_wnd, MOUSE_BUTTON_LEFT);
vlc_mutex_unlock(&_mutex);
}
- (void)updateConstraints
{
[super updateConstraints];
[self reshape];
}
/* Subview are expected to fill the whole frame so tell the compositor
* that it doesn't have to bother with what's behind the window. */
- (BOOL)isOpaque
{
return YES;
}
/* Prevent the subviews (which are renderers only) to get events so that
* they can be dispatched from this vout_window module. */
- (BOOL)acceptsFirstResponder
{
return YES;
}
@end
/**
* C core wrapper of the vout window operations for the ObjC module.
*/
static int Enable(vlc_window_t *wnd, const vlc_window_cfg_t *cfg)
{
VLCVideoUIView *sys = (__bridge VLCVideoUIView *)wnd->sys;
dispatch_async(dispatch_get_main_queue(), ^{
[sys enable];
});
return VLC_SUCCESS;
}
static void Disable(vlc_window_t *wnd)
{
VLCVideoUIView *sys = (__bridge VLCVideoUIView *)wnd->sys;
dispatch_async(dispatch_get_main_queue(), ^{
[sys disable];
});
}
static void Close(vlc_window_t *wnd)
{
VLCVideoUIView *sys = (__bridge_transfer VLCVideoUIView*)wnd->sys;
/* We need to signal the asynchronous implementation that we have been
* closed and cannot used _wnd anymore. */
[sys detachFromParent];
}
static const struct vlc_window_operations window_ops =
{
.enable = Enable,
.disable = Disable,
.destroy = Close,
};
static int Open(vlc_window_t *wnd)
{
dispatch_sync(dispatch_get_main_queue(), ^{
VLCVideoUIView *sys = [[VLCVideoUIView alloc] initWithWindow:wnd];
wnd->sys = (__bridge_retained void*)sys;
});
if (wnd->sys == NULL)
{
msg_Err(wnd, "Creating UIView window provider failed");
return VLC_EGENERIC;
}
wnd->type = VLC_WINDOW_TYPE_NSOBJECT;
wnd->handle.nsobject = wnd->sys;
wnd->ops = &window_ops;
return VLC_SUCCESS;
}
vlc_module_begin ()
set_shortname("UIView")
set_description("iOS UIView vout window provider")
set_subcategory(SUBCAT_VIDEO_VOUT)
set_capability("vout window", 300)
set_callback(Open)
add_shortcut("uiview", "ios")
vlc_module_end ()