006-音视频
一、音频
音频播放分为音效播放和音乐播放两大类。前者主要是指的是一些短的音频播放,通常作为点缀音频。对于这一类音频不需要进行进度、循环等控制。后者是一些较长的音频,通常是主音频,对于这些音频的播放通常需要进行精确的控制。在iOS中播放两类音频分别使用AudioToolbox.framework和AVFoundation.framework来完成音效和音乐播放
1.音效(System Sound Service)
1)说明
AudioToolbox.framework是一套基于C语言的框架,使用它来播放音效其本质是将段音频注册到系统声音服务(System Sound Service)
System Sound Service是一种简单、底层的声音播放服务,但是它本身也存在着一些限制:
a.音频播放时长不超过30秒
b.数据必须是PCM或者IMA4格式
c.音频文件必须打包成.caf、.aif、.wav中的一种(注意这是官方文档的说法,实际测试发现一些.mp3格式也可以播放)
d.只可以播放本地文件(boundle中的音效文件)
2)使用System Sound Service播放音效的步骤如下:
a.调用AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID *outSystemSoundID)函数获得系统声音ID
b.如果需要监听播放完成操作,则使用AudioServicesAddSystemSoundCompletion(SystemSoundID inSystemSoundID,
CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void *inClientData)方法注册回调函数
c.调用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)或者AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID)方法播放音效(后者带有震动效果)
3)程序实例
1 // SoundController.m文件 2 #import "SoundController.h" 3 #import <AudioToolbox/AudioToolbox.h> // 播放音效需要导入的框架 4 5 @interface SoundController () 6 7 @end 8 9 @implementation SoundController 10 11 - (void)viewDidLoad 12 { 13 [super viewDidLoad]; 14 15 [self initUI]; // 界面 16 } 17 18 #pragma mark - 界面 19 - (void)initUI 20 { 21 self.view.backgroundColor = [UIColor whiteColor]; 22 23 // 播放音效按钮 24 UIButton *playBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 25 playBtn.bounds = CGRectMake(0, 0, 150, 40); 26 playBtn.center = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0); 27 [playBtn setTitle:@"播放音效" forState:UIControlStateNormal]; 28 playBtn.tintColor = [UIColor whiteColor]; 29 playBtn.backgroundColor = [UIColor grayColor]; 30 playBtn.titleLabel.font = [UIFont boldSystemFontOfSize:20]; 31 [playBtn addTarget:self action:@selector(playBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 32 [self.view addSubview:playBtn]; 33 } 34 35 #pragma mark - 播放音效点击事件 36 - (void)playBtnClick:(UIButton *)playBtn 37 { 38 [self playSoundEffectWithFileName:@"yinxiao.caf"]; 39 } 40 41 #pragma mark - 播放音效文件 42 // 参数:fileName 文件名 43 - (void)playSoundEffectWithFileName:(NSString *)fileName 44 { 45 // 获得音效文件路径(必须是本地文件) 46 NSString *audioFile = [[NSBundle mainBundle] pathForResource:fileName ofType:nil]; 47 // 根据音效文件路径获得音效文件URL 48 NSURL *fileURL = [NSURL fileURLWithPath:audioFile]; 49 50 // 获得系统声音ID 51 SystemSoundID soundID = 0; 52 53 // 此函数会将音效文件加入到系统音频服务中并返回一个长整形ID 54 // 参数1:音频文件URL 55 // 参数2:声音ID 56 AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(fileURL), &soundID); 57 58 // 如果需要在播放完成之后执行某些操作,可以调用下面方法注册一个播放完成的回调函数 59 AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallBack, NULL); 60 61 // 播放音效文件 62 AudioServicesPlaySystemSound(soundID); // 不带振动效果 63 AudioServicesPlayAlertSound(soundID); // 带有振动效果 64 } 65 66 #pragma mark - 播放完成回调函数 67 // 参数1:soundID 系统声音ID 68 // 参数2:clientData 回调时传递的数据 69 void soundCompleteCallBack(SystemSoundID soundID,void * clientData){ 70 NSLog(@"播放完成..."); 71 } 72 73 @end
2.音乐(AVAudioPlayer)
1)说明
如果播放较大的音频文件或者要对音频文件有精准的控制,则System Sound Service可能就很难满足实际需求了,通常这种情况会选择使用AVFoundation.framework中的AVAudioPlayer来实现。AVAudioPlayer可以看成一个播放器,它支持多种音频格式,而且能够进行进度、音量、播放速度等控制。另外与System Sound Service一样,AVFoundation.framework中的AVAudioPlayer也只能是播放本地音乐(boundle里面的音乐文件)
2)常见属性和方法
| 属性 | 说明 |
| @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方法更新测量值 |
| @property(nonatomic, copy) NSArray *channelAssignments | 获得或设置播放声道 |
| 对象方法 | 说明 |
| - (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方法 |
| 代理方法 | 说明 |
| - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag | 音频播放完成 |
| - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error | 音频解码发生错误 |
3)AVAudioPlayer的使用步骤
a.初始化AVAudioPlayer对象,此时通常指定本地文件路径
b.设置播放器属性,例如重复次数、音量大小等
c.调用play方法播放
4)代码示例
当然由于AVAudioPlayer一次只能播放一个音频文件,所有上一曲、下一曲其实可以通过创建多个播放器对象来完成,这里暂不实现。播放进度的实现主要依靠一个定时器实时计算当前播放时长和音频总时长的比例
1 // MusicController.m文件 2 #import "MusicController.h" 3 #import <AVFoundation/AVFoundation.h> // 需要导入的框架 4 5 @interface MusicController ()<AVAudioPlayerDelegate> 6 7 #pragma mark - 音乐播放器 8 @property (nonatomic, strong) AVAudioPlayer *audioPlayer; 9 #pragma mark - 播放进度 10 @property (nonatomic, strong) UIProgressView *progressView; 11 #pragma mark - 播放/暂停 12 @property (nonatomic, strong) UIButton *playOrPauseBtn; 13 #pragma mark - 定时器 14 @property (nonatomic, strong) NSTimer *timer; 15 16 @end 17 18 @implementation MusicController 19 20 #pragma mark - 懒加载 21 // 1.audioPlayer 22 - (AVAudioPlayer *)audioPlayer 23 { 24 if (!_audioPlayer) { 25 // 本地音乐文件 26 NSString *urlStr=[[NSBundle mainBundle]pathForResource:@"演员.mp3" ofType:nil]; 27 NSURL *url = [NSURL fileURLWithPath:urlStr]; 28 NSError *error = nil; 29 // 初始化播放器 30 // 注意:这里的URL只能是文件路径,也就是只能是本地音乐文件;不支持HTTP URL,也就是网络歌曲 31 _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error]; 32 // 设置播放器属性 33 _audioPlayer.numberOfLoops = 0; // 设置为0不循环 34 _audioPlayer.delegate = self; 35 [_audioPlayer prepareToPlay]; // 加载音频文件到缓存 36 if (error) { 37 NSLog(@"初始化播放器过程发生错误,错误信息:%@", error.localizedDescription); 38 return nil; 39 } 40 } 41 return _audioPlayer; 42 } 43 // 2.timer 44 - (NSTimer *)timer 45 { 46 if (!_timer) { 47 _timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:YES]; 48 // 加入到运行循环 49 [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 50 } 51 return _timer; 52 } 53 54 - (void)viewDidLoad 55 { 56 [super viewDidLoad]; 57 58 [self initUI]; // 界面 59 } 60 61 #pragma mark - 界面 62 - (void)initUI 63 { 64 // playOrPauseBtn 65 _playOrPauseBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 66 _playOrPauseBtn.frame = CGRectMake(100, 100, self.view.bounds.size.width - 100 * 2, 60); 67 [_playOrPauseBtn setTitle:@"PLAY" forState:UIControlStateNormal]; 68 [_playOrPauseBtn setTitle:@"PAUSE" forState:UIControlStateSelected]; 69 _playOrPauseBtn.tintColor = [UIColor redColor]; 70 [_playOrPauseBtn addTarget:self action:@selector(playOrPauseBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 71 [self.view addSubview:_playOrPauseBtn]; 72 73 // progress 74 _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(30, CGRectGetMaxY(_playOrPauseBtn.frame) + 10, self.view.bounds.size.width - 30 * 2, 5)]; 75 _progressView.progress = 0; 76 [self.view addSubview:_progressView]; 77 } 78 79 #pragma mark - 播放/暂停点击事件 80 - (void)playOrPauseBtnClick:(UIButton *)sender 81 { 82 sender.selected = !sender.selected; 83 84 if (sender.isSelected) { 85 // 播放 86 [self.audioPlayer play]; 87 // 恢复定时器 88 self.timer.fireDate=[NSDate distantPast]; 89 90 } else { 91 [self.audioPlayer pause]; 92 // 暂停定时器 93 // 注意不能调用invalidate方法,此方法会取消,之后无法恢复 94 self.timer.fireDate = [NSDate distantFuture]; 95 } 96 } 97 98 #pragma mark - 定时器事件 99 - (void)updateProgress 100 { 101 float progressValue = self.audioPlayer.currentTime / self.audioPlayer.duration; 102 // 进度设置 103 [self.progressView setProgress:progressValue animated:YES]; 104 } 105 106 #pragma mark - 代理方法<AVAudioPlayerDelegate> 107 // 播放完毕调用 108 - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 109 { 110 NSLog(@"音乐播放完成..."); 111 } 112 113 @end
3.音频会话(AVAudioPlayer & AVAudioSession)
如果需要使播放器即使退出到后台也是可以播放的,即支持后台播放功能,我们还需要做以下几件事情:
1)设置后台运行模式:在plist列表中添加字段“Required background modes”,并且设置item 0 = App plays audio or streams audio/video using AirPlay(其实可以直接通过Xcode在Project Targets-Capabilities-Background Modes中设置)

