IOS m3u8格式视频流截图

m3u8格式视频流截图。方法如下:

-(UIImage *)screenshotsm3u8WithCurrentTime:(CMTime)currentTime playerItemVideoOutput:(AVPlayerItemVideoOutput *)output{
    
    CVPixelBufferRef pixelBuffer = [output copyPixelBufferForItemTime:currentTime itemTimeForDisplay:nil];
    CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
    CIContext *temporaryContext = [CIContext contextWithOptions:nil];
    CGImageRef videoImage = [temporaryContext createCGImage:ciImage
                                                   fromRect:CGRectMake(0, 0,
                                                 CVPixelBufferGetWidth(pixelBuffer),
                                                 CVPixelBufferGetHeight(pixelBuffer))];
    UIImage *frameImg = [UIImage imageWithCGImage:videoImage];
    CGImageRelease(videoImage);
    //不释放会造成内存泄漏
    CVBufferRelease(pixelBuffer);
    return frameImg;
}

需要特别指出来的那两个release方法,不调用的话会造成内存泄漏(可用Instruments调试)。
另外还有一点需要注意,m3u8格式的视频流在截取时需要传入一个AVPlayerItemVideoOutput对象,这个对象一定要在AVPlayerItem初始化的时候设置好,如果以临时变量的形式传入或导致截取失败。
在截屏事件里直接调用以上方法即可获取到截取的image。demo链接地址:https://github.com/zhanqin/screenshots

 

播放器封装如下:

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface MMVideoPlayer : UIView

@property(nonatomic,strong) AVPlayer * videPlayer;
@property(nonatomic,strong) AVPlayerItem * videoPlayerItem;
@property(nonatomic,strong) AVPlayerItemVideoOutput * playerOutput;
@property(nonatomic,strong) NSString * videoUrl;
@property(nonatomic,assign) BOOL isPlayerPlaying;

-(void)playVideo;
-(void)pauseVideo;

@end

具体实现如下:

//
//  MMVideoPlayer.m
//  VideoScreenshots
//
//  Created by zhanqin on 2018/1/4.
//  Copyright © 2018年 zhanqin. All rights reserved.
//

#import "MMVideoPlayer.h"

@interface MMVideoPlayer ()

@property(nonatomic,strong) AVPlayerLayer * videoPlayerLayer;
@property(nonatomic,strong) UIActivityIndicatorView * activityIndicatorView;

@end

@implementation MMVideoPlayer

-(instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor blackColor];
        [self setupView];
    }
    return self;
}

-(void)dealloc{
    [self.videoPlayerItem removeObserver:self forKeyPath:@"status"];
    [self.videoPlayerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
    NSLog(@"MMVideoPlayer dealloc");
}

-(void)layoutSubviews{
    [super layoutSubviews];
    self.videoPlayerLayer.frame = self.bounds;
}

-(void)setupView{
    self.videPlayer = [[AVPlayer alloc] init];
    self.videoPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.videPlayer];
    [self.layer addSublayer:self.videoPlayerLayer];
}

-(void)setVideoUrl:(NSString *)videoUrl{
    _videoUrl = videoUrl;
    self.videoPlayerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:videoUrl]];
    self.playerOutput = [[AVPlayerItemVideoOutput alloc] init];
    [self.videoPlayerItem addOutput:self.playerOutput];
    [self setupObserver];
    [self addSubview:self.activityIndicatorView];
    [self.activityIndicatorView startAnimating];
}

-(void)setupObserver{
    [self.videoPlayerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    [self.videoPlayerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    AVPlayerItem *playerItem=object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
        if(status==AVPlayerStatusReadyToPlay){
            [self.activityIndicatorView stopAnimating];
            NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(playerItem.duration));
        }else if (status == AVPlayerStatusFailed){
            NSLog(@"播放失败");
        }else if (status == AVPlayerStatusUnknown){
            NSLog(@"未知错误");
        }
    }else if([keyPath isEqualToString:@"loadedTimeRanges"]){
        NSArray *array=playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
        NSLog(@"共缓冲:%.2f",totalBuffer);
    }
}

#pragma mark - click
-(void)playVideo{
    self.isPlayerPlaying = YES;
    [self.videPlayer replaceCurrentItemWithPlayerItem:self.videoPlayerItem];
    [self.videPlayer play];
}

-(void)pauseVideo{
    self.isPlayerPlaying = NO;
    [self.videPlayer pause];
}

#pragma mark - lazy
-(UIActivityIndicatorView *)activityIndicatorView{
    if (!_activityIndicatorView) {
        _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
        _activityIndicatorView.frame = CGRectMake(0, 0, 50, 50);
        _activityIndicatorView.center = self.center;
    }
    return _activityIndicatorView;
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/

@end

具体调用方法:

#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
#define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)
#define PADDING_X (SCREEN_WIDTH - SCREEN_HEIGHT/3.0)/2.0

#import <UIKit/UIKit.h>
#import "MMScreenshotsManager.h"
#import "MMVideoPlayer.h"

@interface MMBaseViewController : UIViewController

@property(nonatomic,strong) UIImageView * screenshotsImageView;
@property(nonatomic,strong) UIButton * screenshotsButton;
@property(nonatomic,strong) MMVideoPlayer * playerView;
@property(nonatomic,strong) UIButton * operationButton;

-(void)screenshotsClick;
-(void)setupVideoButton;

@end

实现:



#import "MMBaseViewController.h"

@interface MMBaseViewController ()
@property(nonatomic,assign) CGFloat navigationBarHeight;

@end

@implementation MMBaseViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _navigationBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height + self.navigationController.navigationBar.frame.size.height;
    self.view.backgroundColor = [UIColor whiteColor];
    [self setupNavigationItem];
    [self setupScreenshotsButton];
    [self.view addSubview:self.screenshotsImageView];
}

