iOS-QQ音樂播放器的簡單實現
一. QQ音樂播放器的簡單實現
每個音樂播放器的實現都大致相同,個人認為難點在于歌曲播放與Slider的同步,歌詞的解析與播放的同步。這些過程雖然繁瑣,但是理解起來并不難。先來看看簡單實現結果吧。
QQ音樂播放器簡單實現
雖然功能簡單,但是還是耗費了我很長時間來整理其中的邏輯關系,接下來我們就來分析一下音樂播放器的簡單實現。
二. 主界面的搭建
這個播放器比較簡單,只有一個界面,創建CLPlayingViewController,使用stortyboard布局,先來看一下布局
播放器頁面布局
界面共分為四部分,其中需要注意的是 中間歌手圖片約束的添加 ,為了保證其在不同的屏幕上都為圓形,這里先將1、3、4部分布局約束添加好,然后設置歌手圖片距離上面第1部分和下面第3部分歌詞分別有一個距離并且居中顯示,然后設置圖片長寬比為1:1即可,其他部分的約束比較簡單,這里不再贅述,添加約束還是需要多練才能掌握。歌手圖片的約束如下。
歌手圖片Image的約束
背景圖片的毛玻璃效果
添加毛玻璃效果的方法有很多種,如果本來就有一張模糊處理過的美景圖片那就最好不過了,如果沒有的話只能自己添加,可以使用第三方框架 (DRNRealTimeBlur)或者自己通過coreImage實現高斯模糊。以上兩種方法功能強大但是比較麻煩,我們這里只是簡單的實現圖片模糊,可以使用給UIImageView添加UIToolbar來實現
// 1.初始化toolBar
UIToolbar *toolBar = [[UIToolbar alloc] init];
// 2. 設置frame
toolBar.frame = [UIScreen mainScreen].bounds;
// 3. 設置模糊的樣式
toolBar.barStyle = UIBarStyleBlack;
// 4. 添加到imageView
[self.albumView addSubview:toolBar];
而iOS8之后storyboard中出現了專門給圖片添加模糊效果的控件。
Blur
我們只需將blur添加到imageView上面然后設置blur的樣式即可,
blur的樣式
需要注意的是:blur需要添加到背景imageView上面和其他View之間,防止模糊效果影響到歌手圖片,播放按鈕等其他控件。
歌手圖片進行圓角處理
這里直接修改imageView的layer進行圓角處理
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// .添加圓角
self.iconView.layer.cornerRadius = self.iconView.bounds.size.width * 0.5;
self.iconView.layer.masksToBounds = YES;
// 設置邊框顏色寬度
self.iconView.layer.borderColor = CLColor(36, 36, 36, 1.0).CGColor;
self.iconView.layer.borderWidth = 5;
}
這里需要注意的是雖然我們在storyboard中為歌手圖片添加約束,但是當運行到模擬器上時,屏幕大小和storyboard中屏幕大小可能會不同,如果在viewDidLoad中設置圓角,此時拿到的歌手圖片的大小還是storyboard中的大小,所以顯示在模擬器上就會使圓形計算錯誤,因此我們在viewWillLayoutSubviews方法中添加圓角設置。
歌手圖片的轉動動畫效果
圖片轉動用到核心動畫利用CABasicAnimation修改圖片z軸進行旋轉,設置一定時間旋轉一圈,重復無數次。
CABasicAnimation *rotateAnimate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotateAnimate.fromValue = @(0);
rotateAnimate.toValue = @(M_PI * 2);
rotateAnimate.repeatCount = NSIntegerMax;
rotateAnimate.duration = 36;
[self.iconView.layer addAnimation:rotateAnimate forKey:nil];
最后,修改Slider的原點的樣式,我們發現在storyboard是沒有辦法修改Slider原點的圖片的,只能修改顏色,所以需要通過代碼修改Slider原點的圖片
[self.progressSlider setThumbImage:[UIImage imageNamed:@"player_slider_playback_thumb"] forState:UIControlStateNormal];
三. 播放音樂
這里為了方便使用本地音樂進行播放,首先根據plist文件創建CLMusicModel模型,然后創建CLMusicTool工具類,用來獲取所有音樂以及當前正在播放的音樂設置默認播放的音樂等等。最后創建CLAVdioTool工具類用來播放音樂,以及切換上一首,下一首音樂。
接下來來詳細分析這三個類的作用。
CLMusicModel模型類僅僅是歌曲模型,內含歌曲名,文件名,歌詞文件名,歌手信息等。
CLMusicTool工具類提供方法用來初始化音樂列表將plist文件轉化為Model,并存儲到數組中,獲取所有音樂數組,以及設置默認播放的音樂
static NSArray *_musics;
static CLMusicModel *_playingMusic;
// 類加載的時候初始化音樂列表和播放音樂
+(void)initialize
{
if (_musics == nil) {
// 使用MJExtension將plist文件轉化為模型
_musics = [CLMusicModel objectArrayWithFilename:@"Musics.plist"];
}
if (_playingMusic == nil) {
// 設置一開始就播放的音樂
_playingMusic = _musics[0];
}
}
// 獲取所有音樂
+(NSArray *)Musics
{
return _musics;
}
// 當前正在播放的音樂
+(CLMusicModel *)playingMusic
{
return _playingMusic;
}
// 設置默認播放的音樂
+(void)setUpPlayingMusic:(CLMusicModel *)playingMusic
{
_playingMusic = playingMusic;
}
CLAVdioTool工具類用來播放音樂,以及切換上一首,下一首音樂。這里提供三個方法,根據參數文件名找到文件路徑并根據文件路徑創建播放器player,創建全局字典用來存儲播放器,每首歌對應一個播放器,播放音樂的時候先去字典中找到對應的播放器進行播放,如果沒有就創建對應的播放器。
static NSMutableDictionary *_players;
+(void)initialize
{
_players = [NSMutableDictionary dictionary];
}
+(AVAudioPlayer *)playingMusicWithMusicFileName:(NSString *)filename
{
AVAudioPlayer *player = nil;
player = _players[filename];
if (player == nil) {
// 文件路徑轉化為url
NSURL *url = [[NSBundle mainBundle]URLForResource:filename withExtension:nil];
if (url == nil) {
return nil;
}
// 創建player
player = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
// 準備播放
[player prepareToPlay];
// 將播放器存儲到字典中
[_players setObject:player forKey:filename];
}
// 開始播放
[player play];
return player;
}
+(void)pauseMusicWithMusicFileName:(NSString *)filename
{
AVAudioPlayer *player = _players[filename];
if (player) {
[player pause];
}
}
+(void)stopMusicWithMusicFileName:(NSString *)filename
{
AVAudioPlayer *player = _players[filename];
if (player) {
[player stop];
[_players removeObjectForKey:filename];
player = nil;
}
}
此時在CLPlayingViewController中進行音樂播放就非常簡單了,使用CLMusicTool獲得當前正在播放的CLMusicModel音樂模型,對頁面信息進行設置,使用CLAVdioTool根據CLMusicModel的屬性音樂名,播放音樂。主要代碼如下。
// 獲取當前正在播放的音樂
CLMusicModel *playingMusic = [CLMusicTool playingMusic];
// 根據文件名播放音樂并且獲取播放的音樂
AVAudioPlayer currentPlayer = [CLAVdioTool playingMusicWithMusicFileName:playingMusic.filename];</code></pre>
四. Slider時間條的處理
播放時間和歌曲總時間的string處理,通過播放器可以拿到已經播放時間currentTime和歌曲總時間duration,播放器返回給我們的是秒,需要將秒轉化為分鐘,這里給NSString添加分類,添加stringWithTime方法將返回的NSTimeInterval轉化為NSString。
+ (NSString
)stringWithTime:(NSTimeInterval)time
{
NSInteger min = time / 60;
NSInteger sec = (int)round(time) % 60;
return [NSString stringWithFormat:@"%02ld:%02ld",min,sec];
}</code></pre>
Slider隨播放時間而移動
通過添加定時器的方法,使Slider原點隨著播放的時間而移動,將定時器添加到主RunLoop中并修改Mode為NSRunLoopCommonModes防止在滑動時定時器失效。
#pragma mark - 對進度條時間的處理
- (void)addProgressTimer
{
// 定時器每1s執行一次,第一次來到這里需要先等1s,所以先刷新一下界面
[self updateProgressInfo];
self.progressTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateProgressInfo) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.progressTimer forMode:NSRunLoopCommonModes];
}
- (void)removeProgressTimer
{
[self.progressTimer invalidate];
self.progressTimer = nil;
}
#pragma mark - 更新進度條
- (void)updateProgressInfo
{
// 1.更新播放的時間,使用NSString分類方法處理時間。
self.currentTimeLabel.text = [NSString stringWithTime:self.currentPlayer.currentTime];
// 2.更新滑動條
self.progressSlider.value = self.currentPlayer.currentTime / self.currentPlayer.duration;
}</code></pre>
注意:當我們在播放音樂方法里面添加定時器的時候需要先移除定時器,然后在添加,避免當點擊下一首的時候,定時器沒有移除,時間發生錯誤。
Slider滑動更新界面和音樂播放時間
給Slider添加點擊事件,監聽Slider的滑動。在storyboard中給Slider添加點擊事件,分別監聽Slider的點擊,滑動和松開。
- 當按Slider滑塊下時移除定時器。
- 當滑動Slider滑塊時,根據滑動的數值 * 歌曲總時間計算出當前滑動點對應的播放時間,然后更新播放時間label的text。
- 當手指松開時,設置播放器播放時間并且添加定時器。
#pragma mark - slider 事件處理
- (IBAction)start {
// 移除定時器
[self removeProgressTimer];
}
- (IBAction)end {
// 1.更新播放的時間
self.currentPlayer.currentTime = self.progressSlider.value * self.currentPlayer.duration;
// 2.添加定時器
[self addProgressTimer];
}
- (IBAction)progressValueChange {
self.currentTimeLabel.text = [NSString stringWithTime:self.progressSlider.value self.currentPlayer.duration];
}</code></pre>
Slider的點擊直接跳轉當前時間播放
通過storyboard給Slider添加手勢監聽Slider的點擊,當點擊Slider直接跳轉到點擊位置開始播放。
獲取點擊的位置,然后計算點擊位置占真個Slider的比例,根據比例計算出當前播放時間,最后更新label時間和滑塊的位置。
- (IBAction)sliderClick:(UITapGestureRecognizer
)sender {
// 1.獲取點擊到的點
CGPoint point = [sender locationInView:sender.view];
// 2.獲取點擊的比例
CGFloat ratio = point.x / self.progressSlider.bounds.size.width;
// 3.更新播放的時間
self.currentPlayer.currentTime = self.currentPlayer.duration * ratio;
// 4.更新時間和滑塊的位置
[self updateProgressInfo];
}</code></pre>
五. 播放暫停、上一首、下一首的點擊處理
監聽播放按鈕點擊
播放按鈕有播放和暫停兩個狀態,程序一開始運行就自動播放,所以首先需要在音樂一開始播放的時候修改播放按鈕的selected。
self.playWithPauseBtn.selected = currentPlayer.isPlaying;
當點擊播放按鈕的時候首先需要修改按鈕的狀態,然后判斷音樂播放的狀態,如果正在播放則暫停音樂,移除定時器,并且停止歌手圖片的動畫,如果是暫停的則開始播放,添加定時器,并且回復動畫。
暫停動畫和恢復動畫通過給CALayer添加分類方法實現。分類可以直接拖到別的項目中使用
- (void)pauseAnimate
{
CFTimeInterval pausedTime = [self convertTime:CACurrentMediaTime() fromLayer:nil];
self.speed = 0.0;
self.timeOffset = pausedTime;
}
- (void)resumeAnimate
{
CFTimeInterval pausedTime = [self timeOffset];
self.speed = 1.0;
self.timeOffset = 0.0;
self.beginTime = 0.0;
CFTimeInterval timeSincePause = [self convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
self.beginTime = timeSincePause;
}</code></pre>
播放按鈕點擊事件實現
- (IBAction)playWithPause:(UIButton *)sender {
sender.selected = !sender.selected;
if (self.currentPlayer.playing) {
// 1.暫停播放器
[self.currentPlayer pause];
// 2.移除定時器
[self removeProgressTimer];
// 3.暫停旋轉動畫
[self.iconView.layer pauseAnimate];
} else {
// 1.開始播放
[self.currentPlayer play];
// 2.添加定時器
[self addProgressTimer];
// 3.恢復動畫
[self.iconView.layer resumeAnimate];
}
}</code></pre>
上一首下一首按鈕點擊實現
我們可以在CLMusicTool工具類中添加獲取上一首歌曲和下一首歌曲的方法,首先拿到當前播放音樂的下標,然后在獲取上一首或者下一首歌曲時需要對下標進行判斷,拿上一首為例,如果當前歌曲的下標為0,則返回最后一首歌,形成循環播放,如果不為0則獲取上一首即可,否則會造成數組越界。獲取下一首判斷方法相同。
// 返回上一首音樂
- (CLMusicModel *)previousMusic
{
NSInteger index = [_musics indexOfObject:_playingMusic];
if (index == 0) {
index = _musics.count -1;
}else{
index = index -1;
}
CLMusicModel *previousMusic = _musics[index];
return previousMusic;
}
// 返回下一首音樂
- (CLMusicModel *)nextMusic
{
NSInteger index = [_musics indexOfObject:_playingMusic];
if (index == _musics.count - 1) {
index = 0;
}else{
index = index +1;
}
CLMusicModel previousMusic = _musics[index];
return previousMusic;
}</code></pre>
此時點擊上一首或者下一首按鈕時,首先停止當前播放的音樂,然后將上一首或者下一首歌曲設置為默認播放歌曲,最后開始播放,因為停止播放當前音樂,開始播放下一首音樂的代碼相同,將其抽成一個方法
- (IBAction)nextMusic {
CLMusicModel
nsxtMusic = [CLMusicTool nextMusic];
[self playMusicWithMusic:nsxtMusic];
}
- (IBAction)previousMusic {
CLMusicModel *previousMusic = [CLMusicTool previousMusic];
[self playMusicWithMusic:previousMusic];
}
- (void)playMusicWithMusic:(CLMusicModel )muisc
{
// 獲取當前播放的音樂并停止
CLMusicModel playingMusic = [CLMusicTool playingMusic];
[CLAVdioTool stopMusicWithMusicFileName:playingMusic.filename];
// 設置下一首或者上一首為默認播放音樂
[CLMusicTool setUpPlayingMusic:muisc];
// 更新界面
[self startPlayingMusic];
}</code></pre>
六. 歌詞的處理
創建存放歌詞的tableView
當滑動歌手圖片時,會來到歌詞界面,這里往歌手圖片和歌詞label上面覆蓋scrollView,設置scrollView的contentSize為兩個屏幕的寬度,顯示歌詞的tableView放在屏幕外面,布局如圖。

歌詞tableView布局
使用storyboard添加scrollView并自定義scrollView為CLLrcView,使用代碼添加tableView,在scrollView的initWithFrame方法中創建并初始化tableView, 在layoutSubviews中對tableView進行一些設置。例如設置tableView的背景圖片為透明,需要cell之間的線,設置tabaleView的contentInset一開始滑動到屏幕中央。
scrollView滑動歌手圖片逐漸消失處理
當向右滑動出現歌詞時,歌手圖片和歌詞label是逐漸消失的,我們通過scrollView的代理監聽scrollView的滑動,根據scrollView.contentOffset.x的長度占據整個屏幕的比例設置歌手圖片和歌詞label的透明度,當完全滑動一個屏幕寬度時,歌手圖片和歌詞label的透明度為0。
#pragma mark - scrollView代理方法
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint offcetPoint = scrollView.contentOffset;
CGFloat alpha = 1 - offcetPoint.x / self.view.frame.size.width;
self.iconView.alpha = alpha;
self.lrcLabel.alpha = alpha;
}
自定義tableView的cell和cell中的label
自定義tableView的cell為CLLrcTableViewCell,對cell進行初始化,對cell的style和背景進行設置,對cell內label的frame和字體等進行設置。
自定義label為CLLrcLabel,便于我們之后對label中的歌詞進行一些處理。
歌詞的顯示處理
歌詞顯示處理邏輯比較繁瑣,這里盡量使代碼解耦,便于更清晰的理解其中的邏輯。
首先歌詞的顯示在自定義的CLLrcView中的tableView中,所以給CLLrcView添加lrcName歌詞文件名字屬性,用來接收歌詞文件的名字,然后重寫setLrcName:方法根據歌詞名獲得歌詞并對歌詞進行一些處理。先來看一下歌詞文件中的歌詞樣式

