Skip to content

Commit

Permalink
Fix iOS caching + add iOS feature: preCaching (#670)
Browse files Browse the repository at this point in the history
* Fix iOS caching + add iOS feature: preCaching

* Added required changes

* Fix compile issues
  • Loading branch information
themadmrj authored Sep 7, 2021
1 parent a0ec2db commit 57a04e0
Show file tree
Hide file tree
Showing 8 changed files with 506 additions and 91 deletions.
7 changes: 4 additions & 3 deletions ios/Classes/BetterPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
#import <Flutter/Flutter.h>
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
#import <KTVHTTPCache/KTVHTTPCache.h>
#import <GLKit/GLKit.h>
#import "BetterPlayerTimeUtils.h"
#import "BetterPlayerView.h"
#import "BetterPlayerEzDrmAssetsLoaderDelegate.h"

NS_ASSUME_NONNULL_BEGIN

@class CacheManager;

@interface BetterPlayer : NSObject <FlutterPlatformView, FlutterStreamHandler, AVPictureInPictureControllerDelegate>
@property(readonly, nonatomic) AVPlayer* player;
@property(readonly, nonatomic) BetterPlayerEzDrmAssetsLoaderDelegate* loaderDelegate;
Expand Down Expand Up @@ -44,8 +45,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithFrame:(CGRect)frame;
- (void)setMixWithOthers:(bool)mixWithOthers;
- (void)seekTo:(int)location;
- (void)setDataSourceAsset:(NSString*)asset withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl overriddenDuration:(int) overriddenDuration;
- (void)setDataSourceURL:(NSURL*)url withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl withHeaders:(NSDictionary*)headers withCache:(BOOL)useCache overriddenDuration:(int) overriddenDuration;
- (void)setDataSourceAsset:(NSString*)asset withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl cacheKey:(NSString*)cacheKey cacheManager:(CacheManager*)cacheManager overriddenDuration:(int) overriddenDuration;
- (void)setDataSourceURL:(NSURL*)url withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl withHeaders:(NSDictionary*)headers withCache:(BOOL)useCache cacheKey:(NSString*)cacheKey cacheManager:(CacheManager*)cacheManager overriddenDuration:(int) overriddenDuration;
- (void)setVolume:(double)volume;
- (void)setSpeed:(double)speed result:(FlutterResult)result;
- (void) setAudioTrack:(NSString*) name index:(int) index;
Expand Down
87 changes: 43 additions & 44 deletions ios/Classes/BetterPlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#import "BetterPlayer.h"
#import <better_player/better_player-Swift.h>

static void* timeRangeContext = &timeRangeContext;
static void* statusContext = &statusContext;
Expand Down Expand Up @@ -76,11 +77,11 @@ - (void)clear {
if (_player.currentItem == nil) {
return;
}

if (_player.currentItem == nil) {
return;
}

[self removeObservers];
AVAsset* asset = [_player.currentItem asset];
[asset cancelLoading];
Expand Down Expand Up @@ -116,7 +117,7 @@ - (void)itemDidPlayToEndTime:(NSNotification*)notification {
if (_eventSink) {
_eventSink(@{@"event" : @"completed", @"key" : _key});
[ self removeObservers];

}
}
}
Expand All @@ -143,11 +144,11 @@ - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransfor
[AVMutableVideoCompositionLayerInstruction
videoCompositionLayerInstructionWithAssetTrack:videoTrack];
[layerInstruction setTransform:_preferredTransform atTime:kCMTimeZero];

AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition];
instruction.layerInstructions = @[ layerInstruction ];
videoComposition.instructions = @[ instruction ];

// If in portrait mode, switch the width and height of the video
CGFloat width = videoTrack.naturalSize.width;
CGFloat height = videoTrack.naturalSize.height;
Expand All @@ -158,11 +159,11 @@ - (AVMutableVideoComposition*)getVideoCompositionWithTransform:(CGAffineTransfor
height = videoTrack.naturalSize.width;
}
videoComposition.renderSize = CGSizeMake(width, height);

// TODO(@recastrodiaz): should we use videoTrack.nominalFrameRate ?
// Currently set at a constant 30 FPS
videoComposition.frameDuration = CMTimeMake(1, 30);

return videoComposition;
}

Expand All @@ -187,41 +188,39 @@ - (CGAffineTransform)fixTransform:(AVAssetTrack*)videoTrack {
return transform;
}

