iosiphoneswiftmpmovieplayercontrollercode-migration

Change MPMoviePlayerController fullscreen button icon to caption icon in iOS in Swift Project


I am using MPMoviePlayerController as an embedded video player, but the fullscreen icon from iOS 10+ has changed.

Original image in iOS 8, 9

enter image description here

Image in iOS 10+

enter image description here

I made the change earlier in an Objective-C project. Referring to this StackOverflow post. WorkaroundInlinePlayerFullScreenButtonBug.m

@import MediaPlayer;
@import ObjectiveC;

static void (*configureAuxiliaryButtonsIMP)(id, SEL, BOOL);

static void ConfigureAuxiliaryButtons(id self, SEL _cmd, BOOL flag)
{
    configureAuxiliaryButtonsIMP(self, _cmd, flag);
    @try
    {
        id delegate = [self delegate]; // Either nil or MPInlineVideoController (responds to `isFullscreen`) or MPInlineVideoFullscreenViewController (does not respond to `isFullscreen`)
        BOOL isFullscreen = [delegate respondsToSelector:@selector(isFullscreen)] ? [delegate isFullscreen] : YES;
        NSString *imageName = [@[ @"Video", @"Player", @"_", isFullscreen ? @"Exit" : @"Enter", @"Fullscreen" ] componentsJoinedByString:@""];
        SEL imageNamedForControlState = NSSelectorFromString([@[ @"_", @"image", @"Named", @":", @"for", @"Control", @"State", @":" ] componentsJoinedByString:@""]);
        UIImage *normalImage = ((UIImage *(*)(id, SEL, NSString *, UIControlState))objc_msgSend)(self, imageNamedForControlState, imageName, UIControlStateNormal);
        UIImage *highlightedImage = ((UIImage *(*)(id, SEL, NSString *, UIControlState))objc_msgSend)(self, imageNamedForControlState, imageName, UIControlStateHighlighted);
        UIButton *fullscreenButton = [self valueForKey:[@[ @"fullscreen", @"Button" ] componentsJoinedByString:@""]];
        [fullscreenButton setImage:normalImage forState:UIControlStateNormal];
        [fullscreenButton setImage:highlightedImage forState:UIControlStateHighlighted];
    }
    @catch (NSException *exception)
    {
        NSLog(@"Failed to workaround inline player fullscreen button bug: %@", exception);
    }
}

void WorkaroundInlinePlayerFullScreenButtonBug(void)
{
    if (![NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 0, 0}])
        return;

    Class MPVideoPlaybackOverlayView = NSClassFromString([@[ @"M", @"P", @"Video", @"Playback", @"Overlay", @"View" ] componentsJoinedByString:@""]);
    SEL configureAuxiliaryButtonsSEL = NSSelectorFromString([@[ @"_", @"configure", @"Auxiliary", @"Buttons", @":" ] componentsJoinedByString:@""]);
    NSMethodSignature *methodSignature = [MPVideoPlaybackOverlayView instanceMethodSignatureForSelector:configureAuxiliaryButtonsSEL];
    if (methodSignature.numberOfArguments != 3)
    {
        NSLog(@"Failed to workaround inline player fullscreen button bug (method not found)");
        return;
    }

    const char *returnType = methodSignature.methodReturnType;
    const char *argType = [methodSignature getArgumentTypeAtIndex:2];
    if (strcmp(returnType, @encode(void)) != 0 || strcmp(argType, @encode(BOOL)) != 0)
    {
        NSLog(@"Failed to workaround inline player fullscreen button bug (type mismatch)");
        return;
    }

    Method configureAuxiliaryButtons = class_getInstanceMethod(MPVideoPlaybackOverlayView, configureAuxiliaryButtonsSEL);
    configureAuxiliaryButtonsIMP = (__typeof__(configureAuxiliaryButtonsIMP))method_getImplementation(configureAuxiliaryButtons);
    method_setImplementation(configureAuxiliaryButtons, (IMP)ConfigureAuxiliaryButtons);
}

And then calling it from the main.m function as

#import "AppDelegate.h"

extern void WorkaroundInlinePlayerFullScreenButtonBug(void);

