美文网首页iOS第三方框架使用技巧
iOS项目FreeStreamer音乐播放器灵活运用

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

作者: 叶情宇 | 来源:发表于2019-08-02 18:14 被阅读0次

    新需求要在项目中增加音频播放的功能,原本使用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

    相关文章

      网友评论

        本文标题:iOS项目FreeStreamer音乐播放器灵活运用

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