iOS提供了几种播放音频的方式:AudioToolbox 、AVAudioPlayer、AVPlayer。
AudioToolbox:播放的时间不能超过30秒,支持格式:caf/wav/aiff
AVAudioPlayer: 使用简单但只能播放本地音乐文件
AVPlayer:既可以播放音乐又可以播放视频,支持播放本地音频和支持流媒体播放,比起上面两种更适合用来做音乐播放器
AVPlayer的官方说明
AVPlayer可以用来播放视频和音频,这里简单的讲下如何用AVPlayer写音乐播放器。
首先需要了解这两个类:
1 AVPlayer:用于控制播放器的播放,暂停,播放速度,跳转播放位置等
2 AVPlayerItem:资源管理对象,管理播放链接,可用于监听播放状态,缓冲进度等
导入 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;
前两种方法设置对应链接可直接播放,如果只需直接播放不需要监听播放进度播放状态等可使用直接播放对应链接,但如果需要做一个功能比较完善的播放器就得使用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]; // 播放
对状态进行监听:
#pragma mark - 监听
-(void)addPlayerItemListener{
AVPlayerItem *playerItem = self.player.currentItem;
//监听播放状态
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//监听缓存进度
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
//添加视频播放结束通知
[[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:未知状态,此时不能播放");
}
break;
case AVPlayerStatusReadyToPlay:{
NSLog(@"KVO:准备完毕,可以播放");
return;
}
break;
case AVPlayerStatusFailed:{
NSLog(@"KVO:加载失败,网络或者服务器出现问题");
[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];// 获取缓冲区域
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;// 计算缓冲总进度
return result;
}
播放器的状态:
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;
}];
改变播放的速度,可以设置player的rate:
@property (nonatomic) float rate; //播放速度,正常播放为1.0,停止播放为0.0
self.player.rate = 0.5;//0.5倍速播放
也可以通过对rate进行监听来判断播放状态
[self.player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:nil];
跳转到指定的位置:
-(void)seekToTime:(CMTime)time;
NSTimeInterval durationTime = CMTimeGetSeconds(player.currentItem.duration);
CMTime changedTime = CMTimeMake(durationTime * progress, 1.0);//progress跳转的位置,百分比
[self.player seekToTime:changedTime];
要做上一首和下一首切换歌曲功能,需要维护一个播放列表数组,监听用户点击上下一首按钮,然后AVPlayer进行歌曲切换需要用到:
-(void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;
重新设置播放的AVPlayerItem,如果之前设置了对当前PlayerItem的监听,*需要移除监听,设置好要切换的歌曲后再重新添加对AVPlayerItem的监听。
如何做到锁屏界面的播放器显示和控制呢,
先导入#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];
}
设置好对应的控制事件,播放/暂停/上一曲/下一曲
然后设置显示的信息,当歌曲信息变化时,如切换歌曲,歌词变化时,需要刷新
MPMediaItemArtwork *itemArtwork = [[MPMediaItemArtwork alloc] initWithImage:artworkImage];//封面
/* 播放信息中心,用于控制锁屏界面显示的内容
MPMediaItemPropertyAlbumTitle 专辑
MPMediaItemPropertyTitle 歌名
MPMediaItemPropertyArtist 歌手
MPMediaItemPropertyArtwork 歌曲封面
MPMediaItemPropertyComposer 编曲
MPMediaItemPropertyPlaybackDuration 持续时间
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