@castlabs/react-native-prestoplay-ios-avplayerviewcontroller 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/AVPlayerViewControllerExtension.swift +72 -0
- package/ios/ApplePlayerControlsPlugin.swift +23 -0
- package/ios/RNPrestoplayApplePlayerControlsModule.swift +192 -0
- package/ios/RNPrestoplayApplePlayerControlsPluginFactory.swift +19 -0
- package/ios/RNPrestroplay-Bridging-Header.h +9 -0
- package/ios/RNPrestroplayBridge.m +26 -0
- package/lib/AVPlayerViewControllerExtension.d.ts +28 -0
- package/lib/AVPlayerViewControllerExtension.js +148 -0
- package/lib/AVPlayerViewControllerPlugin.d.ts +15 -0
- package/lib/AVPlayerViewControllerPlugin.js +20 -0
- package/lib/components/AVPlayerViewControllerProvider.ios.d.ts +38 -0
- package/lib/components/AVPlayerViewControllerProvider.ios.js +41 -0
- package/lib/components/AppleTvContextualAction.ios.d.ts +32 -0
- package/lib/components/AppleTvContextualAction.ios.js +43 -0
- package/lib/components/AppleTvInfoBarAction.ios.d.ts +34 -0
- package/lib/components/AppleTvInfoBarAction.ios.js +46 -0
- package/lib/components/AppleTvPlayerControls.ios.d.ts +52 -0
- package/lib/components/AppleTvPlayerControls.ios.js +60 -0
- package/lib/components/AppleTvTransportBarAction.ios.d.ts +34 -0
- package/lib/components/AppleTvTransportBarAction.ios.js +47 -0
- package/lib/components/AppleTvTransportBarPopupMenu.ios.d.ts +41 -0
- package/lib/components/AppleTvTransportBarPopupMenu.ios.js +81 -0
- package/lib/components/AppleTvTransportBarSubMenu.ios.d.ts +41 -0
- package/lib/components/AppleTvTransportBarSubMenu.ios.js +70 -0
- package/lib/components/AppleTvTransportBarSubMenuItem.ios.d.ts +24 -0
- package/lib/components/AppleTvTransportBarSubMenuItem.ios.js +23 -0
- package/lib/events/AppleTvActionDetails.d.ts +7 -0
- package/lib/events/AppleTvActionDetails.js +1 -0
- package/lib/events/EventType.d.ts +7 -0
- package/lib/events/EventType.js +8 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +11 -0
- package/lib/types/Action.d.ts +13 -0
- package/lib/types/Action.js +1 -0
- package/lib/types/ActionType.d.ts +6 -0
- package/lib/types/ActionType.js +7 -0
- package/lib/types/ActionUiProperty.d.ts +5 -0
- package/lib/types/ActionUiProperty.js +1 -0
- package/lib/types/PopupMenu.d.ts +8 -0
- package/lib/types/PopupMenu.js +1 -0
- package/lib/types/PopupMenuInternal.d.ts +8 -0
- package/lib/types/PopupMenuInternal.js +1 -0
- package/lib/types/SubMenu.d.ts +7 -0
- package/lib/types/SubMenu.js +1 -0
- package/lib/types/SubMenuItem.d.ts +4 -0
- package/lib/types/SubMenuItem.js +1 -0
- package/package.json +89 -0
- package/react-native-prestoplay-ios-avplayerviewcontroller.podspec +21 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import AVKit
|
|
2
|
+
import PRESTOplay
|
|
3
|
+
import react_native_prestoplay
|
|
4
|
+
|
|
5
|
+
class AVPlayerViewControllerExtension: BasePlayerExtension {
|
|
6
|
+
override init(player: PlayerProtocol, playerEventEmitter: PlayerEventEmitter) {
|
|
7
|
+
super.init(player: player, playerEventEmitter: playerEventEmitter)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override open func onContentLoaded() {
|
|
11
|
+
// Check if AVPlayerViewController is being used
|
|
12
|
+
guard let playerViewController = player?.getViewController() else {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
playerViewController.playerDelegate = self;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@available(tvOS 15.0, *)
|
|
20
|
+
extension AVPlayerViewControllerExtension: PlayerViewControllerProtocol {
|
|
21
|
+
|
|
22
|
+
func didPlayToEnd(_ playerViewController: any PRESTOplay.PlayerViewControllerAPI) {
|
|
23
|
+
//print ("didPlayToEnd: ", playerViewController.avPlayerInstance?.currentItem)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
func playerIsClosed(_ playerViewController: any PRESTOplay.PlayerViewControllerAPI) {
|
|
28
|
+
self.player?.destroy()
|
|
29
|
+
|
|
30
|
+
DispatchQueue.main.async { [weak self] in
|
|
31
|
+
self?.playerEventEmitter?.emit(
|
|
32
|
+
consumerId: self?.player?.id ?? "",
|
|
33
|
+
eventType: "playerClosed",
|
|
34
|
+
eventDetails: [:],
|
|
35
|
+
preventDuplicates: false
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func action(_: PlayerViewControllerAPI, ofId id: String, eventName: String, data: String) {
|
|
41
|
+
|
|
42
|
+
// print ("action: id: ", id,"eventName: ", eventName, " data: ", data)
|
|
43
|
+
|
|
44
|
+
emitAction(
|
|
45
|
+
playerId: player?.id ?? "",
|
|
46
|
+
actionId: id,
|
|
47
|
+
eventName: eventName,
|
|
48
|
+
data: data
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func emitAction(playerId: String,
|
|
53
|
+
actionId: String,
|
|
54
|
+
eventName: String,
|
|
55
|
+
data: String) {
|
|
56
|
+
|
|
57
|
+
DispatchQueue.main.async { [weak self] in
|
|
58
|
+
self?.playerEventEmitter?.emit(
|
|
59
|
+
consumerId: playerId,
|
|
60
|
+
eventType: "appleTvAction",
|
|
61
|
+
eventDetails: [
|
|
62
|
+
"actionId": actionId,
|
|
63
|
+
"eventName": eventName,
|
|
64
|
+
"data": data
|
|
65
|
+
],
|
|
66
|
+
preventDuplicates: false
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import PRESTOplay
|
|
2
|
+
import react_native_prestoplay
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ApplePlayerControlsPlugin: BasePlugin {
|
|
6
|
+
override func onSdkWillInitialize() -> [CLPluginProtocol] {
|
|
7
|
+
// print ("ApplePlayerControlsPlugin - onSdkWillInitialize")
|
|
8
|
+
#if os(tvOS)
|
|
9
|
+
return []
|
|
10
|
+
#else
|
|
11
|
+
return []
|
|
12
|
+
#endif
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
override func onPlayerCreated(player: PlayerProtocol, playerEventEmitter: PlayerEventEmitter) {
|
|
16
|
+
player.addExtension(
|
|
17
|
+
playerExtension: AVPlayerViewControllerExtension(
|
|
18
|
+
player: player,
|
|
19
|
+
playerEventEmitter: playerEventEmitter
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import React
|
|
2
|
+
import AVKit
|
|
3
|
+
import PRESTOplay
|
|
4
|
+
import react_native_prestoplay
|
|
5
|
+
|
|
6
|
+
extension ActionSubMenuItemInfo {
|
|
7
|
+
public convenience init?(from dic: NSDictionary) {
|
|
8
|
+
guard let title = dic["title"] as? String else { return nil }
|
|
9
|
+
guard let value = dic["value"] as? String else { return nil }
|
|
10
|
+
|
|
11
|
+
self.init(title: title, value: value)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
extension ActionSubMenuInfo {
|
|
16
|
+
public convenience init?(from dic: NSDictionary) {
|
|
17
|
+
guard let menuItems = dic["menuItems"] as? NSArray else { return nil }
|
|
18
|
+
|
|
19
|
+
let defaultValue = dic["defaultValue"] as? String
|
|
20
|
+
let subMenuItemInfos: [ActionSubMenuItemInfo] = menuItems.compactMap {
|
|
21
|
+
guard let menuItemDic = $0 as? NSDictionary else { return nil }
|
|
22
|
+
return ActionSubMenuItemInfo(from: menuItemDic)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
self.init(defaultValue: defaultValue, menuItems: subMenuItemInfos)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
extension ActionButtonInfo {
|
|
30
|
+
public convenience init?(from dic: NSDictionary) {
|
|
31
|
+
guard let id = dic["id"] as? String else { return nil }
|
|
32
|
+
guard let title = dic["title"] as? String else { return nil }
|
|
33
|
+
|
|
34
|
+
var subMenuInfo: ActionSubMenuInfo?
|
|
35
|
+
if let subMenu = dic["subMenu"] as? NSDictionary {
|
|
36
|
+
subMenuInfo = ActionSubMenuInfo(from: subMenu)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
self.init(
|
|
40
|
+
id: id,
|
|
41
|
+
title: title,
|
|
42
|
+
imageName: dic["imageName"] as? String,
|
|
43
|
+
subMenu: subMenuInfo
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
extension ActionPopupMenuInfo {
|
|
49
|
+
public convenience init?(from dic: NSDictionary) {
|
|
50
|
+
guard let title = dic["title"] as? String else { return nil }
|
|
51
|
+
guard let imageName = dic["imageName"] as? String else { return nil }
|
|
52
|
+
guard let actions = dic["actions"] as? NSArray else { return nil }
|
|
53
|
+
|
|
54
|
+
let actionInfos: [ActionButtonInfo] = actions.compactMap {
|
|
55
|
+
guard let actionDic = $0 as? NSDictionary else { return nil }
|
|
56
|
+
return ActionButtonInfo(from: actionDic)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
self.init(title: title, imageName: imageName, actionInfos: actionInfos)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
extension ContextualActionButtonInfo {
|
|
64
|
+
public convenience init?(from dic: NSDictionary) {
|
|
65
|
+
guard let id = dic["id"] as? String else { return nil }
|
|
66
|
+
guard let title = dic["title"] as? String else { return nil }
|
|
67
|
+
guard let startSecond = dic["startSecond"] as? CMTimeValue else { return nil }
|
|
68
|
+
guard let endSecond = dic["endSecond"] as? CMTimeValue else { return nil }
|
|
69
|
+
|
|
70
|
+
self.init(
|
|
71
|
+
id: id,
|
|
72
|
+
title: title,
|
|
73
|
+
imageName: dic["imageName"] as? String,
|
|
74
|
+
startSecond: startSecond,
|
|
75
|
+
endSecond: endSecond
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@objc(RNPrestoplayApplePlayerControlsModule)
|
|
81
|
+
class RNPrestoplayApplePlayerControlsModule: NSObject {
|
|
82
|
+
|
|
83
|
+
var playerId: String?;
|
|
84
|
+
|
|
85
|
+
override init() {
|
|
86
|
+
super.init()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private let PLAYER_EVENT = "onPlayerEvent"
|
|
90
|
+
private let TRANSPORT_BAR_ACTION_EVENT = "onTransportBarActionEvent"
|
|
91
|
+
private let INFO_BAR_ACTION_EVENT = "onInfoBarActionEvent"
|
|
92
|
+
private let CONTEXTUAL_ACTION_EVENT = "onContextualActionEvent"
|
|
93
|
+
|
|
94
|
+
@objc(setAppleTvActions:transportBarActions:infoBarActions:contextualActions:resolver:rejecter:)
|
|
95
|
+
public func setAppleTvActions(
|
|
96
|
+
_ playerId: String!,
|
|
97
|
+
_ actionsInfo: [NSDictionary]?,
|
|
98
|
+
_ infoBarActionsInfo: [NSDictionary]?,
|
|
99
|
+
_ contextualActionsInfo: [NSDictionary]?,
|
|
100
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
101
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
102
|
+
) {
|
|
103
|
+
|
|
104
|
+
self.playerId = playerId
|
|
105
|
+
|
|
106
|
+
guard let player = Repository.shared.players.get(key: playerId) else {
|
|
107
|
+
Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
guard let playerViewController = player.getViewController() else {
|
|
112
|
+
Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound, message: "invalid player type"))
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#if os(tvOS)
|
|
117
|
+
DispatchQueue.main.async { [weak self] in
|
|
118
|
+
|
|
119
|
+
let transportActions: [AnyObject]? = actionsInfo?.compactMap {
|
|
120
|
+
if let popupMenu = ActionPopupMenuInfo(from: $0) {
|
|
121
|
+
return popupMenu
|
|
122
|
+
} else if let action = ActionButtonInfo(from: $0) {
|
|
123
|
+
return action
|
|
124
|
+
} else {
|
|
125
|
+
return nil
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if let transportActions {
|
|
130
|
+
playerViewController.setTransportBarActions(transportActions, eventName: self!.TRANSPORT_BAR_ACTION_EVENT)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let infobarActions: [ActionButtonInfo]? = infoBarActionsInfo?.compactMap {
|
|
134
|
+
if let action = ActionButtonInfo(from: $0) {
|
|
135
|
+
return action
|
|
136
|
+
} else {
|
|
137
|
+
return nil
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if let infobarActions {
|
|
142
|
+
playerViewController.setInfoTabActions(infobarActions, eventName: self!.INFO_BAR_ACTION_EVENT)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let contextualActions: [ContextualActionButtonInfo]? = contextualActionsInfo?.compactMap {
|
|
146
|
+
if let action = ContextualActionButtonInfo(from: $0) {
|
|
147
|
+
return action
|
|
148
|
+
} else {
|
|
149
|
+
return nil
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if let contextualActions {
|
|
153
|
+
playerViewController.setContextualActions(contextualActions, eventName: self!.CONTEXTUAL_ACTION_EVENT)
|
|
154
|
+
}
|
|
155
|
+
resolve(nil)
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
#endif
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@objc(setActionStateAndUi:actionId:state:uiInfo:resolver:rejecter:)
|
|
162
|
+
public func setActionStateAndUi(
|
|
163
|
+
_ playerId: String!,
|
|
164
|
+
_ actionId: String,
|
|
165
|
+
state: Bool,
|
|
166
|
+
uiInfo: NSDictionary,
|
|
167
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
168
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
169
|
+
) {
|
|
170
|
+
#if os(tvOS)
|
|
171
|
+
|
|
172
|
+
guard let player = Repository.shared.players.get(key: playerId) else {
|
|
173
|
+
Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
guard let playerViewController = player.getViewController() else {
|
|
177
|
+
Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound, message: "invalid player type"))
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
DispatchQueue.main.async { [weak self] in
|
|
182
|
+
playerViewController.setActionState(
|
|
183
|
+
ofId: actionId,
|
|
184
|
+
state: state,
|
|
185
|
+
title: uiInfo["title"] as? String,
|
|
186
|
+
imageName: uiInfo["imageName"] as? String
|
|
187
|
+
)
|
|
188
|
+
resolve(nil)
|
|
189
|
+
}
|
|
190
|
+
#endif
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React
|
|
2
|
+
import react_native_prestoplay
|
|
3
|
+
|
|
4
|
+
@objc(RNPrestoplayAVPlayerViewControllerPluginFactory)
|
|
5
|
+
class RNPrestoplayAVPlayerViewControllerPluginFactory: NSObject, RCTBridgeModule, PluginFactoryProtocol {
|
|
6
|
+
|
|
7
|
+
static func moduleName() -> String! {
|
|
8
|
+
return "RNPrestoplayAVPlayerViewControllerPluginFactory"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@objc(requiresMainQueueSetup)
|
|
12
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public func getPlugin() -> PluginProtocol {
|
|
17
|
+
return ApplePlayerControlsPlugin()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Use this file to import your target's public headers that you would like to expose to Swift.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#import <React/RCTViewManager.h>
|
|
6
|
+
#import <React/RCTUIManager.h>
|
|
7
|
+
#import <React/RCTBridgeModule.h>
|
|
8
|
+
#import <React/RCTEventEmitter.h>
|
|
9
|
+
#import <React/RCTRootContentView.h>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <React/RCTViewManager.h>
|
|
3
|
+
#import <React/RCTBridgeModule.h>
|
|
4
|
+
#import <React/RCTEventEmitter.h>
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@interface RCT_EXTERN_MODULE(RNPrestoplayApplePlayerControlsModule, NSObject)
|
|
8
|
+
|
|
9
|
+
RCT_EXTERN_METHOD(
|
|
10
|
+
setAppleTvActions:(nonnull NSString*) playerId
|
|
11
|
+
transportBarActions:(NSArray *)transportBarActions
|
|
12
|
+
infoBarActions:(NSArray *)infoBarActions
|
|
13
|
+
contextualActions:(NSArray *)contextualActions
|
|
14
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
15
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
16
|
+
|
|
17
|
+
RCT_EXTERN_METHOD(
|
|
18
|
+
setActionStateAndUi:(nonnull NSString*) playerId
|
|
19
|
+
actionId:(NSString *)id
|
|
20
|
+
state:(BOOL)state
|
|
21
|
+
uiInfo:(NSDictionary *)uiInfo
|
|
22
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
23
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
24
|
+
@end
|
|
25
|
+
|
|
26
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AppleTvTransportBarActionProps } from './components/AppleTvTransportBarAction.ios';
|
|
2
|
+
import { AppleTvInfoBarActionProps } from './components/AppleTvInfoBarAction.ios';
|
|
3
|
+
import { Player, PlayerExtension, BaseEventEmitter } from '@castlabs/react-native-prestoplay';
|
|
4
|
+
import { AppleTvContextualActionProps } from './components/AppleTvContextualAction.ios';
|
|
5
|
+
import { ActionType } from './types/ActionType';
|
|
6
|
+
import { PopupMenuInternal } from './types/PopupMenuInternal';
|
|
7
|
+
export declare class AVPlayerViewControllerExtension extends PlayerExtension {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
private readyToSubmit;
|
|
10
|
+
private readonly logger;
|
|
11
|
+
private transportBarActions;
|
|
12
|
+
private infoBarActions;
|
|
13
|
+
private contextualActions;
|
|
14
|
+
private popUpMenus;
|
|
15
|
+
readonly eventEmitter: BaseEventEmitter;
|
|
16
|
+
constructor(player: Player);
|
|
17
|
+
destroy(): void;
|
|
18
|
+
private onAppleTvAction;
|
|
19
|
+
private registerTransportBarActions;
|
|
20
|
+
private registerTransportBarSubMenu;
|
|
21
|
+
addAction(type: ActionType, actionId: string, props: AppleTvTransportBarActionProps | AppleTvInfoBarActionProps | AppleTvContextualActionProps): void;
|
|
22
|
+
removeAction(type: ActionType, actionId: string): void;
|
|
23
|
+
setPopupMenu(menuId: string, popUpMenu: PopupMenuInternal): void;
|
|
24
|
+
removePopupMenu(menuId: string): void;
|
|
25
|
+
updateAction(actionId: string, state: boolean, title: string, icon: string): Promise<void>;
|
|
26
|
+
onContentLoaded(): void;
|
|
27
|
+
private submitActions;
|
|
28
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { PlayerExtension, rethrowAsPrestoPlayErrorIfPossible, Logger, BaseEventEmitter, } from '@castlabs/react-native-prestoplay';
|
|
2
|
+
import { NativeModules } from 'react-native';
|
|
3
|
+
import { ActionType } from './types/ActionType';
|
|
4
|
+
import { EventType as LocalEventType } from './events/EventType';
|
|
5
|
+
export class AVPlayerViewControllerExtension extends PlayerExtension {
|
|
6
|
+
id;
|
|
7
|
+
readyToSubmit = false;
|
|
8
|
+
logger = new Logger('AVPlayerViewControllerExtension');
|
|
9
|
+
transportBarActions = [];
|
|
10
|
+
infoBarActions = [];
|
|
11
|
+
contextualActions = [];
|
|
12
|
+
popUpMenus = new Map();
|
|
13
|
+
eventEmitter;
|
|
14
|
+
constructor(player) {
|
|
15
|
+
super(player);
|
|
16
|
+
this.id = player.id;
|
|
17
|
+
this.eventEmitter = new BaseEventEmitter();
|
|
18
|
+
player.addEventListener(LocalEventType.AppleTvAction, this.onAppleTvAction.bind(this));
|
|
19
|
+
}
|
|
20
|
+
destroy() {
|
|
21
|
+
this.player.removeEventListener(LocalEventType.AppleTvAction, this.onAppleTvAction.bind(this));
|
|
22
|
+
}
|
|
23
|
+
onAppleTvAction(event) {
|
|
24
|
+
const details = event.details;
|
|
25
|
+
this.eventEmitter.emit(details.actionId, event);
|
|
26
|
+
}
|
|
27
|
+
registerTransportBarActions(actions) {
|
|
28
|
+
const actionsResult = [];
|
|
29
|
+
actions.forEach(action => {
|
|
30
|
+
actionsResult.push({
|
|
31
|
+
id: action.id,
|
|
32
|
+
title: action.title,
|
|
33
|
+
state: action.state,
|
|
34
|
+
imageName: action.imageName,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return actionsResult;
|
|
38
|
+
}
|
|
39
|
+
registerTransportBarSubMenu(submenus) {
|
|
40
|
+
const actions = [];
|
|
41
|
+
submenus.forEach(menu => {
|
|
42
|
+
actions.push({
|
|
43
|
+
id: menu.id,
|
|
44
|
+
title: menu.title,
|
|
45
|
+
subMenu: menu,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
return actions;
|
|
49
|
+
}
|
|
50
|
+
addAction(type, actionId, props) {
|
|
51
|
+
if (type === ActionType.InfoBar) {
|
|
52
|
+
const infoBarProps = props;
|
|
53
|
+
this.infoBarActions.push({
|
|
54
|
+
id: actionId,
|
|
55
|
+
title: props.title,
|
|
56
|
+
state: infoBarProps.state,
|
|
57
|
+
imageName: props.icon,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else if (type === ActionType.Contextual) {
|
|
61
|
+
const contextualProps = props;
|
|
62
|
+
this.contextualActions.push({
|
|
63
|
+
id: actionId,
|
|
64
|
+
title: props.title,
|
|
65
|
+
imageName: props.icon,
|
|
66
|
+
startSecond: contextualProps.startAt,
|
|
67
|
+
endSecond: contextualProps.endAt,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else if (type === ActionType.TransportBar) {
|
|
71
|
+
const transportBarProps = props;
|
|
72
|
+
this.transportBarActions.push({
|
|
73
|
+
id: actionId,
|
|
74
|
+
title: props.title,
|
|
75
|
+
state: transportBarProps.state,
|
|
76
|
+
imageName: props.icon,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (this.readyToSubmit) {
|
|
80
|
+
this.submitActions();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
removeAction(type, actionId) {
|
|
84
|
+
if (type === ActionType.InfoBar) {
|
|
85
|
+
this.infoBarActions = this.infoBarActions.filter(item => item.id !== actionId);
|
|
86
|
+
}
|
|
87
|
+
else if (type === ActionType.Contextual) {
|
|
88
|
+
this.contextualActions = this.contextualActions.filter(item => item.id !== actionId);
|
|
89
|
+
}
|
|
90
|
+
else if (type === ActionType.TransportBar) {
|
|
91
|
+
this.transportBarActions = this.transportBarActions.filter(item => item.id !== actionId);
|
|
92
|
+
}
|
|
93
|
+
if (this.readyToSubmit) {
|
|
94
|
+
this.submitActions();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
setPopupMenu(menuId, popUpMenu) {
|
|
98
|
+
const newPopupMenu = {
|
|
99
|
+
id: menuId,
|
|
100
|
+
title: popUpMenu.title,
|
|
101
|
+
imageName: popUpMenu.icon,
|
|
102
|
+
actions: [],
|
|
103
|
+
};
|
|
104
|
+
newPopupMenu.actions.push(...this.registerTransportBarActions(popUpMenu.transportBarActions));
|
|
105
|
+
newPopupMenu.actions.push(...this.registerTransportBarSubMenu(popUpMenu.subMenus));
|
|
106
|
+
this.popUpMenus.set(menuId, newPopupMenu);
|
|
107
|
+
if (this.readyToSubmit) {
|
|
108
|
+
this.submitActions();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
removePopupMenu(menuId) {
|
|
112
|
+
this.popUpMenus.delete(menuId);
|
|
113
|
+
if (this.readyToSubmit) {
|
|
114
|
+
this.submitActions();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async updateAction(actionId, state, title, icon) {
|
|
118
|
+
const update = {
|
|
119
|
+
title: title,
|
|
120
|
+
imageName: icon,
|
|
121
|
+
};
|
|
122
|
+
await NativeModules.RNPrestoplayApplePlayerControlsModule.setActionStateAndUi(this.player.id, actionId, state, update);
|
|
123
|
+
}
|
|
124
|
+
onContentLoaded() {
|
|
125
|
+
/**
|
|
126
|
+
* setAppleTvActions must be called after AVPlayerViewController is available
|
|
127
|
+
* which is after content is loaded due API restrinction on prestroplay-apple
|
|
128
|
+
*/
|
|
129
|
+
this.readyToSubmit = true;
|
|
130
|
+
this.submitActions();
|
|
131
|
+
}
|
|
132
|
+
async submitActions() {
|
|
133
|
+
if (!this.player.isApplePlayer) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const transportBarActions = [...this.transportBarActions];
|
|
137
|
+
this.popUpMenus.forEach((menu, _id) => {
|
|
138
|
+
transportBarActions.push(menu);
|
|
139
|
+
});
|
|
140
|
+
NativeModules.RNPrestoplayApplePlayerControlsModule.setAppleTvActions(this.player.id, transportBarActions, this.infoBarActions, this.contextualActions)
|
|
141
|
+
.then(() => {
|
|
142
|
+
this.logger.debug('AVPlayerViewControllerExtension: submitActions done');
|
|
143
|
+
})
|
|
144
|
+
.catch((error) => {
|
|
145
|
+
rethrowAsPrestoPlayErrorIfPossible(error);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Player, Plugin } from '@castlabs/react-native-prestoplay';
|
|
2
|
+
/**
|
|
3
|
+
* Provides {@link AVPlayerViewControllerProvider} integration for iOS and tvOS.
|
|
4
|
+
*
|
|
5
|
+
* In case of tvOS, it also enables the use of custom Apple TV player controls
|
|
6
|
+
* by using the {@link AppleTvPlayerControls} component.
|
|
7
|
+
*
|
|
8
|
+
* @group Classes
|
|
9
|
+
*/
|
|
10
|
+
export default class AVPlayerViewControllerPlugin extends Plugin {
|
|
11
|
+
/** @hidden */
|
|
12
|
+
getNativePluginFactoryModuleName(): string | null;
|
|
13
|
+
/** @hidden */
|
|
14
|
+
onPlayerCreated(player: Player): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Plugin } from '@castlabs/react-native-prestoplay';
|
|
2
|
+
import { AVPlayerViewControllerExtension } from './AVPlayerViewControllerExtension';
|
|
3
|
+
/**
|
|
4
|
+
* Provides {@link AVPlayerViewControllerProvider} integration for iOS and tvOS.
|
|
5
|
+
*
|
|
6
|
+
* In case of tvOS, it also enables the use of custom Apple TV player controls
|
|
7
|
+
* by using the {@link AppleTvPlayerControls} component.
|
|
8
|
+
*
|
|
9
|
+
* @group Classes
|
|
10
|
+
*/
|
|
11
|
+
export default class AVPlayerViewControllerPlugin extends Plugin {
|
|
12
|
+
/** @hidden */
|
|
13
|
+
getNativePluginFactoryModuleName() {
|
|
14
|
+
return 'RNPrestoplayAVPlayerViewControllerPluginFactory';
|
|
15
|
+
}
|
|
16
|
+
/** @hidden */
|
|
17
|
+
onPlayerCreated(player) {
|
|
18
|
+
player.addExtension(new AVPlayerViewControllerExtension(player));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { PlayerConfiguration } from '@castlabs/react-native-prestoplay';
|
|
3
|
+
import AppleTvPlayerControls from './AppleTvPlayerControls.ios';
|
|
4
|
+
/**
|
|
5
|
+
* Props for the {@link AVPlayerViewControllerProvider} component.
|
|
6
|
+
*
|
|
7
|
+
* @group Type Aliases
|
|
8
|
+
*/
|
|
9
|
+
export type AVPlayerViewControllerProviderProps = {
|
|
10
|
+
/**
|
|
11
|
+
* The configuration used to initialize the player instance.
|
|
12
|
+
*
|
|
13
|
+
* For detailed options, see {@link PlayerConfiguration}.
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
playerConfig?: PlayerConfiguration;
|
|
17
|
+
/**
|
|
18
|
+
* Callback invoked when the underlying `AVPlayerViewController` is closed.
|
|
19
|
+
*/
|
|
20
|
+
onClose?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Child component are not rendered visually, but they can
|
|
23
|
+
* interact with the player instance.
|
|
24
|
+
*
|
|
25
|
+
* This may include {@link AppleTvPlayerControls} for custom tvOS player controls.
|
|
26
|
+
*/
|
|
27
|
+
children: ReactNode | ReactElement<typeof AppleTvPlayerControls>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* An alternative to the `PlayerProvider` component, which uses an
|
|
31
|
+
* AVPlayerViewController to present the player UI on iOS and tvOS.
|
|
32
|
+
*
|
|
33
|
+
* You can use the {@link AppleTvPlayerControls} component as a child to
|
|
34
|
+
* present custom tvOS player controls.
|
|
35
|
+
*
|
|
36
|
+
* @group Components
|
|
37
|
+
*/
|
|
38
|
+
export declare const AVPlayerViewControllerProvider: React.NamedExoticComponent<AVPlayerViewControllerProviderProps>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { memo, useEffect, useMemo } from 'react';
|
|
2
|
+
import { EventType, PlayerContext, } from '@castlabs/react-native-prestoplay';
|
|
3
|
+
import { Player } from '@castlabs/react-native-prestoplay';
|
|
4
|
+
/**
|
|
5
|
+
* An alternative to the `PlayerProvider` component, which uses an
|
|
6
|
+
* AVPlayerViewController to present the player UI on iOS and tvOS.
|
|
7
|
+
*
|
|
8
|
+
* You can use the {@link AppleTvPlayerControls} component as a child to
|
|
9
|
+
* present custom tvOS player controls.
|
|
10
|
+
*
|
|
11
|
+
* @group Components
|
|
12
|
+
*/
|
|
13
|
+
export const AVPlayerViewControllerProvider = memo(function ({ playerConfig, onClose, children, }) {
|
|
14
|
+
const player = useMemo(() => {
|
|
15
|
+
return new Player(true);
|
|
16
|
+
}, []);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const onEvent = (_event) => {
|
|
19
|
+
onClose && onClose();
|
|
20
|
+
};
|
|
21
|
+
player.addEventListener(EventType.PlayerClosed, onEvent);
|
|
22
|
+
return () => {
|
|
23
|
+
player.removeEventListener(EventType.PlayerClosed, onEvent);
|
|
24
|
+
player.destroy();
|
|
25
|
+
};
|
|
26
|
+
}, [player, onClose]);
|
|
27
|
+
// The children might be a new value, while config remains the same.
|
|
28
|
+
// Avoid creating the new player instance by comparing the hash of the config.
|
|
29
|
+
const playerConfigHash = JSON.stringify(playerConfig);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (playerConfig) {
|
|
32
|
+
console.info('Opening player with config:', playerConfig);
|
|
33
|
+
player.open(playerConfig).catch(error => {
|
|
34
|
+
console.error('Error opening player:', error);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
|
+
[playerConfigHash]);
|
|
40
|
+
return (<PlayerContext.Provider value={{ player }}>{children}</PlayerContext.Provider>);
|
|
41
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
/**
|
|
3
|
+
* When you set a value for the {@link AppleTvContextualAction} properties,
|
|
4
|
+
* the player presents the controls immediately. To present them only during a
|
|
5
|
+
* relevant section of the content, set the {@link startAt} and {@link endAt}
|
|
6
|
+
* properties to define the presentation range.
|
|
7
|
+
*
|
|
8
|
+
* @group Type Aliases
|
|
9
|
+
*/
|
|
10
|
+
export type AppleTvContextualActionProps = {
|
|
11
|
+
/** The text of the contextual action button. */
|
|
12
|
+
title: string;
|
|
13
|
+
/** The name of the image resource in the Xcode app project. */
|
|
14
|
+
icon: string;
|
|
15
|
+
/** The time in seconds when the contextual action becomes visible. */
|
|
16
|
+
startAt: number;
|
|
17
|
+
/** The time in seconds when the contextual action stops being visible. */
|
|
18
|
+
endAt: number;
|
|
19
|
+
/** Action to be executed when the button is pressed. */
|
|
20
|
+
onPress: () => void;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* **⚠️ This component is only supported on tvOS.**
|
|
24
|
+
*
|
|
25
|
+
* You can use the tvOS player UI to present controls contextually, which you
|
|
26
|
+
* display for a specific range of time in the content and then dismiss.
|
|
27
|
+
*
|
|
28
|
+
* For more details, see Apple documentation: {@link https://developer.apple.com/documentation/avkit/customizing-the-tvos-playback-experience#Present-Actions-Contextually | Present Actions Contextually}.
|
|
29
|
+
*
|
|
30
|
+
* @group Components
|
|
31
|
+
*/
|
|
32
|
+
export declare const AppleTvContextualAction: React.FC<AppleTvContextualActionProps>;
|