//
//  MKRAVPlayer.m
//  MKRAVPlayer_Example
//
//  Created by yyqxiaoyin on 2018/10/19.
//  Copyright © 2018 yyqxiaoyin. All rights reserved.
//

#import "MKRAVPlayer.h"
#import <AVFoundation/AVFoundation.h>

inline static bool isFloatZero(float value)
{
    return fabsf(value) <= 0.00001f;
}

static void * PlayerStatusObservationContext = &PlayerStatusObservationContext;

@interface MKRAVPlayer (){
    BOOL _interruptWhenPlaying;
}
@property (nonatomic, strong) id                timeObserver;
@property (nonatomic, assign) UIBackgroundTaskIdentifier bgTaskId;//后台播放id
@property (nonatomic, strong) AVPlayer *player;//播放器player
@property (nonatomic, strong) AVPlayerItem *playerItem;//当前播放的item
@property (nonatomic, strong) NSURL *playUrl;//播放资源路径URL
@property (nonatomic, strong) NSString *currentUrlString;//当前播放资源路径字符串
@property (nonatomic, strong) AVURLAsset *playAsset;//播放资源
@property (nonatomic, assign) BOOL isInitPlayer;//是否初始化了播放器
@property (nonatomic, assign) MKRAVPlayerState state;//播放状态
@property (nonatomic, assign) CGFloat progress;//缓冲进度
@property (nonatomic, strong) NSArray <NSString *> *playList;//播放列表
//@property (nonatomic, assign, readwrite) NSInteger currentIndex;//当前播放的下标
@property (nonatomic, copy)   MKRAVPlayerURLRequsetBlcok block;
@property (nonatomic, assign) BOOL isSeeking;//是否快进或快退
@property (nonatomic, assign) NSTimeInterval seekTime;//快进或者快退的目标时间
@end

@implementation MKRAVPlayer

- (instancetype)init{
    if (self = [super init]) {
        self.rate      = 1.0f;
        self.isSeeking = NO;
        self.seekTime  = 0.0f;
        self.currentIndex = 0;
        _interruptWhenPlaying = NO;
    }
    return self;
}

