searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

iOS 画中画(PiP)实现以及注意事项

2024-06-17 07:05:55
293
0

1. 配置项

1)配置AudioSession:

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];

[[AVAudioSession sharedInstance] setActive:YES error:nil];

2)配置后台模式

勾选“Audio, Airplay, Picture in Picture”选项

 

2. 初始化AVPictureInPictureController,有两张方式:

1)通过initWithPlayerLayer初始化:

AVPictureInPictureController *pipController = [[AVPictureInPictureController alloc]initWithPlayerLayer:layer]; 

2) 通过 initWithContentSource初始化:

AVPictureInPictureControllerContentSource *contentSource = [[AVPictureInPictureControllerContentSource alloc] initWithSampleBufferDisplayLayer:(AVSampleBufferDisplayLayer *)self.layer playbackDelegate:self];

AVPictureInPictureController *pipController = [[AVPictureInPictureController alloc] initWithContentSource:contentSource];

 

第二种方法是iOS15后支持

 

3.AVPictureInPictureController代理方法

//将要启动画中画

- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//已启动画中画

- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//启动画中画失败

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error

//将要停止画中画

- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//已停止画中画

- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler

4. AVPictureInPictureSampleBufferPlaybackDelegate方法

///  PiP 窗口大小改变

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController

         didTransitionToRenderSize:(CMVideoDimensions)newRenderSize

 

/// 点击 PiP 窗口中的播放/暂停

- (void)pictureInPictureController:(nonnull AVPictureInPictureController *)pictureInPictureController

                        setPlaying:(BOOL)playing

 

/// 点击 PiP 窗口中的快进后图

- (void)pictureInPictureController:(nonnull AVPictureInPictureController *)pictureInPictureController

                    skipByInterval:(CMTime)skipInterval completionHandler:(nonnull void (^)(void))completionHandler

 

/// 前视频是否处于暂停状态

/// 当点击播放/暂停按钮时,PiP 会调用该方法,决定 setPlaying: 的值,同时该方法返回值也决定了PiP窗口展示击播放/暂停 icon

- (BOOL)pictureInPictureControllerIsPlaybackPaused:(nonnull AVPictureInPictureController *)pictureInPictureController

 

/// 视频的可播放时间范围

- (CMTimeRange)pictureInPictureControllerTimeRangeForPlayback:(nonnull AVPictureInPictureController *)pictureInPictureController

 

5.  画中画的使用技巧和注意事项:

1)AVPictureInPictureController要在UIViewcontroller上下文中,否则有启动不成功的概率;

2)不能使用app生命周期的回调方法来主动启动画中画: [pipController startPictureInPicture],是启动不成功的;

3)要实现退到后台自动启动画中画,设置canStartPictureInPictureAutomaticallyFromInline为YES即可;

4)在app设置 关闭/开启 画中画的场景中,关闭时不能直接把AVPictureInPictureController销毁掉,再开启时,有几率出现画中画启动不成功,最好的办法是关闭时把资源销毁,开启时重新创建资源:

- (AVPictureInPictureControllerContentSource *)createContentSource API_AVAILABLE(ios(15.0))

{

    AVPictureInPictureControllerContentSource *contentSource = [[AVPictureInPictureControllerContentSource alloc] initWithSampleBufferDisplayLayer:(AVSampleBufferDisplayLayer *)self.layer playbackDelegate:self];

    return contentSource;

}

 

5)解码后的CVPixelBufferRef数据,转成CMSampleBufferRef给AVSampleBufferDisplayLayer使用

+ (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer withLayer:(AVSampleBufferDisplayLayer *)sampleLayer

{

    if (!pixelBuffer)

    {

        return;

    }

    

    CMSampleTimingInfo timing = {kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid};

    CMVideoFormatDescriptionRef videoInfo = NULL;

    OSStatus result = CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &videoInfo);

    

    CMSampleBufferRef sampleBuffer = NULL;

    result = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault,pixelBuffer, true, NULL, NULL, videoInfo, &timing, &sampleBuffer);

    CFRelease(pixelBuffer);

    CFRelease(videoInfo);

    

    CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);

    CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);

    CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);

    

    if (sampleLayer.status == AVQueuedSampleBufferRenderingStatusFailed)

    {

        [sampleLayer flush];

    }

    

    dispatch_async(dispatch_get_main_queue(), ^{

        [sampleLayer enqueueSampleBuffer:sampleBuffer];

        CFRelease(sampleBuffer);

    });

}

 

