1. 程式人生 > >IOS後臺執行 之 後臺播放音樂

IOS後臺執行 之 後臺播放音樂

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傳送訊息通知給相應的函式處理。

 ///////////////////////////////////////////////////////////////////////////

IOS開發之----詳解在IOS後臺執行

 (2013-06-21 14:36:37) 轉載
標籤: 

it

分類: IOS開發

文一

我從蘋果文件中得知,一般的應用在進入後臺的時候可以獲取一定時間來執行相關任務,也就是說可以在後臺執行一小段時間。


還有三種類型的可以執行在後以,
1.音樂
2.location

3.voip

文二

在IOS後臺執行是本文要介紹的內容,大多數應用程式進入後臺狀態不久後轉入暫停狀態。在這種狀態下,應用程式不執行任何程式碼,並有可能在任意時候從記憶體中刪除。應用程式提供特定的服務,使用者可以請求後臺執行時間,以提供這些服務。

判斷是否支援多執行緒

  1. UIDevice* device = [UIDevice currentDevice];
  2. BOOL backgroundSupported = NO;
  3. if ([device respondsToSelector:@selector(isMultitaskingSupported)])
  4. backgroundSupported = device.multitaskingSupported;

宣告你需要的後臺任務

Info.plist中新增UIBackgroundModes鍵值,它包含一個或多個string的值,包括

audio:在後臺提供聲音播放功能,包括音訊流和播放視訊時的聲音

location:在後臺可以保持使用者的位置資訊

voip:在後臺使用VOIP功能

前面的每個value讓系統知道你的應用程式應該在適當的時候被喚醒。例如,一個應用程式,開始播放音樂,然後移動到後臺仍然需要執行時間,以填補音訊輸出緩衝區。新增audio鍵用來告訴系統框架,需要繼續播放音訊,並且可以在合適的時間間隔下回調應用程式;如果應用程式不包括此項,任何音訊播放在移到後臺後將停止執行。

除了新增鍵值的方法,IOS還提供了兩種途徑使應用程式在後臺工作:

Task completion—應用程式可以向系統申請額外的時間去完成給定的任務

Local notifications—應用程式可以預先安排時間執行local notifications 傳遞

文三

如何讓程式後臺播放音樂

http://developer.apple.com/library/ios/#qa/qa1668/_index.html

文四

如果你的應用程式需要後臺執行,可以使用以下方法:

1。應用程式可以請求一個有限的時間內完成一些重要任務

2。應用程式可以宣告為支援特定服務需要定期後臺執行時間

3。應用程式可以使用本地生成使用者在指定的時間警報,應用程式正在執行與否通知

文五

後臺執行被第一次提到

http://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS4.html#//apple_ref/doc/uid/TP40009559-SW1

文六

後臺執行官方文件

http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW3

在IOS後臺執行是本文要介紹的內容,大多數應用程式進入後臺狀態不久後轉入暫停狀態。在這種狀態下,應用程式不執行任何程式碼,並有可能在任意時候從記憶體中刪除。應用程式提供特定的服務,使用者可以請求後臺執行時間,以提供這些服務。

判斷是否支援多執行緒

  1. UIDevice* device = [UIDevice currentDevice];
  2. BOOL backgroundSupported = NO;
  3. if ([device respondsToSelector:@selector(isMultitaskingSupported)])
  4. backgroundSupported = device.multitaskingSupported;

宣告你需要的後臺任務

Info.plist中新增UIBackgroundModes鍵值,它包含一個或多個string的值,包括

audio:在後臺提供聲音播放功能,包括音訊流和播放視訊時的聲音

location:在後臺可以保持使用者的位置資訊

voip:在後臺使用VOIP功能

前面的每個value讓系統知道你的應用程式應該在適當的時候被喚醒。例如,一個應用程式,開始播放音樂,然後移動到後臺仍然需要執行時間,以填補音訊輸出緩衝區。新增audio鍵用來告訴系統框架,需要繼續播放音訊,並且可以在合適的時間間隔下回調應用程式;如果應用程式不包括此項,任何音訊播放在移到後臺後將停止執行。

除了新增鍵值的方法,IOS還提供了兩種途徑使應用程式在後臺工作:

Task completion—應用程式可以向系統申請額外的時間去完成給定的任務

