探索iOS自定义ijkplayer画中画播放

这篇具有很好参考价值的文章主要介绍了探索iOS自定义ijkplayer画中画播放。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

iOS提供AVPictureInPictureController用于画中画播放控制,但是只能绑定AVPlayer使用,对于开发者来说挺头痛的。在iOS 15.0后呼之欲出,支持SampleBufferDisplayLayer自定义数据源显示图层,意味着我们可以自定义第三方播放器实现画中画。以ijkplayer为例,让我们一起探索如何支持画中画播放。

目录

一、判断画中画支持

二、后台播放配置

1、配置AudioSession

2、配置后台模式

三、画中画生命周期

1、从全屏进入画中画

2、退出画中画回到全屏

3、生命周期

 四、画中画创建流程

1、初始化

2、提供start和stop方法

3、实现画中画代理方法

4、更新缓冲区图层

5、更新播放状态

6、避坑指南


一、判断画中画支持

我们可以通过isPictureInPictureSupported() 判断当前设备是否支持画中画。

二、后台播放配置

1、配置AudioSession

在application创建时,配置AudioSession为playback模式,代码如下:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    let audioSession = AVAudioSession.sharedInstance()
     do {
        try audioSession.setCategory(.playback)
     } catch {
         print("Setting category to AVAudioSessionCategoryPlayback failed.")
     }

    return true
}

2、配置后台模式

勾选“Audio, Airplay, Picture in Picture”选项,如下图所示:

ios开发 画中画,iOS画中画播放,ijkplayer画中画播放

三、画中画生命周期

1、从全屏进入画中画

启动画中画时,首先执行willStartPictureInPicture()方法,此时动画还未开始。等到初始化完毕,执行didStartPictureInPicture()方法,完成从全屏到画中画的切换,进入后台状态。

ios开发 画中画,iOS画中画播放,ijkplayer画中画播放

2、退出画中画回到全屏

退出画中画时,从后台状态恢复app的UI,执行willStopPictureInPicture()方法。最终完成退出,执行didStopPictureInPicture()方法,回到全屏播放。

ios开发 画中画,iOS画中画播放,ijkplayer画中画播放

3、生命周期

画中画完整生命周期包括:从全屏到画中画,最终退出画中画回到全屏。即上面两个生命周期的结合,如下图所示:

ios开发 画中画,iOS画中画播放,ijkplayer画中画播放

 四、画中画创建流程

1、初始化

初始化AVSampleBufferDisplayLayer,包括设置frame、opaque、position、videoGravity、controlTimebase。示例代码如下:

- (AVSampleBufferDisplayLayer *) createDisplayLayer
{
    AVSampleBufferDisplayLayer *layer;
    layer = [[AVSampleBufferDisplayLayer alloc] init];
    layer.frame = self.bounds;
    layer.opaque = YES;
    layer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
    layer.videoGravity = AVLayerVideoGravityResizeAspect;
    
    CMTimebaseRef controlTimebase;
    CMTimebaseCreateWithSourceClock( CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &controlTimebase);
    layer.controlTimebase = controlTimebase;
    
    return layer;
}

使用AVSampleBufferDisplayLayer来初始化ContentSource,前面先判断设备是否支持画中画。示例代码如下:

- (BOOL)initPictureInPicture:(AVSampleBufferDisplayLayer *)displayLayer {
    if ([AVPictureInPictureController isPictureInPictureSupported] == NO) {
        NSLog(@"Sorry, don't support PictureInPicture mode!");
        return NO;
    }
    
    if (@available(iOS 15.0, *)) {
        _displayLayer  = displayLayer;
        _contentSource = [[AVPictureInPictureControllerContentSource alloc]
                            initWithSampleBufferDisplayLayer:displayLayer playbackDelegate:self];
        
        return YES;
    } else {
        return NO;
    }
}

2、提供start和stop方法

开始画中画,包括初始化AVPictureInPictureController、设置delegate代理、设置播放速率、启动。。示例代码如下:

- (void)startPictureInPicture {
    if (@available(iOS 15.0, *)) {
        if (_pipController != nil) {
            return;
        }
        _pipController = [[AVPictureInPictureController alloc] initWithContentSource:_contentSource];
        _pipController.delegate = self;
        CMTimebaseSetRate(_displayLayer.controlTimebase, 1);
        CMTimebaseSetTime(_displayLayer.controlTimebase, CMTimeMake([_delegate getCurrentTime], 1));
        // 延时启动,否则可能会启动失败
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(100 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
            if ([self->_pipController isPictureInPicturePossible]) {
                [self->_pipController startPictureInPicture];
                NSLog(@"startPictureInPicture...");
            }
        });
    }
}

结束画中画, 示例代码如下:

- (void)stopPictureInPicture {
    if (_pipController != nil && [_pipController isPictureInPictureActive]) {
        [_pipController stopPictureInPicture];
    }
}

3、实现画中画代理方法

