@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.
Files changed (84) hide show
  1. package/README.md +5 -0
  2. package/lib/js/api/getstreamobject.test.js +2 -2
  3. package/lib/js/markers/updateLiveMarkers.d.ts +4 -0
  4. package/lib/js/markers/updateLiveMarkers.js +21 -0
  5. package/lib/js/markers/updateLiveMarkers.test.d.ts +1 -0
  6. package/lib/js/markers/updateLiveMarkers.test.js +53 -0
  7. package/lib/js/playeractions/handlers/error.d.ts +2 -2
  8. package/lib/js/playeractions/handlers/processsourceconfig.d.ts +1 -1
  9. package/lib/js/playeractions/handlers/processsourceconfig.js +12 -10
  10. package/lib/js/ui/components/adbutton.js +1 -1
  11. package/lib/js/ui/components/buttons.js +2 -2
  12. package/lib/js/ui/components/nativemobile/buttons.d.ts +7 -1
  13. package/lib/js/ui/components/nativemobile/buttons.js +27 -4
  14. package/lib/js/ui/components/nativemobile/controlbar.js +1 -2
  15. package/lib/js/ui/components/nativemobile/playnext.d.ts +2 -1
  16. package/lib/js/ui/components/nativemobile/playnext.js +5 -4
  17. package/lib/js/ui/components/settingspanel.js +30 -23
  18. package/lib/js/ui/components/shared/playnextscreen.d.ts +2 -2
  19. package/lib/js/ui/components/shared/playnextscreen.js +14 -2
  20. package/lib/js/ui/components/shared/playnextstreen.test.d.ts +6 -0
  21. package/lib/js/ui/components/shared/playnextstreen.test.js +56 -0
  22. package/lib/js/ui/handlers/accessibilityhandler.js +26 -2
  23. package/lib/js/ui/handlers/accessibilityhandler.test.d.ts +1 -0
  24. package/lib/js/ui/handlers/accessibilityhandler.test.js +63 -0
  25. package/lib/js/ui/handlers/domhandlers.d.ts +3 -0
  26. package/lib/js/ui/handlers/domhandlers.js +7 -0
  27. package/lib/js/ui/handlers/domhandlers.test.d.ts +1 -0
  28. package/lib/js/ui/handlers/domhandlers.test.js +49 -0
  29. package/lib/js/ui/handlers/nicamhandler.js +1 -1
  30. package/lib/js/ui/handlers/streamhandler.js +4 -4
  31. package/lib/js/ui/nativemobileuicontainer.js +5 -5
  32. package/lib/js/ui/nativemobileuifactory.js +82 -42
  33. package/lib/js/ui/nativemobileuifactory.test.d.ts +1 -0
  34. package/lib/js/ui/nativemobileuifactory.test.js +66 -0
  35. package/lib/js/ui/uicontainer.js +4 -3
  36. package/lib/js/utilities/utilities.d.ts +3 -0
  37. package/lib/js/utilities/utilities.js +9 -0
  38. package/lib/lang/nl.json +1 -1
  39. package/lib/npoplayer-bridge.test.d.ts +1 -0
  40. package/lib/npoplayer-bridge.test.js +23 -0
  41. package/lib/npoplayer.d.ts +2 -1
  42. package/lib/npoplayer.js +13 -4
  43. package/lib/npoplayer.test.js +4 -4
  44. package/lib/package.json +3 -2
  45. package/lib/src/js/markers/updateLiveMarkers.d.ts +4 -0
  46. package/lib/src/js/markers/updateLiveMarkers.test.d.ts +1 -0
  47. package/lib/src/js/playeractions/handlers/error.d.ts +2 -2
  48. package/lib/src/js/playeractions/handlers/processsourceconfig.d.ts +1 -1
  49. package/lib/src/js/ui/components/nativemobile/buttons.d.ts +7 -1
  50. package/lib/src/js/ui/components/nativemobile/playnext.d.ts +2 -1
  51. package/lib/src/js/ui/components/shared/playnextscreen.d.ts +2 -2
  52. package/lib/src/js/ui/components/shared/playnextstreen.test.d.ts +6 -0
  53. package/lib/src/js/ui/handlers/accessibilityhandler.test.d.ts +1 -0
  54. package/lib/src/js/ui/handlers/domhandlers.d.ts +3 -0
  55. package/lib/src/js/ui/handlers/domhandlers.test.d.ts +1 -0
  56. package/lib/src/js/ui/nativemobileuifactory.test.d.ts +1 -0
  57. package/lib/src/js/utilities/utilities.d.ts +3 -0
  58. package/lib/src/npoplayer-bridge.test.d.ts +1 -0
  59. package/lib/src/npoplayer.d.ts +2 -1
  60. package/lib/src/types/interfaces.d.ts +26 -2
  61. package/lib/tests/mocks/mockNpoplayer.js +4 -1
  62. package/lib/types/interfaces.d.ts +26 -2
  63. package/lib/types/interfaces.js +12 -2
  64. package/package.json +3 -2
  65. package/src/scss/components/_advert.scss +34 -68
  66. package/src/scss/components/_hugeplaybacktogglebutton.scss +11 -0
  67. package/src/scss/components/_icons.scss +50 -40
  68. package/src/scss/components/_nicam.scss +9 -2
  69. package/src/scss/components/_playnext.scss +5 -1
  70. package/src/scss/components/_replay.scss +1 -1
  71. package/src/scss/components/_seekbar.scss +13 -0
  72. package/src/scss/components/_settingspanel.scss +37 -12
  73. package/src/scss/components/_textbuttons.scss +12 -11
  74. package/src/scss/components/_volumeslider.scss +5 -0
  75. package/src/scss/components/audio/_playbutton.scss +6 -0
  76. package/src/scss/components/audio/_volumeslider.scss +6 -1
  77. package/src/scss/npoplayer.css +80 -56
  78. package/src/scss/npoplayer.scss +3 -0
  79. package/src/scss/variants/_player-base.scss +1 -9
  80. package/src/scss/variants/_player-large.scss +20 -0
  81. package/src/scss/variants/_player-medium.scss +31 -1
  82. package/src/scss/variants/_player-native-mobile.scss +4 -0
  83. package/src/scss/variants/_player-small.scss +11 -8
  84. 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 element = container.querySelector('.bmpui-seekbar-label-title');
