iOS之基於FreeStreamer的簡單音樂播放器(模仿QQ音樂)

代碼地址以下:<br>http://www.demodashi.com/demo/11944.htmlhtml

天道酬勤ios

前言

做爲一名iOS開發者,每當使用APP的時候,總不免會不由自主的去想一想,這個怎麼作的?該怎麼實現呢?好久以前,就想寫一個關於音樂方面的播放器,最近恰好得空,就趁機摸索着寫了下,寫的很差,還望多多指教。spring

前提準備

爲了可以有明確的思路來作這個demo,我下載了QQ音樂網易雲音樂,而後分別對比,最終選擇了QQ音樂來參照,先是獲取了其中的全部資源文件,在這以後就是研究使用技術,這裏我選擇了FreeStreamer ,雖然系統也有,可是該框架可能更好用點。數組

實現部分

在這以前,先來看看大概效果圖吧 IMG_6210.PNG緩存

IMG_6211.PNG

list.png

再看完效果圖以後,咱們就來看看這其中涉及到的幾個難點吧(在我看開~)session

  • 一、先讓播放器跑起來 這裏我使用的是pods來管理三方庫,代碼以下
platform:ios,’8.0’
target "GLMusicBox" do
pod 'FreeStreamer', '~> 3.7.3'
pod 'SDWebImage', '~> 4.0.0’
pod 'MJRefresh', '~> 3.1.11’
pod 'Masonry', '~> 1.0.2'
pod 'Reachability', '~> 3.2'
pod 'AFNetworking', '~> 3.0'
pod 'IQKeyboardManager', '~> 3.3.2’
end

針對FreeStreamer 我簡單進行了封裝下app

#import "FSAudioStream.h"

@class GLMusicLRCModel;

typedef NS_ENUM(NSInteger,GLLoopState){
    GLSingleLoop = 0,//單曲循環
    GLForeverLoop,//重複循環
    GLRandomLoop,//隨機播放
    GLOnceLoop//列表一次順序播放
};


@protocol GLMusicPlayerDelegate<NSObject>

/**
 *
 實時更新
 *
 **/
- (void)updateProgressWithCurrentPosition:(FSStreamPosition)currentPosition endPosition:(FSStreamPosition)endPosition;

- (void)updateMusicLrc;

@end

@interface GLMusicPlayer : FSAudioStream

/**
 *
 播放列表
 *
 **/
@property (nonatomic,strong) NSMutableArray *musicListArray;


/**
 當前播放歌曲的歌詞
 */
@property (nonatomic,strong) NSMutableArray <GLMusicLRCModel*>*musicLRCArray;

/**
 *
 當前播放
 *
 **/
@property (nonatomic,assign,readonly) NSUInteger currentIndex;

/**
 *
 當前播放的音樂的標題
 *
 **/
@property (nonatomic,strong) NSString *currentTitle;


/**
 是不是暫停狀態
 */
@property (nonatomic,assign) BOOL isPause;

@property (nonatomic,weak) id<GLMusicPlayerDelegate>glPlayerDelegate;

//默認 重複循環 GLForeverLoop
@property (nonatomic,assign) GLLoopState loopState;

/**
 *
 單例播放器
 *
 **/
+ (instancetype)defaultPlayer;

/**
 播放隊列中的指定的文件 

 @param index 序號
 */
- (void)playMusicAtIndex:(NSUInteger)index;

/**
 播放前一首
 */
- (void)playFont;

/**
 播放下一首
 */
- (void)playNext;

@end

這裏繼承了FSAudioStream ,而且採用了單例模式框架

