iOS开荒类别,高仿天涯论坛云音乐播放器

新浪云音乐凭着不错的互动体验,优质丰裕的财富在终端一贯具备不错的商海。相比较市道上的主流音乐播放器(QQ音乐、虾米音乐),作者更赞成于云音乐的UED。

iOS开垦连串--音频播放、录音、录像播放、拍照、摄像摄像,ios音频播放

--iOS多媒体

图片 1demo.gif

概览

乘胜移动网络的升华,方今的无绳电话机早就不是通话、发短信那么轻便了,播放音乐、录制、录音、拍照等都以很常用的功效。在iOS中对于多媒体的辅助是老大强大的,无论是音录制播放、录像,依然对迈克风、摄像头的操作都提供了多套API。在今日的稿子中将会对这一个剧情实行逐项介绍:

  1. 音频
  2. 视频
  3. 摄像头
  4. 总结

单从广播器页来讲,云音乐的分界面极其简洁,只保留了重在的操作作用,防止过多的音信形成视觉上的疲态。主视图上不断转动的黑胶唱片十二分有带入感,左滑右滑切换唱片的安顿性也极度精彩。

音频

在iOS中音频播放从花样上能够分为音响效果播放和音乐播放。前面二个首要指的是一对短音频播放,平时作为点缀音频,对于那类音频无需展开速度、循环等决定。后面一个指的是有个别较长的点子,经常是主音频,对于那一个点子的播放常常需求展开规范的调节。在iOS中播放两类节奏分别采纳奥迪(Audi)oToolbox.framework和AVFoundation.framework来成功音响效果和音乐广播。

正文首要介绍了播放器落成的一对着力代码和思路,完整代码请通过文末链接自行clone

音效

奥迪oToolbox.framework是一套基于C语言的框架,使用它来播音音响效果其本质是将短音频注册到系统声音服务(System Sound Service)。System Sound Service是一种轻便、底层的动静播放服务,可是它自个儿也设有着有个别范围:

  • 旋律播放时间不能够超越30s
  • 数码必得是PCM可能IMSpirior格式
  • 音频文件务必打包成.caf、.aif、.wav中的一种(注意这是合法语档的传教,实际测验发现有的.mp5也得以播放)

行使System Sound Service 播放音响效果的步子如下:

下边是一个差不离的亲自去做程序:

//
//  KCMainViewController.m
//  Audio
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  音效播放

#import "KCMainViewController.h"
#import <AudioToolbox/AudioToolbox.h>

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self playSoundEffect:@"videoRing.caf"];
}

/**
 *  播放完成回调函数
 *
 *  @param soundID    系统声音ID
 *  @param clientData 回调时传递的数据
 */
void soundCompleteCallback(SystemSoundID soundID,void * clientData){
    NSLog(@"播放完成...");
}

/**
 *  播放音效文件
 *
 *  @param name 音频文件名称
 */
-(void)playSoundEffect:(NSString *)name{
    NSString *audioFile=[[NSBundle mainBundle] pathForResource:name ofType:nil];
    NSURL *fileUrl=[NSURL fileURLWithPath:audioFile];
    //1.获得系统声音ID
    SystemSoundID soundID=0;
    /**
     * inFileUrl:音频文件url
     * outSystemSoundID:声音id(此函数会将音效文件加入到系统音频服务中并返回一个长整形ID)
     */
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
    //如果需要在播放完之后执行某些操作,可以调用如下方法注册一个播放完成回调函数
    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
    //2.播放音频
    AudioServicesPlaySystemSound(soundID);//播放音效
//    AudioServicesPlayAlertSound(soundID);//播放音效并震动
}

@end

必备知识储备:

音乐

假定播放相当的大的节奏或许要对旋律有正确的支配则System Sound Service大概就很难满足实际供给了,日常这种境况会接纳接纳AVFoundation.framework中的AV奥迪(Audi)oPlayer来达成。AV奥迪oPlayer能够看成八个播放器,它支持三种音频格式,而且可以举行进度、音量、播放速度等调控。首先轻巧看一下AV奥迪(Audi)oPlayer常用的性质和章程:

属性 说明
@property(readonly, getter=isPlaying) BOOL playing 是否正在播放,只读
@property(readonly) NSUInteger numberOfChannels 音频声道数,只读
@property(readonly) NSTimeInterval duration 音频时长
@property(readonly) NSURL *url 音频文件路径,只读
@property(readonly) NSData *data 音频数据,只读
@property float pan 立体声平衡,如果为-1.0则完全左声道,如果0.0则左右声道平衡,如果为1.0则完全为右声道
@property float volume 音量大小,范围0-1.0
@property BOOL enableRate 是否允许改变播放速率
@property float rate 播放速率,范围0.5-2.0,如果为1.0则正常播放,如果要修改播放速率则必须设置enableRate为YES
@property NSTimeInterval currentTime 当前播放时长
@property(readonly) NSTimeInterval deviceCurrentTime 输出设备播放音频的时间,注意如果播放中被暂停此时间也会继续累加
@property NSInteger numberOfLoops 循环播放次数,如果为0则不循环,如果小于0则无限循环,大于0则表示循环次数
@property(readonly) NSDictionary *settings 音频播放设置信息,只读
@property(getter=isMeteringEnabled) BOOL meteringEnabled 是否启用音频测量,默认为NO,一旦启用音频测量可以通过updateMeters方法更新测量值
对象方法 说明
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError 使用文件URL初始化播放器,注意这个URL不能是HTTP URL,AVAudioPlayer不支持加载网络媒体流,只能播放本地文件
- (instancetype)initWithData:(NSData *)data error:(NSError **)outError 使用NSData初始化播放器,注意使用此方法时必须文件格式和文件后缀一致,否则出错,所以相比此方法更推荐使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString *)utiString error:(NSError **)outError方法进行初始化
- (BOOL)prepareToPlay; 加载音频文件到缓冲区,注意即使在播放之前音频文件没有加载到缓冲区程序也会隐式调用此方法。
- (BOOL)play; 播放音频文件
- (BOOL)playAtTime:(NSTimeInterval)time 在指定的时间开始播放音频
- (void)pause; 暂停播放
- (void)stop; 停止播放
- (void)updateMeters 更新音频测量值,注意如果要更新音频测量值必须设置meteringEnabled为YES,通过音频测量值可以即时获得音频分贝等信息
- (float)peakPowerForChannel:(NSUInteger)channelNumber; 获得指定声道的分贝峰值,注意如果要获得分贝峰值必须在此之前调用updateMeters方法
- (float)averagePowerForChannel:(NSUInteger)channelNumber 获得指定声道的分贝平均值,注意如果要获得分贝平均值必须在此之前调用updateMeters方法
@property(nonatomic, copy) NSArray *channelAssignments 获得或设置播放声道
代理方法 说明
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 音频播放完成
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error 音频解码发生错误

AV奥迪(Audi)oPlayer的利用比较轻松:

上边就选拔AV奥迪oPlayer完毕一个轻易易行播放器,在这么些播放器中实现了广播、暂停、突显播放进程成效,当然比如调度高低、设置循环格局、以致是声波图像(通过解析音频分贝值)等职能都能够兑现,这里就不再一一演示。分界面效果如下:

理当如此是因为AV奥迪(Audi)oPlayer叁遍只好播放四个音频文件,全数上一曲、下一曲其实能够经过创办多少个播放器对象来成功,这里暂不达成。播放进程的贯彻着重借助一个机械漏刻实时总括当前播发时长和拍子总时间长度的比例,别的为了演示委托方法,下边的代码中也兑现了播音完结委托方法,平时假如有下一曲成效的话播放完能够触发下一曲音乐播放。上边是至关心重视要代码:

//
//  ViewController.m
//  KCAVAudioPlayer
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define kMusicFile @"刘若英 - 原来你也在这里.mp3"
#define kMusicSinger @"刘若英"
#define kMusicTitle @"原来你也在这里"

@interface ViewController ()<AVAudioPlayerDelegate>

@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器
@property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板
@property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放进度
@property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暂停按钮(如果tag为0认为是暂停状态,1是播放状态)

@property (weak ,nonatomic) NSTimer *timer;//进度更新定时器

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];

}

/**
 *  初始化UI
 */
-(void)setupUI{
    self.title=kMusicTitle;
    self.musicSinger.text=kMusicSinger;
}

-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true];
    }
    return _timer;
}

/**
 *  创建播放器
 *
 *  @return 音频播放器
 */
-(AVAudioPlayer *)audioPlayer{
    if (!_audioPlayer) {
        NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil];
        NSURL *url=[NSURL fileURLWithPath:urlStr];
        NSError *error=nil;
        //初始化播放器,注意这里的Url参数只能时文件路径,不支持HTTP Url
        _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
        //设置播放器属性
        _audioPlayer.numberOfLoops=0;//设置为0不循环
        _audioPlayer.delegate=self;
        [_audioPlayer prepareToPlay];//加载音频文件到缓存
        if(error){
            NSLog(@"初始化播放器过程发生错误,错误信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _audioPlayer;
}

/**
 *  播放音频
 */
-(void)play{
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
        self.timer.fireDate=[NSDate distantPast];//恢复定时器
    }
}

/**
 *  暂停播放
 */
-(void)pause{
    if ([self.audioPlayer isPlaying]) {
        [self.audioPlayer pause];
        self.timer.fireDate=[NSDate distantFuture];//暂停定时器,注意不能调用invalidate方法,此方法会取消,之后无法恢复

    }
}

/**
 *  点击播放/暂停按钮
 *
 *  @param sender 播放/暂停按钮
 */
- (IBAction)playClick:(UIButton *)sender {
    if(sender.tag){
        sender.tag=0;
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted];
        [self pause];
    }else{
        sender.tag=1;
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted];
        [self play];
    }
}

/**
 *  更新播放进度
 */
-(void)updateProgress{
    float progress= self.audioPlayer.currentTime /self.audioPlayer.duration;
    [self.playProgress setProgress:progress animated:true];
}

#pragma mark - 播放器代理方法
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"音乐播放完成...");
}

@end

运营效果:

  • 基本功数据结构知识
  • AVFoundation/AVPlayer
  • AVAudioSession
  • CoreAnimation
  • Autolayout
  • KVO、Notification

音频会话

骨子里上边的播放器还留存有的标题,举个例子常常大家看出的播放器尽管脱离到后台也是足以播放的,而以此播放器如果退出到后台它会活动脚刹踏板。若是要帮助后台播放要求做下边几件事情:

1.设置后台运营格局:在plist文件中增多Required background modes,并且安装item 0=App plays audio or streams audio/video using AirPlay(其实能够直接通过Xcode在Project Targets-Capabilities-Background Modes中装置)

2.安装AV奥迪oSession的门类为AV奥迪(Audi)oSessionCategoryPlayback何况调用setActive::方法运维会话。

    AVAudioSession *audioSession=[AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
    [audioSession setActive:YES error:nil];

3.为了能够让使用退到后台之后帮忙耳麦调整,提出加多远程序调控制事件(这一步不是后台播放必得的)

前两步是后台播放所不可不设置的,第三步关键用来收纳远程事件,那有的内容前边的小说中有详细介绍,借使这一步不安装虽让也能够在后台播放,然而爱莫能助取得音频调节权(借使在应用当前应用从前使用别的播放器播放音乐来讲,此时假诺按动铁耳机播放键大概调控中央的播放按键则会播放前一个用到的节奏),並且无法采用动铁耳机举行音频调节。第一步操作相信我们都很轻便了然,借使应用程序要允许运转到后台必得设置,平常情状下使用假若进入后台会被挂起,通过该装置能够上应用程序继续在后台运营。但是第二步使用的AVAudioSession有须求进行一下详细的印证。

在iOS中每种应用都有一个音频会话,这些会话就经过AV奥迪(Audi)oSession来表示。AV奥迪oSession同样存在于AVFoundation框架中,它是单例方式设计,通过sharedInstance进行造访。在使用Apple设备时大家会开采有个别应用只要展开其余音频播放就能够停下,而有一点点应用却能够和别的应用还要播报,在多样旋律情状中什么去决定播放的不二秘籍就是经过音频会话来完结的。下边是音频会话的二种会话形式:

会话类型 说明 是否要求输入 是否要求输出 是否遵从静音键
AVAudioSessionCategoryAmbient 混音播放,可以与其他音频应用同时播放
AVAudioSessionCategorySoloAmbient 独占播放
AVAudioSessionCategoryPlayback 后台播放,也是独占的
AVAudioSessionCategoryRecord 录音模式,用于录音时使用
AVAudioSessionCategoryPlayAndRecord 播放和录音,此时可以录音也可以播放
AVAudioSessionCategoryAudioProcessing 硬件解码音频,此时不能播放和录制
AVAudioSessionCategoryMultiRoute 多种输入输出,例如可以耳机、USB设备同时播放

小心:是不是依据静音键表示在播报进程中纵然客户通过硬件装置为静音是或不是能关闭声音。

依据前面前遇到音频会话的领会,相信大家付出出可以在后台播放的节奏播放器并容易,可是注意一下,在日前的代码中也论及设置完音频会话类型之后必要调用setActive::方法将会话激活技能起效果。类似的,即使二个使用已经在播放音频,展开大家的采取之后设置了在后台播放的对话类型,此时别的使用的音频会甘休而播放我们的旋律,倘若指望我们的顺序音频播放完之后(关闭或退出到后台之后)能够持续播放其余应用的韵律的话则能够调用setActive::方法关闭对话。代码如下:

//
//  ViewController.m
//  KCAVAudioPlayer
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  AVAudioSession 音频会话

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define kMusicFile @"刘若英 - 原来你也在这里.mp3"
#define kMusicSinger @"刘若英"
#define kMusicTitle @"原来你也在这里"

@interface ViewController ()<AVAudioPlayerDelegate>

@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器
@property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板
@property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放进度
@property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暂停按钮(如果tag为0认为是暂停状态,1是播放状态)

@property (weak ,nonatomic) NSTimer *timer;//进度更新定时器

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];

}

/**
 *  显示当面视图控制器时注册远程事件
 *
 *  @param animated 是否以动画的形式显示
 */
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //开启远程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    //作为第一响应者
    //[self becomeFirstResponder];
}
/**
 *  当前控制器视图不显示时取消远程控制
 *
 *  @param animated 是否以动画的形式消失
 */
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    //[self resignFirstResponder];
}

/**
 *  初始化UI
 */
-(void)setupUI{
    self.title=kMusicTitle;
    self.musicSinger.text=kMusicSinger;
}

-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true];
    }
    return _timer;
}

/**
 *  创建播放器
 *
 *  @return 音频播放器
 */