NSURL * MKRUrlWithString(NSString *string){
    NSURL *url;
    if (string == nil) {
        string = @"";
    }
    if ([string isKindOfClass:[NSURL class]]) {
        return (NSURL *)string;
    }
    if ([string rangeOfString:@"/"].location == 0) {//本地资源
        url = [NSURL fileURLWithPath:string];
    }else{
        string = [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
        url = [NSURL URLWithString:string];
    }
    return url;
}

- (id)initWithPlayList:(NSArray<NSString *> *)playList{
    self = [self init];
    if (self) {
        self.playList = playList;
    }
    return self;
}

- (id)initWithPlayList:(NSArray<NSString *> *)playList playMode:(MKRAVPlayerPlayMode)playMode{
    self = [self initWithPlayList:playList];
    if (self) {
        self.playMode = playMode;
    }
    return self;
}

- (id)initWithContentURL:(NSURL *)aUrl{
    self = [self init];
    if (self) {
        self.playUrl  = aUrl;
        self.playList = [NSArray arrayWithObject:aUrl];
    }
    return self;
}

- (id)initWithContentURLString:(NSString *)aUrl{
    NSURL *url = MKRUrlWithString(aUrl);
    return [self initWithContentURL:url];
}

- (id)initWithObjectsPlayList:(NSArray<id<MKRRadioResorce>> *)playList{
    NSArray *urls = [playList valueForKey:@"songUrl"];
    return [self initWithPlayList:urls];
}

- (id)initWithObjectsPlayList:(NSArray<id<MKRRadioResorce>> *)playList playMode:(MKRAVPlayerPlayMode)playMode{
    NSArray *urls = [playList valueForKey:@"songUrl"];
    return [self initWithPlayList:urls playMode:playMode];
}

- (void)updatePlayList:(NSArray<NSString *> *)playList{
    self.playList = playList;
    NSUInteger index = [playList indexOfObject:self.playUrl.absoluteString];
    if (index != NSNotFound) {
        self.currentIndex = index;
    }
}

#pragma mark - 播放相关

- (void)play{
    if (!self.isInitPlayer) {
        [self preparePlay];
    }else{
        if (self.state == MKRAVPlayerStateStopped || self.state == MKRAVPlayerStatePause) {
            [self.player play];
            self.state = MKRAVPlayerStatePlaying;
        }else if (self.state == MKRAVPlayerStateBuffering){
            [self.player play];
            self.state = MKRAVPlayerStatePlaying;
        }else if (self.state == MKRAVPlayerStatePlaying){
            if (self.player && !self.player.rate) {
                [self.player play];
                self.state = MKRAVPlayerStatePlaying;
            }
        }else if (self.state == MKRAVPlayerStateFinished){
            NSLog(@"播放完成");
        }
    }
}

- (void)playAtIndex:(NSInteger)index{
    NSLog(@"当前模式%zd 下标：%zd",self.playMode,index);
    if (self.playList && self.playList.count > index) {
        self.currentIndex = index;
        NSString *url = [self.playList objectAtIndex:index];
        self.playUrl = MKRUrlWithString(url);
        [self play];
    }
}

- (void)nextWhenPlayDidEnd{
    if (!self.playList.count) return;
    [self resetPlayer];
    switch (_playMode) {
        case MKRAVPlayerPlayModeRandom:{
            [self playAtIndex:[self ramdomIndex]];
        }
            break;
        case MKRAVPlayerPlayModeSingle:{
            [self playAtIndex:self.currentIndex];
        }
            break;
        case MKRAVPlayerPlayModeOrder:
        default:{
            [self next];
        }
            break;
    }
}

- (void)next{
    if (_playMode == MKRAVPlayerPlayModeRandom) {
        [self nextWhenPlayDidEnd];
        return;
    }
    [self resetPlayer];
    NSInteger index = self.currentIndex + 1;
    if (index > self.playList.count - 1) {
        index = 0;
    }
    [self playAtIndex:index];
}

- (void)previous{
    [self resetPlayer];
    NSInteger index = self.currentIndex - 1;
    if (index < 0) {
        index = self.playList.count - 1;
    }
    [self playAtIndex:index];
}

- (NSInteger)ramdomIndex{
    NSInteger index = arc4random_uniform((int)self.playList.count);
    if (self.playList.count >= 1) {
        while (index == self.currentIndex) {
            index = arc4random_uniform((int)self.playList.count);
        }
    }
    return index;
}

- (void)pause{
    [self.player pause];
    if (self.state == MKRAVPlayerStatePlaying) {
        self.state = MKRAVPlayerStatePause;
    }
}

- (void)resetPlayer{
    self.playerItem = nil;
    self.isInitPlayer = NO;
    [self.player replaceCurrentItemWithPlayerItem:nil];
    [self.player.currentItem cancelPendingSeeks];
    [self.player.currentItem.asset cancelLoading];
    [self.player pause];
    [self removeNotification];
    [self removePlayerKVO];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:nil
                                                  object:_playerItem];
    
    @try {
        [self.player removeObserver:self forKeyPath:@"rate"];
    } @catch (NSException *exception) {
    } @finally {
    }
    self.player = nil;
}

- (void)preparePlay{
    if (self.delegate && [self.delegate respondsToSelector:@selector(songPlayUrlWithIndex:completion:)]) {
        __weak __typeof(self)weakSelf = self;
        [self.delegate songPlayUrlWithIndex:self.currentIndex completion:^(NSString * _Nonnull resourceUrl) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            strongSelf.playUrl = MKRUrlWithString(resourceUrl);
            [strongSelf createPlayerAndReadyToPlay];
        }];
        return;
    }
    [self createPlayerAndReadyToPlay];
}

