iOS项目FreeStreamer音乐播放器灵活运用

新需求要在项目中增加音频播放的功能,原本使用AVPlayer自己写了一个demo实现了音频文件的播放,但是考虑到以后的拓展和内存的优化等问题,决定找找有没有好一点的第三方框架可以使用,于是乎发现了FreeStreamer
使用FreeStreamer实现音频播放,音频播放控制,后台播放,后台播放控制等功能。

一、实现音频播放

  • 使用cocoapods添加FreeStreamer库

pod 'FreeStreamer', '~> 3.8.2'

  • 导入FreeStreamer框架

#import <FSAudioStream.h>

  • 创建播放类并进行播放音频
_audioStream = [[FSAudioStream alloc] init];
// 播放失败的回调
_audioStream.onFailure = ^(FSAudioStreamError error,NSString *description){
            NSLog(@"播放过程中发生错误,错误信息:%@",description);
            };
// 播放完成的回调
_audioStream.onCompletion=^(){
            NSLog(@"播放完成!");
            };
// 设置音量
[_audioStream setVolume:0.5];
// 使用音频链接URL播放音频
NSString *urlStr = @"http://up.mcyt.net/down/45957.mp3";
NSURL *url = [NSURL URLWithString:urlStr];
[_audioStream playFromURL:url];

如果出现Strict content type checking active, application/octet-stream is not an audio content type的错误信息,许多服务器也许会给你发送错误的 MIME 类型。这种情况下,FreeStreamer 也许就不能播放这个音频了。如果你想避免 content-type 检查(但是这个 steam 依然是音频文件),你可以设置如下的属性:

// 不进行检测格式 <开启检测之后,有些网络音频链接无法播放>
_audioStream.strictContentTypeChecking = NO;
_audioStream.defaultContentType = @"audio/mpeg";

二、实现音频的播放控制

  • 暂停

直接调用[self.audioStream pause]即可

- (void)pauseAction:(id)sender {
    if (self.audioStream.isPlaying) {
        [self.audioStream pause];
    }
}

  • 播放

[self.audioStream play]不是音频暂停之后进行播放,而是初始化完成之后开始播放音频,所以,音频暂停之后继续调用pause方法即可恢复播放。

- (void)playAction:(id)sender {
    if (!self.audioStream.isPlaying) {
        [self.audioStream pause];
    }
}

  • 上一曲 / 下一曲

使用[self.audioStream playFromURL:url]直接切换音频链接即可

// 上一曲
- (IBAction)lastMusicAction:(UIButton *)sender {
    [self.audioStream stop];
    NSString *urlStr = [NSString stringWithFormat:@"http://up.mcyt.net/down/47541.mp3", _currentID];
    NSURL *url=[NSURL URLWithString:urlStr];
    [self.audioStream playFromURL:url];
}
// 下一曲
- (IBAction)nextMusicAction:(id)sender {
    [self.audioStream stop];
    NSString *urlStr = @"http://up.mcyt.net/down/47541.mp3";
    NSURL *url=[NSURL URLWithString:urlStr];
    [self.audioStream playFromURL:url];
}

  • 快进 / 快退

使用[self.audioStream seekToPosition:position]进行播放进度的切换
根据不同的状态给UISlider添加不同的addTarget方法:

[self.progress addTarget:self action:@selector(progressChangeAction:) forControlEvents:(UIControlEventValueChanged)];
[self.progress addTarget:self action:@selector(progressTouchBeginAction:) forControlEvents:(UIControlEventTouchDown)];
[self.progress addTarget:self action:@selector(progressTouchEndAction:) forControlEvents:(UIControlEventTouchUpInside)];

实现addTarget方法,实现快进或快退:

// 进度正在改变
- (void)progressChangeAction:(UISlider *)slider {
    float value = slider.value;
    // 进度 * 总时间 获取当前时间
    float current = value * _totalTime;
    // 当前分钟数
    double minutesElapsed =floor(fmod(current/60.0,60.0));
    // 当前秒数
    double secondsElapsed =floor(fmod(current,60.0));
    // 格式化当前时间
    NSString *currentTime = [NSString stringWithFormat:@"%02.0f:%02.0f", minutesElapsed, secondsElapsed];
    // 改变显示当前时间的标签文字
    self.currentTimeLabel.text = currentTime;
}
// 开始改变进度
- (void)progressTouchBeginAction:(UISlider *)sender {
    NSLog(@"开始触摸");
    [self removeTimer];
    // 暂停
    [self pauseAction:nil];
}
// 结束改变进度
- (void)progressTouchEndAction:(UISlider *)sender {
    NSLog(@"结束触摸");
    [self addTimer];
    // 播放
    [self playAction:nil];
    // 获取进度 0 ~ 1
    float value = sender.value == 0 ? 0.001 : sender.value;
    // 创建播放进度对象
    FSStreamPosition position;
    // 赋值
    position.position = value;
    // 跳转进度
    [self.audioStream seekToPosition:position];
}

FSStreamPosition是一个结构体,可使用跳转的分钟minute和秒second;跳转的时间总秒数playbackTimeInSeconds;跳转的位置position三种方式进行跳转:

typedef struct {
    /**
     * minute 分钟数
     * second 秒数    
     */
    unsigned minute;
    unsigned second;

    /**
     * Playback time in seconds. 播放时间总秒数
     */
    float playbackTimeInSeconds;

    /**
     * Position within the stream, where 0 is the beginning
     * and 1.0 is the end. 播放时间的位置<进度0~1>
     */
    float position;
} FSStreamPosition;

  • 动态改变播放进度、播放时间

该方法需要使用定时器持续调用

- (void)playProgressAction {
    FSStreamPosition cur = self.audioStream.currentTimePlayed;
    float playbackTime = cur.playbackTimeInSeconds/1;
    double minutesElapsed = floor(fmod(playbackTime/60.0,60.0));
    double secondsElapsed = floor(fmod(playbackTime,60.0));
    NSString *currentTime = [NSString stringWithFormat:@"%02.0f:%02.0f", minutesElapsed, secondsElapsed];
    NSLog(@"当前播放时间:%f", playbackTime);//播放进度
    NSLog(@"格式化当前播放时间:%@", currentTime);
    // 获取视频的总时长
    float totalTime = playbackTime / cur.position;
    // 记录音频总时间
    _totalTime = totalTime;
    NSLog(@"总时间:%f", totalTime);
    if ([[NSString stringWithFormat:@"%f",totalTime] isEqualToString:@"nan"]) {
        NSLog(@"格式化总时间:00:00");
    }else{
        double minutesElapsed1 =floor(fmod(totalTime/60.0,60.0));
        double secondsElapsed1 =floor(fmod(totalTime,60.0));
        NSString *total = [NSString stringWithFormat:@"%02.0f:%02.0f",minutesElapsed1, secondsElapsed1];
        NSLog(@"格式化总时间:%@", total);
        // 改变当前播放时间和音频总时间的显示
        self.currentTimeLabel.text = currentTime;
        self.totalTimeLabel.text = total;
    }
    float  prebuffer = (float)self.audioStream.prebufferedByteCount;
    float contentlength = (float)self.audioStream.contentLength;
    if (contentlength>0) {
        NSLog(@"缓存进度:%f", prebuffer / contentlength);
        // 改变播放进度
        self.progress.value = cur.position;
    }
}

  • 调节音量

给调节音量的UISlider添加addTarget方法:

[self.valumSlider addTarget:self action:@selector(valumChangeAction:) forControlEvents:(UIControlEventValueChanged)];

实现调节音量的方法:

- (void)valumChangeAction:(UISlider *)slider {
    self.audioStream.volume = slider.value;
}

三、实现后台播放

  • 打开后台模式

进入工程TARGETS打开后台模式:

image
  • 检查info.plist是否自动生成后台播放标签

打开后台模式之后,在info.plist文件中自动生成Required background modes标签:

image

至此,返回后台已经可以继续播放音频了。

  • 添加后台播放任务

以上配置完成之后,在进入后台播放一段时间后,还是会被系统停止音频播放,此时还应在AppDelegate.m中开启后台任务。

@interface AppDelegate ()
{
    UIBackgroundTaskIdentifier _bgTaskId;
}
@end

