
iOS 4開(kāi)始引入的multitask,我們可以實(shí)現(xiàn)像ipod程序那樣在后臺(tái)播放音頻了。如果音頻操作是用蘋(píng)果官方的AVFoundation.framework實(shí)現(xiàn),像用AvAudioPlayer,AvPlayer播放的話(huà),要實(shí)現(xiàn)完美的后臺(tái)音頻播放,依據(jù)app的功能需要,可能需要實(shí)現(xiàn)幾個(gè)關(guān)鍵的功能。

成都創(chuàng)新互聯(lián)主營(yíng)當(dāng)涂網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,手機(jī)APP定制開(kāi)發(fā),當(dāng)涂h5小程序設(shè)計(jì)搭建,當(dāng)涂網(wǎng)站營(yíng)銷(xiāo)推廣歡迎當(dāng)涂等地區(qū)企業(yè)咨詢(xún)
首先,播放音頻之前先要設(shè)置AVAudioSession模式,通常只用來(lái)播放的App可以設(shè)為AVAudioSessionCategoryPlayback即可。模式意義及其他模式請(qǐng)參考文檔。
1 //后臺(tái)播放音頻設(shè)置
2 AVAudioSession *session = [AVAudioSession sharedInstance];
3 [session setActive:YES error:nil];
4 [session setCategory:AVAudioSessionCategoryPlayback error:nil];
1.通知IOS該app支持background audio。缺省情況下,當(dāng)按下home鍵時(shí),當(dāng)前正在運(yùn)行的程序被suspend,狀態(tài)從active變成in-active,也就是說(shuō)如果正在播放音頻,按下HOME后就會(huì)停止。這里需要讓app在按在HOME后,轉(zhuǎn)到后臺(tái)運(yùn)行而非被suspend,解決辦法是在程序的-info.plist中增加required background modes這個(gè)key項(xiàng),并選擇App plays audio or streams audio/video using AirPlay這個(gè)value項(xiàng)(如果用過(guò)Xcode5.0,在TARGETS-Capabilities-Background Modes設(shè)置為ON,勾選Audio and AirPlay選項(xiàng))。
2.如果你在后臺(tái)播放使用的時(shí)加載網(wǎng)絡(luò)音頻,恰巧網(wǎng)速很慢,音頻被停止下來(lái)這時(shí)候程序也隨之suspend,曾經(jīng)有山寨的解決辦法是專(zhuān)門(mén)起一個(gè)player的實(shí)例連續(xù)不停的放同一無(wú)聲音片斷,阻止程序被suspend。這里提供的方法是通過(guò)申請(qǐng)后臺(tái)taskID達(dá)到后臺(tái)切換播放文件的功能。
即使聲明taskID也最多只能在后臺(tái)運(yùn)行600秒鐘。(在ios7sdk中可以使用NSURLSession來(lái)實(shí)現(xiàn)后臺(tái)緩沖)
(一般情況下,按HOME將程序送到后臺(tái),可以有5或10秒時(shí)間可以進(jìn)行一些收尾工作,具體時(shí)間[[UIApplication sharedApplication] backgroundTimeRemaining]返回值,超時(shí)后app會(huì)被suspend。)
3.ipod播放程序在后臺(tái)時(shí),雙擊HOME鍵,會(huì)有個(gè)控制界面,可以對(duì)它進(jìn)行播放控制(暫停開(kāi)始、上一曲、下一曲)。如果您想讓您的app可以像ipod一樣在后臺(tái)也可以方便的通過(guò)雙擊HOME鍵來(lái)控制(在ios7中是使用上拉菜單控制),就要用到遠(yuǎn)程控制事件了。
首先在viewdidload等初始化的地方聲明App接收遠(yuǎn)程控制事件,并在相應(yīng)地方結(jié)束聲明
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
當(dāng)然也不一定是在viewcontroller中,也可以是在applicationDidEnterBackground:方法中開(kāi)始接受遠(yuǎn)程控制,applicationDidBecomeActive:中結(jié)束接受遠(yuǎn)程控制,但是當(dāng)前的appdelegate中要繼承與UIResponder,因?yàn)樵诩せ钸h(yuǎn)程控制以后要把當(dāng)前類(lèi)變成第一響應(yīng),重寫(xiě)canBecomeFirstResponder方法。
最后定義?remoteControlReceivedWithEvent,處理具體的播放、暫停、前進(jìn)、后退等具體事件
//重寫(xiě)父類(lèi)方法,接受外部事件的處理
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
[self playAndStopSong:self.playButton];
break;
case UIEventSubtypeRemoteControlPreviousTrack:
[self playLastButton:self.lastButton];
break;
case UIEventSubtypeRemoteControlNextTrack:
[self playNextSong:self.nextButton];
break;
case UIEventSubtypeRemoteControlPlay:
[self playAndStopSong:self.playButton];
break;
case UIEventSubtypeRemoteControlPause:
[self playAndStopSong:self.playButton];
break;
default:
break;
}
}
}
其它外部事件也可通過(guò)這種方式實(shí)現(xiàn),如“搖一搖”響應(yīng)等。
4. 至此,您有播放App已經(jīng)基本完成了,其次插拔耳機(jī)是否響應(yīng)停止播放時(shí)間需要進(jìn)一步研究耳機(jī)檢測(cè)和聲音路由切換的問(wèn)題,再次不詳細(xì)講述。
5. 還有一些開(kāi)發(fā)者可能會(huì)發(fā)現(xiàn),有一些音視頻app在定義的時(shí)候自定一些控件可以調(diào)節(jié)系統(tǒng)的音量大小,不需要用戶(hù)調(diào)整音量按鈕。經(jīng)查看相關(guān)的資料總結(jié)出有兩種方法:
一種是調(diào)用控件MPVolumeView在屏幕中創(chuàng)建一個(gè)音量條,拖動(dòng)可以改變系統(tǒng)的音量大小。
另一種是使用MPMusicPlayerController類(lèi),可以自定義控件調(diào)整系統(tǒng)音量的大?。ǖ窃趇os7sdk中已經(jīng)被棄用,估計(jì)以后幾個(gè)版本中可能找不到這個(gè)方法了)。
MPMusicPlayerController *mpc = [MPMusicPlayerController applicationMusicPlayer];
mpc.volume = 0;? //0.0~1.0
6. 在一些其他的音樂(lè)播放軟件中如:酷我、qq音樂(lè)等,你會(huì)發(fā)在播放的時(shí)候,當(dāng)設(shè)備鎖屏以后依然可以看到用戶(hù)播放的音樂(lè)名稱(chēng)、演唱者、專(zhuān)輯名稱(chēng)、音樂(lè)時(shí)長(zhǎng)、專(zhuān)輯圖片等信息。這些就需要在用戶(hù)切換完歌去的時(shí)候,在程序中設(shè)置信息了。
//設(shè)置鎖屏狀態(tài),顯示的歌曲信息
-(void)configNowPlayingInfoCenter{
if (NSClassFromString(@"MPNowPlayingInfoCenter")) {
NSDictionary *info = [self.musicList objectAtIndex:_playIndex];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
//歌曲名稱(chēng)
[dict setObject:[info objectForKey:@"name"] forKey:MPMediaItemPropertyTitle];
//演唱者
[dict setObject:[info objectForKey:@"singer"] forKey:MPMediaItemPropertyArtist];
//專(zhuān)輯名
[dict setObject:[info objectForKey:@"album"] forKey:MPMediaItemPropertyAlbumTitle];
//專(zhuān)輯縮略圖
UIImage *image = [UIImage imageNamed:[info objectForKey:@"image"]];
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image];
[dict setObject:artwork forKey:MPMediaItemPropertyArtwork];
//音樂(lè)剩余時(shí)長(zhǎng)
[dict setObject:[NSNumber numberWithDouble:self.player.duration] forKey:MPMediaItemPropertyPlaybackDuration];
//音樂(lè)當(dāng)前播放時(shí)間 在計(jì)時(shí)器中修改
//[dict setObject:[NSNumber numberWithDouble:0.0] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
//設(shè)置鎖屏狀態(tài)下屏幕顯示播放音樂(lè)信息
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
}
}
上面的if?(NSClassFromString(@”MPNowPlayingInfoCenter”))語(yǔ)句,說(shuō)是為了避免了版本兼容問(wèn)題,這個(gè)API貌似只出現(xiàn)在5里面。
7. 下面就在計(jì)時(shí)器中不斷刷新鎖屏狀態(tài)下的播放進(jìn)度條了。
//計(jì)時(shí)器修改進(jìn)度
- (void)changeProgress:(NSTimer *)sender{
if(self.player){
//當(dāng)前播放時(shí)間
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo]];
[dict setObject:[NSNumber numberWithDouble:self.player.currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; //音樂(lè)當(dāng)前已經(jīng)過(guò)時(shí)間
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
}
}
8. 當(dāng)前的很多常見(jiàn)的播放器都可以在鎖屏狀態(tài)下顯示顯示歌詞,經(jīng)過(guò)一番查找后,終于找到方法(詳情: 點(diǎn)擊查看 ),大致就是根據(jù)播放的時(shí)間和歌詞顯示時(shí)間,利用計(jì)時(shí)器不斷的用歌詞和專(zhuān)輯封面合成圖片,達(dá)到顯示歌詞的效果。還有就是在屏幕變暗停止這一操作、屏幕點(diǎn)亮的時(shí)候開(kāi)始計(jì)時(shí)器,以節(jié)省電量和cpu,有兩種方法可以監(jiān)聽(tīng)上述現(xiàn)象:
一種是監(jiān)聽(tīng)內(nèi)核層DarwinNotification,在Darwin中,有很多的系統(tǒng)事件,但apple的api文檔描述這些api使用有限制,也就是灰色地帶的api,所以能不用則不用;
另一種方法可以通過(guò)notify_get_state來(lái)獲取com.apple.springboard.hasBlankedScreen?的狀態(tài)值,通過(guò)狀態(tài)值我們可以判斷屏幕狀態(tài),屏幕亮或者暗系統(tǒng)會(huì)給出不同狀態(tài)值,然后根據(jù)狀態(tài)值,通過(guò)NotificationCenter發(fā)送消息通知給相應(yīng)的函數(shù)處理。
一、簡(jiǎn)單介紹
簡(jiǎn)單來(lái)說(shuō),音頻可以分為2種
(1)音效
又稱(chēng)“短音頻”,通常在程序中的播放時(shí)長(zhǎng)為1~2秒
在應(yīng)用程序中起到點(diǎn)綴效果,提升整體用戶(hù)體驗(yàn)
(2)音樂(lè)
比如游戲中的“背景音樂(lè)”,一般播放時(shí)間較長(zhǎng)
框架:播放音頻需要用到AVFoundation.framework框架
二、音效的播放
1.獲得音效文件的路徑
復(fù)制代碼 代碼如下:
NSURL *url = [[NSBundle mainBundle] URLForResource:@"m_03.wav" withExtension:nil];
2.加載音效文件,得到對(duì)應(yīng)的音效ID
復(fù)制代碼 代碼如下:
SystemSoundID soundID = 0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(url), soundID);
3.播放音效
復(fù)制代碼 代碼如下:
AudioServicesPlaySystemSound(soundID);
注意:音效文件只需要加載1次
4.音效播放常見(jiàn)函數(shù)總結(jié)
加載音效文件
復(fù)制代碼 代碼如下:
AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID *outSystemSoundID)
釋放音效資源
復(fù)制代碼 代碼如下:
AudioServicesDisposeSystemSoundID(SystemSoundID inSystemSoundID)
播放音效
復(fù)制代碼 代碼如下:
AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)
播放音效帶點(diǎn)震動(dòng)
復(fù)制代碼 代碼如下:
AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID)
三、程序示例
先導(dǎo)入需要依賴(lài)的框架
導(dǎo)入需要播放的音效文件素材
說(shuō)明:AVFoundation.framework框架中的東西轉(zhuǎn)換為CF需要使用橋接。
代碼示例:
復(fù)制代碼 代碼如下:
YYViewController.m文件
//
// YYViewController.m
// 14-音效播放
//
// Created by apple on 14-8-8.
// Copyright (c) 2014年 yangyong. All rights reserved.
//
#import "YYViewController.h"
#import
@interface YYViewController ()
@end
復(fù)制代碼 代碼如下:
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//1.獲得音效文件的全路徑
NSURL *url=[[NSBundle mainBundle]URLForResource:@"buyao.wav" withExtension:nil];
//2.加載音效文件,創(chuàng)建音效ID(SoundID,一個(gè)ID對(duì)應(yīng)一個(gè)音效文件)
SystemSoundID soundID=0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, soundID);
//把需要銷(xiāo)毀的音效文件的ID傳遞給它既可銷(xiāo)毀
//AudioServicesDisposeSystemSoundID(soundID);
//3.播放音效文件
//下面的兩個(gè)函數(shù)都可以用來(lái)播放音效文件,第一個(gè)函數(shù)伴隨有震動(dòng)效果
AudioServicesPlayAlertSound(soundID);
//AudioServicesPlaySystemSound(#systemsoundid)
}
@end
說(shuō)明:點(diǎn)擊屏幕可以播放音效文件。
音樂(lè)的播放
一、簡(jiǎn)單說(shuō)明
音樂(lè)播放用到一個(gè)叫做AVAudioPlayer的`類(lèi),這個(gè)類(lèi)可以用于播放手機(jī)本地的音樂(lè)文件。
注意:
(1)該類(lèi)(AVAudioPlayer)只能用于播放本地音頻。
(2)時(shí)間比較短的(稱(chēng)之為音效)使用AudioServicesCreateSystemSoundID來(lái)創(chuàng)建,而本地時(shí)間較長(zhǎng)(稱(chēng)之為音樂(lè))使用AVAudioPlayer類(lèi)。
二、代碼示例
AVAudioPlayer類(lèi)依賴(lài)于AVFoundation框架,因此使用該類(lèi)必須先導(dǎo)入AVFoundation框架,并包含其頭文件(包含主頭文件即可)。
導(dǎo)入必要的,需要播放的音頻文件到項(xiàng)目中。
代碼示例:
復(fù)制代碼 代碼如下:
//
// YYViewController.m
// 15-播放音樂(lè)
//
#import "YYViewController.h"
#import
@interface YYViewController ()
@end
復(fù)制代碼 代碼如下:
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//1.音頻文件的url路徑
NSURL *url=[[NSBundle mainBundle]URLForResource:@"235319.mp3" withExtension:Nil];
//2.創(chuàng)建播放器(注意:一個(gè)AVAudioPlayer只能播放一個(gè)url)
AVAudioPlayer *audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:Nil];
//3.緩沖
[audioPlayer prepareToPlay];
//4.播放
[audioPlayer play];
}
@end
代碼說(shuō)明:運(yùn)行程序,點(diǎn)擊模擬器界面,卻并沒(méi)有能夠播放音頻文件,原因是代碼中創(chuàng)建的AVAudioPlayer播放器是一個(gè)局部變量,應(yīng)該調(diào)整為全局屬性。
可將代碼調(diào)整如下,即可播放音頻:
復(fù)制代碼 代碼如下:
#import "YYViewController.h"
#import
@interface YYViewController ()
@property(nonatomic,strong)AVAudioPlayer *audioplayer;
@end
復(fù)制代碼 代碼如下:
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//1.音頻文件的url路徑
NSURL *url=[[NSBundle mainBundle]URLForResource:@"235319.mp3" withExtension:Nil];
//2.創(chuàng)建播放器(注意:一個(gè)AVAudioPlayer只能播放一個(gè)url)
self.audioplayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:Nil];
//3.緩沖
[self.audioplayer prepareToPlay];
//4.播放
[self.audioplayer play];
}
@end
注意:一個(gè)AVAudioPlayer只能播放一個(gè)url,如果想要播放多個(gè)文件,那么就得創(chuàng)建多個(gè)播放器。
三、相關(guān)說(shuō)明
新建一個(gè)項(xiàng)目,在storyboard中放三個(gè)按鈕,分別用來(lái)控制音樂(lè)的播放、暫停和停止。
程序代碼如下:
復(fù)制代碼 代碼如下:
#import "YYViewController.h"
#import
@interface YYViewController ()
@property(nonatomic,strong)AVAudioPlayer *player;
- (IBAction)play;
- (IBAction)pause;
- (IBAction)stop;
@end
@implementation YYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//1.音頻文件的url路徑
NSURL *url=[[NSBundle mainBundle]URLForResource:@"235319.mp3" withExtension:Nil];
//2.創(chuàng)建播放器(注意:一個(gè)AVAudioPlayer只能播放一個(gè)url)
self.player=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:Nil];
//3.緩沖
[self.player prepareToPlay];
}
- (IBAction)play {
//開(kāi)始播放/繼續(xù)播放
[self.player play];
}
- (IBAction)pause {
//暫停
[self.player pause];
}
- (IBAction)stop {
//停止
//注意:如果點(diǎn)擊了stop,那么一定要讓播放器重新創(chuàng)建,否則會(huì)出現(xiàn)一些莫名其面的問(wèn)題
[self.player stop];
}
@end
注意:如果點(diǎn)了“停止”,那么一定要播放器重新創(chuàng)建,不然的話(huà)會(huì)出現(xiàn)莫名其妙的問(wèn)題。
點(diǎn)擊了stop之后,播放器實(shí)際上就不能再繼續(xù)使用了,如果還繼續(xù)使用,那么后續(xù)的一些東西會(huì)無(wú)法控制。
推薦代碼:
復(fù)制代碼 代碼如下:
#import "YYViewController.h"
#import
@interface YYViewController ()
@property(nonatomic,strong)AVAudioPlayer *player;
- (IBAction)play;
- (IBAction)pause;
- (IBAction)stop;
@end
復(fù)制代碼 代碼如下:
@implementation YYViewController
#pragma mark-懶加載
-(AVAudioPlayer *)player
{
if (_player==Nil) {
//1.音頻文件的url路徑
NSURL *url=[[NSBundle mainBundle]URLForResource:@"235319.mp3" withExtension:Nil];
//2.創(chuàng)建播放器(注意:一個(gè)AVAudioPlayer只能播放一個(gè)url)
self.player=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:Nil];
//3.緩沖
[self.player prepareToPlay];
}
return _player;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)play {
//開(kāi)始播放/繼續(xù)播放
[self.player play];
}
- (IBAction)pause {
//暫停
[self.player pause];
}
- (IBAction)stop {
//停止
//注意:如果點(diǎn)擊了stop,那么一定要讓播放器重新創(chuàng)建,否則會(huì)出現(xiàn)一些莫名其面的問(wèn)題
[self.player stop];
self.player=Nil;
}
@end
四、播放多個(gè)文件
點(diǎn)擊,url,按住common建查看。
可以發(fā)現(xiàn),這個(gè)url是只讀的,因此只能通過(guò)initWithContentsOfUrl的方式進(jìn)行設(shè)置,也就意味著一個(gè)播放器對(duì)象只能播放一個(gè)音頻文件。
那么如何實(shí)現(xiàn)播放多個(gè)音頻文件呢?
可以考慮封裝一個(gè)播放音樂(lè)的工具類(lèi),下一篇文章將會(huì)介紹具體怎么實(shí)現(xiàn)。
我們常常會(huì)在使用app的時(shí)候,邊聽(tīng)音樂(lè)(網(wǎng)易云音樂(lè),qq音樂(lè)等)邊使用軟件,如果我們?cè)赼pp中使用了聲音,例如“叮~”的一聲 提醒,就會(huì)導(dǎo)致音樂(lè)的停止播放。而像微信中的語(yǔ)音播放,會(huì)在播放完成后音樂(lè)恢復(fù)播放,這樣的體驗(yàn)就很好,那么需要怎么做呢?其實(shí)很簡(jiǎn)單,只需要一句話(huà)就可以。
當(dāng)你的app中的聲音播放完畢后,加上這一句話(huà),被打斷的音樂(lè)便會(huì)恢復(fù)播放了。
當(dāng)然還可以設(shè)置讓app的聲音和其他音樂(lè)兼容(默認(rèn)是不兼容的)
withOptions后面的屬性是一個(gè)枚舉,不同的類(lèi)型會(huì)有不同的效果,自己試試吧!
1、只可以播放完整的音頻
2、支持播放本地和網(wǎng)絡(luò)音頻
3、在播放網(wǎng)絡(luò)音頻的時(shí)候需要先下載到本地,做轉(zhuǎn)碼播放
4、只能存在一個(gè),想要播放下一個(gè)音樂(lè)的時(shí)候需要將上一個(gè)銷(xiāo)毀
建議使用AVPlayer
需要實(shí)現(xiàn)像鬧鐘那樣通知過(guò)后然后不進(jìn)入app直接播放音樂(lè)的功能
持續(xù)后臺(tái)播放,網(wǎng)絡(luò)歌曲也可以,重點(diǎn)是持續(xù)播放,后臺(tái)播放很簡(jiǎn)單,但是后臺(tái)持續(xù)播放,則需要做一些處理,申請(qǐng)后臺(tái)id,才能實(shí)現(xiàn)持續(xù)播放。
1.開(kāi)啟后臺(tái)模式
2.在Appdelegate.m的applicationWillResignActive:方法中激活后臺(tái)播放
3.處理中斷事件,如電話(huà),微信語(yǔ)音等。
原理是,在音樂(lè)播放被中斷時(shí),暫停播放,在中斷完成后,開(kāi)始播放
繼續(xù)研究,上面主要實(shí)現(xiàn)了音樂(lè)在app中開(kāi)啟播放然后推到后臺(tái)繼續(xù)播放,而我的需求是通知來(lái)到之后進(jìn)行音樂(lè)的啟動(dòng)播放
1.發(fā)現(xiàn)鬧鐘的原理并不是后臺(tái)播放音樂(lè),而是到了一個(gè)時(shí)間發(fā)出一個(gè)通知,這個(gè)通知每分鐘重復(fù)一次,然后播放自定義通知音樂(lè),需要在30s以?xún)?nèi)
2.如果是處理得比較好的,進(jìn)入app界面內(nèi)再次響鈴然后程序退到后臺(tái)掉用后臺(tái)持續(xù)播放音樂(lè)
3.例子:火箭鬧鐘