- (void)createPlayerAndReadyToPlay{
    if (self.isInitPlayer) {
        return;
    }
    //注册通知
    [self registerNotification];
    if (self.playerItem) {
        [self resetPlayer];
        self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
    }else{
        self.playAsset = [AVURLAsset assetWithURL:self.playUrl];
        NSArray *requestedKeys = @[@"playable"];
        [self.playAsset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{
             [self didPrepareToPlayAsset:self.playAsset withKeys:requestedKeys];
        }];
        self.playerItem = [AVPlayerItem playerItemWithAsset:self.playAsset];
        self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
        [self.player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:PlayerStatusObservationContext];
        __weak __typeof(self)weakSelf = self;
        self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(0.1*30, 30) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
            CGFloat currentT = (CGFloat)CMTimeGetSeconds(time);
            if (weakSelf.currentStatus) {
                weakSelf.currentStatus.currentDuration = currentT;
            }
        }];
    }
    self.isInitPlayer = YES;
    if (@available(iOS 10.0, *)) {
        self.player.automaticallyWaitsToMinimizeStalling = NO;
    }
    self.state = MKRAVPlayerStateBuffering;
}

- (void)didPrepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys{
    for (NSString *thisKey in requestedKeys) {
        NSError *error = nil;
        AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
        if (keyStatus == AVKeyValueStatusLoaded) {
            dispatch_async(dispatch_get_main_queue(), ^{
            });
        }
    }
}

- (void)setPlayerItem:(AVPlayerItem *)playerItem{
    if (_playerItem == playerItem) {
        return;
    }
    if (_playerItem) {
        [self removePlayerKVO];
        _playerItem = nil;
    }
    _playerItem = playerItem;
    if (_playerItem) {
        [self addPlayerItemKVO];
        [self.player replaceCurrentItemWithPlayerItem:_playerItem];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
    }
}

#pragma mark - 通知实现方法

- (void)appDidEnterBackground:(NSNotification *)note{
    _bgTaskId = [self backgroundPlayerID:_bgTaskId];
}

- (UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId{
    UIBackgroundTaskIdentifier newTaskId = UIBackgroundTaskInvalid;
    newTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    if (newTaskId != UIBackgroundTaskInvalid && backTaskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:backTaskId];
    }
    return newTaskId;
}

- (void)appWillEnterForeground:(NSNotification *)note{
    _bgTaskId = UIBackgroundTaskInvalid;
    [[UIApplication sharedApplication] endBackgroundTask:_bgTaskId];
}

- (void)playbackStalled:(NSNotification *)note{
    if (self.delegate && [self.delegate respondsToSelector:@selector(playbackStalled)]) {
        [self.delegate playbackStalled];
    }
    NSLog(@"中断");
}

- (void)playerItemFailedToPlayToEndTime:(NSNotification *)note{
    NSLog(@"失败e没结束");
    if (self.delegate && [self.delegate respondsToSelector:@selector(didFailedToPlayToEndTime)]) {
        [self.delegate didFailedToPlayToEndTime];
    }
}

- (void)failedToPlayToEndTimeError:(NSNotification *)note{
    NSLog(@"错误e没结束");
    if (self.delegate && [self.delegate respondsToSelector:@selector(didFailedToPlayToEndTime)]) {
        [self.delegate didFailedToPlayToEndTime];
    }
}

- (void)moviePlayDidEnd:(NSNotification *)note{
    self.state = MKRAVPlayerStateFinished;
    self.rate = 1.0f;
    self.seekTime = 0.0f;
    self.isSeeking = NO;
    [self nextWhenPlayDidEnd];
    if (self.delegate && [self.delegate respondsToSelector:@selector(didFinishPlay:)]) {
        [self.delegate didFinishPlay:self.currentIndex];
    }
}

#pragma mark - setter

- (void)setPlayUrl:(NSURL *)playUrl{
    _playUrl = playUrl;
    self.currentUrlString = playUrl.absoluteString;
}

