PlayerManger.m 10.3 KB
//
//  PlayerManger.m
//  DreamSleep
//
//  Created by peter on 2022/10/24.
//

#import "PlayerManger.h"
#import <FSAudioController.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>
#import "SubAudioModel.h"

@interface PlayerManger ()
/// AudioStream 播放器
@property (nonatomic, strong) FSAudioStream *audioStream;
/// 播放进度定时器
@property (nonatomic, strong) CADisplayLink *displayLink;
/// 是否正在拖动滑块
@property (nonatomic, assign) BOOL isDraging;
/// streamState
@property (nonatomic, assign) FSAudioStreamState streamState;
/// 播放数据model
@property (nonatomic, strong) SubAudioModel *playItem;
/// 控制中心信息
@property (nonatomic, strong) NSMutableDictionary *remoteInfoDictionary;
/// 是否进入后台
@property (nonatomic, assign) BOOL isBackground;
@end

@implementation PlayerManger

- (instancetype)initWithItem:(id)item {
    if (self = [super init]) {
        self.playItem = (SubAudioModel *)item;
        
        [self addStreamStateObserver];
        [self addPlayerObserver];
        [self addRemoteControlHandler];
    }
    return self;
}

- (void)dealloc {
    [self.displayLink invalidate];
    self.displayLink = nil;
    
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil];
    
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    MPRemoteCommandCenter *center = [MPRemoteCommandCenter sharedCommandCenter];
    [[center playCommand] removeTarget:self];
    [[center pauseCommand] removeTarget:self];
    [[center nextTrackCommand] removeTarget:self];
    [[center previousTrackCommand] removeTarget:self];
    [center.changePlaybackPositionCommand removeTarget:self];
}

#pragma mark - public
- (void)startPlay {
    [self.audioStream playFromURL:[NSURL URLWithString:self.playItem.audio_url]];
    
    // 设置音频锁屏界面信息
    [self _addPlayingCenterInfo];
}

- (void)seekToPosition:(float)position {
    FSStreamPosition pos = {0};
    pos.position = position;
    [self.audioStream seekToPosition:pos];
}

- (void)stop {
    if (self.streamState == kFsAudioStreamPlaying) {
        [self.audioStream pause];
    }
    [self.audioStream stop];
}

- (void)playOrPause {
    // 播放
    if (self.streamState == kFsAudioStreamStopped || self.streamState == kFsAudioStreamRetrievingURL) {
        [self.audioStream play];
    } else if (self.streamState == kFsAudioStreamPlaying) {
        // 暂停播放
        [self.audioStream pause];
    } else if (self.streamState == kFsAudioStreamPaused) {
        // 恢复播放
        [self.audioStream pause];
    }
}

- (void)updatePlayingCenterInfo {
    if (!self.isBackground) { return; }
    self.remoteInfoDictionary[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithDouble:self.audioStream.currentTimePlayed.playbackTimeInSeconds];
    self.remoteInfoDictionary[MPMediaItemPropertyPlaybackDuration] = [NSNumber numberWithDouble:self.audioStream.duration.playbackTimeInSeconds];
    [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = self.remoteInfoDictionary;
}

#pragma mark - 播放状态监控
- (void)addStreamStateObserver {
    WS(weakSelf);
    [self.audioStream setOnStateChange:^(FSAudioStreamState state) {
        weakSelf.streamState = state;
        if (weakSelf.playStateChange) {
            weakSelf.playStateChange(state == kFsAudioStreamPlaying);
        }
        [weakSelf.displayLink setPaused:!(state == kFsAudioStreamPlaying)];
        switch (state) {
            case kFsAudioStreamRetrievingURL:
                [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
                DSLog(@"retrieving URL  -- 检索文件");
                break;
            case kFsAudioStreamStopped:
                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                DSLog(@"kFsAudioStreamStopped --- 停止播放了");
                break;
            case kFsAudioStreamBuffering: {
                [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
                DSLog(@"buffering --- 缓存中");
                break;
            }
            case kFsAudioStreamPaused:
                DSLog(@"暂停了");
                break;
            case kFsAudioStreamSeeking:
                [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
                DSLog(@"kFsAudioStreamSeeking -- 快进 或者 快退");
                break;
            case kFsAudioStreamPlaying:
                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                DSLog(@"播放ing...");
                break;
            case kFsAudioStreamFailed:
                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                DSLog(@"音频文件加载失败");
                break;
            case kFsAudioStreamPlaybackCompleted:
                // 单曲循环
                [weakSelf.audioStream play];
                break;
            case kFsAudioStreamRetryingStarted:
                DSLog(@"回放失败");
                break;
            case kFsAudioStreamRetryingSucceeded:
                DSLog(@"重试成功");
                break;
            case kFsAudioStreamRetryingFailed:
                DSLog(@"Failed to retry playback -- 重试失败");
                break;
            default:
                break;
        }
    }];
    self.audioStream.onFailure = ^(FSAudioStreamError error, NSString *errorDescription) {
        DSLog(@"音频加载失败:%ld", error);
    };
}

#pragma mark - noti
- (void)addPlayerObserver {
    // 将要进入后台
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerWillResignActive) name:UIApplicationWillResignActiveNotification object:nil];
    // 已经进入前台
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerDidEnterForeground) name:UIApplicationDidBecomeActiveNotification object:nil];
    // 监听播放器被打断(别的软件播放音乐,来电话)
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerAudioBeInterrupted:)
                                                 name:AVAudioSessionInterruptionNotification
                                               object:[AVAudioSession sharedInstance]];
}

- (void)playerWillResignActive {
    self.isBackground = YES;
}

- (void)playerDidEnterForeground {
    self.isBackground = NO;
}

- (void)playerAudioBeInterrupted:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    if ([userInfo[AVAudioSessionInterruptionTypeKey] integerValue] == 1) { // 打断开始
        [self _pause];
    } else { // 打断结束
        if ([userInfo[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == 1) {
            [self _play];
        }
    }
}

#pragma mark - 息屏播放
- (void)addRemoteControlHandler {
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    MPRemoteCommandCenter *center = [MPRemoteCommandCenter sharedCommandCenter];
    [self _addRemoteCommand:center.playCommand selector:@selector(_play)];
    [self _addRemoteCommand:center.pauseCommand selector:@selector(_pause)];
}

#pragma mark - private
- (void)display {
    FSStreamPosition curPosition = self.audioStream.currentTimePlayed;
    FSStreamPosition endPosition = self.audioStream.duration;
    
    float progress = curPosition.position;
    NSString *curTime = [NSString stringWithFormat:@"%02i:%02i", curPosition.minute, curPosition.second];
    NSString *totalTime = [NSString stringWithFormat:@"%02i:%02i", endPosition.minute, endPosition.second];
    
    if (self.progressChange) {
        self.progressChange(progress, curTime, totalTime);
    }
}

- (void)_addPlayingCenterInfo {
    self.remoteInfoDictionary = [NSMutableDictionary dictionary];
    
    self.remoteInfoDictionary[MPMediaItemPropertyTitle] = self.playItem.audio_name;
    self.remoteInfoDictionary[MPMediaItemPropertyAlbumTitle] = @"小梦睡眠";
    NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.playItem.audio_pic]];
    UIImage *artworkImg = [UIImage imageWithData:imageData];
    MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:artworkImg.size requestHandler:^UIImage * _Nonnull(CGSize size) {
        return artworkImg;
    }];
    self.remoteInfoDictionary[MPMediaItemPropertyArtwork] = artwork;
    self.remoteInfoDictionary[MPNowPlayingInfoPropertyPlaybackRate] = [NSNumber numberWithFloat:1.0];
    [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = self.remoteInfoDictionary;
}

- (void)_play {
    if (self.streamState == kFsAudioStreamStopped || self.streamState == kFsAudioStreamRetrievingURL) {
        [self.audioStream play];
    }  else if (self.streamState == kFsAudioStreamPaused) {
        // 恢复播放
        [self.audioStream pause];
    }
}

- (void)_pause {
    if (self.streamState == kFsAudioStreamPlaying) {
        // 暂停播放
        [self.audioStream pause];
    }
}

- (void)_addRemoteCommand:(MPRemoteCommand *)command selector:(SEL)selector {
    WS(weaksSelf);
    [command addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        if ([weaksSelf respondsToSelector:selector]) {
            IMP imp = [weaksSelf methodForSelector:selector];
            void (*func)(id, SEL) = (void *)imp;
            func(weaksSelf, selector);
        }
        return MPRemoteCommandHandlerStatusSuccess;
    }];
}

#pragma mark - lazy
- (FSAudioStream *)audioStream {
    if (!_audioStream) {
        _audioStream = [[FSAudioStream alloc] init];
        _audioStream.strictContentTypeChecking = NO;
        _audioStream.defaultContentType = @"audio/mpeg";
        _audioStream.volume = .5;
        [_audioStream setPlayRate:1.0];
    }
    return _audioStream;
}

- (CADisplayLink *)displayLink {
    if (!_displayLink) {
        TimerProxy *proxy = [TimerProxy proxyWithTarget:self];
        _displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(display)];
        [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
    return _displayLink;
}

@end