20
- if (element !== null) {
21
- element.textContent = sourceConfig.title || '';
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 && !streamObject.stream.hasDvrWindow;
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({ id: 'native-mobile-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 _streamObject = {};
25
+ let _webData = {};
19
26
  const removeFinishedClass = () => {
20
27
  player.off(PlayerEvent.Seeked, removeFinishedClass);
21
- const playerFinishedElement = player.getContainer().querySelector('.bmpui-npo-player.bmpui-player-state-finished');
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
- const nicamElement = player.getContainer().querySelector('.bmpui-nicam');
28
- if (nicamElement) {
31
+ adBreakActive = false;
32
+ if (nicamElement)
29
33
  nicamElement.innerHTML = '';
30
- }
31
- player.getContainer().querySelector('.bmpui-npo-player')?.classList.remove('livestream-no-dvr');
32
- const element = player.getContainer().querySelector('.bmpui-seekbar-label-title');
33
- if (element) {
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 (_streamObject.metadata?.ageRating) {
40
- processNicam(_streamObject, player.getContainer().querySelector('.bmpui-nicam'), null);
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 (_streamObject.segment) {
47
- const seg = _streamObject.segment;
48
- const section = {
49
- start: seg.inpoint,
50
- end: seg.outpoint,
51
- title: _streamObject?.title || '',
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
- else {
56
- addFragments(player, mobileUIManager, null);
57
- }
58
- const element = player.getContainer().querySelector('.bmpui-seekbar-label-title');
59
- if (element) {
60
- element.textContent = _streamObject.title || '';
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
- window.bitmovin.customMessageHandler.on(CustomMessages.SET_STREAM_LINK, (data) => {
74
+ const handleData = (data) => {
65
75
  clearUI();
66
76
  if (data) {
67
- _streamObject = JSON.parse(data);
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
- const playbackToggleElement = player.getContainer().querySelector('#native-mobile-playbacktoggleoverlay');
76
- if (playbackToggleElement) {
77
- playbackToggleElement.addEventListener('touchend', (event) => {
78
- event.preventDefault();
79
- player?.play();
80
- sendCustomMessage(CustomMessages.TOGGLE_PLAY_PAUSE, { isChecked: true });
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
+ });
@@ -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": "Secondes",
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 {};
@@ -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, showPlayNextScreen } from './js/ui/components/shared/playnextscreen';
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.SourceLoaded, setLiveOffsetListener);
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
- showPlayNextScreen(overlayDuration, this.container, this.streamOptions.playNext?.proceedCallback);
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();
@@ -1,13 +1,13 @@
1
- import NpoPlayer from "./npoplayer";
2
- const div = document.createElement("div");
1
+ import NpoPlayer from './npoplayer';
2
+ const div = document.createElement('div');
3
3
  const testPlayerConfig = {
4
- key: "D634EA2C-BFE7-4BB6-9EAA-CDCC7B1EDFFA",
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("player init", async () => {
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.20.3",
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 "bitmovin-player";
2
- import { UIComponents } from "../../../types/interfaces";
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?: string | null): SourceConfig;
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
- export declare function createPlayNextScreen(): Container<{
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 showPlayNextScreen(overlayDuration: number, containerEl: HTMLElement, proceedCallBack?: () => void): void;
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,6 @@
1
+ declare global {
2
+ interface Window {
3
+ bitmovin: any;
4
+ }
5
+ }
6
+ export {};