上周遲到了,周末去參加OSC源創(chuàng)會(huì)了,還是有點(diǎn)啟發(fā)的壮莹。但這不是重點(diǎn)剑鞍,重點(diǎn)是 上一篇我只是實(shí)現(xiàn)了一首歌曲的在線播放,這肯定是不夠的铐然。這一篇博客主要是實(shí)現(xiàn)了多首歌曲的順序播放以及上一首和下一首切換蔬崩。
先看一下效果圖
效果圖.png
1.準(zhǔn)備工作
(1)數(shù)據(jù)源
?? 我把歌曲列表存在本地songList.json文件里恶座。用FHAlbumModel管理歌曲。
FHAlbumModel.h
#import <Foundation/Foundation.h>
@interface FHAlbumModel : NSObject
@property (nonatomic, copy) NSString *lrclink; // 歌詞
@property (nonatomic, copy) NSString *pic_big; // 背景圖
@property (nonatomic, copy) NSString *artist_name; // 歌手
@property (nonatomic, copy) NSString *title; // 歌名
@property (nonatomic, copy) NSString *song_id; // 歌曲地址
- (instancetype)initWithInfo: (NSDictionary *)InfoDic;
@end
FHAlbumModel.m
#import "FHAlbumModel.h"
@implementation FHAlbumModel
- (instancetype)initWithInfo: (NSDictionary *)InfoDic {
FHAlbumModel *model = [[FHAlbumModel alloc] init];
// 通過kvo為屬性賦值
[model setValuesForKeysWithDictionary:InfoDic];
return model;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
}
@end
(2)聲明的變量
#import "FHMusicPlayerViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "UIColor+RGBHelper.h"
#import "FHCustomButton.h"
#import "Masonry.h"
#import "FHAlbumModel.h"
#import "FHLrcModel.h"
@interface FHMusicPlayerViewController ()<UITableViewDelegate, UITableViewDataSource>{
UIImageView *_backImageView; // 背景圖
UILabel *_album_titleLabel; // 標(biāo)題
UILabel *_artist_nameLabel; // 副標(biāo)題
UILabel *_currentLabel; // 當(dāng)前時(shí)間
UILabel *_durationLabel; // 總時(shí)間
UIProgressView *_progressView; // 進(jìn)度條
UISlider *_playerSlider; // 播放控制器
FHCustomButton *_playButton; // 播放暫停
FHCustomButton *_prevButton; // 上一首
FHCustomButton *_nextButton; // 下一首
BOOL _isPlay; // 記錄播放暫停狀態(tài)
NSInteger _index; // 記錄播放到了第幾首歌
FHAlbumModel *_currentModel;
UITableView *_lrcTableView; // 用于顯示歌詞
int _row; //記錄歌詞第幾行
}
@property (nonatomic, strong)NSMutableArray *albumArr; //歌曲
@property (nonatomic, strong)NSMutableArray *lrcArr; // 歌詞
@property (nonatomic, strong)AVPlayer *avPlayer;
@property (nonatomic, strong)id timePlayProgerssObserver;// 播放器進(jìn)度觀察者
@end
UI的具體實(shí)現(xiàn)我就不一一介紹了沥阳,可以去我的GitUp下載源碼跨琳。只要記住每個(gè)變量的含義就好了,方便下面的觀看桐罕。
(3)懶加載變量
#pragma - mark 懶加載歌曲
- (NSMutableArray *)albumArr {
if (!_albumArr) {
_albumArr = [NSMutableArray new];
// 從本地獲取json數(shù)據(jù)
NSData *jsonData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"songList" ofType:@"json"]];
// 把json數(shù)據(jù)轉(zhuǎn)換成字典
NSDictionary *rootDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
NSArray *albumArr = [NSArray arrayWithArray:rootDic[@"song_list"]];
for (NSDictionary *dic in albumArr) {
FHAlbumModel *albumModel = [[FHAlbumModel alloc] initWithInfo:dic];
[_albumArr addObject:albumModel];
}
}
return _albumArr;
}
#pragma - mark 懶加載歌詞
- (NSMutableArray *)lrcArr{
if (!_lrcArr) {
_lrcArr = [NSMutableArray new];
}
return _lrcArr;
}
#pragma - mark 懶加載AVPlayer
- (AVPlayer *)avPlayer {
if (!_avPlayer) {
AVPlayerItem *item = [AVPlayerItem new];
_avPlayer = [[AVPlayer alloc] initWithPlayerItem:item];
}
return _avPlayer;
}
2.歌曲輪播
#pragma mark - 播放暫停
- (void)playAction:(UIButton *)button {
_isPlay = !_isPlay;
if (_isPlay) {
_playButton.imageView.image = [UIImage imageNamed:@"play"];
if (_currentModel) {
[self.avPlayer play];
}else {
[self playMusic];
}
}else {
_playButton.imageView.image = [UIImage imageNamed:@"stop"];
[self.avPlayer pause];
}
}
當(dāng)沒有歌曲播放時(shí)候脉让,添加歌曲。當(dāng)有歌曲播放時(shí)功炮,不添加歌曲溅潜。這樣可以保證暫停之后繼續(xù)播放。
- (void)playMusic {
// 1.移除觀察者
[self removeObserver];
// 2.修改播放按鈕的圖片
_playButton.imageView.image = [UIImage imageNamed:@"play"];
// 3.獲取歌曲
FHAlbumModel *albumModel = self.albumArr[_index];
// 4.修改標(biāo)題
_album_titleLabel.text = albumModel.title;
// 5.修改副標(biāo)題
_artist_nameLabel.text = [NSString stringWithFormat:@"%@ - 經(jīng)典老歌榜",albumModel.artist_name];
// 6. 實(shí)例化新的playerItem
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:albumModel.song_id]];
// 7.取代舊的playerItem
[self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];
// 8.開始播放
[self.avPlayer play];
// 9.添加緩存狀態(tài)的觀察者
[self addObserverOfLoadedTimeRanges];
// 10.添加播放進(jìn)度的觀察者
[self addTimePlayProgerssObserver];
// 11.記錄當(dāng)前播放的歌曲
_currentModel = self.albumArr[_index];
// 12.獲取歌詞
[self getAlbumLrc];
}
```
**分析**:1.**添加觀察者之前需要把以前的觀察者移除**薪伏。如果不移除self.avPlayer.currentItem 的觀察者滚澜,就會(huì)報(bào)“An instance 0x174009380 of class AVPlayerItem was deallocated while key value observers were still registered with it”。意思是觀察的對(duì)象已經(jīng)釋放嫁怀,還對(duì)它進(jìn)行觀察设捐。我們切換歌曲時(shí),原來的歌曲對(duì)象已經(jīng)釋放了塘淑,所以對(duì)原來歌曲對(duì)象添加的觀察者也應(yīng)該移除萝招;雖然self.avPlayer一直存在,但是如果對(duì)它一直添加觀察者存捺,會(huì)耗費(fèi)大量?jī)?nèi)存即寒,為了防止內(nèi)存溢出所以也應(yīng)該移除。
```
#pragma mark - 移除觀察者
- (void)removeObserver {
// 沒添加之前不能移除否則會(huì)崩潰
if (!_currentModel) {
return;
}else {
[self.avPlayer.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[self.avPlayer removeTimeObserver:self.timePlayProgerssObserver];
}
}
```
```
#pragma mark - 監(jiān)聽緩存狀態(tài)
- (void)addObserverOfLoadedTimeRanges {
[self.avPlayer.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.avPlayer.currentItem.loadedTimeRanges;
//本次緩沖的時(shí)間范圍
CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
//緩沖總長(zhǎng)度
NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
//音樂的總時(shí)間
NSTimeInterval duration = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
//計(jì)算緩沖百分比例
NSTimeInterval scale = totalLoadTime/duration;
//更新緩沖進(jìn)度條
_progressView.progress = scale;
_durationLabel.text = [NSString stringWithFormat:@"%d:%@",(int)duration/60,[self FormatTime:(int)duration%60]];
}
}
```
```
#pragma mark - 添加播放進(jìn)度的觀察者
- (void)addTimePlayProgerssObserver {
__block UISlider *weakPregressSlider = _playerSlider;
__weak UILabel *waekCurrentLabel = _currentLabel;
__block int weakRow = _row;
__weak typeof(self) weakSelf = self;
self.timePlayProgerssObserver = [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
// 當(dāng)前播放的時(shí)間
float current = CMTimeGetSeconds(time);
// 更新歌詞
if (weakRow < weakSelf.lrcArr.count) {
FHLrcModel *model = weakSelf.lrcArr[weakRow];
if (model.presenTime == (int)current) {
[weakSelf reloadTabelViewWithRow:weakRow];
weakRow++;
}
}
// 總時(shí)間
float total = CMTimeGetSeconds(weakSelf.avPlayer.currentItem.duration);
// 更改當(dāng)前播放時(shí)間
NSString *currentSStr = [weakSelf FormatTime: (int)current % 60];
waekCurrentLabel.text = [NSString stringWithFormat:@"%d:%@",(int)current / 60,currentSStr];
// 更新播放進(jìn)度條
weakPregressSlider.value = current / total;
}];
}
```
```
// 播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nextButtonClick:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
```
??寫在viewDidLoad里召噩,因?yàn)樘砑右淮尉涂梢阅刚浴2シ磐瓿芍苯硬シ畔乱皇住?```
#pragma mark - 上一首
- (void)prevButtonClick :(UIButton *)button {
_index--;
if (_index < 0) {
_index = self.albumArr.count - 1;
}
[self playMusic];
}
#pragma mark - 下一首
- (void)nextButtonClick :(UIButton *)button {
_index++;
if (_index >= self.albumArr.count) {
_index = 0;
}
[self playMusic];
}
```
??當(dāng)播放第一首歌曲時(shí),點(diǎn)擊上一首播放最后一首歌曲具滴。當(dāng)播放最后一首歌曲時(shí)凹嘲,點(diǎn)擊下一首播放第一首歌曲。
??由于篇幅的原因构韵,下一篇博客再介紹歌詞的實(shí)現(xiàn)周蹭。重要的事情說三遍:項(xiàng)目地址[GitUp](https://github.com/haichong/iOSPlayerStudy) ,歡迎下載疲恢。
??