+ (instancetype)defaultPlayer
{
    static GLMusicPlayer *player = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        FSStreamConfiguration *config = [[FSStreamConfiguration alloc] init];
        config.httpConnectionBufferSize *=2;
        config.enableTimeAndPitchConversion = YES;
        
        
        player = [[super alloc] initWithConfiguration:config];
        player.delegate = (id)self;
        player.onFailure = ^(FSAudioStreamError error, NSString *errorDescription) {
            //播放錯誤
            //有待解決
        };
        player.onCompletion = ^{
            //播放完成
                NSLog(@" 打印信息: 播放完成1");
        };
    
        
        player.onStateChange = ^(FSAudioStreamState state) {
            switch (state) {
                case kFsAudioStreamPlaying:
                {
                    NSLog(@" 打印信息  playing.....");
                    player.isPause = NO;
                    
                    [GLMiniMusicView shareInstance].palyButton.selected = YES;
                }
                    break;
                case kFsAudioStreamStopped:
                {
                    NSLog(@" 打印信息  stop.....%@",player.url.absoluteString);
                }
                    break;
                case kFsAudioStreamPaused:
                {
                    //pause
                    player.isPause = YES;
                    [GLMiniMusicView shareInstance].palyButton.selected = NO;
                        NSLog(@" 打印信息: pause");
                }
                    break;
                case kFsAudioStreamPlaybackCompleted:
                {
                    NSLog(@" 打印信息: 播放完成2");
                    [player playMusicForState];
                }
                    break;
                default:
                    break;
            }
        };
        //設置音量
        [player setVolume:0.5];
        //設置播放速率
        [player setPlayRate:1];
        player.loopState = GLForeverLoop;
    });
    return player;
}

而後實現了播放方法dom

- (void)playFromURL:(NSURL *)url
{
    //根據地址 在本地找歌詞
    NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"musiclist" ofType:@"plist"]];
    for (NSString *playStringKey in dic.allKeys) {
        if ([[dic valueForKey:playStringKey] isEqualToString:url.absoluteString]) {
            self.currentTitle = playStringKey;
            break;
        }
    }
    
    [self stop];

    if (![url.absoluteString isEqualToString:self.url.absoluteString]) {
        [super playFromURL:url];
    }else{
        [self play];
    }
    
    NSLog(@" 當前播放歌曲:%@",self.currentTitle);
    
    [GLMiniMusicView shareInstance].titleLable.text = self.currentTitle;
    
    //獲取歌詞
    NSString *lrcFile = [NSString stringWithFormat:@"%@.lrc",self.currentTitle];
    self.musicLRCArray = [NSMutableArray arrayWithArray:[GLMusicLRCModel musicLRCModelsWithLRCFileName:lrcFile]];
    
    if (![self.musicListArray containsObject:url]) {
        [self.musicListArray addObject:url];
    }
    
    //更新主界面歌詞UI
    if (self.glPlayerDelegate && [self.glPlayerDelegate respondsToSelector:@selector(updateMusicLrc)])
    {
        [self.glPlayerDelegate updateMusicLrc];
    }
    _currentIndex = [self.musicListArray indexOfObject:url];
    
    if (!_progressTimer) {
        _progressTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgress)];
        [_progressTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
}

在上面的代碼中,有許多邏輯是後面加的,好比更新UI界面,獲取歌詞等處理,若是要實現簡單的播放,則能夠不用重寫該方法,直接經過playFromURL 就能夠實現咱們的播放功能。iphone

  • 二、更新UI 這裏的UI暫不包括歌詞的更新,而只是進度條的更新,要更新進度條,比不可少的是定時器,這裏我沒有選擇NSTimer,而是選擇了CADisplayLink,至於爲何,我想你們應該都比較瞭解,能夠這麼來對比,下面引用一段其餘博客的對比: iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常狀況下會在每次刷新結束都被調用,精確度至關高。 NSTimer的精確度就顯得低了點,好比NSTimer的觸發時間到的時候,runloop若是在阻塞狀態,觸發時間就會推遲到下一個runloop週期。而且 NSTimer新增了tolerance屬性,讓用戶能夠設置能夠容忍的觸發的時間的延遲範圍。 CADisplayLink使用場合相對專注,適合作UI的不停重繪,好比自定義動畫引擎或者視頻播放的渲染。NSTimer的使用範圍要普遍的多,各類須要單次或者循環定時處理的任務均可以使用。在UI相關的動畫或者顯示內容使用 CADisplayLink比起用NSTimer的好處就是咱們不須要在格外關心屏幕的刷新頻率了,由於它自己就是跟屏幕刷新同步的 使用方法