- (void)setCurrentTime:(NSInteger)currentTime{
    if (!_player) return;
    [_player pause];
    _seekTime = currentTime;
    _isSeeking = YES;
    self.state = MKRAVPlayerStateBuffering;
    [_player seekToTime:CMTimeMakeWithSeconds(currentTime, NSEC_PER_SEC) completionHandler:^(BOOL finished) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self->_isSeeking = NO;
            [self.player play];
            self.player.rate = self.rate;
            self.state = MKRAVPlayerStatePlaying;
        });
    }];
}

- (BOOL)isPlaying
{
    if (self.state == MKRAVPlayerStatePlaying) {
        return YES;
    }
    if (!isFloatZero(_player.rate)) {
        return YES;
    } else {
        if (_isSeeking) {
            return YES;
        } else {
            return NO;
        }
    }
}

- (void)setRate:(float)rate{
    _rate = rate;
    if (_player != nil && !isFloatZero(_player.rate)) {
        _player.rate = _rate;
    }
}

- (void)setState:(MKRAVPlayerState)state{
    if (_state != state) {
        _state = state;
        if (self.delegate && [self.delegate respondsToSelector:@selector(playStateDidChange:)]) {
            [self.delegate playStateDidChange:state];
        }
    }
}

- (void)setDelegate:(id<MKRAVPlayerDelegate>)delegate{
    _delegate = delegate;
}

#pragma mark - getter
- (NSInteger)currentTime{
    if (!_player) return 0.0f;
    if (_isSeeking) return _seekTime;
    if (!self.totalTime) return 0.0f;
    return CMTimeGetSeconds([_player currentTime]);
}

- (MKRPlayResourceStatus *)currentStatus {
    if (!_currentStatus){
        _currentStatus = [[MKRPlayResourceStatus alloc] init];
    }
    _currentStatus.totalTime = self.totalTime;
    _currentStatus.currentTime = self.currentTime;
    _currentStatus.totalDuration = self.totalTime;
//    _currentStatus.currentDuration = self.currentTime;
    _currentStatus.isPlaying = [self isPlaying];
    _currentStatus.playState = (MKRResourcePlayState)self.state;
    return _currentStatus;
}

#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if (context != PlayerStatusObservationContext) {
        return;
    }
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
        switch (status) {
            case AVPlayerStatusUnknown:{
                self.state = MKRAVPlayerStateBuffering;
                NSLog(@"AVPlayerStatusUnknown");
            }
                break;
            case AVPlayerItemStatusReadyToPlay:{
                NSLog(@"AVPlayerItemStatusReadyToPlay");
                if (self.delegate && [self.delegate respondsToSelector:@selector(didReadyToPlay)]) {
                    [self.delegate didReadyToPlay];
                }
                if (self.state == MKRAVPlayerStateStopped || self.state == MKRAVPlayerStatePause) {
                }else{
                    if (self.shouldAutoPlay) {
                        [self.player play];
                        self.state = MKRAVPlayerStatePlaying;
                    }
                }
            }
                break;
            case AVPlayerItemStatusFailed:{
                NSLog(@"AVPlayerItemStatusFailed");
                self.state = MKRAVPlayerStateFaild;
                NSError *error = [self.player.currentItem error];
                NSLog(@"视频加载失败===%@",error.description);
                if (self.delegate && [self.delegate respondsToSelector:@selector(didFailedToLoadURL:error:)]) {
                    [self.delegate didFailedToLoadURL:self.currentUrlString error:error];
                }
            }
                break;
        }
    }else if ([keyPath isEqualToString:@"duration"]){
        if ((CGFloat)CMTimeGetSeconds(self.playerItem.duration) != self.totalTime) {
            self.totalTime = (CGFloat)CMTimeGetSeconds(self.playerItem.asset.duration);
            if (isnan(self.totalTime)) {
                self.totalTime = 0;
                self.state = MKRAVPlayerStateBuffering;
            }else{
                if (self.state == MKRAVPlayerStateStopped || self.state == MKRAVPlayerStatePause) {
                }else{
//                    self.state = MKRAVPlayerStatePlaying;
                }
            }
        }
        
    }else if ([keyPath isEqualToString:@"loadedTimeRanges"]){//缓冲进度
        NSTimeInterval timeInterval = [self calculateDuration];
        CMTime duration             = self.playerItem.duration;
        CGFloat totalDuration       = CMTimeGetSeconds(duration);
        self.progress = timeInterval / totalDuration;
        if (self.progress >= 0.0 && self.progress <= 1.0) {
            if (self.progress == 0.0) {
                NSLog(@"开始缓冲");
            }else if (self.progress == 1.0){
                NSLog(@"缓冲完成");
            }
        }
        
    }else if ([keyPath isEqualToString:@"playbackBufferEmpty"]){
        NSLog(@"playbackBufferEmpty %@",self.playerItem.playbackBufferEmpty?@"YES":@"NO");
        if (self.playerItem.playbackBufferEmpty) {
            NSLog(@"%s playbackBufferEmpty",__FUNCTION__);
            [self loadedTimeRanges];
        }
        
    }else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]){
        NSLog(@"playbackLikelyToKeepUp %@",self.playerItem.playbackLikelyToKeepUp ?@"YES":@"NO");
        if (self.playerItem.playbackLikelyToKeepUp && self.state == MKRAVPlayerStateBuffering) {
            if (self.state == MKRAVPlayerStateStopped || self.state == MKRAVPlayerStatePause) {
//                [self.player play];
            }else{
                if (self.player.rate) {
                    self.state = MKRAVPlayerStatePlaying;
                }
            }
        }
    }else if ([keyPath isEqualToString:@"rate"]){
        NSLog(@"播放速率改变  %f",self.player.rate);
        if (self.playerItem.playbackLikelyToKeepUp && self.state == MKRAVPlayerStatePlaying) {
            if (!self.player.rate) {
                _interruptWhenPlaying = YES;
            }
        }else{
            _interruptWhenPlaying = NO;
        }
    }
}