Local notifications—應用程式可以預先安排時間執行local notifications 傳遞

實現長時間的後臺任務

應用程式可以請求在後臺執行以實現特殊的服務。這些應用程式並不連續的執行,但是會被系統框架在合適的時間喚醒,以實現這些服務

1、 追蹤使用者位置:略

2、在後臺播放音訊:

新增UIBackgroundModes中audio值,註冊後臺音訊應用。這個值使得應用程式可以在後臺使用可聽的背景,如音樂播放或者音訊流應用。對於支援音訊和視訊功能的應用程式也可以新增該值以保證可以繼續持續的執行流。

當audio值設定後,當你的應用程式進入後臺後,系統的多媒體框架會自動阻止它被結束通話,但是,如果應用程式停止播放音訊或者視訊,系統將結束通話應用程式。

當你的應用程式在後臺時,你可以執行任意的系統音訊框架去初始化後臺音訊。你的應用程式在後臺時應該限制自身,使其執行與工作相關的程式碼,不能執行任何與播放內容無關的任務

由於有多個應用程式支援音訊,前臺的應用程式始終允許播放音訊,後臺的應用程式也被允許播放一些音訊內容,這取決於audio session object的設定。應用程式應該始終設定它們的audio session object,並小心的處理其他型別的音訊相關notifications和中斷。詳見audio session programming guide。

3、實現VOIP應用:

VOIP程式需要穩定的網路去連線和它相關的服務,這樣它才能接到來電和其他相關的資料。系統允許VOIP程式被結束通話並提供元件去監聽它們的sockets,而不是在任意時候都處於喚醒狀態。設定VOIP應用程式如下:

A、 新增UIBackgroundModes中的VOIP鍵值

B、 為VOIP設定一個應用程式socket

C、在移出後臺之前,呼叫setKeepAliveTimeout:handler:方法去建立一個定期執行的handler,你的應用程式可以執行這個handler來保持服務的連線。

D、 設定你的audio session去處理這種切換

釋義:

A、大多數VOIP應用需要設定後臺audio 應用去傳遞音訊,因此你應該設定audio 和voip兩個鍵值。

B、為了使應用程式在後臺時保持穩定的連線,你必須tag你的主通訊socket專門應用於VOIP,tagging這個socket來告訴系統,它必須在你的應用程式中斷時接管這個socket。這個切換本身對於你的應用程式時透明的,當新的資料到達socket的時候,系統會喚醒應用程式,並將socket的控制權返回給應用程式,這樣應用程式就可以處理新來的資料。

你只需要tag用於voip服務的socket,這個socket用來接收來電或者其他相關的資料來保持你的VOIP服務的連線。根據收到的資訊,這個socket要決定下一步的動作。比如一個來電,你會想彈出一個本地的通知來告知使用者;對於其他不是那麼關鍵的資料,你可能會想悄悄的處理這些資料並讓系統將應用程式重新中斷。

在IOS中,sockets是用流或者更高階的結構,設定一個VOIP的socket,你只需要在通常的設定中新增一個特殊的key來標明這個介面是用於連線VOIP服務的,下表列出了流的介面和設定:

設定流介面用於voip

介面

設定

  1. NSInputStream 和NSOutputStream

對於 Cocoa streams, 使用 setProperty:forKey: 方法新增

  1. NSStreamNetworkServiceType
  2. 屬性給
  3. stream.
  4. 改屬性的值設為
  5. NSStreamNetworkServiceTypeVoIP.
  6. NSURLRequest

對於 URL loading system, 使用 setNetworkServiceType:

  1. method of your NSMutableURLRequest object to set the network service
  2. type of the request. The service type should be set to
  3. NSURLNetworkServiceTypeVoIP.

CFReadStreamRef和CFWriteStreamRef

  1. For Core Foundation streams, use the CFReadStreamSetProperty or
  2. CFWriteStreamSetProperty function to add the kCFStreamNetwork-
  3. ServiceType property to the stream. The value for this property should be
  4. set to kCFStreamNetworkServiceTypeVoIP.

(注意:當設定socket的時候,你需要在你的主訊號通道中設定合適的service type key。當設定聲道時,不需要設定這個key)

