2017-01-11 05:43:43 -07:00
|
|
|
#import <React/RCTConvert.h>
|
2018-03-01 15:38:38 -07:00
|
|
|
#import "RCTVideoCache.h"
|
2015-03-30 23:07:55 -06:00
|
|
|
#import "RCTVideo.h"
|
2017-01-11 05:43:43 -07:00
|
|
|
#import <React/RCTBridgeModule.h>
|
|
|
|
#import <React/RCTEventDispatcher.h>
|
|
|
|
#import <React/UIView+React.h>
|
2018-06-21 10:08:37 -06:00
|
|
|
#include <MediaAccessibility/MediaAccessibility.h>
|
|
|
|
#include <AVFoundation/AVFoundation.h>
|
2015-03-30 23:07:55 -06:00
|
|
|
|
2015-04-08 00:49:14 -06:00
|
|
|
static NSString *const statusKeyPath = @"status";
|
2015-08-05 06:52:30 -06:00
|
|
|
static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp";
|
2016-02-18 17:45:09 -07:00
|
|
|
static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty";
|
2016-04-28 06:25:45 -06:00
|
|
|
static NSString *const readyForDisplayKeyPath = @"readyForDisplay";
|
2016-04-28 06:29:09 -06:00
|
|
|
static NSString *const playbackRate = @"rate";
|
2017-02-13 19:38:02 -07:00
|
|
|
static NSString *const timedMetadata = @"timedMetadata";
|
2015-04-08 00:49:14 -06:00
|
|
|
|
2015-03-30 23:07:55 -06:00
|
|
|
@implementation RCTVideo
|
|
|
|
{
|
2015-04-07 16:24:49 -06:00
|
|
|
AVPlayer *_player;
|
2015-04-08 00:49:14 -06:00
|
|
|
AVPlayerItem *_playerItem;
|
2015-08-05 06:52:30 -06:00
|
|
|
BOOL _playerItemObserversSet;
|
2016-02-18 17:45:09 -07:00
|
|
|
BOOL _playerBufferEmpty;
|
2015-04-07 16:24:49 -06:00
|
|
|
AVPlayerLayer *_playerLayer;
|
2018-07-09 12:20:32 -06:00
|
|
|
BOOL _playerLayerObserverSet;
|
2015-12-22 16:39:04 -07:00
|
|
|
AVPlayerViewController *_playerViewController;
|
2015-04-07 16:24:49 -06:00
|
|
|
NSURL *_videoURL;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-07 16:24:49 -06:00
|
|
|
/* Required to publish events */
|
|
|
|
RCTEventDispatcher *_eventDispatcher;
|
2016-05-17 01:37:11 -06:00
|
|
|
BOOL _playbackRateObserverRegistered;
|
2018-06-25 12:43:51 -06:00
|
|
|
BOOL _videoLoadStarted;
|
2015-04-06 14:05:05 -06:00
|
|
|
|
2015-04-08 16:15:57 -06:00
|
|
|
bool _pendingSeek;
|
|
|
|
float _pendingSeekTime;
|
|
|
|
float _lastSeekTime;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-07 16:24:49 -06:00
|
|
|
/* For sending videoProgress events */
|
2015-12-22 16:39:04 -07:00
|
|
|
Float64 _progressUpdateInterval;
|
|
|
|
BOOL _controls;
|
|
|
|
id _timeObserver;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-07 21:38:16 -06:00
|
|
|
/* Keep track of any modifiers, need to be applied after each play */
|
|
|
|
float _volume;
|
|
|
|
float _rate;
|
|
|
|
BOOL _muted;
|
2015-05-10 17:01:25 -06:00
|
|
|
BOOL _paused;
|
2015-06-16 02:07:50 -06:00
|
|
|
BOOL _repeat;
|
2018-06-05 19:40:12 -06:00
|
|
|
BOOL _allowsExternalPlayback;
|
2018-06-21 10:08:37 -06:00
|
|
|
NSArray * _textTracks;
|
2018-06-02 03:24:13 -06:00
|
|
|
NSDictionary * _selectedTextTrack;
|
2016-04-28 06:38:21 -06:00
|
|
|
BOOL _playbackStalled;
|
2015-10-30 03:34:54 -06:00
|
|
|
BOOL _playInBackground;
|
2016-04-29 05:55:34 -06:00
|
|
|
BOOL _playWhenInactive;
|
2017-04-20 12:10:06 -06:00
|
|
|
NSString * _ignoreSilentSwitch;
|
2015-06-16 02:07:50 -06:00
|
|
|
NSString * _resizeMode;
|
2016-04-01 06:55:23 -06:00
|
|
|
BOOL _fullscreenPlayerPresented;
|
2016-03-31 12:34:22 -06:00
|
|
|
UIViewController * _presentingViewController;
|
2018-03-01 15:38:38 -07:00
|
|
|
RCTVideoCache * _videoCache;
|
2015-03-30 23:07:55 -06:00
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
|
|
|
{
|
2015-03-30 23:07:55 -06:00
|
|
|
if ((self = [super init])) {
|
2015-04-06 13:17:32 -06:00
|
|
|
_eventDispatcher = eventDispatcher;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-05-17 01:37:11 -06:00
|
|
|
_playbackRateObserverRegistered = NO;
|
2016-04-28 06:38:21 -06:00
|
|
|
_playbackStalled = NO;
|
2015-04-08 11:34:27 -06:00
|
|
|
_rate = 1.0;
|
|
|
|
_volume = 1.0;
|
2015-06-16 22:14:14 -06:00
|
|
|
_resizeMode = @"AVLayerVideoGravityResizeAspectFill";
|
2015-04-08 16:15:57 -06:00
|
|
|
_pendingSeek = false;
|
|
|
|
_pendingSeekTime = 0.0f;
|
|
|
|
_lastSeekTime = 0.0f;
|
2015-12-22 16:39:04 -07:00
|
|
|
_progressUpdateInterval = 250;
|
|
|
|
_controls = NO;
|
2016-02-18 17:45:09 -07:00
|
|
|
_playerBufferEmpty = YES;
|
2016-06-01 23:58:38 -06:00
|
|
|
_playInBackground = false;
|
2018-06-10 10:04:13 -06:00
|
|
|
_allowsExternalPlayback = YES;
|
2016-04-29 05:55:34 -06:00
|
|
|
_playWhenInactive = false;
|
2017-04-20 12:10:06 -06:00
|
|
|
_ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey
|
2018-03-01 15:38:38 -07:00
|
|
|
_videoCache = [RCTVideoCache sharedInstance];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-06-25 16:40:46 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
2015-08-05 06:52:30 -06:00
|
|
|
selector:@selector(applicationWillResignActive:)
|
|
|
|
name:UIApplicationWillResignActiveNotification
|
|
|
|
object:nil];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-04-29 01:46:28 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(applicationDidEnterBackground:)
|
|
|
|
name:UIApplicationDidEnterBackgroundNotification
|
|
|
|
object:nil];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-06-25 16:40:46 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
2015-08-05 06:52:30 -06:00
|
|
|
selector:@selector(applicationWillEnterForeground:)
|
|
|
|
name:UIApplicationWillEnterForegroundNotification
|
|
|
|
object:nil];
|
2018-07-12 22:48:58 -06:00
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(audioRouteChanged:)
|
|
|
|
name:AVAudioSessionRouteChangeNotification
|
|
|
|
object:nil];
|
2015-03-30 23:07:55 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-03-30 23:07:55 -06:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2015-12-22 16:39:04 -07:00
|
|
|
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem {
|
2016-03-31 15:10:25 -06:00
|
|
|
RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init];
|
2017-08-29 15:39:18 -06:00
|
|
|
playerLayer.showsPlaybackControls = YES;
|
2016-03-31 15:10:25 -06:00
|
|
|
playerLayer.rctDelegate = self;
|
2015-12-22 16:39:04 -07:00
|
|
|
playerLayer.view.frame = self.bounds;
|
2018-02-27 19:15:42 -07:00
|
|
|
playerLayer.player = player;
|
2015-12-22 16:39:04 -07:00
|
|
|
playerLayer.view.frame = self.bounds;
|
|
|
|
return playerLayer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------
|
|
|
|
** Get the duration for a AVPlayerItem.
|
|
|
|
** ------------------------------------------------------- */
|
|
|
|
|
|
|
|
- (CMTime)playerItemDuration
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
AVPlayerItem *playerItem = [_player currentItem];
|
|
|
|
if (playerItem.status == AVPlayerItemStatusReadyToPlay)
|
|
|
|
{
|
|
|
|
return([playerItem duration]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(kCMTimeInvalid);
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
|
2016-09-06 20:23:13 -06:00
|
|
|
- (CMTimeRange)playerItemSeekableTimeRange
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
AVPlayerItem *playerItem = [_player currentItem];
|
|
|
|
if (playerItem.status == AVPlayerItemStatusReadyToPlay)
|
|
|
|
{
|
|
|
|
return [playerItem seekableTimeRanges].firstObject.CMTimeRangeValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (kCMTimeRangeZero);
|
2016-09-06 20:23:13 -06:00
|
|
|
}
|
|
|
|
|
2017-09-28 19:37:26 -06:00
|
|
|
-(void)addPlayerTimeObserver
|
|
|
|
{
|
|
|
|
const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000;
|
2018-05-29 17:11:15 -06:00
|
|
|
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m
|
|
|
|
// of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html
|
2017-09-28 19:37:26 -06:00
|
|
|
__weak RCTVideo *weakSelf = self;
|
|
|
|
_timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC)
|
|
|
|
queue:NULL
|
|
|
|
usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; }
|
|
|
|
];
|
|
|
|
}
|
2015-12-22 16:39:04 -07:00
|
|
|
|
|
|
|
/* Cancels the previously registered time observer. */
|
|
|
|
-(void)removePlayerTimeObserver
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if (_timeObserver)
|
|
|
|
{
|
|
|
|
[_player removeTimeObserver:_timeObserver];
|
|
|
|
_timeObserver = nil;
|
|
|
|
}
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Progress
|
|
|
|
|
2015-06-26 12:41:41 -06:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
2015-08-05 06:52:30 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
2017-06-09 15:31:46 -06:00
|
|
|
[self removePlayerLayer];
|
2018-03-19 13:56:55 -06:00
|
|
|
[self removePlayerItemObservers];
|
2016-08-04 06:32:04 -06:00
|
|
|
[_player removeObserver:self forKeyPath:playbackRate context:nil];
|
2015-06-26 12:41:41 -06:00
|
|
|
}
|
|
|
|
|
2015-06-25 16:40:46 -06:00
|
|
|
#pragma mark - App lifecycle handlers
|
|
|
|
|
|
|
|
- (void)applicationWillResignActive:(NSNotification *)notification
|
|
|
|
{
|
2016-04-29 05:55:34 -06:00
|
|
|
if (_playInBackground || _playWhenInactive || _paused) return;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-04-29 05:55:34 -06:00
|
|
|
[_player pause];
|
|
|
|
[_player setRate:0.0];
|
2015-06-25 16:40:46 -06:00
|
|
|
}
|
|
|
|
|
2016-04-29 01:46:28 -06:00
|
|
|
- (void)applicationDidEnterBackground:(NSNotification *)notification
|
|
|
|
{
|
2016-06-01 23:58:38 -06:00
|
|
|
if (_playInBackground) {
|
|
|
|
// Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html
|
|
|
|
[_playerLayer setPlayer:nil];
|
|
|
|
}
|
2016-04-29 01:46:28 -06:00
|
|
|
}
|
|
|
|
|
2015-06-25 16:40:46 -06:00
|
|
|
- (void)applicationWillEnterForeground:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
[self applyModifiers];
|
2016-04-28 07:06:22 -06:00
|
|
|
if (_playInBackground) {
|
|
|
|
[_playerLayer setPlayer:_player];
|
|
|
|
}
|
2015-06-25 16:40:46 -06:00
|
|
|
}
|
|
|
|
|
2018-07-12 22:48:58 -06:00
|
|
|
#pragma mark - Audio events
|
|
|
|
|
|
|
|
- (void)audioRouteChanged:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
NSNumber *reason = [[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey];
|
|
|
|
NSNumber *previousRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey];
|
|
|
|
if (reason.unsignedIntValue == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
|
|
|
|
self.onVideoAudioBecomingNoisy(@{@"target": self.reactTag});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-08 09:46:13 -06:00
|
|
|
#pragma mark - Progress
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)sendProgressUpdate
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
AVPlayerItem *video = [_player currentItem];
|
|
|
|
if (video == nil || video.status != AVPlayerItemStatusReadyToPlay) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CMTime playerDuration = [self playerItemDuration];
|
|
|
|
if (CMTIME_IS_INVALID(playerDuration)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CMTime currentTime = _player.currentTime;
|
|
|
|
const Float64 duration = CMTimeGetSeconds(playerDuration);
|
|
|
|
const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);
|
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}];
|
|
|
|
|
|
|
|
if( currentTimeSecs >= 0 && self.onVideoProgress) {
|
|
|
|
self.onVideoProgress(@{
|
|
|
|
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
|
|
|
|
@"playableDuration": [self calculatePlayableDuration],
|
|
|
|
@"atValue": [NSNumber numberWithLongLong:currentTime.value],
|
|
|
|
@"atTimescale": [NSNumber numberWithInt:currentTime.timescale],
|
|
|
|
@"target": self.reactTag,
|
|
|
|
@"seekableDuration": [self calculateSeekableDuration],
|
|
|
|
});
|
|
|
|
}
|
2015-04-06 14:05:05 -06:00
|
|
|
}
|
|
|
|
|
2015-06-10 15:29:38 -06:00
|
|
|
/*!
|
|
|
|
* Calculates and returns the playable duration of the current player item using its loaded time ranges.
|
|
|
|
*
|
2015-06-11 19:11:55 -06:00
|
|
|
* \returns The playable duration of the current player item in seconds.
|
2015-06-10 15:29:38 -06:00
|
|
|
*/
|
2015-08-05 06:52:30 -06:00
|
|
|
- (NSNumber *)calculatePlayableDuration
|
|
|
|
{
|
2015-06-10 15:29:38 -06:00
|
|
|
AVPlayerItem *video = _player.currentItem;
|
|
|
|
if (video.status == AVPlayerItemStatusReadyToPlay) {
|
|
|
|
__block CMTimeRange effectiveTimeRange;
|
|
|
|
[video.loadedTimeRanges enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
|
|
|
CMTimeRange timeRange = [obj CMTimeRangeValue];
|
|
|
|
if (CMTimeRangeContainsTime(timeRange, video.currentTime)) {
|
|
|
|
effectiveTimeRange = timeRange;
|
|
|
|
*stop = YES;
|
|
|
|
}
|
|
|
|
}];
|
2015-06-17 23:08:01 -06:00
|
|
|
Float64 playableDuration = CMTimeGetSeconds(CMTimeRangeGetEnd(effectiveTimeRange));
|
|
|
|
if (playableDuration > 0) {
|
|
|
|
return [NSNumber numberWithFloat:playableDuration];
|
|
|
|
}
|
2015-06-10 15:29:38 -06:00
|
|
|
}
|
2015-06-17 23:08:01 -06:00
|
|
|
return [NSNumber numberWithInteger:0];
|
2015-06-10 15:29:38 -06:00
|
|
|
}
|
|
|
|
|
2017-09-06 19:12:34 -06:00
|
|
|
- (NSNumber *)calculateSeekableDuration
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
CMTimeRange timeRange = [self playerItemSeekableTimeRange];
|
|
|
|
if (CMTIME_IS_NUMERIC(timeRange.duration))
|
|
|
|
{
|
|
|
|
return [NSNumber numberWithFloat:CMTimeGetSeconds(timeRange.duration)];
|
|
|
|
}
|
|
|
|
return [NSNumber numberWithInteger:0];
|
2017-09-06 19:12:34 -06:00
|
|
|
}
|
|
|
|
|
2015-08-05 06:52:30 -06:00
|
|
|
- (void)addPlayerItemObservers
|
2015-05-30 13:47:14 -06:00
|
|
|
{
|
|
|
|
[_playerItem addObserver:self forKeyPath:statusKeyPath options:0 context:nil];
|
2016-02-18 17:45:09 -07:00
|
|
|
[_playerItem addObserver:self forKeyPath:playbackBufferEmptyKeyPath options:0 context:nil];
|
2015-08-05 06:52:30 -06:00
|
|
|
[_playerItem addObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath options:0 context:nil];
|
2017-02-13 19:38:02 -07:00
|
|
|
[_playerItem addObserver:self forKeyPath:timedMetadata options:NSKeyValueObservingOptionNew context:nil];
|
2015-08-05 06:52:30 -06:00
|
|
|
_playerItemObserversSet = YES;
|
2015-05-30 13:47:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Fixes https://github.com/brentvatne/react-native-video/issues/43
|
|
|
|
* Crashes caused when trying to remove the observer when there is no
|
|
|
|
* observer set */
|
2015-08-05 06:52:30 -06:00
|
|
|
- (void)removePlayerItemObservers
|
2015-05-30 13:47:14 -06:00
|
|
|
{
|
2015-08-05 06:52:30 -06:00
|
|
|
if (_playerItemObserversSet) {
|
2015-05-30 13:47:14 -06:00
|
|
|
[_playerItem removeObserver:self forKeyPath:statusKeyPath];
|
2016-02-18 17:45:09 -07:00
|
|
|
[_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath];
|
2015-08-05 06:52:30 -06:00
|
|
|
[_playerItem removeObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath];
|
2017-02-13 19:38:02 -07:00
|
|
|
[_playerItem removeObserver:self forKeyPath:timedMetadata];
|
2015-08-05 06:52:30 -06:00
|
|
|
_playerItemObserversSet = NO;
|
2015-05-30 13:47:14 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-08 09:46:13 -06:00
|
|
|
#pragma mark - Player and source
|
|
|
|
|
2018-02-28 09:42:49 -07:00
|
|
|
- (void)setCache:(BOOL *)cache
|
|
|
|
{
|
2018-03-01 15:38:38 -07:00
|
|
|
|
2018-02-28 09:42:49 -07:00
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)setSrc:(NSDictionary *)source
|
|
|
|
{
|
2018-03-19 13:56:55 -06:00
|
|
|
[self removePlayerLayer];
|
2015-12-22 16:39:04 -07:00
|
|
|
[self removePlayerTimeObserver];
|
2015-08-05 06:52:30 -06:00
|
|
|
[self removePlayerItemObservers];
|
2018-02-28 09:42:49 -07:00
|
|
|
[self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
|
|
|
|
[self didSetPlayerItemWithSource:source playerItem:playerItem];
|
|
|
|
}];
|
|
|
|
}
|
2015-04-08 00:49:14 -06:00
|
|
|
|
2018-02-28 09:42:49 -07:00
|
|
|
- (void) didSetPlayerItemWithSource:(NSDictionary *)source playerItem:(AVPlayerItem *) playerItem
|
|
|
|
{
|
2016-12-12 17:16:11 -07:00
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
2018-06-21 10:08:37 -06:00
|
|
|
|
|
|
|
// perform on next run loop, otherwise other passed react-props may not be set
|
2018-07-17 06:36:03 -06:00
|
|
|
[self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
|
2018-07-17 07:29:53 -06:00
|
|
|
_playerItem = playerItem;
|
2018-07-17 06:36:03 -06:00
|
|
|
[self addPlayerItemObservers];
|
|
|
|
|
|
|
|
[_player pause];
|
|
|
|
[_playerViewController.view removeFromSuperview];
|
|
|
|
_playerViewController = nil;
|
|
|
|
|
|
|
|
if (_playbackRateObserverRegistered) {
|
|
|
|
[_player removeObserver:self forKeyPath:playbackRate context:nil];
|
|
|
|
_playbackRateObserverRegistered = NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
_player = [AVPlayer playerWithPlayerItem:_playerItem];
|
|
|
|
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
|
|
|
|
|
|
|
|
[_player addObserver:self forKeyPath:playbackRate options:0 context:nil];
|
|
|
|
_playbackRateObserverRegistered = YES;
|
|
|
|
|
|
|
|
[self addPlayerTimeObserver];
|
|
|
|
|
|
|
|
//Perform on next run loop, otherwise onVideoLoadStart is nil
|
|
|
|
if(self.onVideoLoadStart) {
|
|
|
|
id uri = [source objectForKey:@"uri"];
|
|
|
|
id type = [source objectForKey:@"type"];
|
|
|
|
self.onVideoLoadStart(@{@"src": @{
|
|
|
|
@"uri": uri ? uri : [NSNull null],
|
|
|
|
@"type": type ? type : [NSNull null],
|
|
|
|
@"isNetwork": [NSNumber numberWithBool:(bool)[source objectForKey:@"isNetwork"]]},
|
|
|
|
@"target": self.reactTag
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}];
|
2016-12-12 17:16:11 -07:00
|
|
|
});
|
2018-06-25 12:43:51 -06:00
|
|
|
_videoLoadStarted = YES;
|
2015-04-08 00:49:14 -06:00
|
|
|
}
|
2015-04-07 09:31:40 -06:00
|
|
|
|
2018-06-21 10:08:37 -06:00
|
|
|
- (NSURL*) urlFilePath:(NSString*) filepath {
|
|
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
|
|
|
|
|
|
NSString* relativeFilePath = [filepath lastPathComponent];
|
|
|
|
// the file may be multiple levels below the documents directory
|
|
|
|
NSArray* fileComponents = [filepath componentsSeparatedByString:@"Documents/"];
|
|
|
|
if (fileComponents.count>1) {
|
|
|
|
relativeFilePath = [fileComponents objectAtIndex:1];
|
|
|
|
}
|
|
|
|
|
|
|
|
NSString *path = [paths.firstObject stringByAppendingPathComponent:relativeFilePath];
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
|
|
return [NSURL fileURLWithPath:path];
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2018-07-17 06:36:03 -06:00
|
|
|
- (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSMutableDictionary * __nullable)assetOptions withCallback:(void(^)(AVPlayerItem *))handler
|
2015-05-30 13:50:45 -06:00
|
|
|
{
|
2018-07-09 17:28:38 -06:00
|
|
|
if (!_textTracks) {
|
2018-07-17 05:28:37 -06:00
|
|
|
handler([AVPlayerItem playerItemWithAsset:asset]);
|
|
|
|
return;
|
2018-07-09 17:28:38 -06:00
|
|
|
}
|
|
|
|
// sideload text tracks
|
2018-06-21 10:08:37 -06:00
|
|
|
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
|
|
|
|
|
2018-07-09 17:28:38 -06:00
|
|
|
AVAssetTrack *videoAsset = [asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
|
|
|
|
AVMutableCompositionTrack *videoCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
|
2018-06-21 10:08:37 -06:00
|
|
|
[videoCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)
|
2018-07-09 17:28:38 -06:00
|
|
|
ofTrack:videoAsset
|
|
|
|
atTime:kCMTimeZero
|
|
|
|
error:nil];
|
|
|
|
|
|
|
|
AVAssetTrack *audioAsset = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;
|
|
|
|
AVMutableCompositionTrack *audioCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
|
2018-06-21 10:08:37 -06:00
|
|
|
[audioCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)
|
2018-07-09 17:28:38 -06:00
|
|
|
ofTrack:audioAsset
|
|
|
|
atTime:kCMTimeZero
|
|
|
|
error:nil];
|
|
|
|
|
|
|
|
for (int i = 0; i < _textTracks.count; ++i) {
|
|
|
|
AVURLAsset *textURLAsset;
|
|
|
|
NSString *textUri = [_textTracks objectAtIndex:i][@"uri"];
|
|
|
|
if ([[textUri lowercaseString] hasPrefix:@"http"]) {
|
|
|
|
textURLAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:textUri] options:assetOptions];
|
|
|
|
} else {
|
|
|
|
textURLAsset = [AVURLAsset URLAssetWithURL:[self urlFilePath:textUri] options:nil];
|
|
|
|
}
|
|
|
|
AVAssetTrack *textTrackAsset = [textURLAsset tracksWithMediaType:AVMediaTypeText].firstObject;
|
|
|
|
AVMutableCompositionTrack *textCompTrack = [mixComposition
|
|
|
|
addMutableTrackWithMediaType:AVMediaTypeText
|
|
|
|
preferredTrackID:kCMPersistentTrackID_Invalid];
|
|
|
|
[textCompTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.timeRange.duration)
|
|
|
|
ofTrack:textTrackAsset
|
|
|
|
atTime:kCMTimeZero
|
|
|
|
error:nil];
|
|
|
|
}
|
|
|
|
|
2018-07-17 05:28:37 -06:00
|
|
|
handler([AVPlayerItem playerItemWithAsset:mixComposition]);
|
2015-04-08 00:49:14 -06:00
|
|
|
}
|
2015-04-07 09:31:40 -06:00
|
|
|
|
2018-02-28 09:42:49 -07:00
|
|
|
- (void)playerItemForSource:(NSDictionary *)source withCallback:(void(^)(AVPlayerItem *))handler
|
2015-05-30 13:50:45 -06:00
|
|
|
{
|
2015-04-08 11:34:27 -06:00
|
|
|
bool isNetwork = [RCTConvert BOOL:[source objectForKey:@"isNetwork"]];
|
|
|
|
bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]];
|
|
|
|
NSString *uri = [source objectForKey:@"uri"];
|
|
|
|
NSString *type = [source objectForKey:@"type"];
|
2018-07-17 05:28:37 -06:00
|
|
|
|
2015-04-08 11:34:27 -06:00
|
|
|
NSURL *url = (isNetwork || isAsset) ?
|
2018-02-28 09:42:49 -07:00
|
|
|
[NSURL URLWithString:uri] :
|
|
|
|
[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]];
|
2018-07-17 06:36:03 -06:00
|
|
|
NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc] init];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-10-06 15:34:01 -06:00
|
|
|
if (isNetwork) {
|
2018-03-01 15:38:38 -07:00
|
|
|
[_videoCache getItemForUri:uri withCallback:^(AVAsset * _Nullable asset) {
|
|
|
|
if (asset) {
|
2018-07-17 06:36:03 -06:00
|
|
|
[self playerItemPrepareText:asset assetOptions:assetOptions withCallback:handler];
|
2018-03-01 15:38:38 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
|
2018-07-17 06:36:03 -06:00
|
|
|
[assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey];
|
|
|
|
DVURLAsset * dvAsset = [[DVURLAsset alloc] initWithURL:url options:assetOptions networkTimeout: 10000];
|
2018-03-01 15:38:38 -07:00
|
|
|
dvAsset.loaderDelegate = self;
|
2018-07-17 06:36:03 -06:00
|
|
|
[self playerItemPrepareText:dvAsset assetOptions:assetOptions withCallback:handler];
|
2018-03-01 15:38:38 -07:00
|
|
|
}];
|
2018-02-28 09:42:49 -07:00
|
|
|
return;
|
2016-10-06 15:34:01 -06:00
|
|
|
}
|
|
|
|
else if (isAsset) {
|
2015-04-08 11:34:27 -06:00
|
|
|
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
|
2018-07-17 06:36:03 -06:00
|
|
|
[self playerItemPrepareText:asset assetOptions:assetOptions withCallback:handler];
|
2018-02-28 09:42:49 -07:00
|
|
|
return;
|
2015-04-08 11:34:27 -06:00
|
|
|
}
|
2018-07-17 05:28:37 -06:00
|
|
|
|
|
|
|
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]] options:nil];
|
2018-07-17 06:36:03 -06:00
|
|
|
[self playerItemPrepareText:asset assetOptions:assetOptions withCallback:handler];
|
2015-04-08 11:34:27 -06:00
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if (object == _playerItem) {
|
2017-02-13 19:38:02 -07:00
|
|
|
// When timeMetadata is read the event onTimedMetadata is triggered
|
2018-06-25 12:43:51 -06:00
|
|
|
if ([keyPath isEqualToString:timedMetadata]) {
|
2018-02-28 09:42:49 -07:00
|
|
|
NSArray<AVMetadataItem *> *items = [change objectForKey:@"new"];
|
|
|
|
if (items && ![items isEqual:[NSNull null]] && items.count > 0) {
|
|
|
|
NSMutableArray *array = [NSMutableArray new];
|
|
|
|
for (AVMetadataItem *item in items) {
|
2018-07-09 17:28:38 -06:00
|
|
|
NSString *value = (NSString *)item.value;
|
2018-02-28 09:42:49 -07:00
|
|
|
NSString *identifier = item.identifier;
|
|
|
|
|
|
|
|
if (![value isEqual: [NSNull null]]) {
|
|
|
|
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@"value", @"identifier"]];
|
|
|
|
|
|
|
|
[array addObject:dictionary];
|
|
|
|
}
|
2017-02-13 19:38:02 -07:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
|
|
|
self.onTimedMetadata(@{
|
|
|
|
@"target": self.reactTag,
|
|
|
|
@"metadata": array
|
|
|
|
});
|
|
|
|
}
|
2017-02-13 19:38:02 -07:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-08-05 06:52:30 -06:00
|
|
|
if ([keyPath isEqualToString:statusKeyPath]) {
|
|
|
|
// Handle player item status change.
|
|
|
|
if (_playerItem.status == AVPlayerItemStatusReadyToPlay) {
|
|
|
|
float duration = CMTimeGetSeconds(_playerItem.asset.duration);
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-08-05 06:52:30 -06:00
|
|
|
if (isnan(duration)) {
|
|
|
|
duration = 0.0;
|
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-04-08 03:10:22 -06:00
|
|
|
NSObject *width = @"undefined";
|
|
|
|
NSObject *height = @"undefined";
|
2016-05-01 08:41:30 -06:00
|
|
|
NSString *orientation = @"undefined";
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-04-08 03:10:22 -06:00
|
|
|
if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) {
|
2016-05-01 09:26:56 -06:00
|
|
|
AVAssetTrack *videoTrack = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
|
|
|
|
width = [NSNumber numberWithFloat:videoTrack.naturalSize.width];
|
|
|
|
height = [NSNumber numberWithFloat:videoTrack.naturalSize.height];
|
|
|
|
CGAffineTransform preferredTransform = [videoTrack preferredTransform];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-05-01 09:26:56 -06:00
|
|
|
if ((videoTrack.naturalSize.width == preferredTransform.tx
|
2018-02-28 09:42:49 -07:00
|
|
|
&& videoTrack.naturalSize.height == preferredTransform.ty)
|
|
|
|
|| (preferredTransform.tx == 0 && preferredTransform.ty == 0))
|
2016-05-01 09:26:56 -06:00
|
|
|
{
|
2016-05-01 08:41:30 -06:00
|
|
|
orientation = @"landscape";
|
2018-06-25 12:43:51 -06:00
|
|
|
} else {
|
2016-05-01 08:41:30 -06:00
|
|
|
orientation = @"portrait";
|
2018-06-25 12:43:51 -06:00
|
|
|
}
|
2016-04-08 03:10:22 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2018-06-25 12:43:51 -06:00
|
|
|
if (self.onVideoLoad && _videoLoadStarted) {
|
2017-01-16 09:27:08 -07:00
|
|
|
self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration],
|
|
|
|
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)],
|
|
|
|
@"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse],
|
|
|
|
@"canPlayFastForward": [NSNumber numberWithBool:_playerItem.canPlayFastForward],
|
|
|
|
@"canPlaySlowForward": [NSNumber numberWithBool:_playerItem.canPlaySlowForward],
|
|
|
|
@"canPlaySlowReverse": [NSNumber numberWithBool:_playerItem.canPlaySlowReverse],
|
|
|
|
@"canStepBackward": [NSNumber numberWithBool:_playerItem.canStepBackward],
|
|
|
|
@"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward],
|
|
|
|
@"naturalSize": @{
|
2018-02-28 09:42:49 -07:00
|
|
|
@"width": width,
|
|
|
|
@"height": height,
|
|
|
|
@"orientation": orientation
|
|
|
|
},
|
2018-06-11 21:55:23 -06:00
|
|
|
@"textTracks": [self getTextTrackInfo],
|
2017-01-16 09:27:08 -07:00
|
|
|
@"target": self.reactTag});
|
2018-02-28 09:42:49 -07:00
|
|
|
}
|
2018-06-25 12:43:51 -06:00
|
|
|
_videoLoadStarted = NO;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-08-05 06:52:30 -06:00
|
|
|
[self attachListeners];
|
|
|
|
[self applyModifiers];
|
2018-06-25 12:43:51 -06:00
|
|
|
} else if (_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {
|
2016-12-12 17:16:11 -07:00
|
|
|
self.onVideoError(@{@"error": @{@"code": [NSNumber numberWithInteger: _playerItem.error.code],
|
|
|
|
@"domain": _playerItem.error.domain},
|
2018-02-28 09:42:49 -07:00
|
|
|
@"target": self.reactTag});
|
2015-08-05 06:52:30 -06:00
|
|
|
}
|
2016-02-18 17:45:09 -07:00
|
|
|
} else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) {
|
|
|
|
_playerBufferEmpty = YES;
|
2017-01-11 05:51:45 -07:00
|
|
|
self.onVideoBuffer(@{@"isBuffering": @(YES), @"target": self.reactTag});
|
2015-08-05 06:52:30 -06:00
|
|
|
} else if ([keyPath isEqualToString:playbackLikelyToKeepUpKeyPath]) {
|
|
|
|
// Continue playing (or not if paused) after being paused due to hitting an unbuffered zone.
|
2016-09-19 18:49:12 -06:00
|
|
|
if ((!(_controls || _fullscreenPlayerPresented) || _playerBufferEmpty) && _playerItem.playbackLikelyToKeepUp) {
|
2015-08-05 06:52:30 -06:00
|
|
|
[self setPaused:_paused];
|
2015-05-11 00:54:58 -06:00
|
|
|
}
|
2016-02-18 17:45:09 -07:00
|
|
|
_playerBufferEmpty = NO;
|
2017-01-11 05:51:45 -07:00
|
|
|
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
|
2015-04-08 00:49:14 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
} else if (object == _playerLayer) {
|
|
|
|
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) {
|
|
|
|
if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
|
|
|
|
self.onReadyForDisplay(@{@"target": self.reactTag});
|
|
|
|
}
|
2016-04-28 06:28:29 -06:00
|
|
|
}
|
2016-04-28 06:37:45 -06:00
|
|
|
} else if (object == _player) {
|
2018-02-28 09:42:49 -07:00
|
|
|
if([keyPath isEqualToString:playbackRate]) {
|
|
|
|
if(self.onPlaybackRateChange) {
|
|
|
|
self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
|
|
|
|
@"target": self.reactTag});
|
|
|
|
}
|
|
|
|
if(_playbackStalled && _player.rate > 0) {
|
|
|
|
if(self.onPlaybackResume) {
|
|
|
|
self.onPlaybackResume(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
|
|
|
|
@"target": self.reactTag});
|
|
|
|
}
|
|
|
|
_playbackStalled = NO;
|
2016-04-28 06:37:45 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
}
|
2015-04-08 00:49:14 -06:00
|
|
|
} else {
|
2018-02-28 09:42:49 -07:00
|
|
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
2015-04-08 00:49:14 -06:00
|
|
|
}
|
2015-03-30 23:07:55 -06:00
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)attachListeners
|
|
|
|
{
|
2015-07-18 06:38:56 -06:00
|
|
|
// listen for end of file
|
2017-10-24 01:47:43 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
|
|
name:AVPlayerItemDidPlayToEndTimeNotification
|
|
|
|
object:[_player currentItem]];
|
2015-07-18 06:38:56 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(playerItemDidReachEnd:)
|
|
|
|
name:AVPlayerItemDidPlayToEndTimeNotification
|
|
|
|
object:[_player currentItem]];
|
2017-10-24 01:47:43 -06:00
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
|
|
name:AVPlayerItemPlaybackStalledNotification
|
|
|
|
object:nil];
|
2016-04-28 06:38:21 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(playbackStalled:)
|
|
|
|
name:AVPlayerItemPlaybackStalledNotification
|
|
|
|
object:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)playbackStalled:(NSNotification *)notification
|
|
|
|
{
|
2017-01-16 09:27:08 -07:00
|
|
|
if(self.onPlaybackStalled) {
|
|
|
|
self.onPlaybackStalled(@{@"target": self.reactTag});
|
|
|
|
}
|
2016-04-28 06:38:21 -06:00
|
|
|
_playbackStalled = YES;
|
2015-04-10 20:58:36 -06:00
|
|
|
}
|
|
|
|
|
2015-06-26 16:13:03 -06:00
|
|
|
- (void)playerItemDidReachEnd:(NSNotification *)notification
|
2015-05-30 13:50:45 -06:00
|
|
|
{
|
2017-01-16 09:27:08 -07:00
|
|
|
if(self.onVideoEnd) {
|
2018-02-28 09:42:49 -07:00
|
|
|
self.onVideoEnd(@{@"target": self.reactTag});
|
2017-01-16 09:27:08 -07:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-06-24 20:09:05 -06:00
|
|
|
if (_repeat) {
|
2015-04-08 11:34:27 -06:00
|
|
|
AVPlayerItem *item = [notification object];
|
|
|
|
[item seekToTime:kCMTimeZero];
|
|
|
|
[self applyModifiers];
|
2018-05-15 23:19:12 -06:00
|
|
|
} else {
|
|
|
|
[self removePlayerTimeObserver];
|
2015-06-24 20:09:05 -06:00
|
|
|
}
|
2015-04-08 09:46:13 -06:00
|
|
|
}
|
|
|
|
|
2015-04-08 11:34:27 -06:00
|
|
|
#pragma mark - Prop setters
|
2015-04-08 09:46:13 -06:00
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)setResizeMode:(NSString*)mode
|
|
|
|
{
|
2015-12-22 16:39:04 -07:00
|
|
|
if( _controls )
|
|
|
|
{
|
|
|
|
_playerViewController.videoGravity = mode;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_playerLayer.videoGravity = mode;
|
|
|
|
}
|
2015-06-16 02:07:50 -06:00
|
|
|
_resizeMode = mode;
|
2015-04-04 18:55:37 -06:00
|
|
|
}
|
|
|
|
|
2015-10-30 03:34:54 -06:00
|
|
|
- (void)setPlayInBackground:(BOOL)playInBackground
|
|
|
|
{
|
2016-06-02 00:33:18 -06:00
|
|
|
_playInBackground = playInBackground;
|
2015-10-30 03:34:54 -06:00
|
|
|
}
|
|
|
|
|
2018-06-05 19:40:12 -06:00
|
|
|
- (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback
|
|
|
|
{
|
|
|
|
_allowsExternalPlayback = allowsExternalPlayback;
|
|
|
|
_player.allowsExternalPlayback = _allowsExternalPlayback;
|
|
|
|
}
|
|
|
|
|
2016-04-29 05:55:34 -06:00
|
|
|
- (void)setPlayWhenInactive:(BOOL)playWhenInactive
|
|
|
|
{
|
2016-06-02 00:33:18 -06:00
|
|
|
_playWhenInactive = playWhenInactive;
|
2016-04-29 05:55:34 -06:00
|
|
|
}
|
|
|
|
|
2017-04-20 12:10:06 -06:00
|
|
|
- (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch
|
|
|
|
{
|
|
|
|
_ignoreSilentSwitch = ignoreSilentSwitch;
|
|
|
|
[self applyModifiers];
|
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)setPaused:(BOOL)paused
|
|
|
|
{
|
2015-04-07 21:38:16 -06:00
|
|
|
if (paused) {
|
2015-12-22 16:39:04 -07:00
|
|
|
[_player pause];
|
2015-07-20 10:46:49 -06:00
|
|
|
[_player setRate:0.0];
|
2015-04-07 09:31:40 -06:00
|
|
|
} else {
|
2017-04-20 12:10:06 -06:00
|
|
|
if([_ignoreSilentSwitch isEqualToString:@"ignore"]) {
|
|
|
|
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
|
|
|
|
} else if([_ignoreSilentSwitch isEqualToString:@"obey"]) {
|
|
|
|
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
|
|
|
|
}
|
2015-12-22 16:39:04 -07:00
|
|
|
[_player play];
|
2015-07-20 10:46:49 -06:00
|
|
|
[_player setRate:_rate];
|
2015-04-07 09:31:40 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-05-10 17:01:25 -06:00
|
|
|
_paused = paused;
|
2015-04-05 11:17:03 -06:00
|
|
|
}
|
|
|
|
|
2015-12-22 16:39:04 -07:00
|
|
|
- (float)getCurrentTime
|
|
|
|
{
|
|
|
|
return _playerItem != NULL ? CMTimeGetSeconds(_playerItem.currentTime) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setCurrentTime:(float)currentTime
|
|
|
|
{
|
2018-07-09 12:20:32 -06:00
|
|
|
NSDictionary *info = @{
|
|
|
|
@"time": [NSNumber numberWithFloat:currentTime],
|
|
|
|
@"tolerance": [NSNumber numberWithInt:100]
|
|
|
|
};
|
|
|
|
[self setSeek:info];
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
|
2018-07-09 12:20:32 -06:00
|
|
|
- (void)setSeek:(NSDictionary *)info
|
2015-05-30 13:50:45 -06:00
|
|
|
{
|
2018-07-09 12:20:32 -06:00
|
|
|
NSNumber *seekTime = info[@"time"];
|
|
|
|
NSNumber *seekTolerance = info[@"tolerance"];
|
|
|
|
|
|
|
|
int timeScale = 1000;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-08 16:15:57 -06:00
|
|
|
AVPlayerItem *item = _player.currentItem;
|
|
|
|
if (item && item.status == AVPlayerItemStatusReadyToPlay) {
|
|
|
|
// TODO check loadedTimeRanges
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2018-07-09 12:20:32 -06:00
|
|
|
CMTime cmSeekTime = CMTimeMakeWithSeconds([seekTime floatValue], timeScale);
|
2015-04-08 16:15:57 -06:00
|
|
|
CMTime current = item.currentTime;
|
|
|
|
// TODO figure out a good tolerance level
|
2018-07-09 12:20:32 -06:00
|
|
|
CMTime tolerance = CMTimeMake([seekTolerance floatValue], timeScale);
|
2017-03-21 14:25:58 -06:00
|
|
|
BOOL wasPaused = _paused;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-08 16:15:57 -06:00
|
|
|
if (CMTimeCompare(current, cmSeekTime) != 0) {
|
2017-03-21 14:25:58 -06:00
|
|
|
if (!wasPaused) [_player pause];
|
2015-07-18 06:38:56 -06:00
|
|
|
[_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) {
|
2018-05-15 23:19:12 -06:00
|
|
|
if (!_timeObserver) {
|
|
|
|
[self addPlayerTimeObserver];
|
|
|
|
}
|
2017-09-07 06:16:44 -06:00
|
|
|
if (!wasPaused) {
|
2018-07-09 12:20:32 -06:00
|
|
|
[self setPaused:false];
|
2017-09-07 06:16:44 -06:00
|
|
|
}
|
2017-01-16 09:27:08 -07:00
|
|
|
if(self.onVideoSeek) {
|
2018-02-28 09:42:49 -07:00
|
|
|
self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)],
|
2018-07-09 12:20:32 -06:00
|
|
|
@"seekTime": seekTime,
|
2018-02-28 09:42:49 -07:00
|
|
|
@"target": self.reactTag});
|
2017-01-16 09:27:08 -07:00
|
|
|
}
|
2015-07-18 06:38:56 -06:00
|
|
|
}];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-09 08:19:09 -06:00
|
|
|
_pendingSeek = false;
|
2015-04-08 16:15:57 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-08 16:15:57 -06:00
|
|
|
} else {
|
2015-08-05 06:52:30 -06:00
|
|
|
// TODO: See if this makes sense and if so, actually implement it
|
2015-04-08 16:15:57 -06:00
|
|
|
_pendingSeek = true;
|
2018-07-09 12:20:32 -06:00
|
|
|
_pendingSeekTime = [seekTime floatValue];
|
2015-04-08 16:15:57 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)setRate:(float)rate
|
|
|
|
{
|
2015-04-07 21:38:16 -06:00
|
|
|
_rate = rate;
|
|
|
|
[self applyModifiers];
|
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)setMuted:(BOOL)muted
|
|
|
|
{
|
2015-04-07 21:38:16 -06:00
|
|
|
_muted = muted;
|
|
|
|
[self applyModifiers];
|
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)setVolume:(float)volume
|
|
|
|
{
|
2015-04-07 21:38:16 -06:00
|
|
|
_volume = volume;
|
|
|
|
[self applyModifiers];
|
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)applyModifiers
|
|
|
|
{
|
2015-04-07 21:38:16 -06:00
|
|
|
if (_muted) {
|
2015-07-18 06:38:56 -06:00
|
|
|
[_player setVolume:0];
|
|
|
|
[_player setMuted:YES];
|
2015-04-07 21:38:16 -06:00
|
|
|
} else {
|
2015-07-18 06:38:56 -06:00
|
|
|
[_player setVolume:_volume];
|
|
|
|
[_player setMuted:NO];
|
2015-04-07 21:38:16 -06:00
|
|
|
}
|
|
|
|
|
2018-06-02 03:24:13 -06:00
|
|
|
[self setSelectedTextTrack:_selectedTextTrack];
|
2015-06-16 02:07:50 -06:00
|
|
|
[self setResizeMode:_resizeMode];
|
|
|
|
[self setRepeat:_repeat];
|
2015-05-10 17:01:25 -06:00
|
|
|
[self setPaused:_paused];
|
2015-12-22 16:39:04 -07:00
|
|
|
[self setControls:_controls];
|
2018-06-05 19:40:12 -06:00
|
|
|
[self setAllowsExternalPlayback:_allowsExternalPlayback];
|
2015-04-07 21:38:16 -06:00
|
|
|
}
|
2015-04-04 18:55:37 -06:00
|
|
|
|
2015-04-08 00:49:14 -06:00
|
|
|
- (void)setRepeat:(BOOL)repeat {
|
2015-06-16 02:07:50 -06:00
|
|
|
_repeat = repeat;
|
2015-03-31 00:29:15 -06:00
|
|
|
}
|
|
|
|
|
2018-07-06 17:38:24 -06:00
|
|
|
- (void)setSelectedTextTrack:(NSDictionary *)selectedTextTrack {
|
2018-07-09 17:28:38 -06:00
|
|
|
_selectedTextTrack = selectedTextTrack;
|
|
|
|
if (_textTracks) {
|
|
|
|
[self setSideloadedText];
|
|
|
|
} else {
|
|
|
|
[self setStreamingText];
|
|
|
|
}
|
2018-06-21 10:08:37 -06:00
|
|
|
}
|
|
|
|
|
2018-07-09 12:20:32 -06:00
|
|
|
- (void) setSideloadedText {
|
2018-06-21 10:08:37 -06:00
|
|
|
NSString *type = _selectedTextTrack[@"type"];
|
|
|
|
NSArray* textTracks = [self getTextTrackInfo];
|
|
|
|
|
2018-07-09 17:28:38 -06:00
|
|
|
// The first few tracks will be audio & video track
|
|
|
|
int firstTextIndex = 0;
|
|
|
|
for (firstTextIndex = 0; firstTextIndex < _player.currentItem.tracks.count; ++firstTextIndex) {
|
|
|
|
if ([_player.currentItem.tracks[firstTextIndex].assetTrack hasMediaCharacteristic:AVMediaCharacteristicLegible]) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int selectedTrackIndex = -1;
|
2018-06-21 10:08:37 -06:00
|
|
|
|
|
|
|
if ([type isEqualToString:@"disabled"]) {
|
|
|
|
// Do nothing. We want to ensure option is nil
|
|
|
|
} else if ([type isEqualToString:@"language"]) {
|
|
|
|
NSString *selectedValue = _selectedTextTrack[@"value"];
|
|
|
|
for (int i = 0; i < textTracks.count; ++i) {
|
2018-07-09 12:20:32 -06:00
|
|
|
NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];
|
|
|
|
if ([selectedValue isEqualToString:currentTextTrack[@"language"]]) {
|
2018-07-09 17:28:38 -06:00
|
|
|
selectedTrackIndex = i;
|
2018-06-21 10:08:37 -06:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ([type isEqualToString:@"title"]) {
|
|
|
|
NSString *selectedValue = _selectedTextTrack[@"value"];
|
|
|
|
for (int i = 0; i < textTracks.count; ++i) {
|
2018-07-09 12:20:32 -06:00
|
|
|
NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];
|
|
|
|
if ([selectedValue isEqualToString:currentTextTrack[@"title"]]) {
|
2018-07-09 17:28:38 -06:00
|
|
|
selectedTrackIndex = i;
|
2018-06-21 10:08:37 -06:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ([type isEqualToString:@"index"]) {
|
|
|
|
if ([_selectedTextTrack[@"value"] isKindOfClass:[NSNumber class]]) {
|
|
|
|
int index = [_selectedTextTrack[@"value"] intValue];
|
|
|
|
if (textTracks.count > index) {
|
2018-07-09 17:28:38 -06:00
|
|
|
selectedTrackIndex = index;
|
2018-06-21 10:08:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// user's selected language might not be available, or system defaults have captions enabled
|
2018-07-09 17:28:38 -06:00
|
|
|
if (selectedTrackIndex == -1 || [type isEqualToString:@"default"]) {
|
2018-06-21 10:08:37 -06:00
|
|
|
CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser);
|
|
|
|
NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics;
|
|
|
|
if ([captionSettings containsObject: AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) {
|
2018-07-09 12:20:32 -06:00
|
|
|
// iterate through the textTracks to find a matching option, or default to the first object.
|
2018-07-09 22:08:40 -06:00
|
|
|
selectedTrackIndex = 0;
|
2018-07-09 12:20:32 -06:00
|
|
|
|
|
|
|
NSString * systemLanguage = [[NSLocale preferredLanguages] firstObject];
|
|
|
|
for (int i = 0; i < textTracks.count; ++i) {
|
|
|
|
NSDictionary *currentTextTrack = [textTracks objectAtIndex:i];
|
|
|
|
if ([systemLanguage isEqualToString:currentTextTrack[@"language"]]) {
|
2018-07-09 17:28:38 -06:00
|
|
|
selectedTrackIndex = i;
|
2018-07-09 12:20:32 -06:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-06-21 10:08:37 -06:00
|
|
|
}
|
|
|
|
}
|
2018-07-09 17:28:38 -06:00
|
|
|
|
|
|
|
for (int i = firstTextIndex; i < _player.currentItem.tracks.count; ++i) {
|
|
|
|
BOOL isEnabled = i == selectedTrackIndex + firstTextIndex;
|
|
|
|
[_player.currentItem.tracks[i] setEnabled:isEnabled];
|
|
|
|
}
|
2018-06-21 10:08:37 -06:00
|
|
|
}
|
|
|
|
|
2018-07-09 12:20:32 -06:00
|
|
|
-(void) setStreamingText {
|
2018-06-21 10:08:37 -06:00
|
|
|
NSString *type = _selectedTextTrack[@"type"];
|
2018-06-02 03:24:13 -06:00
|
|
|
AVMediaSelectionGroup *group = [_player.currentItem.asset
|
|
|
|
mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
|
2018-07-09 12:20:32 -06:00
|
|
|
AVMediaSelectionOption *mediaOption;
|
2018-06-21 10:08:37 -06:00
|
|
|
|
2018-06-02 03:24:13 -06:00
|
|
|
if ([type isEqualToString:@"disabled"]) {
|
|
|
|
// Do nothing. We want to ensure option is nil
|
|
|
|
} else if ([type isEqualToString:@"language"] || [type isEqualToString:@"title"]) {
|
2018-06-21 10:08:37 -06:00
|
|
|
NSString *value = _selectedTextTrack[@"value"];
|
2018-06-02 03:24:13 -06:00
|
|
|
for (int i = 0; i < group.options.count; ++i) {
|
|
|
|
AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];
|
|
|
|
NSString *optionValue;
|
|
|
|
if ([type isEqualToString:@"language"]) {
|
|
|
|
optionValue = [currentOption extendedLanguageTag];
|
|
|
|
} else {
|
|
|
|
optionValue = [[[currentOption commonMetadata]
|
|
|
|
valueForKey:@"value"]
|
|
|
|
objectAtIndex:0];
|
|
|
|
}
|
|
|
|
if ([value isEqualToString:optionValue]) {
|
2018-07-09 12:20:32 -06:00
|
|
|
mediaOption = currentOption;
|
2018-06-02 03:24:13 -06:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-06-21 10:08:37 -06:00
|
|
|
//} else if ([type isEqualToString:@"default"]) {
|
|
|
|
// option = group.defaultOption; */
|
2018-06-02 03:24:13 -06:00
|
|
|
} else if ([type isEqualToString:@"index"]) {
|
2018-06-21 10:08:37 -06:00
|
|
|
if ([_selectedTextTrack[@"value"] isKindOfClass:[NSNumber class]]) {
|
|
|
|
int index = [_selectedTextTrack[@"value"] intValue];
|
2018-06-02 03:24:13 -06:00
|
|
|
if (group.options.count > index) {
|
2018-07-09 12:20:32 -06:00
|
|
|
mediaOption = [group.options objectAtIndex:index];
|
2018-06-02 03:24:13 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { // default. invalid type or "system"
|
2018-06-02 20:41:25 -06:00
|
|
|
[_player.currentItem selectMediaOptionAutomaticallyInMediaSelectionGroup:group];
|
2018-06-02 03:24:13 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If a match isn't found, option will be nil and text tracks will be disabled
|
2018-07-09 12:20:32 -06:00
|
|
|
[_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group];
|
2018-06-02 03:24:13 -06:00
|
|
|
}
|
|
|
|
|
2018-06-21 10:08:37 -06:00
|
|
|
- (void)setTextTracks:(NSArray*) textTracks;
|
|
|
|
{
|
|
|
|
_textTracks = textTracks;
|
2018-07-09 12:20:32 -06:00
|
|
|
|
|
|
|
// in case textTracks was set after selectedTextTrack
|
|
|
|
if (_selectedTextTrack) [self setSelectedTextTrack:_selectedTextTrack];
|
2018-06-21 10:08:37 -06:00
|
|
|
}
|
|
|
|
|
2018-06-11 21:55:23 -06:00
|
|
|
- (NSArray *)getTextTrackInfo
|
|
|
|
{
|
2018-06-21 10:08:37 -06:00
|
|
|
|
|
|
|
// if sideloaded, textTracks will already be set
|
|
|
|
if (_textTracks) return _textTracks;
|
|
|
|
|
|
|
|
// if streaming video, we extract the text tracks
|
2018-06-11 21:55:23 -06:00
|
|
|
NSMutableArray *textTracks = [[NSMutableArray alloc] init];
|
|
|
|
AVMediaSelectionGroup *group = [_player.currentItem.asset
|
|
|
|
mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
|
|
|
|
for (int i = 0; i < group.options.count; ++i) {
|
|
|
|
AVMediaSelectionOption *currentOption = [group.options objectAtIndex:i];
|
2018-07-09 12:20:32 -06:00
|
|
|
NSString *title = @"";
|
|
|
|
NSArray *values = [[currentOption commonMetadata] valueForKey:@"value"];
|
|
|
|
if (values.count > 0) {
|
|
|
|
title = [values objectAtIndex:0];
|
|
|
|
}
|
|
|
|
NSString *language = [currentOption extendedLanguageTag] ? [currentOption extendedLanguageTag] : @"";
|
2018-06-11 21:55:23 -06:00
|
|
|
NSDictionary *textTrack = @{
|
|
|
|
@"index": [NSNumber numberWithInt:i],
|
|
|
|
@"title": title,
|
2018-07-09 12:20:32 -06:00
|
|
|
@"language": language
|
2018-06-11 21:55:23 -06:00
|
|
|
};
|
|
|
|
[textTracks addObject:textTrack];
|
|
|
|
}
|
|
|
|
return textTracks;
|
|
|
|
}
|
|
|
|
|
2016-03-31 12:34:22 -06:00
|
|
|
- (BOOL)getFullscreen
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
return _fullscreenPlayerPresented;
|
2016-03-31 12:34:22 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setFullscreen:(BOOL)fullscreen
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if( fullscreen && !_fullscreenPlayerPresented )
|
|
|
|
{
|
|
|
|
// Ensure player view controller is not null
|
|
|
|
if( !_playerViewController )
|
2016-03-31 12:34:22 -06:00
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
[self usePlayerViewController];
|
2016-03-31 12:34:22 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
// Set presentation style to fullscreen
|
|
|
|
[_playerViewController setModalPresentationStyle:UIModalPresentationFullScreen];
|
|
|
|
|
|
|
|
// Find the nearest view controller
|
|
|
|
UIViewController *viewController = [self firstAvailableUIViewController];
|
|
|
|
if( !viewController )
|
2016-03-31 12:34:22 -06:00
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
|
|
|
|
viewController = keyWindow.rootViewController;
|
|
|
|
if( viewController.childViewControllers.count > 0 )
|
|
|
|
{
|
|
|
|
viewController = viewController.childViewControllers.lastObject;
|
|
|
|
}
|
2016-03-31 12:34:22 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
if( viewController )
|
|
|
|
{
|
|
|
|
_presentingViewController = viewController;
|
|
|
|
if(self.onVideoFullscreenPlayerWillPresent) {
|
|
|
|
self.onVideoFullscreenPlayerWillPresent(@{@"target": self.reactTag});
|
|
|
|
}
|
|
|
|
[viewController presentViewController:_playerViewController animated:true completion:^{
|
|
|
|
_playerViewController.showsPlaybackControls = YES;
|
|
|
|
_fullscreenPlayerPresented = fullscreen;
|
|
|
|
if(self.onVideoFullscreenPlayerDidPresent) {
|
|
|
|
self.onVideoFullscreenPlayerDidPresent(@{@"target": self.reactTag});
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !fullscreen && _fullscreenPlayerPresented )
|
|
|
|
{
|
|
|
|
[self videoPlayerViewControllerWillDismiss:_playerViewController];
|
|
|
|
[_presentingViewController dismissViewControllerAnimated:true completion:^{
|
|
|
|
[self videoPlayerViewControllerDidDismiss:_playerViewController];
|
|
|
|
}];
|
|
|
|
}
|
2016-03-31 12:34:22 -06:00
|
|
|
}
|
|
|
|
|
2015-12-22 16:39:04 -07:00
|
|
|
- (void)usePlayerViewController
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if( _player )
|
|
|
|
{
|
|
|
|
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];
|
|
|
|
// to prevent video from being animated when resizeMode is 'cover'
|
|
|
|
// resize mode must be set before subview is added
|
|
|
|
[self setResizeMode:_resizeMode];
|
|
|
|
[self addSubview:_playerViewController.view];
|
|
|
|
}
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)usePlayerLayer
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if( _player )
|
|
|
|
{
|
|
|
|
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
|
|
|
|
_playerLayer.frame = self.bounds;
|
|
|
|
_playerLayer.needsDisplayOnBoundsChange = YES;
|
|
|
|
|
|
|
|
// to prevent video from being animated when resizeMode is 'cover'
|
|
|
|
// resize mode must be set before layer is added
|
|
|
|
[self setResizeMode:_resizeMode];
|
|
|
|
[_playerLayer addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];
|
2018-07-09 12:20:32 -06:00
|
|
|
_playerLayerObserverSet = YES;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
|
|
|
[self.layer addSublayer:_playerLayer];
|
|
|
|
self.layer.needsDisplayOnBoundsChange = YES;
|
|
|
|
}
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setControls:(BOOL)controls
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if( _controls != controls || (!_playerLayer && !_playerViewController) )
|
|
|
|
{
|
|
|
|
_controls = controls;
|
|
|
|
if( _controls )
|
2015-12-22 16:39:04 -07:00
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
[self removePlayerLayer];
|
|
|
|
[self usePlayerViewController];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[_playerViewController.view removeFromSuperview];
|
|
|
|
_playerViewController = nil;
|
|
|
|
[self usePlayerLayer];
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
}
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
|
2016-10-01 12:23:50 -06:00
|
|
|
- (void)setProgressUpdateInterval:(float)progressUpdateInterval
|
|
|
|
{
|
|
|
|
_progressUpdateInterval = progressUpdateInterval;
|
2017-09-28 19:37:52 -06:00
|
|
|
|
|
|
|
if (_timeObserver) {
|
|
|
|
[self removePlayerTimeObserver];
|
|
|
|
[self addPlayerTimeObserver];
|
|
|
|
}
|
2016-10-01 12:23:50 -06:00
|
|
|
}
|
|
|
|
|
2016-04-28 06:25:45 -06:00
|
|
|
- (void)removePlayerLayer
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
[_playerLayer removeFromSuperlayer];
|
2018-07-09 12:20:32 -06:00
|
|
|
if (_playerLayerObserverSet) {
|
2018-06-21 10:08:37 -06:00
|
|
|
[_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];
|
2018-07-09 12:20:32 -06:00
|
|
|
_playerLayerObserverSet = NO;
|
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
_playerLayer = nil;
|
2016-04-28 06:25:45 -06:00
|
|
|
}
|
|
|
|
|
2018-07-17 06:36:03 -06:00
|
|
|
#pragma mark - DVAssetLoaderDelegate
|
|
|
|
|
|
|
|
- (void)dvAssetLoaderDelegate:(DVAssetLoaderDelegate *)loaderDelegate
|
|
|
|
didLoadData:(NSData *)data
|
|
|
|
forURL:(NSURL *)url {
|
|
|
|
[_videoCache storeItem:data forUri:[url absoluteString] withCallback:^(BOOL success) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
NSLog(@"data stored succesfully 🎉");
|
|
|
|
#endif
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2016-04-01 02:52:05 -06:00
|
|
|
#pragma mark - RCTVideoPlayerViewControllerDelegate
|
|
|
|
|
|
|
|
- (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if (_playerViewController == playerViewController && _fullscreenPlayerPresented && self.onVideoFullscreenPlayerWillDismiss)
|
|
|
|
{
|
|
|
|
self.onVideoFullscreenPlayerWillDismiss(@{@"target": self.reactTag});
|
|
|
|
}
|
2016-04-01 02:52:05 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
if (_playerViewController == playerViewController && _fullscreenPlayerPresented)
|
|
|
|
{
|
|
|
|
_fullscreenPlayerPresented = false;
|
|
|
|
_presentingViewController = nil;
|
|
|
|
_playerViewController = nil;
|
|
|
|
[self applyModifiers];
|
|
|
|
if(self.onVideoFullscreenPlayerDidDismiss) {
|
|
|
|
self.onVideoFullscreenPlayerDidDismiss(@{@"target": self.reactTag});
|
2016-04-01 02:52:05 -06:00
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
}
|
2016-04-01 02:52:05 -06:00
|
|
|
}
|
|
|
|
|
2015-04-08 11:34:27 -06:00
|
|
|
#pragma mark - React View Management
|
2015-04-08 09:46:13 -06:00
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
|
|
|
|
{
|
2015-12-22 16:39:04 -07:00
|
|
|
// We are early in the game and somebody wants to set a subview.
|
|
|
|
// That can only be in the context of playerViewController.
|
|
|
|
if( !_controls && !_playerLayer && !_playerViewController )
|
|
|
|
{
|
|
|
|
[self setControls:true];
|
|
|
|
}
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-12-22 16:39:04 -07:00
|
|
|
if( _controls )
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
view.frame = self.bounds;
|
|
|
|
[_playerViewController.contentOverlayView insertSubview:view atIndex:atIndex];
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
RCTLogError(@"video cannot have any subviews");
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
2015-03-30 23:07:55 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)removeReactSubview:(UIView *)subview
|
|
|
|
{
|
2015-12-22 16:39:04 -07:00
|
|
|
if( _controls )
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
[subview removeFromSuperview];
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
RCTLogError(@"video cannot have any subviews");
|
|
|
|
}
|
2015-03-30 23:07:55 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)layoutSubviews
|
|
|
|
{
|
2015-03-30 23:07:55 -06:00
|
|
|
[super layoutSubviews];
|
2015-12-22 16:39:04 -07:00
|
|
|
if( _controls )
|
|
|
|
{
|
|
|
|
_playerViewController.view.frame = self.bounds;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-12-22 16:39:04 -07:00
|
|
|
// also adjust all subviews of contentOverlayView
|
|
|
|
for (UIView* subview in _playerViewController.contentOverlayView.subviews) {
|
|
|
|
subview.frame = self.bounds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-02-28 09:42:49 -07:00
|
|
|
[CATransaction begin];
|
|
|
|
[CATransaction setAnimationDuration:0];
|
|
|
|
_playerLayer.frame = self.bounds;
|
|
|
|
[CATransaction commit];
|
2015-12-22 16:39:04 -07:00
|
|
|
}
|
2015-04-04 18:55:37 -06:00
|
|
|
}
|
|
|
|
|
2015-04-08 11:34:27 -06:00
|
|
|
#pragma mark - Lifecycle
|
2015-04-08 09:46:13 -06:00
|
|
|
|
2015-05-30 13:50:45 -06:00
|
|
|
- (void)removeFromSuperview
|
|
|
|
{
|
2015-04-10 01:15:19 -06:00
|
|
|
[_player pause];
|
2016-05-17 01:40:58 -06:00
|
|
|
if (_playbackRateObserverRegistered) {
|
2016-05-19 12:27:23 -06:00
|
|
|
[_player removeObserver:self forKeyPath:playbackRate context:nil];
|
2016-05-17 01:38:35 -06:00
|
|
|
_playbackRateObserverRegistered = NO;
|
2016-05-17 01:40:58 -06:00
|
|
|
}
|
2015-04-10 01:15:19 -06:00
|
|
|
_player = nil;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2016-04-28 06:25:45 -06:00
|
|
|
[self removePlayerLayer];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-12-22 16:39:04 -07:00
|
|
|
[_playerViewController.view removeFromSuperview];
|
|
|
|
_playerViewController = nil;
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-12-22 16:39:04 -07:00
|
|
|
[self removePlayerTimeObserver];
|
2015-08-05 06:52:30 -06:00
|
|
|
[self removePlayerItemObservers];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-04-10 01:15:19 -06:00
|
|
|
_eventDispatcher = nil;
|
2015-04-04 18:55:37 -06:00
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
2018-02-28 09:42:49 -07:00
|
|
|
|
2015-06-24 22:26:36 -06:00
|
|
|
[super removeFromSuperview];
|
2015-03-30 23:07:55 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|