这一步的目的是不让应用程序在后台被挂起,而是处于运行状态
2)设置AVAudioSession的类型为AVAudioSessionCategoryPlayback并且调用setActive::方法启动会话
1 AVAudioSession *audioSession=[AVAudioSession sharedInstance]; 2 [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; 3 [audioSession setActive:YES error:nil];
a.在iOS中每一个应用都有一个音频会话,这个会话就通过AVAudioSession来表示。
b.AVAudioSession属于AVFoundation框架,并且是单例模式设计,通过sharedInstance进行访问。
c.在使用Apple设备时大家会发现有些应用只要打开其他音频播放就会终止,而有些应用却可以和其他应用同时播放,在多种音频环境中如何去控制播放的方式就是通过音频回话来完成的。
音频会话的几种会话模式:

注意:在设置完音频会话类型之后需要调用setActive::方法将会话激活才能起作用。如果设备上已经存在一个正在播放的音频会话,打开我们的应用之后设置了后台播放的会话类型,此时原本正在播放的音频会因我们应用的音频播放而停止,此时,如果希望我们的应用程序在音频播放结束之后,能够继续播放原来的音频,那么我们应该在我们自己的 应用中调用setActive::方法关闭会话。
3)如果需要让应用退出后台支持耳机控制,可以添加远程控制事件(这一步不是后台播放所必须)
代码实例(支持后台播放、支持拔出耳机暂停音乐)
1 // MusicPlayerController.m文件 2 #import "MusicPlayerController.h" 3 #import <AVFoundation/AVFoundation.h> // 需要导入的框架 4 5 @interface MusicPlayerController ()<AVAudioPlayerDelegate> 6 7 #pragma mark - 音乐播放器 8 @property (nonatomic, strong) AVAudioPlayer *audioPlayer; 9 #pragma mark - 播放进度 10 @property (nonatomic, strong) UIProgressView *progressView; 11 #pragma mark - 播放/暂停 12 @property (nonatomic, strong) UIButton *playOrPauseBtn; 13 #pragma mark - 定时器 14 @property (nonatomic, strong) NSTimer *timer; 15 16 @end 17 18 @implementation MusicPlayerController 19 20 #pragma mark - 懒加载 21 // 1.audioPlayer 22 - (AVAudioPlayer *)audioPlayer 23 { 24 if (!_audioPlayer) { 25 // 本地音乐文件 26 NSString *urlStr=[[NSBundle mainBundle]pathForResource:@"演员.mp3" ofType:nil]; 27 NSURL *url = [NSURL fileURLWithPath:urlStr]; 28 NSError *error = nil; 29 // 初始化播放器 30 // 注意:这里的URL只能是文件路径,也就是只能是本地音乐文件;不支持HTTP URL,也就是网络歌曲 31 _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error]; 32 // 设置播放器属性 33 _audioPlayer.numberOfLoops = 0; // 设置为0不循环 34 _audioPlayer.delegate = self; 35 [_audioPlayer prepareToPlay]; // 加载音频文件到缓存 36 if (error) { 37 NSLog(@"初始化播放器过程发生错误,错误信息:%@", error.localizedDescription); 38 return nil; 39 } 40 41 // 设置后台播放模式 42 AVAudioSession *audioSession = [AVAudioSession sharedInstance]; 43 [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; 44 [audioSession setActive:YES error:nil]; 45 // 添加通知,拔出耳机后暂停播放 46 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil]; 47 } 48 return _audioPlayer; 49 } 50 // 2.timer 51 - (NSTimer *)timer 52 { 53 if (!_timer) { 54 _timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:YES]; 55 // 加入到运行循环 56 [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 57 } 58 return _timer; 59 } 60 61 - (void)viewDidLoad 62 { 63 [super viewDidLoad]; 64 65 [self initUI]; // 界面 66 } 67 68 #pragma mark - 显示当前视图控制器时,注册远程事件 69 - (void)viewWillAppear:(BOOL)animated 70 { 71 [super viewWillAppear:animated]; 72 // 开启远程控制 73 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; 74 // 作为第一响应者 75 // [self becomeFirstResponder]; 76 } 77 78 #pragma mark - 当前视图消失时取消远程控制 79 - (void)viewWillDisappear:(BOOL)animated 80 { 81 [super viewWillDisappear:animated]; 82 // 结束远程控制 83 [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; 84 // 失去第一响应 85 // [self resignFirstResponder]; 86 } 87 88 #pragma mark - 界面 89 - (void)initUI 90 { 91 self.view.backgroundColor = [UIColor whiteColor]; 92 93 // playOrPauseBtn 94 _playOrPauseBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 95 _playOrPauseBtn.frame = CGRectMake(100, 100, self.view.bounds.size.width - 100 * 2, 60); 96 [_playOrPauseBtn setTitle:@"PLAY" forState:UIControlStateNormal]; 97 [_playOrPauseBtn setTitle:@"PAUSE" forState:UIControlStateSelected]; 98 _playOrPauseBtn.tintColor = [UIColor redColor]; 99 [_playOrPauseBtn addTarget:self action:@selector(playOrPauseBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 100 [self.view addSubview:_playOrPauseBtn]; 101 102 // progress 103 _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(30, CGRectGetMaxY(_playOrPauseBtn.frame) + 10, self.view.bounds.size.width - 30 * 2, 5)]; 104 _progressView.progress = 0; 105 [self.view addSubview:_progressView]; 106 } 107 108 #pragma mark - 播放/暂停点击事件 109 - (void)playOrPauseBtnClick:(UIButton *)sender 110 { 111 sender.selected = !sender.selected; 112 113 if (sender.isSelected) { 114 // 播放 115 [self play]; 116 117 } else { 118 // 暂停 119 [self pause]; 120 } 121 } 122 123 #pragma mark - 播放 124 - (void)play 125 { 126 // 播放 127 [self.audioPlayer play]; 128 // 恢复定时器 129 self.timer.fireDate=[NSDate distantPast]; 130 } 131 132 #pragma mark - 暂停 133 - (void)pause 134 { 135 // 暂停 136 [self.audioPlayer pause]; 137 // 暂停定时器 138 // 注意不能调用invalidate方法,此方法会取消,之后无法恢复 139 self.timer.fireDate = [NSDate distantFuture]; 140 } 141 142 #pragma mark - 定时器事件 143 - (void)updateProgress 144 { 145 float progressValue = self.audioPlayer.currentTime / self.audioPlayer.duration; 146 // 进度设置 147 [self.progressView setProgress:progressValue animated:YES]; 148 } 149 150 #pragma mark - 代理方法<AVAudioPlayerDelegate> 151 // 播放完毕调用 152 - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 153 { 154 NSLog(@"音乐播放完成..."); 155 // 根据实际情况播放完成可以将会话关闭,其他音频应用继续播放 156 [[AVAudioSession sharedInstance] setActive:NO error:nil]; 157 } 158 159 #pragma mark - 通知事件 160 // 一旦输出改变则执行此方法 161 - (void)routeChange:(NSNotification *)notification 162 { 163 NSDictionary *dict = notification.userInfo; 164 int changeReason = [dict[AVAudioSessionRouteChangeReasonKey] intValue]; 165 // 等于AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示旧输出不可用 166 if (changeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { 167 AVAudioSessionRouteDescription *routeDescription = dict[AVAudioSessionRouteChangePreviousRouteKey]; 168 AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject]; 169 // 原设备为耳机则暂停 170 if ([portDescription.portType isEqualToString:@"Headphones"]) { 171 // 点击播放/暂停按键 172 [self playOrPauseBtnClick:_playOrPauseBtn]; 173 } 174 } 175 } 176 177 #pragma mark - 销毁方法 178 - (void)dealloc 179 { 180 [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; 181 } 182 183 @end
4.播放音乐库中的音乐(MPMusicPlayerController)
1)说明
MPMusicPlayerController是属于MediaPlayer.frameowork框架
2)属性和方法
| 属性 | 说明 |
| @property (nonatomic, readonly) MPMusicPlaybackState playbackState |
播放器状态,枚举类型: MPMusicPlaybackStatePlaying:正在播放 |
| @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 | 准备好播放 |
3)MPMusicPlayerController
a.MPMusicPlayerController有两种播放器:
applicationMusicPlayer:应用退出后,音乐播放会自动停止
systemMusicPlayer:应用停止后不会退出播放状态
b.MPMusicPlayerController加载音乐不同于AVAudioPlayer
AVAudioPlayer是通过一个文件路径来加载
MPMusicPlayerController是需要一个播放队列来加载
c.MPMusicPlayerController提供了两个方法来加载播放队列
- (void)setQueueWithQuery:(MPMediaQuery *)query;
- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection;
正是因为MPMusicPlayerController的播放音频来源是一个队列,因此它支持上一曲、下一曲等操作
d.MPMusicPlayerController如何获取MPMediaQueue或者MPMediaItemCollection
MPMediaQueue对象有以下方法用来获取媒体队列:
+ (MPMediaQuery *)albumsQuery;
+ (MPMediaQuery *)artistsQuery;
+ (MPMediaQuery *)songsQuery;
+ (MPMediaQuery *)playlistsQuery;
+ (MPMediaQuery *)podcastsQuery;
+ (MPMediaQuery *)audiobooksQuery;
+ (MPMediaQuery *)compilationsQuery;
+ (MPMediaQuery *)composersQuery;
+ (MPMediaQuery *)genresQuery;
e.设置音乐来源
MPMediaQueue:- (void)setQueueWithQuery:(MPMediaQuery *)query;
MPMediaItemCollection:- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection;
f.MPMediaPickerController
如果希望用户自己来选择将要播放的音乐,这个时候可以使用MPMediaPickerController
MPMediaPickerController是一个视图控制器,类似于UIImagePickerController
选择完播放来源后可以在其代理方法中获得MPMediaItemCollection对象
g.MPMediaItem(媒体文件对象)
无论通过哪一种方式获得MPMusicPlayerController的媒体源,我们都可以通过MPMediaItem对象获得媒体文件的信息
一个MPMediaItem对象对应着一个媒体文件,通过它可以访问媒体标题、专辑名称、专辑封面、音乐时长等等
无论是MPMediaQueue,还是MPMediaItemCollection都有一个items属性,这是一个装有MPMediaItem的数组,通过这个属性可以获得MPMediaItem对象
4)代码实例
1 // MusicQueueController.m文件 2 #import "MusicQueueController.h" 3 #import <MediaPlayer/MediaPlayer.h> // 导入框架 4 5 @interface MusicQueueController ()<MPMediaPickerControllerDelegate> 6 7 // 媒体选择控制器 8 @property (nonatomic, strong) MPMediaPickerController *mediaPicker; 9 // 音乐播放器 10 @property (nonatomic, strong) MPMusicPlayerController *musicPlayer; 11 12 #pragma mark - 界面上的按钮 13 @property (nonatomic, strong) UIButton *selectBtn; // 选择音乐来源按钮 14 @property (nonatomic, strong) UIButton *playBtn; // 播放按钮 15 @property (nonatomic, strong) UIButton *pauseBtn; // 暂停按钮 16 @property (nonatomic, strong) UIButton *stopBtn; // 停止按钮 17 @property (nonatomic, strong) UIButton *nextBtn; // 下一曲按钮 18 @property (nonatomic, strong) UIButton *prevBtn; // 上一曲按钮 19 20 @end 21 22 @implementation MusicQueueController 23 24 #pragma mark - 懒加载 25 // 1.mediaPicker 26 - (MPMediaPickerController *)mediaPicker 27 { 28 if (!_mediaPicker) { 29 // 初始化媒体选择器 30 // 这里设置媒体类型为音乐,其实这里也可以选择视频、广播等 31 _mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic]; 32 // 允许多选 33 _mediaPicker.allowsPickingMultipleItems = YES; 34 // 显示icloud选项 35 // _mediaPicker.showsCloudItems = YES; 36 _mediaPicker.prompt = @"请选择要播放的音乐"; 37 // 设置选择器代理 38 _mediaPicker.delegate = self; 39 } 40 return _mediaPicker; 41 } 42 // 2.musicPlayer 43 - (MPMusicPlayerController *)musicPlayer 44 { 45 if (!_musicPlayer) { 46 // systemMusicPlayer:应用停止后不会退出播放状态 47 _musicPlayer = [MPMusicPlayerController systemMusicPlayer]; 48 // 开启通知(否则监控不到MPMusicPlayerController的通知) 49 [_musicPlayer beginGeneratingPlaybackNotifications]; 50 // 添加通知 51 [self addNotification]; 52 // 如果不使用MPMediaPickerController可以使用如下方法获得音乐库媒体队列 53 // [_musicPlayer setQueueWithItemCollection:[self getLocalMediaItemCollection]]; 54 } 55 return _musicPlayer; 56 } 57 58 - (void)viewDidLoad 59 { 60 [super viewDidLoad]; 61 62 [self initUI]; // 界面 63 } 64 65 #pragma mark - 界面 66 - (void)initUI 67 { 68 self.view.backgroundColor = [UIColor whiteColor]; 69 70 // 选择音乐来源按钮 71 CGFloat selectBtnX = 40; 72 CGFloat selectBtnY = 80; 73 CGFloat selectBtnW = self.view.bounds.size.width - 40 * 2; 74 CGFloat selectBtnH = 40; 75 _selectBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 76 _selectBtn.frame = CGRectMake(selectBtnX, selectBtnY, selectBtnW, selectBtnH); 77 [_selectBtn setTitle:@"选择音乐来源" forState:UIControlStateNormal]; 78 _selectBtn.tintColor = [UIColor whiteColor]; 79 _selectBtn.backgroundColor = [UIColor blackColor]; 80 _selectBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 81 [_selectBtn addTarget:self action:@selector(selectBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 82 [self.view addSubview:_selectBtn]; 83 84 // 播放按钮 85 CGFloat playBtnX = _selectBtn.frame.origin.x; 86 CGFloat playBtnY = CGRectGetMaxY(_selectBtn.frame) + 10; 87 CGFloat playBtnW = _selectBtn.bounds.size.width; 88 CGFloat playBtnH = _selectBtn.bounds.size.height; 89 _playBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 90 _playBtn.frame = CGRectMake(playBtnX, playBtnY, playBtnW, playBtnH); 91 [_playBtn setTitle:@"播放" forState:UIControlStateNormal]; 92 _playBtn.tintColor = [UIColor whiteColor]; 93 _playBtn.backgroundColor = [UIColor blackColor]; 94 _playBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 95 [_playBtn addTarget:self action:@selector(playBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 96 [self.view addSubview:_playBtn]; 97 98 // 暂停按钮 99 CGFloat pauseBtnX = _selectBtn.frame.origin.x; 100 CGFloat pauseBtnY = CGRectGetMaxY(_playBtn.frame) + 10; 101 CGFloat pauseBtnW = _selectBtn.bounds.size.width; 102 CGFloat pauseBtnH = _selectBtn.bounds.size.height; 103 _pauseBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 104 _pauseBtn.frame = CGRectMake(pauseBtnX, pauseBtnY, pauseBtnW, pauseBtnH); 105 [_pauseBtn setTitle:@"暂停" forState:UIControlStateNormal]; 106 _pauseBtn.tintColor = [UIColor whiteColor]; 107 _pauseBtn.backgroundColor = [UIColor blackColor]; 108 _pauseBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 109 [_pauseBtn addTarget:self action:@selector(pauseBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 110 [self.view addSubview:_pauseBtn]; 111 112 // 停止按钮 113 CGFloat stopBtnX = _selectBtn.frame.origin.x; 114 CGFloat stopBtnY = CGRectGetMaxY(_pauseBtn.frame) + 10; 115 CGFloat stopBtnW = _selectBtn.bounds.size.width; 116 CGFloat stopBtnH = _selectBtn.bounds.size.height; 117 _stopBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 118 _stopBtn.frame = CGRectMake(stopBtnX, stopBtnY, stopBtnW, stopBtnH); 119 [_stopBtn setTitle:@"停止" forState:UIControlStateNormal]; 120 _stopBtn.tintColor = [UIColor whiteColor]; 121 _stopBtn.backgroundColor = [UIColor blackColor]; 122 _stopBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 123 [_stopBtn addTarget:self action:@selector(stopBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 124 [self.view addSubview:_stopBtn]; 125 126 // 下一曲按钮 127 CGFloat nextBtnX = _selectBtn.frame.origin.x; 128 CGFloat nextBtnY = CGRectGetMaxY(_stopBtn.frame) + 10; 129 CGFloat nextBtnW = _selectBtn.bounds.size.width; 130 CGFloat nextBtnH = _selectBtn.bounds.size.height; 131 _nextBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 132 _nextBtn.frame = CGRectMake(nextBtnX, nextBtnY, nextBtnW, nextBtnH); 133 [_nextBtn setTitle:@"下一曲" forState:UIControlStateNormal]; 134 _nextBtn.tintColor = [UIColor whiteColor]; 135 _nextBtn.backgroundColor = [UIColor blackColor]; 136 _nextBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 137 [_nextBtn addTarget:self action:@selector(nextBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 138 [self.view addSubview:_nextBtn]; 139 140 // 上一曲按钮 141 CGFloat prevBtnX = _selectBtn.frame.origin.x; 142 CGFloat prevBtnY = CGRectGetMaxY(_nextBtn.frame) + 10; 143 CGFloat prevBtnW = _selectBtn.bounds.size.width; 144 CGFloat prevBtnH = _selectBtn.bounds.size.height; 145 _prevBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 146 _prevBtn.frame = CGRectMake(prevBtnX, prevBtnY, prevBtnW, prevBtnH); 147 [_prevBtn setTitle:@"上一曲" forState:UIControlStateNormal]; 148 _prevBtn.tintColor = [UIColor whiteColor]; 149 _prevBtn.backgroundColor = [UIColor blackColor]; 150 _prevBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17]; 151 [_prevBtn addTarget:self action:@selector(prevBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 152 [self.view addSubview:_prevBtn]; 153 154 // 按钮交互设置 155 _selectBtn.enabled = YES; 156 _playBtn.enabled = NO; 157 _pauseBtn.enabled = NO; 158 _stopBtn.enabled = NO; 159 _nextBtn.enabled = NO; 160 _prevBtn.enabled = NO; 161 } 162 163 #pragma mark - 点击事件 164 // 1.选择音乐来源 165 - (void)selectBtnClick:(UIButton *)sender 166 { 167 [self presentViewController:self.mediaPicker animated:YES completion:nil]; 168 } 169 // 2.播放 170 - (void)playBtnClick:(UIButton *)sender 171 { 172 [self.musicPlayer play]; 173 } 174 // 3.暂停 175 - (void)pauseBtnClick:(UIButton *)sender 176 { 177 [self.musicPlayer pause]; 178 } 179 // 4.停止 180 - (void)stopBtnClick:(UIButton *)sender 181 { 182 [self.musicPlayer stop]; 183 } 184 // 5.下一曲 185 - (void)nextBtnClick:(UIButton *)sender 186 { 187 [self.musicPlayer skipToNextItem]; 188 } 189 // 6.上一曲 190 - (void)prevBtnClick:(UIButton *)sender 191 { 192 [self.musicPlayer skipToPreviousItem]; 193 } 194 195 #pragma mark - 直接通过MPMediaQueue获得媒体队列或媒体集合 196 // 1.取得媒体队列 197 - (MPMediaQuery *)getLocalMediaQuery 198 { 199 MPMediaQuery *mediaQueue = [MPMediaQuery songsQuery]; 200 for (MPMediaItem *item in mediaQueue.items) { 201 NSLog(@"标题:%@,%@", item.title, item.albumTitle); 202 } 203 return mediaQueue; 204 } 205 // 2.取得媒体集合 206 - (MPMediaItemCollection *)getLocalMediaItemCollection 207 { 208 MPMediaQuery *mediaQueue = [MPMediaQuery songsQuery]; 209 NSMutableArray *array = [NSMutableArray array]; 210 for (MPMediaItem *item in mediaQueue.items) { 211 [array addObject:item]; 212 NSLog(@"标题:%@,%@",item.title,item.albumTitle); 213 } 214 MPMediaItemCollection *mediaItemCollection = [[MPMediaItemCollection alloc]initWithItems:[array copy]]; 215 return mediaItemCollection; 216 } 217 218 #pragma mark - 代理方法<MPMediaPickerController> 219 // 1.选择完成 220 -(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection 221 { 222 // 按钮交互设置 223 _selectBtn.enabled = YES; 224 _playBtn.enabled = YES; 225 _pauseBtn.enabled = NO; 226 _stopBtn.enabled = NO; 227 _nextBtn.enabled = YES; 228 _prevBtn.enabled = YES; 229 230 // 第一个播放音乐 231 MPMediaItem *mediaItem=[mediaItemCollection.items firstObject]; 232 // 注意很多音乐信息(标题、专辑、表演者、封面、时长等)都可以通过MPMediaItem的valueForKey:方法得到,但是从iOS7开始都有对应的属性可以直接访问 233 // NSString *title = [mediaItem valueForKey:MPMediaItemPropertyAlbumTitle]; 234 // NSString *artist = [mediaItem valueForKey:MPMediaItemPropertyAlbumArtist]; 235 // MPMediaItemArtwork *artwork = [mediaItem valueForKey:MPMediaItemPropertyArtwork]; 236 //UIImage *image = [artwork imageWithSize:CGSizeMake(100, 100)]; // 专辑图片 237 NSLog(@"标题:%@,表演者:%@,专辑:%@", mediaItem.title, mediaItem.artist, mediaItem.albumTitle); 238 [self.musicPlayer setQueueWithItemCollection:mediaItemCollection]; 239 [self dismissViewControllerAnimated:YES completion:nil]; 240 } 241 // 2.取消选择 242 -(void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker 243 { 244 // 按钮交互设置 245 _selectBtn.enabled = YES; 246 _playBtn.enabled = NO; 247 _pauseBtn.enabled = NO; 248 _stopBtn.enabled = NO; 249 _nextBtn.enabled = NO; 250 _prevBtn.enabled = NO; 251 [self dismissViewControllerAnimated:YES completion:nil]; 252 } 253 254 #pragma mark - 通知 255 // 1.添加通知 256 - (void)addNotification 257 { 258 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 259 [notificationCenter addObserver:self selector:@selector(playbackStateChange:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.musicPlayer]; 260 } 261 // 2.播放状态改变通知 262 - (void)playbackStateChange:(NSNotification *)notification 263 { 264 switch (self.musicPlayer.playbackState) { 265 case MPMusicPlaybackStatePlaying: 266 NSLog(@"正在播放..."); 267 // 按钮交互设置 268 _selectBtn.enabled = NO; 269 _playBtn.enabled = NO; 270 _pauseBtn.enabled = YES; 271 _stopBtn.enabled = YES; 272 _nextBtn.enabled = YES; 273 _prevBtn.enabled = YES; 274 break; 275 case MPMusicPlaybackStatePaused: 276 NSLog(@"播放暂停."); 277 // 按钮交互设置 278 _selectBtn.enabled = YES; 279 _playBtn.enabled = YES; 280 _pauseBtn.enabled = NO; 281 _stopBtn.enabled = NO; 282 _nextBtn.enabled = YES; 283 _prevBtn.enabled = YES; 284 break; 285 case MPMusicPlaybackStateStopped: 286 NSLog(@"播放停止."); 287 // 按钮交互设置 288 _selectBtn.enabled = YES; 289 _playBtn.enabled = YES; 290 _pauseBtn.enabled = NO; 291 _stopBtn.enabled = NO; 292 _nextBtn.enabled = YES; 293 _prevBtn.enabled = YES; 294 break; 295 default: 296 break; 297 } 298 } 299 300 #pragma mark - 销毁方法 301 - (void)dealloc 302 { 303 // 结束通知 304 [self.musicPlayer endGeneratingPlaybackNotifications]; 305 } 306 307 @end
5.录音(AVAudioRecorder)
1)说明
a.AVAudioRecorder属于AVFoundation框架
b.AVAudioRecorder是专门处理录音操作的,它同样支持多种音频格式
c.AVAudioRecorder跟AVAudioPlayer创建有所不同,创建录音机时除了指定的路径外,还须指定录音设置信息(文件格式、采样率、通道数、每个采样点的位数等)
2)属性和方法
| 属性 | 说明 |
| @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 | 录音编码发生错误 |
3)代码实例(实现录音功能、录音结束自动播放、监听录音的声波变化)
1 // RecorderController.m文件 2 #import "RecorderController.h" 3 #import <AVFoundation/AVFoundation.h> 4 5 #define kRecordAudioFile @"myRecord.caf" 6 7 @interface RecorderController ()<AVAudioRecorderDelegate> 8 9 @property (nonatomic,strong) AVAudioRecorder *audioRecorder; // 音频录音机 10 @property (nonatomic,strong) AVAudioPlayer *audioPlayer; // 音频播放器,用于播放录音文件 11 @property (nonatomic,strong) NSTimer *timer; // 录音声波监控(注意这里暂时不对播放进行监控) 12 13 @property (nonatomic, strong) UIButton *recordBtn; // 开始录音 14 @property (nonatomic, strong) UIButton *pauseBtn; // 暂停录音 15 @property (nonatomic, strong) UIButton *resumeBtn; // 恢复录音 16 @property (nonatomic, strong) UIButton *stopBtn; // 停止录音 17 @property (nonatomic, strong) UIProgressView *audioPowerProgressView; // 音频波动 18 19 @end 20 21 @implementation RecorderController 22 23 #pragma mark - 懒加载 24 // 1.audioRecorder 25 - (AVAudioRecorder *)audioRecorder 26 { 27 if (!_audioRecorder) { 28 // 创建录音文件保存路径 29 NSURL *url = [self getSavePath]; 30 // 创建录音格式设置 31 NSDictionary *setting = [self getAudioSetting]; 32 // 创建录音机 33 NSError *error = nil; 34 _audioRecorder = [[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error]; 35 _audioRecorder.delegate = self; 36 _audioRecorder.meteringEnabled = YES; // 如果要监控声波则必须设置为YES 37 if (error) { 38 NSLog(@"创建录音机对象时发生错误,错误信息:%@", error.localizedDescription); 39 return nil; 40 } 41 } 42 return _audioRecorder; 43 } 44 // 2.audioPlayer 45 - (AVAudioPlayer *)audioPlayer 46 { 47 if (!_audioPlayer) { 48 // 取得录音文件保存路径 49 NSURL *url = [self getSavePath]; 50 NSError *error = nil; 51 // 创建播放器 52 _audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; 53 _audioPlayer.numberOfLoops = 0; 54 [_audioPlayer prepareToPlay]; // 播放缓存 55 if (error) { 56 NSLog(@"创建播放器过程中发生错误,错误信息:%@",error.localizedDescription); 57 return nil; 58 } 59 } 60 return _audioPlayer; 61 } 62 // 3.timer 63 - (NSTimer *)timer 64 { 65 if (!_timer) { 66 _timer=[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(audioPowerChange) userInfo:nil repeats:YES]; 67 } 68 return _timer; 69 } 70 71 - (void)viewDidLoad 72 { 73 [super viewDidLoad]; 74 75 [self initUI]; // 界面 76 [self setAudioSession]; // 设置音频会话 77 } 78 79 #pragma mark - 界面 80 - (void)initUI 81 { 82 self.view.backgroundColor = [UIColor colorWithRed:50 / 255.0 green:88 / 255.0 blue:111 / 255.0 alpha:1]; 83 84 // recordBtn 85 CGFloat recordBtnX = 10; 86 CGFloat recordBtnY = self.view.frame.size.height / 2.0 + 50; 87 CGFloat recordBtnW = (self.view.bounds.size.width - 5 * recordBtnX) / 4; 88 CGFloat recordBtnH = 35; 89 _recordBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 90 _recordBtn.frame = CGRectMake(recordBtnX, recordBtnY, recordBtnW, recordBtnH); 91 [_recordBtn setTitle:@"开始录音" forState:UIControlStateNormal]; 92 _recordBtn.titleLabel.font = [UIFont systemFontOfSize:13]; 93 _recordBtn.backgroundColor = [UIColor whiteColor]; 94 _recordBtn.tintColor = [UIColor greenColor]; 95 _recordBtn.layer.borderWidth = 3; 96 _recordBtn.layer.borderColor = [UIColor greenColor].CGColor; 97 [_recordBtn addTarget:self action:@selector(recordBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 98 [self.view addSubview:_recordBtn]; 99 100 // pauseBtn 101 CGFloat pauseBtnX = CGRectGetMaxX(_recordBtn.frame) + recordBtnX; 102 CGFloat pauseBtnY = _recordBtn.frame.origin.y; 103 CGFloat pauseBtnW = _recordBtn.bounds.size.width; 104 CGFloat pauseBtnH = _recordBtn.bounds.size.height; 105 _pauseBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 106 _pauseBtn.frame = CGRectMake(pauseBtnX, pauseBtnY, pauseBtnW, pauseBtnH); 107 [_pauseBtn setTitle:@"暂停录音" forState:UIControlStateNormal]; 108 _pauseBtn.titleLabel.font = [UIFont systemFontOfSize:13]; 109 _pauseBtn.backgroundColor = [UIColor whiteColor]; 110 _pauseBtn.tintColor = [UIColor greenColor]; 111 _pauseBtn.layer.borderWidth = 3; 112 _pauseBtn.layer.borderColor = [UIColor greenColor].CGColor; 113 [_pauseBtn addTarget:self action:@selector(pauseBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 114 [self.view addSubview:_pauseBtn]; 115 116 // resumeBtn 117 CGFloat resumeBtnX = CGRectGetMaxX(_pauseBtn.frame) + recordBtnX; 118 CGFloat resumeBtnY = _recordBtn.frame.origin.y; 119 CGFloat resumeBtnW = _recordBtn.bounds.size.width; 120 CGFloat resumeBtnH = _recordBtn.bounds.size.height; 121 _resumeBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 122 _resumeBtn.frame = CGRectMake(resumeBtnX, resumeBtnY, resumeBtnW, resumeBtnH); 123 [_resumeBtn setTitle:@"恢复录音" forState:UIControlStateNormal]; 124 _resumeBtn.titleLabel.font = [UIFont systemFontOfSize:13]; 125 _resumeBtn.backgroundColor = [UIColor whiteColor]; 126 _resumeBtn.tintColor = [UIColor greenColor]; 127 _resumeBtn.layer.borderWidth = 3; 128 _resumeBtn.layer.borderColor = [UIColor greenColor].CGColor; 129 [_resumeBtn addTarget:self action:@selector(resumeBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 130 [self.view addSubview:_resumeBtn]; 131 132 // stopBtn 133 CGFloat stopBtnX = CGRectGetMaxX(_resumeBtn.frame) + recordBtnX; 134 CGFloat stopBtnY = _recordBtn.frame.origin.y; 135 CGFloat stopBtnW = _recordBtn.bounds.size.width; 136 CGFloat stopBtnH = _recordBtn.bounds.size.height; 137 _stopBtn = [UIButton buttonWithType:UIButtonTypeSystem]; 138 _stopBtn.frame = CGRectMake(stopBtnX, stopBtnY, stopBtnW, stopBtnH); 139 [_stopBtn setTitle:@"停止录音" forState:UIControlStateNormal]; 140 _stopBtn.titleLabel.font = [UIFont systemFontOfSize:13]; 141 _stopBtn.backgroundColor = [UIColor whiteColor]; 142 _stopBtn.tintColor = [UIColor greenColor]; 143 _stopBtn.layer.borderWidth = 3; 144 _stopBtn.layer.borderColor = [UIColor greenColor].CGColor; 145 [_stopBtn addTarget:self action:@selector(stopBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 146 [self.view addSubview:_stopBtn]; 147 148 // audioPowerProgressView 149 CGFloat audioPowerProgressViewX = recordBtnX; 150 CGFloat audioPowerProgressViewY = self.view.frame.size.height / 2.0; 151 CGFloat audioPowerProgressViewW = self.view.bounds.size.width - audioPowerProgressViewX * 2; 152 CGFloat audioPowerProgressViewH = 20; 153 _audioPowerProgressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar]; 154 _audioPowerProgressView.frame = CGRectMake(audioPowerProgressViewX, audioPowerProgressViewY, audioPowerProgressViewW, audioPowerProgressViewH); 155 _audioPowerProgressView.progress = 0; 156 _audioPowerProgressView.progressTintColor = [UIColor redColor]; 157 _audioPowerProgressView.trackTintColor = [UIColor grayColor]; 158 [self.view addSubview:_audioPowerProgressView]; 159 } 160 161 #pragma mark - 设置音频会话 162 - (void)setAudioSession 163 { 164 AVAudioSession *audioSession = [AVAudioSession sharedInstance]; 165 // 设置为播放和录音状态,以便在录制完之后播放录音 166 [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; 167 [audioSession setActive:YES error:nil]; 168 } 169 170 #pragma mark - 取得录音文件保存路径 171 - (NSURL *)getSavePath 172 { 173 NSString *urlStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 174 urlStr = [urlStr stringByAppendingPathComponent:kRecordAudioFile]; 175 NSLog(@"录音文件保存路径:%@", urlStr); 176 NSURL *url = [NSURL fileURLWithPath:urlStr]; 177 return url; 178 } 179 180 #pragma mark - 取得录音文件设置 181 // 返回的是参数字典 182 - (NSDictionary *)getAudioSetting 183 { 184 NSMutableDictionary *dicM = [NSMutableDictionary dictionary]; 185 // 设置录音格式 186 [dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey]; 187 // 设置录音采样率,8000是电话采样率,对于一般录音已经够了 188 [dicM setObject:@(8000) forKey:AVSampleRateKey]; 189 // 设置通道,这里采用单声道 190 [dicM setObject:@(1) forKey:AVNumberOfChannelsKey]; 191 // 每个采样点位数,分为8、16、24、32 192 [dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey]; 193 // 是否使用浮点数采样 194 [dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey]; 195 return dicM; 196 } 197 198 #pragma mark - 更新设置录音声波状态 199 - (void)audioPowerChange 200 { 201 // 更新测量值 202 [self.audioRecorder updateMeters]; 203 // 取得第一个通道的音频,注意音频强度范围时-160到0 204 float power = [self.audioRecorder averagePowerForChannel:0]; 205 CGFloat progress = (1.0 / 160.0) * (power + 160.0); 206 // 设置audioPowerProgressView的值 207 _audioPowerProgressView.progress = progress; 208 } 209 210 #pragma mark - 点击事件 211 // 1.开始录音 212 - (void)recordBtnClick:(UIButton *)sender 213 { 214 if (![self.audioRecorder isRecording]) { 215 // 首次使用应用时如果调用recordBtnClick:方法会询问用户是否允许使用麦克风 216 [self.audioRecorder record]; 217 self.timer.fireDate = [NSDate distantPast]; 218 } 219 } 220 // 2.暂停录音 221 - (void)pauseBtnClick:(UIButton *)sender 222 { 223 if ([self.audioRecorder isRecording]) { 224 [self.audioRecorder pause]; 225 self.timer.fireDate = [NSDate distantFuture]; 226 } 227 } 228 // 3.恢复录音 229 - (void)resumeBtnClick:(UIButton *)sender 230 { 231 [self recordBtnClick:sender]; 232 } 233 // 4.停止录音 234 - (void)stopBtnClick:(UIButton *)sender 235 { 236 [self.audioRecorder stop]; 237 self.timer.fireDate = [NSDate distantFuture]; 238 _audioPowerProgressView.progress = 0.0; 239 } 240 241 #pragma mark - 代理方法<AVAudioRecorderDelegate> 242 // 参数1:recorder 录音机对象 243 // 参数2:flag 是否成功 244 - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag 245 { 246 if (![self.audioPlayer isPlaying]) { 247 [self.audioPlayer play]; 248 } 249 NSLog(@"录音完成!"); 250 } 251 252 @end
二、视频
1.MPMoviePlayerController(通常作嵌入视频播放)
1)简介
a.属于框架MediaPlayer.framework
b.支持本地视频和网络视频播放
c.MPMoviePlayerController自身实现了MPMediaPlayback协议,因此具有一般的播放器控制功能,例如:播放、停止、暂停等等
d.MPMoviePlayerController自身并不是一个完成的视图控制器,如果要在UI中展示视频,需要将该类中的属性View添加到界面中
2)常用属性和方法
| 属性 | 说明 |
| @property (nonatomic, copy) NSURL *contentURL | 播放媒体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 | 做好播放准备后 |
注:MPMoviePlayerController的状态信息并不是通过代理来和外界交互的,而是通过通知中心。
3)代码示例
注:本例实现获取缩略图(视频第一帧画面),点击缩略图播放视频
1 // VideoPlayerController.m文件 2 #import "VideoPlayerController.h" 3 #import <MediaPlayer/MediaPlayer.h> // 导入框架 4 #import <AVFoundation/AVFoundation.h> // 使用AVURLAsset获取视频第一帧需要导入的框架 5 6 @interface VideoPlayerController () 7 8 @property (nonatomic, strong) MPMoviePlayerController *moviePlayer; // 视频播放控制器 9 @property (nonatomic, strong) UIImageView *imageView; // 显示第一帧视频的图片 10 @property (nonatomic, assign) BOOL isThumbnailRequestFinished; // 第一帧请求是否成功 11 12 @end 13 14 @implementation VideoPlayerController 15 16 #pragma mark - 懒加载 17 // 1.moviePlayer 18 - (MPMoviePlayerController *)moviePlayer 19 { 20 if (!_moviePlayer) { 21 // 获取视频URL 22 NSURL *url = [self getNetworkUrl]; 23 // 根据URL初始化视频播放控制器 24 _moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url]; 25 _moviePlayer.view.frame = CGRectMake(0, 64, self.view.bounds.size.width, 200); 26 // 将视频播放控制器的View添加到控制器的View上 27 [self.view addSubview:_moviePlayer.view]; 28 } 29 return _moviePlayer; 30 } 31 // 2.imageView 32 - (UIImageView *)imageView 33 { 34 if (!_imageView) { 35 CGFloat imageViewX = 0; 36 CGFloat imageViewY = 10; 37 CGFloat imageViewW = self.moviePlayer.view.bounds.size.width; 38 CGFloat imageViewH = self.moviePlayer.view.bounds.size.height - imageViewY * 2; 39 _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(imageViewX, imageViewY, imageViewW, imageViewH)]; 40 _imageView.userInteractionEnabled = YES; 41 // 添加点击手势 42 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick:)]; 43 [_imageView addGestureRecognizer:tap]; 44 [self.moviePlayer.view addSubview:_imageView]; 45 } 46 return _imageView; 47 } 48 49 - (void)viewDidLoad 50 { 51 [super viewDidLoad]; 52 53 [self initUI]; // 界面 54 } 55 56 #pragma mark - 界面 57 - (void)initUI 58 { 59 self.view.backgroundColor = [UIColor blueColor]; 60 61 // [self thumbnailImageRequest]; // 请求第一帧视频画面 62 [self thumbnailImageRequest:0.1]; // 请求第一帧视频画面 63 [self addNotification]; // 注册通知 64 } 65 66 #pragma mark - 注册通知 67 - (void)addNotification 68 { 69 // 监听播放状态发生改变的通知 70 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerPlaybackStateDidChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil]; 71 // 监听播放完成的通知 72 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerPlaybackStateDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil]; 73 // 监听缩略图请求成功的通知 74 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerThumbnailRequestFinished:) name:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:self.moviePlayer]; 75 } 76 77 #pragma mark - 手势点击事件 78 // 点击第一帧视频播放 79 - (void)tapClick:(UITapGestureRecognizer *)tap 80 { 81 // 判断缩略图是否请求成功 82 if (!_isThumbnailRequestFinished) { 83 return; 84 } 85 86 self.imageView.hidden = YES; 87 [self.moviePlayer play]; // 播放 88 } 89 90 #pragma mark - 取得视频URL 91 - (NSURL *)getNetworkUrl 92 { 93 NSString *urlStr = @"http://192.168.1.177:8080/HKGI/upload/001.mp4"; 94 urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 95 NSURL *url = [NSURL URLWithString:urlStr]; 96 return url; 97 } 98 99 #pragma mark - 请求第一帧视频 100 // 1.第一种方法(使用MPMoviePlayerController生成缩略图) 101 // 方法1与方法2的区别:方法1在获取缩略图的时候会播放缩略图的那一帧,而方法2不会 102 - (void)thumbnailImageRequest 103 { 104 // 获取0.1s缩略图 105 [self.moviePlayer requestThumbnailImagesAtTimes:@[@0.1] timeOption:MPMovieTimeOptionNearestKeyFrame]; 106 } 107 // 2.第二种方法(使用AVFoundation生成缩略图) 108 - (void)thumbnailImageRequest:(CGFloat )timeBySecond 109 { 110 // 创建URL 111 NSURL *url = [self getNetworkUrl]; 112 // 根据URL创建AVURLAsset 113 AVURLAsset *urlAsset = [AVURLAsset assetWithURL:url]; 114 // 根据AVURLAsset创建 115 AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset]; 116 117 // 截图 118 // requestTime:缩略图创建时间 119 // actualTime:缩略图实际生成的时间 120 NSError *error = nil; 121 // CMTime:电影时间信息的结构体(参数一:视频第几秒,参数二:每秒有多少帧) 122 // 如果要动态的某一秒的第几帧可以使用CMTimeMake方法 123 CMTime time = CMTimeMakeWithSeconds(timeBySecond, 10); 124 // CMTime time = CMTimeMake(timeBySecond, 10); 125 126 CMTime actualTime; 127 CGImageRef cgImage = [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error]; 128 if (error) { 129 NSLog(@"截取视频缩略图时发生错误,错误信息:%@",error.localizedDescription); 130 return; 131 } 132 133 CMTimeShow(actualTime); 134 // 转化为UIImage 135 UIImage *image = [UIImage imageWithCGImage:cgImage]; 136 self.imageView.image = image; 137 _isThumbnailRequestFinished = YES; 138 139 // 保存到相册 140 // UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); 141 // CGImageRelease(cgImage); 142 } 143 144 #pragma mark - 通知事件 145 // 1.播放状态发生改变 146 - (void)moviePlayerPlaybackStateDidChange:(NSNotification *)notification 147 { 148 switch (self.moviePlayer.playbackState) { 149 case MPMoviePlaybackStatePlaying: 150 NSLog(@"正在播放"); 151 break; 152 case MPMoviePlaybackStatePaused: 153 NSLog(@"暂停播放"); 154 break; 155 case MPMoviePlaybackStateStopped: 156 NSLog(@"停止播放"); 157 break; 158 default: 159 NSLog(@"播放状态:%d", self.moviePlayer.playbackState); 160 break; 161 } 162 } 163 // 2.播放完成 164 - (void)moviePlayerPlaybackStateDidFinish:(NSNotification *)notification 165 { 166 NSLog(@"播放完成:%d", self.moviePlayer.playbackState); 167 self.imageView.hidden = NO; 168 } 169 // 3.缩略图请求成功(该方法每次截图成功都会调用一次)(对于第一种方法获取的缩略图,会以通知形式返回) 170 - (void)moviePlayerThumbnailRequestFinished:(NSNotification *)notification 171 { 172 NSLog(@"视频截图完成"); 173 UIImage *image = notification.userInfo[MPMoviePlayerThumbnailImageKey]; 174 self.imageView.image = image; 175 _isThumbnailRequestFinished = YES; 176 177 // 保存图片到相册(首次调用会请求用户获得访问相册权限) 178 // UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); 179 } 180 181 #pragma mark - 销毁通知 182 - (void)dealloc 183 { 184 // 移除所有通知监控 185 [[NSNotificationCenter defaultCenter] removeObserver:self]; 186 } 187 188 @end
2.MPMoviePlayerViewController(单独跳转控制器播放)
1)说明
如果MPMoviePlayerController不作为嵌入视频播放(例如腾讯新闻中嵌入一个视频),通常在播放时都是占满一个屏幕的,特别是在iPhone上。因此自iOS3.2以后,苹果自己封装了一个继承自UIViewController的控制器MPMoviePlayerViewController,专门用来全屏播放视频,这个控制器里面有一个属性moviePlayer和一个带有URL的初始化方法。同时它的内部也实现了一些作为模态视图展示所特有的功能。例如:默认是全屏模式展示,弹出后自动播放;作为模态窗口展示时,如果点击“Done”按钮会自动退出模态窗口等
2)代码示例
注:本例实现获取缩略图(视频第一帧画面),点击缩略图模态跳转播放视频控制器全屏开始播放
1 // VideoController.m文件 2 #import "VideoController.h" 3 #import <MediaPlayer/MediaPlayer.h> // 导入框架 4 #import <AVFoundation/AVFoundation.h> // 使用AVURLAsset获取视频第一帧需要导入的框架 5 6 @interface VideoController () 7 8 // 播放器视图控制器 9 @property (nonatomic, strong) MPMoviePlayerViewController *moviePlayerViewController; 10 // 显示第一帧视频的图片 11 @property (nonatomic, strong) UIImageView *imageView; 12 // 第一帧请求是否成功 13 @property (nonatomic, assign) BOOL isThumbnailRequestFinished; 14 15 @end 16 17 @implementation VideoController 18 19 #pragma mark - 懒加载 20 // 1.moviePlayerViewController 21 - (MPMoviePlayerViewController *)moviePlayerViewController 22 { 23 if (!_moviePlayerViewController) { 24 // 获取视频URL 25 NSURL *url = [self getNetworkUrl]; 26 // 根据URL初始化视频播放控制器 27 _moviePlayerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:url]; 28 // 注册通知 29 [self addNotification]; 30 } 31 return _moviePlayerViewController; 32 } 33 // 2.imageView 34 - (UIImageView *)imageView 35 { 36 if (!_imageView) { 37 CGFloat imageViewX = 20; 38 CGFloat imageViewY = 80; 39 CGFloat imageViewW = self.view.bounds.size.width - imageViewX * 2; 40 CGFloat imageViewH = 200; 41 _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(imageViewX, imageViewY, imageViewW, imageViewH)]; 42 _imageView.userInteractionEnabled = YES; 43 // 添加点击手势 44 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick:)]; 45 [_imageView addGestureRecognizer:tap]; 46 [self.view addSubview:_imageView]; 47 } 48 return _imageView; 49 } 50 51 - (void)viewDidLoad 52 { 53 [super viewDidLoad]; 54 55 [self initUI]; // 界面 56 } 57 58 #pragma mark - 界面 59 - (void)initUI 60 { 61 self.view.backgroundColor = [UIColor whiteColor]; 62 [self thumbnailImageRequest:0.1]; // 请求第一帧视频画面 63 } 64 65 #pragma mark - 注册通知 66 - (void)addNotification 67 { 68 // 监听播放状态发生改变的通知 69 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerPlaybackStateDidChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil]; 70 // 监听播放完成的通知 71 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerPlaybackStateDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil]; 72 } 73 74 #pragma mark - 手势点击事件 75 // 点击第一帧视频播放 76 - (void)tapClick:(UITapGestureRecognizer *)tap 77 { 78 // 判断缩略图是否请求成功 79 if (!_isThumbnailRequestFinished) { 80 return; 81 } 82 83 self.moviePlayerViewController = nil; 84 [self presentMoviePlayerViewControllerAnimated:self.moviePlayerViewController]; 85 } 86 87 #pragma mark - 请求第一帧视频画面 88 // 使用AVFoundation生成缩略图 89 - (void)thumbnailImageRequest:(CGFloat )timeBySecond 90 { 91 // 创建URL 92 NSURL *url = [self getNetworkUrl]; 93 // 根据URL创建AVURLAsset 94 AVURLAsset *urlAsset = [AVURLAsset assetWithURL:url]; 95 // 根据AVURLAsset创建 96 AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset]; 97 98 // 截图 99 // requestTime:缩略图创建时间 100 // actualTime:缩略图实际生成的时间 101 NSError *error = nil; 102 // CMTime:电影时间信息的结构体(参数一:视频第几秒,参数二:每秒有多少帧) 103 // 如果要动态的某一秒的第几帧可以使用CMTimeMake方法 104 CMTime time = CMTimeMakeWithSeconds(timeBySecond, 10); 105 // CMTime time = CMTimeMake(timeBySecond, 10); 106 107 CMTime actualTime; 108 CGImageRef cgImage = [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error]; 109 if (error) { 110 NSLog(@"截取视频缩略图时发生错误,错误信息:%@",error.localizedDescription); 111 return; 112 } 113 114 CMTimeShow(actualTime); 115 // 转化为UIImage 116 UIImage *image = [UIImage imageWithCGImage:cgImage]; 117 self.imageView.image = image; 118 _isThumbnailRequestFinished = YES; 119 } 120 121 #pragma mark - 获取本地视频URL 122 - (NSURL *)getFileUrl 123 { 124 NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"mp4"]; 125 NSURL *url = [NSURL fileURLWithPath:urlStr]; 126 return url; 127 } 128 129 #pragma mark - 获取网络视频URL 130 - (NSURL *)getNetworkUrl 131 { 132 NSString *urlStr = @"http://192.168.1.177:8080/HKGI/upload/001.mp4"; 133 urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 134 NSURL *url = [NSURL URLWithString:urlStr]; 135 return url; 136 } 137 138 #pragma mark - 通知事件 139 // 1.播放状态发生改变 140 - (void)moviePlayerPlaybackStateDidChange:(NSNotification *)notification 141 { 142 switch (self.moviePlayerViewController.moviePlayer.playbackState) { 143 case MPMoviePlaybackStatePlaying: 144 NSLog(@"正在播放"); 145 break; 146 case MPMoviePlaybackStatePaused: 147 NSLog(@"暂停播放"); 148 break; 149 case MPMoviePlaybackStateStopped: 150 NSLog(@"停止播放"); 151 break; 152 default: 153 NSLog(@"播放状态:%ld", self.moviePlayerViewController.moviePlayer.playbackState); 154 break; 155 } 156 } 157 // 2.播放完成 158 - (void)moviePlayerPlaybackStateDidFinish:(NSNotification *)notification 159 { 160 NSLog(@"播放完成:%ld", self.moviePlayerViewController.moviePlayer.playbackState); 161 } 162 163 #pragma mark - 销毁通知 164 - (void)dealloc 165 { 166 // 移除所有通知监控 167 [[NSNotificationCenter defaultCenter] removeObserver:self]; 168 } 169 170 @end
注:这里需要强调一下,由于MPMoviePlayerViewController的初始化方法做了大量工作(例如设置URL、自动播放、添加点击Done完成的监控等),所以当再次点击播放弹出新的模态窗口的时如果不销毁之前的MPMoviePlayerViewController,那么新的对象就无法完成初始化,这样也就不能再次进行播放。定位到我们代码示例中就是在每次模态跳转播放控制器之前必须将它设置为nil,重新加载初始化一下(self.moviePlayerViewController = nil)
3.AVPlayer(通常用于自定义一个播放器)
1)说明
a.MPMoviePlayerController足够强大,只需要写几行代码就能够完成一个播放器的功能;但是由于它的高度封装,使得我们自定义播放器变得相当复杂或是不可能完成。其实在这个时候,我们通常会使用AVFoundation框架的AVPlayer进行自定义播放器。因为它更加接近底层,灵活性也较强。
b.AVPlayer本身并不能显示视频,而且也不像MPMoviePlayerController有一个View属性。如果AVPlayer要显示,必须创建一个播放器层AVPlayerLayer用于展示。播放器层继承自CALayer。有了这个AVPlayerLayer,我们需要将它添加到控制器的layer中
c.AVPlayer的常用类:
AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
AVPlayerItem:一个媒体资源管理对象,管理视频的一些基本信息和状态(一个AVPlayerItem对应着一个视频资源)
2)代码示例
1 // CustomAVPlayerController.m文件 2 #import "CustomAVPlayerController.h" 3 #import <AVFoundation/AVFoundation.h> // 需要导入的框架 4 5 @interface CustomAVPlayerController () 6 7 // 播放器对象 8 @property (nonatomic, strong) AVPlayer *player; 9 // 播放器容器 10 @property (nonatomic, strong) UIView *containerView; 11 // 播放暂停按钮 12 @property (nonatomic, strong) UIButton *playOrPauseBtn; 13 // 播放进度 14 @property (nonatomic, strong) UIProgressView *progressView; 15 // 保存当前正在播放剧集的按钮 16 @property (nonatomic, strong) UIButton *currentBtn; 17 18 @end 19 20 @implementation CustomAVPlayerController 21 22 #pragma mark - 懒加载 23 // player 24 - (AVPlayer *)player 25 { 26 if (!_player) { 27 // 获取第1个视频文件(AVPlayerItem对象) 28 AVPlayerItem *playerItem = [self getPlayerItemWithIndex:0]; 29 // 根据AVPlayerItem对象初始化播放器对象 30 _player = [AVPlayer playerWithPlayerItem:playerItem]; 31 32 // 给播放器添加进度更新(定时器) 33 [self addProgressObserver]; 34 // 给AVPlayerItem(视频文件对象)添加监控(KVO) 35 [self addObserverToPlayerItem:playerItem]; 36 } 37 return _player; 38 } 39 40 - (void)viewDidLoad 41 { 42 [super viewDidLoad]; 43 44 [self initUI]; // 界面 45 [self.player play]; // 播放 46 } 47 48 #pragma mark - 界面 49 - (void)initUI 50 { 51 self.view.backgroundColor = [UIColor whiteColor]; 52 53 // containerView 54 CGFloat containerViewX = 0; 55 CGFloat containerViewY = 64; 56 CGFloat containerViewW = self.view.bounds.size.width; 57 CGFloat containerViewH = 250; 58 _containerView = [[UIView alloc] initWithFrame:CGRectMake(containerViewX, containerViewY, containerViewW, containerViewH)]; 59 [self.view addSubview:_containerView]; 60 61 // 根据AVPlayer创建播放器层 62 AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 63 playerLayer.frame = _containerView.bounds; 64 // 视频填充模式 65 playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; 66 [_containerView.layer addSublayer:playerLayer]; 67 68 // playOrPauseBtn 69 CGFloat playOrPauseBtnX = 20; 70 CGFloat playOrPauseBtnY = CGRectGetMaxY(_containerView.frame); 71 CGFloat playOrPauseBtnW = 40; 72 CGFloat playOrPauseBtnH = 40; 73 _playOrPauseBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 74 _playOrPauseBtn.frame = CGRectMake(playOrPauseBtnX, playOrPauseBtnY, playOrPauseBtnW, playOrPauseBtnH); 75 [_playOrPauseBtn setImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal]; 76 [_playOrPauseBtn setImage:[UIImage imageNamed:@"pause"] forState:UIControlStateSelected]; 77 [_playOrPauseBtn addTarget:self action:@selector(playOrPauseBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 78 _playOrPauseBtn.selected = YES; 79 [self.view addSubview:_playOrPauseBtn]; 80 81 // progressView 82 CGFloat progressViewX = CGRectGetMaxX(_playOrPauseBtn.frame); 83 CGFloat progressViewY = _playOrPauseBtn.frame.origin.y + _playOrPauseBtn.bounds.size.height / 2.0; 84 CGFloat progressViewW = _containerView.bounds.size.width - progressViewX - _playOrPauseBtn.frame.origin.x; 85 CGFloat progressViewH = 2; 86 _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(progressViewX, progressViewY, progressViewW, progressViewH)]; 87 [self.view addSubview:_progressView]; 88 89 // bottomView 90 CGFloat bottomViewX = _playOrPauseBtn.frame.origin.x; 91 CGFloat bottomViewY = CGRectGetMaxY(_playOrPauseBtn.frame) + 10; 92 CGFloat bottomViewW = _containerView.bounds.size.width - bottomViewX * 2; 93 CGFloat bottomViewH = 70; 94 UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(bottomViewX, bottomViewY, bottomViewW, bottomViewH)]; 95 [self.view addSubview:bottomView]; 96 97 // textLabel 98 CGFloat textLabelX = 0; 99 CGFloat textLabelY = 0; 100 CGFloat textLabelW = bottomView.bounds.size.width; 101 CGFloat textLabelH = 30; 102 UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(textLabelX, textLabelY, textLabelW, textLabelH)]; 103 textLabel.text = @"播放列表"; 104 textLabel.font = [UIFont boldSystemFontOfSize:17]; 105 textLabel.textColor = [UIColor blueColor]; 106 [bottomView addSubview:textLabel]; 107 108 // 循环创建btn 109 NSArray *movieNames = @[@"001", @"002", @"003", @"004"]; 110 for (int i = 0; i < movieNames.count; i++) { 111 CGFloat btnW = 55; 112 CGFloat btnH = 35; 113 CGFloat btnX = textLabel.frame.origin.x + (btnW + 5) * i; 114 CGFloat btnY = CGRectGetMaxY(textLabel.frame); 115 UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem]; 116 btn.frame = CGRectMake(btnX, btnY, btnW, btnH); 117 [btn setTitle:movieNames[i] forState:UIControlStateNormal]; 118 btn.titleLabel.font = [UIFont boldSystemFontOfSize:13]; 119 btn.tintColor = [UIColor blueColor]; 120 btn.backgroundColor = [UIColor grayColor]; 121 btn.tag = 100 + i; 122 [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside]; 123 [bottomView addSubview:btn]; 124 if (i == 0) { 125 _currentBtn = btn; 126 _currentBtn.backgroundColor = [UIColor redColor]; 127 } 128 } 129 } 130 131 #pragma mark - 点击事件 132 // 1.播放暂停按钮 133 - (void)playOrPauseBtnClick:(UIButton *)sender 134 { 135 // 说明暂停 136 if(self.player.rate == 0){ 137 sender.selected = YES; 138 [self.player play]; // 播放 139 140 // 正在播放 141 }else if(self.player.rate == 1){ 142 sender.selected = NO; 143 [self.player pause]; // 暂停 144 } 145 } 146 // 2.播放列表剧集按钮 147 - (void)btnClick:(UIButton *)sender 148 { 149 // 进度归0 150 _progressView.progress = 0; 151 // 切换背景 152 _currentBtn.backgroundColor = [UIColor grayColor]; 153 _currentBtn = sender; 154 _currentBtn.backgroundColor = [UIColor redColor]; 155 // 移除播放完毕通知 156 [self removeNotification]; 157 // 移除当前正在播放的视频文件的属性监听(KVO) 158 [self removeObserverFromPlayerItem:self.player.currentItem]; 159 // 获得切换后的视频文件对象(AVPlayerItem对象) 160 AVPlayerItem *playerItem = [self getPlayerItemWithIndex:(int)(sender.tag - 100)]; 161 // 给AVPlayerItem(视频文件对象)添加监控(KVO) 162 [self addObserverToPlayerItem:playerItem]; 163 // 切换视频 164 [self.player replaceCurrentItemWithPlayerItem:playerItem]; 165 // 注册通知 166 [self addNotification]; 167 } 168 169 #pragma mark - 根据索引取得AVPlayerItem对象 170 // 一个AVPlayerItem对象对应一个视频文件 171 - (AVPlayerItem *)getPlayerItemWithIndex:(int)index 172 { 173 NSArray *movieNames = @[@"001", @"002", @"003", @"004"]; 174 NSString *urlStr = [NSString stringWithFormat:@"http://192.168.1.177:8080/HKGI/upload/%@.mp4", movieNames[index]]; 175 urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 176 NSURL *url = [NSURL URLWithString:urlStr]; 177 // 根据视频URL创建视频文件对象(AVPlayerItem对象) 178 AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url]; 179 return playerItem; 180 } 181 182 #pragma mark - 监控 183 // 1.给播放器添加进度更新(定时器) 184 - (void)addProgressObserver 185 { 186 AVPlayerItem *playerItem = self.player.currentItem; 187 // 这里设置每秒更新一次进度值 188 __weak CustomAVPlayerController *weakSelf = self; 189 [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { 190 float currentTime = CMTimeGetSeconds(time); 191 float totalTime = CMTimeGetSeconds(playerItem.duration); 192 // 判断当前播放时间是否为0 193 if (currentTime) { 194 [weakSelf.progressView setProgress:(currentTime / totalTime) animated:YES]; 195 } 196 }]; 197 } 198 // 2.给AVPlayerItem(视频文件对象)添加监控(KVO) 199 - (void)addObserverToPlayerItem:(AVPlayerItem *)playerItem 200 { 201 // 监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态 202 [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 203 // 监控网络加载情况属性 204 [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; 205 } 206 // 添加播放器通知(通知中心) 207 - (void)addNotification 208 { 209 // 给AVPlayerItem添加播放完成通知 210 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem]; 211 } 212 213 #pragma mark - 通过KVO监控播放器状态 214 // 参数1:keyPath 监控属性 215 // 参数2:object 监视器 216 // 参数3:change 状态改变 217 // 参数4:context 上下文 218 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 219 { 220 AVPlayerItem *playerItem = object; 221 if ([keyPath isEqualToString:@"status"]) { 222 AVPlayerStatus status= [change[@"new"] intValue]; 223 if(status == AVPlayerStatusReadyToPlay) { 224 NSLog(@"正在播放...,视频总长度:%.2f", CMTimeGetSeconds(playerItem.duration)); 225 } 226 227 } else if([keyPath isEqualToString:@"loadedTimeRanges"]) { 228 NSArray *array = playerItem.loadedTimeRanges; 229 CMTimeRange timeRange = [array[0] CMTimeRangeValue]; // 本次缓冲时间范围 230 float startSeconds = CMTimeGetSeconds(timeRange.start); 231 float durationSeconds = CMTimeGetSeconds(timeRange.duration); 232 NSTimeInterval totalBuffer = startSeconds + durationSeconds; // 缓冲总长度 233 NSLog(@"共缓冲:%.2f", totalBuffer); 234 } 235 } 236 237 #pragma mark - 播放完成通知 238 - (void)playbackFinished:(NSNotification *)notification 239 { 240 NSLog(@"视频播放完成."); 241 } 242 243 #pragma mark - 移除KVO监听的属性 244 - (void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem 245 { 246 // KVO监听播放状态属性status 247 [playerItem removeObserver:self forKeyPath:@"status"]; 248 // KVO监听加载进度属性loadedTimeRanges 249 [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; 250 } 251 252 #pragma mark - 移除通知 253 - (void)removeNotification 254 { 255 [[NSNotificationCenter defaultCenter] removeObserver:self]; 256 } 257 258 #pragma mark - 销毁方法 259 - (void)dealloc 260 { 261 // 移除KVO监听的属性 262 [self removeObserverFromPlayerItem:self.player.currentItem]; 263 // 移除通知 264 [self removeNotification]; 265 } 266 267 @end

