From 621d00ea8547a5c2ddcb4b71ee3bddd27761d9b0 Mon Sep 17 00:00:00 2001 From: Maxym Deygin Date: Thu, 20 Jul 2017 15:12:37 +0300 Subject: [PATCH 1/5] added public getter for avPlayer Instance added proxy to mute player --- PersistentStreamPlayer.h | 3 +++ PersistentStreamPlayer.m | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/PersistentStreamPlayer.h b/PersistentStreamPlayer.h index 0e8fe49..52c84c5 100644 --- a/PersistentStreamPlayer.h +++ b/PersistentStreamPlayer.h @@ -24,6 +24,9 @@ @property (nonatomic, assign) BOOL looping; @property (nonatomic, readonly) BOOL playing; @property (nonatomic, assign) float volume; +@property (nonatomic, assign) BOOL muted; + +@property (nonatomic, readonly, nullable) AVPlayer *player; - (void)play; - (void)pause; diff --git a/PersistentStreamPlayer.m b/PersistentStreamPlayer.m index d61de1e..930512c 100644 --- a/PersistentStreamPlayer.m +++ b/PersistentStreamPlayer.m @@ -229,6 +229,17 @@ - (void)setVolume:(float)volume } } + +- (void)setMuted:(BOOL)muted +{ + if (self.loopingLocalAudioPlayer) { + self.loopingLocalAudioPlayer.muted = muted; + } else if (self.player) { + self.player.muted = muted; + } +} + + #pragma mark - AVURLAsset resource loading - (void)processPendingRequests { From 1b2c53ae8e427fbeea727073e3a0f199b2a1ae05 Mon Sep 17 00:00:00 2001 From: Maxym Deygin Date: Thu, 20 Jul 2017 16:19:36 +0300 Subject: [PATCH 2/5] =?UTF-8?q?audioPlayer=20can=E2=80=99t=20be=20muted(yo?= =?UTF-8?q?u=20don=E2=80=99t=20say=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PersistentStreamPlayer.m | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/PersistentStreamPlayer.m b/PersistentStreamPlayer.m index 930512c..298903a 100644 --- a/PersistentStreamPlayer.m +++ b/PersistentStreamPlayer.m @@ -232,11 +232,7 @@ - (void)setVolume:(float)volume - (void)setMuted:(BOOL)muted { - if (self.loopingLocalAudioPlayer) { - self.loopingLocalAudioPlayer.muted = muted; - } else if (self.player) { - self.player.muted = muted; - } + self.player.muted = muted; } From 372b33c85b86efeb1d946414c424a7994154a723 Mon Sep 17 00:00:00 2001 From: Maxym Deygin Date: Thu, 20 Jul 2017 16:23:53 +0300 Subject: [PATCH 3/5] muted getter --- PersistentStreamPlayer.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PersistentStreamPlayer.m b/PersistentStreamPlayer.m index 298903a..db09aae 100644 --- a/PersistentStreamPlayer.m +++ b/PersistentStreamPlayer.m @@ -229,13 +229,16 @@ - (void)setVolume:(float)volume } } +- (BOOL)muted +{ + return self.player.muted; +} - (void)setMuted:(BOOL)muted { self.player.muted = muted; } - #pragma mark - AVURLAsset resource loading - (void)processPendingRequests { From 326504d5543322513133e917bfd8dc9b27148514 Mon Sep 17 00:00:00 2001 From: IntMatrix Date: Mon, 14 Aug 2017 18:09:54 +0300 Subject: [PATCH 4/5] video streaming fixed --- PersistentStreamPlayer.m | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/PersistentStreamPlayer.m b/PersistentStreamPlayer.m index db09aae..474970b 100644 --- a/PersistentStreamPlayer.m +++ b/PersistentStreamPlayer.m @@ -13,6 +13,7 @@ @interface PersistentStreamPlayer () Date: Thu, 17 Aug 2017 12:03:23 +0300 Subject: [PATCH 5/5] resume connection added. refactoring performed. --- PersistentStreamPlayer.h | 2 + PersistentStreamPlayer.m | 349 +++++++++++++++++++++++---------------- 2 files changed, 211 insertions(+), 140 deletions(-) diff --git a/PersistentStreamPlayer.h b/PersistentStreamPlayer.h index 52c84c5..acb4de5 100644 --- a/PersistentStreamPlayer.h +++ b/PersistentStreamPlayer.h @@ -43,4 +43,6 @@ */ - (void)seekToTime:(NSTimeInterval)time; +- (void)resumeConnection; + @end diff --git a/PersistentStreamPlayer.m b/PersistentStreamPlayer.m index 474970b..414ddbc 100644 --- a/PersistentStreamPlayer.m +++ b/PersistentStreamPlayer.m @@ -34,6 +34,8 @@ @interface PersistentStreamPlayer () 0) { + NSString *range = [NSString stringWithFormat:@"bytes=%i-", self.loadedAudioDataLength]; + [request setValue:range forHTTPHeaderField:@"Range"]; + } + + self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; + [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; + [self.connection start]; + + self.isResumed = YES; } - (void)appendDataToTempFile:(NSData *)data @@ -191,6 +193,16 @@ - (void)appendDataToTempFile:(NSData *)data } } +#pragma mark - Getters & Setters + +- (BOOL)playing +{ + if (self.loopingLocalAudioPlayer) { + return self.loopingLocalAudioPlayer.playing; + } + return self.player.rate != 0 && !self.player.error; +} + - (BOOL)tempFileExists { return [[NSFileManager defaultManager] fileExistsAtPath:self.tempURL.path]; @@ -208,16 +220,16 @@ - (NSURL *)currentURLForDataFile return self.connectionHasFinishedLoading ? self.localURL : self.tempURL; } -- (void)connectionDidFinishLoading:(NSURLConnection *)connection +- (NSURL *)audioRemoteStreamingURL { - self.connectionHasFinishedLoading = YES; - - [self processPendingRequests]; - [FileUtils moveFileFromURL:self.tempURL toURL:self.localURL]; - - if ([self.delegate respondsToSelector:@selector(persistentStreamPlayerDidPersistAsset:)]) { - [self.delegate persistentStreamPlayerDidPersistAsset:self]; + if (!self.remoteURL) { + return nil; } + + NSURLComponents *components = [[NSURLComponents alloc] initWithURL:self.remoteURL resolvingAgainstBaseURL:NO]; + self.originalURLScheme = components.scheme; + components.scheme = @"streaming"; + return components.URL; } - (float)volume @@ -244,7 +256,39 @@ - (void)setMuted:(BOOL)muted self.player.muted = muted; } +- (BOOL)isAssetLoaded +{ + AVKeyValueStatus durationStatus = [self.player.currentItem.asset statusOfValueForKey:@"duration" error:NULL]; + return durationStatus == AVKeyValueStatusLoaded && self.player.status == AVPlayerStatusReadyToPlay; +} + +- (NSTimeInterval)duration +{ + if (!self.isAssetLoaded) { + return self.fullAudioDataLength; + /* + return 5 * 60.0; // give it a good guess of 5 min before asset loads... + */ + } + return CMTimeGetSeconds(self.player.currentItem.asset.duration); +} + +- (NSTimeInterval)timeBuffered +{ + CMTimeRange timeRange = [[self.player.currentItem.loadedTimeRanges lastObject] CMTimeRangeValue]; + return CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); +} + +- (NSTimeInterval)currentTime +{ + if (self.loopingLocalAudioPlayer) { + return self.loopingLocalAudioPlayer.currentTime; + } + return CMTimeGetSeconds(self.player.currentTime); +} + #pragma mark - AVURLAsset resource loading + - (void)processPendingRequests { NSMutableArray *requestsCompleted = [NSMutableArray array]; @@ -334,18 +378,56 @@ - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingR [self.pendingRequests removeObject:loadingRequest]; } -#pragma mark - KVO +#pragma mark - Observing + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) { + if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay + && self.playing) { [self.player play]; } } -#pragma mark - health check timer +- (void)addObservers +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:[self.player currentItem]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemDidStall:) + name:AVPlayerItemPlaybackStalledNotification + object:[self.player currentItem]]; +} + +- (void)removeObservers +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - AVPlayerItem + +- (void)playerItemDidStall:(NSNotification *)notification +{ + self.isStalled = YES; + + if ([self.delegate respondsToSelector:@selector(persistentStreamPlayerStreamingDidStall:)]) { + [self.delegate persistentStreamPlayerStreamingDidStall:self]; + } +} + +- (void)playerItemDidReachEnd:(NSNotification *)notification +{ + if ([self.delegate respondsToSelector:@selector(persistentStreamPlayerDidFinishPlaying:)]) { + [self.delegate persistentStreamPlayerDidFinishPlaying:self]; + } + [self stopHealthCheckTimer]; + [self tryToStartLocalLoop]; +} + - (void)startHealthCheckTimer { if (self.healthCheckTimer) { @@ -397,15 +479,6 @@ - (void)tryToPlayIfStalled } } -- (void)playerItemDidReachEnd:(NSNotification *)notification -{ - if ([self.delegate respondsToSelector:@selector(persistentStreamPlayerDidFinishPlaying:)]) { - [self.delegate persistentStreamPlayerDidFinishPlaying:self]; - } - [self stopHealthCheckTimer]; - [self tryToStartLocalLoop]; -} - - (void)tryToStartLocalLoop { if (!self.looping) { @@ -420,32 +493,12 @@ - (void)tryToStartLocalLoop [self.loopingLocalAudioPlayer play]; } -- (void)playerItemDidStall:(NSNotification *)notification -{ - self.isStalled = YES; - - if ([self.delegate respondsToSelector:@selector(persistentStreamPlayerStreamingDidStall:)]) { - [self.delegate persistentStreamPlayerStreamingDidStall:self]; - } -} - -#pragma mark - asset and duration -- (NSTimeInterval)duration -{ - if (!self.isAssetLoaded) { - return self.fullAudioDataLength; - /* - return 5 * 60.0; // give it a good guess of 5 min before asset loads... - */ - } - return CMTimeGetSeconds(self.player.currentItem.asset.duration); -} - - (void)loadAssetIfNecessary { if (self.hasForcedDurationLoad) { return; } + if (self.isAssetLoaded) { return; } @@ -463,40 +516,56 @@ - (void)loadAssetIfNecessary } } -- (BOOL)isAssetLoaded -{ - AVKeyValueStatus durationStatus = [self.player.currentItem.asset statusOfValueForKey:@"duration" error:NULL]; - return durationStatus == AVKeyValueStatusLoaded && self.player.status == AVPlayerStatusReadyToPlay; -} - - (void)forceLoadOfDuration { + __weak typeof(self) weakSelf = self; [self.player.currentItem.asset loadValuesAsynchronouslyForKeys:@[@"duration"] - completionHandler:^{ - if (self.isAssetLoaded) { - if ([self.delegate respondsToSelector:@selector(persistentStreamPlayerDidLoadAsset:)]) { - [self.delegate persistentStreamPlayerDidLoadAsset:self]; - } - } else { - if ([self.delegate respondsToSelector:@selector(persistentStreamPlayerDidFailToLoadAsset:)]) { - [self.delegate persistentStreamPlayerDidFailToLoadAsset:self]; - } - } - }]; -} + completionHandler:^{ + if(weakSelf) { + PersistentStreamPlayer* strongSelf = weakSelf; + if (strongSelf.isAssetLoaded) { + if ([strongSelf.delegate respondsToSelector:@selector(persistentStreamPlayerDidLoadAsset:)]) { + [strongSelf.delegate persistentStreamPlayerDidLoadAsset:strongSelf]; + } + } else { + if ([strongSelf.delegate respondsToSelector:@selector(persistentStreamPlayerDidFailToLoadAsset:)]) { + [strongSelf.delegate persistentStreamPlayerDidFailToLoadAsset:strongSelf]; + } + } + } + }]; +} + +#pragma mark - Memory management -- (NSTimeInterval)timeBuffered +- (void)dealloc { - CMTimeRange timeRange = [[self.player.currentItem.loadedTimeRanges lastObject] CMTimeRangeValue]; - return CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); + [self destroy]; } -- (NSTimeInterval)currentTime +- (void)destroy { - if (self.loopingLocalAudioPlayer) { - return self.loopingLocalAudioPlayer.currentTime; + if (self.isDestroyed) { + return; } - return CMTimeGetSeconds(self.player.currentTime); + self.isDestroyed = YES; + + [self removeObservers]; + + [self stopHealthCheckTimer]; + [self.player pause]; + + [self.player.currentItem removeObserver:self forKeyPath:@"status"]; + [self.player.currentItem cancelPendingSeeks]; + [self.player.currentItem.asset cancelLoading]; + self.player.rate = 0.0; + self.player = nil; + + [self.connection cancel]; + self.connection = nil; + + [self.loopingLocalAudioPlayer stop]; + self.loopingLocalAudioPlayer = nil; } @end