-(AVAudioPlayer *)audioPlayer{
    if (!_audioPlayer) {
        NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil];
        NSURL *url=[NSURL fileURLWithPath:urlStr];
        NSError *error=nil;
        //初始化播放器,注意这里的Url参数只能时文件路径,不支持HTTP Url
        _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
        //设置播放器属性
        _audioPlayer.numberOfLoops=0;//设置为0不循环
        _audioPlayer.delegate=self;
        [_audioPlayer prepareToPlay];//加载音频文件到缓存
        if(error){
            NSLog(@"初始化播放器过程发生错误,错误信息:%@",error.localizedDescription);
            return nil;
        }
        //设置后台播放模式
        AVAudioSession *audioSession=[AVAudioSession sharedInstance];
        [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
//        [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];
        [audioSession setActive:YES error:nil];
        //添加通知,拔出耳机后暂停播放
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];
    }
    return _audioPlayer;
}

/**
 *  播放音频
 */
-(void)play{
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
        self.timer.fireDate=[NSDate distantPast];//恢复定时器
    }
}

/**
 *  暂停播放
 */
-(void)pause{
    if ([self.audioPlayer isPlaying]) {
        [self.audioPlayer pause];
        self.timer.fireDate=[NSDate distantFuture];//暂停定时器,注意不能调用invalidate方法,此方法会取消,之后无法恢复

    }
}

/**
 *  点击播放/暂停按钮
 *
 *  @param sender 播放/暂停按钮
 */
- (IBAction)playClick:(UIButton *)sender {
    if(sender.tag){
        sender.tag=0;
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted];
        [self pause];
    }else{
        sender.tag=1;
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted];
        [self play];
    }
}

/**
 *  更新播放进度
 */
-(void)updateProgress{
    float progress= self.audioPlayer.currentTime /self.audioPlayer.duration;
    [self.playProgress setProgress:progress animated:true];
}

/**
 *  一旦输出改变则执行此方法
 *
 *  @param notification 输出改变通知对象
 */
-(void)routeChange:(NSNotification *)notification{
    NSDictionary *dic=notification.userInfo;
    int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue];
    //等于AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示旧输出不可用
    if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
        AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey];
        AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject];
        //原设备为耳机则暂停
        if ([portDescription.portType isEqualToString:@"Headphones"]) {
            [self pause];
        }
    }

//    [dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
//        NSLog(@"%@:%@",key,obj);
//    }];
}

-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
}

#pragma mark - 播放器代理方法
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"音乐播放完成...");
    //根据实际情况播放完成可以将会话关闭,其他音频应用继续播放
    [[AVAudioSession sharedInstance]setActive:NO error:nil];
}

@end

在地点的代码中还达成了拔出动铁耳机暂停音乐播放的效果,那也是三个比较普及的成效。在iOS7及事后的本子中得以因此公告获得输出更动的布告,然后得到公告对象后依据userInfo得到是何种更动类型,进而依据气象对音乐进行暂停操作。

实现供给:

推而广之--播放音乐库中的音乐

综上说述音乐是iOS的重中之重构成播放,无论是iPod、iTouch、Motorola照旧surface都得以在iTunes购买音乐或加上本地音乐到音乐库中齐声到你的iOS设备。在MediaPlayer.frameowork中有贰个MPMusicPlayerController用于广播音乐库中的音乐。

下边先来看一下MPMusicPlayerController的常用属性和办法:

属性 说明
@property (nonatomic, readonly) MPMusicPlaybackState playbackState 播放器状态,枚举类型:
MPMusicPlaybackStateStopped:停止播放 MPMusicPlaybackStatePlaying:正在播放
MPMusicPlaybackStatePaused:暂停播放
MPMusicPlaybackStateInterrupted:播放中断
MPMusicPlaybackStateSeekingForward:向前查找
MPMusicPlaybackStateSeekingBackward:向后查找
@property (nonatomic) MPMusicRepeatMode repeatMode 重复模式,枚举类型:
MPMusicRepeatModeDefault:默认模式,使用用户的首选项(系统音乐程序设置)
MPMusicRepeatModeNone:不重复
MPMusicRepeatModeOne:单曲循环
MPMusicRepeatModeAll:在当前列表内循环
@property (nonatomic) MPMusicShuffleMode shuffleMode 随机播放模式,枚举类型:
MPMusicShuffleModeDefault:默认模式,使用用户首选项(系统音乐程序设置)
MPMusicShuffleModeOff:不随机播放
MPMusicShuffleModeSongs:按歌曲随机播放
MPMusicShuffleModeAlbums:按专辑随机播放
@property (nonatomic, copy) MPMediaItem *nowPlayingItem 正在播放的音乐项
@property (nonatomic, readonly) NSUInteger indexOfNowPlayingItem 当前正在播放的音乐在播放队列中的索引
@property(nonatomic, readonly) BOOL isPreparedToPlay 是否准好播放准备
@property(nonatomic) NSTimeInterval currentPlaybackTime 当前已播放时间,单位:秒
@property(nonatomic) float currentPlaybackRate 当前播放速度,是一个播放速度倍率,0表示暂停播放,1代表正常速度
类方法 说明
+ (MPMusicPlayerController *)applicationMusicPlayer; 获取应用播放器,注意此类播放器无法在后台播放
+ (MPMusicPlayerController *)systemMusicPlayer 获取系统播放器,支持后台播放
对象方法 说明
- (void)setQueueWithQuery:(MPMediaQuery *)query 使用媒体队列设置播放源媒体队列
- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection 使用媒体项集合设置播放源媒体队列
- (void)skipToNextItem 下一曲
- (void)skipToBeginning 从起始位置播放
- (void)skipToPreviousItem 上一曲
- (void)beginGeneratingPlaybackNotifications 开启播放通知,注意不同于其他播放器,MPMusicPlayerController要想获得通知必须首先开启,默认情况无法获得通知
- (void)endGeneratingPlaybackNotifications 关闭播放通知
- (void)prepareToPlay 做好播放准备(加载音频到缓冲区),在使用play方法播放时如果没有做好准备回自动调用该方法
- (void)play 开始播放
- (void)pause 暂停播放
- (void)stop 停止播放
- (void)beginSeekingForward 开始向前查找(快进)
- (void)beginSeekingBackward 开始向后查找(快退)
- (void)endSeeking 结束查找
通知 说明
(注意:要想获得MPMusicPlayerController通知必须首先调用beginGeneratingPlaybackNotifications开启通知)
MPMusicPlayerControllerPlaybackStateDidChangeNotification 播放状态改变
MPMusicPlayerControllerNowPlayingItemDidChangeNotification 当前播放音频改变
MPMusicPlayerControllerVolumeDidChangeNotification 声音大小改变
MPMediaPlaybackIsPreparedToPlayDidChangeNotification 准备好播放
  • MPMusicPlayerController有两种播放器:applicationMusicPlayer和systemMusicPlayer,前边三个在选拔退出后音乐广播会自动截至,后面一个在行使甘休后不会退出播放状态。
  • MPMusicPlayerController加载音乐差别于后边的AV奥迪oPlayer是经过一个文件路线来加载,而是需求叁个广播队列。在MPMusicPlayerController中提供了七个主意来加载播放队列:- (void)setQueueWithQuery:(MPMediaQuery *)query- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection,就是出于它的播报音频来源是一个行列,由此MPMusicPlayerController支持上一曲、下一曲等操作。

那么接下去的标题正是什么收获MPMediaQueue只怕MPMediaItemCollection?MPMediaQueue对象有一文山会海的类措施来收获媒体队列:

+ (MPMediaQuery )albumsQuery;

  • (MPMediaQuery *)artistsQuery;
  • (MPMediaQuery *)songsQuery;
  • (MPMediaQuery *)playlistsQuery;
  • (MPMediaQuery *)podcastsQuery;
  • (MPMediaQuery *)audiobooksQuery;
  • (MPMediaQuery *)compilationsQuery;
  • (MPMediaQuery *)composersQuery;
  • (MPMediaQuery *)genresQuery;*

有了那么些主意,就可以很轻易获到歌曲、播放列表、专辑媒体等传播媒介队列了,那样就能够通过:- (void)setQueueWithQuery:(MPMediaQuery *)query主意设置音乐来源了又或然获得MPMediaQueue现在创造MPMediaItemCollection,使用- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection设置音乐来源。

不时候大概希望客商自身来摘取要播放的音乐,那时能够使用MPMediaPickerController,它是贰个视图调控器,类似于UIImagePickerController,选用完播放来源后方可在其代理方法中收获MPMediaItemCollection对象。

任由通过哪一类方法得到MPMusicPlayerController的媒体源,大概都期望将各样媒体的新闻突显出来,这时候能够透过MPMediaItem对象得到。多少个MPMediaItem代表二个媒体文件,通过它能够访谈媒体标题、专辑名称、专辑封面、音乐时间长度等等。无论是MPMediaQueue仍旧MPMediaItemCollection都有贰个items属性,它是MPMediaItem数组,通过那特性子能够收获MPMediaItem对象。

上边就回顾看一下MPMusicPlayerController的行使,在底下的例证中回顾演示了音乐的选取、播放、暂停、通告、下一曲、上一曲功效,相信有了地点的概念,代码读起来并不复杂(示例中是直接通过MPMeidaPicker举行音乐选取的,可是照旧提供了五个艺术getLocalMediaQuery和getLocalMediaItemCollection来演示怎么样间接通过MPMediaQueue获得媒体队列或媒体聚焦):

//
//  ViewController.m
//  MPMusicPlayerController
//
//  Created by Kenshin Cui 14/03/30
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()<MPMediaPickerControllerDelegate>

@property (nonatomic,strong) MPMediaPickerController *mediaPicker;//媒体选择控制器
@property (nonatomic,strong) MPMusicPlayerController *musicPlayer; //音乐播放器

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

-(void)dealloc{
    [self.musicPlayer endGeneratingPlaybackNotifications];
}

/**
 *  获得音乐播放器
 *
 *  @return 音乐播放器
 */
-(MPMusicPlayerController *)musicPlayer{
    if (!_musicPlayer) {
        _musicPlayer=[MPMusicPlayerController systemMusicPlayer];
        [_musicPlayer beginGeneratingPlaybackNotifications];//开启通知,否则监控不到MPMusicPlayerController的通知
        [self addNotification];//添加通知
        //如果不使用MPMediaPickerController可以使用如下方法获得音乐库媒体队列
        //[_musicPlayer setQueueWithItemCollection:[self getLocalMediaItemCollection]];
    }
    return _musicPlayer;
}

/**
 *  创建媒体选择器
 *
 *  @return 媒体选择器
 */
-(MPMediaPickerController *)mediaPicker{
    if (!_mediaPicker) {
        //初始化媒体选择器,这里设置媒体类型为音乐,其实这里也可以选择视频、广播等
//        _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeMusic];
        _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeAny];
        _mediaPicker.allowsPickingMultipleItems=YES;//允许多选
//        _mediaPicker.showsCloudItems=YES;//显示icloud选项
        [email protected]"请选择要播放的音乐";
        _mediaPicker.delegate=self;//设置选择器代理
    }
    return _mediaPicker;
}

/**
 *  取得媒体队列
 *
 *  @return 媒体队列
 */
-(MPMediaQuery *)getLocalMediaQuery{
    MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery];
    for (MPMediaItem *item in mediaQueue.items) {
        NSLog(@"标题:%@,%@",item.title,item.albumTitle);
    }
    return mediaQueue;
}

/**
 *  取得媒体集合
 *
 *  @return 媒体集合
 */
-(MPMediaItemCollection *)getLocalMediaItemCollection{
    MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery];
    NSMutableArray *array=[NSMutableArray array];
    for (MPMediaItem *item in mediaQueue.items) {
        [array addObject:item];
        NSLog(@"标题:%@,%@",item.title,item.albumTitle);
    }
    MPMediaItemCollection *mediaItemCollection=[[MPMediaItemCollection alloc]initWithItems:[array copy]];
    return mediaItemCollection;
}