6)图片资源转CVPixelBufferRef

+ (CVPixelBufferRef)CVPixelBufferRefFromUiImage:(UIImage *)img

{

    if (img == nil)

    {

        return NULL;

    }

//    CGSize size = img.size;

    CGImageRef image = [img CGImage];

    GLuint width = (GLuint)CGImageGetWidth(image);

    GLuint height = (GLuint)CGImageGetHeight(image);

    BOOL hasAlpha = CGImageRefContainsAlpha(image);

    CFDictionaryRef empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:

                             [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,

                             [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,

                             empty, kCVPixelBufferIOSurfacePropertiesKey,

                             nil];

    CVPixelBufferRef pxbuffer = NULL;

    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, inputPixelFormat(), (__bridge CFDictionaryRef) options, &pxbuffer);

    

    

    if (status == kCVReturnSuccess && pxbuffer != NULL)

    {

        CVPixelBufferLockBaseAddress(pxbuffer, 0);

        void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);

 

        if (pxdata != NULL)

        {

            CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();

            

            uint32_t bitmapInfo = bitmapInfoWithPixelFormatType(inputPixelFormat(), (bool)hasAlpha);

            

            CGContextRef context = CGBitmapContextCreate(pxdata, width, height, 8, CVPixelBufferGetBytesPerRow(pxbuffer), rgbColorSpace, bitmapInfo);

            if (context)

            {

                CGContextDrawImage(context, CGRectMake(0, 0,width, height), image);

                CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

                

                CGColorSpaceRelease(rgbColorSpace);

                CGContextRelease(context);

                

                return pxbuffer;

            }

        }

    }

    return NULL;

}

7)画中画窗口,隐藏播放、前进后退按钮

[pipController setValue:[NSNumber numberWithInt:1] forKey:@"controlsStyle"];

 

 

0条评论
0 / 1000
张****锋
1文章数
0粉丝数
张****锋
1 文章 | 0 粉丝
张****锋
1文章数
0粉丝数
张****锋
1 文章 | 0 粉丝
原创

iOS 画中画(PiP)实现以及注意事项

2024-06-17 07:05:55
293
0

1. 配置项

1)配置AudioSession:

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];

[[AVAudioSession sharedInstance] setActive:YES error:nil];

2)配置后台模式

勾选“Audio, Airplay, Picture in Picture”选项

 

2. 初始化AVPictureInPictureController,有两张方式:

1)通过initWithPlayerLayer初始化:

AVPictureInPictureController *pipController = [[AVPictureInPictureController alloc]initWithPlayerLayer:layer]; 

2) 通过 initWithContentSource初始化:

AVPictureInPictureControllerContentSource *contentSource = [[AVPictureInPictureControllerContentSource alloc] initWithSampleBufferDisplayLayer:(AVSampleBufferDisplayLayer *)self.layer playbackDelegate:self];

AVPictureInPictureController *pipController = [[AVPictureInPictureController alloc] initWithContentSource:contentSource];

 

第二种方法是iOS15后支持

 

3.AVPictureInPictureController代理方法

//将要启动画中画

- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//已启动画中画

- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//启动画中画失败

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error

//将要停止画中画

- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//已停止画中画

- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController

//

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler

4. AVPictureInPictureSampleBufferPlaybackDelegate方法

///  PiP 窗口大小改变

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController

         didTransitionToRenderSize:(CMVideoDimensions)newRenderSize

 

/// 点击 PiP 窗口中的播放/暂停

- (void)pictureInPictureController:(nonnull AVPictureInPictureController *)pictureInPictureController

                        setPlaying:(BOOL)playing

 

/// 点击 PiP 窗口中的快进后图

- (void)pictureInPictureController:(nonnull AVPictureInPictureController *)pictureInPictureController

                    skipByInterval:(CMTime)skipInterval completionHandler:(nonnull void (^)(void))completionHandler

 

/// 前视频是否处于暂停状态

/// 当点击播放/暂停按钮时,PiP 会调用该方法,决定 setPlaying: 的值,同时该方法返回值也决定了PiP窗口展示击播放/暂停 icon

