@applicaster/zapp-react-native-utils 16.0.0-alpha.3527552622 → 16.0.0-alpha.3550017939
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/actionUtils/index.ts +3 -1
- package/actionsExecutor/ActionExecutor.ts +57 -12
- package/actionsExecutor/ActionExecutorContext.tsx +113 -314
- package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
- package/actionsExecutor/__tests__/feedDecorator.test.ts +61 -0
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +25 -0
- package/actionsExecutor/actions/__tests__/goBack.test.ts +62 -0
- package/actionsExecutor/actions/__tests__/goHome.test.ts +19 -0
- package/actionsExecutor/actions/__tests__/loadItemsFromSource.test.ts +88 -0
- package/actionsExecutor/actions/__tests__/navigateToScreen.test.ts +267 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +100 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +300 -0
- package/actionsExecutor/actions/__tests__/presentScreen.test.ts +127 -0
- package/actionsExecutor/actions/__tests__/showAlert.test.ts +49 -0
- package/actionsExecutor/actions/__tests__/showToast.test.ts +88 -0
- package/actionsExecutor/actions/appRestart.ts +24 -0
- package/actionsExecutor/actions/confirmDialog.ts +53 -0
- package/actionsExecutor/actions/dismissBottomSheet.ts +23 -0
- package/actionsExecutor/actions/goBack.ts +60 -0
- package/actionsExecutor/actions/goHome.ts +31 -0
- package/actionsExecutor/actions/index.ts +42 -0
- package/actionsExecutor/actions/localStorageRemove.ts +27 -0
- package/actionsExecutor/actions/localStorageSet.ts +28 -0
- package/actionsExecutor/actions/localStorageToggleFlag.ts +28 -0
- package/actionsExecutor/actions/navigateToScreen.ts +121 -0
- package/actionsExecutor/actions/openBottomSheet.ts +339 -0
- package/actionsExecutor/actions/presentScreen.ts +78 -0
- package/actionsExecutor/actions/refreshComponent.ts +85 -0
- package/actionsExecutor/actions/screenSetVariable.ts +35 -0
- package/actionsExecutor/actions/screenToggleFlag.ts +38 -0
- package/actionsExecutor/actions/sendCloudEvent.ts +93 -0
- package/actionsExecutor/actions/sessionStorageRemove.ts +19 -0
- package/actionsExecutor/actions/sessionStorageSet.ts +20 -0
- package/actionsExecutor/actions/sessionStorageToggleFlag.ts +15 -0
- package/actionsExecutor/actions/showAlert.ts +51 -0
- package/actionsExecutor/actions/showToast.ts +87 -0
- package/actionsExecutor/actions/switchLayout.ts +40 -0
- package/actionsExecutor/consts.ts +27 -0
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/actionsExecutor/types.ts +59 -0
- package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -2
- package/analyticsUtils/PlayerAnalyticsManager.ts +12 -2
- package/analyticsUtils/__tests__/analyticsMapper.test.ts +35 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testACP_events.json +1 -1
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testBlockUnlistedParams_rules.json +1 -1
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEventRegex_events.json +3 -9
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_events.json +18 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_rules.json +16 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/index.js +20 -0
- package/analyticsUtils/analyticsMapper.ts +4 -1
- package/analyticsUtils/playerAnalyticsTracker.ts +26 -3
- package/appUtils/HooksManager/index.ts +12 -8
- package/appUtils/contextKeysManager/__tests__/getKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/removeKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/setKey/failure/invalidKey.test.ts +4 -4
- package/appUtils/contextKeysManager/index.ts +1 -1
- package/appUtils/contextKeysManager/utils/index.ts +38 -25
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +142 -59
- package/appUtils/playerManager/OverlayObserver/__tests__/getUpcomingQueue.test.ts +67 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/liveContent.test.ts +205 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +56 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.android.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ios.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +24 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.web.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/utils.ts +51 -20
- package/appUtils/playerManager/__tests__/playerContent.test.ts +403 -0
- package/appUtils/playerManager/__tests__/playerDiagnostics.test.ts +62 -0
- package/appUtils/playerManager/__tests__/playerFactory.test.ts +150 -0
- package/appUtils/playerManager/__tests__/playerNative.test.ts +114 -0
- package/appUtils/playerManager/__tests__/playerNativeCommand.test.ts +62 -0
- package/appUtils/playerManager/__tests__/playerToolbox.test.ts +281 -0
- package/appUtils/playerManager/__tests__/usePlayerContent.test.tsx +64 -0
- package/appUtils/playerManager/__tests__/usePlayerControllerSetup.test.tsx +62 -0
- package/appUtils/playerManager/{conts.ts → const.ts} +33 -0
- package/appUtils/playerManager/index.ts +8 -1
- package/appUtils/playerManager/player.ts +140 -3
- package/appUtils/playerManager/playerFactory.ts +18 -35
- package/appUtils/playerManager/playerNative.ts +28 -4
- package/appUtils/playerManager/playerNativeCommand.ts +3 -6
- package/appUtils/playerManager/playerToolbox.ts +196 -0
- package/appUtils/playerManager/usePlayerContent.tsx +43 -0
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +7 -1
- package/appUtils/playerManager/userLanguagePreference.ts +1 -1
- package/arrayUtils/__tests__/{anyThruthy.test.ts → anyTruthy.test.ts} +8 -0
- package/arrayUtils/__tests__/arrayUtils.test.ts +165 -108
- package/arrayUtils/__tests__/isEmptyArray.test.ts +11 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +12 -0
- package/arrayUtils/__tests__/isFirst.test.ts +17 -0
- package/arrayUtils/__tests__/isIndexInRange.test.ts +14 -0
- package/arrayUtils/__tests__/isLast.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOf.test.ts +31 -0
- package/arrayUtils/__tests__/makeListOfIndexes.test.ts +20 -0
- package/arrayUtils/__tests__/reorderByIds.test.ts +63 -0
- package/arrayUtils/__tests__/sample.test.ts +61 -0
- package/arrayUtils/index.ts +119 -20
- package/bottomSheet/__tests__/scrollContext.test.tsx +35 -0
- package/bottomSheet/index.ts +17 -0
- package/cellUtils/__tests__/getButtonsEnabled.test.ts +28 -0
- package/cellUtils/__tests__/getCellNavigationEnabled.test.ts +36 -0
- package/cellUtils/__tests__/getIsCellNavigationEnabled.test.ts +35 -0
- package/cellUtils/__tests__/isCellNavigationEnabled.test.ts +21 -0
- package/cellUtils/index.ts +73 -8
- package/colorUtils/__tests__/isTransparentColor.test.ts +76 -0
- package/colorUtils/__tests__/isValidColor.test.ts +70 -0
- package/colorUtils/index.ts +50 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +21 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +392 -0
- package/configurationUtils/manifestKeyParser.ts +50 -10
- package/configurationUtils/modalBlocksParser.ts +252 -0
- package/entryActions/__tests__/buildEntryActions.test.ts +235 -0
- package/entryActions/aliasPresentation.ts +108 -0
- package/entryActions/buildEntryActions.ts +167 -0
- package/entryActions/index.ts +15 -0
- package/entryActions/types.ts +39 -0
- package/focusManager/FocusManager.ts +26 -0
- package/focusManager/__tests__/FocusManager.test.ts +9 -0
- package/localizationUtils/index.ts +12 -6
- package/logger/__tests__/xrayToolbox.test.ts +69 -0
- package/logger/xrayToolbox.ts +67 -0
- package/manifestUtils/__tests__/player.audioControls.test.js +158 -0
- package/manifestUtils/_internals/index.js +14 -3
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +41 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +357 -20
- package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +84 -0
- package/manifestUtils/fieldUtils/index.js +125 -0
- package/manifestUtils/index.js +3 -0
- package/manifestUtils/keys.js +15 -1
- package/manifestUtils/mobileAction/button/index.js +16 -0
- package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +21 -0
- package/manifestUtils/mobileAction/container/index.js +3 -1
- package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +1 -1
- package/manifestUtils/mobileAction/groups/defaults.js +5 -2
- package/manifestUtils/modalBlocks.js +304 -0
- package/manifestUtils/platformIsTV.js +1 -0
- package/modalState/ContentViewModel.ts +113 -0
- package/modalState/EditableCollection.ts +17 -0
- package/modalState/EditableCollectionRegistry.ts +51 -0
- package/modalState/ModalOrchestrator.ts +212 -0
- package/modalState/RemoteEditableCollection.ts +235 -0
- package/modalState/__tests__/ContentViewModel.editable.test.ts +69 -0
- package/modalState/__tests__/ContentViewModel.test.ts +165 -0
- package/modalState/__tests__/EditableCollectionRegistry.test.ts +112 -0
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +63 -0
- package/modalState/__tests__/RemoteEditableCollection.test.ts +415 -0
- package/modalState/__tests__/index.test.ts +30 -1
- package/modalState/__tests__/useBottomSheetContent.test.ts +349 -0
- package/modalState/__tests__/useModalStoreState.test.ts +142 -0
- package/modalState/index.ts +35 -83
- package/modalState/store.ts +109 -0
- package/modalState/types.ts +162 -0
- package/modalState/useBottomSheetContent.ts +109 -0
- package/navigationUtils/xrayNavigator.ts +12 -0
- package/numberUtils/__tests__/isMinusZero.test.ts +20 -0
- package/numberUtils/__tests__/isZero.test.ts +27 -0
- package/numberUtils/__tests__/requireNumber.test.ts +50 -0
- package/numberUtils/__tests__/toNumber.test.ts +27 -0
- package/numberUtils/__tests__/toNumberWithDefault.test.ts +64 -0
- package/numberUtils/__tests__/toNumberWithDefaultZero.test.ts +48 -0
- package/numberUtils/index.ts +27 -8
- package/package.json +2 -2
- package/pipesUtils/__tests__/buildUrlWithQuery.test.ts +33 -0
- package/pipesUtils/__tests__/withPipesEndpoint.test.tsx +56 -0
- package/pipesUtils/index.ts +1 -0
- package/pipesUtils/withPipesEndpoint.tsx +54 -0
- package/playerUtils/__tests__/contentDataKeys.test.ts +297 -0
- package/playerUtils/__tests__/resolvePlayerActions.test.ts +138 -0
- package/playerUtils/__tests__/resolvePlayerTitleSubtitle.test.ts +180 -0
- package/playerUtils/contentDataKeys.ts +134 -0
- package/playerUtils/index.ts +58 -15
- package/playerUtils/isAudioItem.ts +26 -0
- package/playerUtils/resolvePlayerActions.ts +71 -0
- package/playerUtils/resolvePlayerTitleSubtitle.ts +76 -0
- package/pluginUtils/__tests__/pluginUtils.test.js +32 -0
- package/pluginUtils/index.ts +29 -0
- package/reactHooks/__tests__/getPluginConfiguration.test.ts +82 -0
- package/reactHooks/actions/index.ts +117 -2
- package/reactHooks/cell-click/__tests__/index.test.js +64 -0
- package/reactHooks/cell-click/index.ts +39 -24
- package/reactHooks/connection/index.tsx +8 -5
- package/reactHooks/feed/__mocks__/useMarkPipesDataStale.ts +4 -0
- package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +25 -0
- package/reactHooks/feed/__tests__/useMarkPipesDataStale.test.tsx +243 -0
- package/reactHooks/feed/index.ts +5 -1
- package/reactHooks/feed/useBatchLoading.ts +9 -1
- package/reactHooks/feed/useMarkPipesDataStale.ts +97 -0
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/navigation/__mocks__/index.ts +4 -0
- package/reactHooks/navigation/__tests__/index.test.tsx +176 -1
- package/reactHooks/navigation/__tests__/useIsScreenActive.test.ts +42 -0
- package/reactHooks/navigation/__tests__/usePresentScreen.test.ts +154 -0
- package/reactHooks/navigation/index.ts +3 -1
- package/reactHooks/navigation/useIsScreenActive.ts +29 -8
- package/reactHooks/navigation/usePresentScreen.ts +157 -0
- package/reactHooks/navigation/useRoute.ts +22 -5
- package/reactHooks/screen/__tests__/useIsStandaloneFullscreen.test.ts +114 -0
- package/reactHooks/screen/index.ts +2 -0
- package/reactHooks/screen/useIsStandaloneFullscreen.ts +12 -0
- package/reactHooks/state/useComponentScreenState.ts +1 -1
- package/reactHooks/usePluginConfiguration.ts +88 -47
- package/reactHooks/utils/__tests__/index.test.js +22 -2
- package/reactHooks/utils/index.ts +13 -2
- package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +22 -4
- package/riverComponetsMeasurementProvider/index.tsx +15 -9
- package/stringUtils/__tests__/stringUtils.test.js +42 -0
- package/stringUtils/index.ts +2 -2
- package/uiActionsRegistrator/__tests__/resolveActionIdentifiers.test.ts +93 -0
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +133 -0
- package/uiActionsRegistrator/__tests__/usableActionItems.test.ts +52 -0
- package/uiActionsRegistrator/index.ts +25 -0
- package/uiActionsRegistrator/registry.ts +351 -0
- package/uiActionsRegistrator/resolveActionIdentifiers.ts +100 -0
- package/uiActionsRegistrator/usableActionItems.ts +66 -0
- package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +1 -1
- package/zappFrameworkUtils/__tests__/localStorageHelper.test.ts +146 -0
- package/zappFrameworkUtils/localStorageHelper.ts +19 -15
- package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +0 -56
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +0 -54
- package/playerUtils/getPlayerActionButtons.ts +0 -17
- package/reactHooks/feed/__mocks__/usePipesCacheReset.ts +0 -1
- package/reactHooks/feed/usePipesCacheReset.ts +0 -44
- /package/reactHooks/actions/__tests__/{index.test.js → index.test.tsx} +0 -0
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
-
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
import { BehaviorSubject, Observable } from "rxjs";
|
|
3
|
+
import { distinctUntilChanged } from "rxjs/operators";
|
|
3
4
|
import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
4
5
|
import { createLogger, utilsLogger } from "../../logger";
|
|
5
|
-
import {
|
|
6
|
-
|
|
6
|
+
import {
|
|
7
|
+
getBoolFromConfigValue,
|
|
8
|
+
imageSrcFromMediaItem,
|
|
9
|
+
} from "../../configurationUtils";
|
|
10
|
+
// Imported from the leaf modules rather than the `playerUtils` barrel: that
|
|
11
|
+
// barrel imports `playerManager`, so going through it would create a require
|
|
12
|
+
// cycle (playerManager/index -> player -> playerUtils/index -> playerManager).
|
|
13
|
+
import {
|
|
14
|
+
resolvePlayerTitleSubtitle,
|
|
15
|
+
getPlayerContentDataKeys,
|
|
16
|
+
} from "../../playerUtils/resolvePlayerTitleSubtitle";
|
|
17
|
+
import { isAudioItem } from "../../playerUtils/isAudioItem";
|
|
18
|
+
import { isTV } from "../../reactUtils";
|
|
19
|
+
import { PlayerRole, SharedPlayerCallBacksKeys } from "./const";
|
|
7
20
|
import { OverlaysObserver } from "./OverlayObserver/OverlaysObserver";
|
|
8
21
|
import { TVSeekController } from "../../reactHooks/player/TVSeekControlller/TVSeekController";
|
|
9
22
|
|
|
@@ -19,6 +32,23 @@ export declare type PlayerControllerListenerData = {
|
|
|
19
32
|
id: string;
|
|
20
33
|
listener: QuickBrickPlayer.SharedPlayerCallBacks;
|
|
21
34
|
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The player's resolved Title/Subtitle text, published by the player the same
|
|
38
|
+
* way duration and progress are. Consumers render these values as-is rather
|
|
39
|
+
* than reading `entry` or the plugin configuration themselves.
|
|
40
|
+
*/
|
|
41
|
+
export type PlayerContent = {
|
|
42
|
+
title: Option<string | number>;
|
|
43
|
+
summary: Option<string | number>;
|
|
44
|
+
/**
|
|
45
|
+
* Cover art for the content currently playing. Published alongside the text
|
|
46
|
+
* (rather than read from `entry` by whoever needs it) so it follows the same
|
|
47
|
+
* entry: on live audio that means the programme's own artwork, refreshed
|
|
48
|
+
* with every feed poll, instead of the channel's.
|
|
49
|
+
*/
|
|
50
|
+
imageUrl: Option<string>;
|
|
51
|
+
};
|
|
22
52
|
/**
|
|
23
53
|
* Player Instance Controller
|
|
24
54
|
* Instance exposes utility methods that will help set or get state
|
|
@@ -45,6 +75,13 @@ export class Player {
|
|
|
45
75
|
readonly playerRole: PlayerRole = PlayerRole.Unspecified;
|
|
46
76
|
private overlaysObserver?: OverlaysObserver | null;
|
|
47
77
|
private entryObservable?: BehaviorSubject<ZappEntry> | null;
|
|
78
|
+
private contentSubject?: BehaviorSubject<PlayerContent> | null;
|
|
79
|
+
// Read by the X-Ray `player` toolbox: the last onError/onVideoError of the current entry,
|
|
80
|
+
// and the Play Next action of the PlayerContainer showing this player.
|
|
81
|
+
public lastError: unknown = null;
|
|
82
|
+
public playNextHandler: (() => void) | null = null;
|
|
83
|
+
// The web engine the default player picked (hls.js, dash.js, shaka, video.js); null elsewhere.
|
|
84
|
+
public engine: string | null = null;
|
|
48
85
|
|
|
49
86
|
protected listeners: {
|
|
50
87
|
[key: string]: QuickBrickPlayer.SharedPlayerCallBacks;
|
|
@@ -58,7 +95,18 @@ export class Player {
|
|
|
58
95
|
|
|
59
96
|
set entry(newEntry: ZappEntry | null) {
|
|
60
97
|
this._entry = newEntry;
|
|
98
|
+
this.lastError = null;
|
|
99
|
+
// The live entry is scoped to the entry it overlays - see
|
|
100
|
+
// `OverlaysObserver.clearLiveEntry`. No-op when there is no observer,
|
|
101
|
+
// which is every non-live player.
|
|
102
|
+
this.overlaysObserver?.clearLiveEntry();
|
|
61
103
|
this.entryObservable?.next(newEntry);
|
|
104
|
+
// Optional chaining short-circuits the whole call expression, so
|
|
105
|
+
// `getContent()` is NOT evaluated while no subscriber exists. That
|
|
106
|
+
// matters: `PlayerNative` assigns `this.entry` in its constructor, before
|
|
107
|
+
// subclass arrow-function properties such as `getPluginConfiguration`
|
|
108
|
+
// have been assigned.
|
|
109
|
+
this.contentSubject?.next(this.getContent());
|
|
62
110
|
}
|
|
63
111
|
|
|
64
112
|
constructor(props) {
|
|
@@ -117,6 +165,77 @@ export class Player {
|
|
|
117
165
|
return this.entryObservable;
|
|
118
166
|
};
|
|
119
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The player's resolved Title/Subtitle.
|
|
170
|
+
*
|
|
171
|
+
* Resolved on demand rather than cached: the plugin configuration arrives
|
|
172
|
+
* asynchronously on both the native and cast paths, so a value computed once
|
|
173
|
+
* at entry-set time would be stale for whichever arrives first.
|
|
174
|
+
*
|
|
175
|
+
* Every fallback (no config, empty/whitespace key, unresolvable key, key
|
|
176
|
+
* resolving to an empty string or to a non-string/non-number) lives in
|
|
177
|
+
* `resolvePlayerContentText` and is never reimplemented here.
|
|
178
|
+
*/
|
|
179
|
+
getContent = (): PlayerContent => {
|
|
180
|
+
// Live audio keeps playing one channel while the programme on air
|
|
181
|
+
// changes, so the observer's freshly-loaded programme entry wins when
|
|
182
|
+
// there is one. Read through `?.` so this never constructs an observer:
|
|
183
|
+
// a VOD or cast player has none and falls straight through to its entry.
|
|
184
|
+
const entry = this.overlaysObserver?.liveEntry ?? this.entry;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
...resolvePlayerTitleSubtitle({
|
|
188
|
+
entry,
|
|
189
|
+
pluginConfiguration: this.getPluginConfiguration(),
|
|
190
|
+
dataKeys: getPlayerContentDataKeys({
|
|
191
|
+
isTV: isTV(),
|
|
192
|
+
isAudioContent: isAudioItem(entry),
|
|
193
|
+
}),
|
|
194
|
+
}),
|
|
195
|
+
// Same selection Android's `EntryHelpers.extractCoverImage` makes:
|
|
196
|
+
// prefer the `image_base` media item, otherwise the first one available.
|
|
197
|
+
// Reused rather than reimplemented so the two platforms cannot drift.
|
|
198
|
+
imageUrl: entry ? imageSrcFromMediaItem(entry, []) : undefined,
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Created lazily, like `entryObservable`. An eagerly created subject would
|
|
204
|
+
* resolve during `PlayerNative`'s constructor and call a not-yet-assigned
|
|
205
|
+
* `getPluginConfiguration`.
|
|
206
|
+
*
|
|
207
|
+
* The seed is produced by calling the resolver, never from raw entry fields:
|
|
208
|
+
* a `BehaviorSubject` replays its seed synchronously on subscribe, so a raw
|
|
209
|
+
* seed would overwrite correctly-resolved text at mount.
|
|
210
|
+
*/
|
|
211
|
+
getContentObservable = (): Observable<PlayerContent> => {
|
|
212
|
+
if (!this.contentSubject) {
|
|
213
|
+
this.contentSubject = new BehaviorSubject<PlayerContent>(
|
|
214
|
+
this.getContent()
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return this.contentSubject.asObservable().pipe(
|
|
219
|
+
// `resolvePlayerTitleSubtitle` returns a fresh object every call, so
|
|
220
|
+
// reference equality would never dedupe. Compare by value.
|
|
221
|
+
distinctUntilChanged(
|
|
222
|
+
(previous, next) =>
|
|
223
|
+
previous.title === next.title &&
|
|
224
|
+
previous.summary === next.summary &&
|
|
225
|
+
previous.imageUrl === next.imageUrl
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Re-resolves and publishes the current content. Called when something the
|
|
232
|
+
* resolution depends on changed without `entry` itself being reassigned -
|
|
233
|
+
* today that is the observer swapping in a newly-loaded live programme.
|
|
234
|
+
*/
|
|
235
|
+
publishContent = () => {
|
|
236
|
+
this.contentSubject?.next(this.getContent());
|
|
237
|
+
};
|
|
238
|
+
|
|
120
239
|
/**
|
|
121
240
|
* ---------------------------------------- PLAYER SETTERS GETTERS ----------------------------------------
|
|
122
241
|
* These methods let you get information about the content / ad playing
|
|
@@ -159,6 +278,10 @@ export class Player {
|
|
|
159
278
|
|
|
160
279
|
getEntry = (): ZappEntry | null => this.entry;
|
|
161
280
|
|
|
281
|
+
/** The entry Play Next would play now; null without one. Never creates the overlay observer. */
|
|
282
|
+
getPlayNextEntry = (): ZappEntry | null =>
|
|
283
|
+
this.overlaysObserver?.getPlayNextEntry() ?? null;
|
|
284
|
+
|
|
162
285
|
logState = (text, additionalParams = {}) => {
|
|
163
286
|
const isSeeking = this.playerState.seekPosition !== null;
|
|
164
287
|
|
|
@@ -380,6 +503,8 @@ export class Player {
|
|
|
380
503
|
};
|
|
381
504
|
|
|
382
505
|
onVideoError = (error: Error) => {
|
|
506
|
+
this.lastError = error;
|
|
507
|
+
|
|
383
508
|
this.invokeListenersUpdate({
|
|
384
509
|
callbackName: SharedPlayerCallBacksKeys.OnVideoError,
|
|
385
510
|
event: error,
|
|
@@ -387,6 +512,8 @@ export class Player {
|
|
|
387
512
|
};
|
|
388
513
|
|
|
389
514
|
onError = (error: Error) => {
|
|
515
|
+
this.lastError = error;
|
|
516
|
+
|
|
390
517
|
this.invokeListenersUpdate({
|
|
391
518
|
callbackName: SharedPlayerCallBacksKeys.OnError,
|
|
392
519
|
event: error,
|
|
@@ -634,6 +761,13 @@ export class Player {
|
|
|
634
761
|
});
|
|
635
762
|
};
|
|
636
763
|
|
|
764
|
+
onRemoteCommand = (event: QuickBrickPlayer.RemoteCommandEvent) => {
|
|
765
|
+
this.invokeListenersUpdate({
|
|
766
|
+
callbackName: SharedPlayerCallBacksKeys.OnRemoteCommand,
|
|
767
|
+
event,
|
|
768
|
+
});
|
|
769
|
+
};
|
|
770
|
+
|
|
637
771
|
getTracksState = (): QuickBrickPlayer.TracksState =>
|
|
638
772
|
this.playerState.trackState;
|
|
639
773
|
|
|
@@ -711,6 +845,8 @@ export class Player {
|
|
|
711
845
|
// PIP
|
|
712
846
|
onPlayerDetached: this.onPlayerDetached,
|
|
713
847
|
onPlayerAttached: this.onPlayerAttached,
|
|
848
|
+
|
|
849
|
+
onRemoteCommand: this.onRemoteCommand,
|
|
714
850
|
});
|
|
715
851
|
|
|
716
852
|
play = () => {};
|
|
@@ -720,6 +856,7 @@ export class Player {
|
|
|
720
856
|
setPlaybackRate = (_rate) => {};
|
|
721
857
|
startSleepTimer = (_timestamp) => {};
|
|
722
858
|
cancelSleepTimer = () => {};
|
|
859
|
+
command = (_command: string, _userInfo?: Record<string, any>) => {};
|
|
723
860
|
|
|
724
861
|
getPluginConfiguration = () => null;
|
|
725
862
|
appStateChange = (_appState, _previousAppState) => {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
2
2
|
import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
3
3
|
import { Player } from "./player";
|
|
4
|
-
import { PlayerRole } from "./
|
|
4
|
+
import { PlayerRole } from "./const";
|
|
5
5
|
|
|
6
6
|
type PlayerFactoryItem = {
|
|
7
7
|
Component: any;
|
|
@@ -19,52 +19,35 @@ type PlayerFactoryProps = {
|
|
|
19
19
|
playerRole: PlayerRole;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
interface
|
|
22
|
+
interface PlayerControllerConstructor {
|
|
23
23
|
new (params: any): Player;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
type PlayerProtocol = {
|
|
27
|
+
Component: any;
|
|
28
|
+
controllerClass: PlayerControllerConstructor;
|
|
29
|
+
};
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
export const playerFactory = async (
|
|
32
|
+
config: PlayerFactoryProps
|
|
33
|
+
): Promise<PlayerFactoryItem | null> => {
|
|
34
|
+
if (!config?.playerPluginId) return null;
|
|
34
35
|
|
|
35
36
|
const plugins = appStore.get("plugins");
|
|
37
|
+
if (!plugins) return null;
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const playerPlugin = findPluginByIdentifier(IDENTIFIER, plugins);
|
|
39
|
+
const playerPlugin = findPluginByIdentifier(config.playerPluginId, plugins);
|
|
40
|
+
if (!playerPlugin?.module?.playerProtocol) return null;
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const playerProtocol: PlayerConstructor = playerPlugin.module.playerProtocol(
|
|
48
|
-
config?.screenConfig,
|
|
49
|
-
config?.entry
|
|
50
|
-
);
|
|
42
|
+
const playerProtocol: PlayerProtocol | null =
|
|
43
|
+
await playerPlugin.module.playerProtocol(config.screenConfig, config.entry);
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!playerView) {
|
|
45
|
+
if (!playerProtocol?.Component || !playerProtocol.controllerClass) {
|
|
55
46
|
return null;
|
|
56
47
|
}
|
|
57
48
|
|
|
58
|
-
const Controller = (playerProtocol as any)?.controllerClass || null;
|
|
59
|
-
|
|
60
|
-
if (Controller === null) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const controller = new Controller(config);
|
|
65
|
-
|
|
66
49
|
return {
|
|
67
|
-
controller,
|
|
68
|
-
Component:
|
|
50
|
+
controller: new playerProtocol.controllerClass(config),
|
|
51
|
+
Component: playerProtocol.Component,
|
|
69
52
|
};
|
|
70
53
|
};
|
|
@@ -3,9 +3,10 @@ import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
|
3
3
|
import { createLogger, utilsLogger } from "../../logger";
|
|
4
4
|
import { Player } from "./player";
|
|
5
5
|
import { findNodeHandle } from "react-native";
|
|
6
|
-
import { PlayerRole } from "./
|
|
6
|
+
import { PlayerRole } from "./const";
|
|
7
7
|
import { toBooleanWithDefaultFalse } from "../../booleanUtils";
|
|
8
8
|
import { RNPlayerManager } from "./nativePlayerManager";
|
|
9
|
+
import { isApplePlatform } from "../../reactUtils";
|
|
9
10
|
|
|
10
11
|
const logger = createLogger({
|
|
11
12
|
category: "PlayerNativeController",
|
|
@@ -27,6 +28,7 @@ interface PlayerMethods {
|
|
|
27
28
|
appStateChange?: (appState: string, previousAppState: string) => void;
|
|
28
29
|
closeNativePlayer?: () => void;
|
|
29
30
|
togglePlayPause?: () => void;
|
|
31
|
+
command?: (command: string, userInfo?: Record<string, any>) => void;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
type PlayerComponent = ReactComponent<PlayerRefProps> & PlayerMethods;
|
|
@@ -52,6 +54,7 @@ const PlayerModuleFuncNames = {
|
|
|
52
54
|
startSleepTimer: "startSleepTimer",
|
|
53
55
|
cancelSleepTimer: "cancelSleepTimer",
|
|
54
56
|
getSleepTimerEnd: "getSleepTimerEnd",
|
|
57
|
+
command: "command",
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
export class PlayerNative extends Player {
|
|
@@ -206,6 +209,25 @@ export class PlayerNative extends Player {
|
|
|
206
209
|
}
|
|
207
210
|
};
|
|
208
211
|
|
|
212
|
+
command = (command: string, userInfo?: Record<string, any>) => {
|
|
213
|
+
if (isApplePlatform()) {
|
|
214
|
+
const module = this.getPlayerModule();
|
|
215
|
+
module?.command?.(command, this.playerId, userInfo);
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
!this.invokeNativeFunction(
|
|
222
|
+
PlayerModuleFuncNames.command,
|
|
223
|
+
command,
|
|
224
|
+
userInfo
|
|
225
|
+
)
|
|
226
|
+
) {
|
|
227
|
+
this.currentPlayerComponent()?.command?.(command, userInfo);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
209
231
|
disableBufferAnimation = (): boolean => false;
|
|
210
232
|
|
|
211
233
|
selectTrack = (
|
|
@@ -296,9 +318,11 @@ export class PlayerNative extends Player {
|
|
|
296
318
|
}, listeners);
|
|
297
319
|
};
|
|
298
320
|
|
|
299
|
-
isAudioItem = () =>
|
|
300
|
-
|
|
301
|
-
|
|
321
|
+
isAudioItem = (): boolean =>
|
|
322
|
+
Boolean(
|
|
323
|
+
this.getEntry()?.content?.type?.includes?.("audio") ||
|
|
324
|
+
this.getEntry()?.type?.value?.includes?.("audio")
|
|
325
|
+
);
|
|
302
326
|
|
|
303
327
|
isFullScreenSupported = (): boolean => {
|
|
304
328
|
const config = this.getConfig();
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { isApplePlatform } from "../../reactUtils";
|
|
2
2
|
import { NativeModules } from "react-native";
|
|
3
3
|
|
|
4
|
-
const NativePlayerModule = isApplePlatform()
|
|
5
|
-
? NativeModules.PlayerModule
|
|
6
|
-
: NativeModules.QuickBrickDefaultPlayerModule;
|
|
7
|
-
|
|
8
4
|
const PlayerNativeCommandTypes = {
|
|
9
5
|
pause: "pause",
|
|
10
6
|
clearPlayerData: "clearPlayerData",
|
|
11
7
|
removeViewByNativeId: "removeViewByNativeId",
|
|
8
|
+
setPlayerControls: "setPlayerControls",
|
|
12
9
|
};
|
|
13
10
|
|
|
14
11
|
const PlayerNativeSendCommand = (
|
|
@@ -16,8 +13,8 @@ const PlayerNativeSendCommand = (
|
|
|
16
13
|
playerId: string,
|
|
17
14
|
userInfo?: Record<string, any>
|
|
18
15
|
) => {
|
|
19
|
-
if (playerId && command) {
|
|
20
|
-
|
|
16
|
+
if (isApplePlatform() && playerId && command) {
|
|
17
|
+
NativeModules.PlayerModule?.command?.(command, playerId, userInfo);
|
|
21
18
|
}
|
|
22
19
|
};
|
|
23
20
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { registerXrayToolbox, XrayToolSpec } from "../../logger/xrayToolbox";
|
|
2
|
+
import type { PlayerManager } from "./index";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* X-Ray `player.*` toolbox: lets an agent read and drive the active player without screenshots.
|
|
6
|
+
* Registered by the PlayerManager singleton itself, so it exists wherever players do. Skipped
|
|
7
|
+
* when the installed X-Ray plugin has no React toolbox API.
|
|
8
|
+
*
|
|
9
|
+
* How the player is shown (video modal mode, mini player) is navigation state: see `nav.*`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const toNumber = (value: unknown) =>
|
|
13
|
+
typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
14
|
+
|
|
15
|
+
const clamp = (value: number, max: number) => Math.min(Math.max(value, 0), max);
|
|
16
|
+
|
|
17
|
+
/** Key systems the entry declares (widevine, playready, fairplay...). */
|
|
18
|
+
const drmKeySystems = (drm: unknown) =>
|
|
19
|
+
drm && typeof drm === "object" && Object.keys(drm).length
|
|
20
|
+
? Object.keys(drm)
|
|
21
|
+
: null;
|
|
22
|
+
|
|
23
|
+
/** The range a seek can reach: the DVR window on live, the content on VOD. */
|
|
24
|
+
const requireSeekable = (player: {
|
|
25
|
+
getSeekableDuration: () => number | undefined;
|
|
26
|
+
}) => {
|
|
27
|
+
const seekable = toNumber(player.getSeekableDuration());
|
|
28
|
+
|
|
29
|
+
if (!seekable) throw new Error("Seek is not supported");
|
|
30
|
+
|
|
31
|
+
return seekable;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const tools = (manager: PlayerManager): XrayToolSpec[] => {
|
|
35
|
+
const requirePlayer = () => {
|
|
36
|
+
const player = manager.getActivePlayer();
|
|
37
|
+
|
|
38
|
+
if (!player) throw new Error("No active player");
|
|
39
|
+
|
|
40
|
+
return player;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
name: "state",
|
|
46
|
+
description:
|
|
47
|
+
"The active player: entry, position and duration in seconds (the ad's while an ad plays), " +
|
|
48
|
+
"playing/buffering/ad/live/muted, role (Primary, Cell, Chromecast) and casting; in seconds, " +
|
|
49
|
+
"seekableDuration and contentPosition/contentDuration (the content's, even during ads); " +
|
|
50
|
+
"drm key systems, lastError {message} and next {id, title}, each null when absent; " +
|
|
51
|
+
"plugin (player plugin id) and engine (the default player's web engine, else null). " +
|
|
52
|
+
"active: false when nothing plays.",
|
|
53
|
+
handler: () => {
|
|
54
|
+
const player = manager.getActivePlayer();
|
|
55
|
+
|
|
56
|
+
if (!player) return { active: false, casting: manager.isCasting() };
|
|
57
|
+
|
|
58
|
+
const entry = player.getEntry();
|
|
59
|
+
const seekableDuration = toNumber(player.getSeekableDuration());
|
|
60
|
+
const next = player.getPlayNextEntry();
|
|
61
|
+
const error: any = player.lastError;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
active: true,
|
|
65
|
+
playerId: player.playerId,
|
|
66
|
+
entry: entry ? { id: entry.id, title: entry.title } : null,
|
|
67
|
+
position: toNumber(player.getPosition()),
|
|
68
|
+
duration: toNumber(player.getDuration()),
|
|
69
|
+
// isPlaying() only means "not paused", which is also true before the stream starts.
|
|
70
|
+
playing: !!player.isReadyToPlay() && player.isPlaying(),
|
|
71
|
+
buffering: !!player.isBuffering(),
|
|
72
|
+
readyToPlay: !!player.isReadyToPlay(),
|
|
73
|
+
ad: player.isAd(),
|
|
74
|
+
live: !!player.isLive(),
|
|
75
|
+
muted: !!player.getIsMuted(),
|
|
76
|
+
role: player.playerRole,
|
|
77
|
+
plugin: player.playerPluginId ?? null,
|
|
78
|
+
engine: player.engine ?? null,
|
|
79
|
+
casting: manager.isCasting(),
|
|
80
|
+
seekableDuration,
|
|
81
|
+
contentPosition: toNumber(player.getContentPosition()),
|
|
82
|
+
contentDuration: toNumber(player.getContentDuration()),
|
|
83
|
+
drm: drmKeySystems(entry?.extensions?.drm),
|
|
84
|
+
lastError:
|
|
85
|
+
error == null
|
|
86
|
+
? null
|
|
87
|
+
: { message: String(error?.message ?? error?.error ?? error) },
|
|
88
|
+
next: next ? { id: next.id, title: next.title } : null,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "play",
|
|
94
|
+
description: "Resume the active player.",
|
|
95
|
+
handler: () => {
|
|
96
|
+
requirePlayer().play();
|
|
97
|
+
|
|
98
|
+
return "OK";
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "pause",
|
|
103
|
+
description: "Pause the active player.",
|
|
104
|
+
handler: () => {
|
|
105
|
+
requirePlayer().pause();
|
|
106
|
+
|
|
107
|
+
return "OK";
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "next",
|
|
112
|
+
description:
|
|
113
|
+
"Play the next item, as the Play Next overlay does. Fails when there is none.",
|
|
114
|
+
handler: () => {
|
|
115
|
+
const player = requirePlayer();
|
|
116
|
+
const next = player.getPlayNextEntry();
|
|
117
|
+
|
|
118
|
+
if (!next || !player.playNextHandler) throw new Error("No next item");
|
|
119
|
+
|
|
120
|
+
player.playNextHandler();
|
|
121
|
+
|
|
122
|
+
return { id: next.id, title: next.title };
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "seek",
|
|
127
|
+
description:
|
|
128
|
+
"Seek to an absolute position, as the player's console command does: seconds from the start, " +
|
|
129
|
+
"or from the end when negative. Clamped to the seekable range. Where it lands can differ: " +
|
|
130
|
+
"server-side ads snap back to the break before the target; read player.state afterwards.",
|
|
131
|
+
parameters: [
|
|
132
|
+
{
|
|
133
|
+
name: "position",
|
|
134
|
+
type: "number",
|
|
135
|
+
description:
|
|
136
|
+
"Seconds from the start; negative values count back from the end.",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
handler: ({ position }) => {
|
|
140
|
+
const player = requirePlayer();
|
|
141
|
+
const seconds = toNumber(position);
|
|
142
|
+
|
|
143
|
+
if (seconds === null) {
|
|
144
|
+
throw new Error("'position' must be a number of seconds");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const seekable = requireSeekable(player);
|
|
148
|
+
|
|
149
|
+
const target =
|
|
150
|
+
seconds >= 0
|
|
151
|
+
? Math.min(seconds, seekable)
|
|
152
|
+
: clamp(seekable + seconds, seekable);
|
|
153
|
+
|
|
154
|
+
player.seekTo(target);
|
|
155
|
+
|
|
156
|
+
return { position: target };
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "offset",
|
|
161
|
+
description:
|
|
162
|
+
"Seek relative to the current position, as the player's console command does: positive " +
|
|
163
|
+
"forward, negative back. Clamped to the seekable range.",
|
|
164
|
+
parameters: [
|
|
165
|
+
{
|
|
166
|
+
name: "seconds",
|
|
167
|
+
type: "number",
|
|
168
|
+
description: "Seconds to move: positive forward, negative back.",
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
handler: ({ seconds }) => {
|
|
172
|
+
const player = requirePlayer();
|
|
173
|
+
const delta = toNumber(seconds);
|
|
174
|
+
|
|
175
|
+
if (delta === null) throw new Error("'seconds' must be a number");
|
|
176
|
+
|
|
177
|
+
if (delta === 0) throw new Error("Ignored zero relative seek");
|
|
178
|
+
|
|
179
|
+
const seekable = requireSeekable(player);
|
|
180
|
+
const from = toNumber(player.getContentPosition()) ?? 0;
|
|
181
|
+
const target = clamp(from + delta, seekable);
|
|
182
|
+
|
|
183
|
+
player.seekTo(target);
|
|
184
|
+
|
|
185
|
+
return { from, position: target };
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const registerPlayerToolbox = (manager: PlayerManager) =>
|
|
192
|
+
registerXrayToolbox({
|
|
193
|
+
name: "player",
|
|
194
|
+
description: "Inspect and control the active player.",
|
|
195
|
+
tools: tools(manager),
|
|
196
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Player, PlayerContent } from "./player";
|
|
3
|
+
import { usePlayer } from "./usePlayer";
|
|
4
|
+
|
|
5
|
+
const EMPTY_CONTENT: PlayerContent = {
|
|
6
|
+
title: null,
|
|
7
|
+
summary: null,
|
|
8
|
+
imageUrl: null,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Subscribes to the player's resolved Title/Subtitle, the same way
|
|
13
|
+
* `usePlayerState` subscribes to playback state. Modeled on that hook
|
|
14
|
+
* deliberately: one React bridge pattern for everything the manager publishes.
|
|
15
|
+
*
|
|
16
|
+
* Passing no `playerId` uses the active player, which is the right default -
|
|
17
|
+
* only one player instance is live at a time. `usePlayer` additionally swaps
|
|
18
|
+
* to the casting receiver while casting, so this follows cast handover
|
|
19
|
+
* automatically.
|
|
20
|
+
*/
|
|
21
|
+
export const usePlayerContent = (playerId?: string): PlayerContent => {
|
|
22
|
+
const player: Player = usePlayer(playerId);
|
|
23
|
+
|
|
24
|
+
const [content, setContent] = React.useState<PlayerContent>(
|
|
25
|
+
() => player?.getContent() ?? EMPTY_CONTENT
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
if (!player) {
|
|
30
|
+
setContent(EMPTY_CONTENT);
|
|
31
|
+
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const subscription = player
|
|
36
|
+
.getContentObservable()
|
|
37
|
+
.subscribe((next) => setContent(next));
|
|
38
|
+
|
|
39
|
+
return () => subscription.unsubscribe();
|
|
40
|
+
}, [player]);
|
|
41
|
+
|
|
42
|
+
return content;
|
|
43
|
+
};
|
|
@@ -3,11 +3,12 @@ import { useConfiguration } from "../../reactHooks/useConfiguration";
|
|
|
3
3
|
import { useEffect } from "react";
|
|
4
4
|
import { playerManager } from "./index";
|
|
5
5
|
import { useValidatePlayerConfig } from "../../playerUtils/useValidatePlayerConfig";
|
|
6
|
-
import { PlayerRole } from "./
|
|
6
|
+
import { PlayerRole } from "./const";
|
|
7
7
|
import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
|
|
8
8
|
import { TVSeekController } from "../../reactHooks/player/TVSeekControlller/TVSeekController";
|
|
9
9
|
import { KeyInputHandler } from "../keyInputHandler/KeyInputHandler";
|
|
10
10
|
import { isTV } from "../../reactUtils";
|
|
11
|
+
import { PlayerAnalyticsManager } from "../../analyticsUtils/PlayerAnalyticsManager";
|
|
11
12
|
|
|
12
13
|
// TODO: Rename to ControllerType
|
|
13
14
|
export const usePlayerControllerSetup = ({
|
|
@@ -68,6 +69,11 @@ export const usePlayerControllerSetup = ({
|
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
// The analytics manager attaches its listener when a player registers, so it
|
|
73
|
+
// must exist first; on web the Player wrapper that also creates it renders
|
|
74
|
+
// only after the lazily loaded engine chunk, i.e. after registration.
|
|
75
|
+
PlayerAnalyticsManager.getInstance();
|
|
76
|
+
|
|
71
77
|
playerManager.registerPlayer({
|
|
72
78
|
id: playerId,
|
|
73
79
|
playerController,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
2
2
|
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
|
|
3
3
|
import { createLogger, utilsLogger } from "../../logger";
|
|
4
|
-
import { userPreferencesNamespace } from "./
|
|
4
|
+
import { userPreferencesNamespace } from "./const";
|
|
5
5
|
|
|
6
6
|
export const preferredAudioLanguageStorageKey = "preferred_audio_language";
|
|
7
7
|
|
|
@@ -21,4 +21,12 @@ describe("anyTruthy", () => {
|
|
|
21
21
|
expect(anyTruthy([true])).toBe(true);
|
|
22
22
|
expect(anyTruthy([false])).toBe(false);
|
|
23
23
|
});
|
|
24
|
+
|
|
25
|
+
it("should return true when the first value is true", () => {
|
|
26
|
+
expect(anyTruthy([true, false, false])).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should return true when only the last value is true", () => {
|
|
30
|
+
expect(anyTruthy([false, false, true])).toBe(true);
|
|
31
|
+
});
|
|
24
32
|
});
|