iOS 4開始引入的multitask,我們可以實現像ipod程序那樣在后台播放音頻了。如果音頻操作是用蘋果官方的AVFoundation.framework實現,像用AvAudioPlayer,AvPlayer播放的話,要實現完美的后台音頻播放,依據app的功能需要,可能需要實現幾個關鍵的功能。
首先,播放音頻之前先要設置AVAudioSession模式,通常只用來播放的App可以設為AVAudioSessionCategoryPlayback即可。模式意義及其他模式請參考文檔。
1 //后台播放音頻設置 2 AVAudioSession *session = [AVAudioSession sharedInstance]; 3 [session setActive:YES error:nil]; 4 [session setCategory:AVAudioSessionCategoryPlayback error:nil];
1.通知IOS該app支持background audio。缺省情況下,當按下home鍵時,當前正在運行的程序被suspend,狀態從active變成in-active,也就是說如果正在播放音頻,按下HOME后就會停止。這里需要讓app在按在HOME后,轉到后台運行而非被suspend,解決辦法是在程序的-info.plist中增加required background modes這個key項,並選擇App plays audio or streams audio/video using AirPlay這個value項(如果用過Xcode5.0,在TARGETS-Capabilities-Background Modes設置為ON,勾選Audio and AirPlay選項)。
2.如果你在后台播放使用的時加載網絡音頻,恰巧網速很慢,音頻被停止下來這時候程序也隨之suspend,曾經有山寨的解決辦法是專門起一個player的實例連續不停的放同一無聲音片斷,阻止程序被suspend。這里提供的方法是通過申請后台taskID達到后台切換播放文件的功能。
即使聲明taskID也最多只能在后台運行600秒鍾。(在ios7sdk中可以使用NSURLSession來實現后台緩沖)
(一般情況下,按HOME將程序送到后台,可以有5或10秒時間可以進行一些收尾工作,具體時間[[UIApplication sharedApplication] backgroundTimeRemaining]返回值,超時后app會被suspend。)
3.ipod播放程序在后台時,雙擊HOME鍵,會有個控制界面,可以對它進行播放控制(暫停開始、上一曲、下一曲)。如果您想讓您的app可以像ipod一樣在后台也可以方便的通過雙擊HOME鍵來控制(在ios7中是使用上拉菜單控制),就要用到遠程控制事件了。
首先在viewdidload等初始化的地方聲明App接收遠程控制事件,並在相應地方結束聲明
1 - (void) viewWillAppear:(BOOL)animated 2 { 3 [super viewWillAppear:animated]; 4 [UIApplication sharedApplication] beginReceivingRemoteControlEvents]; 5 [self becomeFirstResponder]; 6 } 7 8 - (void) viewWillDisappear:(BOOL)animated 9 { 10 [super viewWillDisappear:animated]; 11 [UIApplication sharedApplication] endReceivingRemoteControlEvents]; 12 [self resignFirstResponder]; 13 } 14 15 - (BOOL)canBecomeFirstResponder 16 { 17 return YES; 18 }
當然也不一定是在viewcontroller中,也可以是在applicationDidEnterBackground:方法中開始接受遠程控制,applicationDidBecomeActive:中結束接受遠程控制,但是當前的appdelegate中要繼承與UIResponder,因為在激活遠程控制以后要把當前類變成第一響應,重寫canBecomeFirstResponder方法。
最后定義 remoteControlReceivedWithEvent,處理具體的播放、暫停、前進、后退等具體事件
1 //重寫父類方法,接受外部事件的處理 2 - (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent { 3 if (receivedEvent.type == UIEventTypeRemoteControl) { 4 5 switch (receivedEvent.subtype) { 6 7 case UIEventSubtypeRemoteControlTogglePlayPause: 8 [self playAndStopSong:self.playButton]; 9 break; 10 11 case UIEventSubtypeRemoteControlPreviousTrack: 12 [self playLastButton:self.lastButton]; 13 break; 14 15 case UIEventSubtypeRemoteControlNextTrack: 16 [self playNextSong:self.nextButton]; 17 break; 18 19 case UIEventSubtypeRemoteControlPlay: 20 [self playAndStopSong:self.playButton]; 21 break; 22 23 case UIEventSubtypeRemoteControlPause: 24 [self playAndStopSong:self.playButton]; 25 break; 26 27 default: 28 break; 29 } 30 } 31 }
其它外部事件也可通過這種方式實現,如“搖一搖”響應等。
4. 至此,您有播放App已經基本完成了,其次插拔耳機是否響應停止播放時間需要進一步研究耳機檢測和聲音路由切換的問題,再次不詳細講述。
5. 還有一些開發者可能會發現,有一些音視頻app在定義的時候自定一些控件可以調節系統的音量大小,不需要用戶調整音量按鈕。經查看相關的資料總結出有兩種方法:
一種是調用控件MPVolumeView在屏幕中創建一個音量條,拖動可以改變系統的音量大小。
另一種是使用MPMusicPlayerController類,可以自定義控件調整系統音量的大小(但是在ios7sdk中已經被棄用,估計以后幾個版本中可能找不到這個方法了)。
1 MPMusicPlayerController *mpc = [MPMusicPlayerController applicationMusicPlayer]; 2 mpc.volume = 0; //0.0~1.0
6. 在一些其他的音樂播放軟件中如:酷我、qq音樂等,你會發在播放的時候,當設備鎖屏以后依然可以看到用戶播放的音樂名稱、演唱者、專輯名稱、音樂時長、專輯圖片等信息。這些就需要在用戶切換完歌去的時候,在程序中設置信息了。
1 //設置鎖屏狀態,顯示的歌曲信息 2 -(void)configNowPlayingInfoCenter{ 3 if (NSClassFromString(@"MPNowPlayingInfoCenter")) { 4 NSDictionary *info = [self.musicList objectAtIndex:_playIndex]; 5 NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; 6 7 //歌曲名稱 8 [dict setObject:[info objectForKey:@"name"] forKey:MPMediaItemPropertyTitle]; 9 10 //演唱者 11 [dict setObject:[info objectForKey:@"singer"] forKey:MPMediaItemPropertyArtist]; 12 13 //專輯名 14 [dict setObject:[info objectForKey:@"album"] forKey:MPMediaItemPropertyAlbumTitle]; 15 16 //專輯縮略圖 17 UIImage *image = [UIImage imageNamed:[info objectForKey:@"image"]]; 18 MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image]; 19 [dict setObject:artwork forKey:MPMediaItemPropertyArtwork]; 20 21 //音樂剩余時長 22 [dict setObject:[NSNumber numberWithDouble:self.player.duration] forKey:MPMediaItemPropertyPlaybackDuration]; 23 24 //音樂當前播放時間 在計時器中修改 25 //[dict setObject:[NSNumber numberWithDouble:0.0] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; 26 27 //設置鎖屏狀態下屏幕顯示播放音樂信息 28 [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict]; 29 } 30 }
上面的if (NSClassFromString(@"MPNowPlayingInfoCenter"))語句,說是為了避免了版本兼容問題,這個API貌似只出現在5里面。
7. 下面就在計時器中不斷刷新鎖屏狀態下的播放進度條了。
1 //計時器修改進度 2 - (void)changeProgress:(NSTimer *)sender{ 3 if(self.player){ 4 //當前播放時間 5 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo]]; 6 [dict setObject:[NSNumber numberWithDouble:self.player.currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; //音樂當前已經過時間 7 [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict]; 8 9 } 10 }
8. 當前的很多常見的播放器都可以在鎖屏狀態下顯示顯示歌詞,經過一番查找后,終於找到方法(詳情:點擊查看),大致就是根據播放的時間和歌詞顯示時間,利用計時器不斷的用歌詞和專輯封面合成圖片,達到顯示歌詞的效果。還有就是在屏幕變暗停止這一操作、屏幕點亮的時候開始計時器,以節省電量和cpu,有兩種方法可以監聽上述現象:
一種是監聽內核層DarwinNotification,在Darwin中,有很多的系統事件,但apple的api文檔描述這些api使用有限制,也就是灰色地帶的api,所以能不用則不用;
另一種方法可以通過notify_get_state來獲取com.apple.springboard.hasBlankedScreen 的狀態值,通過狀態值我們可以判斷屏幕狀態,屏幕亮或者暗系統會給出不同狀態值,然后根據狀態值,通過NotificationCenter發送消息通知給相應的函數處理。