iOS 音乐播放器demo讲解

一.前言

首先做一个项目我们最好先分析我们要做哪些功能,按功能模块一个个划分好结构。每个功能模块都有相对应的职责。切入正题,我做的这款音乐播放器,实现的是播放本地音乐。有以下几个要点:
1.如何实现播放音乐?
2.如何切换当前正在播放的音乐资源?
3.如何监听音乐播放器的各种状态(播放器状态,播放的进度,缓冲的进度,播放是否完成)?
4.如何手动监控播放进度?
5.如何在后台模式下或者锁屏模式下播放音乐、显示音乐播放信息和远程操控音乐?

二.网络音乐播放器的核心技术点

iOS自带的AVFoundation框架的AVPlayer类,KVO和KVC,通知机制,远程控制,SDWebImage

三.开始工作

1.创建应用程序

导入图片资源并设置icon图标,command+r运行程序后,再按command + shift + h 可以看到icon图片,如下所示
这里写图片描述

2.使用storyboard搭建主界面

storyboard是主流的开发方式,开发速度快,降低理解难度。如下所示:
这里写图片描述

3.界面的专辑图片的旋转效果

我们容易想到用计时器,但是你是否遇到过,self 强引用NStimer,NStimer对self强引用。这里互相强引用不释放。如果用__weak typeof(self) wself = self;解决,这是有bu g的,因为你不知道self何时会释放,加入timer在运行时,self就释放了会引起野指针错误。因此最有效的解决办法如下:

#pragma mark 专辑图片的旋转
//实现图片的旋转效果    弧度 = 度数 / 180 * M_PI
-(void)rotate {
    _musicIcon.transform = CGAffineTransformRotate(_musicIcon.transform, 0.5/180 * M_PI );
}

-(void)reloadUI:(MusicModel*)model
{
    self.musicName.text = model.name;
    self.artist.text = model.artist;
    [self.musicIcon sd_setImageWithURL:[NSURL URLWithString:model.cover]];
    //创建一个定时器,每隔一段时间去访问rotate方法
    self.rotatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(rotate) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_rotatingTimer forMode:NSRunLoopCommonModes];
    self.duration.text = model.duration;
    self.playBtn.selected = YES;
    self.playSlider.value = 0;   
    self.loadTimeProgress.progress = 0;
}

//**在视图消失时,要释放计时器**
- (void)viewWillDisappear:(BOOL)animated{
    [self.rotatingTimer invalidate];
    self.rotatingTimer = nil;
}

4.导入AVFoundation框架,创建AVPlayer播放器

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadData];  //加载数据,把数据转成模型,存放在dataSource数组中
    self.currentIndex = 0;
    [self playBtnAction:self.playBtn];

#pragma mark - 加载数据,存在数组里
-(void)loadData
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"music" ofType:@"json"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    //反序列化
    NSArray *datas =  [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
    //字典转模型
    for (NSDictionary *dic in datas) {
        MusicModel *model = [[MusicModel alloc] initWithDic:dic];
        [self.dataSource addObject:model];   //把模型存到数组里
    }
}

//播放,播放音乐时要监听音乐的状态,进而调用playWithUrl:
- (IBAction)playBtnAction:(UIButton *)sender
{
    if (!sender.selected) {
        [self playWithUrl:self.dataSource[self.currentIndex]];
        sender.selected = YES;
    }else{
        [self.player pause];
        [self removePlayStatus];
        [self removePlayLoadTime];
        self.currentModel = nil;
        sender.selected = NO;
    }

}

#pragma mark- 音乐播放相关
//播放音乐
-(void)playWithUrl:(MusicModel*)model
{
    AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:model.url]];

    //替换当前音乐资源
    [self.player replaceCurrentItemWithPlayerItem:item];

    //刷新界面UI
    [self reloadUI:model];

    //监听音乐播放完成通知
    [self addNSNotificationForPlayMusicFinish];

    //开始播放
    [self.player play];

    //监听播放器状态
    [self addPlayStatus];

    //监听音乐缓冲进度
    [self addPlayLoadTime];

    //监听音乐播放的进度
    [self addMusicProgressWithItem:item];

   //记录当前播放音乐的索引
    self.currentIndex = [model.Id integerValue];
    self.currentModel = model;

    //音乐锁屏信息展示
    [self setupLockScreenInfo];

}

我们来一个个分析playWithUrl:里的方法

1)替换当前音乐

[self.player replaceCurrentItemWithPlayerItem:item];

2)刷新界面UI

上面讲过的设置定时器的那一部分,重新设置歌名,歌手,专辑图片,音乐播放的当前时间和总时间,音乐播放进度和缓冲进度,按钮的状态

-(void)reloadUI:(MusicModel*)model
{
    self.musicName.text = model.name;
    self.artist.text = model.artist;
    [self.musicIcon sd_setImageWithURL:[NSURL URLWithString:model.cover]];
    //创建一个定时器,每隔一段时间去访问rotate方法
    self.rotatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(rotate) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_rotatingTimer forMode:NSRunLoopCommonModes];
    self.duration.text = model.duration;
    self.playBtn.selected = YES;
    self.playSlider.value = 0;
    self.loadTimeProgress.progress = 0;

}

3)监听音乐播放完成通知

