天天看點

iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)

iOS開發之網絡音樂播放器(SC音樂)(一)

前言

一直都想做一款自己的網絡音樂播放器,兩個月前做了一個swift版的網絡音樂播放器,但是那個播放器資料來源于我自己用VPS和nginx搭建的伺服器,所有的檔案都要自己準備,包括mp3、歌詞、專輯圖檔等,非常麻煩,有興趣的可以跟我要源碼。現在這款音樂播放器資料是來源于百度音樂,前前後後花了一個多星期搞定,網上有一些音樂網站的API,有興趣的同學可以去查一下。我這裡貼一下我自己用到的百度音樂API:http://blog.csdn.net/zuiaisha1/article/details/61200422。

正題

一、播放控制

SC音樂用的是AVPlayer,這個庫是蘋果自帶的視訊庫,也可以播放音頻,可以支援邊播放邊緩存,使用也比較簡單。詳細看蘋果官網介紹:https://developer.apple.com/documentation/avfoundation/avplayer。這裡介紹一下要用到的東西。我們知道,播放器要有播放、暫停、上一曲、下一曲的功能,還要知道播放總時間,目前時間,播放狀态,能夠從歌曲的任意時間點開始播放。在AVPlayer庫中:

play ---- 播放

pause ---- 暫停

rate ---- 播放狀态(0.0代表目前狀态是暫停, 1.0代表目前狀态是播放)

seekToTime ---- 從某個時間點開始播放(拖動進度條用到)

duration ---- 歌曲總時間

currentTime ---- 目前播放時間

上一曲和下一曲可以通過改變歌曲url來實作。

初始化一個AVPlayer需要一個playItem,是以先初始化一個playItem,再用這個playItem去執行個體化一個play,具體代碼:

MusicPlayerManager.h

//
//  MusicPlayerManager.h
//  BaiduMusic
//
//  Created by 淩       陳 on 8/21/17.
//  Copyright © 2017 淩       陳. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>

@interface MusicPlayerManager : NSObject

typedef enum : NSUInteger {
    RepeatPlayMode,
    RepeatOnlyOnePlayMode,
    ShufflePlayMode,
} ShuffleAndRepeatState;

@property (nonatomic,strong) AVPlayer *play;
@property (nonatomic,strong) AVPlayerItem *playItem;
@property (nonatomic,assign) ShuffleAndRepeatState shuffleAndRepeatState;
@property (nonatomic,assign) NSInteger playingIndex;

+ (MusicPlayerManager *)sharedManager;
-(void) setPlayItem: (NSString *)songURL;
-(void) setPlay;
-(void) startPlay;
-(void) stopPlay;
-(void) play: (NSString *)songURL;

@end
           

MusicPlayerManager.m

//
//  MusicPlayerManager.m
//  BaiduMusic
//
//  Created by 淩       陳 on 8/21/17.
//  Copyright © 2017 淩       陳. All rights reserved.
//

#import "MusicPlayerManager.h"


@implementation MusicPlayerManager

static MusicPlayerManager *_sharedManager = nil;

+(MusicPlayerManager *)sharedManager {
    @synchronized( [MusicPlayerManager class] ){
        if(!_sharedManager)
            _sharedManager = [[self alloc] init];
        return _sharedManager;
    }
    return nil;
}


-(void) setPlayItem: (NSString *)songURL {
    NSURL * url  = [NSURL URLWithString:songURL];
    _playItem = [[AVPlayerItem alloc] initWithURL:url];
}

-(void) setPlay {
    _play = [[AVPlayer alloc] initWithPlayerItem:_playItem];
}

-(void) startPlay {
    [_play play];
}

-(void) stopPlay {
    [_play pause];
}

-(void) play: (NSString *)songURL {
    [self setPlayItem:songURL];
    [self setPlay];
    [self startPlay];
}

@end

           

将一首歌的url傳進play方法就可以實作播放音樂了。上一曲下一曲隻是改變一下歌曲的url就可以實作。