歌詞內容格式
這里需要將每一句歌詞轉化為歌詞模型,模型中包含時間和歌詞,并且時間需要轉化為秒。
例如 [01:32.64]寧愿相信我們前世有約 需要轉化為time = 96 text = @"寧愿相信我們前世有約"存入到模型中。
首先需要將歌詞一行一行分開轉化為數組,這里創建CLLrcTool工具類用來將每一行歌詞分開,并將每一行存入到數組中,此時數組中存儲的歌詞樣式為 [01:32.64]寧愿相信我們前世有約
然后創建CLLrcLine歌詞模型,模型中提供方法將 [01:32.64]寧愿相信我們前世有約 樣式的歌詞分割出時間和歌詞內容并轉化為模型。
先來看CLLrcTool的將歌詞轉化為歌詞數組方法
+(NSArray )lrcToolWithLrcName:(NSString )lrcName
{
// 1. 獲取路徑
NSString lrcFilePath = [[NSBundle mainBundle]pathForResource:lrcName ofType:nil];
// 2. 獲取歌詞
NSString lrcString = [NSString stringWithContentsOfFile:lrcFilePath encoding:NSUTF8StringEncoding error:nil];
// 將歌詞轉化為數組,會以每個\n為分隔符 將每一行轉化為數組中的每個元素
NSArray lrcArr = [lrcString componentsSeparatedByString:@"\n"];
NSMutableArray tempArr = [NSMutableArray array];
// 遍歷數組,將不需要的去除,并調用模型的方法,將字符串轉化為模型
for (NSString *lrcLineString in lrcArr) {
// 過濾掉不要的字符串,如果是以這些開頭 或者不是以[開頭的直接退出循環
if ([lrcLineString hasPrefix:@"[ti:"] ||
[lrcLineString hasPrefix:@"[ar:"] ||
[lrcLineString hasPrefix:@"[al:"] ||
![lrcLineString hasPrefix:@"["]) {
continue;
}
// 將字符串轉化為模型
CLLrcLine *lrcLine = [CLLrcLine LrcLineString:lrcLineString];
[tempArr addObject:lrcLine];
}
return tempArr;
}</code></pre>
模型中將字符串轉化為模型的方法
// 返回模型
- (instancetype)LrcLineString:(NSString *)lrcLineString
{
return [[self alloc] initWithLrcLineString:lrcLineString];
}
// 將字符串分割為時間和歌詞內容
- (instancetype)initWithLrcLineString:(NSString *)lrcLineString
{
if (self = [super init]) {
// [01:32.64]寧愿相信我們前世有約
NSArray *lrcArray = [lrcLineString componentsSeparatedByString:@"]"];
// 設置歌詞內容
self.text = lrcArray[1];
// 對時間進行處理然后設置時間
self.time = [self timeWithString:[lrcArray[0] substringFromIndex:1]];
}
return self;
}
// 處理時間,將時間轉化為秒
- (NSTimeInterval)timeWithString:(NSString )timeString
{
// 01:32.64
NSInteger min = [[timeString componentsSeparatedByString:@":"][0] integerValue];
NSInteger sec = [[timeString substringWithRange:NSMakeRange(3, 2)] integerValue];
NSInteger hs = [[timeString componentsSeparatedByString:@"."][1] integerValue];
return min 60 + sec + hs * 0.01;
}</code></pre>
此時已經得到歌詞模型的數組,刷新tableView即可。
但是此時的歌詞是固定的,并不會根據播放時間即時的顯示當先播放的時間。
歌詞的即時顯示
如果想即時的按照播放時間顯示歌詞,則需要拿到歌曲的總時間并且使用定時器不斷的獲取當前播放的時間,因為歌詞的時間需要比較精確,這里使用CADisplayLink定時器
#pragma mark - 歌詞定時器
- (void)addLrcTimer
{
self.lrcTiemr = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateLrcInfo)];
[self.lrcTiemr addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)removeLrcTimer
{
[self.lrcTiemr invalidate];
self.lrcTiemr = nil;
}
#pragma mark 更新歌詞
- (void)updateLrcInfo
{
self.lrcScrollView.currentTime = self.currentPlayer.currentTime;
}</code></pre>
為CLLrcView添加currentTime已播放時間和duration歌曲總時間屬性,重寫setCurrentTime:對currentTime進行一些判斷。
- 獲取當前歌曲歌詞數組的行數。
- 遍歷獲得每一行和下一行歌詞的時間。
- 進行判斷,當當前播放的時間大于等于第i行的時間,并且小于第i+1行的時間則表明當前正在唱的是第i行。
- 將第i行移動到屏幕中央,然后將第i行記錄下來,更新第i行,回到 tableView:tableView cellForRowAtIndexPath: 方法中判斷如果是第i行則將lable的字體放大,如果不是則改為原來的值。
- 因為修改第i行內容字體大小之前,第i-1行的內容也被修改過,因此在更新第i行時需要同時更新第i-1行。
- 每次切換歌曲時,需要將當前行數清空,避免造成數組越界。
歌詞的即時渲染
為達到歌詞隨播放時間即時渲染變換顏色,通過重寫CLLrcLabel的drawRect:方法渲染歌詞的顏色,并為CLLrcLabel添加progress屬性用來記錄歌詞的播放進度,通過播放進度的變化隨時調用CLLrcLabel的setNeedsDisplay刷新CLLrcLabel的渲染長度。
播放進度 = (當前播放的時間 - 正在唱的歌詞的開始時間)/ 當前唱的歌詞需要的總時間。
主頁面歌詞的即時顯示
將主頁面歌詞的label同樣設置為CLLrcLabel型,為CLLrcView添加lrcLabel屬性,lrcLabel是CLLrcLabel類型的,在獲得當前播放放的歌詞之后,通過lrcLabel屬相將歌詞內容回傳給主頁面歌詞label即可。
CLLrcView中setCurrentTime:方法
#pragma mark - 當前播放時間set方法
- (void)setCurrentTime:(NSTimeInterval)currentTime
{
// 記錄當前時間
_currentTime = currentTime;
// 獲取歌詞行數
NSInteger count = self.lrcList.count;
for (int i = 0; i < count; i ++) {
// 獲取i位置的歌詞
CLLrcLine *currentLrcLine = self.lrcList[i];
// 獲取下一句歌詞
NSInteger nextIndex = i + 1;
// 先創建空的歌詞模型
CLLrcLine *nextLrcLine = nil;
// 判斷歌詞是否存在
if (nextIndex < self.lrcList.count) {
// 說明存在
nextLrcLine = self.lrcList[nextIndex];
}
// 用播放器的當前的時間和i位置歌詞、i+1位置歌詞的時間進行比較,如果大于等于i位置的時間并且小于等于i+1歌詞的時間,說明應該顯示i位置的歌詞。
// 并且如果正在顯示的就是這行歌詞則不用重復判斷
if (self.currentIndex != i && currentTime >= currentLrcLine.time && currentTime < nextLrcLine.time) {
// 設置主頁上的歌詞
self.lrcLabel.text = currentLrcLine.text;
// 將當前播放的歌詞移動到中間
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
// 記錄上一句位置,當移動到下一句時,上一句和當前這一句都需要進行更新行
NSIndexPath *previousPath = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
// 記錄當前播放的下標。下次來到這里,currentIndex指的就是上一句
self.currentIndex = i;
[self.tableView reloadRowsAtIndexPaths:@[indexPath,previousPath] withRowAnimation:UITableViewRowAnimationNone];
}
if (self.currentIndex == i) {
// 獲取播放速度 已經播放的時間 / 播放整句需要的時間
CGFloat progress = (currentTime - currentLrcLine.time) / (nextLrcLine.time - currentLrcLine.time);
// 獲取當前行數的cell
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
CLLrcTableViewCell *lrccell = [self.tableView cellForRowAtIndexPath:indexPath];
// 設置歌詞界面歌詞的進度
lrccell.lrcLabel.progress = progress;
// 設置主頁面歌詞的進度
self.lrcLabel.progress = progress;
}
}
}</code></pre>
CLLrcLabel的setProgress:方法
- (void)setProgress:(CGFloat)progress
{
_progress = progress;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
[[UIColor greenColor] set];
CGRect fillRect = CGRectMake(0, 0, self.bounds.size.width * self.progress, self.bounds.size.height);
UIRectFillUsingBlendMode(fillRect, kCGBlendModeSourceIn);
}</code></pre>
七. 播在放線音樂
雖然項目中播放的是本地音樂,但是使用AVFoundation播放在線音樂也非常簡單。
// 1.創建音樂資源
NSURL *url = [NSURL URLWithString:@"url"];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
// 2.創建播放器
// AVPlayer *player = [AVPlayer playerWithURL:url];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
[player play];
注意:AVAudioPlayer只能播放本地音樂,AVPlayerItem既能播放本地音樂也能播放在線音樂
八. 總結
至此,QQ音樂播放器已經基本實現,其中還有許多細節沒有處理到位,例如歌曲播放完畢之后的處理,進入后臺在返回的旋轉動畫的處理等,另外對于歌詞即時顯示感覺講的還不是很清晰,如果有不清楚的地方還請提出來,我們一起探討分析一下。
來自:http://www.jianshu.com/p/540948679f9a