#pragma mark - NSNotification
-(void)addNSNotificationForPlayMusicFinish
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    //给AVPlayerItem添加播放完成通知,播放完成放下一首的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
}

-(void)playFinished:(NSNotification*)notification
{
    //播放下一首
    [self nextBtnAction:nil];
}

4).开始播放

//开始播放
    [self.player play];

5).监听播放器状态

#pragma mark - 监听音乐各种状态
//通过KVO监听播放器状态
-(void)addPlayStatus
{
    [self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];       
}

//移除监听播放器状态1
-(void)removePlayStatus
{
    if (self.currentModel == nil) {return;}
    [self.player.currentItem removeObserver:self forKeyPath:@"status"];
}

6).监听音乐缓冲进度

//KVO监听音乐缓冲状态
-(void)addPlayLoadTime
{
  [self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

}
//移除监听音乐缓冲状态
-(void)removePlayLoadTime
{
    if (self.currentModel == nil) {return;}
    [self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}

7).监听音乐播放的进度

   //监听音乐播放的进度
-(void)addMusicProgressWithItem:(AVPlayerItem *)item
{
    //移除监听音乐播放进度
    [self removeTimeObserver];
    __weak typeof(self) weakSelf = self;
    self.timeObserver =  [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        //当前播放的时间
        float current = CMTimeGetSeconds(time);
        //总时间
        float total = CMTimeGetSeconds(item.duration);
        if (current) {
            float progress = current / total;
            //更新播放进度条
           weakSelf.playSlider.value = progress;
            weakSelf.currentTime.text = [weakSelf timeFormatted:current];
        }
    }];

}

8).观察者回调

//观察者回调
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"status"]) {
        switch (self.player.status) {
            case AVPlayerStatusUnknown:
            {
                NSLog(@"未知转态");
            }
                break;
            case AVPlayerStatusReadyToPlay:
            {
                NSLog(@"准备播放");
            }
                break;
            case AVPlayerStatusFailed:
            {
                NSLog(@"加载失败");
            }
                break;

            default:
                break;
        }

    }

    if ([keyPath isEqualToString:@"loadedTimeRanges"]) {

        NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;
        //本次缓冲的时间范围
        CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
        //缓冲总长度
        NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
        //音乐的总时间
        NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);
        //计算缓冲百分比例
        NSTimeInterval scale = totalLoadTime/duration;
        //更新缓冲进度条
       self.loadTimeProgress.progress = scale;
    }
}

9).记录当前音乐播放的索引

//记录当前播放音乐的索引
    self.currentIndex = [model.Id integerValue];
    self.currentModel = model;

10).音乐锁屏信息 [self setupLockScreenInfo];

#pragma mark - 设置锁屏信息

//音乐锁屏信息展示
- (void)setupLockScreenInfo
{
    // 1.获取锁屏中心
    MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter];

    //初始化一个存放音乐信息的字典
    NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary];
    // 2、设置歌曲名
    if (self.currentModel.name) {
        [playingInfoDict setObject:self.currentModel.name forKey:MPMediaItemPropertyAlbumTitle];
    }
    // 设置歌手名
    if (self.currentModel.artist) {
        [playingInfoDict setObject:self.currentModel.artist forKey:MPMediaItemPropertyArtist];
    }
    // 3设置封面的图片
    UIImage *image = [self getMusicImageWithMusicId:self.currentModel];
    if (image) {
        MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image];
        [playingInfoDict setObject:artwork forKey:MPMediaItemPropertyArtwork];
    }

    // 4设置歌曲的总时长
    [playingInfoDict setObject:self.currentModel.detailDuration forKey:MPMediaItemPropertyPlaybackDuration];

    //音乐信息赋值给获取锁屏中心的nowPlayingInfo属性
    playingInfoCenter.nowPlayingInfo = playingInfoDict;

    // 5.开启远程交互
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}

//获取远程网络图片,如有缓存取缓存,没有缓存,远程加载并缓存
-(UIImage*)getMusicImageWithMusicId:(MusicModel*)model
{
    UIImage *image;
    NSString *key = [model.Id stringValue];
    UIImage *cacheImage = self.musicImageDic[key];
    if (cacheImage) {
        image = cacheImage;
    }else{
        //这里用了非常规的做法,仅用于demo快速测试,实际开发不推荐,会堵塞主线程
        //建议加载歌曲时先把网络图片请求下来再设置
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:model.cover]];
        image =  [UIImage imageWithData:data];
        if (image) {
            [self.musicImageDic setObject:image forKey:key];

        }
    }

    return image;
}


//监听远程交互方法
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{

    switch (event.subtype) {
            //播放
        case UIEventSubtypeRemoteControlPlay:{
            [self.player play];
        }
            break;
            //停止
        case UIEventSubtypeRemoteControlPause:{
            [self.player pause];
        }
            break;
            //下一首
        case UIEventSubtypeRemoteControlNextTrack:
            [self nextBtnAction:nil];
            break;
            //上一首
        case UIEventSubtypeRemoteControlPreviousTrack:
            [self lastBtnAction:nil];
            break;

        default:
            break;
    }
}

附上github地址:(https://github.com/lizichenzi/onlionmusic/tree/master)

非常感谢
http://www.jianshu.com/p/31644a7f581c

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值