iOS Question Help with Inline Objective C (SOLVED)

walterf25

Expert
Licensed User
Longtime User
Hi All, it's been a while that I have played around with B4I, I am playing with the PIP Controller, found this code somewhere online and wanted to see if I can get it to work, I am getting an error about the initWithPlayerViewController method not being found
Here's the objective C code
PIPController:
#If OBJC
#import <Foundation/Foundation.h>
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
@end
@interface PIPController: NSObject <AVPictureInPictureControllerDelegate>

@property (nonatomic, strong) AVPictureInPictureController *pipController;
@property (nonatomic, strong) AVPlayerViewController *playerViewController;

- (instancetype)initWithPlayerViewController:(AVPlayerViewController *)playerViewController;
- (void)startPIP;
- (void)stopPIP;
@end


@implementation PIPController

- (instancetype)initWithPlayerViewController:(AVPlayerViewController *)playerViewController {
    NSLog(@"inside");
    self = [super init];
    if (self) {
        self.playerViewController = playerViewController;
        AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:playerViewController.player];
        if ([AVPictureInPictureController isPictureInPictureSupported]) {
            self.pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:playerLayer];
            self.pipController.delegate = self;
        }
    }
    return self;
}

- (void)startPIP {
    if (self.pipController) {
        [self.pipController startPictureInPicture];
    }
}

- (void)stopPIP {
    if (self.pipController) {
        [self.pipController stopPictureInPicture];
    }
}
#End If

the error I am seeing is the following:
Error occurred on line: 133 (B4XMainPage)
Method not found: initWithPlayerViewController:, target: PIPController
Stack Trace: (
CoreFoundation 42CCFC7B-FF32-3D25-8F01-CCB2AD843A8B + 40516
libobjc.A.dylib objc_exception_throw + 60
CoreFoundation 42CCFC7B-FF32-3D25-8F01-CCB2AD843A8B + 1023016
B4i Example +[B4I runDynamicMethod:method:throwErrorIfMissing:args:] + 352
B4i Example -[B4INativeObject RunMethod::] + 176
B4i Example -[b4i_b4xmainpage _createpipcontroller::] + 668
B4i Example -[b4i_b4xmainpage _button1_click:] + 512
CoreFoundation 42CCFC7B-FF32-3D25-8F01-CCB2AD843A8B + 476852
CoreFoundation 42CCFC7B-FF32-3D25-8F01-CCB2AD843A8B + 133916
B4i Example +[B4I runDynamicMethod:method:throwErrorIfMissing:args:] + 1348
B4i Example -[B4IShell runMethod:] + 320
B4i Example -[B4IShell raiseEventImpl:method:args::] + 928
B4i Example -[B4IShellBI raiseEvent:event:params:] + 1008
B4i Example __33-[B4I raiseUIEvent:event:params:]_block_invoke + 52
libdispatch.dylib DED4D0A5-1420-32AE-83A6-C31D938A1C9A + 9312
libdispatch.dylib DED4D0A5-1420-32AE-83A6-C31D938A1C9A + 16264
libdispatch.dylib DED4D0A5-1420-32AE-83A6-C31D938A1C9A + 75764
libdispatch.dylib _dispatch_main_queue_callback_4CF + 44
CoreFoundation 42CCFC7B-FF32-3D25-8F01-CCB2AD843A8B + 632520
CoreFoundation 42CCFC7B-FF32-3D25-8F01-CCB2AD843A8B + 507948
CoreFoundation CFRunLoopRunSpecific + 612
GraphicsServices GSEventRunModal + 164
UIKitCore CF21AD9C-EFBF-3961-A7C0-54BD30CEFEA9 + 3806824
UIKitCore UIApplicationMain + 340
B4i Example main + 100
dyld 4B042F28-0D14-30EC-A1DE-3DBB10866AD7 + 88416
)
This is how I am Initializing the playerViewController etc.
B4X:
    playerViewController = CreatePlayerViewController("https://bestvpn.org/html5demos/assets/dizzy.mp4")
    VideoPlayer1 = playerViewController.GetField("view")
    pip = CreatePIPController(playerViewController)