- (void)setDataSourceAsset:(NSString*)asset withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl overriddenDuration:(int) overriddenDuration{
- (void)setDataSourceAsset:(NSString*)asset withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl cacheKey:(NSString*)cacheKey cacheManager:(CacheManager*)cacheManager overriddenDuration:(int) overriddenDuration{
NSString* path = [[NSBundle mainBundle] pathForResource:asset ofType:nil];
return [self setDataSourceURL:[NSURL fileURLWithPath:path] withKey:key withCertificateUrl:certificateUrl withLicenseUrl:licenseUrl withHeaders: @{} withCache: false overriddenDuration:overriddenDuration];
return [self setDataSourceURL:[NSURL fileURLWithPath:path] withKey:key withCertificateUrl:certificateUrl withLicenseUrl:(NSString*)licenseUrl withHeaders: @{} withCache: false cacheKey:cacheKey cacheManager:cacheManager overriddenDuration:overriddenDuration];
}

- (void)setDataSourceURL:(NSURL*)url withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl withHeaders:(NSDictionary*)headers withCache:(BOOL)useCache overriddenDuration:(int) overriddenDuration{
- (void)setDataSourceURL:(NSURL*)url withKey:(NSString*)key withCertificateUrl:(NSString*)certificateUrl withLicenseUrl:(NSString*)licenseUrl withHeaders:(NSDictionary*)headers withCache:(BOOL)useCache cacheKey:(NSString*)cacheKey cacheManager:(CacheManager*)cacheManager overriddenDuration:(int) overriddenDuration{
_overriddenDuration = 0;
if (headers == [NSNull null]){
headers = @{};
}
AVPlayerItem* item;
if (useCache){
[KTVHTTPCache downloadSetAdditionalHeaders:headers];
NSURL *proxyURL = [KTVHTTPCache proxyURLWithOriginalURL:url];
item = [AVPlayerItem playerItemWithURL:proxyURL];
item = [cacheManager getCachingPlayerItem:url cacheKey:cacheKey headers:headers];
} else {
AVURLAsset* asset = [AVURLAsset URLAssetWithURL:url
options:@{@"AVURLAssetHTTPHeaderFieldsKey" : headers}];

if (certificateUrl && certificateUrl != [NSNull null] && [certificateUrl length] > 0) {
NSURL * certificateNSURL = [[NSURL alloc] initWithString: certificateUrl];
NSURL * licenseNSURL = [[NSURL alloc] initWithString: licenseUrl];

_loaderDelegate = [[BetterPlayerEzDrmAssetsLoaderDelegate alloc] init:certificateNSURL withLicenseURL:licenseNSURL];
dispatch_queue_attr_t qos = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1);
dispatch_queue_t streamQueue = dispatch_queue_create("streamQueue", qos);
[asset.resourceLoader setDelegate:_loaderDelegate queue:streamQueue];
}
item = [AVPlayerItem playerItemWithAsset:asset];
}

if (@available(iOS 10.0, *) && overriddenDuration > 0) {
_overriddenDuration = overriddenDuration;
}

return [self setDataSourcePlayerItem:item withKey:key];
}

Expand All @@ -231,7 +230,7 @@ - (void)setDataSourcePlayerItem:(AVPlayerItem*)item withKey:(NSString*)key{
_isStalledCheckStarted = false;
_playerRate = 1;
[_player replaceCurrentItemWithPlayerItem:item];

AVAsset* asset = [item asset];
void (^assetCompletionHandler)(void) = ^{
if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) {
Expand Down Expand Up @@ -260,7 +259,7 @@ - (void)setDataSourcePlayerItem:(AVPlayerItem*)item withKey:(NSString*)key{
}
}
};

[asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler];
[self addObservers:item];
}
Expand Down Expand Up @@ -289,7 +288,7 @@ -(void)startStalledCheck{
return;
}
[self performSelector:@selector(startStalledCheck) withObject:nil afterDelay:1];

}
}

Expand All @@ -305,14 +304,14 @@ - (NSTimeInterval) availableDuration
} else {
return 0;
}

}

