中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

iOS之基于FreeStreamer的簡單音樂播放器示例

前提準備

成都創(chuàng)新互聯(lián)公司成都網(wǎng)站建設(shè)按需網(wǎng)站建設(shè),是成都網(wǎng)站建設(shè)公司,為OPP膠袋提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計、前端HTML5制作、后臺程序開發(fā)等。成都網(wǎng)站營銷推廣熱線:13518219792

為了能夠有明確的思路來做這個demo,我下載了QQ音樂和網(wǎng)易云音樂,然后分別對比,最終選擇了QQ音樂來參照,先是獲取了其中的所有資源文件(如果有不知道怎么提取資源文件的,可以參考iOS提取APP中的圖片資源),在這之后就是研究使用技術(shù),這里我選擇了FreeStreamer,雖然系統(tǒng)也有,但是該框架可能更好用點。

實現(xiàn)部分

在這之前,先來看看大概效果圖吧

iOS之基于FreeStreamer的簡單音樂播放器示例

iOS之基于FreeStreamer的簡單音樂播放器示例 

iOS之基于FreeStreamer的簡單音樂播放器示例

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

1、先讓播放器跑起來

這里我使用的是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我簡單進行了封裝下

#import "FSAudioStream.h"
@class GLMusicLRCModel;
typedef NS_ENUM(NSInteger,GLLoopState){
  GLSingleLoop = 0,//單曲循環(huán)
  GLForeverLoop,//重復(fù)循環(huán)
  GLRandomLoop,//隨機播放
  GLOnceLoop//列表一次順序播放
};
@protocol GLMusicPlayerDelegate/**
 *
 實時更新
 *
 **/
- (void)updateProgressWithCurrentPosition:(FSStreamPosition)currentPosition endPosition:(FSStreamPosition)endPosition;
- (void)updateMusicLrc;
@end
@interface GLMusicPlayer : FSAudioStream
/**
 *
 播放列表
 *
 **/
@property (nonatomic,strong) NSMutableArray *musicListArray;
/**
 當前播放歌曲的歌詞
 */
@property (nonatomic,strong) NSMutableArray *musicLRCArray;
/**
 *
 當前播放
 *
 **/
@property (nonatomic,assign,readonly) NSUInteger currentIndex;
/**
 *
 當前播放的音樂的標題
 *
 **/
@property (nonatomic,strong) NSString *currentTitle;
/**
 是否是暫停狀態(tài)
 */
@property (nonatomic,assign) BOOL isPause;
@property (nonatomic,weak) idglPlayerDelegate;
//默認 重復(fù)循環(huán) 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;
      }
    };
    //設(shè)置音量
    [player setVolume:0.5];
    //設(shè)置播放速率
    [player setPlayRate:1];
    player.loopState = GLForeverLoop;
  });
  return player;
}

然后實現(xiàn)了播放方法

- (void)playFromURL:(NSURL *)url
{
  //根據(jù)地址 在本地找歌詞
  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界面,獲取歌詞等處理,如果要實現(xiàn)簡單的播放,則可以不用重寫該方法,直接通過playFromURL就可以實現(xiàn)我們的播放功能。

2、更新UI

這里的UI暫不包括歌詞的更新,而只是進度條的更新,要更新進度條,比不可少的是定時器,這里我沒有選擇NSTimer,而是選擇了CADisplayLink,至于為什么,我想大家應(yīng)該都比較了解,可以這么來對比,下面引用一段其他博客的對比:

iOS設(shè)備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結(jié)束都被調(diào)用,精確度相當高。

NSTimer的精確度就顯得低了點,比如NSTimer的觸發(fā)時間到的時候,runloop如果在阻塞狀態(tài),觸發(fā)時間就會推遲到下一個runloop周期。并且 NSTimer新增了tolerance屬性,讓用戶可以設(shè)置可以容忍的觸發(fā)的時間的延遲范圍。

CADisplayLink使用場合相對專一,適合做UI的不停重繪,比如自定義動畫引擎或者視頻播放的渲染。NSTimer的使用范圍要廣泛的多,各種需要單次或者循環(huán)定時處理的任務(wù)都可以使用。在UI相關(guān)的動畫或者顯示內(nèi)容使用 CADisplayLink比起用NSTimer的好處就是我們不需要在格外關(guān)心屏幕的刷新頻率了,因為它本身就是跟屏幕刷新同步的

使用方法

 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,分別保存著當前播放時間和總時間,是如下的結(jié)構(gòu)體

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的時候,主要可以根據(jù)其中的minute和second來,如果播放了90s,那么minute就為1,而second為30,所以我們在計算的時候,應(yīng)該是這樣的(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控件沒有用系統(tǒng)的,而是簡單的寫了一個,大概如下

@interface GLSlider : UIControl
//進度條顏色
@property (nonatomic,strong) UIColor *progressColor;
//緩存條顏色
@property (nonatomic,strong) UIColor *progressCacheColor;
//滑塊顏色
@property (nonatomic,strong) UIColor *thumbColor;
//設(shè)置進度值 0-1
@property (nonatomic,assign) CGFloat value;
//設(shè)置緩存進度值 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;
}
....

這里是添加了緩存進度條的,但是由于時間關(guān)系,代碼中還未實時更新緩存進度

3、更新歌詞界面

說到歌詞界面,我們看到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該方法能夠?qū)崿F(xiàn)逐字進行漸變的效果