if (!_progressTimer) {
        _progressTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgress)];
        [_progressTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }

更新進度

- (void)updateProgress
{
    if (self.glPlayerDelegate && [self.glPlayerDelegate respondsToSelector:@selector(updateProgressWithCurrentPosition:endPosition:)])
    {
        [self.glPlayerDelegate updateProgressWithCurrentPosition:self.currentTimePlayed endPosition:self.duration];
    }
    
    [self showLockScreenCurrentTime:(self.currentTimePlayed.second + self.currentTimePlayed.minute * 60) totalTime:(self.duration.second + self.duration.minute * 60)];
}

在這裏有兩個屬性:currentTimePlayed duration,分別保存着當前播放時間和總時間,是以下的結構體

typedef struct {
    unsigned minute;
    unsigned second;
    
    /**
     * Playback time in seconds.
     */
    float playbackTimeInSeconds;
    
    /**
     * Position within the stream, where 0 is the beginning
     * and 1.0 is the end.
     */
    float position;
} FSStreamPosition;

咱們在更新UI的時候,主要能夠根據其中的minutesecond來,若是播放了90s,那麼minute就爲1,而second30,因此咱們在計算的時候,應該是這樣的(self.currentTimePlayed.second + self.currentTimePlayed.minute * 60) 固然在更新進度條的時候,咱們也能夠經過position直接來給slider進行賦值,這表示當前播放的比例

#pragma mark == GLMusicPlayerDelegate
- (void)updateProgressWithCurrentPosition:(FSStreamPosition)currentPosition endPosition:(FSStreamPosition)endPosition
{
    //更新進度條
    self.playerControlView.slider.value = currentPosition.position;
    
    self.playerControlView.leftTimeLable.text = [NSString translationWithMinutes:currentPosition.minute seconds:currentPosition.second];
    self.playerControlView.rightTimeLable.text = [NSString translationWithMinutes:endPosition.minute seconds:endPosition.second];
    
    //更新歌詞
    [self updateMusicLrcForRowWithCurrentTime:currentPosition.position *(endPosition.minute *60 + endPosition.second)];

    self.playerControlView.palyMusicButton.selected = [GLMusicPlayer defaultPlayer].isPause;
}

本項目中,slider控件沒有用系統的,而是簡單的寫了一個,大概以下

@interface GLSlider : UIControl

//進度條顏色
@property (nonatomic,strong) UIColor *progressColor;
//緩存條顏色
@property (nonatomic,strong) UIColor *progressCacheColor;
//滑塊顏色
@property (nonatomic,strong) UIColor *thumbColor;

//設置進度值 0-1
@property (nonatomic,assign) CGFloat value;
//設置緩存進度值 0-1
@property (nonatomic,assign) CGFloat cacheValue;
@end
static CGFloat const kProgressHeight = 2;
static CGFloat const kProgressLeftPadding = 2;
static CGFloat const kThumbHeight = 16;
@interface GLSlider()

//滑塊 默認
@property (nonatomic,strong) CALayer *thumbLayer;
//進度條
@property (nonatomic,strong) CALayer *progressLayer;
//緩存進度條
@property (nonatomic,strong) CALayer *progressCacheLayer;

@property (nonatomic,assign) BOOL isTouch;

@end

@implementation GLSlider

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self addSubLayers];
    }
    return self;
}
....

這裏是添加了緩存進度條的,可是因爲時間關係,代碼中還未實時更新緩存進度

  • 三、更新歌詞界面

說到歌詞界面,咱們看到QQ音樂的效果是這樣的,逐行逐字進行更新,注意不是逐行更新。考慮到逐字進行更新,那麼咱們必需要對lable進行乾點什麼,這裏對其進行了繼承,並添加了些方法

@interface GLMusicLrcLable : UILabel

//進度
@property (nonatomic,assign) CGFloat progress;

@end
#import "GLMusicLrcLable.h"

@implementation GLMusicLrcLable

