【iOS】音頻播放之AVAudioPlayer,AVPlayer,AVQueuePlayer


https://blog.csdn.net/dolacmeng/article/details/77430108

 

前言

在婚語APP中,分別使用了AVAudioPlayer,AVPlayer,AVQueuePlayer來實現音頻播放功能,下面以婚語的實際需求分別介紹它們的使用方法和區別。

需求1 檔期備忘:用戶新建檔期記錄時,可以進行錄音備忘,錄音完成后可直接播放,保存檔期時將錄音文件上傳到服務器。

分析1:因為錄音備忘一般時長較短文件較小,所以錄音完將錄音文件上傳到服務器的同時,本地也保留錄音文件,用戶查看檔期並點擊播放語音備忘時,先讀取本地錄音文件,找不到時再到服務器下載保存到本地,然后再使用AVAudioPlayer實現本地音頻播放。

需求2 婚禮音樂播放:如圖,用戶可以在線試聽一些婚禮現場使用的背景音樂,只能選中某一首背景音樂進行播放,播放完成后停止,不能自動播放下一首。

 

分析2:因為試聽的音樂是在服務器上,而AVAudioPlayer只能播放本地音樂文件,所以需要使用支持在線音樂的AVPlayer進行播放。

需求3 婚語開場白:如圖,在需求2的前提下,支持列表自動播放,類似於網易音樂。同時支持后台播放、鎖屏歌曲信息顯示和控制、耳機控制等

 

分析3:此時可以使用AVPlayer的子類AVQueuePlayer進行列表播放。AVAudioPlayer,AVPlayer,AVQueuePlayer都支持后台播放、鎖屏信息、耳機控制等

一、 AVAudioPlayer

可以通過音頻的NSData或者本地音頻文件的url,來創建一個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];
}
1
2
3
4
5
6
加載音頻文件后,可以調用prepareToPlay方法,這樣可以提前獲取需要的硬件支持,並加載音頻到緩沖區。在調用play方法時,減少開始播放的延遲。

當調用play方法后,開始播放音樂:

[self.player play];
1
可以調用pause或stop來暫停播放,這里的stop方法的效果也只是暫停播放,不同之處是stop會撤銷prepareToPlay方法所做的准備。

[self.player stop];
1
另外,我們可以進行更多的操作:

單獨設置音樂的音量(默認1.0,可設置范圍為0.0至1.0,兩個極端為靜音、系統音量):

self.player.volume = 0.5;
1
修改左右聲道的平衡(默認0.0,可設置范圍為-1.0至1.0,兩個極端分別為只有左聲道、只有右聲道):

self.player.pan = -1;
1
設置播放速度(默認1.0,可設置范圍為0.5至2.0,兩個極端分別為一半速度、兩倍速度):

self.player.rate = 0.5;
1
設置循環播放(默認1,若設置值大於0,則為相應的循環次數,設置為-1可以實現無限循環):

self.player.numberOfLoops = -1;
1
二、 AVPlayer

AVPlayer支持播放本地、分步下載、或在線流媒體音視頻,不僅可以播放音頻,配合AVPlayerLayer類可實現視頻播放。另外支持播放進度監聽。

1.AVPlayer需要通過AVPlayerItem來關聯需要播放的媒體。

#import <AVFoundation/AVFoundation.h>
1
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:urlStr]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];
1
2
2.在准備播放前,通過KVO添加播放狀態改變監聽

[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
1
處理KVO回調事件:

-(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(@"未知轉態");
}
break;
case AVPlayerStatusReadyToPlay:
{
NSLog(@"准備播放");
}
break;
case AVPlayerStatusFailed:
{
NSLog(@"加載失敗");
}
break;

default:
break;
}

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
3.KVO監聽音樂緩沖狀態:

[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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
4.開始播放后,通過KVO添加播放結束事件監聽

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playFinished:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:_player.currentItem];
1
2
3
4
5.開始播放時,通過AVPlayer的方法監聽播放進度,並更新進度條(定期監聽的方法):

