一储笑、前言
這兩天做了個小項目涉及到了遠程音樂播放,因為第一次做這種音樂項目倾剿,邊查資料邊做艘刚,其中涉及到主要技術(shù)點有:
- 如何播放遠程網(wǎng)絡(luò)音樂
- 如何切換當(dāng)前正在播放中的音樂資源
- 如何監(jiān)聽音樂播放的各種狀態(tài)(播放器狀態(tài)、播放的進度芭析、緩沖的進度锚扎,播放完成)
- 如何手動操控播放進度
- 如何在后臺模式或者鎖屏情況下正常播放音樂
- 如何在鎖屏模式下顯示音樂播放信息和遠程操控音樂
如果您對一塊技術(shù)點有興趣或者正在尋找相關(guān)資料,那么本篇或許能提供一些參考或啟發(fā)馁启。
二驾孔、 網(wǎng)絡(luò)音樂播放的核心技術(shù)點
根據(jù)自己的經(jīng)驗和查了一些音樂播放的相關(guān)資料,最簡單和最易上手的的技術(shù)方案我想應(yīng)該是采用ios系統(tǒng)自帶的AVFoundation框架。
我們知道AVFoundation框架是蘋果專門為多媒體打造的一個庫助币,這個庫非常強大浪听,專門用來處理音視頻等復(fù)雜的多媒體技術(shù),而本篇要講的所有技術(shù)點就是基于AVFoundation框架中的一個類——AVPlayer眉菱。
那么AVPlayer是什么迹栓?
你可以把他看成是一個已經(jīng)封裝好的播放器,它的作用是用來播放遠程的或本地的視頻和音頻俭缓。因為本地的音視頻的播放比較簡單克伊,這里就不做講述,本編主要是講遠程音樂播放华坦,因為都是基于AVPlayer同一套API愿吹,所以掌握遠程音樂播放其實就是相當(dāng)于掌握遠程視頻播放。好了廢話就不多說了惜姐,下面開始上菜犁跪。
1、導(dǎo)入AVFoundation框架歹袁,創(chuàng)建AVPlayer播放器
-(AVPlayer *)player
{
if (_player == nil) {
// AVPlayerItem是一個包裝音樂資源的類坷衍,初始化時可以傳入一個音樂的url
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:@"http://xxxxxxxx"]];
//通過AVPlayerItem初始化player
_player = [[AVPlayer alloc] initWithPlayerItem:item];
}
return _player;
}
此處懶加載創(chuàng)建,讓播放器成為控制器的全局屬性条舔,注意需要強引用枫耳,否則回收釋放掉了就無法播放。
2孟抗、播放或停止音樂
//開始播放
[self.player play];
//停止播放
[self.player pause];
這個沒什么好講的迁杨,只要調(diào)用AVPlayer的兩個實例方法
3、切換當(dāng)前正在播放中的音樂資源
//創(chuàng)建需要播放的AVPlayerItem
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:model.url]];
//替換當(dāng)前音樂資源
[self.player replaceCurrentItemWithPlayerItem:item];
這個可以用于歌曲的切換凄硼,如上一首铅协、下一首。
4帆喇、通過KVO監(jiān)聽播放器的狀態(tài)
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
拿到播放器的currentItem
警医,注冊當(dāng)前對象為觀察者,監(jiān)聽它的status
屬性坯钦。status
屬性是AVPlayerItemStatus
類型,它是一個枚舉類型侈玄,如下:
typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {
AVPlayerItemStatusUnknown,//未知狀態(tài)
AVPlayerItemStatusReadyToPlay,//準備播放
AVPlayerItemStatusFailed//加載失敗
};
當(dāng)status屬性值發(fā)生改變時婉刀,就會觸發(fā)觀察者方法的回調(diào),如下:
//觀察者回調(diào)
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//注意這里查看的是self.player.status屬性
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序仙;
}
}
}
當(dāng) self.player.status == AVPlayerStatusReadyToPlay
時突颊,音樂就會開始正常播放,另外兩種狀態(tài)音樂是無法播放的,可以在上面方法相應(yīng)狀態(tài)里給出提示律秃。這里需要特別強調(diào)一點的是觀察者監(jiān)聽的對象是self.player.currentItem
爬橡,而不是self.player
,而當(dāng)監(jiān)聽的屬性發(fā)生改變時棒动,觀察者回調(diào)的方法里需要查看的是self.player.status
糙申。當(dāng)然,你也可以不這么干船惨,但是我嘗試過好幾次柜裸,不這么干的后果是無法監(jiān)聽到self.player.status
屬性的改變。
當(dāng)音樂播放完成粱锐,或者切換下一首歌曲時疙挺,請務(wù)必記得移除觀察者,否則會crash怜浅。操作如下:
//移除觀察者
[self.player.currentItem removeObserver:self forKeyPath:@"status"];
5铐然、監(jiān)聽音樂的緩沖進度
這個也是通過KVO監(jiān)聽播放器當(dāng)前播放的音樂資源AVPlayerItem
的loadedTimeRanges
屬性。我們先看監(jiān)聽恶座,如下:
//KVO監(jiān)聽音樂緩沖狀態(tài)
[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
當(dāng)loadedTimeRanges
屬性發(fā)生改變時锦爵,回調(diào)如下:
-(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;
}
}
loadedTimeRanges這個屬性是一個數(shù)組,里面裝的是本次緩沖的時間范圍奥裸,這個范圍是用一個結(jié)構(gòu)體CMTimeRange
表示险掀,當(dāng)然在oc中結(jié)構(gòu)體是不能直接存放數(shù)組的,所以它被包裝成了oc對象NSValue
湾宙。
我們來看下這個結(jié)構(gòu)體:
typedef struct
{
CMTime start;
CMTime duration;
} CMTimeRange;
start表示本次緩沖時間的起點樟氢,duratin表示本次緩沖持續(xù)的時間范圍,具體詳細的計算方法可以看上面方法的實現(xiàn)侠鳄。
當(dāng)音樂播放完成埠啃,或者切換下一首歌曲時,請務(wù)必記得移除觀察者伟恶,否則會crash碴开。操作如下:
[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
6、監(jiān)聽音樂播放的進度
這個不是通過KVO了博秫,AVPlayer專門提供了下面這個api用來監(jiān)聽播放的進度:
/**
監(jiān)聽音樂播放進度
@param interval 監(jiān)聽的時間間隔潦牛,用來設(shè)置多長時間回調(diào)一次
@param queue 隊列,一般傳主隊列
@param block 回調(diào)的block挡育,會把當(dāng)前的播放時間傳遞過來
@return 監(jiān)聽的對象
*/
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
操作如下:
__weak typeof(self) weakSelf = self;
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
//當(dāng)前播放的時間
float current = CMTimeGetSeconds(time);
//總時間
float total = CMTimeGetSeconds(item.duration);
if (current) {
float progress = current / total;
//更新播放進度條
weakSelf.playSlider.value = progress;
weakSelf.currentTime.text = [weakSelf timeFormatted:current];
}
}];
我們可以這個block里面拿到當(dāng)前播放時間巴碗,根據(jù)總時間計算出當(dāng)前播放所占的時間比例,最后更新播放進度條即寒。這里又涉及到了一個數(shù)據(jù)類類型CMTime橡淆,它也是一個結(jié)構(gòu)體召噩,用來作為時間的格式,定義如下:
typedef struct
CMTimeValue value;
CMTimeScale timescale;
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
CMTime是以分數(shù)的形式表示時間逸爵,value表示分子具滴,timescale表示分母,flags是位掩碼师倔,表示時間的指定狀態(tài)构韵。所以我們要獲得時間的秒數(shù)需要分子除以分母。當(dāng)然你還可以用下面這個函數(shù)來獲取時間的秒數(shù):
Float64 CMTimeGetSeconds(CMTime time)
最后溯革,當(dāng)音樂播放完成或者切換音樂時贞绳,依然需要移除監(jiān)聽:
if (self.timeObserver) {
[self.player removeTimeObserver:self.timeObserver];
self.timeObserver = nil;
}
7、手動超控(移動滑塊)播放進度
這是一個播放音視頻很常見的功能致稀,所以強大的AVPlayer理所當(dāng)然的提供了幾個api冈闭,下面只講述其中最簡單的一個:
/**
定位播放時間
@param time 指定的播放時間
*/
- (void)seekToTime:(CMTime)time;
具體使用如下:
//移動滑塊調(diào)整播放進度
- (IBAction)playSliderValueChange:(UISlider *)sender
{
//根據(jù)值計算時間
float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);
//跳轉(zhuǎn)到當(dāng)前指定時間
[self.player seekToTime:CMTimeMake(time, 1)];
}
8、監(jiān)聽音樂播放完成
一般音視頻播放完成時我們或多或少的都要處理一些業(yè)務(wù)抖单,比如循環(huán)播放萎攒,播完退出界面等等。下面看下如何監(jiān)聽AVPlayer的播放完成矛绘。
//給AVPlayerItem添加播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
這里是采用注冊監(jiān)聽AVPlayerItemDidPlayToEndTimeNotification
通知耍休,當(dāng)AVPlayer一播放完成時,便會發(fā)出這個通知货矮,我們收到通知后進行處理即可
9羊精、設(shè)置音樂后臺播放
我們知道運行在ios系統(tǒng)下的程序一旦進入后臺就會處于休眠狀態(tài),程序停止運行了囚玫,也就播放不了什么音樂了喧锦。但是有一些特定功能的app還是處于可以后臺運行的,比如音樂類型的app正處于這個范疇抓督。但是燃少,并不是說你在應(yīng)用中播放音樂就能后臺高枕無憂的運行了,你依然需要做如下幾步操作:
(1)開啟后臺模式
target ->capabilities-> Background modes ->打開開關(guān) ->勾選第一個選項
(2)程序啟動時設(shè)置音頻會話
//一般在方法:application: didFinishLaunchingWithOptions:設(shè)置
//獲取音頻會話
AVAudioSession *session = [AVAudioSession sharedInstance];
//設(shè)置類型是播放铃在。
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
//激活音頻會話阵具。
[session setActive:YES error:nil];
以上兩步設(shè)置無誤,程序進入后臺模式定铜,便可以進行音樂播放
10阳液、如何設(shè)置音樂鎖頻信息
我們看百度音樂鎖頻時,也依然能在屏幕上展示歌曲的信息宿稀,以及切換歌曲等趁舀。下面看看這個功能是如何實現(xiàn)的:
//音樂鎖屏信息展示
- (void)setupLockScreenInfo
{
// 1.獲取鎖屏中心
MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter];
//初始化一個存放音樂信息的字典
NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary];
// 2、設(shè)置歌曲名
if (self.currentModel.name) {
[playingInfoDict setObject:self.currentModel.name forKey:MPMediaItemPropertyAlbumTitle];
}
// 設(shè)置歌手名
if (self.currentModel.artist) {
[playingInfoDict setObject:self.currentModel.artist forKey:MPMediaItemPropertyArtist];
}
// 3設(shè)置封面的圖片
UIImage *image = [self getMusicImageWithMusicId:self.currentModel];
if (image) {
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image];
[playingInfoDict setObject:artwork forKey:MPMediaItemPropertyArtwork];
}
// 4設(shè)置歌曲的總時長
[playingInfoDict setObject:self.currentModel.detailDuration forKey:MPMediaItemPropertyPlaybackDuration];
//音樂信息賦值給獲取鎖屏中心的nowPlayingInfo屬性
playingInfoCenter.nowPlayingInfo = playingInfoDict;
// 5.開啟遠程交互祝沸,只有開啟這個才能進行遠程操控
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
這里設(shè)置圖片時需要注意下矮烹,異步加載網(wǎng)絡(luò)圖片后再設(shè)置是無效的,所以圖片信息最好是先請求下來后再進行設(shè)置罩锐。
遠程超控的回調(diào)如下:
//監(jiān)聽遠程交互方法
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
switch (event.subtype) {
//播放
case UIEventSubtypeRemoteControlPlay:{
[self.player play];
}
break;
//停止
case UIEventSubtypeRemoteControlPause:{
[self.player pause];
}
break;
//下一首
case UIEventSubtypeRemoteControlNextTrack:
[self nextBtnAction:nil];
break;
//上一首
case UIEventSubtypeRemoteControlPreviousTrack:
[self lastBtnAction:nil];
break;
default:
break;
}
}
三奉狈、總結(jié)
最后,畫了一張圖總結(jié)下播放遠程網(wǎng)絡(luò)音樂的流程:
根據(jù)QQ音樂的界面做了個小demo涩惑,下面是demo的真機前臺和后臺播放的運行效果:
附上github下載地址:https://github.com/yedexiong/MsuicPlayDemo.git
四仁期、結(jié)束語
播放遠程網(wǎng)絡(luò)音樂的核心技術(shù)點基本上已經(jīng)寫完,當(dāng)然AVPlayer還有很多強大的功能沒有寫出來竭恬,有興趣的可以進一步挖掘跛蛋。寫到這里已經(jīng)疲倦至極,如果喜歡的可以點贊和關(guān)注,后續(xù)會持續(xù)更新一些精彩的技術(shù)點痊硕。