|
@@ -8,6 +8,7 @@
|
|
|
|
|
|
#import "MKRAVPlayer.h"
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
+#import <IJKMediaFramework/IJKMediaFramework.h>
|
|
|
|
|
|
inline static bool isFloatZero(float value)
|
|
|
{
|
|
@@ -36,6 +37,9 @@ static void * PlayerStatusObservationContext = &PlayerStatusObservationContext;
|
|
|
@property (nonatomic, assign) NSTimeInterval seekTime;//快进或者快退的目标时间
|
|
|
@property (nonatomic, strong) NSMutableArray *resourcePlayList;//MKRRadioResorce播放列表
|
|
|
@property (nonatomic, assign) BOOL initByUrlPlayList;//是字符串播放列表
|
|
|
+@property (nonatomic, strong) id<IJKMediaPlayback> ijkPlayer;
|
|
|
+@property (nonatomic, assign) BOOL isIjkPlayerMode;
|
|
|
+@property (nonatomic, assign) BOOL isInstallIjkObservers;
|
|
|
@end
|
|
|
|
|
|
@implementation MKRAVPlayer
|
|
@@ -148,16 +152,32 @@ NSURL * MKRUrlWithString(NSString *string){
|
|
|
[self preparePlay];
|
|
|
}else{
|
|
|
if (self.state == MKRAVPlayerStateStopped || self.state == MKRAVPlayerStatePause) {
|
|
|
- [self.player play];
|
|
|
+ if (self.isIjkPlayerMode){
|
|
|
+ [self.ijkPlayer prepareToPlay];
|
|
|
+ [self.ijkPlayer play];
|
|
|
+ }else{
|
|
|
+ [self.player play];
|
|
|
+ }
|
|
|
self.state = MKRAVPlayerStatePlaying;
|
|
|
}else if (self.state == MKRAVPlayerStateBuffering){
|
|
|
- [self.player play];
|
|
|
+ if (self.isIjkPlayerMode){
|
|
|
+ [self.ijkPlayer prepareToPlay];
|
|
|
+ [self.ijkPlayer play];
|
|
|
+ }else{
|
|
|
+ [self.player play];
|
|
|
+ }
|
|
|
self.state = MKRAVPlayerStatePlaying;
|
|
|
}else if (self.state == MKRAVPlayerStatePlaying){
|
|
|
- if (self.player && !self.player.rate) {
|
|
|
- [self.player play];
|
|
|
- self.state = MKRAVPlayerStatePlaying;
|
|
|
+ if (self.isIjkPlayerMode && self.ijkPlayer && !self.player.rate){
|
|
|
+ [self.ijkPlayer prepareToPlay];
|
|
|
+ [self.ijkPlayer play];
|
|
|
+ }else{
|
|
|
+ if (self.player && !self.player.rate) {
|
|
|
+ [self.player play];
|
|
|
+ }
|
|
|
}
|
|
|
+ self.state = MKRAVPlayerStatePlaying;
|
|
|
+
|
|
|
}else if (self.state == MKRAVPlayerStateFinished){
|
|
|
// NSLog(@"播放完成");
|
|
|
}
|
|
@@ -250,18 +270,32 @@ NSURL * MKRUrlWithString(NSString *string){
|
|
|
}
|
|
|
|
|
|
- (void)pause{
|
|
|
- [self.player pause];
|
|
|
+ if (self.isIjkPlayerMode){
|
|
|
+ [self.ijkPlayer pause];
|
|
|
+ }else{
|
|
|
+ [self.player pause];
|
|
|
+ }
|
|
|
if (self.state == MKRAVPlayerStatePlaying) {
|
|
|
self.state = MKRAVPlayerStatePause;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)replayCurrentResource{
|
|
|
- [self resetPlayer];
|
|
|
+ if (self.isIjkPlayerMode){
|
|
|
+ [self destroyIjkPlayer];
|
|
|
+ }else{
|
|
|
+ [self resetPlayer];
|
|
|
+ }
|
|
|
[self playAtIndex:self.currentIndex];
|
|
|
}
|
|
|
|
|
|
- (void)resetPlayer{
|
|
|
+ if (self.isIjkPlayerMode){
|
|
|
+ [self destroyIjkPlayer];
|
|
|
+ }
|
|
|
+ if (!self.player){
|
|
|
+ return;
|
|
|
+ }
|
|
|
if ([self.delegate respondsToSelector:@selector(willChangePlayResouce:)]) {
|
|
|
[self.delegate willChangePlayResouce:self.currentResource];
|
|
|
}
|
|
@@ -316,23 +350,34 @@ NSURL * MKRUrlWithString(NSString *string){
|
|
|
[self registerNotification];
|
|
|
if (self.playerItem) {
|
|
|
[self resetPlayer];
|
|
|
- self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
|
|
|
+ if (@available(iOS 16.0, *) && self.currentResource.sourceType == MKRSourceTypeBroadcast) {
|
|
|
+ [self createIjkPlayer];
|
|
|
+ }else{
|
|
|
+ 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 resetPlayer];
|
|
|
+ if (@available(iOS 16.0, *) && self.currentResource.sourceType == MKRSourceTypeBroadcast) {
|
|
|
+ [self createIjkPlayer];
|
|
|
+ }else{
|
|
|
+ self.isIjkPlayerMode = NO;
|
|
|
+ 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, *)) {
|
|
@@ -341,6 +386,62 @@ NSURL * MKRUrlWithString(NSString *string){
|
|
|
self.state = MKRAVPlayerStateBuffering;
|
|
|
}
|
|
|
|
|
|
+- (void)createIjkPlayer{
|
|
|
+ [self destroyIjkPlayer];
|
|
|
+ IJKFFOptions *options = [IJKFFOptions optionsByDefault];
|
|
|
+ self.ijkPlayer = [[IJKFFMoviePlayerController alloc] initWithContentURL:self.playUrl withOptions:options];
|
|
|
+ [self.ijkPlayer prepareToPlay];
|
|
|
+ self.isInitPlayer = YES;
|
|
|
+ self.isIjkPlayerMode = YES;
|
|
|
+ if (self.isInstallIjkObservers){
|
|
|
+ [self removeIjkMovieNotificationObservers];
|
|
|
+ }
|
|
|
+ [self installIjkMovieNotificationObservers];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)destroyIjkPlayer{
|
|
|
+ if (self.ijkPlayer){
|
|
|
+ [self.ijkPlayer shutdown];
|
|
|
+ [self.ijkPlayer.view removeFromSuperview];
|
|
|
+ self.ijkPlayer = nil;
|
|
|
+ [self removeIjkMovieNotificationObservers];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+-(void)installIjkMovieNotificationObservers
|
|
|
+{
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
+ selector:@selector(loadStateDidChange:)
|
|
|
+ name:IJKMPMoviePlayerLoadStateDidChangeNotification
|
|
|
+ object:self.ijkPlayer];
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
+ selector:@selector(moviePlayBackDidFinish:)
|
|
|
+ name:IJKMPMoviePlayerPlaybackDidFinishNotification
|
|
|
+ object:self.ijkPlayer];
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
+ selector:@selector(mediaIsPreparedToPlayDidChange:)
|
|
|
+ name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification
|
|
|
+ object:self.ijkPlayer];
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
+ selector:@selector(moviePlayBackStateDidChange:)
|
|
|
+ name:IJKMPMoviePlayerPlaybackStateDidChangeNotification
|
|
|
+ object:self.ijkPlayer];
|
|
|
+ self.isInstallIjkObservers = YES;
|
|
|
+}
|
|
|
+
|
|
|
+-(void)removeIjkMovieNotificationObservers
|
|
|
+{
|
|
|
+ [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerLoadStateDidChangeNotification object:_player];
|
|
|
+ [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerPlaybackDidFinishNotification object:_player];
|
|
|
+ [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification object:_player];
|
|
|
+ [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerPlaybackStateDidChangeNotification object:_player];
|
|
|
+ self.isInstallIjkObservers = NO;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
- (void)didPrepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys{
|
|
|
for (NSString *thisKey in requestedKeys) {
|
|
|
NSError *error = nil;
|
|
@@ -529,6 +630,98 @@ NSURL * MKRUrlWithString(NSString *string){
|
|
|
return _currentStatus;
|
|
|
}
|
|
|
|
|
|
+- (void)loadStateDidChange:(NSNotification*)notification
|
|
|
+{
|
|
|
+ // MPMovieLoadStateUnknown = 0,
|
|
|
+ // MPMovieLoadStatePlayable = 1 << 0,
|
|
|
+ // MPMovieLoadStatePlaythroughOK = 1 << 1, // Playback will be automatically started in this state when shouldAutoplay is YES
|
|
|
+ // MPMovieLoadStateStalled = 1 << 2, // Playback will be automatically paused in this state, if started
|
|
|
+
|
|
|
+ IJKMPMovieLoadState loadState = self.ijkPlayer.loadState;
|
|
|
+
|
|
|
+ if ((loadState & IJKMPMovieLoadStatePlaythroughOK) != 0) {
|
|
|
+ NSLog(@"loadStateDidChange: IJKMPMovieLoadStatePlaythroughOK: %d\n", (int)loadState);
|
|
|
+ [self.ijkPlayer play];
|
|
|
+ } else if ((loadState & IJKMPMovieLoadStatePlayable) != 0) {
|
|
|
+ NSLog(@"loadStateDidChange: IJKMPMovieLoadStatePlayable: %d\n", (int)loadState);
|
|
|
+ [self.ijkPlayer play];
|
|
|
+ }else if ((loadState & IJKMPMovieLoadStateStalled) != 0) {
|
|
|
+ NSLog(@"loadStateDidChange: IJKMPMovieLoadStateStalled: %d\n", (int)loadState);
|
|
|
+ self.state = MKRAVPlayerStateBuffering;
|
|
|
+ } else {
|
|
|
+ NSLog(@"loadStateDidChange: ???: %d\n", (int)loadState);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)moviePlayBackDidFinish:(NSNotification*)notification
|
|
|
+{
|
|
|
+ int reason = [[[notification userInfo] valueForKey:IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
|
|
|
+
|
|
|
+ switch (reason)
|
|
|
+ {
|
|
|
+ case IJKMPMovieFinishReasonPlaybackEnded:
|
|
|
+ [self moviePlayDidEnd:self.ijkPlayer];
|
|
|
+ break;
|
|
|
+
|
|
|
+ case IJKMPMovieFinishReasonUserExited:
|
|
|
+ break;
|
|
|
+
|
|
|
+ case IJKMPMovieFinishReasonPlaybackError:
|
|
|
+ [self failedToPlayToEndTimeError:self.ijkPlayer];
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)mediaIsPreparedToPlayDidChange:(NSNotification*)notification
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+- (void)moviePlayBackStateDidChange:(NSNotification*)notification
|
|
|
+{
|
|
|
+ // MPMoviePlaybackStateStopped,
|
|
|
+ // MPMoviePlaybackStatePlaying,
|
|
|
+ // MPMoviePlaybackStatePaused,
|
|
|
+ // MPMoviePlaybackStateInterrupted,
|
|
|
+ // MPMoviePlaybackStateSeekingForward,
|
|
|
+ // MPMoviePlaybackStateSeekingBackward
|
|
|
+
|
|
|
+ switch (self.ijkPlayer.playbackState)
|
|
|
+ {
|
|
|
+ case IJKMPMoviePlaybackStateStopped: {
|
|
|
+ NSLog(@"IJKMPMoviePlayBackStateDidChange %d: stoped", (int)self.ijkPlayer.playbackState);
|
|
|
+ self.state = MKRAVPlayerStateStopped;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case IJKMPMoviePlaybackStatePlaying: {
|
|
|
+ NSLog(@"IJKMPMoviePlayBackStateDidChange %d: playing", (int)self.ijkPlayer.playbackState);
|
|
|
+ self.state = MKRAVPlayerStatePlaying;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case IJKMPMoviePlaybackStatePaused: {
|
|
|
+ NSLog(@"IJKMPMoviePlayBackStateDidChange %d: paused", (int)self.ijkPlayer.playbackState);
|
|
|
+ self.state = MKRAVPlayerStatePause;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case IJKMPMoviePlaybackStateInterrupted: {
|
|
|
+ NSLog(@"IJKMPMoviePlayBackStateDidChange %d: interrupted", (int)self.ijkPlayer.playbackState);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case IJKMPMoviePlaybackStateSeekingForward:
|
|
|
+ case IJKMPMoviePlaybackStateSeekingBackward: {
|
|
|
+ NSLog(@"IJKMPMoviePlayBackStateDidChange %d: seeking", (int)self.ijkPlayer.playbackState);
|
|
|
+ self.state = MKRAVPlayerStateBuffering;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default: {
|
|
|
+ NSLog(@"IJKMPMoviePlayBackStateDidChange %d: unknown", (int)self.ijkPlayer.playbackState);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#pragma mark - KVO
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
|
|
|
if (context != PlayerStatusObservationContext) {
|