__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;
}
}];
1
2
3
4
5
6
7
8
9
10
11
12
6.用戶拖動進度條,修改播放進度

- (void)playSliderValueChange:(UISlider *)sender
{
//根據值計算時間
float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);
//跳轉到當前指定時間
[self.player seekToTime:CMTimeMake(time, 1)];
}
1
2
3
4
5
6
7
三、 AVQueuePlayer

AVPlayer只支持單個媒體資源的播放,我們可以使用AVPlayer的子類AVQueuePlayer實現列表播放。在AVPlayer的基礎上,增加以下方法:

//通過給定的AVPlayerItem數組創建一個AVQueuePlayer實例
+ (instancetype)queuePlayerWithItems:(NSArray<AVPlayerItem *> *)items;

//通過給定的AVPlayerItem數組初始化AVQueuePlayer實例
- (AVQueuePlayer *)initWithItems:(NSArray<AVPlayerItem *> *)items;

//獲得當前的播放隊列數組
- (NSArray<AVPlayerItem *> *)items;

//停止播放當前音樂,並播放隊列中的下一首
- (void)advanceToNextItem;

//往播放隊列中插入新的AVPlayerItem
- (void)insertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem;

//從播放隊列中移除指定AVPlayerItem
- (void)removeItem:(AVPlayerItem *)item;

//清空播放隊列
- (void)removeAllItems;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
*官方API中沒找到播放上一首的方法,所以其實直接用AVPlayer做列表播放也是可以的,自己維護一個播放列表數組,監聽用戶點擊上一首和下一首按鈕,並監聽播放結束事件,調用 AVPlayer 實例的replaceCurrentItemWithPlayerItem:方法傳入播放列表中的上一首或下一首就行。

四、 后台播放

首先在AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:方法中添加代碼:

AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
1
2
3
然后在Info.plist文件中添加:

<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
1
2
3
4
五、 鎖屏信息

在每次准備播放下一首時,更新鎖屏信息:

MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"封面圖片"]];
infoCenter.nowPlayingInfo = @{
MPMediaItemPropertyTitle :@"歌曲名",
MPMediaItemPropertyArtist :@"歌手名",
MPMediaItemPropertyPlaybackDuration :歌曲時間長度,
MPNowPlayingInfoPropertyElapsedPlaybackTime : @(已播放時間長度),
MPMediaItemPropertyArtwork : artwork
};
1
2
3
4
5
6
7
8
9
六、 通過耳機、鎖屏界面控制

// 在需要處理遠程控制事件的具體控制器或其它類中調用下面這個方法
1
#import <MediaPlayer/MPRemoteCommandCenter.h>
#import <MediaPlayer/MPRemoteCommand.h>
1
2
- (void)remoteControlEventHandler
{
// 直接使用sharedCommandCenter來獲取MPRemoteCommandCenter的shared實例
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];

// 啟用播放命令 (鎖屏界面和上拉快捷功能菜單處的播放按鈕觸發的命令)
commandCenter.playCommand.enabled = YES;
// 為播放命令添加響應事件, 在點擊后觸發
[commandCenter.playCommand addTarget:self action:@selector(playAction:)];

// 播放, 暫停, 上下曲的命令默認都是啟用狀態, 即enabled默認為YES
// 為暫停, 上一曲, 下一曲分別添加對應的響應事件
[commandCenter.pauseCommand addTarget:self action:@selector(pauseAction:)];
[commandCenter.previousTrackCommand addTarget:self action:@selector(previousTrackAction:)];
[commandCenter.nextTrackCommand addTarget:self action:@selector(nextTrackAction:)];

// 啟用耳機的播放/暫停命令 (耳機上的播放按鈕觸發的命令)
commandCenter.togglePlayPauseCommand.enabled = YES;
// 為耳機的按鈕操作添加相關的響應事件
[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];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
代碼demo:https://github.com/dolacmeng/AVAudioDemo
————————————————
版權聲明:本文為CSDN博主「imJackXu」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/dolacmeng/article/details/77430108


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM