一、AVAudioPlayer簡介
AVAudioPlayer在AVFoundation框架下,所以我們要導入AVFoundation.framework。
AVAudioPlayer類封裝了播放單個聲音的能力。播放器可以用NSURL或者NSData來初始化,要注意的是NSURL並不可以是網絡url而必須是本地文件URL,
因為 AVAudioPlayer不具備播放網絡音頻的能力,如果要播放網絡URL,需要先轉化為NSData.但是此法並不可取,
因為AVAudioPlayer只能播放一個完整的文件,並不支持流式播放,所以必須是緩沖完才能播放,所以如果網絡文件過大抑或是網速不夠豈不是要等很久?
所以播放網絡音頻我們一般用音頻隊列。播放較大的音頻或者要對音頻有精確的,這種情況會選擇使用AVFoundation.framework中的AVAudioPlayer來實現。
AVAudioPlayer不支持邊下邊播,所以只能下載到本地再播放,這是他的缺點。但是AVAudioPlayer能夠精確控制播放進度、音量、播放速度等屬性,這是很強大的優點。
注意:需要添加AVFoundation.framework
二、AVAudioPlayer使用
1.實例化方法
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError;
2.預加載資源
- (BOOL)prepareToPlay;
3.遵守協議
AVAudioPlayerDelegate
4.播放完成之后回調以下方法
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
先搭建UI吧,直接上圖:

#import "AVAudioPlayerViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface AVAudioPlayerViewController ()
{
//聲明一個播放器
AVAudioPlayer *_musicPlay;//音樂播放器
}
- (IBAction)playAction:(id)sender;//播放
- (IBAction)pauseAction:(id)sender;//暫停
- (IBAction)voiceAction:(UISlider *)sender;//音量
- (IBAction)progressAction:(UISlider *)sender;//進度
@property (weak, nonatomic) IBOutlet UISlider *progressSlider;//進度條
@property (nonatomic,strong)NSTimer *myTimer;//聲明一個定時器類型的成員變量
@end
@implementation AVAudioPlayerViewController
- (void)viewDidLoad {
[super viewDidLoad];
//獲取資源路徑 需要事先導入本地音頻內容
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"mp3"];
NSLog(@"%@",filePath);
NSURL *URL =[NSURL fileURLWithPath:filePath];
_musicPlay = [[AVAudioPlayer alloc] initWithContentsOfURL:URL error:nil];
//實例化音樂播放器
// _musicPlay = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:nil];
//設置音量大小處於中等強度
_musicPlay.volume = 0.5;
//循環次數,默認是1
_musicPlay.numberOfLoops = 1;
// 聲道數
NSUInteger channels = _musicPlay.numberOfChannels;//只讀屬性
//預加載資源
if ([_musicPlay prepareToPlay]) {
NSLog(@"准備完畢");
}
//設置總進度大小
self.progressSlider.maximumValue = _musicPlay.duration;
}
#pragma mark- 各類觸發事件
-(void)change:(NSTimer *)sender{
//每一秒鍾設置一下進度值 播放位置
self.progressSlider.value = _musicPlay.currentTime;
}
#pragma mark- AVAudioPlayer相關的方法
- (IBAction)playAction:(id)sender {
//播放
[_musicPlay play];
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(change:) userInfo:nil repeats:YES];
}
- (IBAction)pauseAction:(id)sender {
//暫停
[_musicPlay pause];
//停止定時器
[self.myTimer invalidate];
NSLog(@"%f",_musicPlay.currentTime);
}
- (IBAction)voiceAction:(UISlider *)sender {
//改變音量大小
_musicPlay.volume = sender.value;
}
- (IBAction)progressAction:(UISlider *)sender {
//設置進度
_musicPlay.currentTime = sender.value;
}
代理方法
加入播放出現異常,或者被更高級別的系統任務打斷,我們的程序還沒來得及收場就掛了,怎么辦?不急,我們可以通過幾個委托方法很好地處理所有的情形。
首先給player設置委托是必須的:
- player.delegate = self;
- - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag{
- //播放結束時執行的動作
- }
- - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer*)player error:(NSError *)error{
- //解碼錯誤執行的動作
- }
- - (void)audioPlayerBeginInteruption:(AVAudioPlayer*)player{
- //處理中斷的代碼
- }
- - (void)audioPlayerEndInteruption:(AVAudioPlayer*)player{
- //處理中斷結束的代碼
- }
三、AVAudioPlayer擴展
1) 如何做后台播放
1> 在plist文件下添加 key : Required background modes,並設置item0 = App plays audio or streams audio/video using AirPlay

2> 設置AVAudioSession的類型為AVAudioSessionCategoryPlayback並且調用setActive::方法啟動會話。
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[audioSession setActive:YES error:nil];
正常來說,當app退到后台,程序處於懸掛狀態,即暫停播放。
在iOS中每個應用都有一個音頻會話,這個會話就通過AVAudioSession來表示。
AVAudioSession同樣存在於AVFoundation框架中,它是單例模式設計,通過sharedInstance進行訪問。在使用Apple設備時大家會發現有些應用只要打開其他音頻播放就會終止,而有些應用卻可以和其他應用同時播放,在多種音頻環境中如何去控制播放的方式就是通過音頻會話來完成的。
下面是音頻會話的幾種會話模式:

注意 : 前面的代碼中也提到設置完音頻會話類型之后需要調用setActive::方法將會話激活才能起作用。
(2) 如何做輸出改變監聽(拔出耳機音樂暫停播放)
ios6.0后還可以監聽輸出改變通知。通俗來說,就是拔出耳機,音樂播放暫停。
代碼如下:
//添加觀察者
[[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"])
{
//這邊必須回調到主線程
dispatch_async(dispatch_get_main_queue(), ^{
self.playOrPause.selected = NO;
});
[self pauseMusic];
}
}
}//輸出改變通知
(3) 歌詞輪播實現思路
歌詞應該是 時間 和 對應歌詞 的字典類型數據結構。用UITableView實現。獲取播放器當前播放時間,查找字典找到對應的key,進而找到對應的NSIndexPath,滾動到當前cell在屏幕中央即可。
(4) 關於定時器的小細節
關於NSLoopMode的問題
由於+ (NSTimer *)scheduledTimerWithTimeInterval:..; 此時的timer會被加入到當前線程的runloop中,默認為NSDefaultRunLoopMode。如果當前線程是主線程,某些事件,如UIScrollView的拖動時,會將runloop切換到NSEventTrackingRunLoopMode模式,在拖動的過程中,默認的NSDefaultRunLoopMode模式中注冊的事件是不會被執行的。從而此時的timer也就不會觸發。
解決方案:把創建好的timer手動添加到指定模式中,此處為NSRunLoopCommonModes,這個模式其實就是NSDefaultRunLoopMode與NSEventTrackingRunLoopMode的結合。
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