#pragma mark - MPMediaPickerController代理方法
//选择完成
-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection{
    MPMediaItem *mediaItem=[mediaItemCollection.items firstObject];//第一个播放音乐
    //注意很多音乐信息如标题、专辑、表演者、封面、时长等信息都可以通过MPMediaItem的valueForKey:方法得到,但是从iOS7开始都有对应的属性可以直接访问
//    NSString *title= [mediaItem valueForKey:MPMediaItemPropertyAlbumTitle];
//    NSString *artist= [mediaItem valueForKey:MPMediaItemPropertyAlbumArtist];
//    MPMediaItemArtwork *artwork= [mediaItem valueForKey:MPMediaItemPropertyArtwork];
    //UIImage *image=[artwork imageWithSize:CGSizeMake(100, 100)];//专辑图片
    NSLog(@"标题:%@,表演者:%@,专辑:%@",mediaItem.title ,mediaItem.artist,mediaItem.albumTitle);
    [self.musicPlayer setQueueWithItemCollection:mediaItemCollection];
    [self dismissViewControllerAnimated:YES completion:nil];
}
//取消选择
-(void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker{
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - 通知
/**
 *  添加通知
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(playbackStateChange:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.musicPlayer];
}

/**
 *  播放状态改变通知
 *
 *  @param notification 通知对象
 */
-(void)playbackStateChange:(NSNotification *)notification{
    switch (self.musicPlayer.playbackState) {
        case MPMusicPlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMusicPlaybackStatePaused:
            NSLog(@"播放暂停.");
            break;
        case MPMusicPlaybackStateStopped:
            NSLog(@"播放停止.");
            break;
        default:
            break;
    }
}

#pragma mark - UI事件
- (IBAction)selectClick:(UIButton *)sender {
    [self presentViewController:self.mediaPicker animated:YES completion:nil];
}

- (IBAction)playClick:(UIButton *)sender {
    [self.musicPlayer play];
}

- (IBAction)puaseClick:(UIButton *)sender {
    [self.musicPlayer pause];
}

- (IBAction)stopClick:(UIButton *)sender {
    [self.musicPlayer stop];
}

- (IBAction)nextClick:(UIButton *)sender {
    [self.musicPlayer skipToNextItem];
}

- (IBAction)prevClick:(UIButton *)sender {
    [self.musicPlayer skipToPreviousItem];
}

@end
  • 播放器播放、暂停,上一首、下一首
  • 播音格局切换,帮衬单曲循环、顺序播放、随机播放
  • 黑胶唱片仿真动画
  • 后台播放及抛锚调控
  • 支撑iOS10.0包罗及以上系统

录音

除去上面说的,在AVFoundation框架中还要叁个AV奥迪(Audi)oRecorder类特地管理录音操作,它同样支撑八种音频格式。与AV奥迪(Audi)oPlayer类似,你完全能够将它作为是一个录音机调节类,上面是常用的属性和艺术:

属性 说明
@property(readonly, getter=isRecording) BOOL recording; 是否正在录音,只读
@property(readonly) NSURL *url 录音文件地址,只读
@property(readonly) NSDictionary *settings 录音文件设置,只读
@property(readonly) NSTimeInterval currentTime 录音时长,只读,注意仅仅在录音状态可用
@property(readonly) NSTimeInterval deviceCurrentTime 输入设置的时间长度,只读,注意此属性一直可访问
@property(getter=isMeteringEnabled) BOOL meteringEnabled; 是否启用录音测量,如果启用录音测量可以获得录音分贝等数据信息
@property(nonatomic, copy) NSArray *channelAssignments 当前录音的通道
对象方法 说明
- (instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError 录音机对象初始化方法,注意其中的url必须是本地文件url,settings是录音格式、编码等设置
- (BOOL)prepareToRecord 准备录音,主要用于创建缓冲区,如果不手动调用,在调用record录音时也会自动调用
- (BOOL)record 开始录音
- (BOOL)recordAtTime:(NSTimeInterval)time 在指定的时间开始录音,一般用于录音暂停再恢复录音
- (BOOL)recordForDuration:(NSTimeInterval) duration 按指定的时长开始录音
- (BOOL)recordAtTime:(NSTimeInterval)time forDuration:(NSTimeInterval) duration 在指定的时间开始录音,并指定录音时长
- (void)pause; 暂停录音
- (void)stop; 停止录音
- (BOOL)deleteRecording; 删除录音,注意要删除录音此时录音机必须处于停止状态
- (void)updateMeters; 更新测量数据,注意只有meteringEnabled为YES此方法才可用
- (float)peakPowerForChannel:(NSUInteger)channelNumber; 指定通道的测量峰值,注意只有调用完updateMeters才有值
- (float)averagePowerForChannel:(NSUInteger)channelNumber 指定通道的测量平均值,注意只有调用完updateMeters才有值
代理方法 说明
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag 完成录音
- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error 录音编码发生错误

AV奥迪(Audi)oRecorder非常多属性和艺术跟AV奥迪oPlayer都以类似的,可是它的创始有所不相同,在开创录音机时除了钦定路径外还必需钦命录音设置新闻,因为录音机必得精通录音文件的格式、采集样品率、通道数、每一个采集样品点的位数等新闻,然而也并非持有的消息都必得安装,平时只要求多少个常用设置。关于录音设置详细帮忙文书档案中的“AV Foundation 奥迪(Audi)o Settings Constants”。

下边就动用AV奥迪(Audi)oRecorder创造一个录音机,实现了录音、暂停、甘休、播放等效率,落成效益大约如下:

在这些示例上校实行三个一体化的录音调控,蕴涵录音、暂停、复苏、甘休,同期还有只怕会实时展现客户录音的鸣响波动,当顾客点击完停止按键还有恐怕会自动播放录音文件。程序的创设首要分为以下几步:

上面是至关心重视要代码:

//
//  ViewController.m
//  AVAudioRecorder
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define kRecordAudioFile @"myRecord.caf"

@interface ViewController ()<AVAudioRecorderDelegate>

@property (nonatomic,strong) AVAudioRecorder *audioRecorder;//音频录音机
@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//音频播放器,用于播放录音文件
@property (nonatomic,strong) NSTimer *timer;//录音声波监控(注意这里暂时不对播放进行监控)

@property (weak, nonatomic) IBOutlet UIButton *record;//开始录音
@property (weak, nonatomic) IBOutlet UIButton *pause;//暂停录音
@property (weak, nonatomic) IBOutlet UIButton *resume;//恢复录音
@property (weak, nonatomic) IBOutlet UIButton *stop;//停止录音
@property (weak, nonatomic) IBOutlet UIProgressView *audioPower;//音频波动

@end

@implementation ViewController

#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setAudioSession];
}

#pragma mark - 私有方法
/**
 *  设置音频会话
 */
-(void)setAudioSession{
    AVAudioSession *audioSession=[AVAudioSession sharedInstance];
    //设置为播放和录音状态,以便可以在录制完之后播放录音
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    [audioSession setActive:YES error:nil];
}

/**
 *  取得录音文件保存路径
 *
 *  @return 录音文件路径
 */
-(NSURL *)getSavePath{
    NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    urlStr=[urlStr stringByAppendingPathComponent:kRecordAudioFile];
    NSLog(@"file path:%@",urlStr);
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得录音文件设置
 *
 *  @return 录音设置
 */
-(NSDictionary *)getAudioSetting{
    NSMutableDictionary *dicM=[NSMutableDictionary dictionary];
    //设置录音格式
    [dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];
    //设置录音采样率,8000是电话采样率,对于一般录音已经够了
    [dicM setObject:@(8000) forKey:AVSampleRateKey];
    //设置通道,这里采用单声道
    [dicM setObject:@(1) forKey:AVNumberOfChannelsKey];
    //每个采样点位数,分为8、16、24、32
    [dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey];
    //是否使用浮点数采样
    [dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey];
    //....其他设置等
    return dicM;
}

/**
 *  获得录音机对象
 *
 *  @return 录音机对象
 */
-(AVAudioRecorder *)audioRecorder{
    if (!_audioRecorder) {
        //创建录音文件保存路径
        NSURL *url=[self getSavePath];
        //创建录音格式设置
        NSDictionary *setting=[self getAudioSetting];
        //创建录音机
        NSError *error=nil;
        _audioRecorder=[[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error];
        _audioRecorder.delegate=self;
        _audioRecorder.meteringEnabled=YES;//如果要监控声波则必须设置为YES
        if (error) {
            NSLog(@"创建录音机对象时发生错误,错误信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _audioRecorder;
}

/**
 *  创建播放器
 *
 *  @return 播放器
 */
-(AVAudioPlayer *)audioPlayer{
    if (!_audioPlayer) {
        NSURL *url=[self getSavePath];
        NSError *error=nil;
        _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
        _audioPlayer.numberOfLoops=0;
        [_audioPlayer prepareToPlay];
        if (error) {
            NSLog(@"创建播放器过程中发生错误,错误信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _audioPlayer;
}

/**
 *  录音声波监控定制器
 *
 *  @return 定时器
 */
-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(audioPowerChange) userInfo:nil repeats:YES];
    }
    return _timer;
}

/**
 *  录音声波状态设置
 */
-(void)audioPowerChange{
    [self.audioRecorder updateMeters];//更新测量值
    float power= [self.audioRecorder averagePowerForChannel:0];//取得第一个通道的音频,注意音频强度范围时-160到0
    CGFloat progress=(1.0/160.0)*(power+160.0);
    [self.audioPower setProgress:progress];
}
#pragma mark - UI事件
/**
 *  点击录音按钮
 *
 *  @param sender 录音按钮
 */
- (IBAction)recordClick:(UIButton *)sender {
    if (![self.audioRecorder isRecording]) {
        [self.audioRecorder record];//首次使用应用时如果调用record方法会询问用户是否允许使用麦克风
        self.timer.fireDate=[NSDate distantPast];
    }
}

/**
 *  点击暂定按钮
 *
 *  @param sender 暂停按钮
 */
- (IBAction)pauseClick:(UIButton *)sender {
    if ([self.audioRecorder isRecording]) {
        [self.audioRecorder pause];
        self.timer.fireDate=[NSDate distantFuture];
    }
}

/**
 *  点击恢复按钮
 *  恢复录音只需要再次调用record,AVAudioSession会帮助你记录上次录音位置并追加录音
 *
 *  @param sender 恢复按钮
 */
- (IBAction)resumeClick:(UIButton *)sender {
    [self recordClick:sender];
}

/**
 *  点击停止按钮
 *
 *  @param sender 停止按钮
 */
- (IBAction)stopClick:(UIButton *)sender {
    [self.audioRecorder stop];
    self.timer.fireDate=[NSDate distantFuture];
    self.audioPower.progress=0.0;
}

#pragma mark - 录音机代理方法
/**
 *  录音完成,录音完成后播放录音
 *
 *  @param recorder 录音机对象
 *  @param flag     是否成功
 */
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
    }
    NSLog(@"录音完成!");
}

@end

运作效果:

音乐播放器的骨干职能是对准播放财富的决定,其大旨内容富含媒体财富意况调节、播放器状态调整。

旋律队列服务

大家应该早已注意到了,无论是前面的录音依然旋律播放均不补助网络流媒体播放,当然对于录音来讲这种需要恐怕十分的小,但是对于音频播放来讲一时候就很有至关重要了。AV奥迪oPlayer只可以播放当和姑件,何况是一回性加载所以音频数据,开首化AV奥迪oPlayer时钦点的USportageL也不得不是File U卡宴L而不能够是HTTP UENVISIONL。当然,将音频文件下载到本地然后再调用AVAudioPlayer来播放也是一种播放互联网节奏的不二诀窍,不过这种办法最大的缺陷便是必得等到方方面面音频播放完毕技巧播放,而没办法利用流式播放,那频仍在骨子里费用中是不合实际的。那么在iOS中如何播放互连网流媒体呢?就是行使奥迪oToolbox框架中的音频队列服务奥迪(Audi)o Queue Services。

应用音频队列服务完全能够变成音频播放和录像,首先看一下录音音频服务队列:

贰个节奏服务队列奥迪(Audi)o Queue有三局部构成:

多少个缓冲器Buffers:各类缓冲器都以一个囤积音频数据的一时库房。

二个缓冲队列Buffer Queue:多个包蕴音频缓冲器的静止队列。

三个回调Callback:叁个自定义的队列回调函数。

声音通过输入设备步入缓冲队列中,首先填充第两个缓冲器;当第四个缓冲器填充满之后自动填充下三个缓冲器,同一时候会调用回调函数;在回调函数中须求将缓冲器中的音频数据写入磁盘,同偶尔候将缓冲器放回到缓冲队列中以便重用。上边是Apple官方关于音频队列服务的流程暗示图:

就疑似的,看一下旋律播放慢冲队列,其组成都部队分和录音缓冲队列类似。

可是在音频播放缓冲队列中,回调函数调用的机缘不一样于音频摄像缓冲队列,流程刚好相反。将音频读取到缓冲器中,一旦二个缓冲器填充满之后就放到缓冲队列中,然后继续填充其余缓冲器;当开播时,则从第3个缓冲器中读取音频进行播报;一旦播放完事后就能触发回调函数,开播下三个缓冲器中的音频,同时填充第二个缓冲器放;填充满之后再行放回到缓冲队列。下边是事无巨细的流程:

当然,要精通音频队列服务的规律并简单,难题是何等促成这么些自定义的回调函数,那其中大家有恢宏的干活要做,调整作和播出放状态、管理卓殊中断、举行音频编码等等。由于牵扯内容过多,况且不是本文指标,要是之后一时间将另开一篇文章重视介绍,目前有广大第三方能够框架能够从来运用,比方奥迪(Audi)oStreamer、FreeStreamer。由于前者当前只有非ARC版本,所以下边无妨选择FreeStreamer来大约演示在线音频播放的历程,当然在利用在此以前要做如下策画干活:

1.拷贝FreeStreamer中的Reachability.h、Reachability.m和Common、astreamer七个文本夹中的内容到花色中。

2.增加FreeStreamer运用的类库:CFNetwork.framework、奥迪oToolbox.framework、AVFoundation.framework
、libxml2.dylib、MediaPlayer.framework。

3.假设援引libxml2.dylib编写翻译不通过,须求在Xcode的Targets-Build Settings-Header Build Path中加多$(SDKROOT)/usr/include/libxml2。

4.将FreeStreamer中的FreeStreamerMobile-Prefix.pch文件加多到项目中并将Targets-Build Settings-Precompile Prefix Header设置为YES,在Targets-Build Settings-Prefix Header设置为$(SRCROOT)/项目名称/FreeStreamerMobile-Prefix.pch(因为Xcode6暗中认可未有pch文件)

下一场就足以编写制定代码播放网络节奏了:

//
//  ViewController.m
//  AudioQueueServices
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  使用FreeStreamer实现网络音频播放

#import "ViewController.h"
#import "FSAudioStream.h"

@interface ViewController ()

@property (nonatomic,strong) FSAudioStream *audioStream;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.audioStream play];
}

/**
 *  取得本地文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle]pathForResource:@"刘若英 - 原来你也在这里.mp3" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}
-(NSURL *)getNetworkUrl{
    NSString *[email protected]"http://192.168.1.102/liu.mp3";
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  创建FSAudioStream对象
 *
 *  @return FSAudioStream对象
 */
-(FSAudioStream *)audioStream{
    if (!_audioStream) {
        NSURL *url=[self getNetworkUrl];
        //创建FSAudioStream对象
        _audioStream=[[FSAudioStream alloc]initWithUrl:url];
        _audioStream.onFailure=^(FSAudioStreamError error,NSString *description){
            NSLog(@"播放过程中发生错误,错误信息:%@",description);
        };
        _audioStream.onCompletion=^(){
            NSLog(@"播放完成!");
        };
        [_audioStream setVolume:0.5];//设置声音
    }
    return _audioStream;
}

@end

事实上FreeStreamer的机能很有力,不仅是广播本地、互联网节奏那么粗略,它还辅助播放列表、检查包内容、瑞虎SS订阅、播放中断等重重无敌的功能,乃至还隐含了三个旋律深入分析器,风乐趣的仇敌能够访谈官方网址查阅详细用法

值得庆幸的是大家并不用本人去落到实处如在线能源拉取、能源缓存、财富解码播放等复杂的尾巴部分功能,AVFoundation框架能够帮忙咱们成功那一个业务,大家要做的是针对AVPlayer、AVPlayerItem提供的接口依照业务打开包装。

视频

本文不介绍框架的详实使用,只针对现实选用工作场景提供包装思路及主题代码

MPMoviePlayerController

在iOS中播放摄像能够利用MediaPlayer.framework种的MPMoviePlayerController类来造成,它帮忙本地录像和网络摄像播放。这一个类落成了MPMediaPlayback公约,因而全数平常的播放器调控成效,举个例子播放、暂停、结束等。可是MPMediaPlayerController本身并不是八个完完全全的视图调整器,假设要在UI中显得录像必要将view属性增加到分界面中。下边列出了MPMoviePlayerController的常用属性和议程:

属性 说明
@property (nonatomic, copy) NSURL *contentURL 播放媒体URL,这个URL可以是本地路径,也可以是网络路径
@property (nonatomic, readonly) UIView *view 播放器视图,如果要显示视频必须将此视图添加到控制器视图中
@property (nonatomic, readonly) UIView *backgroundView 播放器背景视图
@property (nonatomic, readonly) MPMoviePlaybackState playbackState 媒体播放状态,枚举类型:
MPMoviePlaybackStateStopped:停止播放
MPMoviePlaybackStatePlaying:正在播放
MPMoviePlaybackStatePaused:暂停
MPMoviePlaybackStateInterrupted:中断
MPMoviePlaybackStateSeekingForward:向前定位
MPMoviePlaybackStateSeekingBackward:向后定位
@property (nonatomic, readonly) MPMovieLoadState loadState 网络媒体加载状态,枚举类型:
MPMovieLoadStateUnknown:位置类型
MPMovieLoadStatePlayable:
MPMovieLoadStatePlaythroughOK:这种状态如果shouldAutoPlay为YES将自动播放
MPMovieLoadStateStalled:停滞状态
@property (nonatomic) MPMovieControlStyle controlStyle 控制面板风格,枚举类型:
MPMovieControlStyleNone:无控制面板
MPMovieControlStyleEmbedded:嵌入视频风格
MPMovieControlStyleFullscreen:全屏
MPMovieControlStyleDefault:默认风格
@property (nonatomic) MPMovieRepeatMode repeatMode; 重复播放模式,枚举类型:
MPMovieRepeatModeNone:不重复,默认值
MPMovieRepeatModeOne:重复播放
@property (nonatomic) BOOL shouldAutoplay 当网络媒体缓存到一定数据时是否自动播放,默认为YES
@property (nonatomic, getter=isFullscreen) BOOL fullscreen 是否全屏展示,默认为NO,注意如果要通过此属性设置全屏必须在视图显示完成后设置,否则无效
@property (nonatomic) MPMovieScalingMode scalingMode 视频缩放填充模式,枚举类型:
MPMovieScalingModeNone:不进行任何缩放
MPMovieScalingModeAspectFit:固定缩放比例并且尽量全部展示视频,不会裁切视频
MPMovieScalingModeAspectFill:固定缩放比例并填充满整个视图展示,可能会裁切视频
MPMovieScalingModeFill:不固定缩放比例压缩填充整个视图,视频不会被裁切但是比例失衡
@property (nonatomic, readonly) BOOL readyForDisplay 是否有相关媒体被播放
@property (nonatomic, readonly) MPMovieMediaTypeMask movieMediaTypes 媒体类别,枚举类型:
MPMovieMediaTypeMaskNone:未知类型
MPMovieMediaTypeMaskVideo:视频
MPMovieMediaTypeMaskAudio:音频
@property (nonatomic) MPMovieSourceType movieSourceType 媒体源,枚举类型:
MPMovieSourceTypeUnknown:未知来源
MPMovieSourceTypeFile:本地文件
MPMovieSourceTypeStreaming:流媒体(直播或点播)
@property (nonatomic, readonly) NSTimeInterval duration 媒体时长,如果未知则返回0
@property (nonatomic, readonly) NSTimeInterval playableDuration 媒体可播放时长,主要用于表示网络媒体已下载视频时长
@property (nonatomic, readonly) CGSize naturalSize 视频实际尺寸,如果未知则返回CGSizeZero
@property (nonatomic) NSTimeInterval initialPlaybackTime 起始播放时间
@property (nonatomic) NSTimeInterval endPlaybackTime 终止播放时间
@property (nonatomic) BOOL allowsAirPlay 是否允许无线播放,默认为YES
@property (nonatomic, readonly, getter=isAirPlayVideoActive) BOOL airPlayVideoActive 当前媒体是否正在通过AirPlay播放
@property(nonatomic, readonly) BOOL isPreparedToPlay 是否准备好播放
@property(nonatomic) NSTimeInterval currentPlaybackTime 当前播放时间,单位:秒
@property(nonatomic) float currentPlaybackRate 当前播放速度,如果暂停则为0,正常速度为1.0,非0数据表示倍率
对象方法 说明
- (instancetype)initWithContentURL:(NSURL *)url 使用指定的URL初始化媒体播放控制器对象
- (void)setFullscreen:(BOOL)fullscreen animated:(BOOL)animated 设置视频全屏,注意如果要通过此方法设置全屏则必须在其视图显示之后设置,否则无效
- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option 获取在指定播放时间的视频缩略图,第一个参数是获取缩略图的时间点数组;第二个参数代表时间点精度,枚举类型:
MPMovieTimeOptionNearestKeyFrame:时间点附近
MPMovieTimeOptionExact:准确时间
- (void)cancelAllThumbnailImageRequests 取消所有缩略图获取请求
- (void)prepareToPlay 准备播放,加载视频数据到缓存,当调用play方法时如果没有准备好会自动调用此方法
- (void)play 开始播放
- (void)pause 暂停播放
- (void)stop 停止播放
- (void)beginSeekingForward 向前定位
- (void)beginSeekingBackward 向后定位
- (void)endSeeking 停止快进/快退
通知 说明
MPMoviePlayerScalingModeDidChangeNotification 视频缩放填充模式发生改变
MPMoviePlayerPlaybackDidFinishNotification 媒体播放完成或用户手动退出,具体完成原因可以通过通知userInfo中的key为MPMoviePlayerPlaybackDidFinishReasonUserInfoKey的对象获取
MPMoviePlayerPlaybackStateDidChangeNotification 播放状态改变,可配合playbakcState属性获取具体状态
MPMoviePlayerLoadStateDidChangeNotification 媒体网络加载状态改变
MPMoviePlayerNowPlayingMovieDidChangeNotification 当前播放的媒体内容发生改变
MPMoviePlayerWillEnterFullscreenNotification 将要进入全屏
MPMoviePlayerDidEnterFullscreenNotification 进入全屏后
MPMoviePlayerWillExitFullscreenNotification 将要退出全屏
MPMoviePlayerDidExitFullscreenNotification 退出全屏后
MPMoviePlayerIsAirPlayVideoActiveDidChangeNotification 当媒体开始通过AirPlay播放或者结束AirPlay播放
MPMoviePlayerReadyForDisplayDidChangeNotification 视频显示状态改变
MPMovieMediaTypesAvailableNotification 确定了媒体可用类型后
MPMovieSourceTypeAvailableNotification 确定了媒体来源后
MPMovieDurationAvailableNotification 确定了媒体播放时长后
MPMovieNaturalSizeAvailableNotification 确定了媒体的实际尺寸后
MPMoviePlayerThumbnailImageRequestDidFinishNotification 缩略图请求完成之后
MPMediaPlaybackIsPreparedToPlayDidChangeNotification 做好播放准备后

只顾MPMediaPlayerController的情形等音信并不是通过代办来和外侧交互的,而是经过通知中央,因而从下边包车型大巴列表中可以看出常用的局部通知。由于MPMoviePlayerController本人对于媒体播放做了深度的卷入,使用起来就一定简单:创造MPMoviePlayerController对象,设置frame属性,将MPMoviePlayerController的view增多到调整器视图中。下边包车型地铁言传身教上校创造叁个播放调整器并累加播放状态改动及广播完结的关照:

//
//  ViewController.m
//  MPMoviePlayerController
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()

@property (nonatomic,strong) MPMoviePlayerController *moviePlayer;//视频播放控制器

@end

@implementation ViewController

#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

    //播放
    [self.moviePlayer play];

    //添加通知
    [self addNotification];

}

-(void)dealloc{
    //移除所有通知监控
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - 私有方法
/**
 *  取得本地文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得网络文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getNetworkUrl{
    NSString *[email protected]"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  创建媒体播放控制器
 *
 *  @return 媒体播放控制器
 */
-(MPMoviePlayerController *)moviePlayer{
    if (!_moviePlayer) {
        NSURL *url=[self getNetworkUrl];
        _moviePlayer=[[MPMoviePlayerController alloc]initWithContentURL:url];
        _moviePlayer.view.frame=self.view.bounds;
        _moviePlayer.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        [self.view addSubview:_moviePlayer.view];
    }
    return _moviePlayer;
}

/**
 *  添加通知监控媒体播放控制器状态
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];

}

/**
 *  播放状态改变,注意播放完成时的状态是暂停
 *
 *  @param notification 通知对象
 */
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
    switch (self.moviePlayer.playbackState) {
        case MPMoviePlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMoviePlaybackStatePaused:
            NSLog(@"暂停播放.");
            break;
        case MPMoviePlaybackStateStopped:
            NSLog(@"停止播放.");
            break;
        default:
            NSLog(@"播放状态:%li",self.moviePlayer.playbackState);
            break;
    }
}

/**
 *  播放完成
 *
 *  @param notification 通知对象
 */
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
    NSLog(@"播放完成.%li",self.moviePlayer.playbackState);
}


@end

运维效果:

从上边包车型地铁API我们也轻巧看出其实MPMoviePlayerController效率万分强劲,平常支出中作为日常的媒体播放器也截然没反常。MPMoviePlayerController除了常常的录像播放和垄断(monopoly)外还会有一对强有力的效果与利益,比如截取录像缩略图。诉求视频缩略图时假使调用- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option格局钦定得到缩略图的时间点,然后监察和控制MPMoviePlayerThumbnailImageRequestDidFinishNotification公告,每一种时间点的缩略图乞求达成就能够调用通告,在公告调用方法中能够透过MPMoviePlayerThumbnailImageKey获得UIImage对象管理就能够。举个例子下边包车型地铁程序演示了在程序运营后获取三个时间点的缩略图的历程,截图成功后保存到相册:

//
//  ViewController.m
//  MPMoviePlayerController
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  视频截图

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()

@property (nonatomic,strong) MPMoviePlayerController *moviePlayer;//视频播放控制器

@end

@implementation ViewController

#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

    //播放
    [self.moviePlayer play];

    //添加通知
    [self addNotification];

    //获取缩略图
    [self thumbnailImageRequest];
}