实现AVPictureInPictureControllerDelegate代理方法如下:

- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"pictureInPictureControllerWillStart...");
}

- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    // 回调画中画开始
    [_delegate onStartPictureInPicture:nil];
}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
                                    failedToStartPictureInPictureWithError:(NSError *)error {
    // 画中画开始失败
    [_delegate onStartPictureInPicture:error];
}

- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"pictureInPictureControllerWillStop...");
}

- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    [self stopPictureInPicture];
    // 回调画中画结束
    [_delegate onStopPictureInPicture];
}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
    restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler {
    completionHandler(true);
    // 恢复全屏界面
}

4、更新缓冲区图层

在ijk解码出来后,封装成CVPixelBuffer,然后封装CMSampleBufferRef,存入displayLayer。示例代码如下:

-(void) enqueueBuffer:(CVPixelBufferRef)pixelBuffer
{
    if (!_displayLayer || !pixelBuffer)
        return;
    @autoreleasepool {
        CMSampleBufferRef sampleBuffer = nil;
        CMVideoFormatDescriptionRef format = nil;
        // 设置dts、pts
        CMSampleTimingInfo timeInfo = {.presentationTimeStamp = kCMTimeInvalid, .decodeTimeStamp = kCMTimeInvalid};
        OSStatus status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &format);
        status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, true, NULL, NULL,
                                                    format, &timeInfo, &sampleBuffer);
        if (format != nil) {
            CFRelease(format);
        }
        if (sampleBuffer == nil || status != noErr) {
            NSLog(@"sampleBuffer error!");
            return;
        }
        CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
        CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
        // 设置立即渲染
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
        
        [_displayLayer enqueueSampleBuffer:sampleBuffer];
        CFRelease(sampleBuffer);
        if (_displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
            NSLog(@"displayLayer error=%@", [_displayLayer error]);
        }
    }
}

5、更新播放状态

实现AVPictureInPictureSampleBufferPlaybackDelegate代理方法,更新播放状态、seek进度。示例代码如下:

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController setPlaying:(BOOL)playing {
    _isPlaybackPaused = !playing;
    // 回调播放/暂停
    [_delegate setPlaying:playing];
    if (@available(iOS 15.0, *)) {
        [pictureInPictureController invalidatePlaybackState];
    }
    if (playing == NO) {
        _playClickTime = [[NSDate date] timeIntervalSince1970] * 1000;
    }
}

- (CMTimeRange)pictureInPictureControllerTimeRangeForPlayback:(AVPictureInPictureController *)pictureInPictureController {
    // 计算time range:0~duration
    NSTimeInterval durationTime = [_delegate getDurationTime];
    return CMTimeRangeMake(kCMTimeZero, CMTimeMake(durationTime * 1000, 1000));
}

- (BOOL)pictureInPictureControllerIsPlaybackPaused:(AVPictureInPictureController *)pictureInPictureController {
    return _isPlaybackPaused;
}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
         didTransitionToRenderSize:(CMVideoDimensions)newRenderSize {

}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController skipByInterval:(CMTime)skipInterval
                 completionHandler:(void (^)(void))completionHandler {
    // 回调seek
    [_delegate seekTo:CMTimeGetSeconds(skipInterval)];
    if (@available(iOS 15.0, *)) {
        // seek开始会暂停播放,seek成功后恢复播放状态
        if (([[NSDate date] timeIntervalSince1970] * 1000 - _playClickTime) < 200) {
            [_delegate setPlaying:YES];
        }
        // 更新displaylayer的controlTimebase时钟,从而更新进度
        CMTimebaseSetTime(_displayLayer.controlTimebase, CMTimeMake([_delegate getCurrentTime] * 1000, 1000));
        // 更新播放状态
        [_pipController invalidatePlaybackState];
    }
}

ijkplayer提供play、seek、getCurrentPosition、getDuration、isPlaying等接口,给自定义的画中画Controller调用:

- (BOOL)isPlaybackPaused
{
    return [self isPlaying] == NO;
}

- (void)setPlaying:(BOOL)playing
{
    if (playing) {
        [self play];
    } else {
        [self pause];
    }
}

- (void)seekTo:(NSTimeInterval)relativePosition
{
    NSTimeInterval position = [self currentPlaybackTime] + relativePosition;
    [self setCurrentPlaybackTime:position];
}

- (NSTimeInterval)getCurrentTime
{
    return [self currentPlaybackTime];
}

- (NSTimeInterval)getDurationTime
{
    return [self duration];
}

6、避坑指南

(1) 延时启动:大概要延时100ms左右启动AVPictureInPictureController,否则可能启动画中画失败,有点坑爹

(2) 设置时钟:需要设置dts、pts,设置立即渲染。坑爹+1

(3) 更新播放状态:seek开始时会暂停播放,等到seek成功要恢复播放状态,坑爹+2文章来源地址https://www.toymoban.com/news/detail-520391.html

