最近接觸到新項目里的音頻業(yè)務哭当,根據(jù)這幾天的整理猪腕,總結(jié)一點內(nèi)容,方便記錄钦勘。后續(xù)不斷更新陋葡。。彻采。
在iOS程序中脖岛,音頻播放隨處可見,有的聲音只有1秒颊亮,有的聲音好幾分鐘 。iOS支持的音頻格式AAC陨溅、ALAC终惑、IMA4、linear门扇、MP3雹有。
AVAudioPlayer
AVAudioPlayer類用于回放音頻數(shù)據(jù)。是一個易于使用的類臼寄,它提供了大量的功能霸奕。使用該類可以實現(xiàn)音頻的載入,播放吉拳,暫停质帅,停止。需要加入AVFoundation.framework框架留攒,在使用的類中引入<AVFoundation/AVFoundation.h>煤惩。
- AVAudioPlayer只能播放本地音樂文件
可以通過音頻的NSData或者本地音頻文件的url,來創(chuàng)建一個AVAudioPlayer實例炼邀,如加載本地的music.mp3的音頻文件:
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"music" withExtension:@"mp3"];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];
if (self.player) {
[self.player prepareToPlay];
}
加載音頻文件后魄揉,可以調(diào)用prepareToPlay方法,這樣可以提前獲取需要的硬件支持拭宁,并加載音頻到緩沖區(qū)洛退。在調(diào)用play方法時,減少開始播放的延遲杰标。
當調(diào)用play方法后兵怯,開始播放音樂:
[self.player play];
可以調(diào)用pause或stop來暫停播放,這里的stop方法的效果也只是暫停播放在旱,不同之處是stop會撤銷prepareToPlay方法所做的準備摇零。
[self.player stop];
另外,我們可以進行更多的操作:
單獨設置音樂的音量(默認1.0,可設置范圍為0.0至1.0驻仅,兩個極端為靜音谅畅、系統(tǒng)音量):
self.player.volume = 0.5;
修改左右聲道的平衡(默認0.0,可設置范圍為-1.0至1.0噪服,兩個極端分別為只有左聲道毡泻、只有右聲道):
self.player.pan = -1;
設置播放速度(默認1.0,可設置范圍為0.5至2.0粘优,兩個極端分別為一半速度仇味、兩倍速度):
self.player.rate = 0.5;
設置循環(huán)播放(默認1,若設置值大于0雹顺,則為相應的循環(huán)次數(shù)丹墨,設置為-1可以實現(xiàn)無限循環(huán)):
self.player.numberOfLoops = -1;
AVPlayer
支持播放本地、分步下載嬉愧、或在線流媒體音視頻贩挣,不僅可以播放音頻,配合AVPlayerLayer類可實現(xiàn)視頻播放没酣。另外支持播放進度監(jiān)聽王财。
- AVPLayer:可以用來播放在線及本地音視頻
- AVAudioSession:音頻會話,主要用來管理音頻設置與硬件交互
使用時需要導入
1.AVPlayer需要通過AVPlayerItem來關(guān)聯(lián)需要播放的媒體裕便。
#import <AVFoundation/AVFoundation.h>
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:urlStr]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];
- 2.在準備播放前绒净,通過KVO添加播放狀態(tài)改變監(jiān)聽
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
處理KVO回調(diào)事件:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"status"]) {
switch (self.player.status) {
case AVPlayerStatusUnknown:
{
NSLog(@"未知轉(zhuǎn)態(tài)");
}
break;
case AVPlayerStatusReadyToPlay:
{
NSLog(@"準備播放");
}
break;
case AVPlayerStatusFailed:
{
NSLog(@"加載失敗");
}
break;
default:
break;
}
}
}
- 3.KVO監(jiān)聽音樂緩沖狀態(tài):
[self.player.currentItem addObserver:self
forKeyPath:@"loadedTimeRanges"
options:NSKeyValueObservingOptionNew
context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//...
if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;
//本次緩沖的時間范圍
CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
//緩沖總長度
NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
//音樂的總時間
NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);
//計算緩沖百分比例
NSTimeInterval scale = totalLoadTime/duration;
//更新緩沖進度條
// self.loadTimeProgress.progress = scale;
}
}
- 4.開始播放后,通過KVO添加播放結(jié)束事件監(jiān)聽
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playFinished:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:_player.currentItem];
- 5.開始播放時偿衰,通過AVPlayer的方法監(jiān)聽播放進度挂疆,并更新進度條(定期監(jiān)聽的方法):
__weak typeof(self) weakSelf = self;
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
//當前播放的時間
float current = CMTimeGetSeconds(time);
//總時間
float total = CMTimeGetSeconds(item.duration);
if (current) {
float progress = current / total;
//更新播放進度條
weakSelf.playSlider.value = progress;
}
}];
- 6.用戶拖動進度條,修改播放進度
- (void)playSliderValueChange:(UISlider *)sender
{
//根據(jù)值計算時間
float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);
//跳轉(zhuǎn)到當前指定時間
[self.player seekToTime:CMTimeMake(time, 1)];
}
后臺播放
AVAudioSession中配置選項:
- AVAudioSessionCategory
1.AVAudioSessionCategoryAmbient
當前App的播放聲音可以和其他app播放的聲音共存哎垦,當鎖屏或按靜音時停止囱嫩。
2.AVAudioSessionCategorySoloAmbient
只能播放當前App的聲音,其他app的聲音會停止漏设,當鎖屏或按靜音時停止墨闲。
3.AVAudioSessionCategoryPlayback
只能播放當前App的聲音,其他app的聲音會停止郑口,當鎖屏或按靜音時不會停止鸳碧。
4.AVAudioSessionCategoryRecord
只能用于錄音,其他app的聲音會停止犬性,當鎖屏或按靜音時不會停止
5.AVAudioSessionCategoryPlayAndRecord
在錄音的同時播放其他聲音瞻离,當鎖屏或按靜音時不會停止
可用于聽筒播放,比如微信語音消息聽筒播放
6.AVAudioSessionCategoryAudioProcessing
使用硬件解碼器處理音頻乒裆,該音頻會話使用期間套利,不能播放或錄音
7.AVAudioSessionCategoryMultiRoute
多種音頻輸入輸出,例如可以耳機、USB設備同時播放等
- AVAudioSessionCategoryOptions
1.AVAudioSessionModeDefault
默認的模式肉迫,適用于所有的場景验辞,可用于場景還原
2.AVAudioSessionModeVoiceChat
適用類別 :
AVAudioSessionCategoryPlayAndRecord
應用場景VoIP
3.AVAudioSessionModeGameChat
適用類別:
AVAudioSessionCategoryPlayAndRecord
應用場景游戲錄制,由GKVoiceChat自動設置喊衫,無需手動調(diào)用
4.AVAudioSessionModeVideoRecording
適用類別:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryRecord
應用場景視頻錄制
5.AVAudioSessionModeMoviePlayback
適用類別:
AVAudioSessionCategoryPlayBack
應用場景視頻播放
6.AVAudioSessionModeVideoChat
適用類別:
AVAudioSessionCategoryPlayAndRecord
應用場景視頻通話
7.AVAudioSessionModeMeasurement
適用類別:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryRecord
AVAudioSessionCategoryPlayback
AVAudioSessionModeSpokenAudio
- AVAudioSessionMode
1.AVAudioSessionCategoryOptionMixWithOthers
適用于:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute
用于可以和其他app進行混音
2.AVAudioSessionCategoryOptionDuckOthers
適用于:
AVAudioSessionCategoryAmbient
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute
用于壓低其他聲音播放的音量跌造,使期音量變小
3.AVAudioSessionCategoryOptionAllowBluetooth
適用于:
AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord
用于是否支持藍牙設備耳機等
4.AVAudioSessionCategoryOptionDefaultToSpeaker
適用于:
AVAudioSessionCategoryPlayAndRecord
用于將聲音從Speaker播放,外放族购,即免提
5.AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
適用于:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute
6.AVAudioSessionCategoryOptionAllowBluetoothA2DP
適用于:
AVAudioSessionCategoryPlayAndRecord
藍牙和a2dp
7.AVAudioSessionCategoryOptionAllowAirPlay
適用于:
AVAudioSessionCategoryPlayAndRecord
airplay
系統(tǒng)中斷響應
上面說的這些Category啊壳贪、Option啊以及Mode都是對自己作為播放主體時的表現(xiàn),但是假設寝杖,現(xiàn)在正在播放著违施,突然來電話了、鬧鐘響了或者你在后臺放歌但是用戶啟動其他App用上面的方法影響的時候瑟幕,我們的App該如何表現(xiàn)呢醉拓?最常用的場景當然是先暫停,待恢復的時候再繼續(xù)收苏。那我們的App要如何感知到這個終端以及何時恢復呢?
AVAudioSession提供了多種Notifications來進行此類狀況的通知愤兵。其中將來電話鹿霸、鬧鈴響等都歸結(jié)為一般性的中斷,用
AVAudioSessionInterruptionNotification來通知秆乳。其回調(diào)回來的userInfo主要包含兩個鍵:
AVAudioSessionInterruptionTypeKey: 取值為AVAudioSessionInterruptionTypeBegan表示中斷開始懦鼠,我們應該暫停播放和采集
取值為AVAudioSessionInterruptionTypeEnded表示中斷結(jié)束,我們可以繼續(xù)播放和采集屹堰。
AVAudioSessionInterruptionOptionKey: 當前只有一種值AVAudioSessionInterruptionOptionShouldResume表示此時也應該恢復繼續(xù)播放和采集肛冶。
而將其他App占據(jù)AudioSession的時候用AVAudioSessionSilenceSecondaryAudioHintNotification來進行通知。其回調(diào)回來的userInfo鍵為:
AVAudioSessionSilenceSecondaryAudioHintTypeKey
可能包含的值:
AVAudioSessionSilenceSecondaryAudioHintTypeBegin: 表示其他App開始占據(jù)Session
AVAudioSessionSilenceSecondaryAudioHintTypeEnd: 表示其他App開始釋放Session
首先在AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:方法中添加代碼:
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
然后在Info.plist文件中添加:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
鎖屏信息
- 在每次準備播放下一首時扯键,更新鎖屏信息:
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"封面圖片"]];
infoCenter.nowPlayingInfo = @{
MPMediaItemPropertyTitle :@"歌曲名",
MPMediaItemPropertyArtist :@"歌手名",
MPMediaItemPropertyPlaybackDuration :歌曲時間長度,
MPNowPlayingInfoPropertyElapsedPlaybackTime : @(已播放時間長度),
MPMediaItemPropertyArtwork : artwork
};
通過耳機睦袖、鎖屏界面控制
- 在需要處理遠程控制事件的具體控制器或其它類中調(diào)用下面這個方法
#import <MediaPlayer/MPRemoteCommandCenter.h>
#import <MediaPlayer/MPRemoteCommand.h>
- (void)remoteControlEventHandler
{
// 直接使用sharedCommandCenter來獲取MPRemoteCommandCenter的shared實例
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
// 啟用播放命令 (鎖屏界面和上拉快捷功能菜單處的播放按鈕觸發(fā)的命令)
commandCenter.playCommand.enabled = YES;
// 為播放命令添加響應事件, 在點擊后觸發(fā)
[commandCenter.playCommand addTarget:self action:@selector(playAction:)];
// 播放, 暫停, 上下曲的命令默認都是啟用狀態(tài), 即enabled默認為YES
// 為暫停, 上一曲, 下一曲分別添加對應的響應事件
[commandCenter.pauseCommand addTarget:self action:@selector(pauseAction:)];
[commandCenter.previousTrackCommand addTarget:self action:@selector(previousTrackAction:)];
[commandCenter.nextTrackCommand addTarget:self action:@selector(nextTrackAction:)];
// 啟用耳機的播放/暫停命令 (耳機上的播放按鈕觸發(fā)的命令)
commandCenter.togglePlayPauseCommand.enabled = YES;
// 為耳機的按鈕操作添加相關(guān)的響應事件
[commandCenter.togglePlayPauseCommand addTarget:self action:@selector(playOrPauseAction:)];
}
-(void)playAction:(id)obj{
[[HYPlayerTool sharePlayerTool] play];
}
-(void)pauseAction:(id)obj{
[[HYPlayerTool sharePlayerTool] pause];
}
-(void)nextTrackAction:(id)obj{
[[HYPlayerTool sharePlayerTool] playNext];
}
-(void)previousTrackAction:(id)obj{
[[HYPlayerTool sharePlayerTool] playPre];
}
-(void)playOrPauseAction:(id)obj{
if ([[HYPlayerTool sharePlayerTool] isPlaying]) {
[[HYPlayerTool sharePlayerTool] pause];
}else{
[[HYPlayerTool sharePlayerTool] play];
}
}
AVQueuePlayer
AVPlayer只支持單個媒體資源的播放,我們可以使用AVPlayer的子類AVQueuePlayer實現(xiàn)列表播放荣刑。在AVPlayer的基礎上馅笙,增加以下方法:
//通過給定的AVPlayerItem數(shù)組創(chuàng)建一個AVQueuePlayer實例
+ (instancetype)queuePlayerWithItems:(NSArray<AVPlayerItem *> *)items;
//通過給定的AVPlayerItem數(shù)組初始化AVQueuePlayer實例
- (AVQueuePlayer *)initWithItems:(NSArray<AVPlayerItem *> *)items;
//獲得當前的播放隊列數(shù)組
- (NSArray<AVPlayerItem *> *)items;
//停止播放當前音樂,并播放隊列中的下一首
- (void)advanceToNextItem;
//往播放隊列中插入新的AVPlayerItem
- (void)insertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem;
//從播放隊列中移除指定AVPlayerItem
- (void)removeItem:(AVPlayerItem *)item;
//清空播放隊列
- (void)removeAllItems;
- 官方API中沒找到播放上一首的方法厉亏,所以其實直接用AVPlayer做列表播放也是可以的董习,自己維護一個播放列表數(shù)組,監(jiān)聽用戶點擊上一首和下一首按鈕爱只,并監(jiān)聽播放結(jié)束事件皿淋,調(diào)用 AVPlayer 實例的replaceCurrentItemWithPlayerItem:方法傳入播放列表中的上一首或下一首就行。