-(void)dealloc{
    //移除所有通知监控
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - 私有方法
/**
 *  取得本地文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得网络文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getNetworkUrl{
    NSString *[email protected]"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  创建媒体播放控制器
 *
 *  @return 媒体播放控制器
 */
-(MPMoviePlayerController *)moviePlayer{
    if (!_moviePlayer) {
        NSURL *url=[self getNetworkUrl];
        _moviePlayer=[[MPMoviePlayerController alloc]initWithContentURL:url];
        _moviePlayer.view.frame=self.view.bounds;
        _moviePlayer.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        [self.view addSubview:_moviePlayer.view];
    }
    return _moviePlayer;
}

/**
 *  获取视频缩略图
 */
-(void)thumbnailImageRequest{
    //获取13.0s、21.5s的缩略图
    [self.moviePlayer requestThumbnailImagesAtTimes:@[@13.0,@21.5] timeOption:MPMovieTimeOptionNearestKeyFrame];
}

#pragma mark - 控制器通知
/**
 *  添加通知监控媒体播放控制器状态
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerThumbnailRequestFinished:) name:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:self.moviePlayer];

}

/**
 *  播放状态改变,注意播放完成时的状态是暂停
 *
 *  @param notification 通知对象
 */
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
    switch (self.moviePlayer.playbackState) {
        case MPMoviePlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMoviePlaybackStatePaused:
            NSLog(@"暂停播放.");
            break;
        case MPMoviePlaybackStateStopped:
            NSLog(@"停止播放.");
            break;
        default:
            NSLog(@"播放状态:%li",self.moviePlayer.playbackState);
            break;
    }
}

/**
 *  播放完成
 *
 *  @param notification 通知对象
 */
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
    NSLog(@"播放完成.%li",self.moviePlayer.playbackState);
}

/**
 *  缩略图请求完成,此方法每次截图成功都会调用一次
 *
 *  @param notification 通知对象
 */
-(void)mediaPlayerThumbnailRequestFinished:(NSNotification *)notification{
    NSLog(@"视频截图完成.");
    UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
    //保存图片到相册(首次调用会请求用户获得访问相册权限)
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}

@end

截图效果:

    

AVPlayerItem封装

AVPlayerItemAVPlayer广播能源的基本单位,得益于OC语言特出的命名习惯,大家极度轻便精晓AVPlayerItem类的定义,它管理了媒体财富的地址时长缓存,并提供相关事态属性用于监听。下边代码块彰显的是棉被服装进的性质:

// AVPlayerItem.h/**订阅这个通知,当资源完成播放时该通知会被发出*/AVF_EXPORT NSString *const AVPlayerItemDidPlayToEndTimeNotification NS_AVAILABLE(10_7, 4_0); // item has played to its end time/*!@property status@abstractThe ability of the receiver to be used for playback.@discussionThe value of this property is an AVPlayerItemStatus that indicates whether the receiver can be used for playback.When the value of this property is AVPlayerItemStatusFailed, the receiver can no longer be used for playback and a new instance needs to be created in its place. When this happens, clients can check the value of the errorproperty to determine the nature of the failure. This property is key value observable.@translation监听这个属性可以帮助我们获取当前资源的状态是否可以用于播放*/@property (nonatomic, readonly) AVPlayerItemStatus status;/*!@property loadedTimeRanges@abstract This property provides a collection of time ranges for which the player has the media data readily available. The ranges provided might be discontinuous.@discussion Returns an NSArray of NSValues containing CMTimeRanges.@translation这个属性将会返回当前资源的缓存进度*/@property (nonatomic, readonly) NSArray<NSValue *> *loadedTimeRanges;

值得关心的是,该类的有余景色响应使用了KVO、公告等办法发生,在接口阅读和动用上给大家带来了一定的劳累,所以要将气象的监察和控制再封装成大家熟谙的款型(delegate、block),并追加媒体播放的自定义参数:

// CMPlayerItem.h@class CMPlayerItem;@protocol CMPlayItemDelegate <NSObject>@optional/** 缓存进度 */- musicPlayerItem:(CMPlayerItem *)item bufferSeconds:(NSTimeInterval)seconds rate:rate;/** 资源状态 */- musicPlayerItem:(CMPlayerItem *)item playItemStatus:(AVPlayerItemStatus)status;@end@interface CMPlayerItem : AVPlayerItem/** 名称 */@property (nonatomic, copy) NSString *musicName;/** 作者 */@property (nonatomic, copy) NSString *musicAuthor;/** 封面 */@property (nonatomic, strong) NSURL *musicCoverURL;/** 工厂方法 */+ (instancetype)musicPlayItemWithURL:URL name:(NSString *)name author:(NSString *)author coverURL:coverURL;/** 缓存时长 */@property (nonatomic, assign, readonly) NSTimeInterval bufferSeconds;/** 总时长 */@property (nonatomic, assign, readonly) NSTimeInterval durationSeconds;/** 资源状态代理 */@property (nonatomic, weak) id<CMPlayItemDelegate> delegate;@end// CMPlayerItem.m@implementation CMPlayerItem+ (instancetype)musicPlayItemWithURL:URL name:(NSString *)name author:(NSString *)author coverURL:coverURL { CMPlayerItem *item = [self playerItemWithURL:URL]; item.musicName = name; item.musicAuthor = author; item.musicCoverURL = coverURL; [item addObserver:item forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; [item addObserver:item forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; return item;}#pragma mark -- observeValueForKeyPath:(NSString *)keyPath ofObject:object change:(NSDictionary<NSString *,id> *)change context:context { if (!self.delegate) return; // 监听资源状态 if ([keyPath isEqualToString:@"status"]) { if ([self.delegate respondsToSelector:@selector(musicPlayerItem:playItemStatus:)]) { [self.delegate musicPlayerItem:self playItemStatus:self.status]; } // 监听资源下载进度 } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { if ([self.delegate respondsToSelector:@selector(musicPlayerItem:bufferSeconds:rate:)]) { [self.delegate musicPlayerItem:self bufferSeconds:self.bufferSeconds rate:self.bufferSeconds / self.durationSeconds]; } }}...@end