#pragma mark - UI
-(void)setupNavigationItem{
    [self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
    self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:0.9];
    
    UIButton *backBtn=[UIButton buttonWithType:UIButtonTypeCustom];
    backBtn.frame = CGRectMake(0, 0, 50, 30);
    [backBtn setTitle:@"返回" forState:UIControlStateNormal];
    [backBtn.titleLabel setFont:[UIFont systemFontOfSize:17]];
    [backBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [backBtn setImage:[UIImage imageNamed:@"icon_nav_back_black"] forState:UIControlStateNormal];
    [backBtn setContentEdgeInsets:UIEdgeInsetsMake(0, -10, 0, 0)];
    [backBtn sizeThatFits:CGSizeMake(60, 30)];
    [backBtn addTarget:self action:@selector(navLeftButtonClick) forControlEvents:UIControlEventTouchUpInside];
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backBtn];
}

-(void)setupScreenshotsButton{
    [self.view addSubview:self.screenshotsButton];
}

-(void)setupVideoButton{
    self.screenshotsButton.frame = CGRectMake(PADDING_X - 40, self.screenshotsButton.frame.origin.y, self.screenshotsButton.frame.size.width, 50);
    [self.view addSubview:self.operationButton];
}

#pragma mark - click
- (void)navLeftButtonClick
{
    [self.navigationController popViewControllerAnimated:YES];
}

-(void)screenshotsClick{
    
}

-(void)operationClick{
    if (self.playerView.isPlayerPlaying) {
        //暂停
        [self.playerView pauseVideo];
        [self.operationButton setImage:[UIImage imageNamed:@"button_player_play"] forState:UIControlStateNormal];
    }else{
        //播放
        [self.playerView playVideo];
        [self.operationButton setImage:[UIImage imageNamed:@"button_player_pause"] forState:UIControlStateNormal];
    }
}

#pragma mark - lazy
-(UIImageView *)screenshotsImageView{
    if (!_screenshotsImageView) {
        _screenshotsImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, SCREEN_HEIGHT - SCREEN_HEIGHT/3.0 - _navigationBarHeight, SCREEN_WIDTH, SCREEN_HEIGHT/3.0)];
        _screenshotsImageView.contentMode = UIViewContentModeScaleAspectFit;
        _screenshotsImageView.clipsToBounds = YES;
    }
    return _screenshotsImageView;
}

-(UIButton *)screenshotsButton{
    if (!_screenshotsButton) {
        _screenshotsButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _screenshotsButton.frame = CGRectMake(PADDING_X, SCREEN_HEIGHT/3.0 + (SCREEN_HEIGHT/3.0 - 50 - _navigationBarHeight)/2.0, SCREEN_HEIGHT/3.0, 50);
        _screenshotsButton.backgroundColor = [UIColor orangeColor];
        _screenshotsButton.layer.cornerRadius = 6;
        _screenshotsButton.layer.masksToBounds = YES;
        [_screenshotsButton setTitle:@"点我截屏" forState:UIControlStateNormal];
        [_screenshotsButton addTarget:self action:@selector(screenshotsClick) forControlEvents:UIControlEventTouchUpInside];
    }
    return _screenshotsButton;
}

-(MMVideoPlayer *)playerView{
    if (!_playerView) {
        _playerView = [[MMVideoPlayer alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT/3.0)];
    }
    return _playerView;
}

-(UIButton *)operationButton{
    if (!_operationButton) {
        _operationButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _operationButton.frame = CGRectMake(_screenshotsButton.frame.origin.x + _screenshotsButton.frame.size.width + 30, _screenshotsButton.frame.origin.y, 50, 50);
        [_operationButton addTarget:self action:@selector(operationClick) forControlEvents:UIControlEventTouchUpInside];
        [_operationButton setImage:[UIImage imageNamed:@"button_player_pause"] forState:UIControlStateNormal];
    }
    return _operationButton;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

M3U8播放和截屏如下:

#import "MMBaseViewController.h"

@interface MMVideom3u8CaptureViewController : MMBaseViewController

@end

实现:

//
//  MMVideom3u8CaptureViewController.m
//  VideoScreenshots
//
//  Created by zhanqin on 2018/1/4.
//  Copyright © 2018年 zhanqin. All rights reserved.
//

#import "MMVideom3u8CaptureViewController.h"
#import "MMPushViewController.h"

@interface MMVideom3u8CaptureViewController ()

@end

@implementation MMVideom3u8CaptureViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.title = @"m3u8截屏";
    [self.view addSubview:self.playerView];
    self.playerView.videoUrl = @"http://www.streambox.fr/playlists/test_001/stream.m3u8";
    [self.playerView playVideo];
    [self setupVideoButton];
}

-(void)dealloc{
    NSLog(@"MMVideom3u8CaptureViewController dealloc");
}

-(void)screenshotsClick{
    self.screenshotsImageView.image = [[[MMScreenshotsManager alloc] init] screenshotsm3u8WithCurrentTime:self.playerView.videPlayer.currentTime playerItemVideoOutput:self.playerView.playerOutput];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    MMPushViewController * vc = [[MMPushViewController alloc] init];
    [self.navigationController pushViewController:vc animated:YES];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

本文转载自阿里工程师博客,版权属于对方。我只是读后分享。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值