- (void)setProgress:(CGFloat)progress
{
    _progress = progress;
    //重繪
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    
    CGRect fillRect = CGRectMake(0, 0, self.bounds.size.width * _progress, self.bounds.size.height);
    
    [UICOLOR_FROM_RGB(45, 185, 105) set];
    
    UIRectFillUsingBlendMode(fillRect, kCGBlendModeSourceIn);
}
@end

注意UIRectFillUsingBlendMode 該方法可以實現逐字進行漸變的效果 逐字的問題解決了,那麼就剩下逐行問題了,逐行的問題應該不難,是的。咱們只須要在指定的時間內將其滾動就行,以下

[self.lrcTableView scrollToRowAtIndexPath:currentIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]

可是這中要注意一個問題,那就是必須作到,在下一行進行展現的時候,取消上一行的效果,以下

//設置當前行的狀態
                [currentCell reloadCellForSelect:YES];
                //取消上一行的選中狀態
                [previousCell reloadCellForSelect:NO];
- (void)reloadCellForSelect:(BOOL)select
{
    if (select) {
        _lrcLable.font = [UIFont systemFontOfSize:17];
    }else{
        _lrcLable.font = [UIFont systemFontOfSize:14];
        _lrcLable.progress = 0;
    }
}

其中 _lrcLable.progress = 0;必需要,不然咱們的文字顏色不會改變 在大問題已經解決的狀況下,咱們就須要關心另外一個重要的問題了,那就是歌詞。這裏先介紹一個網站,能夠獲取歌曲名和歌詞的 (找了很久....)歌曲歌詞獲取,不過好多好聽的歌曲竟然播放不了,你懂得,大天朝版權問題....找一首歌,播放就能看到看到歌詞了。關於歌詞,有許多格式,這裏我用的是lrc格式,應該還算比較主流,格式大概以下

[ti:老人與海]
[ar:海鳴威 ]
[al:單曲]
[by:www.5nd.com From 那時花開]
[00:04.08]老人與海 海鳴威
[00:08.78]海鳴威
[00:37.06]秋天的夜凋零在漫天落葉裏面
[00:42.43]泛黃世界一點一點隨風而漸遠
[00:47.58]冬天的雪白色了你個人情人節
[00:53.24]消失不見 愛的碎片
[00:57.87]Rap:
[00:59.32]翻開塵封的相片
[01:00.87]想起和你看過 的那些老舊默片
[01:02.50]老人與海的情節
[01:04.23]畫面中你卻依稀 在浮現

在有了格式後,咱們就須要一個模型,來分離歌曲信息了,下面是我建的模型

#import <Foundation/Foundation.h>


@interface GLMusicLRCModel : NSObject

//該段歌詞對應的時間
@property (nonatomic,assign) NSTimeInterval time;
//歌詞
@property (nonatomic,strong) NSString *title;


/**
 *
 將特色的歌詞格式進行轉換
 *
 **/
+ (id)musicLRCWithString:(NSString *)string;

/**
 *
 根據歌詞的路徑返回歌詞模型數組
 *
 **/
+ (NSArray <GLMusicLRCModel *>*)musicLRCModelsWithLRCFileName:(NSString *)name;

@end
#import "GLMusicLRCModel.h"

@implementation GLMusicLRCModel

+(id)musicLRCWithString:(NSString *)string
{
    GLMusicLRCModel *model = [[GLMusicLRCModel alloc] init];
    NSArray *lrcLines =[string componentsSeparatedByString:@"]"];
    if (lrcLines.count == 2) {
        model.title = lrcLines[1];
        NSString *timeString = lrcLines[0];
        timeString = [timeString stringByReplacingOccurrencesOfString:@"[" withString:@""];
        timeString = [timeString stringByReplacingOccurrencesOfString:@"]" withString:@""];
        NSArray *times = [timeString componentsSeparatedByString:@":"];
        if (times.count == 2) {
            NSTimeInterval time = [times[0] integerValue]*60 + [times[1] floatValue];
            model.time = time;
        }
    }else if(lrcLines.count == 1){
        
    }
    
    return model;
}