到了这里,关于探索iOS自定义ijkplayer画中画播放的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • iOS接入IJKPlayer遇到的问题汇总

    这里有一个我自己编译的IJKMediaFramework,能解决目前Github上反馈很多常见的IJKPlayer使用问题(包含播放异常,UI主线程Crash等),替换自己项目中的IJKMediaFramework即可 链接: https://pan.baidu.com/s/1UO-YfN_1YIDOX81bgW8bag?pwd=vq4u 提取码: vq4u 复制这段内容后打开百度网盘手机App,操作更方便哦

    2024年02月10日
    浏览(31)
  • vue3.0 安卓和ios h5 移动端音频自定义圆环可拖拽播放(兼容微信浏览器)

    安装  npm install weixin-js-sdk 引入 template     div class=\\\"circle_box\\\"         div id=\\\"content\\\"/div          img class=\\\"img_0\\\" src=\\\"https://img.yzcdn.cn/vant/cat.jpeg\\\" alt=\\\"\\\"          img @click=\\\"changeType\\\" class=\\\"img_1\\\" v-show=\\\"playbool\\\" src=\\\"@/assets/decompression/pressure_audio_play.png\\\" alt=\\\"\\\"          img @click=\\\"changeType\\\"

    2023年04月23日
    浏览(93)
  • iOS播放/渲染/解析MIDI

    MIDI:乐器数字接口, Musical Instrument Digital Interface。 MIDI 是计算机能理解的乐谱,计算机和电子乐器都可以处理的乐器格式。 MIDI 不是音频信号,不包含 pcm buffer。 通过音序器 sequencer,结合音频数据 / 乐器 ,播放 MIDI Event 数据 ( 通过音色库 SoundFont,播放乐器的声音。iOS上一

    2023年04月24日
    浏览(27)
  • iOS播放与编辑HDR视频

    在iPhone12发布后,支持使用Dolby Vision来录制HDR视频。至此,升级到iOS14.1系统后,已经支持录制、播放、编辑和导出HDR视频。接下来,让我们一起探索HDR视频的各种操作。 1、Profile与Level HDR视频中,Dolby Vision的Profile档次是8,同时兼容HLG格式。关于Dolby Vision的完整档次如下表所

    2024年02月04日
    浏览(23)
  • iOS】AVPlayer 播放音视频

    iOS开发中不可避免地会遇到音视频播放方面的需求。 常用的音频播放器有 AVAudioPlayer、AVPlayer 等。不同的是,AVAudioPlayer 只支持本地音频的播放,而 AVPlayer 既支持本地音频播放,也支持网络音频播放。 常用的视频播放器有 MPMoviePlayerController、AVPlayer 等。不同的是,MPMoviePlay

    2024年02月14日
    浏览(36)
  • iOS 使用 FLAnimatedImageView 播放GIF,并控制播放一次

    有时候会播放本地gif 图片,并只播放一次, 我们这里使用FLAnimationImageView 实现 添加forcePause 属性 计时器方法 修改计时器方法 添加下面这句 设置结束回调

    2024年02月10日
    浏览(33)
  • 【iOS】对象的本质探索

    生成C++文件的一些命令: clang -rewrite-objc main.m -o main.cpp:(无法区分平台 不建议使用) xcrun -sdk iphonesimulator clang -rewrite-objc main.m -o main.cpp:(模拟器) xcrun -sdk iphoneos clang -rewrite-objc main.m -o main.cpp:(真机) xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件名 -o 输出的cpp文件名

    2024年02月16日
    浏览(26)
  • H5 在ios上自动播放视频

    前言 最近的H5项目中有个开场动画使用了序列帧,但是因为原视频长达15秒,导出的序列帧很大,在loading阶段,用户等待时间过长,所以有这样的方案,在IOS中使用视频来代替序列帧,在安卓中由于不能自动播放视频,所以保持序列帧。 实现 Video 模板: 重要的是怎么触发自

    2024年02月12日
    浏览(28)
  • 【iOS】探索ARC的实现

    ARC (Automatic Reference Counting)是Objective-C在iOS 5.0之后提供的一种自动内存管理机制。它帮助开发者管理应用程序的内存使用,减少了因为忘记释放内存导致的内存泄漏问题,以及过早释放内存引发的程序崩溃问题。ARC工作在编译期和运行期做了以下事情: 自动插入 Retain (引用计

    2024年02月16日
    浏览(28)
  • iOS视频播放器之ZFPlayer剖析

    本文主要针对ZFPlayer的功能实现来剖析,以及总结一下大家遇到的问题和解决方案 首先ZFPlayer现在拥有的功能: 支持横、竖屏切换,在全屏播放模式下还可以锁定屏幕方向 支持本地视频、网络视频播放 支持在TableviewCell播放视频 左侧1/2位置上下滑动调节屏幕亮度(模拟器调不

    2024年02月12日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包