这里大家使用了延续的特征,创制了一个符合业务供给的类,并使用了工厂方法提供了一个新的初阶化器,用于最初化须要财富并丰裕监听。比较父类的接口,经过包装今后的API阅读起来是否便于精通多了~

扩展--使用AVFoundation生成缩略图

因而前边的方法大家应该已经见到,使用MPMoviePlayerController来生成缩略图充分不难,但是假使单单是是为了扭转缩略图而不实行摄像播放的话,此刻利用MPMoviePlayerController就有一点点白璧三献了。其实选用AVFundation框架中的AVAssetImageGenerator就可以猎取录制缩略图。使用AVAssetImageGenerator获取缩略图差非常少分为多个步骤:

//
//  ViewController.m
//  AVAssetImageGenerator
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //获取第13.0s的缩略图
    [self thumbnailImageRequest:13.0];
}

#pragma mark - 私有方法
/**
 *  取得本地文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得网络文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getNetworkUrl{
    NSString *[email protected]"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  截取指定时间的视频缩略图
 *
 *  @param timeBySecond 时间点
 */
-(void)thumbnailImageRequest:(CGFloat )timeBySecond{
    //创建URL
    NSURL *url=[self getNetworkUrl];
    //根据url创建AVURLAsset
    AVURLAsset *urlAsset=[AVURLAsset assetWithURL:url];
    //根据AVURLAsset创建AVAssetImageGenerator
    AVAssetImageGenerator *imageGenerator=[AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
    /*截图
     * requestTime:缩略图创建时间
     * actualTime:缩略图实际生成的时间
     */
    NSError *error=nil;
    CMTime time=CMTimeMakeWithSeconds(timeBySecond, 10);//CMTime是表示电影时间信息的结构体,第一个参数表示是视频第几秒,第二个参数表示每秒帧数.(如果要活的某一秒的第几帧可以使用CMTimeMake方法)
    CMTime actualTime;
    CGImageRef cgImage= [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error];
    if(error){
        NSLog(@"截取视频缩略图时发生错误,错误信息:%@",error.localizedDescription);
        return;
    }
    CMTimeShow(actualTime);
    UIImage *image=[UIImage imageWithCGImage:cgImage];//转化为UIImage
    //保存到相册
    UIImageWriteToSavedPhotosAlbum(image,nil, nil, nil);
    CGImageRelease(cgImage);
}

@end

变迁的缩略图效果:

数据结构深入分析

在封装AVPlayer事先,先来深入分析一下播放器须要。

  • 播放器有三种播放情势,顺序、乱序、单曲循环。点击下一曲开关要依靠不相同的播放形式获得曲目

  • 点击上一曲开关能够播放近来已经播放过上一首的戏码

  • 滑动黑胶唱片能够切换上一曲或下一曲

通过对要求的深入分析,我们将事情转移为数据结构,播放器的播音资源公司得以透过三个新鲜的行列结构进行田间管理,队列的尺寸为3,index=1指向播放中的数据,因为存在二个滑动唱片的操作设计,大家必需确认保证播放队列的剧情长度永世为3,不然在滑行时再加载必然会变成页面卡顿。当切换下一曲时吐弃index=0的多少,从财富列表获取下一曲内容。可是这么的数据结构存在二个短处,当切换上一曲时,我们永恒只可以获得上三遍播放的财富。

精晓的同学会发掘上一曲功效有所LIFO特性,很像八个栈创设。那么大家将事先的播报队列做一点改建,使用贰个栈结构将下一曲切换时抛弃的曲目保存好,当必要上一曲切换时行使栈中的开始和结果增加补充队列空缺。

图片 2playing.png

有了思路,大家将上一曲切换的流程转化为数据结构图并贯彻中央代码。首先播放队列放弃index=2的戏码,并从广播栈中获取栈顶曲目插入播放队列index=0。当播放栈内容为空时,依照不一致的广播形式从播放列表直接获得能源插入播放队列

图片 3prev.png

骨干代码如下:

- prev { ... // 丢弃播放队列最后一个元素 [self.playQueue removeLastObject]; CMPlayerItem *prevItem = [self.playedStack pop]; // 当栈中内容为空 if (!prevItem) { // 直接获取上一首资源 prevItem = [self prevResourceWithPlayingItem:self.playQueue[CM_PLAYQUEUE_PREV_SOURCE]]; } // 将资源插入播放队列 [self.playQueue insertObject:prevItem atIndex:CM_PLAYQUEUE_PREV_SOURCE]; // 更新播放器资源 [self replaceCurrentItemWithPlayerItem:self.playQueue[CM_PLAYQUEUE_PLAYING_SOURCE]]; [self replay]; ...}

下一曲切换操作和上一曲类似,只可是大家要改成一下数据流方向,将播放队列index=0内容丢至广播栈中,并从播放列表中依照当前方式插入播放队列index=2地点

图片 4next.png

着力代码如下:

- next { ... // 将index=0内容放入播放栈 [self.playedStack push:self.playQueue[CM_PLAYQUEUE_PREV_SOURCE]]; [self.playQueue removeObjectAtIndex:CM_PLAYQUEUE_PREV_SOURCE]; // 从播放列表补充下一首数据 [self.playQueue addObject:[self nextResourceWithPlayingItem:self.playQueue[CM_PLAYQUEUE_PLAYING_SOURCE]]]; // 更新播放器资源 [self replaceCurrentItemWithPlayerItem:self.playQueue[CM_PLAYQUEUE_PLAYING_SOURCE]]; [self replay]; ...}

MPMoviePlayerViewController

实在MPMoviePlayerController要是不作为嵌入录制来播音(举个例子在情报中寄存三个摄像),日常在广播时都以占满多个显示屏的,非常是在诺基亚、iTouch上。由此从iOS3.2事后苹果也在观念既然MPMoviePlayerController在动用时平时都以将其视图view增添到别的一个视图调整器中作为子视图,那么何不直接成立一个调整器视图内部创造叁个MPMoviePlayerController属性况且私下认可全屏播放,开垦者在开辟的时候向来使用这几个视图调控器。这么些里面有二个MPMoviePlayerController的视图调控器正是MPMoviePlayerViewController,它接二连三于UIViewController。MPMoviePlayerViewController内部多了多少个moviePlayer属性和五个含有url的初阶化方法,同期它里面贯彻了有个别看成模态视图突显所特有的效率,比如暗中认可是全屏形式显示、弹出后自动播放、作为模态窗口展现时假诺点击“Done”按键会自动退出模态窗口等。在底下的身体力行中就不直接将播放器放到主视图调节器,而是放到一个模态视图控制器中,简单演示MPMoviePlayerViewController的选用。

//
//  ViewController.m
//  MPMoviePlayerViewController
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  MPMoviePlayerViewController使用

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()

//播放器视图控制器
@property (nonatomic,strong) MPMoviePlayerViewController *moviePlayerViewController;

@end

@implementation ViewController

#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

}

-(void)dealloc{
    //移除所有通知监控
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - 私有方法
/**
 *  取得本地文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得网络文件路径
 *
 *  @return 文件路径
 */
-(NSURL *)getNetworkUrl{
    NSString *[email protected]"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

-(MPMoviePlayerViewController *)moviePlayerViewController{
    if (!_moviePlayerViewController) {
        NSURL *url=[self getNetworkUrl];
        _moviePlayerViewController=[[MPMoviePlayerViewController alloc]initWithContentURL:url];
        [self addNotification];
    }
    return _moviePlayerViewController;
}
#pragma mark - UI事件
- (IBAction)playClick:(UIButton *)sender {
    self.moviePlayerViewController=nil;//保证每次点击都重新创建视频播放控制器视图,避免再次点击时由于不播放的问题
//    [self presentViewController:self.moviePlayerViewController animated:YES completion:nil];
    //注意,在MPMoviePlayerViewController.h中对UIViewController扩展两个用于模态展示和关闭MPMoviePlayerViewController的方法,增加了一种下拉展示动画效果
    [self presentMoviePlayerViewControllerAnimated:self.moviePlayerViewController];
}

#pragma mark - 控制器通知
/**
 *  添加通知监控媒体播放控制器状态
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayerViewController.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayerViewController.moviePlayer];

}

/**
 *  播放状态改变,注意播放完成时的状态是暂停
 *
 *  @param notification 通知对象
 */
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
    switch (self.moviePlayerViewController.moviePlayer.playbackState) {
        case MPMoviePlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMoviePlaybackStatePaused:
            NSLog(@"暂停播放.");
            break;
        case MPMoviePlaybackStateStopped:
            NSLog(@"停止播放.");
            break;
        default:
            NSLog(@"播放状态:%li",self.moviePlayerViewController.moviePlayer.playbackState);
            break;
    }
}

/**
 *  播放完成
 *
 *  @param notification 通知对象
 */
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
    NSLog(@"播放完成.%li",self.moviePlayerViewController.moviePlayer.playbackState);
}

@end

运营效果:

此处需求强调一下,由于MPMoviePlayerViewController的起首化方法做了大气做事(举个例子设置U大切诺基L、自动播放、增加点击Done完结的监督等),所以当再次点击播放弹出新的模态窗口的时只要不销毁在此之前的MPMoviePlayerViewController,那么新的靶子就不可能做到开端化,那样也就不可能再度进行广播。

AVPlayer封装

和封装AVPlayerItem思路一致,通过持续的章程大家创设一个新类CMPlayer,整合父类接口并用大家耳闻则诵的情势暴光新的API,下面是.h文件代码

// CMPlayer.h@class CMPlayer;@class CMPlayerItem;/** 播放模式选择 - CMPlayerModeLoop: 顺序 - CMPlayerModeOne: 单曲循环 - CMPlayerModeShuffle: 乱序 */typedef NS_ENUM(NSUInteger, CMPlayerMode) { CMPlayerModeLoop, CMPlayerModeOne, CMPlayerModeShuffle,};@protocol CMPlayerDelegate <NSObject>@optional/** 播放 */- musicPlayerStatusPlaying:(CMPlayer *)player musicPlayerItem:(CMPlayerItem *)item;/** 暂停 */- musicPlayerStatusPaused:(CMPlayer *)player musicPlayerItem:(CMPlayerItem *)item;/** 加载中 */- musicPlayerStatusLoading:(CMPlayer *)player musicPlayerItem:(CMPlayerItem *)item;/** 完成 */- musicPlayerStatusComplete:(CMPlayer *)player musicPlayerItem:(CMPlayerItem *)item;/** 下一首 */- musicPlayerStatusNext:(CMPlayer *)player musicPlayerItem:(CMPlayerItem *)item;/** 上一首 */- musicPlayerStatusPrev:(CMPlayer *)player musicPlayerItem:(CMPlayerItem *)item;/** 重播 */- musicPlayerStatusReplay:(CMPlayer *)player musicPlayerItem:(CMPlayerItem *)item;/** 进度监听,间隔1s */- musicPlayerPlayingProgressCurrenSeconds:(NSTimeInterval)currentSec duration:(NSTimeInterval)durationSec buffer:(NSTimeInterval)bufferSec;@end@interface CMPlayer : AVPlayer/** 初始化播放器 @param playList 播放列表 @return 实例 */- (instancetype)initWithPlayList:(NSArray<CMPlayerItem *> *)playList;/** 播放模式 */@property (nonatomic, assign) CMPlayerMode playerMode;/** 播放器代理 */@property (nonatomic, weak) id<CMPlayerDelegate> delegate;#pragma mark -/** 播放列表 */@property (nonatomic, strong) NSArray<CMPlayerItem *> *playList;/** 播放队列 */@property (nonatomic, readonly) NSArray *currentPlayingQueue;/** 当前播放资源 */@property (nonatomic, readonly) CMPlayerItem *currentMusicItem;#pragma mark -/** 当前播放时长 */@property (nonatomic, readonly) NSTimeInterval currentSeconds;/** 放回当前播放item.duration */@property (nonatomic, readonly) NSTimeInterval durationSeconds;#pragma mark -/** 下一首 */- next;/** 上一首 */- prev;@end

.m文件具体完结代码请通过文末github链接自行clone,此处不再赘言

AVPlayer

MPMoviePlayerController丰硕壮大,大概不用写几行代码就会成就贰个播放器,可是正是出于它的中度封装使得要自定义这一个播放器变得很复杂,以致是不只怕毕其功于一役。举个例子有个别时候须求自定义播放器的体制,那么只要要运用MPMoviePlayerController就不对劲了,借使要对录制有自由的支配则能够动用AVPlayer。AVPlayer存在于AVFoundation中,它越是类似于底层,所以灵活性也越来越强:

AVPlayer本人并不可能显得录像,何况它也不像MPMoviePlayerController有贰个view属性。假若AVPlayer要突显必得创制一个播放器层AVPlayerLayer用于显示,播放器层承袭于CALayer,有了AVPlayerLayer之加多到调整器视图的layer中就可以。要动用AVPlayer首先精通一下多少个常用的类:

AVAsset:首要用于获取多媒体音讯,是多个抽象类,无法直接行使。

AVULANDLAsset:AVAsset的子类,能够依靠一个USportageL路线创造一个含有媒体音讯的AVUCR-VLAsset对象。

AVPlayerItem:多少个媒体能源管理对象,管理者录像的部分基本消息和情形,多个AVPlayerItem对应着四个录制财富。

上边轻松通过一个播放器来演示AVPlayer的运用,播放器的效率如下:

在那个自定义的播放器中贯彻了录像播放、暂停、进度体现和录像列表功效,上边将对那么些作用一一介绍。

首先说一下录制的播音、暂停作用,那也是最宗旨的功用,AVPlayer对应着五个法子play、pause来促成。不过关键难点是怎样判别当前录制是不是在广播,在前面的内容中不管音频播放器如故录制播放器都有料理的意况来判定,不过AVPlayer却不曾如此的情景属性,平常状态下得以因而判定播放器的广播速度来赢得播放状态。借使rate为0表明是终止状态,1是则是不荒谬播放状态。

附带要展现播放进程就未有其它播放器那么粗略了。在头里的播放器中司空眼惯是行使公告来博取播放器的景观,媒体加载状态等,然则无论AVPlayer仍然AVPlayerItem(AVPlayer有叁个属性currentItem是AVPlayerItem类型,表示近些日子播报的录像对象)都没办法儿赢得那些信息。当然AVPlayerItem是有布告的,但是对于得到播放状态和加载状态有用的文告独有一个:播放实现文告AVPlayerItemDidPlayToEndTimeNotification。在播放录制时,特别是广播互连网录像往往必要知道摄像加载情形、缓冲意况、播放境况,这几个消息方可经过KVO监察和控制AVPlayerItem的status、loadedTimeRanges属性来得到。当AVPlayerItem的status属性为AVPlayerStatusReadyToPlay是表明正在播放,独有处于那个意况时能力收获摄像时间长度等新闻;当loadedTimeRanges的改换时(每缓冲一局地数据就能更新此属性)能够获得本次缓冲加载的录像范围(包涵开端时间、此次加载时长),那样一来就足以实时获取缓冲景况。然后正是凭仗AVPlayer的- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block措施获得播放进度,这么些方法会在设定的时刻距离指定期更新播放进程,通过time参数布告顾客端。相信有了这一个摄像音信播报进度就小意思了,事实上通过那些新闻就到底日常阅览标其他播放器的缓冲速度显示以及拖动播放的效应也能够安枕无忧的落到实处。

末段就是摄像切换的作用,在后面介绍的有着播放器中种种播放器对象二遍只可以播放一个录像,若是要切换摄像只好重复创立一个目标,不过AVPlayer却提供了- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item艺术用于在分裂的摄像之间切换(事实上在AVFoundation内部还可能有二个AVQueuePlayer专门管理播放列表切换,有意思味的意中人能够活动钻研,这里不再赘述)。

上面附上代码:

//
//  ViewController.m
//  AVPlayer
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@property (nonatomic,strong) AVPlayer *player;//播放器对象

@property (weak, nonatomic) IBOutlet UIView *container; //播放器容器
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暂停按钮
@property (weak, nonatomic) IBOutlet UIProgressView *progress;//播放进度

@end

@implementation ViewController

#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
    [self.player play];
}

-(void)dealloc{
    [self removeObserverFromPlayerItem:self.player.currentItem];
    [self removeNotification];
}

#pragma mark - 私有方法
-(void)setupUI{
    //创建播放器层
    AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:self.player];
    playerLayer.frame=self.container.frame;
    //playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;//视频填充模式
    [self.container.layer addSublayer:playerLayer];
}

