iOS中音頻播放分爲音效播放和音樂播放。音效播放主要指的是一些短音頻播放,通常作爲點綴音頻,對於這類音頻不需要進行進度、循環等控制。音樂播放指的是一些較長的音頻,通常是主音頻,對於這些音頻的播放通常需要進行精確的控制。在iOS中播放兩類音頻分別使用AudioToolbox.framework和AVFoundation.framework來完成音效和音樂播放。
音效
AudioToolbox.framework是一套基於C語言的框架,使用它來播放音效其本質是將短音頻註冊到系統聲音服務(System Sound Service)。System Sound Service是一種簡單、底層的聲音播放服務,但是它本身也存在着一些限制:
- 音頻播放時間不能超過30s
- 數據必須是PCM或者IMA4格式
- 音頻文件必須打包成.caf、.aif、.wav中的一種(注意這是官方文檔的說法,實際測試發現一些.mp3也可以播放)
使用System Sound Service 播放音效的步驟如下:
- 調用AudioServicesCreateSystemSoundID( CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函數獲得系統聲音ID。
- 如果需要監聽播放完成操作,則使用AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID,
CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)方法註冊回調函數。 - 調用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID) 或者AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(後者帶有震動效果)。
下面是一個簡單的示例程序:
#import "KCMainViewController.h" #import <AudioToolbox/AudioToolbox.h> @interface KCMainViewController () @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self playSoundEffect:@"videoRing.caf"]; } /** * 播放完成回調函數 * * @param soundID 系統聲音ID * @param clientData 回調時傳遞的數據 */ void soundCompleteCallback(SystemSoundID soundID,void * clientData){ NSLog(@"播放完成..."); } /** * 播放音效文件 * * @param name 音頻文件名稱 */ -(void)playSoundEffect:(NSString *)name{ NSString *audioFile=[[NSBundle mainBundle] pathForResource:name ofType:nil]; NSURL *fileUrl=[NSURL fileURLWithPath:audioFile]; //1.獲得系統聲音ID SystemSoundID soundID=0; /** * inFileUrl:音頻文件url * outSystemSoundID:聲音id(此函數會將音效文件加入到系統音頻服務中並返回一個長整形ID) */ AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID); //如果需要在播放完之後執行某些操作,可以調用如下方法註冊一個播放完成回調函數 AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL); //2.播放音頻 AudioServicesPlaySystemSound(soundID);//播放音效 // AudioServicesPlayAlertSound(soundID);//播放音效並震動 } @end
音樂
如果播放較大的音頻或者要對音頻有精確的控制則System Sound Service可能就很難滿足實際需求了,通常這種情況會選擇使用AVFoundation.framework中的AVAudioPlayer來實現。AVAudioPlayer可以看成一個播放器,它支持多種音頻格式,而且能夠進行進度、音量、播放速度等控制。首先簡單看一下AVAudioPlayer常用的屬性和方法:
屬性 | 說明 |
@property(readonly, getter=isPlaying) BOOL playing | 是否正在播放,只讀 |
@property(readonly) NSUInteger numberOfChannels | 音頻聲道數,只讀 |
@property(readonly) NSTimeInterval duration | 音頻時長 |
@property(readonly) NSURL *url | 音頻文件路徑,只讀 |
@property(readonly) NSData *data | 音頻數據,只讀 |
@property float pan | 立體聲平衡,如果爲-1.0則完全左聲道,如果0.0則左右聲道平衡,如果爲1.0則完全爲右聲道 |
@property float volume | 音量大小,範圍0-1.0 |
@property BOOL enableRate | 是否允許改變播放速率 |
@property float rate | 播放速率,範圍0.5-2.0,如果爲1.0則正常播放,如果要修改播放速率則必須設置enableRate爲YES |
@property NSTimeInterval currentTime | 當前播放時長 |
@property(readonly) NSTimeInterval deviceCurrentTime | 輸出設備播放音頻的時間,注意如果播放中被暫停此時間也會繼續累加 |
@property NSInteger numberOfLoops | 循環播放次數,如果爲0則不循環,如果小於0則無限循環,大於0則表示循環次數 |
@property(readonly) NSDictionary *settings | 音頻播放設置信息,只讀 |
@property(getter=isMeteringEnabled) BOOL meteringEnabled | 是否啓用音頻測量,默認爲NO,一旦啓用音頻測量可以通過updateMeters方法更新測量值 |
對象方法 | 說明 |
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError | 使用文件URL初始化播放器,注意這個URL不能是HTTP URL,AVAudioPlayer不支持加載網絡媒體流,只能播放本地文件 |
- (instancetype)initWithData:(NSData *)data error:(NSError **)outError | 使用NSData初始化播放器,注意使用此方法時必須文件格式和文件後綴一致,否則出錯,所以相比此方法更推薦使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString *)utiString error:(NSError **)outError方法進行初始化 |
- (BOOL)prepareToPlay; | 加載音頻文件到緩衝區,注意即使在播放之前音頻文件沒有加載到緩衝區程序也會隱式調用此方法。 |
- (BOOL)play; | 播放音頻文件 |
- (BOOL)playAtTime:(NSTimeInterval)time | 在指定的時間開始播放音頻 |
- (void)pause; | 暫停播放 |
- (void)stop; | 停止播放 |
- (void)updateMeters | 更新音頻測量值,注意如果要更新音頻測量值必須設置meteringEnabled爲YES,通過音頻測量值可以即時獲得音頻分貝等信息 |
- (float)peakPowerForChannel:(NSUInteger)channelNumber; | 獲得指定聲道的分貝峯值,注意如果要獲得分貝峯值必須在此之前調用updateMeters方法 |
- (float)averagePowerForChannel:(NSUInteger)channelNumber | 獲得指定聲道的分貝平均值,注意如果要獲得分貝平均值必須在此之前調用updateMeters方法 |
@property(nonatomic, copy) NSArray *channelAssignments | 獲得或設置播放聲道 |
代理方法 | 說明 |
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag | 音頻播放完成 |
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error | 音頻解碼發生錯誤 |
AVAudioPlayer的使用比較簡單:
- 初始化AVAudioPlayer對象,此時通常指定本地文件路徑。
- 設置播放器屬性,例如重複次數、音量大小等。
- 調用play方法播放。
下面就使用AVAudioPlayer實現一個簡單播放器,在這個播放器中實現了播放、暫停、顯示播放進度功能,當然例如調節音量、設置循環模式、甚至是聲波圖像(通過分析音頻分貝值)等功能都可以實現,這裏就不再一一演示。界面效果如下:
當然由於AVAudioPlayer一次只能播放一個音頻文件,所有上一曲、下一曲其實可以通過創建多個播放器對象來完成,這裏暫不實現。播放進度的實現主要依靠一個定時器實時計算當前播放時長和音頻總時長的比例,另外爲了演示委託方法,下面的代碼中也實現了播放完成委託方法,通常如果有下一曲功能的話播放完可以觸發下一曲音樂播放。下面是主要代碼:
#import "ViewController.h" #import <AVFoundation/AVFoundation.h> #define kMusicFile @"劉若英 - 原來你也在這裏.mp3" #define kMusicSinger @"劉若英" #define kMusicTitle @"原來你也在這裏" @interface ViewController ()<AVAudioPlayerDelegate> @property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器 @property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板 @property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放進度 @property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者 @property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag爲0認爲是暫停狀態,1是播放狀態) @property (weak ,nonatomic) NSTimer *timer;//進度更新定時器 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self setupUI]; } /** * 初始化UI */ -(void)setupUI{ self.title=kMusicTitle; self.musicSinger.text=kMusicSinger; } -(NSTimer *)timer{ if (!_timer) { _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true]; } return _timer; } /** * 創建播放器 * * @return 音頻播放器 */ -(AVAudioPlayer *)audioPlayer{ if (!_audioPlayer) { NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil]; NSURL *url=[NSURL fileURLWithPath:urlStr]; NSError *error=nil; //初始化播放器,注意這裏的Url參數只能時文件路徑,不支持HTTP Url _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; //設置播放器屬性 _audioPlayer.numberOfLoops=0;//設置爲0不循環 _audioPlayer.delegate=self; [_audioPlayer prepareToPlay];//加載音頻文件到緩存 if(error){ NSLog(@"初始化播放器過程發生錯誤,錯誤信息:%@",error.localizedDescription); return nil; } } return _audioPlayer; } /** * 播放音頻 */ -(void)play{ if (![self.audioPlayer isPlaying]) { [self.audioPlayer play]; self.timer.fireDate=[NSDate distantPast];//恢復定時器 } } /** * 暫停播放 */ -(void)pause{ if ([self.audioPlayer isPlaying]) { [self.audioPlayer pause]; self.timer.fireDate=[NSDate distantFuture];//暫停定時器,注意不能調用invalidate方法,此方法會取消,之後無法恢復 } } /** * 點擊播放/暫停按鈕 * * @param sender 播放/暫停按鈕 */ - (IBAction)playClick:(UIButton *)sender { if(sender.tag){ sender.tag=0; [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted]; [self pause]; }else{ sender.tag=1; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted]; [self play]; } } /** * 更新播放進度 */ -(void)updateProgress{ float progress= self.audioPlayer.currentTime /self.audioPlayer.duration; [self.playProgress setProgress:progress animated:true]; } #pragma mark - 播放器代理方法 -(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ NSLog(@"音樂播放完成..."); } @end運行效果:
音頻會話
事實上上面的播放器還存在一些問題,例如通常我們看到的播放器即使退出到後臺也是可以播放的,而這個播放器如果退出到後臺它會自動暫停。如果要支持後臺播放需要做下面幾件事情:
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::方法啓動會話。
AVAudioSession *audioSession=[AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[audioSession setActive:YES error:nil];
3.爲了能夠讓應用退到後臺之後支持耳機控制,建議添加遠程控制事件(這一步不是後臺播放必須的)
前兩步是後臺播放所必須設置的,第三步主要用於接收遠程事件,這部分內容之前的文章中有詳細介紹,如果這一步不設置雖讓也能夠在後臺播放,但是無法獲得音頻控制權(如果在使用當前應用之前使用其他播放器播放音樂的話,此時如果按耳機播放鍵或者控制中心的播放按鈕則會播放前一個應用的音頻),並且不能使用耳機進行音頻控制。第一步操作相信大家都很容易理解,如果應用程序要允許運行到後臺必須設置,正常情況下應用如果進入後臺會被掛起,通過該設置可以上應用程序繼續在後臺運行。但是第二步使用的AVAudioSession有必要進行一下詳細的說明。
在iOS中每個應用都有一個音頻會話,這個會話就通過AVAudioSession來表示。AVAudioSession同樣存在於AVFoundation框架中,它是單例模式設計,通過sharedInstance進行訪問。在使用Apple設備時大家會發現有些應用只要打開其他音頻播放就會終止,而有些應用卻可以和其他應用同時播放,在多種音頻環境中如何去控制播放的方式就是通過音頻會話來完成的。下面是音頻會話的幾種會話模式:
會話類型 | 說明 | 是否要求輸入 | 是否要求輸出 | 是否遵從靜音鍵 |
AVAudioSessionCategoryAmbient | 混音播放,可以與其他音頻應用同時播放 | 否 | 是 | 是 |
AVAudioSessionCategorySoloAmbient | 獨佔播放 | 否 | 是 | 是 |
AVAudioSessionCategoryPlayback | 後臺播放,也是獨佔的 | 否 | 是 | 否 |
AVAudioSessionCategoryRecord | 錄音模式,用於錄音時使用 | 是 | 否 | 否 |
AVAudioSessionCategoryPlayAndRecord | 播放和錄音,此時可以錄音也可以播放 | 是 | 是 | 否 |
AVAudioSessionCategoryAudioProcessing | 硬件解碼音頻,此時不能播放和錄製 | 否 | 否 | 否 |
AVAudioSessionCategoryMultiRoute | 多種輸入輸出,例如可以耳機、USB設備同時播放 | 是 | 是 | 否 |
注意:是否遵循靜音鍵表示在播放過程中如果用戶通過硬件設置爲靜音是否能關閉聲音。
根據前面對音頻會話的理解,相信大家開發出能夠在後臺播放的音頻播放器並不難,但是注意一下,在前面的代碼中也提到設置完音頻會話類型之後需要調用setActive::方法將會話激活才能起作用。類似的,如果一個應用已經在播放音頻,打開我們的應用之後設置了在後臺播放的會話類型,此時其他應用的音頻會停止而播放我們的音頻,如果希望我們的程序音頻播放完之後(關閉或退出到後臺之後)能夠繼續播放其他應用的音頻的話則可以調用setActive::方法關閉會話。代碼如下:
#import "ViewController.h" #import <AVFoundation/AVFoundation.h> #define kMusicFile @"劉若英 - 原來你也在這裏.mp3" #define kMusicSinger @"劉若英" #define kMusicTitle @"原來你也在這裏" @interface ViewController ()<AVAudioPlayerDelegate> @property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器 @property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板 @property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放進度 @property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者 @property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag爲0認爲是暫停狀態,1是播放狀態) @property (weak ,nonatomic) NSTimer *timer;//進度更新定時器 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self setupUI]; } /** * 顯示當面視圖控制器時註冊遠程事件 * * @param animated 是否以動畫的形式顯示 */ -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; //開啓遠程控制 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; //作爲第一響應者 //[self becomeFirstResponder]; } /** * 當前控制器視圖不顯示時取消遠程控制 * * @param animated 是否以動畫的形式消失 */ -(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; //[self resignFirstResponder]; } /** * 初始化UI */ -(void)setupUI{ self.title=kMusicTitle; self.musicSinger.text=kMusicSinger; } -(NSTimer *)timer{ if (!_timer) { _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true]; } return _timer; } /** * 創建播放器 * * @return 音頻播放器 */ -(AVAudioPlayer *)audioPlayer{ if (!_audioPlayer) { NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil]; NSURL *url=[NSURL fileURLWithPath:urlStr]; NSError *error=nil; //初始化播放器,注意這裏的Url參數只能時文件路徑,不支持HTTP Url _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; //設置播放器屬性 _audioPlayer.numberOfLoops=0;//設置爲0不循環 _audioPlayer.delegate=self; [_audioPlayer prepareToPlay];//加載音頻文件到緩存 if(error){ NSLog(@"初始化播放器過程發生錯誤,錯誤信息:%@",error.localizedDescription); return nil; } //設置後臺播放模式 AVAudioSession *audioSession=[AVAudioSession sharedInstance]; [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; // [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil]; [audioSession setActive:YES error:nil]; //添加通知,拔出耳機後暫停播放 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil]; } return _audioPlayer; } /** * 播放音頻 */ -(void)play{ if (![self.audioPlayer isPlaying]) { [self.audioPlayer play]; self.timer.fireDate=[NSDate distantPast];//恢復定時器 } } /** * 暫停播放 */ -(void)pause{ if ([self.audioPlayer isPlaying]) { [self.audioPlayer pause]; self.timer.fireDate=[NSDate distantFuture];//暫停定時器,注意不能調用invalidate方法,此方法會取消,之後無法恢復 } } /** * 點擊播放/暫停按鈕 * * @param sender 播放/暫停按鈕 */ - (IBAction)playClick:(UIButton *)sender { if(sender.tag){ sender.tag=0; [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted]; [self pause]; }else{ sender.tag=1; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted]; [self play]; } } /** * 更新播放進度 */ -(void)updateProgress{ float progress= self.audioPlayer.currentTime /self.audioPlayer.duration; [self.playProgress setProgress:progress animated:true]; } /** * 一旦輸出改變則執行此方法 * * @param notification 輸出改變通知對象 */ -(void)routeChange:(NSNotification *)notification{ NSDictionary *dic=notification.userInfo; int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue]; //等於AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示舊輸出不可用 if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey]; AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject]; //原設備爲耳機則暫停 if ([portDescription.portType isEqualToString:@"Headphones"]) { [self pause]; } } // [dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { // NSLog(@"%@:%@",key,obj); // }]; } -(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; } #pragma mark - 播放器代理方法 -(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ NSLog(@"音樂播放完成..."); //根據實際情況播放完成可以將會話關閉,其他音頻應用繼續播放 [[AVAudioSession sharedInstance]setActive:NO error:nil]; } @end
在上面的代碼中還實現了拔出耳機暫停音樂播放的功能,這也是一個比較常見的功能。在iOS7及以後的版本中可以通過通知獲得輸出改變的通知,然後拿到通知對象後根據userInfo獲得是何種改變類型,進而根據情況對音樂進行暫停操作。
擴展--播放音樂庫中的音樂
衆所周知音樂是iOS的重要組成播放,無論是iPod、iTouch、iPhone還是iPad都可以在iTunes購買音樂或添加本地音樂到音樂庫中同步到你的iOS設備。在MediaPlayer.frameowork中有一個MPMusicPlayerController用於播放音樂庫中的音樂。
下面先來看一下MPMusicPlayerController的常用屬性和方法:
屬性 | 說明 |
@property (nonatomic, readonly) MPMusicPlaybackState playbackState |
播放器狀態,枚舉類型: MPMusicPlaybackStateStopped:停止播放 MPMusicPlaybackStatePlaying:正在播放 MPMusicPlaybackStatePaused:暫停播放 MPMusicPlaybackStateInterrupted:播放中斷 MPMusicPlaybackStateSeekingForward:向前查找 MPMusicPlaybackStateSeekingBackward:向後查找 |
@property (nonatomic) MPMusicRepeatMode repeatMode |
重複模式,枚舉類型: MPMusicRepeatModeDefault:默認模式,使用用戶的首選項(系統音樂程序設置) MPMusicRepeatModeNone:不重複 MPMusicRepeatModeOne:單曲循環 MPMusicRepeatModeAll:在當前列表內循環 |
@property (nonatomic) MPMusicShuffleMode shuffleMode |
隨機播放模式,枚舉類型: MPMusicShuffleModeDefault:默認模式,使用用戶首選項(系統音樂程序設置) MPMusicShuffleModeOff:不隨機播放 MPMusicShuffleModeSongs:按歌曲隨機播放 MPMusicShuffleModeAlbums:按專輯隨機播放 |
@property (nonatomic, copy) MPMediaItem *nowPlayingItem | 正在播放的音樂項 |
@property (nonatomic, readonly) NSUInteger indexOfNowPlayingItem | 當前正在播放的音樂在播放隊列中的索引 |
@property(nonatomic, readonly) BOOL isPreparedToPlay | 是否準好播放準備 |
@property(nonatomic) NSTimeInterval currentPlaybackTime | 當前已播放時間,單位:秒 |
@property(nonatomic) float currentPlaybackRate | 當前播放速度,是一個播放速度倍率,0表示暫停播放,1代表正常速度 |
類方法 | 說明 |
+ (MPMusicPlayerController *)applicationMusicPlayer; | 獲取應用播放器,注意此類播放器無法在後臺播放 |
+ (MPMusicPlayerController *)systemMusicPlayer | 獲取系統播放器,支持後臺播放 |
對象方法 | 說明 |
- (void)setQueueWithQuery:(MPMediaQuery *)query | 使用媒體隊列設置播放源媒體隊列 |
- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection | 使用媒體項集合設置播放源媒體隊列 |
- (void)skipToNextItem | 下一曲 |
- (void)skipToBeginning | 從起始位置播放 |
- (void)skipToPreviousItem | 上一曲 |
- (void)beginGeneratingPlaybackNotifications | 開啓播放通知,注意不同於其他播放器,MPMusicPlayerController要想獲得通知必須首先開啓,默認情況無法獲得通知 |
- (void)endGeneratingPlaybackNotifications | 關閉播放通知 |
- (void)prepareToPlay | 做好播放準備(加載音頻到緩衝區),在使用play方法播放時如果沒有做好準備回自動調用該方法 |
- (void)play | 開始播放 |
- (void)pause | 暫停播放 |
- (void)stop | 停止播放 |
- (void)beginSeekingForward | 開始向前查找(快進) |
- (void)beginSeekingBackward | 開始向後查找(快退) |
- (void)endSeeking | 結束查找 |
通知 |
說明 (注意:要想獲得MPMusicPlayerController通知必須首先調用beginGeneratingPlaybackNotifications開啓通知) |
MPMusicPlayerControllerPlaybackStateDidChangeNotification | 播放狀態改變 |
MPMusicPlayerControllerNowPlayingItemDidChangeNotification | 當前播放音頻改變 |
MPMusicPlayerControllerVolumeDidChangeNotification | 聲音大小改變 |
MPMediaPlaybackIsPreparedToPlayDidChangeNotification | 準備好播放 |
- MPMusicPlayerController有兩種播放器:applicationMusicPlayer和systemMusicPlayer,前者在應用退出後音樂播放會自動停止,後者在應用停止後不會退出播放狀態。
- MPMusicPlayerController加載音樂不同於前面的AVAudioPlayer是通過一個文件路徑來加載,而是需要一個播放隊列。在MPMusicPlayerController中提供了兩個方法來加載播放隊列:- (void)setQueueWithQuery:(MPMediaQuery *)query和- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection,正是由於它的播放音頻來源是一個隊列,因此MPMusicPlayerController支持上一曲、下一曲等操作。
那麼接下來的問題就是如何獲取MPMediaQueue或者MPMediaItemCollection?MPMediaQueue對象有一系列的類方法來獲得媒體隊列:
+ (MPMediaQuery *)albumsQuery;
+ (MPMediaQuery *)artistsQuery;
+ (MPMediaQuery *)songsQuery;
+ (MPMediaQuery *)playlistsQuery;
+ (MPMediaQuery *)podcastsQuery;
+ (MPMediaQuery *)audiobooksQuery;
+ (MPMediaQuery *)compilationsQuery;
+ (MPMediaQuery *)composersQuery;
+ (MPMediaQuery *)genresQuery;
有了這些方法,就可以很容易獲到歌曲、播放列表、專輯媒體等媒體隊列了,這樣就可以通過:- (void)setQueueWithQuery:(MPMediaQuery *)query方法設置音樂來源了。又或者得到MPMediaQueue之後創建MPMediaItemCollection,使用- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection設置音樂來源。
有時候可能希望用戶自己來選擇要播放的音樂,這時可以使用MPMediaPickerController,它是一個視圖控制器,類似於UIImagePickerController,選擇完播放來源後可以在其代理方法中獲得MPMediaItemCollection對象。
無論是通過哪種方式獲得MPMusicPlayerController的媒體源,可能都希望將每個媒體的信息顯示出來,這時候可以通過MPMediaItem對象獲得。一個MPMediaItem代表一個媒體文件,通過它可以訪問媒體標題、專輯名稱、專輯封面、音樂時長等等。無論是MPMediaQueue還是MPMediaItemCollection都有一個items屬性,它是MPMediaItem數組,通過這個屬性可以獲得MPMediaItem對象。
下面就簡單看一下MPMusicPlayerController的使用,在下面的例子中簡單演示了音樂的選擇、播放、暫停、通知、下一曲、上一曲功能,相信有了上面的概念,代碼讀起來並不複雜(示例中是直接通過MPMeidaPicker進行音樂選擇的,但是仍然提供了兩個方法getLocalMediaQuery和getLocalMediaItemCollection來演示如何直接通過MPMediaQueue獲得媒體隊列或媒體集合):
// // ViewController.m // MPMusicPlayerController // // Created by Kenshin Cui 14/03/30 // Copyright (c) 2014年 cmjstudio. All rights reserved. // #import "ViewController.h" #import <MediaPlayer/MediaPlayer.h> @interface ViewController ()<MPMediaPickerControllerDelegate> @property (nonatomic,strong) MPMediaPickerController *mediaPicker;//媒體選擇控制器 @property (nonatomic,strong) MPMusicPlayerController *musicPlayer; //音樂播放器 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } -(void)dealloc{ [self.musicPlayer endGeneratingPlaybackNotifications]; } /** * 獲得音樂播放器 * * @return 音樂播放器 */ -(MPMusicPlayerController *)musicPlayer{ if (!_musicPlayer) { _musicPlayer=[MPMusicPlayerController systemMusicPlayer]; [_musicPlayer beginGeneratingPlaybackNotifications];//開啓通知,否則監控不到MPMusicPlayerController的通知 [self addNotification];//添加通知 //如果不使用MPMediaPickerController可以使用如下方法獲得音樂庫媒體隊列 //[_musicPlayer setQueueWithItemCollection:[self getLocalMediaItemCollection]]; } return _musicPlayer; } /** * 創建媒體選擇器 * * @return 媒體選擇器 */ -(MPMediaPickerController *)mediaPicker{ if (!_mediaPicker) { //初始化媒體選擇器,這裏設置媒體類型爲音樂,其實這裏也可以選擇視頻、廣播等 // _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeMusic]; _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeAny]; _mediaPicker.allowsPickingMultipleItems=YES;//允許多選 // _mediaPicker.showsCloudItems=YES;//顯示icloud選項 _mediaPicker.prompt=@"請選擇要播放的音樂"; _mediaPicker.delegate=self;//設置選擇器代理 } return _mediaPicker; } /** * 取得媒體隊列 * * @return 媒體隊列 */ -(MPMediaQuery *)getLocalMediaQuery{ MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery]; for (MPMediaItem *item in mediaQueue.items) { NSLog(@"標題:%@,%@",item.title,item.albumTitle); } return mediaQueue; } /** * 取得媒體集合 * * @return 媒體集合 */ -(MPMediaItemCollection *)getLocalMediaItemCollection{ MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery]; NSMutableArray *array=[NSMutableArray array]; for (MPMediaItem *item in mediaQueue.items) { [array addObject:item]; NSLog(@"標題:%@,%@",item.title,item.albumTitle); } MPMediaItemCollection *mediaItemCollection=[[MPMediaItemCollection alloc]initWithItems:[array copy]]; return mediaItemCollection; } #pragma mark - MPMediaPickerController代理方法 //選擇完成 -(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection{ MPMediaItem *mediaItem=[mediaItemCollection.items firstObject];//第一個播放音樂 //注意很多音樂信息如標題、專輯、表演者、封面、時長等信息都可以通過MPMediaItem的valueForKey:方法得到,但是從iOS7開始都有對應的屬性可以直接訪問 // NSString *title= [mediaItem valueForKey:MPMediaItemPropertyAlbumTitle]; // NSString *artist= [mediaItem valueForKey:MPMediaItemPropertyAlbumArtist]; // MPMediaItemArtwork *artwork= [mediaItem valueForKey:MPMediaItemPropertyArtwork]; //UIImage *image=[artwork imageWithSize:CGSizeMake(100, 100)];//專輯圖片 NSLog(@"標題:%@,表演者:%@,專輯:%@",mediaItem.title ,mediaItem.artist,mediaItem.albumTitle); [self.musicPlayer setQueueWithItemCollection:mediaItemCollection]; [self dismissViewControllerAnimated:YES completion:nil]; } //取消選擇 -(void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker{ [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - 通知 /** * 添加通知 */ -(void)addNotification{ NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(playbackStateChange:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.musicPlayer]; } /** * 播放狀態改變通知 * * @param notification 通知對象 */ -(void)playbackStateChange:(NSNotification *)notification{ switch (self.musicPlayer.playbackState) { case MPMusicPlaybackStatePlaying: NSLog(@"正在播放..."); break; case MPMusicPlaybackStatePaused: NSLog(@"播放暫停."); break; case MPMusicPlaybackStateStopped: NSLog(@"播放停止."); break; default: break; } } #pragma mark - UI事件 - (IBAction)selectClick:(UIButton *)sender { [self presentViewController:self.mediaPicker animated:YES completion:nil]; } - (IBAction)playClick:(UIButton *)sender { [self.musicPlayer play]; } - (IBAction)puaseClick:(UIButton *)sender { [self.musicPlayer pause]; } - (IBAction)stopClick:(UIButton *)sender { [self.musicPlayer stop]; } - (IBAction)nextClick:(UIButton *)sender { [self.musicPlayer skipToNextItem]; } - (IBAction)prevClick:(UIButton *)sender { [self.musicPlayer skipToPreviousItem]; } @end