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

前言

前段时间写过一个基于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

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

推荐阅读更多精彩内容

  • 简介: 她是京城四大家族乔家的小小姐,拥有绝美容貌,集全家宠爱于一身。 他是唯一能与四大家族比肩的军门世家冷家的大...
    吟诗悦阅读 345评论 0 0