+(NSArray <GLMusicLRCModel *>*)musicLRCModelsWithLRCFileName:(NSString *)name
{
    NSString *lrcPath = [[NSBundle mainBundle] pathForResource:name ofType:nil];
    NSString *lrcString = [NSString stringWithContentsOfFile:lrcPath encoding:NSUTF8StringEncoding error:nil];
    NSArray *lrcLines = [lrcString componentsSeparatedByString:@"\n"];

    NSMutableArray *lrcModels = [NSMutableArray array];
    for (NSString *lrcLineString in lrcLines) {

        if ([lrcLineString hasPrefix:@"[ti"] || [lrcLineString hasPrefix:@"[ar"] || [lrcLineString hasPrefix:@"[al"] || ![lrcLineString hasPrefix:@"["]) {
            continue;
        }
        GLMusicLRCModel *lrcModel = [GLMusicLRCModel musicLRCWithString:lrcLineString];
        [lrcModels addObject:lrcModel];
    }
    return lrcModels;
}

@end

在歌詞模型準備好以後,咱們要展現歌詞,這裏我選擇的是tableview,經過每個cell來加載不一樣的歌詞,而後經過歌詞的時間信息來更新和滾動

#pragma mark == UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [GLMusicPlayer defaultPlayer].musicLRCArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MusicLRCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"musicLrc" forIndexPath:indexPath];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.backgroundColor = [UIColor clearColor];
    cell.contentView.backgroundColor = [UIColor clearColor];
    
    cell.lrcModel = [GLMusicPlayer defaultPlayer].musicLRCArray[indexPath.row];
    
    if (indexPath.row == self.currentLcrIndex) {
        [cell reloadCellForSelect:YES];
    }else{
        [cell reloadCellForSelect:NO];
    }
    
    return cell;
}

這裏面惟一比較麻煩的可能就是更新歌詞了,在上面的定時器中,咱們也經過代理來更新了進度條,因此我也將更新歌詞的部分放在了代理中,這樣能夠達到實時更新的目的,下面看看方法