歌曲總時長:

_play.currentItem.duration

目前播放時間:

_play.currentTime

從某個時間點開始播放:

//播放器定位到對應的位置

CMTime targetTime = CMTimeMake((int64_t)(currentTime), 1);

[musicPlayer.play seekToTime:targetTime];

播放狀态:

//播放或者暫停按鍵按下,要判斷播放狀态

if (_play.rate == 0) {

// 目前狀态為暫停

// 下面要執行播放的代碼

} else {

// 目前狀态為播放

// 下面要執行暫停的代碼

}

監管播放(更新播放進度條和目前時間):

_playerTimeObserver = [musicPlayer.play addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

// 這裡一秒進來一次,可以更新時間、播放進度條和歌詞

// 需要注意的是_playerTimeObserver必須要每首歌播放結束後清掉,不清會有問題。

}

播放結束通知:

// 歌曲播放結束後會調用自定義的方法finishedPlaying

[[NSNotificationCenter defaultCenter]  addObserver:self selector:@selector(finishedPlaying) name:AVPlayerItemDidPlayToEndTimeNotification object:_play.currentItem];

二、擷取百度音樂資料

百度音樂的全接口:http://tingapi.ting.baidu.com/v1/restserver/ting。所有的資料都是以這個為開頭,後面加一些其他東西。

可以請求到的資料有很多,這裡隻說幾個:

一、擷取歌曲清單(新歌榜、熱歌榜、經典老歌榜等)

例:method=baidu.ting.billboard.billList&type=1&size=10&offset=0

完整的請求位址:http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.billboard.billList&format=json&type=1&offset=0&size=100 (前100熱門歌曲,要擷取哪個榜隻需要改變一下type的值就行了)

參數: type = 1-新歌榜,2-熱歌榜,11-搖滾榜,12-爵士,16-流行,21-歐美金曲榜,22-經典老歌榜,23-情歌對唱榜,24-影視金曲榜,25-網絡歌曲榜

size = 10 //傳回條目數量

offset = 0 //擷取偏移

擷取到的資料如截圖所示:

iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)
iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)

我們隻需要“song_list”裡面的資料,點開後發現“song_list“就是一個字典, 繼續點開[0],裡面是一首歌的資訊,包括歌名、歌手名、專輯名等等,但是并沒有歌曲url,别急這個要另外擷取,需要用到這裡的"song_id".

iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)
iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)
iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)
iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)

二、擷取歌曲url

例:method=baidu.ting.song.lry&songid=877578

完整的請求位址:http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.song.play&songid=877578

參數:songid = 877578 //假設這個是歌曲id

擷取到的資料如圖所示:

iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)

我們隻關注”bitrate“中的資料,點開發現裡面有”file_link“,這個就是歌曲的url:

iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)

網絡請求我用大名鼎鼎的AFNetworking,擷取到的資料解析我用MJExtension,主要将資料轉成NSArray,将這兩個庫拉入自己的工程,添加頭檔案#import "AFNetworking.h"   #import "MJExtension.h"即可。

// 擷取歌曲資訊請求

// 新歌榜

#pragma mark - 導入資料
- (void)loadNewSongs: (UITableView *)songListTableView
{
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    NSString *path = @"http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.billboard.billList&format=json&type=1&offset=0&size=100";//前100熱門歌曲
    [manager GET:path parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        if ([responseObject isKindOfClass:[NSDictionary class]])
        {
            NSArray *array = [responseObject objectForKey:@"song_list"];
            _OMSongs = [OMHotSongInfo mj_objectArrayWithKeyValuesArray:array];
            //            [self reloadTableView:_radioAndMusicTableView];
            [songListTableView reloadData];
        }
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"error--%@",error);
    }];
}
           

其它的榜單改一下type的值就可以了。

擷取歌曲url請求:

#pragma mark - 擷取歌曲url
-(void)getSelectedSong: (NSString *)songID index: (long)index {
    
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    NSString *path = [@"http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.song.play&songid="  stringByAppendingString:songID];
    [manager GET:path parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        if ([responseObject isKindOfClass:[NSDictionary class]])
        {
            NSDictionary *array = [responseObject objectForKey:@"bitrate"];
            
            self.file_link = [array objectForKey:@"file_link"];
            self.file_size = [array objectForKey:@"file_size"];
            self.file_duration = [array objectForKey:@"file_duration"];
            self.playSongIndex = index;

        }
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"error--%@",error);
    }];
    
}
           

三、播放界面

1、圓形專輯圖檔(原先是矩形的,需要處理一下)

.h檔案

@property (nonatomic, strong) UIView *playControllerView;

@property (nonatomic, strong) UIImageView *currentPlaySongImage;
           

.m檔案

// 專輯圖檔

// 先将專輯圖檔放到正方形UIImageView, 再将UIImageView圓角設定為正方形邊長的一半就得到圓形的UIImageView了

_currentPlaySongImage = [[SCImageView alloc] initWithFrame:CGRectMake(10, 10 , _playControllerView.frame.size.height - 20 , _playControllerView.frame.size.height - 20)];

_currentPlaySongImage.image = [UIImage imageNamed:@"album_default"];

_currentPlaySongImage.clipsToBounds = true;

_currentPlaySongImage.layer.cornerRadius = (_playControllerView.frame.size.height - 20) * 0.5;

[_playControllerView addSubview:_currentPlaySongImage];
           

2、專輯圖檔旋轉

我封裝了一個UIImageView的旋轉動畫類,代碼如下:

SCImageView.h

//
//  SCImageView.h
//  BaiduMusic
//
//  Created by 淩       陳 on 8/22/17.
//  Copyright © 2017 淩       陳. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface SCImageView : UIImageView

-(void) startRotating;
-(void) stopRotating;
-(void) resumeRotate;

@end
           

SCImageView.m

//
//  SCImageView.m
//  BaiduMusic
//
//  Created by 淩       陳 on 8/22/17.
//  Copyright © 2017 淩       陳. All rights reserved.
//

#import "SCImageView.h"

@implementation SCImageView

// 開始旋轉
-(void) startRotating {
    CABasicAnimation* rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotateAnimation.fromValue = [NSNumber numberWithFloat:0.0];
    rotateAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2];   // 旋轉一周
    rotateAnimation.duration = 20.0;                                 // 旋轉時間20秒
    rotateAnimation.repeatCount = MAXFLOAT;                          // 重複次數,這裡用最大次數

    [self.layer addAnimation:rotateAnimation forKey:nil];
  
}

// 停止旋轉
-(void) stopRotating {
    
    CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    self.layer.speed = 0.0;                                          // 停止旋轉
    self.layer.timeOffset = pausedTime;                              // 儲存時間,恢複旋轉需要用到
}

// 恢複旋轉
-(void) resumeRotate {
    
    if (self.layer.timeOffset == 0) {
        [self startRotating];
        return;
    }
    
    CFTimeInterval pausedTime = self.layer.timeOffset;
    self.layer.speed = 1.0;                                         // 開始旋轉
    self.layer.timeOffset = 0.0;
    self.layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;                                             // 恢複時間
    self.layer.beginTime = timeSincePause;                          // 從暫停的時間點開始旋轉
}

@end
           

歌曲剛開始播放調用startRotating,開始旋轉,點選暫停按鍵時調用stopRotating停止旋轉,點選播放按鍵時調用resumeRotate恢複旋轉(如果調用startRotating則又從頭開始旋轉)。

3、歌詞解析和歌詞滾動

首先我們先來看一下lrc檔案的格式,如圖:

iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)
iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)

不難發現,除去歌詞頭部資訊後,正文前面[]裡面是時間點,右邊才是歌詞,也就是一段歌詞對應一個時間點,這個時間點是開始點,也就是說當歌曲播放到00:49.65這個時間點的時候,歌詞應該滾動到“因為在 一千年以後”這段。知道原理就好辦了。先解析歌詞。

