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"];