diff --git a/android/src/main/java/jp/manse/BrightcovePlayerManager.java b/android/src/main/java/jp/manse/BrightcovePlayerManager.java index 421d559b..e19bf0fc 100755 --- a/android/src/main/java/jp/manse/BrightcovePlayerManager.java +++ b/android/src/main/java/jp/manse/BrightcovePlayerManager.java @@ -17,6 +17,7 @@ public class BrightcovePlayerManager extends SimpleViewManager { public static final String REACT_CLASS = "BrightcovePlayer"; public static final int COMMAND_SEEK_TO = 1; + public static final int COMMAND_STOP_PLAYBACK = 2; public static final String EVENT_READY = "ready"; public static final String EVENT_PLAY = "play"; public static final String EVENT_PAUSE = "pause"; @@ -108,7 +109,9 @@ public void setFullscreen(BrightcovePlayerView view, boolean fullscreen) { public Map getCommandsMap() { return MapBuilder.of( "seekTo", - COMMAND_SEEK_TO + COMMAND_SEEK_TO, + "stopPlayback", + COMMAND_STOP_PLAYBACK ); } @@ -121,6 +124,10 @@ public void receiveCommand(BrightcovePlayerView view, int commandType, @Nullable view.seekTo((int)(args.getDouble(0) * 1000)); return; } + case COMMAND_STOP_PLAYBACK:{ + view.stopPlayback(); + return; + } } } diff --git a/android/src/main/java/jp/manse/BrightcovePlayerView.java b/android/src/main/java/jp/manse/BrightcovePlayerView.java index 429e6098..d966fcbd 100755 --- a/android/src/main/java/jp/manse/BrightcovePlayerView.java +++ b/android/src/main/java/jp/manse/BrightcovePlayerView.java @@ -240,6 +240,13 @@ public void seekTo(int time) { this.playerVideoView.seekTo(time); } + //We need to stop the player to avoid a potential memory leak. + public void stopPlayback(){ + if(this.playerVideoView != null){ + this.playerVideoView.stopPlayback(); + } + } + private void updateBitRate() { if (this.bitRate == 0) return; ExoPlayerVideoDisplayComponent videoDisplay = ((ExoPlayerVideoDisplayComponent) this.playerVideoView.getVideoDisplay()); diff --git a/ios/BrightcovePlayer.m b/ios/BrightcovePlayer.m index 32c42fb4..241e2b05 100644 --- a/ios/BrightcovePlayer.m +++ b/ios/BrightcovePlayer.m @@ -1,8 +1,9 @@ #import "BrightcovePlayer.h" #import "BrightcovePlayerOfflineVideoManager.h" +#import "BrightcovePlayerUtil.h" @interface BrightcovePlayer () - +@property NSString* loadedVideoToken; @end @implementation BrightcovePlayer @@ -10,25 +11,53 @@ @implementation BrightcovePlayer - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setup]; + [self setupOfflineVideoTokenObserver]; } return self; } +- (void)dealloc +{ + [NSNotificationCenter.defaultCenter removeObserver:self]; +} + +- (void)setupOfflineVideoTokenObserver { + + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(didRemoveOfflineVideoToken:) name:BrightcovePlayerUtil.didRemoveOfflineVideoTokenNotificationName object:nil]; +} + +- (void)didRemoveOfflineVideoToken:(NSNotification*)notification { + NSDictionary *dict = notification.userInfo; + NSString* token = [dict valueForKey:BrightcovePlayerUtil.kDidRemoveOfflineVideoToken]; + + if ([self.loadedVideoToken isEqualToString:token]) { + NSLog(@"%@ %s TOKEN DELETED: %@", self, __FUNCTION__, token); + self.loadedVideoToken = nil; + } +} + - (void)setup { - _playbackController = [BCOVPlayerSDKManager.sharedManager createPlaybackController]; - _playbackController.delegate = self; - _playbackController.autoPlay = NO; - _playbackController.autoAdvance = YES; + + self.playbackController = [BCOVPlayerSDKManager.sharedManager createPlaybackController]; + self.playbackController.delegate = self; + self.playbackController.autoPlay = NO; + self.playbackController.autoAdvance = YES; - _playerView = [[BCOVPUIPlayerView alloc] initWithPlaybackController:self.playbackController options:nil controlsView:[BCOVPUIBasicControlView basicControlViewWithVODLayout] ]; - _playerView.delegate = self; - _playerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - _playerView.backgroundColor = UIColor.blackColor; + self.playerView = [[BCOVPUIPlayerView alloc] initWithPlaybackController:self.playbackController options:nil controlsView:[BCOVPUIBasicControlView basicControlViewWithVODLayout] ]; + self.playerView.delegate = self; + self.playerView.backgroundColor = UIColor.blackColor; - _targetVolume = 1.0; - _autoPlay = NO; + self.targetVolume = 1.0; + self.autoPlay = NO; - [self addSubview:_playerView]; + [self addSubview:self.playerView]; + self.playerView.translatesAutoresizingMaskIntoConstraints = NO; + NSArray* constraints = [NSArray arrayWithObjects:[self.playerView.topAnchor constraintEqualToAnchor:self.topAnchor], + [self.playerView.leftAnchor constraintEqualToAnchor:self.leftAnchor], + [self.playerView.rightAnchor constraintEqualToAnchor:self.rightAnchor], + [self.playerView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], + nil]; + [NSLayoutConstraint activateConstraints:constraints]; } - (void)setupService { @@ -39,27 +68,31 @@ - (void)setupService { } - (void)loadMovie { - if (_videoToken) { - BCOVVideo *video = [[BrightcovePlayerOfflineVideoManager sharedManager] videoObjectFromOfflineVideoToken:_videoToken]; - if (video) { - [self.playbackController setVideos: @[ video ]]; - } - return; - } - if (!_playbackService) return; - if (_videoId) { - [_playbackService findVideoWithVideoID:_videoId parameters:nil completion:^(BCOVVideo *video, NSDictionary *jsonResponse, NSError *error) { - if (video) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + if (self.videoToken) { + BCOVVideo *video = [[BrightcovePlayerOfflineVideoManager sharedManager] videoObjectFromOfflineVideoToken:self.videoToken]; + if (video && ![self.loadedVideoToken isEqualToString:self.videoToken]) { [self.playbackController setVideos: @[ video ]]; + self.loadedVideoToken = video.properties[kBCOVOfflineVideoTokenPropertyKey]; + NSLog(@"%@ %s SET VIDEO %@ FOR TOKEN: %@", self, __FUNCTION__, video.properties[kBCOVVideoPropertyKeyName], self.videoToken); } - }]; - } else if (_referenceId) { - [_playbackService findVideoWithReferenceID:_referenceId parameters:nil completion:^(BCOVVideo *video, NSDictionary *jsonResponse, NSError *error) { - if (video) { - [self.playbackController setVideos: @[ video ]]; - } - }]; - } + return; + } + if (!self.playbackService) return; + if (self.videoId) { + [self.playbackService findVideoWithVideoID:self.videoId parameters:nil completion:^(BCOVVideo *video, NSDictionary *jsonResponse, NSError *error) { + if (video) { + [self.playbackController setVideos: @[ video ]]; + } + }]; + } else if (self.referenceId) { + [self.playbackService findVideoWithReferenceID:self.referenceId parameters:nil completion:^(BCOVVideo *video, NSDictionary *jsonResponse, NSError *error) { + if (video) { + [self.playbackController setVideos: @[ video ]]; + } + }]; + } + }); } - (id)createPlaybackController { @@ -106,11 +139,11 @@ - (void)setAutoPlay:(BOOL)autoPlay { } - (void)setPlay:(BOOL)play { - if (_playing == play) return; + if (self.playing == play) return; if (play) { - [_playbackController play]; + [self.playbackController play]; } else { - [_playbackController pause]; + [self.playbackController pause]; } } diff --git a/ios/BrightcovePlayerUtil.h b/ios/BrightcovePlayerUtil.h index 43fffaa7..60558bfa 100644 --- a/ios/BrightcovePlayerUtil.h +++ b/ios/BrightcovePlayerUtil.h @@ -6,4 +6,6 @@ @interface BrightcovePlayerUtil : RCTEventEmitter @property (nonatomic) BCOVPlaybackService *playbackService; ++(NSString*)didRemoveOfflineVideoTokenNotificationName; ++(NSString*)kDidRemoveOfflineVideoToken; @end diff --git a/ios/BrightcovePlayerUtil.m b/ios/BrightcovePlayerUtil.m index 8650e180..bbd9ae5d 100644 --- a/ios/BrightcovePlayerUtil.m +++ b/ios/BrightcovePlayerUtil.m @@ -18,11 +18,21 @@ static NSString *const kPlaylistName = @"name"; static NSString *const kPlaylistDescription = @"description"; static NSString *const kPlaylistDuration = @"duration"; +static NSString *const didRemoveOfflineVideoTokenNotificationNameConst = @"DidRemoveOfflineVideoToken"; +static NSString *const kdidRemoveOfflineVideoTokenConst = @"DidRemoveOfflineVideoToken"; @implementation BrightcovePlayerUtil { bool hasListeners; } ++(NSString*)didRemoveOfflineVideoTokenNotificationName { + return didRemoveOfflineVideoTokenNotificationNameConst; +} + ++(NSString*)kDidRemoveOfflineVideoToken { + return kdidRemoveOfflineVideoTokenConst; +} + RCT_EXPORT_MODULE(); - (NSArray *)supportedEvents { @@ -45,14 +55,39 @@ - (void)stopObserving { reject(kErrorCode, error.description, error); return; } - [[BrightcovePlayerOfflineVideoManager sharedManager] requestVideoDownload:video parameters:[self generateDownloadParameterWithBitRate:bitRate] completion:^(BCOVOfflineVideoToken offlineVideoToken, NSError *error) { + + AVURLAsset *avURLAsset = [[BrightcovePlayerOfflineVideoManager sharedManager] urlAssetForVideo:video error:nil]; + // If mediaSelections is `nil` the SDK will default to the AVURLAsset's `preferredMediaSelection` + NSArray *mediaSelections = nil; + + if (@available(iOS 11.0, *)) { + mediaSelections = avURLAsset.allMediaSelections; + + AVMediaSelectionGroup *legibleMediaSelectionGroup = [avURLAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; + AVMediaSelectionGroup *audibleMediaSelectionGroup = [avURLAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + + int counter = 0; + for (AVMediaSelection *s in mediaSelections) + { + AVMediaSelectionOption *legibleMediaSelectionOption = [s selectedMediaOptionInMediaSelectionGroup:legibleMediaSelectionGroup]; + AVMediaSelectionOption *audibleMediaSelectionOption = [s selectedMediaOptionInMediaSelectionGroup:audibleMediaSelectionGroup]; + + NSLog(@"AVMediaSelection option %i | legible display name: %@", counter, legibleMediaSelectionOption.displayName ?: @"nil"); + NSLog(@"AVMediaSelection option %i | audible display name: %@", counter, audibleMediaSelectionOption.displayName ?: @"nil"); + + counter++; + } + } + + [BrightcovePlayerOfflineVideoManager sharedManager].delegate = self; + [[BrightcovePlayerOfflineVideoManager sharedManager] requestVideoDownload:video mediaSelections:mediaSelections parameters:[self generateDownloadParameterWithBitRate:bitRate] completion:^(BCOVOfflineVideoToken offlineVideoToken, NSError *error) { if (error) { reject(kErrorCode, error.description, error); return; } [NSUserDefaults.standardUserDefaults setObject: @{kUserDefaultKeyOfflineAccountId: accountId, kUserDefaultKeyOfflineVideoId: video.properties[kBCOVVideoPropertyKeyId] - } + } forKey:[kUserDefaultKeyOfflinePrefix stringByAppendingString:offlineVideoToken]]; [NSUserDefaults.standardUserDefaults synchronize]; [self sendOfflineNotification]; @@ -68,20 +103,43 @@ - (void)stopObserving { reject(kErrorCode, error.description, error); return; } - [BrightcovePlayerOfflineVideoManager sharedManager].delegate = self; - [[BrightcovePlayerOfflineVideoManager sharedManager] requestVideoDownload:video parameters:[self generateDownloadParameterWithBitRate:bitRate] completion:^(BCOVOfflineVideoToken offlineVideoToken, NSError *error) { - if (error) { - reject(kErrorCode, error.description, error); - return; + + AVURLAsset *avURLAsset = [[BrightcovePlayerOfflineVideoManager sharedManager] urlAssetForVideo:video error:nil]; + // If mediaSelections is `nil` the SDK will default to the AVURLAsset's `preferredMediaSelection` + NSArray *mediaSelections = nil; + + if (@available(iOS 11.0, *)) { + mediaSelections = avURLAsset.allMediaSelections;; + AVMediaSelectionGroup *legibleMediaSelectionGroup = [avURLAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; + AVMediaSelectionGroup *audibleMediaSelectionGroup = [avURLAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + + int counter = 0; + for (AVMediaSelection *s in mediaSelections) + { + AVMediaSelectionOption *legibleMediaSelectionOption = [s selectedMediaOptionInMediaSelectionGroup:legibleMediaSelectionGroup]; + AVMediaSelectionOption *audibleMediaSelectionOption = [s selectedMediaOptionInMediaSelectionGroup:audibleMediaSelectionGroup]; + + NSLog(@"AVMediaSelection option %i | legible display name: %@", counter, legibleMediaSelectionOption.displayName ?: @"nil"); + NSLog(@"AVMediaSelection option %i | audible display name: %@", counter, audibleMediaSelectionOption.displayName ?: @"nil"); + + counter++; } - [NSUserDefaults.standardUserDefaults setObject: @{kUserDefaultKeyOfflineAccountId: accountId, - kUserDefaultKeyOfflineVideoId: video.properties[kBCOVVideoPropertyKeyId] - } - forKey:[kUserDefaultKeyOfflinePrefix stringByAppendingString:offlineVideoToken]]; - [NSUserDefaults.standardUserDefaults synchronize]; - [self sendOfflineNotification]; - resolve(offlineVideoToken); - }]; + } + + [BrightcovePlayerOfflineVideoManager sharedManager].delegate = self; + [[BrightcovePlayerOfflineVideoManager sharedManager] requestVideoDownload:video mediaSelections:mediaSelections parameters:[self generateDownloadParameterWithBitRate:bitRate] completion:^(BCOVOfflineVideoToken offlineVideoToken, NSError *error) { + if (error) { + reject(kErrorCode, error.description, error); + return; + } + [NSUserDefaults.standardUserDefaults setObject: @{kUserDefaultKeyOfflineAccountId: accountId, + kUserDefaultKeyOfflineVideoId: video.properties[kBCOVVideoPropertyKeyId] + } + forKey:[kUserDefaultKeyOfflinePrefix stringByAppendingString:offlineVideoToken]]; + [NSUserDefaults.standardUserDefaults synchronize]; + [self sendOfflineNotification]; + resolve(offlineVideoToken); + }]; }]; } @@ -94,10 +152,15 @@ - (void)stopObserving { reject(kErrorCode, kErrorMessageDelete, nil); return; } + + BCOVVideo *video = [[BrightcovePlayerOfflineVideoManager sharedManager] videoObjectFromOfflineVideoToken:videoToken]; + NSLog(@"%@ %s ATTEMPTING TO DELETE VIDEO %@ WITH TOKEN: %@", self, __FUNCTION__, video.properties[kBCOVVideoPropertyKeyName], videoToken); + [BrightcovePlayerOfflineVideoManager sharedManager].delegate = self; [[BrightcovePlayerOfflineVideoManager sharedManager] cancelVideoDownload:videoToken]; [[BrightcovePlayerOfflineVideoManager sharedManager] deleteOfflineVideo:videoToken]; [NSUserDefaults.standardUserDefaults removeObjectForKey:[kUserDefaultKeyOfflinePrefix stringByAppendingString:videoToken]]; + [NSNotificationCenter.defaultCenter postNotificationName:BrightcovePlayerUtil.didRemoveOfflineVideoTokenNotificationName object:nil userInfo:@{BrightcovePlayerUtil.kDidRemoveOfflineVideoToken: videoToken}]; [NSUserDefaults.standardUserDefaults synchronize]; [self sendOfflineNotification]; resolve(nil); diff --git a/src/BrightcovePlayer.js b/src/BrightcovePlayer.js index d8b9606c..e1c7fdea 100644 --- a/src/BrightcovePlayer.js +++ b/src/BrightcovePlayer.js @@ -110,6 +110,19 @@ BrightcovePlayer.prototype.seekTo = Platform.select({ } }); +BrightcovePlayer.prototype.stopPlayback = Platform.select({ + ios: function () { + //no method for ios + }, + android: function () { + UIManager.dispatchViewManagerCommand( + ReactNative.findNodeHandle(this._root), + UIManager.BrightcovePlayer.Commands.stopPlayback, + [] + ); + } +}); + BrightcovePlayer.propTypes = { ...(ViewPropTypes || View.propTypes), policyKey: PropTypes.string,