@applicaster/zapp-react-native-ui-components 16.0.0-alpha.3081324238 → 16.0.0-alpha.3202041045
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/Components/MasterCell/DefaultComponents/ActionButton.tsx +13 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +12 -3
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +3 -0
- package/Components/MasterCell/DefaultComponents/__tests__/ActionButton.test.tsx +288 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +2 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +4 -2
- package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
- package/Components/MasterCell/index.tsx +28 -4
- package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
- package/Components/MasterCell/utils/index.ts +18 -0
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +40 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +55 -15
- package/Components/ModalComponent/BottomSheetModalContent.tsx +5 -1
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +25 -0
- package/Components/ModalComponent/showTextInput.ts +2 -0
- package/Components/PlayerContainer/PlayerContainer.tsx +22 -5
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +51 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +4 -1
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
- package/Components/VideoLive/LiveImageManager.ts +126 -12
- package/Components/VideoLive/MuteUnmuteController.ts +135 -0
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +15 -19
- package/Components/VideoLive/__tests__/LiveImageMuteState.test.ts +311 -0
- package/Components/VideoLive/__tests__/MuteUnmuteController.test.ts +135 -0
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +63 -1
- package/Components/VideoModal/PlayerWrapper.tsx +4 -1
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
- package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
- package/Components/VideoModal/playerWrapperStyle.ts +21 -3
- package/Components/VideoModal/playerWrapperUtils.ts +7 -1
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
- package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
- package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
- package/package.json +5 -5
|
@@ -381,6 +381,22 @@ export function mergeHeaderConfiguration(
|
|
|
381
381
|
// Button Merging Helpers
|
|
382
382
|
// ---------------------------------------------------------------------------
|
|
383
383
|
|
|
384
|
+
function fontFamilyName(value: unknown): string | undefined {
|
|
385
|
+
if (typeof value === "string" && value.trim()) {
|
|
386
|
+
return value;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (value && typeof value === "object") {
|
|
390
|
+
const family = (value as { fontFamily?: unknown }).fontFamily;
|
|
391
|
+
|
|
392
|
+
if (typeof family === "string" && family.trim()) {
|
|
393
|
+
return family;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
|
|
384
400
|
function mergeButtonBackground(
|
|
385
401
|
base: ButtonBackgroundConfiguration,
|
|
386
402
|
defaultOverride?: Record<string, any>,
|
|
@@ -399,6 +415,9 @@ function mergeButtonBackground(
|
|
|
399
415
|
...(defaultOverride?.backgroundColor
|
|
400
416
|
? { defaultColor: defaultOverride.backgroundColor }
|
|
401
417
|
: {}),
|
|
418
|
+
...(defaultOverride?.inactiveColor
|
|
419
|
+
? { inactiveColor: defaultOverride.inactiveColor }
|
|
420
|
+
: {}),
|
|
402
421
|
...(focusedColor != null && focusedColor !== "" ? { focusedColor } : {}),
|
|
403
422
|
...(defaultOverride?.borderRadius !== undefined
|
|
404
423
|
? { cornerRadius: defaultOverride.borderRadius }
|
|
@@ -415,47 +434,62 @@ function mergeButtonBackground(
|
|
|
415
434
|
...(defaultOverride?.paddingLeft !== undefined
|
|
416
435
|
? { paddingLeft: defaultOverride.paddingLeft }
|
|
417
436
|
: {}),
|
|
437
|
+
...(defaultOverride?.horizontalGutter !== undefined
|
|
438
|
+
? { horizontalGutter: defaultOverride.horizontalGutter }
|
|
439
|
+
: {}),
|
|
418
440
|
};
|
|
419
441
|
}
|
|
420
442
|
|
|
421
443
|
function mergeButtonTitle(
|
|
422
444
|
base: ButtonTitleConfiguration,
|
|
423
|
-
override?: Record<string, any
|
|
445
|
+
override?: Record<string, any>,
|
|
446
|
+
focusedOverride?: Record<string, any>
|
|
424
447
|
): ButtonTitleConfiguration {
|
|
425
|
-
if (!override) return base;
|
|
448
|
+
if (!override && !focusedOverride) return base;
|
|
449
|
+
|
|
450
|
+
const fontFamily = fontFamilyName(override?.fontFamily);
|
|
451
|
+
|
|
452
|
+
const focusedFontColor =
|
|
453
|
+
focusedOverride?.fontColor || override?.focusedFontColor;
|
|
454
|
+
|
|
455
|
+
const inactiveFontColor = override?.inactiveFontColor;
|
|
426
456
|
|
|
427
457
|
return {
|
|
428
458
|
...base,
|
|
429
|
-
...(override
|
|
430
|
-
...(
|
|
431
|
-
...(
|
|
459
|
+
...(override?.fontColor ? { fontColor: override.fontColor } : {}),
|
|
460
|
+
...(focusedFontColor ? { focusedFontColor } : {}),
|
|
461
|
+
...(inactiveFontColor ? { inactiveFontColor } : {}),
|
|
462
|
+
...(override?.fontSize !== undefined
|
|
463
|
+
? { fontSize: override.fontSize }
|
|
464
|
+
: {}),
|
|
465
|
+
...(override?.lineHeight !== undefined
|
|
432
466
|
? { lineHeight: override.lineHeight }
|
|
433
467
|
: {}),
|
|
434
|
-
...(
|
|
468
|
+
...(fontFamily
|
|
435
469
|
? {
|
|
436
|
-
iosFontFamily:
|
|
437
|
-
androidFontFamily:
|
|
470
|
+
iosFontFamily: fontFamily,
|
|
471
|
+
androidFontFamily: fontFamily,
|
|
438
472
|
}
|
|
439
473
|
: {}),
|
|
440
|
-
...(override
|
|
474
|
+
...(override?.iosFontFamily
|
|
441
475
|
? { iosFontFamily: override.iosFontFamily }
|
|
442
476
|
: {}),
|
|
443
|
-
...(override
|
|
477
|
+
...(override?.androidFontFamily
|
|
444
478
|
? { androidFontFamily: override.androidFontFamily }
|
|
445
479
|
: {}),
|
|
446
|
-
...(override
|
|
480
|
+
...(override?.letterSpacing !== undefined
|
|
447
481
|
? {
|
|
448
482
|
iosLetterSpacing: override.letterSpacing,
|
|
449
483
|
androidLetterSpacing: override.letterSpacing,
|
|
450
484
|
}
|
|
451
485
|
: {}),
|
|
452
|
-
...(override
|
|
486
|
+
...(override?.iosLetterSpacing !== undefined
|
|
453
487
|
? { iosLetterSpacing: override.iosLetterSpacing }
|
|
454
488
|
: {}),
|
|
455
|
-
...(override
|
|
489
|
+
...(override?.androidLetterSpacing !== undefined
|
|
456
490
|
? { androidLetterSpacing: override.androidLetterSpacing }
|
|
457
491
|
: {}),
|
|
458
|
-
...(override
|
|
492
|
+
...(override?.textTransform
|
|
459
493
|
? { textTransform: override.textTransform }
|
|
460
494
|
: {}),
|
|
461
495
|
};
|
|
@@ -511,13 +545,19 @@ export function mergeButtonConfiguration(
|
|
|
511
545
|
const titleOverride =
|
|
512
546
|
buttonBlock?.title?.default || actionBlock?.title?.default;
|
|
513
547
|
|
|
548
|
+
const titleFocused =
|
|
549
|
+
buttonBlock?.title?.focused ||
|
|
550
|
+
buttonBlock?.title?.focus ||
|
|
551
|
+
actionBlock?.title?.focused ||
|
|
552
|
+
actionBlock?.title?.focus;
|
|
553
|
+
|
|
514
554
|
const iconsOverride = buttonBlock?.icons || actionBlock?.icons;
|
|
515
555
|
const leadingOverride = buttonBlock?.leadingIcon || actionBlock?.leadingIcon;
|
|
516
556
|
|
|
517
557
|
return {
|
|
518
558
|
...base,
|
|
519
559
|
background: mergeButtonBackground(base.background, bgDefault, bgFocused),
|
|
520
|
-
title: mergeButtonTitle(base.title, titleOverride),
|
|
560
|
+
title: mergeButtonTitle(base.title, titleOverride, titleFocused),
|
|
521
561
|
icons: mergeButtonIcons(base.icons, iconsOverride, leadingOverride),
|
|
522
562
|
};
|
|
523
563
|
}
|
|
@@ -90,6 +90,7 @@ type ModalComponentProps = {
|
|
|
90
90
|
/** Optional body that replaces the items list (header stays DefaultModalHeader). */
|
|
91
91
|
contentComponent?: React.ComponentType<any>;
|
|
92
92
|
contentComponentProps?: Record<string, any>;
|
|
93
|
+
themePluginId?: string;
|
|
93
94
|
styleOverrides?: ModalBlocksConfig | Record<string, any>;
|
|
94
95
|
measurementPhase?: boolean;
|
|
95
96
|
/** Copy under Next Up when the queue has no upcoming items. */
|
|
@@ -115,6 +116,7 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
115
116
|
iconPlacement,
|
|
116
117
|
contentComponent: ContentComponent,
|
|
117
118
|
contentComponentProps = {},
|
|
119
|
+
themePluginId: themePluginIdProp,
|
|
118
120
|
measurementPhase = false,
|
|
119
121
|
emptyMessage,
|
|
120
122
|
} = props;
|
|
@@ -139,12 +141,14 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
139
141
|
role,
|
|
140
142
|
behavior,
|
|
141
143
|
editableCollection,
|
|
142
|
-
themePluginId,
|
|
144
|
+
themePluginId: themePluginIdFromSheet,
|
|
143
145
|
} = useBottomSheetContent({
|
|
144
146
|
onPress,
|
|
145
147
|
viewModel,
|
|
146
148
|
});
|
|
147
149
|
|
|
150
|
+
const themePluginId = themePluginIdFromSheet || themePluginIdProp;
|
|
151
|
+
|
|
148
152
|
const plugins = usePlugins();
|
|
149
153
|
|
|
150
154
|
const plugin = useMemo(
|
|
@@ -92,4 +92,29 @@ describe("showTextInputAction", () => {
|
|
|
92
92
|
expect(result).toBe(ActionResult.Error);
|
|
93
93
|
expect(actionExecutor.handleAction).not.toHaveBeenCalled();
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
it("forwards themePluginId so the sheet can read plugin button styles", async () => {
|
|
97
|
+
const action = {
|
|
98
|
+
type: "showTextInput",
|
|
99
|
+
options: {
|
|
100
|
+
headerTitle: "Create New Playlist",
|
|
101
|
+
inputLabel: "Name your playlist",
|
|
102
|
+
buttonLabel: "Create",
|
|
103
|
+
themePluginId: "playlist-experience",
|
|
104
|
+
actions: [{ type: "sendCloudEvent", options: {} }],
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
await showTextInputAction(action as any);
|
|
109
|
+
|
|
110
|
+
expect(actionExecutor.handleAction).toHaveBeenCalledWith(
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
type: "openBottomSheet",
|
|
113
|
+
options: expect.objectContaining({
|
|
114
|
+
themePluginId: "playlist-experience",
|
|
115
|
+
}),
|
|
116
|
+
}),
|
|
117
|
+
undefined
|
|
118
|
+
);
|
|
119
|
+
});
|
|
95
120
|
});
|
|
@@ -21,6 +21,7 @@ export async function showTextInputAction(
|
|
|
21
21
|
validationLabel,
|
|
22
22
|
actions,
|
|
23
23
|
maxLength,
|
|
24
|
+
themePluginId,
|
|
24
25
|
} = action.options || {};
|
|
25
26
|
|
|
26
27
|
if (!headerTitle || !buttonLabel || !actions || !actions.length) {
|
|
@@ -34,6 +35,7 @@ export async function showTextInputAction(
|
|
|
34
35
|
header: {
|
|
35
36
|
title: headerTitle,
|
|
36
37
|
},
|
|
38
|
+
...(themePluginId ? { themePluginId } : {}),
|
|
37
39
|
content: {
|
|
38
40
|
component: TextInputContent,
|
|
39
41
|
props: {
|
|
@@ -273,7 +273,10 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
273
273
|
const playEntry = (entry: ZappEntry) => navigator.replaceTop(entry, { mode });
|
|
274
274
|
|
|
275
275
|
const onPlayNextPerformNextVideoPlay = React.useCallback(() => {
|
|
276
|
-
|
|
276
|
+
// The overlay's entry, or the player's when the overlay is not showing yet (X-Ray player.next)
|
|
277
|
+
const nextEntry = playNextOverlayState?.entry ?? player?.getPlayNextEntry();
|
|
278
|
+
|
|
279
|
+
if (!nextEntry) {
|
|
277
280
|
log_warning(
|
|
278
281
|
"onPlayNextPerformNextVideoPlay: playNextOverlayState.entry is not defined"
|
|
279
282
|
);
|
|
@@ -282,16 +285,30 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
282
285
|
}
|
|
283
286
|
|
|
284
287
|
log_debug(
|
|
285
|
-
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${
|
|
288
|
+
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${nextEntry.title}, id: ${nextEntry.id}, outgoing item id: ${item?.id}`
|
|
286
289
|
);
|
|
287
290
|
|
|
288
|
-
|
|
291
|
+
(
|
|
292
|
+
playNextOverlayState?.hidePlayNext ??
|
|
293
|
+
player?.getOverlayObservable()?.hidePlayNext
|
|
294
|
+
)?.();
|
|
289
295
|
|
|
290
296
|
playerEvent(PLAYER_EVENTS.PlayNextTriggered, { id: item?.id });
|
|
291
297
|
|
|
292
298
|
setIsLoadingNextVideo(true);
|
|
293
|
-
playEntry(
|
|
294
|
-
}, [playNextOverlayState, item?.id]);
|
|
299
|
+
playEntry(nextEntry);
|
|
300
|
+
}, [playNextOverlayState, item?.id, player]);
|
|
301
|
+
|
|
302
|
+
// Lets the X-Ray player.next tool run the same Play Next path as the overlay.
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
if (!player) return;
|
|
305
|
+
|
|
306
|
+
player.playNextHandler = onPlayNextPerformNextVideoPlay;
|
|
307
|
+
|
|
308
|
+
return () => {
|
|
309
|
+
player.playNextHandler = null;
|
|
310
|
+
};
|
|
311
|
+
}, [player, onPlayNextPerformNextVideoPlay]);
|
|
295
312
|
|
|
296
313
|
const playNextData: Nullable<PlayNextData> = React.useMemo(() => {
|
|
297
314
|
if (!playNextOverlayState) {
|
|
@@ -127,6 +127,8 @@ jest.mock(
|
|
|
127
127
|
seekTo: jest.fn(),
|
|
128
128
|
getPluginConfiguration: jest.fn(() => ({})),
|
|
129
129
|
getOverlayObservable: jest.fn(() => ({ getPlayNextEntry: () => null })),
|
|
130
|
+
getPlayNextEntry: jest.fn(() => null),
|
|
131
|
+
playNextHandler: null,
|
|
130
132
|
};
|
|
131
133
|
|
|
132
134
|
return {
|
|
@@ -335,3 +337,52 @@ describe("PlayerContainer hardware back handling", () => {
|
|
|
335
337
|
expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
|
|
336
338
|
});
|
|
337
339
|
});
|
|
340
|
+
|
|
341
|
+
describe("PlayerContainer Play Next action for X-Ray player.next", () => {
|
|
342
|
+
beforeEach(() => {
|
|
343
|
+
jest.clearAllMocks();
|
|
344
|
+
mockIsTV.mockReturnValue(false);
|
|
345
|
+
mockPlayer.getPluginConfiguration.mockReturnValue({});
|
|
346
|
+
mockPlayerManager.getPluginConfiguration.mockReturnValue({});
|
|
347
|
+
mockPlayerManager.on.mockReturnThis();
|
|
348
|
+
mockPlayerManager.removeHandler.mockReturnThis();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("sets its Play Next action on the player and clears it on unmount", () => {
|
|
352
|
+
const { unmount } = renderPlayerContainer();
|
|
353
|
+
|
|
354
|
+
expect(mockPlayer.playNextHandler).toEqual(expect.any(Function));
|
|
355
|
+
|
|
356
|
+
unmount();
|
|
357
|
+
|
|
358
|
+
expect(mockPlayer.playNextHandler).toBeNull();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("plays the player's next entry once, with analytics and the overlay hidden, when the overlay is not showing", () => {
|
|
362
|
+
const next = { id: "entry-2", title: "Next" };
|
|
363
|
+
const hidePlayNext = jest.fn();
|
|
364
|
+
mockPlayer.getPlayNextEntry.mockReturnValue(next);
|
|
365
|
+
|
|
366
|
+
mockPlayer.getOverlayObservable.mockReturnValue({
|
|
367
|
+
getPlayNextEntry: () => next,
|
|
368
|
+
hidePlayNext,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
renderPlayerContainer();
|
|
372
|
+
|
|
373
|
+
mockPlayer.playNextHandler();
|
|
374
|
+
|
|
375
|
+
expect(hidePlayNext).toHaveBeenCalledTimes(1);
|
|
376
|
+
|
|
377
|
+
expect(mockPlayerManager.invokeHandler).toHaveBeenCalledWith(
|
|
378
|
+
"play_next_triggered",
|
|
379
|
+
{ id: "entry-1" }
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
expect(mockNavigator.replaceTop).toHaveBeenCalledTimes(1);
|
|
383
|
+
|
|
384
|
+
expect(mockNavigator.replaceTop).toHaveBeenCalledWith(next, {
|
|
385
|
+
mode: VideoModalMode.MAXIMIZED,
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
});
|
|
@@ -118,7 +118,10 @@ function ComponentsMapComponent(props: Props) {
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
return (
|
|
121
|
-
<ScreenLoadingMeasurementsListItemWrapper
|
|
121
|
+
<ScreenLoadingMeasurementsListItemWrapper
|
|
122
|
+
index={index}
|
|
123
|
+
testID={`component-${item.id}`}
|
|
124
|
+
>
|
|
122
125
|
<RiverItem {...riverItemProps} />
|
|
123
126
|
</ScreenLoadingMeasurementsListItemWrapper>
|
|
124
127
|
);
|
|
@@ -213,6 +213,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
213
213
|
<View
|
|
214
214
|
onLayout={[Function]}
|
|
215
215
|
style={{}}
|
|
216
|
+
testID="component-A1"
|
|
216
217
|
>
|
|
217
218
|
<View />
|
|
218
219
|
</View>
|
|
@@ -225,6 +226,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
225
226
|
<View
|
|
226
227
|
onLayout={[Function]}
|
|
227
228
|
style={{}}
|
|
229
|
+
testID="component-A2"
|
|
228
230
|
/>
|
|
229
231
|
</View>
|
|
230
232
|
<View
|
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
PlayerLifecycleListener,
|
|
4
4
|
playerManager,
|
|
5
5
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getUserCellPlayerMutedPreference,
|
|
8
|
+
setUserCellPlayerMutedPreference,
|
|
9
|
+
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager/userCellPlayerMutedPreference";
|
|
7
10
|
import { playerFactory } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerFactory";
|
|
8
11
|
import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/const";
|
|
9
12
|
import { loggerLiveImageManager } from "./loggerHelper";
|
|
@@ -21,13 +24,15 @@ export type LiveImageManagerEvent =
|
|
|
21
24
|
| "onEnd"
|
|
22
25
|
| "onStart"
|
|
23
26
|
| "onError"
|
|
24
|
-
| "onDestroy"
|
|
27
|
+
| "onDestroy"
|
|
28
|
+
| "onRegister"
|
|
29
|
+
| "onPlaybackChange"
|
|
30
|
+
| "onMuteChange";
|
|
25
31
|
|
|
26
32
|
export type LiveImageManagerListenerData = {
|
|
27
33
|
id: string;
|
|
28
|
-
listener:
|
|
29
|
-
LiveImageManagerEvent,
|
|
30
|
-
(event?: Record<string, any>) => void
|
|
34
|
+
listener: Partial<
|
|
35
|
+
Record<LiveImageManagerEvent, (event?: Record<string, any>) => void>
|
|
31
36
|
>;
|
|
32
37
|
};
|
|
33
38
|
|
|
@@ -54,6 +59,7 @@ type PlayerFactoryConfig = {
|
|
|
54
59
|
};
|
|
55
60
|
|
|
56
61
|
type LiveImageProps = {
|
|
62
|
+
audioMutedByDefault?: boolean;
|
|
57
63
|
playerId: string;
|
|
58
64
|
setMode?: (type: LiveImageType) => void;
|
|
59
65
|
preloadHooks?: PreloadHookConfig[];
|
|
@@ -81,9 +87,11 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
81
87
|
|
|
82
88
|
protected listeners: Record<
|
|
83
89
|
string,
|
|
84
|
-
|
|
90
|
+
LiveImageManagerListenerData["listener"]
|
|
85
91
|
> = {};
|
|
86
92
|
|
|
93
|
+
private mutedSubscriptions = new Map<LiveImage, () => void>();
|
|
94
|
+
|
|
87
95
|
constructor() {
|
|
88
96
|
playerManager.addLifecycleListener(this);
|
|
89
97
|
this.listeners = {};
|
|
@@ -96,8 +104,25 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
public register = (item: LiveImage): (() => void) => {
|
|
107
|
+
if (!isTV()) {
|
|
108
|
+
const muted = !!getUserCellPlayerMutedPreference(item.getMuted());
|
|
109
|
+
|
|
110
|
+
if (muted !== item.getMuted()) {
|
|
111
|
+
muted ? item.mute() : item.unmute();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
99
115
|
this.items.push(item);
|
|
116
|
+
|
|
117
|
+
this.mutedSubscriptions.set(
|
|
118
|
+
item,
|
|
119
|
+
item.subscribeMuted(() =>
|
|
120
|
+
this.invokeListenersUpdate({ callbackName: "onMuteChange" })
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
|
|
100
124
|
log_debug(`register: live image ${item.playerId} - ${item.tag}`);
|
|
125
|
+
this.invokeListenersUpdate({ callbackName: "onRegister" });
|
|
101
126
|
|
|
102
127
|
// TV only Start playing video once registered
|
|
103
128
|
if (isTV()) {
|
|
@@ -109,6 +134,8 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
109
134
|
|
|
110
135
|
public unregister = (item: LiveImage) => {
|
|
111
136
|
log_debug(`unregister: live-image ${item.playerId} - ${item.tag}`);
|
|
137
|
+
this.mutedSubscriptions.get(item)?.();
|
|
138
|
+
this.mutedSubscriptions.delete(item);
|
|
112
139
|
|
|
113
140
|
if (this.currentlyPlaying === item) {
|
|
114
141
|
this.currentlyPlaying = null;
|
|
@@ -172,6 +199,25 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
172
199
|
return this.currentlyPlaying;
|
|
173
200
|
};
|
|
174
201
|
|
|
202
|
+
public getEntryMuted = (entryId: string): boolean | undefined => {
|
|
203
|
+
const matches = (item: LiveImage) => String(item.getEntryId()) === entryId;
|
|
204
|
+
|
|
205
|
+
const item =
|
|
206
|
+
this.currentlyPlaying && matches(this.currentlyPlaying)
|
|
207
|
+
? this.currentlyPlaying
|
|
208
|
+
: this.items.find(matches);
|
|
209
|
+
|
|
210
|
+
return item?.getMuted();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
public getPlayingCellId = (entryId: string): string | undefined => {
|
|
214
|
+
const item = this.currentlyPlaying;
|
|
215
|
+
|
|
216
|
+
return item && String(item.getEntryId()) === entryId && item.isPlaying()
|
|
217
|
+
? item.playerId
|
|
218
|
+
: undefined;
|
|
219
|
+
};
|
|
220
|
+
|
|
175
221
|
private findNextPlayableItem = () => {
|
|
176
222
|
if (isTV()) {
|
|
177
223
|
return this.items[this.items.length - 1];
|
|
@@ -302,6 +348,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
302
348
|
setUserCellPlayerMutedPreference(true);
|
|
303
349
|
|
|
304
350
|
this.items.forEach((liveImage) => liveImage.mute());
|
|
351
|
+
this.invokeListenersUpdate({ callbackName: "onMuteChange" });
|
|
305
352
|
};
|
|
306
353
|
|
|
307
354
|
public unmuteAll = () => {
|
|
@@ -310,6 +357,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
310
357
|
setUserCellPlayerMutedPreference(false);
|
|
311
358
|
|
|
312
359
|
this.items.forEach((liveImage) => liveImage.unmute());
|
|
360
|
+
this.invokeListenersUpdate({ callbackName: "onMuteChange" });
|
|
313
361
|
};
|
|
314
362
|
|
|
315
363
|
public onViewPositionChanged = (item: LiveImage) => {
|
|
@@ -579,20 +627,67 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
579
627
|
left: 0,
|
|
580
628
|
};
|
|
581
629
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
630
|
+
private muted: boolean;
|
|
631
|
+
private mutedListeners = new Set<() => void>();
|
|
632
|
+
private playbackEnded = false;
|
|
633
|
+
|
|
634
|
+
getMuted = (): boolean => this.muted;
|
|
635
|
+
|
|
636
|
+
getEntryId = () => this.factoryConfig.entry.id;
|
|
637
|
+
|
|
638
|
+
isPlaying = (): boolean =>
|
|
639
|
+
!this.playbackEnded &&
|
|
640
|
+
!!this.player?.isReadyToPlay() &&
|
|
641
|
+
this.player.isPlaying();
|
|
642
|
+
|
|
643
|
+
subscribeMuted = (listener: () => void): (() => void) => {
|
|
644
|
+
this.mutedListeners.add(listener);
|
|
645
|
+
|
|
646
|
+
return () => this.mutedListeners.delete(listener);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
private setMuted = (muted: boolean) => {
|
|
650
|
+
if (this.muted === muted) return;
|
|
651
|
+
|
|
652
|
+
this.muted = muted;
|
|
653
|
+
|
|
654
|
+
this.mutedListeners.forEach((listener) => {
|
|
655
|
+
try {
|
|
656
|
+
listener();
|
|
657
|
+
} catch (error) {
|
|
658
|
+
log_error(`Mute listener failed for ${this.playerId}`, { error });
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
};
|
|
585
662
|
|
|
586
663
|
mute = () => {
|
|
587
|
-
this.
|
|
664
|
+
this.setMuted(true);
|
|
588
665
|
this.player?.mute();
|
|
589
666
|
};
|
|
590
667
|
|
|
591
668
|
unmute = () => {
|
|
592
|
-
this.
|
|
669
|
+
this.setMuted(false);
|
|
593
670
|
this.player?.unmute();
|
|
594
671
|
};
|
|
595
672
|
|
|
673
|
+
onPlayerMute = () => this.setMuted(true);
|
|
674
|
+
|
|
675
|
+
onPlayerUnmute = () => this.setMuted(false);
|
|
676
|
+
|
|
677
|
+
onPlayerResume = () => {
|
|
678
|
+
this.playbackEnded = false;
|
|
679
|
+
|
|
680
|
+
LiveImageManager.instance.invokeListenersUpdate({
|
|
681
|
+
callbackName: "onPlaybackChange",
|
|
682
|
+
});
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
onPlayerPause = () => {
|
|
686
|
+
LiveImageManager.instance.invokeListenersUpdate({
|
|
687
|
+
callbackName: "onPlaybackChange",
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
|
|
596
691
|
positionToString() {
|
|
597
692
|
if (!this.position) {
|
|
598
693
|
return "position not set";
|
|
@@ -617,6 +712,10 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
617
712
|
this.preloadHooks = props.preloadHooks;
|
|
618
713
|
this.factoryConfig = props.factoryConfig;
|
|
619
714
|
this.tag = props.tag || "untagged";
|
|
715
|
+
|
|
716
|
+
this.muted = isTV()
|
|
717
|
+
? false
|
|
718
|
+
: !!getUserCellPlayerMutedPreference(props.audioMutedByDefault);
|
|
620
719
|
}
|
|
621
720
|
|
|
622
721
|
async prepareForPlayback(): Promise<boolean> {
|
|
@@ -670,7 +769,7 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
670
769
|
playerId: this.factoryConfig.playerId,
|
|
671
770
|
autoplay: false,
|
|
672
771
|
entry,
|
|
673
|
-
muted: this.
|
|
772
|
+
muted: this.muted,
|
|
674
773
|
playerPluginId: this.factoryConfig.playerPluginId,
|
|
675
774
|
screenConfig: this.factoryConfig.screenConfig,
|
|
676
775
|
playerRole: PlayerRole.Cell,
|
|
@@ -686,6 +785,11 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
686
785
|
this.component = factoryItem.Component;
|
|
687
786
|
this.player.addListener({ id: "live-image", listener: this });
|
|
688
787
|
|
|
788
|
+
// A user toggle may have arrived while the async factory was pending.
|
|
789
|
+
if (this.player.getIsMuted() !== this.muted) {
|
|
790
|
+
this.muted ? this.player.mute() : this.player.unmute();
|
|
791
|
+
}
|
|
792
|
+
|
|
689
793
|
return true;
|
|
690
794
|
}
|
|
691
795
|
|
|
@@ -694,6 +798,8 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
694
798
|
};
|
|
695
799
|
|
|
696
800
|
onLoad = (_event) => {
|
|
801
|
+
this.playbackEnded = false;
|
|
802
|
+
|
|
697
803
|
log_info(
|
|
698
804
|
`onLoad: live-image, video loaded, switching to video, ${playerInfo(
|
|
699
805
|
this.player
|
|
@@ -704,6 +810,8 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
704
810
|
};
|
|
705
811
|
|
|
706
812
|
onError = (error: Error) => {
|
|
813
|
+
this.playbackEnded = true;
|
|
814
|
+
|
|
707
815
|
log_error(
|
|
708
816
|
`onError: live-image, fail to play, switching back to image. error: ${
|
|
709
817
|
error?.message
|
|
@@ -714,6 +822,8 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
714
822
|
};
|
|
715
823
|
|
|
716
824
|
onVideoError = (error: Error) => {
|
|
825
|
+
this.playbackEnded = true;
|
|
826
|
+
|
|
717
827
|
log_error(
|
|
718
828
|
`onVideoError: live-image, fail to play, switching back to image. error: ${
|
|
719
829
|
error?.message
|
|
@@ -724,6 +834,8 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
724
834
|
};
|
|
725
835
|
|
|
726
836
|
onVideoEnd = (_event) => {
|
|
837
|
+
this.playbackEnded = true;
|
|
838
|
+
|
|
727
839
|
log_info(
|
|
728
840
|
`onVideoEnd: live-image, video completed, switching back to image, ${playerInfo(
|
|
729
841
|
this.player
|
|
@@ -734,6 +846,8 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
734
846
|
};
|
|
735
847
|
|
|
736
848
|
onPlayerClose = () => {
|
|
849
|
+
this.playbackEnded = true;
|
|
850
|
+
|
|
737
851
|
log_info(
|
|
738
852
|
`onPlayerClose: live-image, player closed, switching back to image, ${playerInfo(
|
|
739
853
|
this.player
|