@npo/player 1.20.3 → 1.21.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/README.md +5 -0
- package/lib/js/api/getstreamobject.test.js +2 -2
- package/lib/js/markers/updateLiveMarkers.d.ts +4 -0
- package/lib/js/markers/updateLiveMarkers.js +21 -0
- package/lib/js/markers/updateLiveMarkers.test.d.ts +1 -0
- package/lib/js/markers/updateLiveMarkers.test.js +53 -0
- package/lib/js/playeractions/handlers/error.d.ts +2 -2
- package/lib/js/playeractions/handlers/processsourceconfig.d.ts +1 -1
- package/lib/js/playeractions/handlers/processsourceconfig.js +12 -10
- package/lib/js/ui/components/adbutton.js +1 -1
- package/lib/js/ui/components/buttons.js +2 -2
- package/lib/js/ui/components/nativemobile/buttons.d.ts +7 -1
- package/lib/js/ui/components/nativemobile/buttons.js +27 -4
- package/lib/js/ui/components/nativemobile/controlbar.js +1 -2
- package/lib/js/ui/components/nativemobile/playnext.d.ts +2 -1
- package/lib/js/ui/components/nativemobile/playnext.js +5 -4
- package/lib/js/ui/components/settingspanel.js +30 -23
- package/lib/js/ui/components/shared/playnextscreen.d.ts +2 -2
- package/lib/js/ui/components/shared/playnextscreen.js +14 -2
- package/lib/js/ui/components/shared/playnextstreen.test.d.ts +6 -0
- package/lib/js/ui/components/shared/playnextstreen.test.js +56 -0
- package/lib/js/ui/handlers/accessibilityhandler.js +26 -2
- package/lib/js/ui/handlers/accessibilityhandler.test.d.ts +1 -0
- package/lib/js/ui/handlers/accessibilityhandler.test.js +63 -0
- package/lib/js/ui/handlers/domhandlers.d.ts +3 -0
- package/lib/js/ui/handlers/domhandlers.js +7 -0
- package/lib/js/ui/handlers/domhandlers.test.d.ts +1 -0
- package/lib/js/ui/handlers/domhandlers.test.js +49 -0
- package/lib/js/ui/handlers/nicamhandler.js +1 -1
- package/lib/js/ui/handlers/streamhandler.js +4 -4
- package/lib/js/ui/nativemobileuicontainer.js +5 -5
- package/lib/js/ui/nativemobileuifactory.js +82 -42
- package/lib/js/ui/nativemobileuifactory.test.d.ts +1 -0
- package/lib/js/ui/nativemobileuifactory.test.js +66 -0
- package/lib/js/ui/uicontainer.js +4 -3
- package/lib/js/utilities/utilities.d.ts +3 -0
- package/lib/js/utilities/utilities.js +9 -0
- package/lib/lang/nl.json +1 -1
- package/lib/npoplayer-bridge.test.d.ts +1 -0
- package/lib/npoplayer-bridge.test.js +23 -0
- package/lib/npoplayer.d.ts +2 -1
- package/lib/npoplayer.js +13 -4
- package/lib/npoplayer.test.js +4 -4
- package/lib/package.json +3 -2
- package/lib/src/js/markers/updateLiveMarkers.d.ts +4 -0
- package/lib/src/js/markers/updateLiveMarkers.test.d.ts +1 -0
- package/lib/src/js/playeractions/handlers/error.d.ts +2 -2
- package/lib/src/js/playeractions/handlers/processsourceconfig.d.ts +1 -1
- package/lib/src/js/ui/components/nativemobile/buttons.d.ts +7 -1
- package/lib/src/js/ui/components/nativemobile/playnext.d.ts +2 -1
- package/lib/src/js/ui/components/shared/playnextscreen.d.ts +2 -2
- package/lib/src/js/ui/components/shared/playnextstreen.test.d.ts +6 -0
- package/lib/src/js/ui/handlers/accessibilityhandler.test.d.ts +1 -0
- package/lib/src/js/ui/handlers/domhandlers.d.ts +3 -0
- package/lib/src/js/ui/handlers/domhandlers.test.d.ts +1 -0
- package/lib/src/js/ui/nativemobileuifactory.test.d.ts +1 -0
- package/lib/src/js/utilities/utilities.d.ts +3 -0
- package/lib/src/npoplayer-bridge.test.d.ts +1 -0
- package/lib/src/npoplayer.d.ts +2 -1
- package/lib/src/types/interfaces.d.ts +26 -2
- package/lib/tests/mocks/mockNpoplayer.js +4 -1
- package/lib/types/interfaces.d.ts +26 -2
- package/lib/types/interfaces.js +12 -2
- package/package.json +3 -2
- package/src/scss/components/_advert.scss +34 -68
- package/src/scss/components/_hugeplaybacktogglebutton.scss +11 -0
- package/src/scss/components/_icons.scss +50 -40
- package/src/scss/components/_nicam.scss +9 -2
- package/src/scss/components/_playnext.scss +5 -1
- package/src/scss/components/_replay.scss +1 -1
- package/src/scss/components/_seekbar.scss +13 -0
- package/src/scss/components/_settingspanel.scss +37 -12
- package/src/scss/components/_textbuttons.scss +12 -11
- package/src/scss/components/_volumeslider.scss +5 -0
- package/src/scss/components/audio/_playbutton.scss +6 -0
- package/src/scss/components/audio/_volumeslider.scss +6 -1
- package/src/scss/npoplayer.css +80 -56
- package/src/scss/npoplayer.scss +3 -0
- package/src/scss/variants/_player-base.scss +1 -9
- package/src/scss/variants/_player-large.scss +20 -0
- package/src/scss/variants/_player-medium.scss +31 -1
- package/src/scss/variants/_player-native-mobile.scss +4 -0
- package/src/scss/variants/_player-small.scss +11 -8
- package/src/scss/vars/_colors.scss +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { handleLiveStreamNoDvr } from "./domhandlers";
|
|
2
|
+
jest.mock("../../utilities/utilities", () => ({
|
|
3
|
+
getPlayerElement: jest.fn(),
|
|
4
|
+
}));
|
|
5
|
+
const { getPlayerElement } = require("../../utilities/utilities");
|
|
6
|
+
describe('handleLiveStreamNoDvr', () => {
|
|
7
|
+
let mockPlayer;
|
|
8
|
+
let mockStreamObject;
|
|
9
|
+
let mockContainer;
|
|
10
|
+
let mockPlayerElement;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockPlayer = {};
|
|
13
|
+
mockStreamObject = {
|
|
14
|
+
stream: {
|
|
15
|
+
isLiveStream: false,
|
|
16
|
+
hasDvrWindow: true,
|
|
17
|
+
avType: "",
|
|
18
|
+
drmType: "",
|
|
19
|
+
streamProfile: "",
|
|
20
|
+
sourceProfile: "",
|
|
21
|
+
streamURL: ""
|
|
22
|
+
},
|
|
23
|
+
metadata: {
|
|
24
|
+
description: "",
|
|
25
|
+
title: ""
|
|
26
|
+
},
|
|
27
|
+
assets: {
|
|
28
|
+
scrubbingThumbnail: "",
|
|
29
|
+
subtitles: null,
|
|
30
|
+
preroll: ""
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
mockContainer = document.createElement('div');
|
|
34
|
+
mockPlayerElement = document.createElement('div');
|
|
35
|
+
getPlayerElement.mockReturnValue(mockPlayerElement);
|
|
36
|
+
});
|
|
37
|
+
it('should add "livestream-no-dvr" class when isLiveStream is true and hasDvrWindow is false', () => {
|
|
38
|
+
mockStreamObject.stream.isLiveStream = true;
|
|
39
|
+
mockStreamObject.stream.hasDvrWindow = false;
|
|
40
|
+
handleLiveStreamNoDvr(mockPlayer, mockStreamObject, mockContainer);
|
|
41
|
+
expect(mockPlayerElement.classList.contains('livestream-no-dvr')).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it('should remove "livestream-no-dvr" class when isLiveStream is false or hasDvrWindow is true', () => {
|
|
44
|
+
mockStreamObject.stream.isLiveStream = false;
|
|
45
|
+
mockStreamObject.stream.hasDvrWindow = true;
|
|
46
|
+
handleLiveStreamNoDvr(mockPlayer, mockStreamObject, mockContainer);
|
|
47
|
+
expect(mockPlayerElement.classList.contains('livestream-no-dvr')).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -33,7 +33,7 @@ export function showNicamAfterUiDelay(player, uiManager) {
|
|
|
33
33
|
if (!playerContainer)
|
|
34
34
|
return;
|
|
35
35
|
uiManager.activeUi.onControlsHide.subscribeOnce(() => {
|
|
36
|
-
const className = 'show-nicam';
|
|
36
|
+
const className = 'bmpui-show-nicam';
|
|
37
37
|
playerContainer.classList.add(className);
|
|
38
38
|
setTimeout(() => {
|
|
39
39
|
playerContainer.classList.remove(className);
|
|
@@ -16,15 +16,15 @@ export function processStream(streamObject, container, streamOptions, player, ui
|
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
18
|
setFragments(player, uiManager, { sections: [] });
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
|
|
19
|
+
const seekbarTitle = container.querySelector('.bmpui-seekbar-label-title');
|
|
20
|
+
if (seekbarTitle !== null) {
|
|
21
|
+
seekbarTitle.textContent = sourceConfig.title || '';
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
processNicam(streamObject, container.querySelector('.bmpui-nicam'), streamOptions);
|
|
25
25
|
if (streamOptions?.startOffset != null)
|
|
26
26
|
playerAction.handleStartOffset(player, streamOptions.startOffset);
|
|
27
|
-
const isLiveStreamNoDvr = streamObject.stream.isLiveStream &&
|
|
27
|
+
const isLiveStreamNoDvr = streamObject.stream.isLiveStream && streamObject.stream.hasDvrWindow === false;
|
|
28
28
|
if (isLiveStreamNoDvr) {
|
|
29
29
|
container.querySelector('.bmpui-npo-player')?.classList.add('livestream-no-dvr');
|
|
30
30
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BufferingOverlay, CastStatusOverlay, ErrorMessageOverlay, PlaybackToggleOverlay, SubtitleOverlay, UIContainer, } from 'bitmovin-player-ui';
|
|
1
|
+
import { BufferingOverlay, CastStatusOverlay, ErrorMessageOverlay, PlaybackToggleOverlay, PlayerUtils, SubtitleOverlay, UIContainer, } from 'bitmovin-player-ui';
|
|
2
2
|
import { createPlayNextScreen } from './components/nativemobile/playnext';
|
|
3
3
|
import { createCTABar } from './components/nativemobile/ctabar';
|
|
4
4
|
import { createControlBar } from './components/nativemobile/controlbar';
|
|
@@ -10,7 +10,6 @@ const defaultData = {
|
|
|
10
10
|
defaultActionRequired: true,
|
|
11
11
|
};
|
|
12
12
|
export function sendCustomMessage(message, data) {
|
|
13
|
-
console.log('sendCustomMessage', message, data);
|
|
14
13
|
try {
|
|
15
14
|
if (data !== undefined) {
|
|
16
15
|
return JSON.parse(window.bitmovin.customMessageHandler.sendSynchronous(message, JSON.stringify(data)));
|
|
@@ -25,7 +24,7 @@ export function sendCustomMessage(message, data) {
|
|
|
25
24
|
}
|
|
26
25
|
export function nativeMobileUIContainer(player) {
|
|
27
26
|
const middleButtons = createMiddleButtons(player);
|
|
28
|
-
const playNextScreen = createPlayNextScreen();
|
|
27
|
+
const playNextScreen = createPlayNextScreen(player);
|
|
29
28
|
const settingsPanel = createSettingsPanel(player);
|
|
30
29
|
const ctaBar = createCTABar(player);
|
|
31
30
|
const topBar = createTopBar(player, settingsPanel);
|
|
@@ -35,16 +34,17 @@ export function nativeMobileUIContainer(player) {
|
|
|
35
34
|
components: [
|
|
36
35
|
new SubtitleOverlay(),
|
|
37
36
|
new BufferingOverlay(),
|
|
38
|
-
new PlaybackToggleOverlay(
|
|
37
|
+
new PlaybackToggleOverlay(),
|
|
39
38
|
new CastStatusOverlay(),
|
|
40
|
-
playNextScreen,
|
|
41
39
|
middleButtons,
|
|
42
40
|
controlBar,
|
|
41
|
+
playNextScreen,
|
|
43
42
|
ctaBar,
|
|
44
43
|
topBar,
|
|
45
44
|
createTitleBar(),
|
|
46
45
|
errorMessageOverlay
|
|
47
46
|
],
|
|
48
47
|
cssClasses: ['npo-player', 'native-mobile'],
|
|
48
|
+
hidePlayerStateExceptions: [PlayerUtils.PlayerState.Paused],
|
|
49
49
|
});
|
|
50
50
|
}
|
|
@@ -7,81 +7,121 @@ import { addFragments } from '../ui/components/nativemobile/addFragments';
|
|
|
7
7
|
import { CustomMessages } from '../../types/interfaces';
|
|
8
8
|
import { processNicam } from './handlers/nicamhandler';
|
|
9
9
|
import { removeFragments } from '../fragments/removefragments';
|
|
10
|
+
import { showPlayNextScreenIfNeeded } from './components/shared/playnextscreen';
|
|
11
|
+
import { getPlayerElement, removePlayerElementClass } from '../utilities/utilities';
|
|
12
|
+
import { handleLiveStreamNoDvr } from './handlers/domhandlers';
|
|
10
13
|
export function nativeMobileUiFactory(player, config = {}) {
|
|
11
14
|
UIManager.setLocalizationConfig(localizationConfig);
|
|
15
|
+
const containerElement = getPlayerElement(player, '.bmpui-npo-player');
|
|
16
|
+
const nicamElement = getPlayerElement(player, '.bmpui-nicam');
|
|
17
|
+
const seekbarTitleElement = getPlayerElement(player, '.bmpui-seekbar-label-title');
|
|
18
|
+
let adBreakActive = false;
|
|
12
19
|
const uiConfig = {
|
|
13
20
|
...config,
|
|
14
21
|
errorMessages: customSpecificErrorMessageOverlayConfig,
|
|
15
22
|
disableAutoHideWhenHovered: true,
|
|
16
23
|
};
|
|
17
24
|
const mobileUIManager = new UIManager(player, nativeMobileUIContainer(player), uiConfig);
|
|
18
|
-
let
|
|
25
|
+
let _webData = {};
|
|
19
26
|
const removeFinishedClass = () => {
|
|
20
27
|
player.off(PlayerEvent.Seeked, removeFinishedClass);
|
|
21
|
-
|
|
22
|
-
if (playerFinishedElement) {
|
|
23
|
-
playerFinishedElement.classList.remove('bmpui-player-state-finished');
|
|
24
|
-
}
|
|
28
|
+
removePlayerElementClass(player, '.bmpui-npo-player.bmpui-player-state-finished', 'bmpui-player-state-finished');
|
|
25
29
|
};
|
|
26
30
|
const clearUI = () => {
|
|
27
|
-
|
|
28
|
-
if (nicamElement)
|
|
31
|
+
adBreakActive = false;
|
|
32
|
+
if (nicamElement)
|
|
29
33
|
nicamElement.innerHTML = '';
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
element.textContent = '';
|
|
35
|
-
}
|
|
34
|
+
if (seekbarTitleElement)
|
|
35
|
+
seekbarTitleElement.textContent = '';
|
|
36
|
+
removePlayerElementClass(player, '.bmpui-npo-player', 'livestream-no-dvr');
|
|
37
|
+
player.off(PlayerEvent.TimeChanged, handleShowPlayNextScreen);
|
|
36
38
|
removeFragments(mobileUIManager);
|
|
37
39
|
};
|
|
38
40
|
const updateUI = () => {
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const isLiveStreamNoDvr = _streamObject.stream?.isLiveStream && !_streamObject.stream?.hasDvrWindow;
|
|
43
|
-
if (isLiveStreamNoDvr) {
|
|
44
|
-
player.getContainer().querySelector('.bmpui-npo-player')?.classList.add('livestream-no-dvr');
|
|
41
|
+
if (_webData.assets.preroll) {
|
|
42
|
+
adBreakActive = true;
|
|
43
|
+
player.on(PlayerEvent.AdBreakFinished, setAdBreakActiveToFalse);
|
|
45
44
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
addFragments(player, mobileUIManager, section);
|
|
45
|
+
if (_webData.metadata?.ageRating)
|
|
46
|
+
processNicam(_webData, nicamElement, null);
|
|
47
|
+
if (seekbarTitleElement)
|
|
48
|
+
seekbarTitleElement.textContent = _webData.title || '';
|
|
49
|
+
handleLiveStreamNoDvr(player, _webData, containerElement);
|
|
50
|
+
if (_webData.playNext?.showPlayNext) {
|
|
51
|
+
player.on(PlayerEvent.TimeChanged, handleShowPlayNextScreen);
|
|
54
52
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
const section = _webData.segment ? {
|
|
54
|
+
start: _webData.segment.inpoint,
|
|
55
|
+
end: _webData.segment.outpoint,
|
|
56
|
+
title: _webData?.title || '',
|
|
57
|
+
} : null;
|
|
58
|
+
addFragments(player, mobileUIManager, section);
|
|
59
|
+
};
|
|
60
|
+
const setAdBreakActiveToFalse = () => {
|
|
61
|
+
adBreakActive = false;
|
|
62
|
+
player.off(PlayerEvent.AdBreakFinished, setAdBreakActiveToFalse);
|
|
63
|
+
};
|
|
64
|
+
const handleShowPlayNextScreen = () => {
|
|
65
|
+
let contentDuration = player.getDuration();
|
|
66
|
+
let playnextOffset = _webData.playNext.offset;
|
|
67
|
+
const container = player.getContainer();
|
|
68
|
+
if (player.getCurrentTime() >= contentDuration - playnextOffset && !adBreakActive) {
|
|
69
|
+
showPlayNextScreenIfNeeded(_webData.playNext.duration, container, undefined, true);
|
|
70
|
+
player.off(PlayerEvent.TimeChanged, handleShowPlayNextScreen);
|
|
61
71
|
}
|
|
62
72
|
};
|
|
63
73
|
if (window.bitmovin?.customMessageHandler) {
|
|
64
|
-
|
|
74
|
+
const handleData = (data) => {
|
|
65
75
|
clearUI();
|
|
66
76
|
if (data) {
|
|
67
|
-
|
|
77
|
+
_webData = JSON.parse(data);
|
|
68
78
|
updateUI();
|
|
69
79
|
}
|
|
80
|
+
};
|
|
81
|
+
window.bitmovin.customMessageHandler.on(CustomMessages.SET_STREAM_LINK, handleData);
|
|
82
|
+
window.bitmovin.customMessageHandler.on(CustomMessages.SET_WEB_DATA, handleData);
|
|
83
|
+
if (_webData.playNext?.showPlayNext === true) {
|
|
84
|
+
player.on(PlayerEvent.TimeChanged, handleShowPlayNextScreen);
|
|
85
|
+
}
|
|
86
|
+
window.bitmovin.customMessageHandler.on(CustomMessages.DO_ERROR, (data) => {
|
|
87
|
+
if (!data)
|
|
88
|
+
return;
|
|
89
|
+
const messageData = JSON.parse(data);
|
|
90
|
+
if (messageData.errorMessage) {
|
|
91
|
+
doError(messageData.errorMessage);
|
|
92
|
+
}
|
|
70
93
|
});
|
|
71
94
|
}
|
|
72
95
|
const addEventListeners = () => {
|
|
73
96
|
player.off(PlayerEvent.Ready, addEventListeners);
|
|
74
97
|
sendCustomMessage(CustomMessages.JAVASCRIPT_READY);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
};
|
|
99
|
+
const doError = (data) => {
|
|
100
|
+
window.bitmovin.customMessageHandler.off(CustomMessages.DO_ERROR, data.toString());
|
|
101
|
+
const input = data.input;
|
|
102
|
+
if (input) {
|
|
103
|
+
const uiComponents = mobileUIManager.activeUi.getUI().getComponents();
|
|
104
|
+
const errorMessageOverlay = uiComponents.find(component => component.getConfig().cssClass === 'ui-errormessage-overlay');
|
|
105
|
+
if (!errorMessageOverlay)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
errorMessageOverlay.display(input);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
82
113
|
}
|
|
83
114
|
};
|
|
115
|
+
const onPlayerError = (event) => {
|
|
116
|
+
player.off(PlayerEvent.Error, (event) => {
|
|
117
|
+
onPlayerError(event);
|
|
118
|
+
});
|
|
119
|
+
sendCustomMessage(CustomMessages.ERROR_TRIGGERED, { error: event });
|
|
120
|
+
};
|
|
84
121
|
player.on(PlayerEvent.Seeked, removeFinishedClass);
|
|
85
122
|
player.on(PlayerEvent.Ready, addEventListeners);
|
|
123
|
+
player.on(PlayerEvent.Error, (event) => {
|
|
124
|
+
onPlayerError(event);
|
|
125
|
+
});
|
|
86
126
|
return mobileUIManager;
|
|
87
127
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { nativeMobileUiFactory } from './nativemobileuifactory';
|
|
2
|
+
import { CustomMessages } from '../../types/interfaces';
|
|
3
|
+
const title = 'Test Stream';
|
|
4
|
+
let storedCallbacks = {};
|
|
5
|
+
jest.mock('bitmovin-player-ui', () => {
|
|
6
|
+
return jest.requireActual('../../../tests/mocks/mockPlayerUi').mockPlayerUi;
|
|
7
|
+
});
|
|
8
|
+
describe('nativeMobileUiFactory', () => {
|
|
9
|
+
let mockPlayer;
|
|
10
|
+
let containerElement;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
containerElement = document.createElement('div');
|
|
13
|
+
const wrapperElement = document.createElement('div');
|
|
14
|
+
wrapperElement.className = 'bmpui-npo-player';
|
|
15
|
+
containerElement.appendChild(wrapperElement);
|
|
16
|
+
const seekbarTitleElement = document.createElement('div');
|
|
17
|
+
seekbarTitleElement.className = 'bmpui-seekbar-label-title';
|
|
18
|
+
wrapperElement.appendChild(seekbarTitleElement);
|
|
19
|
+
const nicamElement = document.createElement('div');
|
|
20
|
+
nicamElement.className = 'bmpui-nicam';
|
|
21
|
+
wrapperElement.appendChild(nicamElement);
|
|
22
|
+
mockPlayer = {
|
|
23
|
+
on: jest.fn(),
|
|
24
|
+
off: jest.fn(),
|
|
25
|
+
getContainer: () => containerElement,
|
|
26
|
+
getVolume: jest.fn().mockReturnValue(50),
|
|
27
|
+
};
|
|
28
|
+
storedCallbacks = {};
|
|
29
|
+
if (storedCallbacks) {
|
|
30
|
+
window.bitmovin = window.bitmovin || {};
|
|
31
|
+
window.bitmovin.customMessageHandler = {
|
|
32
|
+
on: jest.fn((messageType, callbackFn) => {
|
|
33
|
+
storedCallbacks[messageType] = callbackFn;
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
nativeMobileUiFactory(mockPlayer);
|
|
38
|
+
});
|
|
39
|
+
test('should update the DOM correctly when sendSynchronous is called with setstreamlink', () => {
|
|
40
|
+
const mockData = {
|
|
41
|
+
title: title,
|
|
42
|
+
metadata: {
|
|
43
|
+
ageRating: '12',
|
|
44
|
+
nicam: ['GEWELD', 'ANGST', 'GROF_TAALGEBRUIK'],
|
|
45
|
+
},
|
|
46
|
+
stream: {
|
|
47
|
+
isLiveStream: true,
|
|
48
|
+
hasDvrWindow: false
|
|
49
|
+
},
|
|
50
|
+
assets: {
|
|
51
|
+
preroll: null,
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
if (storedCallbacks[CustomMessages.SET_STREAM_LINK]) {
|
|
55
|
+
storedCallbacks[CustomMessages.SET_STREAM_LINK](JSON.stringify(mockData));
|
|
56
|
+
}
|
|
57
|
+
const container = mockPlayer.getContainer();
|
|
58
|
+
expect(container.querySelector('.bmpui-nicam')?.innerHTML).not.toBe('');
|
|
59
|
+
expect(container.querySelector('.bmpui-seekbar-label-title')?.textContent).toBe(title);
|
|
60
|
+
expect(container.querySelector('.bmpui-npo-player')?.classList.contains('livestream-no-dvr')).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
jest.clearAllMocks();
|
|
64
|
+
document.body.innerHTML = '';
|
|
65
|
+
});
|
|
66
|
+
});
|
package/lib/js/ui/uicontainer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UIContainer, PlaybackToggleOverlay, BufferingOverlay, CastStatusOverlay, SubtitleOverlay, ErrorMessageOverlay, UIManager, PlaybackToggleButton } from 'bitmovin-player-ui';
|
|
1
|
+
import { UIContainer, PlaybackToggleOverlay, BufferingOverlay, CastStatusOverlay, SubtitleOverlay, ErrorMessageOverlay, UIManager, PlaybackToggleButton, PlayerUtils } from 'bitmovin-player-ui';
|
|
2
2
|
import { createMiddleButtons } from './components/buttons';
|
|
3
3
|
import { createPlayNextScreen } from './components/playnext';
|
|
4
4
|
import { createSettingsPanel } from './components/settingspanel';
|
|
@@ -45,7 +45,6 @@ export function createUIContainer(npoplayer, player, variant, container) {
|
|
|
45
45
|
bufferingOverlay,
|
|
46
46
|
playbackToggleOverlay,
|
|
47
47
|
new CastStatusOverlay(),
|
|
48
|
-
playNextScreen,
|
|
49
48
|
middleButtons,
|
|
50
49
|
];
|
|
51
50
|
const baseComponentsAfter = [
|
|
@@ -54,6 +53,7 @@ export function createUIContainer(npoplayer, player, variant, container) {
|
|
|
54
53
|
let conditionalComponents = [];
|
|
55
54
|
if (variant === NpoPlayerUIVariants.DEFAULT) {
|
|
56
55
|
conditionalComponents.push(...baseComponentsBefore);
|
|
56
|
+
conditionalComponents.push(playNextScreen);
|
|
57
57
|
conditionalComponents.push(controlBar);
|
|
58
58
|
conditionalComponents.push(ctaBar);
|
|
59
59
|
conditionalComponents.push(titleBar);
|
|
@@ -74,7 +74,8 @@ export function createUIContainer(npoplayer, player, variant, container) {
|
|
|
74
74
|
components: [...conditionalComponents, ...baseComponentsAfter],
|
|
75
75
|
cssClasses: cssClassArray,
|
|
76
76
|
ariaLabel: localize('player'),
|
|
77
|
-
hideDelay: uiDelay
|
|
77
|
+
hideDelay: uiDelay,
|
|
78
|
+
hidePlayerStateExceptions: [PlayerUtils.PlayerState.Paused],
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
export function removeUIContainer(container) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PlayerAPI } from 'bitmovin-player';
|
|
1
2
|
import { type LocalizableText } from 'bitmovin-player-ui/dist/js/framework/localization/i18n';
|
|
2
3
|
export declare const jwtToBase64: (jwt: string) => string;
|
|
3
4
|
export declare const base64ToObject: (base64: string) => Record<string, any>;
|
|
@@ -11,3 +12,5 @@ export declare function getModuleExport(module: unknown): unknown;
|
|
|
11
12
|
export declare function isUrl(url: string): boolean;
|
|
12
13
|
export declare function isMediaUrl(url: string): Promise<boolean>;
|
|
13
14
|
export declare function replaceSpecialChars(text: string): string;
|
|
15
|
+
export declare function getPlayerElement(player: PlayerAPI, query: string): Element | null;
|
|
16
|
+
export declare function removePlayerElementClass(player: PlayerAPI, query: string, className: string): void;
|
|
@@ -67,3 +67,12 @@ export async function isMediaUrl(url) {
|
|
|
67
67
|
export function replaceSpecialChars(text) {
|
|
68
68
|
return text.replace(/['()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
69
69
|
}
|
|
70
|
+
export function getPlayerElement(player, query) {
|
|
71
|
+
return player.getContainer().querySelector(query);
|
|
72
|
+
}
|
|
73
|
+
export function removePlayerElementClass(player, query, className) {
|
|
74
|
+
const element = getPlayerElement(player, query);
|
|
75
|
+
if (element) {
|
|
76
|
+
element.classList.remove(className);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/lib/lang/nl.json
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"settings.subtitles.font.family.smallcapital": "small capital",
|
|
38
38
|
"settings.time.hours": "Uren",
|
|
39
39
|
"settings.time.minutes": "Minuten",
|
|
40
|
-
"settings.time.seconds": "
|
|
40
|
+
"settings.time.seconds": "Seconden",
|
|
41
41
|
"ads.remainingTime": "De advertentie duurt nog {remainingTime} seconden.",
|
|
42
42
|
"settings": "Instellingen",
|
|
43
43
|
"fullscreen": "Volledig scherm",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
jest.mock('./js/ui/nativemobileuifactory', () => ({
|
|
2
|
+
nativeMobileUiFactory: jest.fn(),
|
|
3
|
+
}));
|
|
4
|
+
const { nativeMobileUiFactory } = require('./js/ui/nativemobileuifactory');
|
|
5
|
+
beforeAll(() => {
|
|
6
|
+
window.bitmovin = {};
|
|
7
|
+
window.bitmovin.playerui = () => { };
|
|
8
|
+
window.bitmovin.playerui.UIFactory = {
|
|
9
|
+
buildDefaultSmallScreenUI: (player, config = {}) => {
|
|
10
|
+
return nativeMobileUiFactory(player, config);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
test('Bridge initation: nativeMobileUiFactory is called with correct arguments for building the default small screen UI', () => {
|
|
18
|
+
const mockPlayer = {};
|
|
19
|
+
const mockConfig = {};
|
|
20
|
+
window.bitmovin.playerui.UIFactory.buildDefaultSmallScreenUI(mockPlayer, mockConfig);
|
|
21
|
+
expect(nativeMobileUiFactory).toHaveBeenCalledWith(mockPlayer, mockConfig);
|
|
22
|
+
});
|
|
23
|
+
export {};
|
package/lib/npoplayer.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type InitialisationProps } from '@npotag/tag/dist/types/src/npoTag';
|
|
|
3
3
|
import { type PlayerAPI, type PlayerConfig, type SourceConfig } from 'bitmovin-player';
|
|
4
4
|
import { UIManager } from 'bitmovin-player-ui';
|
|
5
5
|
import { LogEmitter } from './types/classes';
|
|
6
|
-
import { type DRMProfile, type ApiPayload, type NPOTagObject, type Section, type StreamObject, type StreamOptions, type UIComponents, NpoPlayerUIVariants } from './types/interfaces';
|
|
6
|
+
import { type DRMProfile, type ApiPayload, type NPOTagObject, type Section, type StreamObject, type StreamOptions, type UIComponents, type TimeLineMarker, NpoPlayerUIVariants } from './types/interfaces';
|
|
7
7
|
export { type PlayerConfig, type InitialisationProps, type NPOTagObject, type StreamOptions, NpoPlayerUIVariants, };
|
|
8
8
|
export default class NpoPlayer {
|
|
9
9
|
playerConfig: PlayerConfig;
|
|
@@ -41,6 +41,7 @@ export default class NpoPlayer {
|
|
|
41
41
|
goBackwards(seconds: number): void;
|
|
42
42
|
watchFromStart(): void;
|
|
43
43
|
showPlayNextScreen(): void;
|
|
44
|
+
updateMarkers(timeLineMarkers: TimeLineMarker[]): void;
|
|
44
45
|
cancelPlayNextScreen(): void;
|
|
45
46
|
hidePlayNextScreen(): void;
|
|
46
47
|
doPlayNext(): void;
|
package/lib/npoplayer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Player, PlayerEvent, } from 'bitmovin-player';
|
|
2
2
|
import AdsModuleBM from 'bitmovin-player/modules/bitmovinplayer-advertising-bitmovin';
|
|
3
3
|
import { logEvent, initPlayerTracker, startPlayerTracker } from './js/tracking/playertracker';
|
|
4
|
-
import { hidePlayNextScreen,
|
|
4
|
+
import { hidePlayNextScreen, showPlayNextScreenIfNeeded } from './js/ui/components/shared/playnextscreen';
|
|
5
5
|
import * as utility from './js/utilities/utilities';
|
|
6
6
|
import * as drm from './js/drm/drm';
|
|
7
7
|
import * as playerAction from './js/playeractions/playeractions';
|
|
@@ -20,6 +20,7 @@ import { setupMediaSessionActionHandlers } from './js/playeractions/handlers/med
|
|
|
20
20
|
import { processStream } from './js/ui/handlers/streamhandler';
|
|
21
21
|
import { removeReplayClass } from './js/playeractions/handlers/removereplayclass';
|
|
22
22
|
import { showNicamAfterUiDelay } from './js/ui/handlers/nicamhandler';
|
|
23
|
+
import { updateLiveMarkers } from './js/markers/updateLiveMarkers';
|
|
23
24
|
export { NpoPlayerUIVariants, };
|
|
24
25
|
export default class NpoPlayer {
|
|
25
26
|
constructor(_container, _playerConfig, _npotag, _npotaginstance, _variant) {
|
|
@@ -129,7 +130,7 @@ export default class NpoPlayer {
|
|
|
129
130
|
if (this.streamObject?.stream == undefined)
|
|
130
131
|
return;
|
|
131
132
|
const drmType = this.streamObject.stream.drmType ?? null;
|
|
132
|
-
this.sourceConfig = playerAction.processSourceConfig(options.sourceConfig ?? {}, this.streamObject, drmType && drmType.length > 0 ? profile.drm : null);
|
|
133
|
+
this.sourceConfig = await playerAction.processSourceConfig(options.sourceConfig ?? {}, this.streamObject, drmType && drmType.length > 0 ? profile.drm : null, this.streamOptions.useWidevineServerCertificate ?? true);
|
|
133
134
|
await drm.verifyDRM(this, this.player, payload);
|
|
134
135
|
setupMediaSessionActionHandlers(this.player, this.sourceConfig, _streamObject);
|
|
135
136
|
logEvent(this, 'load');
|
|
@@ -152,6 +153,8 @@ export default class NpoPlayer {
|
|
|
152
153
|
this.player?.off(PlayerEvent.SourceLoaded, triggerAddAccessibilityAttributes);
|
|
153
154
|
this.player?.off(PlayerEvent.AdBreakFinished, triggerAddAccessibilityAttributes);
|
|
154
155
|
this.player?.off(PlayerEvent.AdError, triggerAddAccessibilityAttributes);
|
|
156
|
+
if (this.streamOptions.enableSubtitles === true)
|
|
157
|
+
this.player?.subtitles.enable('sub0');
|
|
155
158
|
void addAccessibilityAttributes(this.container, this.streamObject.metadata);
|
|
156
159
|
};
|
|
157
160
|
this.player.on(PlayerEvent.SourceLoaded, triggerAddAccessibilityAttributes);
|
|
@@ -182,7 +185,7 @@ export default class NpoPlayer {
|
|
|
182
185
|
this.player.off(PlayerEvent.SourceLoaded, setLiveOffsetListener);
|
|
183
186
|
playerAction.handleLiveOffsetLogic(this, this.player, options);
|
|
184
187
|
}.bind(this);
|
|
185
|
-
this.player.on(PlayerEvent.
|
|
188
|
+
this.player.on(PlayerEvent.Ready, setLiveOffsetListener);
|
|
186
189
|
}
|
|
187
190
|
async createUIManager(player, variant) {
|
|
188
191
|
if (this.uiManager === null || variant !== this.variant) {
|
|
@@ -274,13 +277,19 @@ export default class NpoPlayer {
|
|
|
274
277
|
const showPlayNextAt = videoDuration - offset;
|
|
275
278
|
if (!this.isShowingPlayNextScreen && this.player.getCurrentTime() > showPlayNextAt && !this.canceledPlayNextScreen) {
|
|
276
279
|
this.isShowingPlayNextScreen = true;
|
|
277
|
-
|
|
280
|
+
showPlayNextScreenIfNeeded(overlayDuration, this.container, this.streamOptions.playNext?.proceedCallback);
|
|
278
281
|
}
|
|
279
282
|
else if (this.isShowingPlayNextScreen && this.player.getCurrentTime() < showPlayNextAt) {
|
|
280
283
|
this.isShowingPlayNextScreen = false;
|
|
281
284
|
hidePlayNextScreen(this.container);
|
|
282
285
|
}
|
|
283
286
|
}
|
|
287
|
+
updateMarkers(timeLineMarkers) {
|
|
288
|
+
if (!(this.player && this.uiManager && this.streamObject.stream.isLiveStream))
|
|
289
|
+
return;
|
|
290
|
+
updateLiveMarkers(timeLineMarkers, this.player, this.uiManager);
|
|
291
|
+
}
|
|
292
|
+
;
|
|
284
293
|
cancelPlayNextScreen() {
|
|
285
294
|
this.canceledPlayNextScreen = true;
|
|
286
295
|
this.hidePlayNextScreen();
|
package/lib/npoplayer.test.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import NpoPlayer from
|
|
2
|
-
const div = document.createElement(
|
|
1
|
+
import NpoPlayer from './npoplayer';
|
|
2
|
+
const div = document.createElement('div');
|
|
3
3
|
const testPlayerConfig = {
|
|
4
|
-
key:
|
|
4
|
+
key: 'dummy-key',
|
|
5
5
|
};
|
|
6
6
|
jest.mock('./js/ui/handlers/streamhandler', () => ({
|
|
7
7
|
processStream: jest.fn(),
|
|
8
8
|
}));
|
|
9
9
|
let player;
|
|
10
|
-
test(
|
|
10
|
+
test('player init', async () => {
|
|
11
11
|
player = new NpoPlayer(div, testPlayerConfig);
|
|
12
12
|
expect(player).toBeInstanceOf(NpoPlayer);
|
|
13
13
|
expect(player.playerConfig.key).toBe(testPlayerConfig.key);
|
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npo/player",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "NPO Player",
|
|
5
5
|
"author": "Publieke Omroep <player@npo.nl>",
|
|
6
6
|
"contributors": [
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"build:ts": "bash build-lib.sh",
|
|
26
26
|
"test": "jest",
|
|
27
27
|
"test:ci": "jest --collectCoverage --testResultsProcessor=jest-sonar-reporter",
|
|
28
|
-
"version:dev": "npm version prerelease --preid=dev --no-git-tag-version"
|
|
28
|
+
"version:dev": "npm version prerelease --preid=dev --no-git-tag-version",
|
|
29
|
+
"prepublishOnly": "aws codeartifact login --tool npm --namespace @npo-player --repository npo-player --domain npoplayer $([ -z \"$TF_BUILD\" ] && echo \" --profile codeartifact\")"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|
|
31
32
|
"LICENSE",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { PlayerAPI } from "bitmovin-player";
|
|
2
|
+
import { UIManager } from "bitmovin-player-ui";
|
|
3
|
+
import { TimeLineMarker } from "types/interfaces";
|
|
4
|
+
export declare function updateLiveMarkers(timeLineMarkers: TimeLineMarker[], player: PlayerAPI, uiManager: UIManager): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { PlayerAPI } from
|
|
2
|
-
import { UIComponents } from
|
|
1
|
+
import { PlayerAPI } from 'bitmovin-player';
|
|
2
|
+
import { UIComponents } from '../../../types/interfaces';
|
|
3
3
|
export declare function handlePlayerError(player: PlayerAPI, uiComponents: UIComponents, input: any): void;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { StreamObject } from 'types/interfaces';
|
|
2
2
|
import { SourceConfig } from 'bitmovin-player';
|
|
3
|
-
export declare function processSourceConfig(_sourceConfig: SourceConfig | undefined, _streamObject: StreamObject, drm
|
|
3
|
+
export declare function processSourceConfig(_sourceConfig: SourceConfig | undefined, _streamObject: StreamObject, drm: string | null | undefined, useWidevineServerCertificate: boolean): Promise<SourceConfig>;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { AirPlayToggleButton, Button, CastToggleButton, ControlBar, FullscreenToggleButton, PictureInPictureToggleButton, PlaybackToggleButton, SettingsPanel, SettingsToggleButton, VolumeToggleButton } from 'bitmovin-player-ui';
|
|
2
2
|
import { PlayerAPI } from 'bitmovin-player';
|
|
3
3
|
export declare function createMiddleButtons(player: PlayerAPI): ControlBar;
|
|
4
|
-
export declare function createPlayNextButton(): Button<{
|
|
4
|
+
export declare function createPlayNextButton(player: PlayerAPI): Button<{
|
|
5
|
+
cssClass: string;
|
|
6
|
+
text: string;
|
|
7
|
+
ariaLabel: string;
|
|
8
|
+
hidden: false;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function createCancelPlayNextButton(player: PlayerAPI): Button<{
|
|
5
11
|
cssClass: string;
|
|
6
12
|
text: string;
|
|
7
13
|
ariaLabel: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Container } from 'bitmovin-player-ui';
|
|
2
|
-
|
|
2
|
+
import { PlayerAPI } from 'bitmovin-player';
|
|
3
|
+
export declare function createPlayNextScreen(player: PlayerAPI): Container<{
|
|
3
4
|
components: import("bitmovin-player-ui").Button<{
|
|
4
5
|
cssClass: string;
|
|
5
6
|
text: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function
|
|
2
|
-
export declare function hidePlayNextScreen(containerEl: HTMLElement): void;
|
|
1
|
+
export declare function showPlayNextScreenIfNeeded(overlayDuration: number, containerEl: HTMLElement, proceedCallBack?: () => void, nativeUI?: boolean): void;
|
|
2
|
+
export declare function hidePlayNextScreen(containerEl: HTMLElement, nativeUI?: boolean): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|