int main(int argc, char *argv[])
{
    @autoreleasepool
    {
        WorkaroundInlinePlayerFullScreenButtonBug();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

The above solution worked fine, but in Swift, I am not sure how to achieve this functionality.


Solution

  • The solution to this problem has multiple stages. First, we need to add a main.m to our Swift so that the code in the original solution can be injected. In Swift projects the main file is abstracted. Refer to this post.

    So the first step is to remove @UIApplicationMain keyword from AppDelegate class.

    Then we add a main.m by adding an Objective-C.m file and naming it as main enter image description here.

    Add the following code as below in the main.m file

    #import <UIKit/UIKit.h>
    #import "MoviePlayerDemo-Swift.h"
    
    extern void WorkaroundInlinePlayerFullScreenButtonBug(void);
    
    int main(int argc, char *argv[])
    {
        @autoreleasepool
        {
            WorkaroundInlinePlayerFullScreenButtonBug();
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    The original icon changing code remains the same as in the original answer:

    @import MediaPlayer;
    @import ObjectiveC;
    
    static void (*configureAuxiliaryButtonsIMP)(id, SEL, BOOL);
    
    static void ConfigureAuxiliaryButtons(id self, SEL _cmd, BOOL flag)
    {
        configureAuxiliaryButtonsIMP(self, _cmd, flag);
        @try
        {
            id delegate = [self delegate]; // Either nil or MPInlineVideoController (responds to `isFullscreen`) or MPInlineVideoFullscreenViewController (does not respond to `isFullscreen`)
            BOOL isFullscreen = [delegate respondsToSelector:@selector(isFullscreen)] ? [delegate isFullscreen] : YES;
            NSString *imageName = [@[ @"Video", @"Player", @"_", isFullscreen ? @"Exit" : @"Enter", @"Fullscreen" ] componentsJoinedByString:@""];
            SEL imageNamedForControlState = NSSelectorFromString([@[ @"_", @"image", @"Named", @":", @"for", @"Control", @"State", @":" ] componentsJoinedByString:@""]);
            UIImage *normalImage = ((UIImage *(*)(id, SEL, NSString *, UIControlState))objc_msgSend)(self, imageNamedForControlState, imageName, UIControlStateNormal);
            UIImage *highlightedImage = ((UIImage *(*)(id, SEL, NSString *, UIControlState))objc_msgSend)(self, imageNamedForControlState, imageName, UIControlStateHighlighted);
            UIButton *fullscreenButton = [self valueForKey:[@[ @"fullscreen", @"Button" ] componentsJoinedByString:@""]];
            [fullscreenButton setImage:normalImage forState:UIControlStateNormal];
            [fullscreenButton setImage:highlightedImage forState:UIControlStateHighlighted];
        }
        @catch (NSException *exception)
        {
            NSLog(@"Failed to workaround inline player fullscreen button bug: %@", exception);
        }
    }
    
    void WorkaroundInlinePlayerFullScreenButtonBug(void)
    {
        if (![NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 0, 0}])
            return;
    
        Class MPVideoPlaybackOverlayView = NSClassFromString([@[ @"M", @"P", @"Video", @"Playback", @"Overlay", @"View" ] componentsJoinedByString:@""]);
        SEL configureAuxiliaryButtonsSEL = NSSelectorFromString([@[ @"_", @"configure", @"Auxiliary", @"Buttons", @":" ] componentsJoinedByString:@""]);
        NSMethodSignature *methodSignature = [MPVideoPlaybackOverlayView instanceMethodSignatureForSelector:configureAuxiliaryButtonsSEL];
        if (methodSignature.numberOfArguments != 3)
        {
            NSLog(@"Failed to workaround inline player fullscreen button bug (method not found)");
            return;
        }
    
        const char *returnType = methodSignature.methodReturnType;
        const char *argType = [methodSignature getArgumentTypeAtIndex:2];
        if (strcmp(returnType, @encode(void)) != 0 || strcmp(argType, @encode(BOOL)) != 0)
        {
            NSLog(@"Failed to workaround inline player fullscreen button bug (type mismatch)");
            return;
        }
    
        Method configureAuxiliaryButtons = class_getInstanceMethod(MPVideoPlaybackOverlayView, configureAuxiliaryButtonsSEL);
        configureAuxiliaryButtonsIMP = (__typeof__(configureAuxiliaryButtonsIMP))method_getImplementation(configureAuxiliaryButtons);
        method_setImplementation(configureAuxiliaryButtons, (IMP)ConfigureAuxiliaryButtons);
    }