- (void)loadedTimeRanges{
    NSLog(@"缓冲区为空");
    if (self.player.rate == 0 && !self.playerItem.playbackLikelyToKeepUp) {
        if (_interruptWhenPlaying) {
            NSLog(@"网络状况导致卡顿");
            self.state = MKRAVPlayerStateBuffering;
        }
    }
    if (self.delegate && [self.delegate respondsToSelector:@selector(stalledByCacheEmtpy)]) {
        [self.delegate stalledByCacheEmtpy];
    }
}

//计算缓冲进度
- (NSTimeInterval)calculateDuration{
    NSArray *loadedTimeRanges = [_playerItem loadedTimeRanges];
    CMTimeRange range = [loadedTimeRanges.firstObject CMTimeRangeValue];
    CGFloat start = CMTimeGetSeconds(range.start);
    CGFloat duration = CMTimeGetSeconds(range.duration);
    return (start + duration);
}

- (void)registerNotification{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)removeNotification{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)addPlayerItemKVO{
    [_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:PlayerStatusObservationContext];
    [_playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:PlayerStatusObservationContext];
    //缓冲区没有足够数据以供播放
    [_playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:PlayerStatusObservationContext];
    //缓冲区有足够数据以供播放
    [_playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:PlayerStatusObservationContext];
    [_playerItem addObserver:self forKeyPath:@"duration" options:NSKeyValueObservingOptionNew context:PlayerStatusObservationContext];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackStalled:) name:AVPlayerItemPlaybackStalledNotification object:_playerItem];
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemFailedToPlayToEndTime:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_playerItem];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(failedToPlayToEndTimeError:) name:AVPlayerItemFailedToPlayToEndTimeErrorKey object:_playerItem];
}

- (void)removePlayerKVO{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
    [_playerItem removeObserver:self forKeyPath:@"status"];
    [_playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
    [_playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
    [_playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
    [_playerItem removeObserver:self forKeyPath:@"duration"];
    if (self.timeObserver) {
        [self.player removeTimeObserver:self.timeObserver];
        self.timeObserver = nil;
    }
    
}

@end