//逐行更新歌詞
- (void)updateMusicLrcForRowWithCurrentTime:(NSTimeInterval)currentTime
{
    for (int i = 0; i < [GLMusicPlayer defaultPlayer].musicLRCArray.count; i ++) {
        GLMusicLRCModel *model = [GLMusicPlayer defaultPlayer].musicLRCArray[i];
        
        NSInteger next = i + 1;
        
        GLMusicLRCModel *nextLrcModel = nil;
        if (next < [GLMusicPlayer defaultPlayer].musicLRCArray.count) {
            nextLrcModel = [GLMusicPlayer defaultPlayer].musicLRCArray[next];
        }
        
        if (self.currentLcrIndex != i && currentTime >= model.time)
        {
            BOOL show = NO;
            if (nextLrcModel) {
                if (currentTime < nextLrcModel.time) {
                    show = YES;
                }
            }else{
                show = YES;
            }
            
            if (show) {
                NSIndexPath *currentIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
                NSIndexPath *previousIndexPath = [NSIndexPath indexPathForRow:self.currentLcrIndex inSection:0];
                
                self.currentLcrIndex = i;
                
                MusicLRCTableViewCell *currentCell = [self.lrcTableView cellForRowAtIndexPath:currentIndexPath];
                MusicLRCTableViewCell *previousCell = [self.lrcTableView cellForRowAtIndexPath:previousIndexPath];
                
                //設置當前行的狀態
                [currentCell reloadCellForSelect:YES];
                //取消上一行的選中狀態
                [previousCell reloadCellForSelect:NO];
    
    
                if (!self.isDrag) {
                    [self.lrcTableView scrollToRowAtIndexPath:currentIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
                }
            }
        }
        
        if (self.currentLcrIndex == i) {
            MusicLRCTableViewCell *cell = [self.lrcTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
            
            CGFloat totalTime = 0;
            if (nextLrcModel) {
                totalTime = nextLrcModel.time - model.time;
            }else{
                totalTime = [GLMusicPlayer defaultPlayer].duration.minute * 60 +  [GLMusicPlayer defaultPlayer].duration.second - model.time;
            }
            CGFloat progressTime = currentTime - model.time;
            cell.lrcLable.progress = progressTime / totalTime;
        }
    }
}

到此爲止,咱們一個簡單的播放器就差很少實現了,可是這...並無完,相比QQ音樂而言,它還差一個播放順序切換的功能和鎖屏播放功能

  • 四、切換播放順序 這個比較簡單,只是須要注意在切換的時候,注意數組的越界和不一樣模式的處理 這裏,我定義了以下幾種模式
typedef NS_ENUM(NSInteger,GLLoopState){
    GLSingleLoop = 0,//單曲循環
    GLForeverLoop,//重複循環
    GLRandomLoop,//隨機播放
    GLOnceLoop//列表一次順序播放
};

切換代碼

//不一樣狀態下 播放歌曲
- (void)playMusicForState
{
    switch (self.loopState) {
        case GLSingleLoop:
        {
            [self playMusicAtIndex:self.currentIndex];
        }
            break;
        case GLForeverLoop:
        {
            if (self.currentIndex == self.musicListArray.count-1) {
                [self playMusicAtIndex:0];
            }else{
                [self playMusicAtIndex:self.currentIndex + 1];
            }
        }
            break;
        case GLRandomLoop:
        {
            //取隨機值
            int index = arc4random() % self.musicListArray.count;
            [self playMusicAtIndex:index];
        }
            break;
        case GLOnceLoop:
        {
            if (self.currentIndex == self.musicListArray.count-1) {
                [self stop];
            }else{
                [self playMusicAtIndex:self.currentIndex + 1];
            }
        }
            break;
            
        default:
            break;
    }
}
  • 五、鎖屏播放 就如上圖2中那樣,因爲在iOS 11中好像不能支持背景圖片和歌詞展現,多是爲了界面更加簡潔吧,因此我這裏也就沒有加該功功能,只是簡答的有個播放界面和幾個控制按鈕 首先須要在工程中這樣設置,保證在後臺播放 setting.png 而後就是在appdelegate中添加以下代碼
AVAudioSession *session = [AVAudioSession sharedInstance];
//    [session setActive:YES error:nil];
    [session setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

而且添加控制事件

#pragma mark == event response
-(void)remoteControlReceivedWithEvent:(UIEvent *)event{
    
    NSLog(@"%ld",event.subtype);
    
    if (event.type == UIEventTypeRemoteControl) {
        switch (event.subtype) {
            case UIEventSubtypeRemoteControlPlay:
            {
                //點擊播放按鈕或者耳機線控中間那個按鈕
                [[GLMusicPlayer defaultPlayer] pause];
            }
                break;
            case UIEventSubtypeRemoteControlPause:
            {
                //點擊暫停按鈕
                [[GLMusicPlayer defaultPlayer] pause];
            }
                break;
            case UIEventSubtypeRemoteControlStop :
            {
                //點擊中止按鈕
                [[GLMusicPlayer defaultPlayer] stop];
            }
                break;
            case UIEventSubtypeRemoteControlTogglePlayPause:
            {
                //點擊播放與暫停開關按鈕(iphone抽屜中使用這個)
                [[GLMusicPlayer defaultPlayer] pause];
            }
                break;
            case UIEventSubtypeRemoteControlNextTrack:
            {
                //點擊下一曲按鈕或者耳機中間按鈕兩下
                [[GLMusicPlayer defaultPlayer] playNext];
            }
                break;
            case  UIEventSubtypeRemoteControlPreviousTrack:
            {
                //點擊上一曲按鈕或者耳機中間按鈕三下
                [[GLMusicPlayer defaultPlayer] playFont];
            }
                break;
            case UIEventSubtypeRemoteControlBeginSeekingBackward:
            {
                //快退開始 點擊耳機中間按鈕三下不放開
            }
                break;
            case UIEventSubtypeRemoteControlEndSeekingBackward:
            {
                //快退結束 耳機快退控制鬆開後
            }
                break;
            case UIEventSubtypeRemoteControlBeginSeekingForward:
            {
                //開始快進 耳機中間按鈕兩下不放開
            }
                break;
            case UIEventSubtypeRemoteControlEndSeekingForward:
            {
                //快進結束 耳機快進操做鬆開後
            }
                break;
                
            default:
                break;
        }
        
    }
}

beginReceivingRemoteControlEvents 爲容許傳遞遠程控制事件,remoteControlReceivedWithEvent 爲接收一個遠程控制事件,關於控制事件的類型,在代碼中,已經註釋過,這裏就再也不說了。 控制事件搞定了,剩下的就是界面的展現了,主要是歌曲信息的展現,經過以下的代碼就能實現

NSMutableDictionary *musicInfoDict = [[NSMutableDictionary alloc] init];
        //設置歌曲題目
        [musicInfoDict setObject:self.currentTitle forKey:MPMediaItemPropertyTitle];
        //設置歌手名
        [musicInfoDict setObject:@"" forKey:MPMediaItemPropertyArtist];
        //設置專輯名
        [musicInfoDict setObject:@"" forKey:MPMediaItemPropertyAlbumTitle];
        //設置歌曲時長
        [musicInfoDict setObject:[NSNumber numberWithFloat:totalTime]
                          forKey:MPMediaItemPropertyPlaybackDuration];
        //設置已經播放時長
        [musicInfoDict setObject:[NSNumber numberWithFloat:currentTime]
                          forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
        
        [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:musicInfoDict];

關於歌曲信息的設置,能夠不按照我這樣,定時器中時刻進行刷新,只須要在播放暫停快進快退這些時間有變化的地方傳入當前歌曲的關鍵信息就能夠,系統會自動去根據播放狀況去更新鎖屏界面上的進度條,而不須要咱們時刻傳入當前播放時間。這裏我爲了偷懶,就加在裏面了。爲了防止頻繁操做,我採起了個方法,在其餘地方看到的,就是監聽鎖屏狀況

//監聽鎖屏狀態 lock=1則爲鎖屏狀態
    uint64_t locked;
    __block int token = 0;
    notify_register_dispatch("com.apple.springboard.lockstate",&token,dispatch_get_main_queue(),^(int t){
    });
    notify_get_state(token, &locked);
    
    //監聽屏幕點亮狀態 screenLight = 1則爲變暗關閉狀態
    uint64_t screenLight;
    __block int lightToken = 0;
    notify_register_dispatch("com.apple.springboard.hasBlankedScreen",&lightToken,dispatch_get_main_queue(),^(int t){
    });
    notify_get_state(lightToken, &screenLight);

經過該狀況來設置。

在上面鎖屏播放的過程當中,出現一個問題,就是當我切換歌曲的時候,不論是在鎖屏狀況下,仍是在appbug.jpg 經過各類查找,大概找到問題,首先在appdelegate中將[session setActive:YES error:nil]改爲了[session setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil],而後再播放的地方加了一個[self stop],先中止播放

- (void)playFromURL:(NSURL *)url
{
    //根據地址 在本地找歌詞
    NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"musiclist" ofType:@"plist"]];
    for (NSString *playStringKey in dic.allKeys) {
        if ([[dic valueForKey:playStringKey] isEqualToString:url.absoluteString]) {
            self.currentTitle = playStringKey;
            break;
        }
    }
    
    [self stop];

    if (![url.absoluteString isEqualToString:self.url.absoluteString]) {
        [super playFromURL:url];
    }else{
        [self play];
    }

到此爲止,一個簡單的播放器就差很少了,因爲時間關係,可能還有些bug,但願你們能多多提出來,我好進行修正,後續我還將加一個功能,由於這兩天公司有個很老的項目,有個下載問題,有點蛋疼,因此準備些一個隊列下載,而後順便加到播放器上。

結構截圖

結構截圖

操做步驟

操做方式 iOS之基於FreeStreamer的簡單音樂播放器(模仿QQ音樂)

代碼地址以下:<br>http://www.demodashi.com/demo/11944.html

注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權

相關文章
相關標籤/搜索