iOS提供了幾種播放音頻的方式:AudioToolbox 、AVAudioPlayer、AVPlayer。
AudioToolbox:播放的時間不能超過30秒,支持格式:caf/wav/aiff
AVAudioPlayer: 使用簡單但只能播放本地音樂文件
AVPlayer:既可以播放音樂又可以播放視頻,支持播放本地音頻和支持流媒體播放,比起上面兩種更適合用來做音樂播放器
AVPlayer的官方說明
AVPlayer可以用來播放視頻和音頻,這里簡單的講下如何用AVPlayer寫音樂播放器。
首先需要了解這兩個類:
1 AVPlayer:用于控制播放器的播放,暫停,播放速度,跳轉(zhuǎn)播放位置等
2 AVPlayerItem:資源管理對象,管理播放鏈接,可用于監(jiān)聽播放狀態(tài),緩沖進度等
導(dǎo)入 AVFoundation.framework以及
#import <Foundation/Foundation.h>
avplayer播放音頻方式用這幾種:
+(instancetype)playerWithURL:(NSURL *)URL;
-(instancetype)initWithURL:(NSURL *)URL;
+(instancetype)playerWithPlayerItem:(nullable AVPlayerItem *)item;
-(instancetype)initWithPlayerItem:(nullable AVPlayerItem *)item;
前兩種方法設(shè)置對應(yīng)鏈接可直接播放,如果只需直接播放不需要監(jiān)聽播放進度播放狀態(tài)等可使用直接播放對應(yīng)鏈接,但如果需要做一個功能比較完善的播放器就得使用AVPlayerItem。
播放器常用的功能:
-(void)play; //播放
-(void)pause;//暫停
播放本地音頻文件
NSURL *url = [NSURL fileURLWithPath:filePath];//filePath本地音頻路徑
self.player = [[AVPlayer alloc] initWithURL:url];
[self.player play]; // 播放
播放流媒體
NSURL *url = [NSURL URLWithString:urlString];//urlString音頻鏈接
self.player = [[AVPlayer alloc] initWithURL:url];
[self.player play]; // 播放
使用AVPlayerItem進行播放:
AVPlayerItem *playerItem = [[AVPlayerItem alloc]initWithURL:url];
self.player = [[AVPlayer alloc]initWithPlayerItem:playerItem];
[self.player play]; // 播放
對狀態(tài)進行監(jiān)聽:
#pragma mark - 監(jiān)聽
-(void)addPlayerItemListener{
AVPlayerItem *playerItem = self.player.currentItem;
//監(jiān)聽播放狀態(tài)
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//監(jiān)聽緩存進度
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
//添加視頻播放結(jié)束通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerToEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
//添加異常中斷通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerStalled:)
name:AVPlayerItemPlaybackStalledNotification object:self.player.currentItem];
}
#pragma mark - kvo事件處理
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context {
if ([keyPath isEqualToString:@"status"]) {
AVPlayerItemStatus status = self.player.currentItem.status;
switch (status) {
case AVPlayerStatusUnknown:{
NSLog(@"KVO:未知狀態(tài),此時不能播放");
}
break;
case AVPlayerStatusReadyToPlay:{
NSLog(@"KVO:準備完畢,可以播放");
return;
}
break;
case AVPlayerStatusFailed:{
NSLog(@"KVO:加載失敗,網(wǎng)絡(luò)或者服務(wù)器出現(xiàn)問題");
[self.player.currentItem cancelPendingSeeks];
[self pause];
return;
}
break;
default:
break;
}
}else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
//緩存進度
NSLog(@"loadedTimeRanges %f ",[self availableDuration] / self.durationTime));
} else {
NSLog(@"其他");
}
}
#pragma mark - 計算緩沖總進度
- (NSTimeInterval)availableDuration {
NSArray *loadedTimeRanges = [[self.player currentItem] loadedTimeRanges];
CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 獲取緩沖區(qū)域
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;// 計算緩沖總進度
return result;
}
播放器的狀態(tài):
typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {
AVPlayerItemStatusUnknown,//未知
AVPlayerItemStatusReadyToPlay,//準備播放
AVPlayerItemStatusFailed//播放失敗
};
跟蹤當前的時間,顯示當前播放進度:
self.playTimeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(3, 4) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
// MARK: 播放進度
weakSelf.currentTime = CMTimeGetSeconds(time);
CGFloat progress = weakSelf.currentTime / weakSelf.durationTime;
}];
改變播放的速度,可以設(shè)置player的rate:
@property (nonatomic) float rate; //播放速度,正常播放為1.0,停止播放為0.0
self.player.rate = 0.5;//0.5倍速播放
也可以通過對rate進行監(jiān)聽來判斷播放狀態(tài)
[self.player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:nil];
跳轉(zhuǎn)到指定的位置:
-(void)seekToTime:(CMTime)time;
NSTimeInterval durationTime = CMTimeGetSeconds(player.currentItem.duration);
CMTime changedTime = CMTimeMake(durationTime * progress, 1.0);//progress跳轉(zhuǎn)的位置,百分比
[self.player seekToTime:changedTime];
要做上一首和下一首切換歌曲功能,需要維護一個播放列表數(shù)組,監(jiān)聽用戶點擊上下一首按鈕,然后AVPlayer進行歌曲切換需要用到:
-(void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;
重新設(shè)置播放的AVPlayerItem,如果之前設(shè)置了對當前PlayerItem的監(jiān)聽,*需要移除監(jiān)聽,設(shè)置好要切換的歌曲后再重新添加對AVPlayerItem的監(jiān)聽。
如何做到鎖屏界面的播放器顯示和控制呢,
先導(dǎo)入#import <MediaPlayer/MediaPlayer.h>
#pragma mark 添加遠程控制中心
- (void)addRemoteCommandCenterWithTarget:(id)target
playAction:(SEL)playAction
pauseAction:(SEL)pauseAction
lastSongAction:(SEL)lastAction
nextSongAction:(SEL)nextAction {
// 遠程控制類 播放/暫停/上一曲/下一曲
MPRemoteCommandCenter *center = [MPRemoteCommandCenter sharedCommandCenter];
[center.playCommand addTarget:target action:playAction];
[center.pauseCommand addTarget:target action:pauseAction];
[center.previousTrackCommand addTarget:target action:lastAction];
[center.nextTrackCommand addTarget:target action:nextAction];
}
設(shè)置好對應(yīng)的控制事件,播放/暫停/上一曲/下一曲
然后設(shè)置顯示的信息,當歌曲信息變化時,如切換歌曲,歌詞變化時,需要刷新
MPMediaItemArtwork *itemArtwork = [[MPMediaItemArtwork alloc] initWithImage:artworkImage];//封面
/* 播放信息中心,用于控制鎖屏界面顯示的內(nèi)容
MPMediaItemPropertyAlbumTitle 專輯
MPMediaItemPropertyTitle 歌名
MPMediaItemPropertyArtist 歌手
MPMediaItemPropertyArtwork 歌曲封面
MPMediaItemPropertyComposer 編曲
MPMediaItemPropertyPlaybackDuration 持續(xù)時間
MPNowPlayingInfoPropertyElapsedPlaybackTime 當前播放時間
*/
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
infoCenter.nowPlayingInfo = @{ MPMediaItemPropertyArtist : self.currentSongModel.singer,
MPMediaItemPropertyTitle : self.currentSongModel.name,
MPMediaItemPropertyPlaybackDuration : @(durationTime),
MPNowPlayingInfoPropertyElapsedPlaybackTime : @(currentTime),
MPMediaItemPropertyArtwork : itemArtwork,
};
// MPMediaItemPropertyAlbumTitle : self.currentSongModel.album,
demo(待完善):https://github.com/Luy7788/LyMusicPlayer