- (BOOL)pictureInPictureControllerIsPlaybackPaused:(nonnull AVPictureInPictureController *)pictureInPictureController

 

/// 视频的可播放时间范围

- (CMTimeRange)pictureInPictureControllerTimeRangeForPlayback:(nonnull AVPictureInPictureController *)pictureInPictureController

 

5.  画中画的使用技巧和注意事项:

1)AVPictureInPictureController要在UIViewcontroller上下文中,否则有启动不成功的概率;

2)不能使用app生命周期的回调方法来主动启动画中画: [pipController startPictureInPicture],是启动不成功的;

3)要实现退到后台自动启动画中画,设置canStartPictureInPictureAutomaticallyFromInline为YES即可;

4)在app设置 关闭/开启 画中画的场景中,关闭时不能直接把AVPictureInPictureController销毁掉,再开启时,有几率出现画中画启动不成功,最好的办法是关闭时把资源销毁,开启时重新创建资源:

- (AVPictureInPictureControllerContentSource *)createContentSource API_AVAILABLE(ios(15.0))

{

    AVPictureInPictureControllerContentSource *contentSource = [[AVPictureInPictureControllerContentSource alloc] initWithSampleBufferDisplayLayer:(AVSampleBufferDisplayLayer *)self.layer playbackDelegate:self];

    return contentSource;

}

 

5)解码后的CVPixelBufferRef数据,转成CMSampleBufferRef给AVSampleBufferDisplayLayer使用

+ (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer withLayer:(AVSampleBufferDisplayLayer *)sampleLayer

{

    if (!pixelBuffer)

    {

        return;

    }

    

    CMSampleTimingInfo timing = {kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid};

    CMVideoFormatDescriptionRef videoInfo = NULL;

    OSStatus result = CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &videoInfo);

    

    CMSampleBufferRef sampleBuffer = NULL;

    result = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault,pixelBuffer, true, NULL, NULL, videoInfo, &timing, &sampleBuffer);

    CFRelease(pixelBuffer);

    CFRelease(videoInfo);

    

    CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);

    CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);

    CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);

    

    if (sampleLayer.status == AVQueuedSampleBufferRenderingStatusFailed)

    {

        [sampleLayer flush];

    }

    

    dispatch_async(dispatch_get_main_queue(), ^{

        [sampleLayer enqueueSampleBuffer:sampleBuffer];

        CFRelease(sampleBuffer);

    });

}

 

6)图片资源转CVPixelBufferRef

+ (CVPixelBufferRef)CVPixelBufferRefFromUiImage:(UIImage *)img

{

    if (img == nil)

    {

        return NULL;

    }

//    CGSize size = img.size;

    CGImageRef image = [img CGImage];

    GLuint width = (GLuint)CGImageGetWidth(image);

    GLuint height = (GLuint)CGImageGetHeight(image);

    BOOL hasAlpha = CGImageRefContainsAlpha(image);

    CFDictionaryRef empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:

                             [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,

                             [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,

                             empty, kCVPixelBufferIOSurfacePropertiesKey,

                             nil];

    CVPixelBufferRef pxbuffer = NULL;

    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, inputPixelFormat(), (__bridge CFDictionaryRef) options, &pxbuffer);

    

    

    if (status == kCVReturnSuccess && pxbuffer != NULL)

    {

        CVPixelBufferLockBaseAddress(pxbuffer, 0);

        void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);

 

        if (pxdata != NULL)

        {

            CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();

            

            uint32_t bitmapInfo = bitmapInfoWithPixelFormatType(inputPixelFormat(), (bool)hasAlpha);

            

            CGContextRef context = CGBitmapContextCreate(pxdata, width, height, 8, CVPixelBufferGetBytesPerRow(pxbuffer), rgbColorSpace, bitmapInfo);

            if (context)

            {

                CGContextDrawImage(context, CGRectMake(0, 0,width, height), image);

                CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

                

                CGColorSpaceRelease(rgbColorSpace);

                CGContextRelease(context);

                

                return pxbuffer;

            }

        }

    }

    return NULL;

}

7)画中画窗口,隐藏播放、前进后退按钮

[pipController setValue:[NSNumber numberWithInt:1] forKey:@"controlsStyle"];

 

 

文章来自个人专栏
iOSer开发
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0