/**
 *  截取指定时间的视频缩略图
 *
 *  @param timeBySecond 时间点
 */

/**
 *  初始化播放器
 *
 *  @return 播放器对象
 */
-(AVPlayer *)player{
    if (!_player) {
        AVPlayerItem *playerItem=[self getPlayItem:0];
        _player=[AVPlayer playerWithPlayerItem:playerItem];
        [self addProgressObserver];
        [self addObserverToPlayerItem:playerItem];
    }
    return _player;
}

/**
 *  根据视频索引取得AVPlayerItem对象
 *
 *  @param videoIndex 视频顺序索引
 *
 *  @return AVPlayerItem对象
 */
-(AVPlayerItem *)getPlayItem:(int)videoIndex{
    NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.161/%i.mp4",videoIndex];
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:url];
    return playerItem;
}
#pragma mark - 通知
/**
 *  添加播放器通知
 */
-(void)addNotification{
    //给AVPlayerItem添加播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}

-(void)removeNotification{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/**
 *  播放完成通知
 *
 *  @param notification 通知对象
 */
-(void)playbackFinished:(NSNotification *)notification{
    NSLog(@"视频播放完成.");
}

#pragma mark - 监控
/**
 *  给播放器添加进度更新
 */
-(void)addProgressObserver{
    AVPlayerItem *playerItem=self.player.currentItem;
    UIProgressView *progress=self.progress;
    //这里设置每秒执行一次
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        float current=CMTimeGetSeconds(time);
        float total=CMTimeGetSeconds([playerItem duration]);
        NSLog(@"当前已经播放%.2fs.",current);
        if (current) {
            [progress setProgress:(current/total) animated:YES];
        }
    }];
}

/**
 *  给AVPlayerItem添加监控
 *
 *  @param playerItem AVPlayerItem对象
 */
-(void)addObserverToPlayerItem:(AVPlayerItem *)playerItem{
    //监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //监控网络加载情况属性
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{
    [playerItem removeObserver:self forKeyPath:@"status"];
    [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}
/**
 *  通过KVO监控播放器状态
 *
 *  @param keyPath 监控属性
 *  @param object  监视器
 *  @param change  状态改变
 *  @param context 上下文
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem *playerItem=object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
        if(status==AVPlayerStatusReadyToPlay){
            NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(playerItem.duration));
        }
    }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 - UI事件
/**
 *  点击播放/暂停按钮
 *
 *  @param sender 播放/暂停按钮
 */
- (IBAction)playClick:(UIButton *)sender {
//    AVPlayerItemDidPlayToEndTimeNotification
    //AVPlayerItem *playerItem= self.player.currentItem;
    if(self.player.rate==0){ //说明时暂停
        [sender setImage:[UIImage imageNamed:@"player_pause"] forState:UIControlStateNormal];
        [self.player play];
    }else if(self.player.rate==1){//正在播放
        [self.player pause];
        [sender setImage:[UIImage imageNamed:@"player_play"] forState:UIControlStateNormal];
    }
}


/**
 *  切换选集,这里使用按钮的tag代表视频名称
 *
 *  @param sender 点击按钮对象
 */
- (IBAction)navigationButtonClick:(UIButton *)sender {
    [self removeNotification];
    [self removeObserverFromPlayerItem:self.player.currentItem];
    AVPlayerItem *playerItem=[self getPlayItem:sender.tag];
    [self addObserverToPlayerItem:playerItem];
    //切换视频
    [self.player replaceCurrentItemWithPlayerItem:playerItem];
    [self addNotification];
}

@end

运维效果:

到如今甘休无论是MPMoviePlayerController依然AVPlayer来播放摄像都特别壮大,但是它也存在着部分不得逃避的题材,那正是支撑的录像编码格式很单薄:H.264、MPEG-4,扩张名(压缩格式):.DVD、.mov、.m4v、.m2v、.3gp、.3g2等。可是不论MPMoviePlayerController依然AVPlayer它们都帮忙绝大多数旋律编码,所以我们假诺纯粹是为着播放音乐的话也能够想念采用那多少个播放器。那么如何协理越来越多录制编码格式呢?方今以来任重(英文名:rèn zhòng)而道远依旧依靠第三方框架,在iOS上常用的录像编码、解码框架有:VLC、ffmpeg, 具体应用办法明日就不再做详细介绍。

同意后台播放

播放器须要在运用切换来后台时,保持节奏播放技能。允许后台播放最简便易行的办法,是在Target - Capability - Background Modes中打开布置

图片 5Enabling Background Audio

参考文献:《Enabling Background 奥迪(Audi)o》

摄像头

设置AVAudioSession

能够运用AVAudioSession报告操作系统如什么地方理你的App音频流,而无需和旋律硬件爆发间接的并行也许操作

图片 6Audio Session

设置AVAudioSessionCategory鲜明基才能件表现,再利用Mode Options两日性格对作为开展微调,上边是播放器设置源码:

- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... // 设置并激活音频会话类别 AVAudioSession *session = [AVAudioSession sharedInstance]; // 设置Category只支持音频播放,打断其他不支持混音App [session setCategory:AVAudioSessionCategoryPlayback error:nil]; // 启用session [session setActive:YES error:nil]; ... return YES;}

参照他事他说加以考察文献:《奥迪o Session Programming Guide》

UIImagePickerController拍照和录像摄像

上边看一下在iOS怎么着拍录和摄像摄像。在iOS中要拍录和摄像录制最简便的措施就是利用UIImagePickerController。UIImagePickerController传承于UINavigationController,前面包车型大巴篇章中首要性采用它来摘取照片,其实UIImagePickerController的效应不仅仅如此,它还是能用来拍片和录像视频。首先看一下以此类常用的属性和章程:

属性 说明
@property(nonatomic)           UIImagePickerControllerSourceType     sourceType 拾取源类型,sourceType是枚举类型:
UIImagePickerControllerSourceTypePhotoLibrary:照片库
,默认值
UIImagePickerControllerSourceTypeCamera:摄像头
UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
@property(nonatomic,copy)      NSArray                              *mediaTypes 媒体类型,默认情况下此数组包含kUTTypeImage,所以拍照时可以不用设置;但是当要录像的时候必须设置,可以设置为kUTTypeVideo(视频,但不带声音)或者kUTTypeMovie(视频并带有声音)
@property(nonatomic)           NSTimeInterval                        videoMaximumDuration 视频最大录制时长,默认为10 s
@property(nonatomic)           UIImagePickerControllerQualityType    videoQuality 视频质量,枚举类型:
UIImagePickerControllerQualityTypeHigh:高清质量
UIImagePickerControllerQualityTypeMedium:中等质量,适合WiFi传输
UIImagePickerControllerQualityTypeLow:低质量,适合蜂窝网传输
UIImagePickerControllerQualityType640x480:640*480
UIImagePickerControllerQualityTypeIFrame1280x720:1280*720
UIImagePickerControllerQualityTypeIFrame960x540:960*540
@property(nonatomic)           BOOL                                  showsCameraControls 是否显示摄像头控制面板,默认为YES
@property(nonatomic,retain)    UIView                                *cameraOverlayView 摄像头上覆盖的视图,可用通过这个视频来自定义拍照或录像界面
@property(nonatomic)           CGAffineTransform                     cameraViewTransform 摄像头形变
@property(nonatomic) UIImagePickerControllerCameraCaptureMode cameraCaptureMode 摄像头捕获模式,捕获模式是枚举类型:
UIImagePickerControllerCameraCaptureModePhoto:拍照模式
UIImagePickerControllerCameraCaptureModeVideo:视频录制模式
@property(nonatomic) UIImagePickerControllerCameraDevice      cameraDevice 摄像头设备,cameraDevice是枚举类型:
UIImagePickerControllerCameraDeviceRear:前置摄像头
UIImagePickerControllerCameraDeviceFront:后置摄像头
@property(nonatomic) UIImagePickerControllerCameraFlashMode   cameraFlashMode 闪光灯模式,枚举类型:
UIImagePickerControllerCameraFlashModeOff:关闭闪光灯
UIImagePickerControllerCameraFlashModeAuto:闪光灯自动
UIImagePickerControllerCameraFlashModeOn:打开闪光灯
类方法 说明
+ (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType 指定的源类型是否可用,sourceType是枚举类型:
UIImagePickerControllerSourceTypePhotoLibrary:照片库
UIImagePickerControllerSourceTypeCamera:摄像头
UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
+ (NSArray *)availableMediaTypesForSourceType:(UIImagePickerControllerSourceType)sourceType 指定的源设备上可用的媒体类型,一般就是图片和视频
+ (BOOL)isCameraDeviceAvailable:(UIImagePickerControllerCameraDevice)cameraDevice 指定的摄像头是否可用,cameraDevice是枚举类型:
UIImagePickerControllerCameraDeviceRear:前置摄像头
UIImagePickerControllerCameraDeviceFront:后置摄像头
+ (BOOL)isFlashAvailableForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice 指定摄像头的闪光灯是否可用
+ (NSArray *)availableCaptureModesForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice 获得指定摄像头上的可用捕获模式,捕获模式是枚举类型:
UIImagePickerControllerCameraCaptureModePhoto:拍照模式
UIImagePickerControllerCameraCaptureModeVideo:视频录制模式
对象方法 说明
- (void)takePicture 编程方式拍照
- (BOOL)startVideoCapture 编程方式录制视频
- (void)stopVideoCapture 编程方式停止录制视频
代理方法 说明
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 媒体拾取完成
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 取消拾取
扩展方法(主要用于保存照片、视频到相簿) 说明
UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo) 保存照片到相簿
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath) 能否将视频保存到相簿
void UISaveVideoAtPathToSavedPhotosAlbum(NSString *videoPath, id completionTarget, SEL completionSelector, void *contextInfo) 保存视频到相簿

要用UIImagePickerController来拍照也许摄像摄像常常能够分成如下步骤:

理当如此那一个历程中有过多细节能够安装,譬喻是不是出示拍照调节面板,拍照后是还是不是同意编辑等等,通过地点的性质/方法列表相信并轻巧驾驭。上面就以叁个示范呈现怎么着运用UIImagePickerController来拍照和录像录像,下边包车型地铁前后相继中一经将_isVideo设置为YES就是录像摄像方式,录像完后在主视图调控器中自动播放;借使将_isVideo设置为NO则为水墨画形式,拍照达成之后在主视图调控器中显得拍录的肖像:

//
//  ViewController.m
//  UIImagePickerController
//
//  Created by Kenshin Cui on 14/04/05.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (assign,nonatomic) int isVideo;//是否录制视频,如果为1表示录制视频,0代表拍照
@property (strong,nonatomic) UIImagePickerController *imagePicker;
@property (weak, nonatomic) IBOutlet UIImageView *photo;//照片展示视图
@property (strong ,nonatomic) AVPlayer *player;//播放器,用于录制完视频后播放视频

@end

@implementation ViewController

#pragma mark - 控制器视图事件
- (void)viewDidLoad {
    [super viewDidLoad];
    //通过这里设置当前程序是拍照还是录制视频
    _isVideo=YES;
}

#pragma mark - UI事件
//点击拍照按钮
- (IBAction)takeClick:(UIButton *)sender {
    [self presentViewController:self.imagePicker animated:YES completion:nil];
}

#pragma mark - UIImagePickerController代理方法
//完成
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
    NSString *mediaType=[info objectForKey:UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {//如果是拍照
        UIImage *image;
        //如果允许编辑则获得编辑后的照片,否则获取原始照片
        if (self.imagePicker.allowsEditing) {
            image=[info objectForKey:UIImagePickerControllerEditedImage];//获取编辑后的照片
        }else{
            image=[info objectForKey:UIImagePickerControllerOriginalImage];//获取原始照片
        }
        [self.photo setImage:image];//显示照片
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);//保存到相簿
    }else if([mediaType isEqualToString:(NSString *)kUTTypeMovie]){//如果是录制视频
        NSLog(@"video...");
        NSURL *url=[info objectForKey:UIImagePickerControllerMediaURL];//视频路径
        NSString *urlStr=[url path];
        if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(urlStr)) {
            //保存视频到相簿,注意也可以使用ALAssetsLibrary来保存
            UISaveVideoAtPathToSavedPhotosAlbum(urlStr, self, @selector(video:didFinishSavingWithError:contextInfo:), nil);//保存视频到相簿
        }

    }

    [self dismissViewControllerAnimated:YES completion:nil];
}
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    NSLog(@"取消");
}