逐字的問題解決了,那么就剩下逐行問題了,逐行的問題應(yīng)該不難,是的。我們只需要在指定的時間內(nèi)將其滾動就行,如下

復(fù)制代碼 代碼如下:

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

但是這中要注意一個問題,那就是必須做到,在下一行進行展示的時候,取消上一行的效果,如下

        //設(shè)置當前行的狀態(tài)
        [currentCell reloadCellForSelect:YES];
        //取消上一行的選中狀態(tài)
        [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;必須要,否則我們的文字顏色不會改變

在大問題已經(jīng)解決的情況下,我們就需要關(guān)心另一個重要的問題了,那就是歌詞。這里先介紹一個網(wǎng)站,可以獲取歌曲名和歌詞的

(找了好久....) 歌曲歌詞獲取 ,不過好多好聽的歌曲居然播放不了,你懂得,大天朝版權(quán)問題....找一首歌,播放就能看到看到歌詞了。關(guān)于歌詞,有許多格式,這里我用的是lrc格式,應(yīng)該還算比較主流,格式大概如下

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

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

#import @interface GLMusicLRCModel : NSObject
//該段歌詞對應(yīng)的時間
@property (nonatomic,assign) NSTimeInterval time;
//歌詞
@property (nonatomic,strong) NSString *title;
/**
 *
 將特點的歌詞格式進行轉(zhuǎn)換
 *
 **/
+ (id)musicLRCWithString:(NSString *)string;
/**
 *
 根據(jù)歌詞的路徑返回歌詞模型數(shù)組
 *
 **/
+ (NSArray *)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 *)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];
        
        //設(shè)置當前行的狀態(tài)
        [currentCell reloadCellForSelect:YES];
        //取消上一行的選中狀態(tài)
        [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;
    }
  }
}

到此為止,我們一個簡單的播放器就差不多實現(xiàn)了,但是這...并沒有完,相比QQ音樂而言,它還差一個播放順序切換的功能和鎖屏播放功能

4、切換播放順序

這個比較簡單,只是需要注意在切換的時候,注意數(shù)組的越界和不同模式的處理

這里,我定義了如下幾種模式

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

切換代碼

//不同狀態(tài)下 播放歌曲
- (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;
  }
}

5、鎖屏播放

就如上圖2中那樣,由于在iOS 11中好像不能支持背景圖片和歌詞展示,可能是為了界面更加簡潔吧,所以我這里也就沒有加該功功能,只是簡答的有個播放界面和幾個控制按鈕

首先需要在工程中這樣設(shè)置,保證在后臺播放

iOS之基于FreeStreamer的簡單音樂播放器示例 

然后就是在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:
      {
        //點擊播放與暫停開關(guān)按鈕(iphone抽屜中使用這個)
        [[GLMusicPlayer defaultPlayer] pause];
      }
        break;
      case UIEventSubtypeRemoteControlNextTrack:
      {
        //點擊下一曲按鈕或者耳機中間按鈕兩下
        [[GLMusicPlayer defaultPlayer] playNext];
      }
        break;
      case UIEventSubtypeRemoteControlPreviousTrack:
      {
        //點擊上一曲按鈕或者耳機中間按鈕三下
        [[GLMusicPlayer defaultPlayer] playFont];
      }
        break;
      case UIEventSubtypeRemoteControlBeginSeekingBackward:
      {
        //快退開始 點擊耳機中間按鈕三下不放開
      }
        break;
      case UIEventSubtypeRemoteControlEndSeekingBackward:
      {
        //快退結(jié)束 耳機快退控制松開后
      }
        break;
      case UIEventSubtypeRemoteControlBeginSeekingForward:
      {
        //開始快進 耳機中間按鈕兩下不放開
      }
        break;
      case UIEventSubtypeRemoteControlEndSeekingForward:
      {
        //快進結(jié)束 耳機快進操作松開后
      }
        break;
        
      default:
        break;
    }
    
  }
}

beginReceivingRemoteControlEvents為允許傳遞遠程控制事件,remoteControlReceivedWithEvent為接收一個遠程控制事件,關(guān)于控制事件的類型,在代碼中,已經(jīng)注釋過,這里就不再說了。

控制事件搞定了,剩下的就是界面的展示了,主要是歌曲信息的展示,通過如下的代碼就能實現(xiàn)

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

