iOS音频播放第三方框架FreeStreamer解析。

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

一、实现音频播放

  • 使用cocoapods添加FreeStreamer库

pod 'FreeStreamer', '~> 3.8.2'

  • 导入FreeStreamer框架

#import

  • 创建播放类并进行播放音频
_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打开后台模式:


iOS音频播放第三方框架FreeStreamer解析。_第1张图片
打开后台模式.jpeg
  • 检查info.plist是否自动生成后台播放标签

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

iOS音频播放第三方框架FreeStreamer解析。_第2张图片
info.jpeg

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

  • 添加后台播放任务

以上配置完成之后,在进入后台播放一段时间后,还是会被系统停止音频播放,此时还应在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];
        
    }
}

如果对您有帮助,请给个 ❤️ 哦!

你可能感兴趣的:(iOS音频播放第三方框架FreeStreamer解析。)