#pragma mark - 私有方法
-(UIImagePickerController *)imagePicker{
    if (!_imagePicker) {
        _imagePicker=[[UIImagePickerController alloc]init];
        _imagePicker.sourceType=UIImagePickerControllerSourceTypeCamera;//设置image picker的来源,这里设置为摄像头
        _imagePicker.cameraDevice=UIImagePickerControllerCameraDeviceRear;//设置使用哪个摄像头,这里设置为后置摄像头
        if (self.isVideo) {
            [email protected][(NSString *)kUTTypeMovie];
            _imagePicker.videoQuality=UIImagePickerControllerQualityTypeIFrame1280x720;
            _imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModeVideo;//设置摄像头模式(拍照,录制视频)

        }else{
            _imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModePhoto;
        }
        _imagePicker.allowsEditing=YES;//允许编辑
        _imagePicker.delegate=self;//设置代理,检测操作
    }
    return _imagePicker;
}

//视频保存后的回调
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
    if (error) {
        NSLog(@"保存视频过程中发生错误,错误信息:%@",error.localizedDescription);
    }else{
        NSLog(@"视频保存成功.");
        //录制完之后自动播放
        NSURL *url=[NSURL fileURLWithPath:videoPath];
        _player=[AVPlayer playerWithURL:url];
        AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:_player];
        playerLayer.frame=self.photo.frame;
        [self.photo.layer addSublayer:playerLayer];
        [_player play];

    }
}
@end

运转效果(摄像录制):

远程序调控制媒体播放

我们还需支撑在锁屏的媒体能源音信展现以及调整中央中对音乐的主宰。我们得以使用Media Player框架的MPRemoteCommandCenterMPNowPlayingInfoCenter实现。

MPRemoteCommandCenter利用事件绑定的诀窍贯彻对长距离事件的监听管理,每一种调整事件都打包了三个MPRemoteCommand指标,为具体的风浪增加响应措施:

// CMPlayer.m- handleRemoteControlEvent { MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; // 播放 [commandCenter.playCommand addTarget:self action:@selector]; // 暂停 [commandCenter.pauseCommand addTarget:self action:@selector]; // 上一首 [commandCenter.previousTrackCommand addTarget:self action:@selector]; // 下一首 [commandCenter.nextTrackCommand addTarget:self action:@selector]; // 为耳机的按钮操作添加相关的响应事件 [commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { if (self.timeControlStatus == AVPlayerTimeControlStatusPlaying) { [self pause]; } else { [self play]; } return MPRemoteCommandHandlerStatusSuccess; }];}

MPNowPlayingInfoCenter是贰个方可设置当前播报媒体必要体现消息的对象,那么些新闻会被展现到锁屏分界面、调控主旨等处,以下是粗略用法:

// CMPlayer.m- configNowPlayingInfoCenter { NSMutableDictionary *nowPlayInfo = [[NSMutableDictionary alloc] init]; // 歌曲名称 [nowPlayInfo setObject:self.currentMusicItem.musicName forKey:MPMediaItemPropertyTitle]; // 演唱者 [nowPlayInfo setObject:self.currentMusicItem.musicAuthor forKey:MPMediaItemPropertyArtist]; // 音乐剩余时长 [nowPlayInfo setObject:@(self.currentMusicItem.durationSeconds) forKey:MPMediaItemPropertyPlaybackDuration]; // 音乐当前播放时间 [nowPlayInfo setObject:@(self.currentSeconds) forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nowPlayInfo];}

参谋文献:《Controlling Background 奥迪o》

本文为博主原创文章,项目能源均只供就学请勿商用,转发请附上博文链接!

德姆o个人博客

AVFoundation拍照和摄像摄像

只得说UIImagePickerController确实庞大,可是与MPMoviePlayerController类似,由于它的可观封装性,要实行一些自定义专门的学业就相比复杂了。比方要做出一款类似于美颜相机的摄像分界面就相比难以达成了,此时就能够设想使用AVFoundation来促成。AVFoundation中提供了重重现成的播放器和录音机,可是实际它还会有更为底层的开始和结果能够供开拓者使用。因为AVFoundation中抽了比非常多和底部输入、输出设备打交道的类,依赖这几个类开拓人士面临的不再是包装好的韵律播放器AV奥迪oPlayer、录音机(AV奥迪(Audi)oRecorder)、摄像(包罗音频)播放器AVPlayer,而是输入设备(举个例子迈克风、摄像头)、输出设备(图片、摄像)等。首先明白一下用到AVFoundation做拍照和录像摄像开拓用到的相关类:

AVCaptureSession:媒体(音、录像)捕获会话,肩负把捕获的音摄像数据输出到输出设备中。贰个AVCaptureSession能够有五个输入输出:

AVCaptureDevice:输入设备,包括Mike风、摄像头,通过该对象能够安装物理设备的局地品质(比如相机聚集、白平衡等)。

AVCaptureDeviceInput:设备输入数据管理对象,可以依附AVCaptureDevice创制对应的AVCaptureDeviceInput对象,该对象将会被增添到AVCaptureSession中管理。

AVCaptureOutput:输出数据管理对象,用于收纳各样出口数据,日常选拔相应的子类AVCapture奥迪oDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,该对象将会被增多到AVCaptureSession中管理。注意:前边多少个目的的出口数据都以NSData类型,而AVCaptureFileOutput代表数量以文件情势出口,类似的,AVCcaptureFileOutput也不会平素创造使用,平日会利用其子类:AVCapture奥迪oFileOutput、AVCaptureMovieFileOutput。当把叁个输入或然输出增多到AVCaptureSession之后AVCaptureSession就能够在颇负契合的输入、输出设备之间创立连接(AVCaptionConnection):

AVCaptureVideoPreviewLayer:相机拍照预览图层,是CALayer的子类,使用该对象能够实时查看摄像或录制摄像效果,创立该目的急需钦赐相应的AVCaptureSession对象。

运用AVFoundation拍照和摄像录像的形似步骤如下:

拍照

上面看一下哪些使用AVFoundation完成三个拍照程序,在这么些程序少将实现摄像头预览、切换前后摄像头、闪光灯设置、对焦、拍照保存等效能。应用大约效果如下:

在前后相继中定义会话、输入、输出等相关对象。

@interface ViewController ()
@property (strong,nonatomic) AVCaptureSession *captureSession;//负责输入和输出设备之间的数据传递
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//负责从AVCaptureDevice获得输入数据
@property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片输出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相机拍摄预览图层
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按钮
@property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自动闪光灯按钮
@property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打开闪光灯按钮
@property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//关闭闪光灯按钮
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光标
@end

在调节器视图就要体现时创立并先导化会话、摄像头设备、输入、输出、预览图层,并且增加预览图层到视图中,除了这些之外还做了一部分开端化专门的学业,比方增添手势(点击显示屏举办聚集)、初阶化界面等。

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //初始化会话
    _captureSession=[[AVCaptureSession alloc]init];
    if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//设置分辨率
        _captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
    }
    //获得输入设备
    AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置摄像头
    if (!captureDevice) {
        NSLog(@"取得后置摄像头时出现问题.");
        return;
    }

    NSError *error=nil;
    //根据输入设备初始化设备输入对象,用于获得输入数据
    _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
        return;
    }
    //初始化设备输出对象,用于获得输出数据
    _captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];
    NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
    [_captureStillImageOutput setOutputSettings:outputSettings];//输出设置

    //将设备输入添加到会话中
    if ([_captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
    }

    //将设备输出添加到会话中
    if ([_captureSession canAddOutput:_captureStillImageOutput]) {
        [_captureSession addOutput:_captureStillImageOutput];
    }

    //创建视频预览层,用于实时展示摄像头状态
    _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];

    CALayer *layer=self.viewContainer.layer;
    layer.masksToBounds=YES;

    _captureVideoPreviewLayer.frame=layer.bounds;
    _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    //将视频预览层添加到界面中
    //[layer addSublayer:_captureVideoPreviewLayer];
    [layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];

    [self addNotificationToCaptureDevice:captureDevice];
    [self addGenstureRecognizer];
    [self setFlashModeButtonStatus];
}

在调控器视图显示和视图离开分界面时起步、甘休会话。

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    [self.captureSession startRunning];
}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.captureSession stopRunning];
}

概念闪光灯开闭及活动格局成效,注意无论是设置闪光灯、白平衡还是其余输入设备质量,在装置在此之前必需先锁定配置,修改完后解锁。

/**
 *  改变设备属性的统一操作方法
 *
 *  @param propertyChange 属性改变操作
 */
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
    AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
    NSError *error;
    //注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
    if ([captureDevice lockForConfiguration:&error]) {
        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
    }else{
        NSLog(@"设置设备属性过程发生错误,错误信息:%@",error.localizedDescription);
    }
}

/**
 *  设置闪光灯模式
 *
 *  @param flashMode 闪光灯模式
 */
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFlashModeSupported:flashMode]) {
            [captureDevice setFlashMode:flashMode];
        }
    }];
}

概念切换摄像头功效,切换摄像头的长河正是将本来输入移除,在对话中增添新的输入,但是注意动态修改会话要求首先开启配置,配置成功后交付配置。

#pragma mark 切换前后摄像头
- (IBAction)toggleButtonClick:(UIButton *)sender {
    AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
    AVCaptureDevicePosition currentPosition=[currentDevice position];
    [self removeNotificationFromCaptureDevice:currentDevice];
    AVCaptureDevice *toChangeDevice;
    AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
    if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
        toChangePosition=AVCaptureDevicePositionBack;
    }
    toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
    [self addNotificationToCaptureDevice:toChangeDevice];
    //获得要调整的设备输入对象
    AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];

    //改变会话的配置前一定要先开启配置,配置完成后提交配置改变
    [self.captureSession beginConfiguration];
    //移除原有输入对象
    [self.captureSession removeInput:self.captureDeviceInput];
    //添加新的输入对象
    if ([self.captureSession canAddInput:toChangeDeviceInput]) {
        [self.captureSession addInput:toChangeDeviceInput];
        self.captureDeviceInput=toChangeDeviceInput;
    }
    //提交会话配置
    [self.captureSession commitConfiguration];

    [self setFlashModeButtonStatus];
}

增多点拍手势操作,点按预览视图时开展聚集、白平衡设置。

/**
 *  设置聚焦点
 *
 *  @param point 聚焦点
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
        }
        if ([captureDevice isFocusPointOfInterestSupported]) {
            [captureDevice setFocusPointOfInterest:point];
        }
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
        }
        if ([captureDevice isExposurePointOfInterestSupported]) {
            [captureDevice setExposurePointOfInterest:point];
        }
    }];
}

/**
 *  添加点按手势,点按时聚焦
 */
-(void)addGenstureRecognizer{
    UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
    [self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
    CGPoint point= [tapGesture locationInView:self.viewContainer];
    //将UI坐标转化为摄像头坐标
    CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
    [self setFocusCursorWithPoint:point];
    [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}

概念拍照作用,拍照的长河便是收获连接,从再三再四中赢得捕获的输出数据并做保留操作。

#pragma mark 拍照
- (IBAction)takeButtonClick:(UIButton *)sender {
    //根据设备输出获得连接
    AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
    //根据连接取得设备输出的数据
    [self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        if (imageDataSampleBuffer) {
            NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            UIImage *image=[UIImage imageWithData:imageData];
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
//            ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
//            [assetsLibrary writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:nil];
        }

    }];
}

末段附上完整代码:

//
//  ViewController.m
//  AVFoundationCamera
//
//  Created by Kenshin Cui on 14/04/05.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);

@interface ViewController ()

@property (strong,nonatomic) AVCaptureSession *captureSession;//负责输入和输出设备之间的数据传递
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//负责从AVCaptureDevice获得输入数据
@property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片输出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相机拍摄预览图层
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按钮
@property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自动闪光灯按钮
@property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打开闪光灯按钮
@property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//关闭闪光灯按钮
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光标



@end

@implementation ViewController

#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

}

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //初始化会话
    _captureSession=[[AVCaptureSession alloc]init];
    if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//设置分辨率
        _captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
    }
    //获得输入设备
    AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置摄像头
    if (!captureDevice) {
        NSLog(@"取得后置摄像头时出现问题.");
        return;
    }

    NSError *error=nil;
    //根据输入设备初始化设备输入对象,用于获得输入数据
    _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
        return;
    }
    //初始化设备输出对象,用于获得输出数据
    _captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];
    NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
    [_captureStillImageOutput setOutputSettings:outputSettings];//输出设置

    //将设备输入添加到会话中
    if ([_captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
    }

    //将设备输出添加到会话中
    if ([_captureSession canAddOutput:_captureStillImageOutput]) {
        [_captureSession addOutput:_captureStillImageOutput];
    }

    //创建视频预览层,用于实时展示摄像头状态
    _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];

    CALayer *layer=self.viewContainer.layer;
    layer.masksToBounds=YES;

    _captureVideoPreviewLayer.frame=layer.bounds;
    _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    //将视频预览层添加到界面中
    //[layer addSublayer:_captureVideoPreviewLayer];
    [layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];

    [self addNotificationToCaptureDevice:captureDevice];
    [self addGenstureRecognizer];
    [self setFlashModeButtonStatus];
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    [self.captureSession startRunning];
}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.captureSession stopRunning];
}

-(void)dealloc{
    [self removeNotification];
}
#pragma mark - UI方法
#pragma mark 拍照
- (IBAction)takeButtonClick:(UIButton *)sender {
    //根据设备输出获得连接
    AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
    //根据连接取得设备输出的数据
    [self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        if (imageDataSampleBuffer) {
            NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            UIImage *image=[UIImage imageWithData:imageData];
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
//            ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
//            [assetsLibrary writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:nil];
        }

    }];
}
#pragma mark 切换前后摄像头
- (IBAction)toggleButtonClick:(UIButton *)sender {
    AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
    AVCaptureDevicePosition currentPosition=[currentDevice position];
    [self removeNotificationFromCaptureDevice:currentDevice];
    AVCaptureDevice *toChangeDevice;
    AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
    if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
        toChangePosition=AVCaptureDevicePositionBack;
    }
    toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
    [self addNotificationToCaptureDevice:toChangeDevice];
    //获得要调整的设备输入对象
    AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];

    //改变会话的配置前一定要先开启配置,配置完成后提交配置改变
    [self.captureSession beginConfiguration];
    //移除原有输入对象
    [self.captureSession removeInput:self.captureDeviceInput];
    //添加新的输入对象
    if ([self.captureSession canAddInput:toChangeDeviceInput]) {
        [self.captureSession addInput:toChangeDeviceInput];
        self.captureDeviceInput=toChangeDeviceInput;
    }
    //提交会话配置
    [self.captureSession commitConfiguration];

    [self setFlashModeButtonStatus];
}

#pragma mark 自动闪光灯开启
- (IBAction)flashAutoClick:(UIButton *)sender {
    [self setFlashMode:AVCaptureFlashModeAuto];
    [self setFlashModeButtonStatus];
}
#pragma mark 打开闪光灯
- (IBAction)flashOnClick:(UIButton *)sender {
    [self setFlashMode:AVCaptureFlashModeOn];
    [self setFlashModeButtonStatus];
}
#pragma mark 关闭闪光灯
- (IBAction)flashOffClick:(UIButton *)sender {
    [self setFlashMode:AVCaptureFlashModeOff];
    [self setFlashModeButtonStatus];
}