// 解析歌詞

.h檔案

@property (nonatomic,strong) NSMutableDictionary *mLRCDictinary;

@property (nonatomic,strong) NSMutableArray *mTimeArray;

@property (nonatomic, assign) BOOL mIsLRCPrepared;
           

.m檔案

-(void) AnalysisLRC: (NSString *)lrcStr {
    
    
    NSString* contentStr = lrcStr;
    
    NSArray *lrcArray = [contentStr componentsSeparatedByString:@"\n"];
    
    [songInfo.mLRCDictinary removeAllObjects];
    [songInfo.mTimeArray removeAllObjects];
    
    for (NSString *line in lrcArray) {
        
        // 首先處理歌詞中無用的東西
        // [ti:][ar:][al:]這類的直接跳過
        if ([line containsString:@"[0"] || [line containsString:@"[1"] || [line containsString:@"[2"] || [line containsString:@"[3"]) {
            NSArray *lineArr = [line componentsSeparatedByString:@"]"];
            NSString *str1 = [line substringWithRange:NSMakeRange(3, 1)];
            NSString *str2 = [line substringWithRange:NSMakeRange(6, 1)];
            
            if ([str1 isEqualToString:@":"] && [str2 isEqualToString:@"."]) {
                NSString *lrcStr = lineArr[1];
                NSString *timeStr = [lineArr[0] substringWithRange:NSMakeRange(1, 5)];
                [songInfo.mLRCDictinary setObject:lrcStr forKey:timeStr];
                [songInfo.mTimeArray addObject:timeStr];
            }
        } else {
            continue;
        }
    }
    
    _mIsLRCPrepared = true;
    [self.tableView reloadData];

}
           

mLRCDictinary存放配對時間點和歌詞段,mTimeArray存放時間點,通過時間點來找到相應的歌詞段,不過這個時間點是NSString格式,需要轉成int(我的精度要求不高,隻到秒,後面的小數沒要)

NSString轉int

-(int) stringToInt: (NSString *)timeString {

    NSArray *strTemp = [timeString componentsSeparatedByString:@":"];

    int time = [strTemp.firstObject intValue] * 60 + [strTemp.lastObject intValue];

    return time;

}
           

int轉NSString(顯示目前播放時間要用到)

-(NSString *)intToString: (int)needTransformInteger {

    //實作00:00這種格式播放時間

    int wholeTime = needTransformInteger;

    int min  = wholeTime / 60;

    int sec = wholeTime % 60;

    NSString *str = [NSString stringWithFormat:@"%02d:%02d", min , sec];

    return str;

}
           

歌詞滾動:

// songInfo.lrcIndex記錄歌詞第幾行,用currentTime 和 mTimeArray中第幾行歌詞的時間相比較,大于那個時間歌詞tableView滾動到那一行。

if (songInfo.lrcIndex <= songInfo.mLRCDictinary.count - 1) {

    if ((int)currentTime >= [songInfo stringToInt:songInfo.mTimeArray[songInfo.lrcIndex]]) {

        _deliverView.midView.midLrcView.currentRow = songInfo.lrcIndex;

        [_deliverView.midView.midLrcView.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_deliverView.midView.midLrcView.currentRow inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];

       [_deliverView.midView.midLrcView.tableView reloadData];

       songInfo.lrcIndex = songInfo.lrcIndex + 1;

    }

}
           

先寫到這裡,後續還會補充鎖屏播放設定,背景播放設定,手勢操作等。如果各位覺得還可以,别忘了加個星星哦!

項目運作截圖:

iOS開發之網絡音樂播放器(SC音樂)(一)iOS開發之網絡音樂播放器(SC音樂)(一)

iOS開發之網絡音樂播放器(SC音樂)(二)位址:http://blog.csdn.net/u014636932/article/details/77878371

這裡附上github位址:https://github.com/Mozartisnotmyname/SCMusic.git