用AVPlayer写音乐播放器

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容