關(guān)于歌曲信息的設(shè)置,可以不按照我這樣,定時器中時刻進行刷新,只需要在播放、暫停、快進快退這些時間有變化的地方傳入當前歌曲的關(guān)鍵信息就可以,系統(tǒng)會自動去根據(jù)播放情況去更新鎖屏界面上的進度條,而不需要我們時刻傳入當前播放時間。這里我為了偷懶,就加在里面了。為了防止頻繁操作,我采取了個方法,在其他地方看到的,就是監(jiān)聽鎖屏情況

 //監(jiān)聽鎖屏狀態(tài) lock=1則為鎖屏狀態(tài)
  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);
  
  //監(jiān)聽屏幕點亮狀態(tài) screenLight = 1則為變暗關(guān)閉狀態(tài)
  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);

通過該情況來設(shè)置。

在上面鎖屏播放的過程中,出現(xiàn)一個問題,就是當我切換歌曲的時候,不管是在鎖屏情況下,還是在app內(nèi)

通過各種查找,大概找到問題,首先在appdelegate中將[session setActive:YES error:nil]改成了[session setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil],然后再播放的地方加了一個[self stop],先停止播放

- (void)playFromURL:(NSURL *)url
{
  //根據(jù)地址 在本地找歌詞
  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];
  }

到此為止,一個簡單的播放器就差不多了,由于時間關(guān)系,可能還有些bug,希望大家能多多提出來,我好進行修正。下面還是附上 demo ,后續(xù)我還將加一個功能,因為這兩天公司有個很老的項目,有個下載問題,有點蛋疼,所以準備些一個隊列下載,然后順便加到播放器上。

說說遇到的坑

第一個就是我們項目中也有用到科大訊飛的語音.和錄音的功能這些東西都需要對AVAudioSession進行操作.在切換使用AVAudioSession的時候就會報[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session. 這樣的錯誤,這個錯會導(dǎo)致音頻在播放但是沒有聲音. 我的解決辦法是搜索框架中所有的setActive:NO,把NO改成YES,這個問題就完美的解決了.

第二個坑就是當剛開始緩存但是沒有出聲音的時候這個時候調(diào)暫停的方法是沒用的,即使調(diào)用了暫停的方法.但是音頻還是會播放.我剛開始的解決辦法是在監(jiān)聽FSAudioStreamState的kFsAudioStreamPlaying狀態(tài).在playFromURL:的時候設(shè)置了一個屬性Buffering置為YES,在調(diào)用kFsAudioStreamPlaying的置為NO,這樣在暫停方法里這樣寫

- (void)suspentFM { 
   
  if (self.isSuspendFM==YES) return; 
   
  if (self.Buffering ==YES) { 
    [_audioStream stop]; 
  }else { 
     
    [_audioStream pause]; 
     
  } 
  self.isSuspendFM = YES; 
  _suspentBtn.hidden = NO; 
   
   
} 

就解決了這個問題.但是解決的并不完美.kFsAudioStreamPlaying這個狀態(tài)會調(diào)用很多次.這樣在少數(shù)情況下還是會有問題,具體情況已經(jīng)忘了.于是乎我就放出了終極大招在定時器里監(jiān)聽進度

 if ( progressView.progress<0.007) {
    self.Buffering = YES;
  }else {
    
    self.Buffering = NO;
  }

到這里才完美的解決這個問題

在接下來說使用小技巧吧.就是緩存的進度和播放的進度

FSStreamPosition cur = self.audioStream.currentTimePlayed; 
  self.playbackTime =cur.playbackTimeInSeconds/1; 
  self.ProgressView.progress = cur.position;//播放進度 
  self.progress = cur.position; 
  float prebuffer = (float)self.audioStream.prebufferedByteCount; 
  float contentlength = (float)self.audioStream.contentLength; 
   
  if (contentlength>0) { 
    self.ProgressView.cacheProgress = prebuffer /contentlength;//緩存進度 
  } 

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。

網(wǎng)站標題:iOS之基于FreeStreamer的簡單音樂播放器示例
URL網(wǎng)址:http://www.rwnh.cn/article6/gdijig.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、Google、面包屑導(dǎo)航網(wǎng)頁設(shè)計公司、全網(wǎng)營銷推廣外貿(mào)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)
平顶山市| 包头市| 玉溪市| 翼城县| 体育| 五河县| 台南市| 张家界市| 高州市| 天等县| 依安县| 淮安市| 廉江市| 灌南县| 平度市| 东光县| 松江区| 崇信县| 安达市| 宁夏| 微山县| 张家界市| 关岭| 连南| 万盛区| 新郑市| 大丰市| 南和县| 收藏| 宜春市| 伊金霍洛旗| 翁源县| 体育| 宜宾县| 东兰县| 临江市| 息烽县| 齐齐哈尔市| 诸城市| 诸城市| 墨江|