diff --git a/README.md b/README.md index 74f01ec5..425f5af0 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,7 @@ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Conf | **`position`** | Returns promise that then returns the current position of the player in seconds. | `none` | | **`toggleSpeed`** | Toggles the player speed one of `0.5`, `1.0`, `1.5`, `2.0`. | `none` | | **`setSpeed`** | Sets the player speed. | `Double` | +| **`setVolume`** | Sets the player volume. | `Double` | | **`setPlaylistIndex`** | Sets the current playing item in the loaded playlist. | `Int` | | **`setControls`** | Sets the display of the control buttons on the player. | `Boolean` | | **`setLockScreenControls`** | *(iOS only)* Sets the locks screen controls for the currently playing media, can be used to control what player to show the controls for. | `Boolean` | @@ -393,6 +394,7 @@ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Conf | **`getAudioTracks`** | Returns promise that returns an array of [AudioTracks](#AudioTrack) | `none` | | **`getCurrentAudioTrack`** | Returns promise that returns the index of the current audio track in array returned by getAudioTracks | `none` | | **`setCurrentAudioTrack`** | Sets the current audio track to the audio track at the specified index in the array returned by getAudioTracks | `Int` | +| **`setCurrentCaptions`** | Turns off captions when argument is 0. Setting argument to another integer, sets captions to track at playlistItem.tracks[integer - 1] | `Int` | ## Available callbacks diff --git a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java index 29c26ad7..b466786e 100644 --- a/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java +++ b/android/src/main/java/com/appgoalz/rnjwplayer/RNJWPlayerModule.java @@ -361,6 +361,24 @@ public void execute (NativeViewHierarchyManager nvhm) { } } + @ReactMethod + public void setCurrentCaptions(final int reactTag, final int index) { + try { + UIManagerModule uiManager = mReactContext.getNativeModule(UIManagerModule.class); + uiManager.addUIBlock(new UIBlock() { + public void execute (NativeViewHierarchyManager nvhm) { + RNJWPlayerView playerView = (RNJWPlayerView) nvhm.resolveView(reactTag); + + if (playerView != null && playerView.mPlayer != null) { + playerView.mPlayer.setCurrentCaptions(index); + } + } + }); + } catch (IllegalViewOperationException e) { + throw e; + } + } + private int stateToInt(PlayerState playerState) { switch (playerState) { case IDLE: diff --git a/index.d.ts b/index.d.ts index 9e8fd7b7..aa5d77b8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -142,6 +142,7 @@ declare module "react-native-jw-media-player" { processSpcUrl?: string; fairplayCertUrl?: string; contentUUID?: string; + viewOnly: boolean; } interface PropsType { config: Config; @@ -176,6 +177,7 @@ declare module "react-native-jw-media-player" { stop(): void; toggleSpeed(): void; setSpeed(speed: number): void; + setVolume(volume: number): void; setPlaylistIndex(index: number): void; setControls(show: boolean): void; setLockScreenControls(show: boolean): void; diff --git a/index.js b/index.js index b37ba70f..db2ce0a0 100644 --- a/index.js +++ b/index.js @@ -194,6 +194,7 @@ export default class JWPlayer extends Component { getAudioTracks: PropTypes.func, getCurrentAudioTrack: PropTypes.func, setCurrentAudioTrack: PropTypes.func, + setCurrentCaptions: PropTypes.func, onAudioTracks: PropTypes.func, }; @@ -400,6 +401,15 @@ export default class JWPlayer extends Component { } } + setCurrentCaptions(index) { + if (RNJWPlayerManager) { + RNJWPlayerManager.setCurrentCaptions( + this.getRNJWPlayerBridgeHandle(), + index + ); + } + } + getRNJWPlayerBridgeHandle() { return ReactNative.findNodeHandle(this.refs[RCT_RNJWPLAYER_REF]); } diff --git a/ios/RNJWPlayer.podspec b/ios/RNJWPlayer.podspec index a2b4a95b..9f056893 100644 --- a/ios/RNJWPlayer.podspec +++ b/ios/RNJWPlayer.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.platform = :ios, "10.0" s.source = { :git => "https://github.com/chaimPaneth/react-native-jw-media-player.git", :tag => "v#{s.version}" } s.source_files = "RNJWPlayer/*.{h,m}" - s.dependency 'JWPlayerKit', '~> 4.4.0' + s.dependency 'JWPlayerKit', '~> 4.5.0' s.dependency 'google-cast-sdk', '~> 4.7.0' s.dependency 'React' # s.static_framework = true diff --git a/ios/RNJWPlayer/RNJWPlayerView.h b/ios/RNJWPlayer/RNJWPlayerView.h index 4ffecbac..3da79521 100644 --- a/ios/RNJWPlayer/RNJWPlayerView.h +++ b/ios/RNJWPlayer/RNJWPlayerView.h @@ -8,10 +8,13 @@ #import #import #import +#import "RNJWPlayerViewController.h" + +@class RNJWPlayerViewController; @interface RNJWPlayerView : UIView -@property(nonatomic, strong)JWPlayerViewController* playerViewController; +@property(nonatomic, strong)RNJWPlayerViewController* playerViewController; @property(nonatomic, strong)JWPlayerView *playerView; @property(nonatomic)BOOL pipEnabled; diff --git a/ios/RNJWPlayer/RNJWPlayerView.m b/ios/RNJWPlayer/RNJWPlayerView.m index 7f7ab4b3..1032a41f 100644 --- a/ios/RNJWPlayer/RNJWPlayerView.m +++ b/ios/RNJWPlayer/RNJWPlayerView.m @@ -372,13 +372,12 @@ -(JWPlayerItem*)getPlayerItem:item NSURL *fileUrl = [NSURL URLWithString:file]; NSString *label = [item objectForKey:@"label"]; - JWMediaTrack *trackItem = [JWMediaTrack init]; JWCaptionTrackBuilder* trackBuilder = [[JWCaptionTrackBuilder alloc] init]; [trackBuilder file:fileUrl]; [trackBuilder label:label]; - trackItem = [trackBuilder buildAndReturnError:&error]; + JWMediaTrack *trackItem = [trackBuilder buildAndReturnError:&error]; [tracksArray addObject:trackItem]; } @@ -597,8 +596,8 @@ -(void)setupPlayerViewController:config :(JWPlayerConfiguration*)playerConfig { [self dismissPlayerViewController]; - _playerViewController = [JWPlayerViewController new]; - _playerViewController.delegate = self; + _playerViewController = [RNJWPlayerViewController new]; + _playerViewController.parentView = self; id interfaceBehavior = config[@"interfaceBehavior"]; if ((interfaceBehavior != nil) && (interfaceBehavior != (id)[NSNull null])) { @@ -620,6 +619,11 @@ -(void)setupPlayerViewController:config :(JWPlayerConfiguration*)playerConfig _playerViewController.enableLockScreenControls = YES; } + id allowsPictureInPicturePlayback = config[@"allowsPictureInPicturePlayback"]; + if ((allowsPictureInPicturePlayback != nil && allowsPictureInPicturePlayback != (id)[NSNull null])) { + _playerViewController.allowsPictureInPicturePlayback = allowsPictureInPicturePlayback; + } + id styling = config[@"styling"]; [self setStyling:styling]; @@ -685,13 +689,14 @@ -(void)presentPlayerViewController:(JWPlayerConfiguration*)configuration _playerViewController.interfaceBehavior = JWInterfaceBehaviorHidden; } } - - _playerViewController.playerView.delegate = self; - _playerViewController.player.delegate = self; - _playerViewController.player.playbackStateDelegate = self; - _playerViewController.player.adDelegate = self; - _playerViewController.player.avDelegate = self; - _playerViewController.player.contentKeyDataSource = self; + +// _playerViewController.delegate = self; +// _playerViewController.playerView.delegate = self; +// _playerViewController.player.delegate = self; +// _playerViewController.player.playbackStateDelegate = self; +// _playerViewController.player.adDelegate = self; +// _playerViewController.player.avDelegate = self; +// _playerViewController.player.contentKeyDataSource = self; } #pragma mark - JWPlayer View helpers @@ -1256,6 +1261,13 @@ - (void)jwplayer:(id)player playbackRateChangedTo:(double)rate at:(NST } } +- (void)jwplayer:(id)player updatedCues:(NSArray * _Nonnull)cues +{ + if (_playerViewController) { + [_playerViewController jwplayer:player updatedCues:cues]; + } +} + #pragma mark - JWPlayer Ad Delegate - (void)jwplayer:(id _Nonnull)player adEvent:(JWAdEvent * _Nonnull)event { diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.h b/ios/RNJWPlayer/RNJWPlayerViewController.h new file mode 100644 index 00000000..fe39d255 --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerViewController.h @@ -0,0 +1,27 @@ +// +// RNJWPlayerViewController.m +// RNJWPlayer +// +// Created by Chaim Paneth on 3/30/22. +// + +#if __has_include("React/RCTViewManager.h") +#import "React/RCTViewManager.h" +#else +#import "RCTViewManager.h" +#endif + +#import +#import +#import +#import +#import "JWPlayerKit/JWPlayerObjCViewController.h" +#import "RNJWPlayerView.h" + +@class RNJWPlayerView; + +@interface RNJWPlayerViewController : JWPlayerObjCViewController + +@property(nonatomic, strong)RNJWPlayerView *parentView; + +@end diff --git a/ios/RNJWPlayer/RNJWPlayerViewController.m b/ios/RNJWPlayer/RNJWPlayerViewController.m new file mode 100644 index 00000000..af176232 --- /dev/null +++ b/ios/RNJWPlayer/RNJWPlayerViewController.m @@ -0,0 +1,587 @@ +// +// RNJWPlayerViewController.m +// RNJWPlayer +// +// Created by Chaim Paneth on 3/30/22. +// + +#import "RNJWPlayerViewController.h" + +@implementation RNJWPlayerViewController + +- (instancetype)init +{ + self = [super init]; + if (self) { + self.delegate = self; + self.playerView.delegate = self; + self.player.delegate = self; + self.player.playbackStateDelegate = self; + self.player.adDelegate = self; + self.player.avDelegate = self; + } + return self; +} + +#pragma mark - JWPlayer Delegate + +- (void)jwplayerIsReady:(id)player +{ + if (_parentView.onPlayerReady) { + _parentView.onPlayerReady(@{}); + } +} + +- (void)jwplayer:(id)player failedWithError:(NSUInteger)code message:(NSString *)message +{ + if (_parentView.onPlayerError) { + _parentView.onPlayerError(@{@"error": message}); + } +} + +- (void)jwplayer:(id)player failedWithSetupError:(NSUInteger)code message:(NSString *)message +{ + if (_parentView.onSetupPlayerError) { + _parentView.onSetupPlayerError(@{@"error": message}); + } +} + +- (void)jwplayer:(id)player encounteredWarning:(NSUInteger)code message:(NSString *)message +{ + if (_parentView.onPlayerWarning) { + _parentView.onPlayerWarning(@{@"warning": message}); + } +} + +- (void)jwplayer:(id _Nonnull)player encounteredAdError:(NSUInteger)code message:(NSString * _Nonnull)message { + if (_parentView.onPlayerAdError) { + _parentView.onPlayerAdError(@{@"error": message}); + } +} + + +- (void)jwplayer:(id _Nonnull)player encounteredAdWarning:(NSUInteger)code message:(NSString * _Nonnull)message { + if (_parentView.onPlayerAdWarning) { + _parentView.onPlayerAdWarning(@{@"warning": message}); + } +} + + +#pragma mark - JWPlayer View Delegate + +- (void)playerView:(JWPlayerView *)view sizeChangedFrom:(CGSize)oldSize to:(CGSize)newSize +{ + if (_parentView.onPlayerSizeChange) { + NSMutableDictionary* oldSizeDict = [[NSMutableDictionary alloc] init]; + [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.width] forKey:@"width"]; + [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.height] forKey:@"height"]; + + NSMutableDictionary* newSizeDict = [[NSMutableDictionary alloc] init]; + [newSizeDict setObject:[NSNumber numberWithFloat: newSize.width] forKey:@"width"]; + [newSizeDict setObject:[NSNumber numberWithFloat: newSize.height] forKey:@"height"]; + + NSMutableDictionary* sizesDict = [[NSMutableDictionary alloc] init]; + [sizesDict setObject:oldSizeDict forKey:@"oldSize"]; + [sizesDict setObject:newSizeDict forKey:@"newSize"]; + + NSError* error = nil; + NSData* data = [NSJSONSerialization dataWithJSONObject:sizesDict options:NSJSONWritingPrettyPrinted error: &error]; + _parentView.onPlayerSizeChange(@{@"sizes": data}); + } +} + +#pragma mark - JWPlayer View Controller Delegate + +- (void)playerViewController:(JWPlayerViewController *)controller sizeChangedFrom:(CGSize)oldSize to:(CGSize)newSize +{ + if (_parentView.onPlayerSizeChange) { + NSMutableDictionary* oldSizeDict = [[NSMutableDictionary alloc] init]; + [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.width] forKey:@"width"]; + [oldSizeDict setObject:[NSNumber numberWithFloat: oldSize.height] forKey:@"height"]; + + NSMutableDictionary* newSizeDict = [[NSMutableDictionary alloc] init]; + [newSizeDict setObject:[NSNumber numberWithFloat: newSize.width] forKey:@"width"]; + [newSizeDict setObject:[NSNumber numberWithFloat: newSize.height] forKey:@"height"]; + + NSMutableDictionary* sizesDict = [[NSMutableDictionary alloc] init]; + [sizesDict setObject:oldSizeDict forKey:@"oldSize"]; + [sizesDict setObject:newSizeDict forKey:@"newSize"]; + + NSError* error = nil; + NSData* data = [NSJSONSerialization dataWithJSONObject:sizesDict options:NSJSONWritingPrettyPrinted error: &error]; + _parentView.onPlayerSizeChange(@{@"sizes": data}); + } +} + +- (void)playerViewController:(JWPlayerViewController *)controller screenTappedAt:(CGPoint)position +{ + if (_parentView.onScreenTapped) { + _parentView.onScreenTapped(@{@"x": @(position.x), @"y": @(position.y)}); + } +} + +- (void)playerViewController:(JWPlayerViewController *)controller controlBarVisibilityChanged:(BOOL)isVisible frame:(CGRect)frame +{ + if (_parentView.onControlBarVisible) { + _parentView.onControlBarVisible(@{@"visible": @(isVisible)}); + } +} + +- (JWFullScreenViewController * _Nullable)playerViewControllerWillGoFullScreen:(JWPlayerViewController * _Nonnull)controller { + if (_parentView.onFullScreenRequested) { + _parentView.onFullScreenRequested(@{}); + } + return nil; +} + +- (void)playerViewControllerDidGoFullScreen:(JWPlayerViewController *)controller +{ + if (_parentView.onFullScreen) { + _parentView.onFullScreen(@{}); + } +} + +- (void)playerViewControllerWillDismissFullScreen:(JWPlayerViewController *)controller +{ + if (_parentView.onFullScreenExitRequested) { + _parentView.onFullScreenExitRequested(@{}); + } +} + +- (void)playerViewControllerDidDismissFullScreen:(JWPlayerViewController *)controller +{ + if (_parentView.onFullScreenExit) { + _parentView.onFullScreenExit(@{}); + } +} + +- (void)playerViewController:(JWPlayerViewController *)controller relatedMenuClosedWithMethod:(enum JWRelatedInteraction)method +{ + +} + +- (void)playerViewController:(JWPlayerViewController *)controller relatedMenuOpenedWithItems:(NSArray *)items withMethod:(enum JWRelatedInteraction)method +{ + +} + +- (void)playerViewController:(JWPlayerViewController *)controller relatedItemBeganPlaying:(JWPlayerItem *)item atIndex:(NSInteger)index withMethod:(enum JWRelatedInteraction)method +{ + +} + +#pragma mark - DRM Delegate + +- (void)contentIdentifierForURL:(NSURL * _Nonnull)url completionHandler:(void (^ _Nonnull)(NSData * _Nullable))handler { + NSData *uuidData = [_parentView.contentUUID dataUsingEncoding:NSUTF8StringEncoding]; + handler(uuidData); +} + +- (void)appIdentifierForURL:(NSURL * _Nonnull)url completionHandler:(void (^ _Nonnull)(NSData * _Nullable))handler { + NSURL *certURL = [NSURL URLWithString:_parentView.fairplayCertUrl]; + NSData *certData = [NSData dataWithContentsOfURL:certURL]; + handler(certData); +} + +- (void)contentKeyWithSPCData:(NSData * _Nonnull)spcData completionHandler:(void (^ _Nonnull)(NSData * _Nullable, NSDate * _Nullable, NSString * _Nullable))handler { + NSMutableURLRequest *ckcRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:_parentView.processSpcUrl]]; + [ckcRequest setHTTPMethod:@"POST"]; + [ckcRequest setHTTPBody:spcData]; + [ckcRequest addValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; + + [[[NSURLSession sharedSession] dataTaskWithRequest:ckcRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (error != nil || (httpResponse != nil && httpResponse.statusCode != 200)) { + handler(nil, nil, nil); + return; + } + + handler(data, nil, nil); + }] resume]; +} + +#pragma mark - AV Picture In Picture Delegate + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { + [_parentView.playerViewController.player play]; + } +} + +- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController +{ + +} + +- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController +{ + +} + +- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController +{ + +} + +- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error +{ + +} + +- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController +{ + +} + +- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler +{ + +} + +#pragma mark - JWPlayer State Delegate + +- (void)jwplayerContentIsBuffering:(id)player +{ + if (_parentView.onBuffer) { + _parentView.onBuffer(@{}); + } +} + +- (void)jwplayer:(id)player isBufferingWithReason:(enum JWBufferReason)reason +{ + if (_parentView.onBuffer) { + _parentView.onBuffer(@{}); + } +} + +- (void)jwplayer:(id)player updatedBuffer:(double)percent position:(JWTimeData *)time +{ + if (_parentView.onUpdateBuffer) { + _parentView.onUpdateBuffer(@{@"percent": @(percent), @"position": time}); + } +} + +- (void)jwplayer:(id)player didFinishLoadingWithTime:(NSTimeInterval)loadTime +{ + if (_parentView.onLoaded) { + _parentView.onLoaded(@{}); + } +} + +- (void)jwplayer:(id)player isAttemptingToPlay:(JWPlayerItem *)playlistItem reason:(enum JWPlayReason)reason +{ + if (_parentView.onAttemptPlay) { + _parentView.onAttemptPlay(@{}); + } +} + +- (void)jwplayer:(id)player isPlayingWithReason:(enum JWPlayReason)reason +{ + if (_parentView.onPlay) { + _parentView.onPlay(@{}); + } + + _parentView.userPaused = NO; + _parentView.wasInterrupted = NO; +} + +- (void)jwplayer:(id)player willPlayWithReason:(enum JWPlayReason)reason +{ + if (_parentView.onBeforePlay) { + _parentView.onBeforePlay(@{}); + } +} + +- (void)jwplayer:(id)player didPauseWithReason:(enum JWPauseReason)reason +{ + if (_parentView.onPause) { + _parentView.onPause(@{}); + } + + if (!_parentView.wasInterrupted) { + _parentView.userPaused = YES; + } +} + +- (void)jwplayer:(id)player didBecomeIdleWithReason:(enum JWIdleReason)reason +{ + if (_parentView.onIdle) { + _parentView.onIdle(@{}); + } +} + +- (void)jwplayer:(id)player isVisible:(BOOL)isVisible +{ + if (_parentView.onVisible) { + _parentView.onVisible(@{@"visible": @(isVisible)}); + } +} + +- (void)jwplayerContentWillComplete:(id)player +{ + if (_parentView.onBeforeComplete) { + _parentView.onBeforeComplete(@{}); + } +} + +- (void)jwplayerContentDidComplete:(id)player +{ + if (_parentView.onComplete) { + _parentView.onComplete(@{}); + } +} + +- (void)jwplayer:(id)player didLoadPlaylistItem:(JWPlayerItem *)item at:(NSUInteger)index +{ + if (_parentView.onPlaylistItem) { + NSMutableDictionary* sourceDict = [[NSMutableDictionary alloc] init]; + for (JWVideoSource* source in item.videoSources) { + [sourceDict setObject:source.file forKey:@"file"]; + [sourceDict setObject:source.label forKey:@"label"]; + [sourceDict setObject:@(source.defaultVideo) forKey:@"default"]; + } + + NSMutableDictionary* schedDict = [[NSMutableDictionary alloc] init]; + for (JWAdBreak* sched in item.adSchedule) { + [schedDict setObject:sched.offset forKey:@"offset"]; + [schedDict setObject:sched.tags forKey:@"tags"]; + [schedDict setObject:@(sched.type) forKey:@"type"]; + } + + NSMutableDictionary* trackDict = [[NSMutableDictionary alloc] init]; + for (JWMediaTrack* track in item.mediaTracks) { + [trackDict setObject:track.file forKey:@"file"]; + [trackDict setObject:track.label forKey:@"label"]; + [trackDict setObject:@(track.defaultTrack) forKey:@"default"]; + } + + NSDictionary* itemDict = [NSDictionary dictionaryWithObjectsAndKeys: + item.mediaId, @"mediaId", + item.title, @"title", + item.description, @"description", + item.posterImage.absoluteString, @"image", + @(item.startTime), @"startTime", + item.vmapURL.absoluteString, @"adVmap", + item.recommendations.absoluteString, @"recommendations", + sourceDict, @"sources", + schedDict, @"adSchedule", + trackDict, @"tracks", + nil]; + + NSError *error; + NSData *data = [NSJSONSerialization dataWithJSONObject:itemDict options:NSJSONWritingPrettyPrinted error: &error]; + + _parentView.onPlaylistItem(@{@"playlistItem": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], @"index": [NSNumber numberWithInteger:index]}); + } + + [item addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil]; +} + +- (void)jwplayer:(id)player didLoadPlaylist:(NSArray *)playlist +{ + if (_parentView.onPlaylist) { + NSMutableArray* playlistArray = [[NSMutableArray alloc] init]; + + for (JWPlayerItem* item in playlist) { + NSMutableDictionary* sourceDict = [[NSMutableDictionary alloc] init]; + for (JWVideoSource* source in item.videoSources) { + [sourceDict setObject:source.file forKey:@"file"]; + [sourceDict setObject:source.label forKey:@"label"]; + [sourceDict setObject:@(source.defaultVideo) forKey:@"default"]; + } + + NSMutableDictionary* schedDict = [[NSMutableDictionary alloc] init]; + for (JWAdBreak* sched in item.adSchedule) { + [schedDict setObject:sched.offset forKey:@"offset"]; + [schedDict setObject:sched.tags forKey:@"tags"]; + [schedDict setObject:@(sched.type) forKey:@"type"]; + } + + NSMutableDictionary* trackDict = [[NSMutableDictionary alloc] init]; + for (JWMediaTrack* track in item.mediaTracks) { + [trackDict setObject:track.file forKey:@"file"]; + [trackDict setObject:track.label forKey:@"label"]; + [trackDict setObject:@(track.defaultTrack) forKey:@"default"]; + } + + NSDictionary* itemDict = [NSDictionary dictionaryWithObjectsAndKeys: + item.mediaId, @"mediaId", + item.title, @"title", + item.description, @"description", + item.posterImage.absoluteString, @"image", + @(item.startTime), @"startTime", + item.vmapURL.absoluteString, @"adVmap", + item.recommendations.absoluteString, @"recommendations", + sourceDict, @"sources", + schedDict, @"adSchedule", + trackDict, @"tracks", + nil]; + + [playlistArray addObject:itemDict]; + } + + NSError *error; + NSData* data = [NSJSONSerialization dataWithJSONObject:playlistArray options:NSJSONWritingPrettyPrinted error: &error]; + + _parentView.onPlaylist(@{@"playlist": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); + } +} + +- (void)jwplayerPlaylistHasCompleted:(id)player +{ + if (_parentView.onPlaylistComplete) { + _parentView.onPlaylistComplete(@{}); + } +} + +- (void)jwplayer:(id)player usesMediaType:(enum JWMediaType)type +{ + +} + +- (void)jwplayer:(id)player seekedFrom:(NSTimeInterval)oldPosition to:(NSTimeInterval)newPosition +{ + if (_parentView.onSeek) { + _parentView.onSeek(@{@"from": @(oldPosition), @"to": @(newPosition)}); + } +} + +- (void)jwplayerHasSeeked:(id)player +{ + if (_parentView.onSeeked) { + _parentView.onSeeked(@{}); + } +} + +- (void)jwplayer:(id)player playbackRateChangedTo:(double)rate at:(NSTimeInterval)time +{ + +} + +- (void)jwplayer:(id)player updatedCues:(NSArray * _Nonnull)cues +{ + +} + +#pragma mark - JWPlayer Ad Delegate + +- (void)jwplayer:(id _Nonnull)player adEvent:(JWAdEvent * _Nonnull)event { + if (_parentView.onAdEvent) { + _parentView.onAdEvent(@{@"client": @(event.client), @"type": @(event.type)}); + } +} + +#pragma mark - JWPlayer Cast Delegate + +- (void)castController:(JWCastController * _Nonnull)controller castingBeganWithDevice:(JWCastingDevice * _Nonnull)device { + if (_parentView.onCasting) { + _parentView.onCasting(@{}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller castingEndedWithError:(NSError * _Nullable)error { + if (_parentView.onCastingEnded) { + _parentView.onCastingEnded(@{@"error": error}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller castingFailedWithError:(NSError * _Nonnull)error { + if (_parentView.onCastingFailed) { + _parentView.onCastingFailed(@{@"error": error}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller connectedTo:(JWCastingDevice * _Nonnull)device { + if (_parentView.onConnectedToCastingDevice) { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + [dict setObject:device.name forKey:@"name"]; + [dict setObject:device.identifier forKey:@"identifier"]; + + NSError *error; + NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error: &error]; + + _parentView.onConnectedToCastingDevice(@{@"device": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller connectionFailedWithError:(NSError * _Nonnull)error { + if (_parentView.onConnectionFailed) { + _parentView.onConnectionFailed(@{@"error": error}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller connectionRecoveredWithDevice:(JWCastingDevice * _Nonnull)device { + if (_parentView.onConnectionRecovered) { + _parentView.onConnectionRecovered(@{}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller connectionSuspendedWithDevice:(JWCastingDevice * _Nonnull)device { + if (_parentView.onConnectionTemporarilySuspended) { + _parentView.onConnectionTemporarilySuspended(@{}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller devicesAvailable:(NSArray * _Nonnull)devices { + _parentView.availableDevices = devices; + + if (_parentView.onCastingDevicesAvailable) { + NSMutableArray *devicesInfo = [[NSMutableArray alloc] init]; + + for (JWCastingDevice *device in devices) { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + [dict setObject:device.name forKey:@"name"]; + [dict setObject:device.identifier forKey:@"identifier"]; + + [devicesInfo addObject:dict]; + } + + NSError *error; + NSData *data = [NSJSONSerialization dataWithJSONObject:devicesInfo options:NSJSONWritingPrettyPrinted error: &error]; + + _parentView.onCastingDevicesAvailable(@{@"devices": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}); + } +} + +- (void)castController:(JWCastController * _Nonnull)controller disconnectedWithError:(NSError * _Nullable)error { + if (_parentView.onDisconnectedFromCastingDevice) { + _parentView.onDisconnectedFromCastingDevice(@{@"error": error}); + } +} + +#pragma mark - JWPlayer AV Delegate + +- (void)jwplayer:(id _Nonnull)player audioTracksUpdated:(NSArray * _Nonnull)levels { + if (_parentView.onAudioTracks) { + _parentView.onAudioTracks(@{}); + } +} + +- (void)jwplayer:(id _Nonnull)player audioTrackChanged:(NSInteger)currentLevel { + +} + +- (void)jwplayer:(id _Nonnull)player captionPresented:(NSArray * _Nonnull)caption at:(JWTimeData * _Nonnull)time { + +} + +- (void)jwplayer:(id _Nonnull)player captionTrackChanged:(NSInteger)index { + +} + +- (void)jwplayer:(id _Nonnull)player qualityLevelChanged:(NSInteger)currentLevel { + +} + +- (void)jwplayer:(id _Nonnull)player qualityLevelsUpdated:(NSArray * _Nonnull)levels { + +} + +- (void)jwplayer:(id _Nonnull)player updatedCaptionList:(NSArray * _Nonnull)options { + +} + +@end diff --git a/ios/RNJWPlayer/RNJWPlayerViewManager.m b/ios/RNJWPlayer/RNJWPlayerViewManager.m index 6b21b149..e86e56ac 100644 --- a/ios/RNJWPlayer/RNJWPlayerViewManager.m +++ b/ios/RNJWPlayer/RNJWPlayerViewManager.m @@ -491,4 +491,19 @@ - (UIView*)view }]; } +RCT_EXPORT_METHOD(setCurrentCaptions: (nonnull NSNumber *)reactTag: (nonnull NSNumber *)index) { + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RNJWPlayerView *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RNJWPlayerView class]] || (view.playerView == nil && view.playerViewController == nil)) { + RCTLogError(@"Invalid view returned from registry, expecting RNJWPlayerView, got: %@", view); + } else { + if (view.playerView) { + [view.playerView.player setCurrentCaptionsTrack:[index integerValue] + 1]; + } else if (view.playerViewController) { + [view.playerViewController.player setCurrentCaptionsTrack:[index integerValue] + 1]; + } + } + }]; +} + @end diff --git a/package.json b/package.json index 3507ac14..f4e389d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-jw-media-player", - "version": "0.2.10", + "version": "0.2.11", "description": "React-native Android/iOS plugin for JWPlayer SDK (https://www.jwplayer.com/)", "main": "index.js", "types": "./index.d.ts",