#pragma mark - 通知
/**
 *  给输入设备添加通知
 */
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
    //注意添加区域改变捕获通知必须首先设置设备允许捕获
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        captureDevice.subjectAreaChangeMonitoringEnabled=YES;
    }];
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //捕获区域发生改变
    [notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
 *  移除所有通知
 */
-(void)removeNotification{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];
}

-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //会话出错
    [notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}

/**
 *  设备连接成功
 *
 *  @param notification 通知对象
 */
-(void)deviceConnected:(NSNotification *)notification{
    NSLog(@"设备已连接...");
}
/**
 *  设备连接断开
 *
 *  @param notification 通知对象
 */
-(void)deviceDisconnected:(NSNotification *)notification{
    NSLog(@"设备已断开.");
}
/**
 *  捕获区域改变
 *
 *  @param notification 通知对象
 */
-(void)areaChange:(NSNotification *)notification{
    NSLog(@"捕获区域改变...");
}

/**
 *  会话出错
 *
 *  @param notification 通知对象
 */
-(void)sessionRuntimeError:(NSNotification *)notification{
    NSLog(@"会话发生错误.");
}

#pragma mark - 私有方法

/**
 *  取得指定位置的摄像头
 *
 *  @param position 摄像头位置
 *
 *  @return 摄像头设备
 */
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
    NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if ([camera position]==position) {
            return camera;
        }
    }
    return nil;
}

/**
 *  改变设备属性的统一操作方法
 *
 *  @param propertyChange 属性改变操作
 */
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
    AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
    NSError *error;
    //注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
    if ([captureDevice lockForConfiguration:&error]) {
        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
    }else{
        NSLog(@"设置设备属性过程发生错误,错误信息:%@",error.localizedDescription);
    }
}

/**
 *  设置闪光灯模式
 *
 *  @param flashMode 闪光灯模式
 */
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFlashModeSupported:flashMode]) {
            [captureDevice setFlashMode:flashMode];
        }
    }];
}
/**
 *  设置聚焦模式
 *
 *  @param focusMode 聚焦模式
 */
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:focusMode];
        }
    }];
}
/**
 *  设置曝光模式
 *
 *  @param exposureMode 曝光模式
 */
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:exposureMode];
        }
    }];
}
/**
 *  设置聚焦点
 *
 *  @param point 聚焦点
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
        }
        if ([captureDevice isFocusPointOfInterestSupported]) {
            [captureDevice setFocusPointOfInterest:point];
        }
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
        }
        if ([captureDevice isExposurePointOfInterestSupported]) {
            [captureDevice setExposurePointOfInterest:point];
        }
    }];
}

/**
 *  添加点按手势,点按时聚焦
 */
-(void)addGenstureRecognizer{
    UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
    [self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
    CGPoint point= [tapGesture locationInView:self.viewContainer];
    //将UI坐标转化为摄像头坐标
    CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
    [self setFocusCursorWithPoint:point];
    [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}

/**
 *  设置闪光灯按钮状态
 */
-(void)setFlashModeButtonStatus{
    AVCaptureDevice *captureDevice=[self.captureDeviceInput device];
    AVCaptureFlashMode flashMode=captureDevice.flashMode;
    if([captureDevice isFlashAvailable]){
        self.flashAutoButton.hidden=NO;
        self.flashOnButton.hidden=NO;
        self.flashOffButton.hidden=NO;
        self.flashAutoButton.enabled=YES;
        self.flashOnButton.enabled=YES;
        self.flashOffButton.enabled=YES;
        switch (flashMode) {
            case AVCaptureFlashModeAuto:
                self.flashAutoButton.enabled=NO;
                break;
            case AVCaptureFlashModeOn:
                self.flashOnButton.enabled=NO;
                break;
            case AVCaptureFlashModeOff:
                self.flashOffButton.enabled=NO;
                break;
            default:
                break;
        }
    }else{
        self.flashAutoButton.hidden=YES;
        self.flashOnButton.hidden=YES;
        self.flashOffButton.hidden=YES;
    }
}

/**
 *  设置聚焦光标位置
 *
 *  @param point 光标位置
 */
-(void)setFocusCursorWithPoint:(CGPoint)point{
    self.focusCursor.center=point;
    self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
    self.focusCursor.alpha=1.0;
    [UIView animateWithDuration:1.0 animations:^{
        self.focusCursor.transform=CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        self.focusCursor.alpha=0;

    }];
}
@end

运营效果:

摄像摄像

实在有了前面的录制应用之后要在此基础上做摄像摄像作用并不复杂,程序只供给做如下修改:

对待拍照程序,程序的退换保护正是以上三点。当然为了让程序尤其完美在上边包车型客车录制录像造进程序中投入了显示器旋转录像、自动布局和后台保存职务等细节。上面是修改后的次第:

//
//  ViewController.m
//  AVFoundationCamera
//
//  Created by Kenshin Cui on 14/04/05.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  视频录制

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);

@interface ViewController ()<AVCaptureFileOutputRecordingDelegate>//视频文件输出代理

@property (strong,nonatomic) AVCaptureSession *captureSession;//负责输入和输出设备之间的数据传递
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//负责从AVCaptureDevice获得输入数据
@property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;//视频输出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相机拍摄预览图层
@property (assign,nonatomic) BOOL enableRotation;//是否允许旋转(注意在视频录制过程中禁止屏幕旋转)
@property (assign,nonatomic) CGRect *lastBounds;//旋转的前大小
@property (assign,nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;//后台任务标识
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按钮
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光标


@end

@implementation ViewController

#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];
}

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //初始化会话
    _captureSession=[[AVCaptureSession alloc]init];
    if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//设置分辨率
        _captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
    }
    //获得输入设备
    AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置摄像头
    if (!captureDevice) {
        NSLog(@"取得后置摄像头时出现问题.");
        return;
    }
    //添加一个音频输入设备
    AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];


    NSError *error=nil;
    //根据输入设备初始化设备输入对象,用于获得输入数据
    _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
        return;
    }
    AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
    if (error) {
        NSLog(@"取得设备输入对象时出错,错误原因:%@",error.localizedDescription);
        return;
    }
    //初始化设备输出对象,用于获得输出数据
    _captureMovieFileOutput=[[AVCaptureMovieFileOutput alloc]init];

    //将设备输入添加到会话中
    if ([_captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
        [_captureSession addInput:audioCaptureDeviceInput];
        AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
        if ([captureConnection isVideoStabilizationSupported ]) {
            captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
        }
    }

    //将设备输出添加到会话中
    if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
        [_captureSession addOutput:_captureMovieFileOutput];
    }

    //创建视频预览层,用于实时展示摄像头状态
    _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];

    CALayer *layer=self.viewContainer.layer;
    layer.masksToBounds=YES;

    _captureVideoPreviewLayer.frame=layer.bounds;
    _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    //将视频预览层添加到界面中
    //[layer addSublayer:_captureVideoPreviewLayer];
    [layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];

    _enableRotation=YES;
    [self addNotificationToCaptureDevice:captureDevice];
    [self addGenstureRecognizer];
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    [self.captureSession startRunning];
}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.captureSession stopRunning];
}

-(BOOL)shouldAutorotate{
    return self.enableRotation;
}

////屏幕旋转时调整视频预览图层的方向
//-(void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
//    [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
////    NSLog(@"%i,%i",newCollection.verticalSizeClass,newCollection.horizontalSizeClass);
//    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
//    NSLog(@"%i",orientation);
//    AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
//    captureConnection.videoOrientation=orientation;
//    
//}
//屏幕旋转时调整视频预览图层的方向
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
    captureConnection.videoOrientation=(AVCaptureVideoOrientation)toInterfaceOrientation;
}
//旋转后重新设置大小
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    _captureVideoPreviewLayer.frame=self.viewContainer.bounds;
}

-(void)dealloc{
    [self removeNotification];
}
#pragma mark - UI方法
#pragma mark 视频录制
- (IBAction)takeButtonClick:(UIButton *)sender {
    //根据设备输出获得连接
    AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    //根据连接取得设备输出的数据
    if (![self.captureMovieFileOutput isRecording]) {
        self.enableRotation=NO;
        //如果支持多任务则则开始多任务
        if ([[UIDevice currentDevice] isMultitaskingSupported]) {
            self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
        }
        //预览图层和视频方向保持一致
        captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;
        NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
        NSLog(@"save path is :%@",outputFielPath);
        NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
        [self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
    }
    else{
        [self.captureMovieFileOutput stopRecording];//停止录制
    }
}
#pragma mark 切换前后摄像头
- (IBAction)toggleButtonClick:(UIButton *)sender {
    AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
    AVCaptureDevicePosition currentPosition=[currentDevice position];
    [self removeNotificationFromCaptureDevice:currentDevice];
    AVCaptureDevice *toChangeDevice;
    AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
    if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
        toChangePosition=AVCaptureDevicePositionBack;
    }
    toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
    [self addNotificationToCaptureDevice:toChangeDevice];
    //获得要调整的设备输入对象
    AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];

    //改变会话的配置前一定要先开启配置,配置完成后提交配置改变
    [self.captureSession beginConfiguration];
    //移除原有输入对象
    [self.captureSession removeInput:self.captureDeviceInput];
    //添加新的输入对象
    if ([self.captureSession canAddInput:toChangeDeviceInput]) {
        [self.captureSession addInput:toChangeDeviceInput];
        self.captureDeviceInput=toChangeDeviceInput;
    }
    //提交会话配置
    [self.captureSession commitConfiguration];

}

#pragma mark - 视频输出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
    NSLog(@"开始录制...");
}
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
    NSLog(@"视频录制完成.");
    //视频录入完成之后在后台将视频存储到相簿
    self.enableRotation=YES;
    UIBackgroundTaskIdentifier lastBackgroundTaskIdentifier=self.backgroundTaskIdentifier;
    self.backgroundTaskIdentifier=UIBackgroundTaskInvalid;
    ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
    [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
        if (error) {
            NSLog(@"保存视频到相簿过程中发生错误,错误信息:%@",error.localizedDescription);
        }
        if (lastBackgroundTaskIdentifier!=UIBackgroundTaskInvalid) {
            [[UIApplication sharedApplication] endBackgroundTask:lastBackgroundTaskIdentifier];
        }
        NSLog(@"成功保存视频到相簿.");
    }];

}

#pragma mark - 通知
/**
 *  给输入设备添加通知
 */
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
    //注意添加区域改变捕获通知必须首先设置设备允许捕获
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        captureDevice.subjectAreaChangeMonitoringEnabled=YES;
    }];
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //捕获区域发生改变
    [notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
 *  移除所有通知
 */
-(void)removeNotification{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];
}

-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //会话出错
    [notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}

/**
 *  设备连接成功
 *
 *  @param notification 通知对象
 */
-(void)deviceConnected:(NSNotification *)notification{
    NSLog(@"设备已连接...");
}
/**
 *  设备连接断开
 *
 *  @param notification 通知对象
 */
-(void)deviceDisconnected:(NSNotification *)notification{
    NSLog(@"设备已断开.");
}
/**
 *  捕获区域改变
 *
 *  @param notification 通知对象
 */
-(void)areaChange:(NSNotification *)notification{
    NSLog(@"捕获区域改变...");
}

/**
 *  会话出错
 *
 *  @param notification 通知对象
 */
-(void)sessionRuntimeError:(NSNotification *)notification{
    NSLog(@"会话发生错误.");
}

#pragma mark - 私有方法

/**
 *  取得指定位置的摄像头
 *
 *  @param position 摄像头位置
 *
 *  @return 摄像头设备
 */
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
    NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if ([camera position]==position) {
            return camera;
        }
    }
    return nil;
}

/**
 *  改变设备属性的统一操作方法
 *
 *  @param propertyChange 属性改变操作
 */
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
    AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
    NSError *error;
    //注意改变设备属性前一定要首先调用lockForConfiguration:调用完之后使用unlockForConfiguration方法解锁
    if ([captureDevice lockForConfiguration:&error]) {
        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
    }else{
        NSLog(@"设置设备属性过程发生错误,错误信息:%@",error.localizedDescription);
    }
}

/**
 *  设置闪光灯模式
 *
 *  @param flashMode 闪光灯模式
 */
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFlashModeSupported:flashMode]) {
            [captureDevice setFlashMode:flashMode];
        }
    }];
}
/**
 *  设置聚焦模式
 *
 *  @param focusMode 聚焦模式
 */
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:focusMode];
        }
    }];
}
/**
 *  设置曝光模式
 *
 *  @param exposureMode 曝光模式
 */
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:exposureMode];
        }
    }];
}
/**
 *  设置聚焦点
 *
 *  @param point 聚焦点
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
        }
        if ([captureDevice isFocusPointOfInterestSupported]) {
            [captureDevice setFocusPointOfInterest:point];
        }
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
        }
        if ([captureDevice isExposurePointOfInterestSupported]) {
            [captureDevice setExposurePointOfInterest:point];
        }
    }];
}

/**
 *  添加点按手势,点按时聚焦
 */
-(void)addGenstureRecognizer{
    UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
    [self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
    CGPoint point= [tapGesture locationInView:self.viewContainer];
    //将UI坐标转化为摄像头坐标
    CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
    [self setFocusCursorWithPoint:point];
    [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}

/**
 *  设置聚焦光标位置
 *
 *  @param point 光标位置
 */
-(void)setFocusCursorWithPoint:(CGPoint)point{
    self.focusCursor.center=point;
    self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
    self.focusCursor.alpha=1.0;
    [UIView animateWithDuration:1.0 animations:^{
        self.focusCursor.transform=CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        self.focusCursor.alpha=0;

    }];
}
@end

运行效果:

总结

前边用了汪洋的篇幅介绍了iOS中的音、录像播放和摄像,有些地点用到了包装好的播放器、录音机直接动用,某些是一直调用系统服务和睦组织封装,正如本篇开始所言,iOS对于多媒体扶助非凡灵活和周密,那么开放历程中怎么着抉择吧,上边就以三个报表轻松相比一下依次开荒技能的利弊。

提示:从本文及以后的文章中可能慢慢使用storyboard或xib,原因如下:1.苹果官方目前主推storyboard;2.后面的文章中做屏幕适配牵扯到很多内容都是storyboard中进行(尽管纯代码也可以实现,但是纯代码对autolayout支持不太好)3.通过前面的一系列文章大家对于纯代码编程应该已经有一定的积累了(纯代码确实可以另初学者更加了解程序运行原理)。

--iOS多媒体 大概浏览 随着移动网络的向上,最近的无绳电话机早就不是打电...

本文由365bet体育在线官网发布于网络工程,转载请注明出处:iOS开荒类别,高仿天涯论坛云音乐播放器

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。