三、摄像头
1.UIImageViewPickerController(可用于选取相册相片、拍照和视频录制)
1)说明
UIImageViewPickerController继承自UINavigationController,主要可用于选取相册相片、拍照和视频录制
2)属性和方法
| 属性 | 说明 |
|
@property (nonatomic) UIImagePickerControllerSourceType sourceType |
拾取源类型,sourceType是枚举类型: UIImagePickerControllerSourceTypePhotoLibrary:照片库(默认值) UIImagePickerControllerSourceTypeCamera:摄像头 UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿 |
| @property (nonatomic, copy) NSArray *mediaTypes |
媒体类型(拍照时可以不用设置;但是当要录像的时候必须设置) 默认情况下此数组包含: kUTTypeImage:图片 kUTTypeVideo:视频不带声音 kUTTypeMovie:视频带有声音 |
| @property (nonatomic) NSTimeInterval videoMaximumDuration | 视频最大录制时长(默认为10s) |
| @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) | 保存视频到相簿 |
3)如果使用UIImagePickerController拍照或是录制视频,使用步骤如下
a.创建UIImagePickerController对象
b.指定拾取源:选择照片时指定拾取源是照片或是相簿,拍照和录制指定拾取源为摄像头类型
c.拍照和录制视频,需要指定摄像头:前置摄像头或者后置摄像头
d.设置媒体类型mediaType,注意如果是录像必须设置;如果是拍照,次步骤可省略,因为mediaType默认包含kUTTypeImage(注意媒体类型定义在MobileCoreServices.framework中)
e.指定捕捉模式、拍照或者录制视频(视频录制时必须先设置媒体类型再设置捕捉模式)
f.展示UIImagePickerController(通常以模态窗口形式打开)
g.拍照和录制视频结束后在代理方法中展示/保存照片或视频
4)代码实例
1 // ImagePickerController.m文件 2 #import "ImagePickerController.h" 3 #import <MobileCoreServices/MobileCoreServices.h> 4 #import <AVFoundation/AVFoundation.h> 5 6 @interface ImagePickerController ()<UIImagePickerControllerDelegate, UINavigationControllerDelegate> 7 8 @property (nonatomic, assign) int isVideo; // 是否录制视频(1:录制视频 0:拍照) 9 @property (nonatomic, strong) UIImagePickerController *imagePicker; 10 @property (nonatomic, strong) UIImageView *photoImageView; // 照片展示视图 11 @property (nonatomic, strong) AVPlayer *player; // 播放器(用于录制完视频后播放视频) 12 13 @end 14 15 @implementation ImagePickerController 16 17 #pragma mark - 懒加载 18 // imagePicker 19 - (UIImagePickerController *)imagePicker 20 { 21 if (!_imagePicker) { 22 _imagePicker = [[UIImagePickerController alloc] init]; 23 // 设置UIImagePickerController的拾取源(这里是摄像头) 24 _imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; 25 // 设置使用哪一个摄像头(这里设置的是后置摄像头) 26 _imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceRear; 27 // 判断当前用户设置是视频录制,还是拍照(1:视频录制 0:拍照) 28 if (_isVideo) { 29 // 视频录制必须设置媒体类型数组(当前设置为视频且带有声音) 30 _imagePicker.mediaTypes = @[(NSString *)kUTTypeMovie]; 31 // 设置视频质量 32 _imagePicker.videoQuality = UIImagePickerControllerQualityTypeIFrame1280x720; 33 // 设置摄像头模式(当前设置为录制视频模式) 34 _imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo; 35 36 } else { 37 // 设置摄像头模式(当前设置为拍照模式) 38 _imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto; 39 } 40 // 允许编辑 41 _imagePicker.allowsEditing = YES; 42 // 设置代理 43 _imagePicker.delegate = self; 44 } 45 return _imagePicker; 46 } 47 48 - (void)viewDidLoad 49 { 50 [super viewDidLoad]; 51 52 [self initUI]; // 界面 53 } 54 55 #pragma mark - 界面 56 - (void)initUI 57 { 58 self.view.backgroundColor = [UIColor whiteColor]; 59 60 // photoImageView 61 CGFloat photoImageViewX = 0; 62 CGFloat photoImageViewY = 64; 63 CGFloat photoImageViewW = self.view.bounds.size.width; 64 CGFloat photoImageViewH = 250; 65 _photoImageView = [[UIImageView alloc] initWithFrame:CGRectMake(photoImageViewX, photoImageViewY, photoImageViewW, photoImageViewH)]; 66 _photoImageView.userInteractionEnabled = YES; 67 [self.view addSubview:_photoImageView]; 68 69 // btn 70 CGFloat btnW = 100; 71 CGFloat btnH = 30; 72 CGFloat btnX = (self.view.bounds.size.width - btnW) / 2.0; 73 CGFloat btnY = CGRectGetMaxY(_photoImageView.frame) + 20; 74 UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem]; 75 btn.frame = CGRectMake(btnX, btnY, btnW, btnH); 76 [btn setTitle:@"调取摄像头" forState:UIControlStateNormal]; 77 btn.tintColor = [UIColor blueColor]; 78 btn.backgroundColor = [UIColor grayColor]; 79 btn.titleLabel.font = [UIFont boldSystemFontOfSize:15]; 80 [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside]; 81 [self.view addSubview:btn]; 82 83 // 当前程序为录制视频 84 _isVideo = YES; 85 } 86 87 #pragma mark - 点击事件 88 // 点击调取摄像头 89 - (void)btnClick:(UIButton *)btn 90 { 91 if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { 92 self.modalPresentationStyle = UIModalPresentationOverCurrentContext; 93 } 94 [self presentViewController:self.imagePicker animated:YES completion:nil]; 95 } 96 97 #pragma mark - 代理方法<UIImagePickerControllerDelegate, UINavigationControllerDelegate> 98 // 1.完成 99 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info 100 { 101 // 获取媒体类型 102 NSString *mediaType = info[UIImagePickerControllerMediaType]; 103 if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) { 104 UIImage *image = nil; 105 // 如果允许编辑,则获得编辑后的图片,否则获取原始图片 106 if (self.imagePicker.allowsEditing) { 107 // 获得编辑后的图片 108 image = info[UIImagePickerControllerEditedImage]; 109 } else { 110 // 获取原始图片 111 image = info[UIImagePickerControllerOriginalImage]; 112 } 113 114 if (image) { 115 // 显示照片 116 self.photoImageView.image = image; 117 // 保存照片到相册 118 UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); 119 } 120 121 } else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) { 122 // 获取录制视频路径 123 NSURL *url = info[UIImagePickerControllerMediaURL]; 124 NSString *urlStr = url.path; 125 if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(urlStr)) { 126 // 保存视频到相簿,注意也可以使用ALAssetsLibrary来保存 127 UISaveVideoAtPathToSavedPhotosAlbum(urlStr, self, @selector(video:didFinishSavingWithError:contextInfo:), nil); 128 } 129 } 130 [self dismissViewControllerAnimated:YES completion:nil]; 131 } 132 // 2.取消 133 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 134 { 135 NSLog(@"取消"); 136 } 137 138 #pragma mark - 视频保存后的回调 139 - (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo 140 { 141 if (error) { 142 NSLog(@"保存视频过程中发生错误,错误信息:%@",error.localizedDescription); 143 144 } else { 145 NSLog(@"视频保存成功."); 146 // 录制完之后自动播放 147 NSURL *url = [NSURL fileURLWithPath:videoPath]; 148 _player = [AVPlayer playerWithURL:url]; 149 AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; 150 playerLayer.frame = self.photoImageView.bounds; 151 [self.photoImageView.layer addSublayer:playerLayer]; 152 [_player play]; 153 } 154 } 155 156 @end
2.AVFoundation的拍照和视频录制(可自定义)
1)说明
UIImagePickerController的功能很强大,但是它与MPMoviePlayerController类似,由于它的高度封装性,要进行某些自定义工作就比较复杂了。那么如果需要自定义,我们可以使用AVFoundation来实现。AVFoundation框架中提供了很多现成的播放器和录音机,但是事实上它还是提供了一些更底层的内容供开发者使用。因为AVFoundation框架中抽了很多底层输入、输出设备打交道的类,依靠这些类开发人员面对的不再是封装好的音频播放器(AVAudioPlayer)、录音机(AVAudioRecorder)、视频(包括音频)播放器(AVPlayer),而是输入设备(例如:麦克风、摄像头)、输出设备(图片、视频)等。
2)AVFoundation拍照和视频录制的相关类:
a.AVCaptureSession:媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出
b.AVCaptureDevice:输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。
c.AVCaptureDeviceInput:设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession
d.AVCaptureOutput:输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,该对象将会被添加到AVCaptureSession中管理。注意:前面几个对象的输出数据都是NSData类型,而AVCaptureFileOutput代表数据以文件形式输出,类似的,AVCcaptureFileOutput也不会直接创建使用,通常会使用其子类:AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。当把一个输入或者输出添加到AVCaptureSession之后,AVCaptureSession就会在所有相符的输入、输出设备之间建立连接(AVCaptionConnection)
e.AVCaptureVideoPreviewLayer:相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession对象。
3)使用AVFoundation拍照和录制视频的一般步骤
a.创建AVCaptureSession对象
b.使用AVCaptureDevice的静态方法获得需要使用的设备,例如:拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备
c.利用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象
d.初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象
e.将数据输入对象AVCaptureDeviceInput、数据输出对象AVCaptureOutput添加到媒体会话管理对象AVCaptureSession中
f.创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到显示容器中,调用AVCaptureSession的startRunning方法开始捕获
g.将捕获的音频或视频数据输出到指定问件
4)代码示例(这里就不做详细写啦,关联博客:http://www.cnblogs.com/kenshincui/p/4186022.html#uiImagePickerController)
四、总结


浙公网安备 33010602011771号