亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

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

2019-10-21 18:43:43
字體:
來源:轉載
供稿:網友

前提準備

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

實現部分

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

iOS,FreeStreamer音樂播放器,音樂播放器,簡單音樂播放器

iOS,FreeStreamer音樂播放器,音樂播放器,簡單音樂播放器

iOS,FreeStreamer音樂播放器,音樂播放器,簡單音樂播放器

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

1、先讓播放器跑起來

這里我使用的是pods來管理三方庫,代碼如下

platform:ios,'8.0'target "GLMusicBox" dopod '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,//單曲循環  GLForeverLoop,//重復循環  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;/** 是否是暫停狀態 */@property (nonatomic,assign) BOOL isPause;@property (nonatomic,weak) idglPlayerDelegate;//默認 重復循環 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;}

然后實現了播放方法

- (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就可以實現我們的播放功能。

2、更新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的時候,主要可以根據其中的minute和second來,如果播放了90s,那么minute就為1,而second為30,所以我們在計算的時候,應該是這樣的(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;@endstatic 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;}....

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

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該方法能夠實現逐字進行漸變的效果

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

 

復制代碼 代碼如下:

[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 @interface GLMusicLRCModel : NSObject//該段歌詞對應的時間@property (nonatomic,assign) NSTimeInterval time;//歌詞@property (nonatomic,strong) NSString *title;/** * 將特點的歌詞格式進行轉換 * **/+ (id)musicLRCWithString:(NSString *)string;/** * 根據歌詞的路徑返回歌詞模型數組 * **/+ (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];                //設置當前行的狀態        [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音樂而言,它還差一個播放順序切換的功能和鎖屏播放功能

4、切換播放順序

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

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

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;  }}

