Merge branch 'master' into master
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#import "RCTVideoPlayerViewController.h"
|
||||
#import "RCTVideoPlayerViewControllerDelegate.h"
|
||||
#import <React/RCTComponent.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||
#import <react-native-video/RCTVideoCache.h>
|
||||
@@ -42,4 +43,6 @@
|
||||
|
||||
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem;
|
||||
|
||||
- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
|
||||
|
||||
@end
|
||||
|
@@ -26,6 +26,7 @@ static int const RCTVideoUnset = -1;
|
||||
{
|
||||
AVPlayer *_player;
|
||||
AVPlayerItem *_playerItem;
|
||||
NSDictionary *_source;
|
||||
BOOL _playerItemObserversSet;
|
||||
BOOL _playerBufferEmpty;
|
||||
AVPlayerLayer *_playerLayer;
|
||||
@@ -51,6 +52,8 @@ static int const RCTVideoUnset = -1;
|
||||
/* Keep track of any modifiers, need to be applied after each play */
|
||||
float _volume;
|
||||
float _rate;
|
||||
float _maxBitRate;
|
||||
|
||||
BOOL _muted;
|
||||
BOOL _paused;
|
||||
BOOL _repeat;
|
||||
@@ -64,8 +67,11 @@ static int const RCTVideoUnset = -1;
|
||||
NSString * _ignoreSilentSwitch;
|
||||
NSString * _resizeMode;
|
||||
BOOL _fullscreen;
|
||||
BOOL _fullscreenAutorotate;
|
||||
NSString * _fullscreenOrientation;
|
||||
BOOL _fullscreenPlayerPresented;
|
||||
NSString *_filterName;
|
||||
BOOL _filterEnabled;
|
||||
UIViewController * _presentingViewController;
|
||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||
RCTVideoCache * _videoCache;
|
||||
@@ -83,6 +89,7 @@ static int const RCTVideoUnset = -1;
|
||||
_rate = 1.0;
|
||||
_volume = 1.0;
|
||||
_resizeMode = @"AVLayerVideoGravityResizeAspectFill";
|
||||
_fullscreenAutorotate = YES;
|
||||
_fullscreenOrientation = @"all";
|
||||
_pendingSeek = false;
|
||||
_pendingSeekTime = 0.0f;
|
||||
@@ -323,6 +330,7 @@ static int const RCTVideoUnset = -1;
|
||||
|
||||
- (void)setSrc:(NSDictionary *)source
|
||||
{
|
||||
_source = source;
|
||||
[self removePlayerLayer];
|
||||
[self removePlayerTimeObserver];
|
||||
[self removePlayerItemObservers];
|
||||
@@ -333,6 +341,8 @@ static int const RCTVideoUnset = -1;
|
||||
[self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
|
||||
_playerItem = playerItem;
|
||||
[self addPlayerItemObservers];
|
||||
[self setFilter:_filterName];
|
||||
[self setMaxBitRate:_maxBitRate];
|
||||
|
||||
[_player pause];
|
||||
[_playerViewController.view removeFromSuperview];
|
||||
@@ -397,10 +407,13 @@ static int const RCTVideoUnset = -1;
|
||||
|
||||
- (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nullable)assetOptions withCallback:(void(^)(AVPlayerItem *))handler
|
||||
{
|
||||
if (!_textTracks) {
|
||||
if (!_textTracks || _textTracks.count==0) {
|
||||
handler([AVPlayerItem playerItemWithAsset:asset]);
|
||||
return;
|
||||
}
|
||||
|
||||
// AVPlayer can't airplay AVMutableCompositions
|
||||
_allowsExternalPlayback = NO;
|
||||
|
||||
// sideload text tracks
|
||||
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
|
||||
@@ -864,6 +877,12 @@ static int const RCTVideoUnset = -1;
|
||||
[self applyModifiers];
|
||||
}
|
||||
|
||||
- (void)setMaxBitRate:(float) maxBitRate {
|
||||
_maxBitRate = maxBitRate;
|
||||
_playerItem.preferredPeakBitRate = maxBitRate;
|
||||
}
|
||||
|
||||
|
||||
- (void)applyModifiers
|
||||
{
|
||||
if (_muted) {
|
||||
@@ -874,6 +893,7 @@ static int const RCTVideoUnset = -1;
|
||||
[_player setMuted:NO];
|
||||
}
|
||||
|
||||
[self setMaxBitRate:_maxBitRate];
|
||||
[self setSelectedAudioTrack:_selectedAudioTrack];
|
||||
[self setSelectedTextTrack:_selectedTextTrack];
|
||||
[self setResizeMode:_resizeMode];
|
||||
@@ -1154,6 +1174,7 @@ static int const RCTVideoUnset = -1;
|
||||
[viewController presentViewController:_playerViewController animated:true completion:^{
|
||||
_playerViewController.showsPlaybackControls = YES;
|
||||
_fullscreenPlayerPresented = fullscreen;
|
||||
_playerViewController.autorotate = _fullscreenAutorotate;
|
||||
if(self.onVideoFullscreenPlayerDidPresent) {
|
||||
self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag});
|
||||
}
|
||||
@@ -1169,6 +1190,13 @@ static int const RCTVideoUnset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFullscreenAutorotate:(BOOL)autorotate {
|
||||
_fullscreenAutorotate = autorotate;
|
||||
if (_fullscreenPlayerPresented) {
|
||||
_playerViewController.autorotate = autorotate;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFullscreenOrientation:(NSString *)orientation {
|
||||
_fullscreenOrientation = orientation;
|
||||
if (_fullscreenPlayerPresented) {
|
||||
@@ -1270,6 +1298,36 @@ static int const RCTVideoUnset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFilter:(NSString *)filterName {
|
||||
_filterName = filterName;
|
||||
|
||||
if (!_filterEnabled) {
|
||||
return;
|
||||
} else if ([[_source objectForKey:@"uri"] rangeOfString:@"m3u8"].location != NSNotFound) {
|
||||
return; // filters don't work for HLS... return
|
||||
} else if (!_playerItem.asset) {
|
||||
return;
|
||||
}
|
||||
|
||||
CIFilter *filter = [CIFilter filterWithName:filterName];
|
||||
_playerItem.videoComposition = [AVVideoComposition
|
||||
videoCompositionWithAsset:_playerItem.asset
|
||||
applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) {
|
||||
if (filter == nil) {
|
||||
[request finishWithImage:request.sourceImage context:nil];
|
||||
} else {
|
||||
CIImage *image = request.sourceImage.imageByClampingToExtent;
|
||||
[filter setValue:image forKey:kCIInputImageKey];
|
||||
CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent];
|
||||
[request finishWithImage:output context:nil];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setFilterEnabled:(BOOL)filterEnabled {
|
||||
_filterEnabled = filterEnabled;
|
||||
}
|
||||
|
||||
#pragma mark - React View Management
|
||||
|
||||
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
|
||||
@@ -1356,4 +1414,78 @@ static int const RCTVideoUnset = -1;
|
||||
[super removeFromSuperview];
|
||||
}
|
||||
|
||||
#pragma mark - Export
|
||||
|
||||
- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
|
||||
AVAsset *asset = _playerItem.asset;
|
||||
|
||||
if (asset != nil) {
|
||||
|
||||
AVAssetExportSession *exportSession = [AVAssetExportSession
|
||||
exportSessionWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
|
||||
|
||||
if (exportSession != nil) {
|
||||
NSString *path = nil;
|
||||
NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
path = [self generatePathInDirectory:[[self cacheDirectoryPath] stringByAppendingPathComponent:@"Videos"]
|
||||
withExtension:@".mp4"];
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
exportSession.outputFileType = AVFileTypeMPEG4;
|
||||
exportSession.outputURL = url;
|
||||
exportSession.videoComposition = _playerItem.videoComposition;
|
||||
exportSession.shouldOptimizeForNetworkUse = true;
|
||||
[exportSession exportAsynchronouslyWithCompletionHandler:^{
|
||||
|
||||
switch ([exportSession status]) {
|
||||
case AVAssetExportSessionStatusFailed:
|
||||
reject(@"ERROR_COULD_NOT_EXPORT_VIDEO", @"Could not export video", exportSession.error);
|
||||
break;
|
||||
case AVAssetExportSessionStatusCancelled:
|
||||
reject(@"ERROR_EXPORT_SESSION_CANCELLED", @"Export session was cancelled", exportSession.error);
|
||||
break;
|
||||
default:
|
||||
resolve(@{@"uri": url.absoluteString});
|
||||
break;
|
||||
}
|
||||
|
||||
}];
|
||||
|
||||
} else {
|
||||
|
||||
reject(@"ERROR_COULD_NOT_CREATE_EXPORT_SESSION", @"Could not create export session", nil);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
reject(@"ERROR_ASSET_NIL", @"Asset is nil", nil);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)ensureDirExistsWithPath:(NSString *)path {
|
||||
BOOL isDir = NO;
|
||||
NSError *error;
|
||||
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
|
||||
if (!(exists && isDir)) {
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];
|
||||
if (error) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)generatePathInDirectory:(NSString *)directory withExtension:(NSString *)extension {
|
||||
NSString *fileName = [[[NSUUID UUID] UUIDString] stringByAppendingString:extension];
|
||||
[self ensureDirExistsWithPath:directory];
|
||||
return [directory stringByAppendingPathComponent:fileName];
|
||||
}
|
||||
|
||||
- (NSString *)cacheDirectoryPath {
|
||||
NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
return array[0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCTVideoManager : RCTViewManager
|
||||
@interface RCTVideoManager : RCTViewManager <RCTBridgeModule>
|
||||
|
||||
@end
|
||||
|
@@ -1,14 +1,13 @@
|
||||
#import "RCTVideoManager.h"
|
||||
#import "RCTVideo.h"
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation RCTVideoManager
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTVideo alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
||||
@@ -16,10 +15,11 @@ RCT_EXPORT_MODULE();
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
return self.bridge.uiManager.methodQueue;
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);
|
||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);
|
||||
@@ -37,7 +37,10 @@ RCT_EXPORT_VIEW_PROPERTY(rate, float);
|
||||
RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(currentTime, float);
|
||||
RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(filter, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
|
||||
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
|
||||
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock);
|
||||
@@ -59,6 +62,21 @@ RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTBubblingEventBlock);
|
||||
RCT_REMAP_METHOD(save,
|
||||
options:(NSDictionary *)options
|
||||
reactTag:(nonnull NSNumber *)reactTag
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
[self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTVideo *> *viewRegistry) {
|
||||
RCTVideo *view = viewRegistry[reactTag];
|
||||
if (![view isKindOfClass:[RCTVideo class]]) {
|
||||
RCTLogError(@"Invalid view returned from registry, expecting RCTVideo, got: %@", view);
|
||||
} else {
|
||||
[view save:options resolve:resolve reject:reject];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
|
@@ -15,5 +15,6 @@
|
||||
|
||||
// Optional paramters
|
||||
@property (nonatomic, weak) NSString* preferredOrientation;
|
||||
@property (nonatomic) BOOL autorotate;
|
||||
|
||||
@end
|
||||
|
@@ -7,7 +7,8 @@
|
||||
@implementation RCTVideoPlayerViewController
|
||||
|
||||
- (BOOL)shouldAutorotate {
|
||||
if (self.preferredOrientation.lowercaseString == nil || [self.preferredOrientation.lowercaseString isEqualToString:@"all"])
|
||||
|
||||
if (self.autorotate || self.preferredOrientation.lowercaseString == nil || [self.preferredOrientation.lowercaseString isEqualToString:@"all"])
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
|
Reference in New Issue
Block a user