- (void)observeValueForKeyPath:(NSString*)path
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {

if ([path isEqualToString:@"rate"]) {
if (@available(iOS 10.0, *)) {
if (_pipController.pictureInPictureActive == true){
Expand Down Expand Up @@ -344,7 +343,7 @@ - (void)observeValueForKeyPath:(NSString*)path
[self handleStalled];
}
}

if (context == timeRangeContext) {
if (_eventSink != nil) {
NSMutableArray<NSArray<NSNumber*>*>* values = [[NSMutableArray alloc] init];
Expand All @@ -358,7 +357,7 @@ - (void)observeValueForKeyPath:(NSString*)path
end = endTime;
}
}

[values addObject:@[ @(start), @(end) ]];
}
_eventSink(@{@"event" : @"bufferingUpdate", @"values" : values, @"key" : _key});
Expand All @@ -367,14 +366,14 @@ - (void)observeValueForKeyPath:(NSString*)path
else if (context == presentationSizeContext){
[self onReadyToPlay];
}

else if (context == statusContext) {
AVPlayerItem* item = (AVPlayerItem*)object;
switch (item.status) {
case AVPlayerItemStatusFailed:
NSLog(@"Failed to load video:");
NSLog(item.error.debugDescription);

if (_eventSink != nil) {
_eventSink([FlutterError
errorWithCode:@"VideoError"
Expand Down Expand Up @@ -415,7 +414,7 @@ - (void)updatePlayingState {
if (!self._observersAdded){
[self addObservers:[_player currentItem]];
}

if (_isPlaying) {
if (@available(iOS 10.0, *)) {
[_player playImmediatelyAtRate:1.0];
Expand All @@ -437,15 +436,15 @@ - (void)onReadyToPlay {
if (_player.status != AVPlayerStatusReadyToPlay) {
return;
}

CGSize size = [_player currentItem].presentationSize;
CGFloat width = size.width;
CGFloat height = size.height;


AVAsset *asset = _player.currentItem.asset;
bool onlyAudio = [[asset tracksWithMediaType:AVMediaTypeVideo] count] == 0;

// The player has not yet initialized.
if (!onlyAudio && height == CGSizeZero.height && width == CGSizeZero.width) {
return;
Expand All @@ -455,18 +454,18 @@ - (void)onReadyToPlay {
if (isLive == false && [self duration] == 0) {
return;
}

//Fix from https://github.com/flutter/flutter/issues/66413
AVPlayerItemTrack *track = [self.player currentItem].tracks.firstObject;
CGSize naturalSize = track.assetTrack.naturalSize;
CGAffineTransform prefTrans = track.assetTrack.preferredTransform;
CGSize realSize = CGSizeApplyAffineTransform(naturalSize, prefTrans);

int64_t duration = [BetterPlayerTimeUtils FLTCMTimeToMillis:(_player.currentItem.asset.duration)];
if (_overriddenDuration > 0 && duration > _overriddenDuration){
_player.currentItem.forwardPlaybackEndTime = CMTimeMake(_overriddenDuration/1000, 1);
}

_isInitialized = true;
[self updatePlayingState];
_eventSink(@{
Expand Down Expand Up @@ -509,7 +508,7 @@ - (int64_t)duration {
if (!CMTIME_IS_INVALID(_player.currentItem.forwardPlaybackEndTime)) {
time = [[_player currentItem] forwardPlaybackEndTime];
}

return [BetterPlayerTimeUtils FLTCMTimeToMillis:(time)];
}

Expand All @@ -519,7 +518,7 @@ - (void)seekTo:(int)location {
if (wasPlaying){
[_player pause];
}

[_player seekToTime:CMTimeMake(location, 1000)
toleranceBefore:kCMTimeZero
toleranceAfter:kCMTimeZero
Expand Down Expand Up @@ -561,7 +560,7 @@ - (void)setSpeed:(double)speed result:(FlutterResult)result {
details:nil]);
}
}

if (_isPlaying){
_player.rate = _playerRate;
}
Expand Down Expand Up @@ -671,15 +670,15 @@ - (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureCo
}

- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController API_AVAILABLE(ios(9.0)){

}

- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {

}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error {

}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler {
Expand All @@ -689,8 +688,8 @@ - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPict
- (void) setAudioTrack:(NSString*) name index:(int) index{
AVMediaSelectionGroup *audioSelectionGroup = [[[_player currentItem] asset] mediaSelectionGroupForMediaCharacteristic: AVMediaCharacteristicAudible];
NSArray* options = audioSelectionGroup.options;


for (int audioTrackIndex = 0; audioTrackIndex < [options count]; audioTrackIndex++) {
AVMediaSelectionOption* option = [options objectAtIndex:audioTrackIndex];
NSArray *metaDatas = [AVMetadataItem metadataItemsFromArray:option.commonMetadata withKey:@"title" keySpace:@"comn"];
Expand All @@ -700,9 +699,9 @@ - (void) setAudioTrack:(NSString*) name index:(int) index{
[[_player currentItem] selectMediaOption:option inMediaSelectionGroup: audioSelectionGroup];
}
}

}

}

- (void)setMixWithOthers:(bool)mixWithOthers {
Expand Down
3 changes: 1 addition & 2 deletions ios/Classes/BetterPlayerPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#import <Flutter/Flutter.h>
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
#import <KTVHTTPCache/KTVHTTPCache.h>
#import <GLKit/GLKit.h>
#import "BetterPlayerTimeUtils.h"
#import "BetterPlayer.h"
Expand All @@ -18,4 +17,4 @@
@property(readonly, strong, nonatomic) NSMutableDictionary* players;
@property(readonly, strong, nonatomic) NSObject<FlutterPluginRegistrar>* registrar;

@end
@end
Loading

0 comments on commit 57a04e0

Please sign in to comment.