5、鎖屏播放

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

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

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:      {        //點擊播放與暫停開關按鈕(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);

通過該情況來設置。

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

通過各種查找,大概找到問題,首先在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,希望大家能多多提出來,我好進行修正。下面還是附上 demo ,后續我還將加一個功能,因為這兩天公司有個很老的項目,有個下載問題,有點蛋疼,所以準備些一個隊列下載,然后順便加到播放器上。

說說遇到的坑

第一個就是我們項目中也有用到科大訊飛的語音.和錄音的功能這些東西都需要對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. 這樣的錯誤,這個錯會導致音頻在播放但是沒有聲音. 我的解決辦法是搜索框架中所有的setActive:NO,把NO改成YES,這個問題就完美的解決了.

第二個坑就是當剛開始緩存但是沒有出聲音的時候這個時候調暫停的方法是沒用的,即使調用了暫停的方法.但是音頻還是會播放.我剛開始的解決辦法是在監聽FSAudioStreamState的kFsAudioStreamPlaying狀態.在playFromURL:的時候設置了一個屬性Buffering置為YES,在調用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這個狀態會調用很多次.這樣在少數情況下還是會有問題,具體情況已經忘了.于是乎我就放出了終極大招在定時器里監聽進度

 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;//緩存進度   } 

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到IOS開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
精品一区二区三区四区在线| 91在线免费观看网站| 国产精品无码专区在线观看| 欧美成人免费全部观看天天性色| 色偷偷av一区二区三区| 亚洲美女免费精品视频在线观看| 91高清视频免费观看| 久久久久久久久久久人体| 国产91在线播放精品91| 精品动漫一区二区三区| 亚洲精品美女免费| 久久九九精品99国产精品| 精品成人在线视频| 精品性高朝久久久久久久| 成人国内精品久久久久一区| 欧美天天综合色影久久精品| 国产精品揄拍500视频| 一区二区三区四区在线观看视频| 亚洲国产天堂久久综合网| 欧美www视频在线观看| 777国产偷窥盗摄精品视频| 国产精品免费一区二区三区都可以| 国产成人97精品免费看片| 精品国产一区av| 亚洲大胆人体在线| 911国产网站尤物在线观看| 久久成人精品一区二区三区| 国产精品高清在线观看| 92福利视频午夜1000合集在线观看| 国产精品国产三级国产aⅴ浪潮| 久久国产精品久久久久| 亚洲色图美腿丝袜| 久久精品国产欧美激情| 在线精品播放av| 中文字幕日韩电影| 亚州成人av在线| 98午夜经典影视| 揄拍成人国产精品视频| 在线观看成人黄色| www亚洲欧美| 欧美韩国理论所午夜片917电影| 亚洲男人7777| 亚洲缚视频在线观看| 国产精品成人播放| 在线观看免费高清视频97| 欧美成人一区在线| 欧美日韩亚洲一区二| 日韩亚洲欧美中文高清在线| 色先锋久久影院av| 激情成人在线视频| 在线视频免费一区二区| 欧美日韩免费网站| 国产在线精品播放| 国产精品电影网| 国产亚洲免费的视频看| 欧美日韩另类在线| 亚洲欧美日韩一区二区三区在线| 97精品视频在线观看| www亚洲欧美| 欧美极品欧美精品欧美视频| 在线精品国产成人综合| 成人h片在线播放免费网站| 亚洲精品一区二区网址| 亚洲一区制服诱惑| 欧美俄罗斯乱妇| 法国裸体一区二区| 久久国产加勒比精品无码| 日韩精品在线私人| 黑人狂躁日本妞一区二区三区| 国产精品视频色| 91视频国产精品| 国产精品久久999| 亚洲免费人成在线视频观看| 亚洲香蕉在线观看| 欧美专区福利在线| 日韩**中文字幕毛片| www.日韩系列| 国产欧美中文字幕| 青青草成人在线| 亚洲人成电影在线观看天堂色| 中日韩午夜理伦电影免费| 久久综合久中文字幕青草| 亚洲奶大毛多的老太婆| 日韩性生活视频| 亚洲美女黄色片| 国产精品jizz在线观看麻豆| 国产欧美精品日韩精品| 国产精品国产自产拍高清av水多| 精品国产乱码久久久久酒店| 日韩毛片在线观看| 日产精品久久久一区二区福利| 亚洲福利在线播放| 神马国产精品影院av| 日韩经典中文字幕在线观看| 丝袜亚洲另类欧美重口| 国产精品视频最多的网站| www.日韩视频| 亚洲欧洲免费视频| 91午夜在线播放| 欧美日韩国产一区中文午夜| 色偷偷综合社区| 成年无码av片在线| 日韩电视剧在线观看免费网站| 日韩精品视频在线播放| 亚洲精品日韩av| 亚洲精品国产福利| 不卡av日日日| 久久久在线观看| 精品无人区太爽高潮在线播放| 日韩麻豆第一页| 日韩久久午夜影院| 久久夜色撩人精品| 日本高清久久天堂| 插插插亚洲综合网| 免费99精品国产自在在线| 国产噜噜噜噜噜久久久久久久久| 91在线视频一区| 国产日韩欧美黄色| 亚洲国产精久久久久久| 欧美一级片久久久久久久| 欧美寡妇偷汉性猛交| 欧美专区国产专区| 国内免费久久久久久久久久久| 亚洲欧美另类中文字幕| 亚洲a∨日韩av高清在线观看| 欧美日韩日本国产| 97在线免费视频| 97人人做人人爱| 亚洲最新av在线| 中文字幕亚洲激情| 国产精品99久久久久久白浆小说| 欧美孕妇性xx| 欧美视频一二三| 国产精品精品视频| 色综合天天综合网国产成人网| 欧美有码在线观看视频| 国产欧亚日韩视频| 青青草国产精品一区二区| 国产一区二区激情| 国产一区二区三区高清在线观看| 国产成人在线精品| 欧美一级bbbbb性bbbb喷潮片| 成人久久久久久久| 亚洲精品乱码久久久久久金桔影视| 91欧美精品午夜性色福利在线| 亚洲国产古装精品网站| 精品亚洲一区二区三区在线播放| 国产亚洲精品久久久| 久久久免费观看视频| 欧美激情高清视频| 国产精品欧美亚洲777777| 91成人性视频| 国产成人小视频在线观看| 国产精品91一区| 亚洲第一区在线| 麻豆国产精品va在线观看不卡| 俺去亚洲欧洲欧美日韩| 欧美日本高清视频| 日韩av最新在线| 欧美大秀在线观看| 色噜噜狠狠狠综合曰曰曰88av| 久久九九亚洲综合| 国产综合在线观看视频| 亚洲欧美日韩天堂|