You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
629 lines
24 KiB
629 lines
24 KiB
4 years ago
|
//
|
||
|
// WXYZ_Player.m
|
||
|
// WXYZ_Player
|
||
|
//
|
||
|
// Created by ihoudf on 2017/7/18.
|
||
|
// Copyright © 2017年 ihoudf. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "WXYZ_Player.h"
|
||
|
#import <MediaPlayer/MediaPlayer.h>
|
||
|
#import "WXYZ_TouchAssistantView.h"
|
||
|
|
||
|
#define WXYZ_PlayerHighGlobalQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
|
||
|
#define WXYZ_PlayerDefaultGlobalQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||
|
|
||
|
/**Asset KEY*/
|
||
|
NSString * const WXYZPlayableKey = @"playable";
|
||
|
/**PlayerItem KEY*/
|
||
|
NSString * const WXYZStatusKey = @"status";
|
||
|
NSString * const WXYZLoadedTimeRangesKey = @"loadedTimeRanges";
|
||
|
NSString * const WXYZPlaybackBufferEmptyKey = @"playbackBufferEmpty";
|
||
|
NSString * const WXYZPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp";
|
||
|
|
||
|
@interface WXYZ_Player()
|
||
|
{
|
||
|
BOOL _isOtherPlaying; // 其他应用是否正在播放
|
||
|
BOOL _isBackground; // 是否进入后台
|
||
|
BOOL _isSeek; // 正在seek
|
||
|
BOOL _isNaturalToEndTime; // 是否是自然结束
|
||
|
dispatch_group_t _netGroupQueue; // 组队列-网络
|
||
|
dispatch_group_t _dataGroupQueue; // 组队列-数据
|
||
|
CGFloat _seekValue; // seek value
|
||
|
NSMutableDictionary *_remoteInfoDictionary;//控制中心信息
|
||
|
}
|
||
|
/** player */
|
||
|
@property (nonatomic, strong) AVPlayer *player;
|
||
|
/** playerItem */
|
||
|
@property (nonatomic, strong) AVPlayerItem *playerItem;
|
||
|
/** 播放进度监测 */
|
||
|
@property (nonatomic, strong) id timeObserver;
|
||
|
/** model数据数组 */
|
||
|
@property (nonatomic, strong) NSMutableArray<WXYZ_PlayerModel *> *playerModelArray;
|
||
|
|
||
|
@property (nonatomic, copy) void(^seekCompletionBlock)(void);
|
||
|
|
||
|
@property (nonatomic, assign) UIBackgroundTaskIdentifier bgTaskId;
|
||
|
|
||
|
@property (nonatomic, readwrite, strong) WXYZ_PlayerModel *currentAudioModel;
|
||
|
@property (nonatomic, readwrite, strong) WXYZ_PlayerInfoModel *currentAudioInfoModel;
|
||
|
@property (nonatomic, readwrite, assign) TFBasicVoicePlayerState state;
|
||
|
@property (nonatomic, readwrite, assign) CGFloat bufferProgress;
|
||
|
@property (nonatomic, readwrite, assign) CGFloat progress;
|
||
|
@property (nonatomic, readwrite, assign) CGFloat currentTime;
|
||
|
@property (nonatomic, readwrite, assign) CGFloat totalTime;
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation WXYZ_Player
|
||
|
|
||
|
#pragma mark - 初始化
|
||
|
+ (WXYZ_Player *)sharedPlayer {
|
||
|
static WXYZ_Player *player = nil;
|
||
|
static dispatch_once_t predicate;
|
||
|
dispatch_once(&predicate, ^{
|
||
|
player = [[[self class] alloc] init];
|
||
|
});
|
||
|
return player;
|
||
|
}
|
||
|
|
||
|
- (void)dealloc{
|
||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
|
UIBackgroundTaskIdentifier taskID = [[UIApplication sharedExtensionApplication] beginBackgroundTaskWithExpirationHandler:^{}];
|
||
|
if (taskID != UIBackgroundTaskInvalid) {
|
||
|
[[UIApplication sharedExtensionApplication] endBackgroundTask:taskID];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma mark - 初始化播放器
|
||
|
- (void)initPlayerWithUserId:(NSString *)userId{
|
||
|
|
||
|
[[AVAudioSession sharedInstance] setActive:YES error:nil];
|
||
|
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||
|
|
||
|
_isOtherPlaying = [AVAudioSession sharedInstance].otherAudioPlaying;
|
||
|
|
||
|
self.playMode = WXYZ_PlayerModeOnlyOnce;
|
||
|
self.state = TFBasicVoicePlayerStateStoped;
|
||
|
_isBackground = NO;
|
||
|
|
||
|
_netGroupQueue = dispatch_group_create();
|
||
|
_dataGroupQueue = dispatch_group_create();
|
||
|
|
||
|
[self addPlayerObserver];
|
||
|
[self addRemoteControlHandler];
|
||
|
}
|
||
|
|
||
|
- (void)addPlayerObserver{
|
||
|
//将要进入后台
|
||
|
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||
|
[notificationCenter addObserver:self
|
||
|
selector:@selector(playerWillResignActive)
|
||
|
name:UIApplicationWillResignActiveNotification
|
||
|
object:nil];
|
||
|
//已经进入前台
|
||
|
[notificationCenter addObserver:self
|
||
|
selector:@selector(playerDidEnterForeground)
|
||
|
name:UIApplicationDidBecomeActiveNotification
|
||
|
object:nil];
|
||
|
//监测耳机
|
||
|
[notificationCenter addObserver:self
|
||
|
selector:@selector(playerAudioRouteChange:)
|
||
|
name:AVAudioSessionRouteChangeNotification
|
||
|
object:nil];
|
||
|
//监听播放器被打断(别的软件播放音乐,来电话)
|
||
|
[notificationCenter addObserver:self
|
||
|
selector:@selector(playerAudioBeInterrupted:)
|
||
|
name:AVAudioSessionInterruptionNotification
|
||
|
object:[AVAudioSession sharedInstance]];
|
||
|
}
|
||
|
|
||
|
- (void)playerWillResignActive{
|
||
|
_isBackground = YES;
|
||
|
_bgTaskId = [self backgroundPlayerID:_bgTaskId];
|
||
|
}
|
||
|
|
||
|
- (UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId {
|
||
|
// 设置并激活音频会话类别
|
||
|
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||
|
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||
|
[session setActive:YES error:nil];
|
||
|
// 允许应用程序接收远程控制
|
||
|
// 设置后台任务ID
|
||
|
UIBackgroundTaskIdentifier taskId = UIBackgroundTaskInvalid;
|
||
|
taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
|
||
|
if(taskId != UIBackgroundTaskInvalid &&
|
||
|
backTaskId != UIBackgroundTaskInvalid) {
|
||
|
[[UIApplication sharedApplication] endBackgroundTask:backTaskId];
|
||
|
}
|
||
|
return taskId;
|
||
|
}
|
||
|
|
||
|
- (void)playerDidEnterForeground{
|
||
|
_isBackground = NO;
|
||
|
}
|
||
|
|
||
|
- (void)playerAudioRouteChange:(NSNotification *)notification{
|
||
|
NSInteger routeChangeReason = [notification.userInfo[AVAudioSessionRouteChangeReasonKey] integerValue];
|
||
|
switch (routeChangeReason) {
|
||
|
case AVAudioSessionRouteChangeReasonNewDeviceAvailable://耳机插入
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isHeadphone:)]) {
|
||
|
[self.delegate player:self isHeadphone:YES];
|
||
|
}
|
||
|
break;
|
||
|
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable://耳机拔出,停止播放操作
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isHeadphone:)]) {
|
||
|
[self.delegate player:self isHeadphone:NO];
|
||
|
}else{
|
||
|
[self pause];
|
||
|
}
|
||
|
break;
|
||
|
case AVAudioSessionRouteChangeReasonCategoryChange:
|
||
|
//
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)playerAudioBeInterrupted:(NSNotification *)notification{
|
||
|
NSDictionary *dic = notification.userInfo;
|
||
|
if ([dic[AVAudioSessionInterruptionTypeKey] integerValue] == 1) {//打断开始
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isInterrupted:)]) {
|
||
|
[self.delegate player:self isInterrupted:YES];
|
||
|
}else{
|
||
|
[self pause];
|
||
|
}
|
||
|
}else {//打断结束
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isInterrupted:)]) {
|
||
|
[self.delegate player:self isInterrupted:NO];
|
||
|
}else{
|
||
|
if ([notification.userInfo[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == 1) {
|
||
|
[self play];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
-(void)playerDidPlayToEndTime:(NSNotification *)notification{
|
||
|
// 其他音视频播放结束也会触发回调,例如穿山甲的激励视频。
|
||
|
if (notification.object != self.playerItem) return;
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(playerDidPlayToEndTime:)]) {
|
||
|
[self.delegate playerDidPlayToEndTime:self];
|
||
|
}else{
|
||
|
_isNaturalToEndTime = YES;
|
||
|
[self next];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**远程线控*/
|
||
|
- (void)addRemoteControlHandler
|
||
|
{
|
||
|
if (@available (iOS 7.1, *)) {
|
||
|
kCodeSync([[UIApplication sharedApplication] endReceivingRemoteControlEvents]);
|
||
|
kCodeSync([[UIApplication sharedApplication] beginReceivingRemoteControlEvents]);
|
||
|
MPRemoteCommandCenter *center = [MPRemoteCommandCenter sharedCommandCenter];
|
||
|
[self addRemoteCommand:center.playCommand selector:@selector(play)];
|
||
|
[self addRemoteCommand:center.pauseCommand selector:@selector(pause)];
|
||
|
[self addRemoteCommand:center.previousTrackCommand selector:@selector(last)];
|
||
|
[self addRemoteCommand:center.nextTrackCommand selector:@selector(next)];
|
||
|
[center.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||
|
if ([WXYZ_Player sharedPlayer].state == TFBasicVoicePlayerStatePlaying) {
|
||
|
[[WXYZ_Player sharedPlayer] pause];
|
||
|
}else{
|
||
|
[[WXYZ_Player sharedPlayer] play];
|
||
|
}
|
||
|
return MPRemoteCommandHandlerStatusSuccess;
|
||
|
}];
|
||
|
|
||
|
if (@available (iOS 9.1,*)) {
|
||
|
[center.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||
|
MPChangePlaybackPositionCommandEvent *positionEvent = (MPChangePlaybackPositionCommandEvent *)event;
|
||
|
if (self.totalTime > 0) {
|
||
|
[self seekToTime:positionEvent.positionTime / self.totalTime completionBlock:nil];
|
||
|
}
|
||
|
return MPRemoteCommandHandlerStatusSuccess;
|
||
|
}];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)addRemoteCommand:(MPRemoteCommand *)command selector:(SEL)selector{
|
||
|
[command addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||
|
if ([self respondsToSelector:selector]) {
|
||
|
IMP imp = [self methodForSelector:selector];
|
||
|
void (*func)(id, SEL) = (void *)imp;
|
||
|
func(self, selector);
|
||
|
}
|
||
|
return MPRemoteCommandHandlerStatusSuccess;
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
- (void)setRemoteCenterPreviousEnable:(BOOL)remoteCenterPreviousEnable
|
||
|
{
|
||
|
_remoteCenterPreviousEnable = remoteCenterPreviousEnable;
|
||
|
[MPRemoteCommandCenter sharedCommandCenter].previousTrackCommand.enabled = remoteCenterPreviousEnable;
|
||
|
}
|
||
|
|
||
|
- (void)setRemoteCenterNextEnable:(BOOL)remoteCenterNextEnable
|
||
|
{
|
||
|
_remoteCenterNextEnable = remoteCenterNextEnable;
|
||
|
[MPRemoteCommandCenter sharedCommandCenter].nextTrackCommand.enabled = remoteCenterNextEnable;
|
||
|
}
|
||
|
|
||
|
#pragma mark - 数据源
|
||
|
|
||
|
- (void)reloadData{
|
||
|
if (self.dataSource && [self.dataSource respondsToSelector:@selector(audioDataForPlayer:)]) {
|
||
|
if (!self.playerModelArray) {
|
||
|
self.playerModelArray = [NSMutableArray array];
|
||
|
}
|
||
|
if (self.playerModelArray.count != 0) {
|
||
|
[self.playerModelArray removeAllObjects];
|
||
|
}
|
||
|
dispatch_group_enter(_dataGroupQueue);
|
||
|
dispatch_group_async(_dataGroupQueue, WXYZ_PlayerHighGlobalQueue, ^{
|
||
|
dispatch_async(WXYZ_PlayerHighGlobalQueue, ^{
|
||
|
|
||
|
[self.playerModelArray addObjectsFromArray:[self.dataSource audioDataForPlayer:self]];
|
||
|
|
||
|
//更新currentAudioId
|
||
|
if (self.currentAudioModel.audioUrl) {
|
||
|
[self.playerModelArray enumerateObjectsWithOptions:(NSEnumerationConcurrent) usingBlock:^(WXYZ_PlayerModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||
|
if ([obj.audioUrl.absoluteString isEqualToString:self.currentAudioModel.audioUrl.absoluteString]) {
|
||
|
self.currentAudioModel.audioId = idx;
|
||
|
*stop = YES;
|
||
|
}
|
||
|
}];
|
||
|
}
|
||
|
dispatch_group_leave(self->_dataGroupQueue);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma mark - 播放 IMPORTANT
|
||
|
|
||
|
- (void)playWithAudioId:(NSUInteger)audioId{
|
||
|
dispatch_group_notify(_dataGroupQueue, WXYZ_PlayerHighGlobalQueue, ^{
|
||
|
if (self.playerModelArray.count > audioId) {
|
||
|
self.currentAudioModel = self.playerModelArray[audioId];
|
||
|
[self audioPrePlay];
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
- (void)audioPrePlay{
|
||
|
[self reset];
|
||
|
|
||
|
if (self.dataSource && [self.dataSource respondsToSelector:@selector(audioInfoForPlayer:)]) {
|
||
|
self.currentAudioInfoModel = [self.dataSource audioInfoForPlayer:self];
|
||
|
}
|
||
|
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(playerAudioAddToPlayQueue:)]) {
|
||
|
[self.delegate playerAudioAddToPlayQueue:self];
|
||
|
}
|
||
|
|
||
|
[self loadPlayerItemWithURL:self.currentAudioModel.audioUrl];
|
||
|
}
|
||
|
|
||
|
- (void)loadPlayerItemWithURL:(NSURL *)URL{
|
||
|
self.playerItem = [[AVPlayerItem alloc] initWithURL:URL];
|
||
|
[self loadPlayer];
|
||
|
}
|
||
|
|
||
|
- (void)loadPlayerItemWithAsset:(AVURLAsset *)asset{
|
||
|
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
|
||
|
[self loadPlayer];
|
||
|
}
|
||
|
|
||
|
- (void)loadPlayer{
|
||
|
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
|
||
|
if (@available(iOS 10.0,*)) {
|
||
|
self.player.automaticallyWaitsToMinimizeStalling = NO;
|
||
|
}
|
||
|
|
||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||
|
// 处理章节开始播放,但监听还未执行的问题
|
||
|
[self play];
|
||
|
});
|
||
|
[self addProgressObserver];
|
||
|
[self addPlayingCenterInfo];
|
||
|
}
|
||
|
|
||
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
|
||
|
if (object == self.player.currentItem) {
|
||
|
if ([keyPath isEqualToString:WXYZStatusKey]) {
|
||
|
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
|
||
|
switch (status) {
|
||
|
case AVPlayerItemStatusUnknown:
|
||
|
self.state = TFBasicVoicePlayerStateStoped;
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||
|
[self.delegate playerStateChange:self.state];
|
||
|
}
|
||
|
break;
|
||
|
case AVPlayerItemStatusReadyToPlay:
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(playerReadyToPlay:)]) {
|
||
|
if (self.state != TFBasicVoicePlayerStatePause) {
|
||
|
[self.delegate playerReadyToPlay:self];
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case AVPlayerItemStatusFailed:
|
||
|
self.state = TFBasicVoicePlayerStateFail;
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||
|
[self.delegate playerStateChange:self.state];
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
} else if ([keyPath isEqualToString:WXYZLoadedTimeRangesKey]) {
|
||
|
[self addBufferProgressObserver];
|
||
|
} else if ([keyPath isEqualToString:WXYZPlaybackBufferEmptyKey]) {
|
||
|
|
||
|
} else if ([keyPath isEqualToString:WXYZPlaybackLikelyToKeepUpKey]) {
|
||
|
|
||
|
}
|
||
|
} else {
|
||
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma mark - 缓冲进度 播放进度 歌曲锁屏信息 音频跳转
|
||
|
|
||
|
- (void)addBufferProgressObserver{
|
||
|
self.totalTime = CMTimeGetSeconds(self.playerItem.asset.duration);
|
||
|
|
||
|
CMTimeRange timeRange = [self.playerItem.loadedTimeRanges.firstObject CMTimeRangeValue];
|
||
|
CGFloat startSeconds = CMTimeGetSeconds(timeRange.start);
|
||
|
CGFloat durationSeconds = CMTimeGetSeconds(timeRange.duration);
|
||
|
if (self.totalTime != 0) {//避免出现inf
|
||
|
self.bufferProgress = (startSeconds + durationSeconds) / self.totalTime;
|
||
|
}
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(player:bufferProgress:)]) {
|
||
|
[self.delegate player:self bufferProgress:self.bufferProgress];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)addProgressObserver
|
||
|
{
|
||
|
WS(weakSelf)
|
||
|
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0, 1.0) queue:nil usingBlock:^(CMTime time){
|
||
|
SS(sSelf)
|
||
|
AVPlayerItem *currentItem = sSelf.playerItem;
|
||
|
NSArray *loadedRanges = currentItem.seekableTimeRanges;
|
||
|
if (loadedRanges.count > 0 && currentItem.duration.timescale != 0){
|
||
|
|
||
|
if (sSelf.state != TFBasicVoicePlayerStatePlaying && sSelf.state != TFBasicVoicePlayerStatePause) {
|
||
|
sSelf.state = TFBasicVoicePlayerStatePlaying;
|
||
|
if (sSelf.delegate && [sSelf.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||
|
[sSelf.delegate playerStateChange:sSelf.state];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CGFloat currentT = (CGFloat)CMTimeGetSeconds(time);
|
||
|
sSelf.currentTime = currentT + (sSelf.totalTime > 1?1:0);
|
||
|
if (sSelf.totalTime != 0 && !isnan(sSelf.totalTime)) {// 避免出现inf
|
||
|
sSelf.progress = (CMTimeGetSeconds([currentItem currentTime]) + (sSelf.totalTime > 1?1:0)) / sSelf.totalTime;
|
||
|
}
|
||
|
|
||
|
if (sSelf.delegate && [sSelf.delegate respondsToSelector:@selector(player:progress:currentTime:totalTime:)]) {
|
||
|
[sSelf.delegate player:sSelf progress:sSelf.progress currentTime:currentT totalTime:sSelf.totalTime];
|
||
|
}
|
||
|
|
||
|
[sSelf updatePlayingCenterInfo];
|
||
|
}
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
- (void)addPlayingCenterInfo{
|
||
|
_remoteInfoDictionary = [NSMutableDictionary dictionary];
|
||
|
|
||
|
if (self.currentAudioInfoModel.audioName) {
|
||
|
_remoteInfoDictionary[MPMediaItemPropertyTitle] = self.currentAudioInfoModel.audioName;
|
||
|
}
|
||
|
if (self.currentAudioInfoModel.audioAlbum) {
|
||
|
_remoteInfoDictionary[MPMediaItemPropertyAlbumTitle] = self.currentAudioInfoModel.audioAlbum;
|
||
|
}
|
||
|
if (self.currentAudioInfoModel.audioSinger) {
|
||
|
_remoteInfoDictionary[MPMediaItemPropertyArtist] = self.currentAudioInfoModel.audioSinger;
|
||
|
}
|
||
|
if ([self.currentAudioInfoModel.audioImage isKindOfClass:[UIImage class]] && self.currentAudioInfoModel.audioImage) {
|
||
|
|
||
|
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:CGSizeMake(BOOK_WIDTH, BOOK_HEIGHT) requestHandler:^UIImage * _Nonnull(CGSize size) {
|
||
|
return self.currentAudioInfoModel.audioImage;
|
||
|
}];
|
||
|
|
||
|
_remoteInfoDictionary[MPMediaItemPropertyArtwork] = artwork;
|
||
|
}
|
||
|
_remoteInfoDictionary[MPNowPlayingInfoPropertyPlaybackRate] = [NSNumber numberWithFloat:1.0];
|
||
|
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = _remoteInfoDictionary;
|
||
|
}
|
||
|
|
||
|
- (void)updatePlayingCenterInfo{
|
||
|
if (!_isBackground) {return;}
|
||
|
|
||
|
if ([WXYZ_TouchAssistantView sharedManager].productionType == TFProductionTypeAudio) {
|
||
|
_remoteInfoDictionary[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithDouble:CMTimeGetSeconds(self.playerItem.currentTime)];
|
||
|
_remoteInfoDictionary[MPMediaItemPropertyPlaybackDuration] = [NSNumber numberWithDouble:CMTimeGetSeconds(self.playerItem.duration)];
|
||
|
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = _remoteInfoDictionary;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)seekToTime:(CGFloat)value completionBlock:(void (^)(void))completionBlock{
|
||
|
_isSeek = YES;
|
||
|
// 先暂停
|
||
|
BOOL resumePlay = NO;
|
||
|
if (self.state == TFBasicVoicePlayerStatePlaying || self.state == TFBasicVoicePlayerStateLoading) {
|
||
|
self.state = TFBasicVoicePlayerStatePause;
|
||
|
[self.player pause];
|
||
|
resumePlay = YES;
|
||
|
}
|
||
|
|
||
|
[self didSeekToTime:value resumePlay:resumePlay completionBlock:completionBlock];
|
||
|
}
|
||
|
|
||
|
- (void)didSeekToTime:(CGFloat)value resumePlay:(BOOL)resumePlay completionBlock:(void (^)(void))completionBlock
|
||
|
{
|
||
|
[self.player seekToTime:CMTimeMake(floorf(self.totalTime * value), 1)
|
||
|
toleranceBefore:kCMTimeZero
|
||
|
toleranceAfter:kCMTimeZero
|
||
|
completionHandler:^(BOOL finished) {
|
||
|
if (finished) {
|
||
|
self->_isSeek = NO;
|
||
|
[self play];
|
||
|
if (!resumePlay) {
|
||
|
[self pause];
|
||
|
}
|
||
|
if (completionBlock) {
|
||
|
completionBlock();
|
||
|
}
|
||
|
}
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
/**倍速播放*/
|
||
|
- (void)setRate:(CGFloat)rate {
|
||
|
_playRate = rate;
|
||
|
for (AVPlayerItemTrack *track in self.playerItem.tracks){
|
||
|
if ([track.assetTrack.mediaType isEqual:AVMediaTypeAudio]){
|
||
|
track.enabled = YES;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (self.state == TFBasicVoicePlayerStatePlaying) {
|
||
|
self.player.rate = rate;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**释放播放器*/
|
||
|
- (void)deallocPlayer{
|
||
|
|
||
|
[self reset];
|
||
|
|
||
|
self.state = TFBasicVoicePlayerStateStoped;
|
||
|
|
||
|
if (@available(iOS 7.1, *)) {
|
||
|
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
|
||
|
MPRemoteCommandCenter *center = [MPRemoteCommandCenter sharedCommandCenter];
|
||
|
[[center playCommand] removeTarget:self];
|
||
|
[[center pauseCommand] removeTarget:self];
|
||
|
[[center nextTrackCommand] removeTarget:self];
|
||
|
[[center previousTrackCommand] removeTarget:self];
|
||
|
[[center togglePlayPauseCommand] removeTarget:self];
|
||
|
if(@available(iOS 9.1, *)) {
|
||
|
[center.changePlaybackPositionCommand removeTarget:self];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_isOtherPlaying) {
|
||
|
[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
|
||
|
}else{
|
||
|
[[AVAudioSession sharedInstance] setActive:NO error:nil];
|
||
|
}
|
||
|
[self.player.currentItem cancelPendingSeeks];
|
||
|
[self.player.currentItem.asset cancelLoading];
|
||
|
|
||
|
if (self.playerModelArray) {
|
||
|
self.playerModelArray = nil;
|
||
|
}
|
||
|
|
||
|
if (self.playerItem) {
|
||
|
self.playerItem = nil;
|
||
|
}
|
||
|
|
||
|
if (self.player) {
|
||
|
self.player = nil;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)reset{
|
||
|
if (self.state == TFBasicVoicePlayerStatePlaying) {
|
||
|
[self.player pause];
|
||
|
}
|
||
|
|
||
|
//移除进度观察者
|
||
|
if (self.timeObserver) {
|
||
|
[self.player removeTimeObserver:self.timeObserver];
|
||
|
self.timeObserver = nil;
|
||
|
}
|
||
|
|
||
|
//重置
|
||
|
self.progress = .0f;
|
||
|
self.bufferProgress = .0f;
|
||
|
self.currentTime = .0f;
|
||
|
self.totalTime = .0f;
|
||
|
}
|
||
|
|
||
|
#pragma mark - 播放 暂停 下一首 上一首
|
||
|
/**播放*/
|
||
|
- (void)play
|
||
|
{
|
||
|
[self.player play];
|
||
|
self.player.rate = self.playRate;
|
||
|
self.state = TFBasicVoicePlayerStateLoading;
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||
|
[self.delegate playerStateChange:self.state];
|
||
|
}
|
||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||
|
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
|
||
|
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**暂停*/
|
||
|
- (void)pause
|
||
|
{
|
||
|
[self.player pause];
|
||
|
self.state = TFBasicVoicePlayerStatePause;
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||
|
[self.delegate playerStateChange:self.state];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**下一首*/
|
||
|
- (void)next
|
||
|
{
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerRemoteCenterSwitchToNext)]) {
|
||
|
[self.delegate audioPlayerRemoteCenterSwitchToNext];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**上一首*/
|
||
|
- (void)last
|
||
|
{
|
||
|
if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerRemoteCenterSwitchToPrevious)]) {
|
||
|
[self.delegate audioPlayerRemoteCenterSwitchToPrevious];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma mark - setter
|
||
|
|
||
|
- (void)setCategory:(AVAudioSessionCategory)category{
|
||
|
[[AVAudioSession sharedInstance] setCategory:category error:nil];
|
||
|
}
|
||
|
|
||
|
- (void)setPlayerItem:(AVPlayerItem *)playerItem{
|
||
|
if (_playerItem == playerItem) {
|
||
|
return;
|
||
|
}
|
||
|
if (_playerItem) {
|
||
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||
|
[_playerItem removeObserver:self forKeyPath:WXYZStatusKey];
|
||
|
[_playerItem removeObserver:self forKeyPath:WXYZLoadedTimeRangesKey];
|
||
|
[_playerItem removeObserver:self forKeyPath:WXYZPlaybackBufferEmptyKey];
|
||
|
[_playerItem removeObserver:self forKeyPath:WXYZPlaybackLikelyToKeepUpKey];
|
||
|
}
|
||
|
_playerItem = playerItem;
|
||
|
if (playerItem) {
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerDidPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||
|
[playerItem addObserver:self forKeyPath:WXYZStatusKey options:NSKeyValueObservingOptionNew context:nil];
|
||
|
[playerItem addObserver:self forKeyPath:WXYZLoadedTimeRangesKey options:NSKeyValueObservingOptionNew context:nil];
|
||
|
[playerItem addObserver:self forKeyPath:WXYZPlaybackBufferEmptyKey options:NSKeyValueObservingOptionNew context:nil];
|
||
|
[playerItem addObserver:self forKeyPath:WXYZPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:nil];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@end
|