由於,VOIP應用程式需要一直執行以確保收到來電,所以如果程式通過一個非零的exit code退出,系統將自動重啟這個應用程式(這種退出方式可以發生在記憶體壓力大時終止程式執行)。儘管如此,中斷應用程式會release所有的sockets,包括那個用於連線voip 服務的socket。因此,當程式執行時,它需要一直從頭建立socket。

C、為了防止斷連,voip程式需要定期被喚醒去檢查它的服務。為了容易實現這個行為,IOS通過使用(UIApplication setKeepAliveTimeout:handler:)方法建立一個特殊的控制代碼。你可以在applicationDidEnterBackground方法中建立該控制代碼。一旦建立,系統至少會在超時之前呼叫該控制代碼一次,來喚醒你的應用程式。

這個keep-alive handler在後臺執行,必須儘快的返回引數,它有最多30秒的時間來執行所需的任務,如果這段時間內控制代碼沒有返回,那麼系統將終止應用程式。

當你建立了handler之後,確定應用程式所需的最大超時。系統保證會在最大超時之前呼叫handler,但是這個時間是不確定的,所以你的handler必須在你申明的超時之前做好執行程式的準備。

D、設定audio session,詳見Audio Session Programming Guide.

在後臺完成有限長度的任務

在被終止之前的任意時間,應用程式會呼叫beginBackgroundTaskWithExpirationHandler:方法讓系統給出額外的時間來完成一些需要在後臺長時間執行的任務。(UIApplication的backgroundTimeRemaining屬性包含程式執行的總時間)

可以使用task completion去保證那些比較重要但是需要長時間執行的程式不會由於使用者切入後臺而突然關閉。比如,你可以用這項功能來將使用者的資訊儲存到disk上或者從網路下載一個重要的檔案。有兩種方式來初始化這樣的任務:

1、將長時間執行的重要任務用beginBackgroundTaskWithExpirationHandler:和endBackgroundTask:包裝。這樣就在程式突然切入後臺的時候保護了這些任務不被中斷。

2、當你的應用程式委託applicationDidEnterBackground:方法被呼叫時再啟動任務

中的兩個方法必須是一一對應的,endBackgroundTask:方法告訴系統任務已經完成,程式在此時可以被終止。由於應用程式只有有限的時間去完成後臺任務,你必須在超時或系統將要終止這個程式之前呼叫這個方法。為了避免被終止,你也可以在一個任務開始的時候提供一個expiration handler和endBackgroundTask:方法。(可以檢視backgroundTimeRemaining屬性來確定還剩多少時間)。

一個程式可以同時提供多個任務,每當你啟動一個任務的時候,beginBackgroundTaskWithExpirationHandler:方法將返回一個獨一無二的handler去識別這個任務。你必須在endBackgroundTask:方法中傳遞相同的handler來終止該任務。

  1. Listing 4-2 Starting a background task at quit time
  2. - (void)applicationDidEnterBackground:(UIApplication *)application
  3. {
  4. UIApplication* app = [UIApplication sharedApplication];
  5. bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
  6. [app endBackgroundTask:bgTask];
  7. bgTask = UIBackgroundTaskInvalid;
  8. }];
  9. // Start the long-running task and return immediately.
  10. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
  11. 0), ^{
  12. // Do the work associated with the task.
  13. [app endBackgroundTask:bgTask];
  14. bgTask = UIBackgroundTaskInvalid;
  15. });
  16. }

上述例子中,bgTask變數是一個類的成員變數,儲存著指向該後臺任務標示的指標。

在expriation handler中,可以新增關閉任務所需的程式碼。儘管如此,加入的程式碼不能執行太長的時間,當expriation handler被呼叫的時候,該程式已經非常接近被關閉,所以只有極短的時間來清除狀態資訊並終止任務。

安排Local Notification的傳遞

UILocalNotification類提供了一種方法來傳遞local notifications。和push notifications需要設定remote server不同,local notifications 在程式中安排並在當前的裝置上執行。滿足如下條件可以使用該能力:

1、一個基於時間的程式,可以在將來特定的時間讓程式post 一個alert,比如鬧鐘

2、一個在後臺執行的程式,post 一個local notification去引起使用者的注意

為了安排local notification 的傳遞,需要建立一個UILocalNotification的例項,並設定它,使用UIApplication類方法來安排它。Local notification物件包含了所要傳遞的型別(sound,alert,或者badge)和時間何時呈現)。UIApplication類方法提供選項