美文网首页iOS开发攻城狮的集散地iOS 知识点ios开发整理
iOS基于FreeStreamer的仿网易云音乐播放器

iOS基于FreeStreamer的仿网易云音乐播放器

作者: QuintGao | 来源:发表于2018-05-11 11:05 被阅读542次

    前言

    前段时间写过一个基于VLCKit实现的网易云音乐播放demo-GKAudioPlayerDemo,以及3篇文章
    iOS-VLCKit实现仿网易云音乐播放音乐(一)
    iOS-VLCKit实现仿网易云音乐播放音乐(二)
    iOS-VLCKit实现仿网易云音乐播放音乐(三)
    有兴趣的可以看看。
    基于VLCKit实现的有很多问题,比如不能播放本地音乐、不能获取播放进度、播放时有很多情况会被暂停或者闪退(有可能是我写的有问题)。于是经过各种搜索和探寻,最终发现FreeStreamer实现的比较好,所以又重新写了GKWYMusic这个demo,本次demo的数据全部来自百度音乐,仅供学习使用。

    说明

    GKWYMusic实现的有以下功能:

    * 网络音乐的播放、缓存、下载
    * 本地音乐的播放(已下载的音乐)
    * 歌词滚动、音量控制、歌曲切换
    * 设置循环类型、上一曲、下一曲、喜欢歌曲等
    * 锁屏控制(播放、暂停、喜欢、上一曲、下一曲、播放条拖动)
    * 耳机线控(播放、暂停、上一曲、下一曲、快进、快退)
    * 通知监听(插拔耳机、播放打断)
    

    本次主要讲一下对FreeStreamer的封装及使用。

    效果图

    gkwymusic.gif

    封装

    本次对FreeStreamer封装了一个单例类GKAudioPlayer,可在demo中查看,使用到了FreeStreamer中的FSAudioStream。
    1、创建FSAudioStream并监听播放状态,如下

    #pragma mark - 懒加载
    - (FSAudioStream *)audioStream {
        if (!_audioStream) {
            _audioStream = [[FSAudioStream alloc] init];
            
            __weak typeof(self) weakSelf = self;
            
            _audioStream.onCompletion = ^{
                NSLog(@"完成");
            };
            
            _audioStream.onStateChange = ^(FSAudioStreamState state) {
                switch (state) {
                    case kFsAudioStreamRetrievingURL:       // 检索url
                        NSLog(@"检索url");
                        weakSelf.state = GKAudioPlayerStateLoading;
                        break;
                    case kFsAudioStreamBuffering:           // 缓冲
                        NSLog(@"缓冲中。。");
                        weakSelf.state = GKAudioPlayerStateBuffering;
                        break;
                    case kFsAudioStreamSeeking:             // seek
                        NSLog(@"seek中。。");
                        weakSelf.state = GKAudioPlayerStateLoading;
                        break;
                    case kFsAudioStreamPlaying:             // 播放
                        NSLog(@"播放中。。");
                        weakSelf.state = GKAudioPlayerStatePlaying;
                        break;
                    case kFsAudioStreamPaused:              // 暂停
                        NSLog(@"播放暂停");
                        weakSelf.state = GKAudioPlayerStatePaused;
                        break;
                    case kFsAudioStreamStopped:              // 停止
                        NSLog(@"播放停止");
                        weakSelf.state = GKAudioPlayerStateStopped;
                        break;
                    case kFsAudioStreamRetryingFailed:              // 检索失败
                        NSLog(@"检索失败");
                        weakSelf.state = GKAudioPlayerStateError;
                        break;
                    case kFsAudioStreamRetryingStarted:             // 检索开始
                        NSLog(@"检索开始");
                        weakSelf.state = GKAudioPlayerStateLoading;
                        break;
                    case kFsAudioStreamFailed:                      // 播放失败
                        NSLog(@"播放失败");
                        weakSelf.state = GKAudioPlayerStateError;
                        break;
                    case kFsAudioStreamPlaybackCompleted:           // 播放完成
                        NSLog(@"播放完成");
                        weakSelf.state = GKAudioPlayerStateEnded;
                        break;
                    case kFsAudioStreamRetryingSucceeded:           // 检索成功
                        NSLog(@"检索成功");
                        weakSelf.state = GKAudioPlayerStateLoading;
                        break;
                    case kFsAudioStreamUnknownState:                // 未知状态
                        NSLog(@"未知状态");
                        weakSelf.state = GKAudioPlayerStateError;
                        break;
                    case kFSAudioStreamEndOfFile:                   // 缓冲结束
                        NSLog(@"缓冲结束");
                        {
                            // 定时器停止后需要再次调用获取进度方法,防止出现进度不准确的情况
                            [weakSelf bufferTimerAction:nil];
                            
                            [weakSelf stopBufferTimer];
                        }
                        break;
                        
                    default:
                        break;
                }
                [weakSelf setupPlayerState:weakSelf.state];
            };
        }
        return _audioStream;
    }
    

    2、缓冲进度的监听,需要在播放时先创建定时器,当缓冲完成时在关闭定时器

    // 创建定时器
    self.bufferTimer = [GKTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(bufferTimerAction:) userInfo:nil repeats:YES];
    
    // 获取进度
    - (void)bufferTimerAction:(id)sender {
        float preBuffer      = (float)self.audioStream.prebufferedByteCount;
        float contentLength  = (float)self.audioStream.contentLength;
        
        // 这里获取的进度不能准确地获取到1
        float bufferProgress = contentLength > 0 ? preBuffer / contentLength : 0;
        
    //    NSLog(@"缓冲进度%.2f", bufferProgress);
        
        // 为了能使进度准确的到1,这里做了一些处理
        int buffer = (int)(bufferProgress + 0.5);
        
        if (bufferProgress > 0.9 && buffer >= 1) {
            [self stopBufferTimer];
        }
        
        if ([self.delegate respondsToSelector:@selector(gkPlayer:bufferProgress:)]) {
            [self.delegate gkPlayer:self bufferProgress:bufferProgress];
        }
    }
    

    3、播放,以为播放时会将数据缓存到本地,所以做了删除处理,可根据需求修改。另外做了url的判断,判断是网络还是本地

    - (void)setPlayUrlStr:(NSString *)playUrlStr {
        if (![_playUrlStr isEqualToString:playUrlStr]) {
            
            // 切换数据,清除缓存
            [self removeCache];
            
            _playUrlStr = playUrlStr;
            
            if ([playUrlStr hasPrefix:@"http"] || [playUrlStr hasPrefix:@"https"]) {
                self.audioStream.url = [NSURL URLWithString:playUrlStr];
            }else {
                self.audioStream.url = [NSURL fileURLWithPath:playUrlStr];
            }
        }
    }
    

    4、seek 设置播放进度

    - (void)setPlayerProgress:(float)progress {
        FSStreamPosition position = {0};
        position.position = progress;
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.audioStream seekToPosition:position];
        });
    }
    

    最后

    本次的demo里面抽离出来了许多工具类如:搜索框、定时器、歌词解析、滑杆、刷新、下载管理等,有需要的可以使用。
    demo会不断更新,如果有需求欢迎随时提出。
    另外推荐下我的图片浏览器GKPhotoBrowser

    相关文章

      网友评论

      • superlees:你好作者,播放界面左右滑动切换太快歌曲播放不会变化问题,请问一下这个bug有解决的思路吗?
        QuintGao:快速滑动确实有问题,我抽时间优化一下
      • BuzzLighty_ec50:你好作者,我想在自己的手机上测试程序怎么改签名啊,求教
        QuintGao:@BuzzLighty_ec50 比较忙,有些地方没有完善。
        BuzzLighty_ec50:@QuintGao 为什么搜索歌手点不进去资料啊
        QuintGao:@BuzzLighty_ec50 替换你自己的bundle id和证书
      • 吃货_X:搜索框位置在X中没能适配,全局播放按钮在搜索取消后会显示出来(未播放状态下)。
        QuintGao:你好,问题已修复
      • 下一个七年:设置音乐的播放速率有问题。设置播放速率1.12和1.13有很大差别
      • 木木123456:GKWYPlayerViewController这个文件里dealloc方法不调用啊?
        QuintGao:单例控制器,当然不会调用啊:sweat_smile:
      • 宋进锋:你好作者,我请教个问题,为什么我有个1小时左右的音频 ,播放时,提示检索url失败,缓冲失败,等。但是链接放在您的demo里就能播放,是哪里需要设置一下吗
        QuintGao:@宋进锋 我这里没问题啊,GKAudioPlayer换成了什么?
        宋进锋:是不是之前写的版本有bug呀,我把相应文件(GKAudioPlayer)换了就可以了
      • kingZhou520:Github上download ZIP 也是报The file couldn’t be opened😓
        QuintGao:xcode是最新版的吗 我这里试过很多次 都没问题的
      • 宋进锋:你这个demo 没有下载进度管理是吗
        宋进锋:@QuintGao 哦,我现在就需要,看来得自己加喽
        QuintGao:@宋进锋 是的,暂时没有加上,以后更新了会加
      • 满卷:请问支持播放ape格式文件吗?
        满卷:额? ......酷狗音乐播放器支持, 网易不支持, 正在找怎么解决
        QuintGao:刚刚试了下 并不支持
      • 佛门净地的孤高:工程文件报错
        QuintGao:@e9cf23116d9a 我这里没问题啊,不管是重新下载,还是clone都是没问题的。你确定你那下载是完整的吗?
        佛门净地的孤高:@QuintGao The file couldn’t be opened.
        QuintGao:@e9cf23116d9a 报什么错

      本文标题:iOS基于FreeStreamer的仿网易云音乐播放器

      本文链接:https://www.haomeiwen.com/subject/qnnxdftx.html