And the rest of the functions are these.
B4X:
Sub CreatePlayerViewController(videoURL As String) As NativeObject
    Dim playerViewController2 As NativeObject
    playerViewController2 = playerViewController2.Initialize("AVPlayerViewController").RunMethod("new", Null)
    
    Dim player As NativeObject
    player = player.Initialize("AVPlayer").RunMethod("playerWithURL:", Array(CreateURL(videoURL)))
    
    playerViewController2.RunMethod("setPlayer:", Array(player))
    Return playerViewController2
End Sub

Sub CreateURL(url As String) As NativeObject
    Dim nsurl As NativeObject
    nsurl = nsurl.Initialize("NSURL").RunMethod("URLWithString:", Array(url))
'''    nsurl.Initialize("NSURL").RunMethod("initWithString:", Array(url))
    Dim description As String = nsurl.RunMethod("absoluteString", Null).AsString
    Log("nsurl: " & description)
    Return nsurl
End Sub

Sub CreatePIPController(playerViewController2 As NativeObject) As NativeObject
    Dim pip As NativeObject
    pip = pip.Initialize("PIPController").RunMethod("initWithPlayerViewController:", Array(playerViewController2))
    Return pip
End Sub

I get the error right at this line in the CreatePIPController function
B4X:
pip = pip.Initialize("PIPController").RunMethod("initWithPlayerViewController:", Array(playerViewController2))

Any thoughts, It's late for me here and my brain if foggy, I know I must be missing something that I just can't seem to figure out right now.

Thanks,
Walter
 

walterf25

Expert
Licensed User
Longtime User
B4X:
pip = pip.Initialize("PIPController").RunMethod("alloc", Null).RunMethod("initWithPlayerViewController:", Array(playerViewController2))
Thank you Erel, It seems I have everything working, but I can't get the PIP working when the home button is pressed, here's my complete code:

B4X:
Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    Private btnStop As B4XView
    
    Private pip As NativeObject
    Private playerViewController As NativeObject
    Private playerLayerHelper As NativeObject
    Private playerContainer As Panel
End Sub

Public Sub Initialize
'    B4XPages.GetManager.LogEvents = True
End Sub

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")
    
    playerViewController = CreatePlayerViewController("https://bestvpn.org/html5demos/assets/dizzy.mp4")
    playerLayerHelper = CreatePlayerLayer(playerViewController)
    AddPlayerViewToContainer(playerViewController, playerContainer)
    pip = CreatePIPController(playerLayerHelper)
    
    
End Sub

'You can see the list of page related events in the B4XPagesManager object. The event name is B4XPage.

Private Sub Button1_Click
    StartPlayingVideo(playerViewController)
End Sub

Sub StartPlayingVideo(playerViewController2 As NativeObject)
    Dim player As NativeObject = playerViewController2.GetField("player")
    player.RunMethod("play", Null)
End Sub

Private Sub btnStop_Click
    pip.RunMethod("stopPIP", Null)
End Sub

Private Sub StartPIP
    pip.RunMethod("startPIP", Null)
End Sub

private Sub StopPIP
    pip.RunMethod("stopPIP", Null)
End Sub
Sub CreatePlayerViewController(videoURL As String) As NativeObject
    Dim playerViewController2 As NativeObject
    playerViewController2 = playerViewController2.Initialize("AVPlayerViewController").RunMethod("new", Null)
    
    Dim player As NativeObject
    player = player.Initialize("AVPlayer").RunMethod("playerWithURL:", Array(CreateURL(videoURL)))
    playerViewController2.RunMethod("setPlayer:", Array(player))
    Return playerViewController2
End Sub

Sub CreateURL(url As String) As NativeObject
    Dim nsurl As NativeObject
    nsurl = nsurl.Initialize("NSURL").RunMethod("URLWithString:", Array(url))
    Dim description As String = nsurl.RunMethod("absoluteString", Null).AsString
    Log("nsurl: " & description)
    Return nsurl
End Sub

Sub CreatePIPController(playerLayer2 As NativeObject) As NativeObject
    Dim pip As NativeObject = Me
    pip = pip.Initialize("PIPController").RunMethod("alloc", Null).RunMethod("initWithPlayerLayer:", Array(playerLayer2))
    Return pip
End Sub

Sub CreatePlayerLayer(playerViewController2 As NativeObject) As NativeObject
    Dim playerHelper2 As NativeObject
    playerHelper2 =    playerHelper2.Initialize("PlayerLayerHelper").RunMethod("new", Null)
    playerHelper2.RunMethod("createPlayerLayer:", Array(playerViewController2))
    
    Return playerHelper2.GetField("playerLayer")
End Sub

Sub AddPlayerViewToContainer(playerViewController2 As NativeObject, container As Panel)
    Dim playerView As NativeObject = playerViewController2.GetField("view")
    container.AddView(playerView.As(View), 0, 0, container.Width/2, container.Height/2)
    Log("playerLayer is initialized: " & playerLayerHelper.IsInitialized)
    Dim flexibleWidth As NativeObject
    Dim flexibleHeight As NativeObject
    flexibleWidth.Initialize("NSNumber").RunMethod("numberWithInt:", Array(2))
    flexibleHeight.Initialize("NSNumber").RunMethod("numberWithInt:", Array(16))
    playerView.RunMethod("setAutoresizingMask:", Array(flexibleWidth Or flexibleHeight))
End Sub

Sub getDelegate As NativeObject
    Dim no As NativeObject = Main.App
    no = no.GetField("delegate")
    Return no
End Sub

#If OBJC
#import <Foundation/Foundation.h>
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
@end
@interface PlayerLayerHelper : NSObject
@property (nonatomic, strong) AVPlayerLayer *playerLayer;

- (void)createPlayerLayer:(AVPlayerViewController *)playerViewController;
- (void)setFrameForView:(UIView *)view x:(CGFloat)x y:(CGFloat)y width:(CGFloat)width height:(CGFloat)height;
@end

@implementation PlayerLayerHelper

- (void)createPlayerLayer:(AVPlayerViewController *)playerViewController {
    NSLog(@"creating PlayerLayer");
    self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:playerViewController.player];
    [playerViewController.view.layer addSublayer:self.playerLayer];
    self.playerLayer.frame = playerViewController.view.bounds;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
}

- (void)setFrameForView:(UIView *)view x:(CGFloat)x y:(CGFloat)y width:(CGFloat)width height:(CGFloat)height {
    NSLog(@"found setFrameForView method");
    view.frame = CGRectMake(x, y, width, height);
}


@end
@interface PIPController: NSObject <AVPictureInPictureControllerDelegate>

@property (nonatomic, strong) AVPictureInPictureController *pipController;
@property (nonatomic, strong) AVPlayerViewController *playerViewController;

- (instancetype)initWithPlayerViewController:(AVPlayerViewController *)playerViewController;
- (void)startPIP;
- (void)stopPIP;
@end

@implementation PIPController

- (instancetype)initWithPlayerLayer:(AVPlayerLayer *)playerLayer {
    self = [super init];
    if (self) {
        if ([AVPictureInPictureController isPictureInPictureSupported]) {
            self.pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:playerLayer];
            self.pipController.delegate = self;
        }
    }
    return self;
}

- (void)startPIP {
    if (self.pipController) {
        NSLog(@"starting playing from PIPController");
        [self.pipController startPictureInPicture];
    }
}

- (void)stopPIP {
    if (self.pipController) {
        [self.pipController stopPictureInPicture];
    }
}
#End If

Sub B4XPage_Background
    Log("application placed in background")
    StartPIP
End Sub

Also I have added the UIBackground Modes PlistExtra

#PlistExtra: <key>UIBackgroundModes</key><array><string>audio</string></array>

But the video still doesn't play in PIP mode when the application is placed in the background, am I missing something?

Walter
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Never mind, found the issue, had to add the following to get it working.
Objective-C:
@interface AudioSessionHelper : NSObject
+ (void)configureAudioSession;
@end

@implementation AudioSessionHelper

+ (void)configureAudioSession {
    NSError *error = nil;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
                                     withOptions:AVAudioSessionCategoryOptionMixWithOthers
                                           error:&error];
    if (error) {
        NSLog(@"Error setting audio session category: %@", error);
    } else {
        [[AVAudioSession sharedInstance] setActive:YES error:&error];
        if (error) {
            NSLog(@"Error activating audio session: %@", error);
        }
    }
}

Now the PIP video works.

Here's the entire code in case anyone needs to use this in any of their apps.

PIP Controller:
Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    Private btnStop As B4XView
    
    Private pip As NativeObject
    Private playerViewController As NativeObject
    Private playerLayerHelper As NativeObject
    Private playerContainer As Panel
End Sub

Public Sub Initialize
'    B4XPages.GetManager.LogEvents = True
End Sub

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")
    
    playerViewController = CreatePlayerViewController("https://bestvpn.org/html5demos/assets/dizzy.mp4")
    playerLayerHelper = CreatePlayerLayer(playerViewController)
    AddPlayerViewToContainer(playerViewController, playerContainer)
    pip = CreatePIPController(playerLayerHelper)
    
    
End Sub

'You can see the list of page related events in the B4XPagesManager object. The event name is B4XPage.

Private Sub Button1_Click
    StartPlayingVideo(playerViewController)
End Sub

Sub StartPlayingVideo(playerViewController2 As NativeObject)
    Dim player As NativeObject = playerViewController2.GetField("player")
    player.RunMethod("play", Null)
End Sub

Private Sub btnStop_Click
    pip.RunMethod("stopPIP", Null)
End Sub

Private Sub StartPIP
    pip.RunMethod("startPIP", Null)
End Sub

private Sub StopPIP
    pip.RunMethod("stopPIP", Null)
End Sub
Sub CreatePlayerViewController(videoURL As String) As NativeObject
    Dim playerViewController2 As NativeObject
    playerViewController2 = playerViewController2.Initialize("AVPlayerViewController").RunMethod("new", Null)
    
    Dim player As NativeObject
    player = player.Initialize("AVPlayer").RunMethod("playerWithURL:", Array(CreateURL(videoURL)))
    playerViewController2.RunMethod("setPlayer:", Array(player))
    
    Dim audioSessionHelper As NativeObject
    audioSessionHelper.Initialize("AudioSessionHelper").RunMethod("configureAudioSession", Null)
    
    Return playerViewController2
End Sub

Sub CreateURL(url As String) As NativeObject
    Dim nsurl As NativeObject
    nsurl = nsurl.Initialize("NSURL").RunMethod("URLWithString:", Array(url))))
    Dim description As String = nsurl.RunMethod("absoluteString", Null).AsString
    Log("nsurl: " & description)
    Return nsurl
End Sub

Sub CreatePIPController(playerLayer2 As NativeObject) As NativeObject
    Dim pip As NativeObject = Me
    pip = pip.Initialize("PIPController").RunMethod("alloc", Null).RunMethod("initWithPlayerLayer:", Array(playerLayer2))
    Return pip
End Sub

Sub CreatePlayerLayer(playerViewController2 As NativeObject) As NativeObject
    Dim playerHelper2 As NativeObject
    playerHelper2 =    playerHelper2.Initialize("PlayerLayerHelper").RunMethod("new", Null)
    playerHelper2.RunMethod("createPlayerLayer:", Array(playerViewController2))
    
    Return playerHelper2.GetField("playerLayer")
End Sub

Sub AddPlayerViewToContainer(playerViewController2 As NativeObject, container As Panel)
    Dim playerView As NativeObject = playerViewController2.GetField("view")
    container.AddView(playerView.As(View), 0, 0, container.Width/2, container.Height/2)
    Log("playerLayer is initialized: " & playerLayerHelper.IsInitialized)
    Dim flexibleWidth As NativeObject
    Dim flexibleHeight As NativeObject
    flexibleWidth.Initialize("NSNumber").RunMethod("numberWithInt:", Array(2))
    flexibleHeight.Initialize("NSNumber").RunMethod("numberWithInt:", Array(16))
    playerView.RunMethod("setAutoresizingMask:", Array(flexibleWidth Or flexibleHeight))
End Sub

Sub getDelegate As NativeObject
    Dim no As NativeObject = Main.App
    no = no.GetField("delegate")
    Return no
End Sub

#If OBJC
#import <Foundation/Foundation.h>
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
@end
@interface PlayerLayerHelper : NSObject
@property (nonatomic, strong) AVPlayerLayer *playerLayer;

- (void)createPlayerLayer:(AVPlayerViewController *)playerViewController;
- (void)setFrameForView:(UIView *)view x:(CGFloat)x y:(CGFloat)y width:(CGFloat)width height:(CGFloat)height;
@end

@implementation PlayerLayerHelper

- (void)createPlayerLayer:(AVPlayerViewController *)playerViewController {
    NSLog(@"creating PlayerLayer");
    self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:playerViewController.player];
    [playerViewController.view.layer addSublayer:self.playerLayer];
    self.playerLayer.frame = playerViewController.view.bounds;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
}

- (void)setFrameForView:(UIView *)view x:(CGFloat)x y:(CGFloat)y width:(CGFloat)width height:(CGFloat)height {
    NSLog(@"found setFrameForView method");
    view.frame = CGRectMake(x, y, width, height);
}


@end
@interface PIPController: NSObject <AVPictureInPictureControllerDelegate>

@property (nonatomic, strong) AVPictureInPictureController *pipController;
@property (nonatomic, strong) AVPlayerViewController *playerViewController;

- (instancetype)initWithPlayerViewController:(AVPlayerViewController *)playerViewController;
- (void)startPIP;
- (void)stopPIP;
@end

@implementation PIPController

- (instancetype)initWithPlayerLayer:(AVPlayerLayer *)playerLayer {
    self = [super init];
    if (self) {
        if ([AVPictureInPictureController isPictureInPictureSupported]) {
            self.pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:playerLayer];
            self.pipController.delegate = self;
        }
    }
    return self;
}

- (void)startPIP {
    if (self.pipController) {
        NSLog(@"starting playing from PIPController");
        [self.pipController startPictureInPicture];
    }
}

- (void)stopPIP {
    if (self.pipController) {
        [self.pipController stopPictureInPicture];
    }
}

@end
@interface AudioSessionHelper : NSObject
+ (void)configureAudioSession;
@end

@implementation AudioSessionHelper

+ (void)configureAudioSession {
    NSError *error = nil;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
                                     withOptions:AVAudioSessionCategoryOptionMixWithOthers
                                           error:&error];
    if (error) {
        NSLog(@"Error setting audio session category: %@", error);
    } else {
        [[AVAudioSession sharedInstance] setActive:YES error:&error];
        if (error) {
            NSLog(@"Error activating audio session: %@", error);
        }
    }
}
#End If

Sub B4XPage_Background
    Log("application placed in background")
    StartPIP
End Sub

don't forget to add this line to the Main page.
#PlistExtra: <key>UIBackgroundModes</key><array><string>audio</string></array>

Cheers,
Walter
 
Upvote 0
Top