现在的移动端呐,播放个视频,播放个音乐已经是很普遍的需求了,大家都知道苹果提供了
AVFoundation
这个库,但是彻底了解的应该很少吧,我有朋友家也做这些播放之类的需求,但是核心的一些东西全部是用第三方的提供好的东西。所以今天我们了解一下PLAYER
。我在学习的同时也会把找到的资料分享出来。
播放本地音频在简单不过了,直接使用AVAudioPlayer
就可以解决问题。
- step1
#import
- step2
NSError * error = nil;
AVAudioPlayer * player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:__kAudioFilePath__] error:&error];
[player prepareToPlay];
-
step3 需要知道的一些属性以及一些方法有
方法:- prepareToPlay 返回一个BOOL值, 如果YES就是准备成功了
- play 播放
- playAtTime 要比当前系统时间大
- pause 暂停, 任然是准备好播放的
- stop 停止, 不再是准备好播放
- updateMeters 更新音量
- peakPowerForChannel 给定声道的最大音量, 调用前必须调用updateMeters
- averagePowerForChannel 给定声道的平均音量, 调用前必须调用updateMeters
属性: - duration 歌曲的持续时间
- url/data 通过哪种方式创建在这里可以得到
- currentTime 当前的播放进度(如果不在播放,那么就是应该开始的歌曲偏移量)
- deviceCurrentTime 输出设备当前时间
- numberOfLoops 循环次数,0就是不循环,正数就是次数,负数就是一直循环
- settings 配置,播放器的配置,包括设置质量,比特率等。在
AVAudioSettings.h
中定义了一些key
. - channelAssignments 获得或者设置声道
- enableRate 是否可以改变速率,设置为yes后可以通过设置
rate
改变,1.0为正常速率 - volume 音量设置 0 - 1
- pan 立体声平衡,如果为-1.0则完全左声道,如果0.0则左右声道平衡,如果为1.0则完全为右声道
-
step4 支持后台播放
设置AVAudioSession的类型为AVAudioSessionCategoryPlayback并且调用setActive::方法启动会话
AVAudioSession *audioSession=[AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[audioSession setActive:YES error:nil];
可以为你的应用加上远程控制(使用耳机等)####
1.启用远程事件接收
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
2.对于UI控件同样要求必须是第一响应者(对于视图控制器UIViewController或者应用程序UIApplication对象监听无此要求), becomeFirstResponder
。
3.应用程序必须是当前音频的控制者,也就是在iOS 7中通知栏中当前音频播放程序必须是我们自己开发程序。
这里我们涉及到一个枚举
typedef NS_ENUM(NSInteger, UIEventSubtype) { // 不包含任何子事件类型
UIEventSubtypeNone = 0, // 摇晃事件(从iOS3.0开始支持此事件)
UIEventSubtypeMotionShake = 1, //远程控制子事件类型(从iOS4.0开始支持远程控制事件) //播放事件【操作:停止状态下,按耳机线控中间按钮一下】
UIEventSubtypeRemoteControlPlay = 100, //暂停事件
UIEventSubtypeRemoteControlPause = 101, //停止事件
UIEventSubtypeRemoteControlStop = 102, //播放或暂停切换【操作:播放或暂停状态下,按耳机线控中间按钮一下】
UIEventSubtypeRemoteControlTogglePlayPause = 103, //暂停【操作:按耳机线控中间按钮一下】 UIEventSubtypeRemoteControlNextTrack = 104, //上一曲【操作:按耳机线控中间按钮三下】
UIEventSubtypeRemoteControlPreviousTrack = 105, //快退开始【操作:按耳机线控中间按钮三下不要松开】
UIEventSubtypeRemoteControlBeginSeekingBackward = 106, //快退停止【操作:按耳机线控中间按钮三下到了快退的位置松开】
UIEventSubtypeRemoteControlEndSeekingBackward = 107, //快进开始【操作:按耳机线控中间按钮两下不要松开】
UIEventSubtypeRemoteControlBeginSeekingForward = 108, //快进停止【操作:按耳机线控中间按钮两下到了快进的位置松开】
UIEventSubtypeRemoteControlEndSeekingForward = 109,
};
-> 首先在应用程序启动后设置接收远程控制事件,并且设置音频会话保证后台运行可以播放(注意要在应用配置中设置允许多任务)在didFinishLaunchWithOptions
中设置。
//设置播放会话,在后台可以继续播放(还需要设置程序允许后台运行模式)
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
if(![[AVAudioSession sharedInstance] setActive:YES error:nil]) {
NSLog(@"Failed to set up a session.");
}
//启用远程控制事件接收
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
-> 在视图控制器中添加远程控制事件并音频播放进行控制,在ViewController中
-(BOOL)canBecomeFirstResponder{ return NO;}
-(void)remoteControlReceivedWithEvent:(UIEvent *)event{
NSLog(@"%i,%i",event.type,event.subtype);
if(event.type==UIEventTypeRemoteControl)
{
switch (event.subtype) {
case UIEventSubtypeRemoteControlPlay:
[_player play];
_isPlaying=true;
break;
case UIEventSubtypeRemoteControlTogglePlayPause:
if (_isPlaying) {
[_player pause];
} else {
[_player play];
}
_isPlaying=!_isPlaying;
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(@"Next...");
break;
case UIEventSubtypeRemoteControlPreviousTrack:
NSLog(@"Previous...");
break;
case UIEventSubtypeRemoteControlBeginSeekingForward:
NSLog(@"Begin seek forward...");
break;
case UIEventSubtypeRemoteControlEndSeekingForward:
NSLog(@"End seek forward...");
break;
case UIEventSubtypeRemoteControlBeginSeekingBackward:
NSLog(@"Begin seek backward...");
break;
case UIEventSubtypeRemoteControlEndSeekingBackward:
NSLog(@"End seek backward...");
break;
default:
break;
}
[self updateUI]; // 改变UI
}
}
为了模拟一个真实的播放器,程序中我们启用了后台运行模式,配置方法:在info.plist中添加UIBackgroundModes并且添加一个元素值为audio。
即使利用线控进行音频控制我们也无法监控到耳机增加音量、减小音量的按键操作(另外注意模拟器无法模拟远程事件,请使用真机调试)。
子事件的类型跟当前音频状态有直接关系,点击一次播放/暂停按钮究竟是【播放】还是【播放/暂停】状态切换要看当前音频处于什么状态,如果处于停止状态则点击一下是播放,如果处于暂停或播放状态点击一下是暂停和播放切换。
上面的程序已在真机调试通过,无论是线控还是点击应用按钮都可以控制播放或暂停。
远程控制技术内容来自 kenshincui
上边提到了AVAudioSession
,顺便提一句:
在iOS中每个应用都有一个音频会话,这个会话就通过AVAudioSession来表示。AVAudioSession同样存在于AVFoundation框架中,它是单例模式设计,通过sharedInstance进行访问。在使用Apple设备时大家会发现有些应用只要打开其他音频播放就会终止,而有些应用却可以和其他应用同时播放,在多种音频环境中如何去控制播放的方式就是通过音频会话来完成的
下面是音频会话的几种会话模式:
大家在使用中还发现,QQ音乐与微信播放时候的小细节,在微信播放结束后,QQ音乐可以接着播放,这里就要用到setActive
。并且在拔出耳机的时候会停止播放。
-> 在前面的代码中也提到设置完音频会话类型之后需要调用setActive::方法将会话激活才能起作用。类似的,如果一个应用已经在播放音频,打开我们的应用之后设置了在后台播放的会话类型,此时其他应用的音频会停止而播放我们的音频,如果希望我们的程序音频播放完之后(关闭或退出到后台之后)能够继续播放其他应用的音频的话则可以调用setActive::方法关闭会话。
//设置后台播放模式
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];
-(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];
}
}
//根据实际情况播放完成可以将会话关闭,其他音频应用继续播放
[[AVAudioSession sharedInstance]setActive:NO error:nil];
AVPlayer 与 MPMoviePlayerController
AVAssetImageGenerator
都可以播放在线视频、音频。MPMoviePlayerController
使用简单,但是自定义度低,AVPlayer
使用稍复杂,但是自定义度很高.
MPMoviePlayerController
的使用方法很简单。
AVPlayer
, 自定义视图,自定义缓存加载,播放队列等。
AVAssetImageGenerator
生成视频的缩略图
我们先来看本地生成一个网络视频的缩略图,而不依靠服务器插件生成。
MPMoviePlayerController
简单使用,模态推出,自动开始播放,点击done消失等。
NSURL *url=[self getNetworkUrl];
_moviePlayerViewController=[[MPMoviePlayerViewController alloc]initWithContentURL:url];
监听播放器的状态
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];
AVPlayer
: MPMoviePlayerController足够强大,几乎不用写几行代码就能完成一个播放器,但是正是由于它的高度封装使得要自定义这个播放器变得很复杂,甚至是不可能完成。例如有些时候需要自定义播放器的样式,那么如果要使用MPMoviePlayerController就不合适了,如果要对视频有自由的控制则可以使用AVPlayer。AVPlayer存在于AVFoundation中,它更加接近于底层,所以灵活性也更强
AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:
AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。
视频的播放、暂停功能,这也是最基本的功能,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
专门处理播放列表切换,有兴趣的朋友可以自行研究,这里不再赘述)。
_player=[AVPlayer playerWithPlayerItem:playerItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
这些是基本的使用,当然playerItem
可以使用AVURLAsset
来初始化,在这里可以设置缓存加载哦。通过实现代理方法可以处理这些操作。不过一般我们喜欢自己控制缓存,而且功能多用于IM
中,所以视频一般都会完整的下载下来,我们自己按照需求去实现一个缓存下载,在初始化item
之前判断一下本地是否有响应的缓存即可。
UIImagePickerController 与 AVFoundation
不得不说UIImagePickerController确实强大,但是与MPMoviePlayerController类似,由于它的高度封装性,要进行某些自定义工作就比较复杂了。例如要做出一款类似于美颜相机的拍照界面就比较难以实现了,此时就可以考虑使用AVFoundation来实现。AVFoundation中提供了很多现成的播放器和录音机,但是事实上它还有更加底层的内容可以供开发者使用。因为AVFoundation中抽了很多和底层输入、输出设备打交道的类,依靠这些类开发人员面对的不再是封装好的音频播放器AVAudioPlayer、录音机(AVAudioRecorder)、视频(包括音频)播放器AVPlayer,而是输入设备(例如麦克风、摄像头)、输出设备(图片、视频)等。
AVCaptureSession:媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出
AVCaptureDevice:输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。
AVCaptureDeviceInput:设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。
AVCaptureOutput:输出数据管理对象,用于接收各类输出数据,通常使用对应的子类
AVCaptureAudioDataOutput
、
AVCaptureStillImageOutput
、
AVCaptureVideoDataOutput
、
AVCaptureFileOutput
,
该对象将会被添加到AVCaptureSession中管理。注意:前面几个对象的输出数据都是NSData类型,而AVCaptureFileOutput代表数据以文件形式输出,类似的,AVCcaptureFileOutput也不会直接创建使用,通常会使用其子类:AVCaptureAudioFileOutput
、
AVCaptureMovieFileOutput
。
当把一个输入或者输出添加到AVCaptureSession之后AVCaptureSession就会在所有相符的输入、输出设备之间建立连接(AVCaptionConnection)
AVCaptureVideoPreviewLayer:相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession对象
UIImagePicker
大家很常用,这里不做多说,直接看下面
使用AVFoundation拍照和录制视频的一般步骤:
- 创建AVCaptureSession对象。
- 使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。
- 利用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象。
- 初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
- 将数据输入对象AVCaptureDeviceInput、数据输出对象AVCaptureOutput添加到媒体会话管理对象AVCaptureSession中。
- 创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到 显示容器中,调用AVCaptureSession的startRuning方法开始捕获。
- 将捕获的音频或视频数据输出到指定文件。
在控制器视图展示和视图离开界面时启动、停止会话。
定义闪光灯开闭及自动模式功能,注意无论是设置闪光灯、白平衡还是其他输入设备属性,在设置之前必须先锁定配置,修改完后解锁。
定义切换摄像头功能,切换摄像头的过程就是将原有输入移除,在会话中添加新的输入,但是注意动态修改会话需要首先开启配置,配置成功后提交配置。
添加点击手势操作,点按预览视图时进行聚焦、白平衡设置。
定义拍照功能,拍照的过程就是获取连接,从连接中获得捕获的输出数据并做保存操作。
#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];
}
}];
}
视频录制
程序只需要做如下修改:
- 添加一个音频输入到会话(使用
[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]
获得输入设备,然后根据此输入设备创建一个设备输入对象),在拍照程序中已经添加了视频输入所以此时不需要添加视频输入。 - 创建一个音乐播放文件输出对象
AVCaptureMovieFileOutput
取代原来的照片输出对象。 - 将捕获到的视频数据写入到临时文件并在停止录制之后保存到相簿
(通过AVCaptureMovieFileOutput
的代理方法)。
总结
感谢 love蚊子
CopyRight@Dylan 2016-4-19
本文来自于蚊子的技术分享,所以文章欢迎转载,修改,意见,分享技术。