- (void)applicationWillResignActive:(UIApplication *)application {
    //开启后台处理多媒体事件
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    AVAudioSession *session=[AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    //后台播放
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    //这样做,可以在按home键进入后台后 ,播放一段时间,几分钟吧。但是不能持续播放网络歌曲,若需要持续播放网络歌曲,还需要申请后台任务id,具体做法是:
    _bgTaskId=[AppDelegate backgroundPlayerID:_bgTaskId];
    //其中的_bgTaskId是后台任务UIBackgroundTaskIdentifier _bgTaskId;
}

+ (UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId {
    //设置并激活音频会话类别
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    [session setActive:YES error:nil];
    //允许应用程序接收远程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    //设置后台任务ID
    UIBackgroundTaskIdentifier newTaskId = UIBackgroundTaskInvalid;
    newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    if(newTaskId != UIBackgroundTaskInvalid && backTaskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:backTaskId];
    }
    return newTaskId;
}

  • 优化后台播放可能遇到的问题

后台播放时可能会出现[avas] AVAudioSession.mm:1074:-[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.的错误信息,解决办法可查看这篇文章

四、线控及锁屏信息

  • 配置第一响应者

让播放控制类成为第一响应者,后台的控制在该类中响应:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    //以及设置app支持接受远程控制事件代码。设置app支持接受远程控制事件,

    //其实就是在dock中可以显示应用程序图标,同时点击该图片时,打开app。

    //或者锁屏时,双击home键,屏幕上方出现应用程序播放控制按钮。

    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

    [self becomeFirstResponder]; //成为FristResponder

}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];

    [self resignFirstResponder];

}

  • 接收远程控制信息

实现远程控制接收事件,进行区分事件的类别,响应不同的操作:

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    if (event.type == UIEventTypeRemoteControl) {
        switch (event.subtype) {
            // 播放
            case UIEventSubtypeRemoteControlPlay:
            {
                [self playAction:nil];
            }
                break;
            // 暂停
            case UIEventSubtypeRemoteControlPause:
            {
                [self pauseAction:nil];
            }
                break;
            // 停止播放
            case UIEventSubtypeRemoteControlStop:
            {
                [self.audioStream stop];
            }
                break;
            // 播放下一曲按钮
            case UIEventSubtypeRemoteControlNextTrack:
            {
                [self nextMusicAction:nil];
            }
                break;
            // 播放上一曲按钮
            case UIEventSubtypeRemoteControlPreviousTrack:
            {
                [self lastMusicAction:nil];
            }
                break;
            case UIEventSubtypeRemoteControlTogglePlayPause:
            {
                if (self.audioStream.isPlaying) {
                    [self pauseAction:nil];
                } else {
                    [self playAction:nil];
                }
            }
                break;
            default:
                break;
        }
    }
}

  • 修改锁屏界面音频信息

当前音频开始播放及时修改信息。

// 改变锁屏歌曲信息
- (void)setLockScreenNowPlayingInfo {

    //更新锁屏时的歌曲信息
    if (NSClassFromString(@"MPNowPlayingInfoCenter")) {

        NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
        // 歌曲名
        [dict setObject:@"体面" forKey:MPMediaItemPropertyTitle];
        // 演唱者
        [dict setObject:@"于文文" forKey:MPMediaItemPropertyArtist];
        // 专辑名
        [dict setObject:@"专辑《体面》" forKey:MPMediaItemPropertyAlbumTitle];

        //专辑缩略图
        UIImage *newImage = [UIImage imageNamed:@"音乐"];
        [dict setObject:[[MPMediaItemArtwork alloc] initWithImage:newImage] forKey:MPMediaItemPropertyArtwork];

        //设置锁屏状态下屏幕显示播放音乐信息
        [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];

    }

}

  • 修改锁屏界面音频的播放进度

需要在修改音频播放进度条的方法中动态调用该方法,以动态改变锁屏界面的播放进度。

/**
 定时器修改进度

 @param duration 总时间
 @param current 当前时间
 */
- (void)changeLockProgress:(NSInteger)duration current:(NSInteger)current {
    if(self.audioStream.isPlaying) {

        //当前播放时间
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo]];
        // 歌曲总时长
        [dict setObject:@(duration) forKey:MPMediaItemPropertyPlaybackDuration];
        // 当前播放时间
        [dict setObject:@(current) forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
        [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];

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

推荐阅读更多精彩内容