@npo/player 1.19.0 → 1.20.1

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 (45) hide show
  1. package/README.md +1 -1
  2. package/lib/js/drm/handlers/decideprofile.js +17 -17
  3. package/lib/js/drm/handlers/decideprofile.test.d.ts +1 -0
  4. package/lib/js/drm/handlers/decideprofile.test.js +33 -0
  5. package/lib/js/fragments/removefragments.test.d.ts +1 -0
  6. package/lib/js/fragments/removefragments.test.js +26 -0
  7. package/lib/js/fragments/setfragments.test.d.ts +1 -0
  8. package/lib/js/fragments/setfragments.test.js +72 -0
  9. package/lib/js/playeractions/handlers/processsourceconfig.js +13 -7
  10. package/lib/js/playeractions/handlers/removereplayclass.test.d.ts +1 -0
  11. package/lib/js/playeractions/handlers/removereplayclass.test.js +28 -0
  12. package/lib/js/playeractions/handlers/resolvekeypress.test.d.ts +1 -0
  13. package/lib/js/playeractions/handlers/resolvekeypress.test.js +80 -0
  14. package/lib/js/tracking/handlers/eventlogging.test.d.ts +1 -0
  15. package/lib/js/tracking/handlers/eventlogging.test.js +46 -0
  16. package/lib/js/ui/handlers/listboxhandlers.js +0 -1
  17. package/lib/js/ui/handlers/nicamhandler.d.ts +3 -0
  18. package/lib/js/ui/handlers/nicamhandler.js +16 -0
  19. package/lib/js/ui/nativemobileuicontainer.js +2 -2
  20. package/lib/js/ui/nativemobileuifactory.js +12 -12
  21. package/lib/npoplayer.d.ts +4 -2
  22. package/lib/npoplayer.js +36 -11
  23. package/lib/package.json +5 -5
  24. package/lib/src/js/drm/handlers/decideprofile.test.d.ts +1 -0
  25. package/lib/src/js/fragments/removefragments.test.d.ts +1 -0
  26. package/lib/src/js/fragments/setfragments.test.d.ts +1 -0
  27. package/lib/src/js/playeractions/handlers/removereplayclass.test.d.ts +1 -0
  28. package/lib/src/js/playeractions/handlers/resolvekeypress.test.d.ts +1 -0
  29. package/lib/src/js/tracking/handlers/eventlogging.test.d.ts +1 -0
  30. package/lib/src/js/ui/handlers/nicamhandler.d.ts +3 -0
  31. package/lib/src/npoplayer.d.ts +4 -2
  32. package/lib/src/types/interfaces.d.ts +9 -0
  33. package/lib/tests/mocks/mockNpoplayer.d.ts +2 -0
  34. package/lib/tests/mocks/mockNpoplayer.js +115 -0
  35. package/lib/types/interfaces.d.ts +9 -0
  36. package/package.json +5 -5
  37. package/src/scss/components/_advert.scss +5 -5
  38. package/src/scss/components/_hugeplaybacktogglebutton.scss +1 -0
  39. package/src/scss/components/_metadata.scss +17 -0
  40. package/src/scss/components/_nicam.scss +20 -0
  41. package/src/scss/components/_replay.scss +0 -1
  42. package/src/scss/components/_seekbarthumbnail.scss +0 -1
  43. package/src/scss/components/_subtitles.scss +1 -1
  44. package/src/scss/npoplayer.css +35 -30
  45. package/src/scss/variants/_player-base.scss +6 -1
package/README.md CHANGED
@@ -11,7 +11,7 @@ Extensive and up-to-date documentation is available at https://docs.npoplayer.nl
11
11
  Code quality is analysed by SonarCloud. The project can be found at https://sonarcloud.io/project/overview?id=NPOstart_npo-player
12
12
 
13
13
  # Changelog
14
- Current version: v1.19.0
14
+ Current version: v1.20.0
15
15
 
16
16
  The changelog is available at https://docs.npoplayer.nl/implementation/web/changelog/
17
17
 
@@ -12,39 +12,39 @@ export async function decideProfile(player, preferredDRM = '') {
12
12
  supportedTech = player.getSupportedTech();
13
13
  supportedDRM = await player.getSupportedDRM();
14
14
  }
15
- supportedTech.forEach(function (tech) {
15
+ for (const tech of supportedTech) {
16
16
  if (bestWithoutDRM === '')
17
17
  bestWithoutDRM = tech.streaming;
18
18
  if (bestWithDRM !== '')
19
- return;
20
- if (tech.streaming === 'dash') {
21
- supportedDRM.forEach(function (drm) {
19
+ continue;
20
+ if (tech.streaming === 'dash' && preferredDRM !== 'fairplay') {
21
+ for (const drm of supportedDRM) {
22
22
  if (bestDRM !== '' && (preferredDRM === '' || preferredDRM.length > 0)) {
23
- return;
23
+ continue;
24
24
  }
25
- if (['com.widevine.alpha'].includes(drm)) {
25
+ if (['com.widevine.alpha', 'com.microsoft.playready'].includes(drm) && preferredDRM === '') {
26
26
  bestDRM = 'widevine';
27
27
  }
28
- if (['com.microsoft.playready', 'com.microsoft.playready.recommendation'].includes(drm)) {
29
- bestDRM = 'playready';
28
+ if (['com.apple.fps.1_0', 'com.apple.fps.2_0'].includes(drm) && preferredDRM === 'fairplay') {
29
+ bestDRM = 'fairplay';
30
30
  }
31
- });
31
+ }
32
32
  if (bestDRM !== '')
33
33
  bestWithDRM = 'dash';
34
34
  }
35
- if (tech.streaming === 'hls') {
36
- if (bestDRM !== '' && (preferredDRM === '' || bestDRM === preferredDRM)) {
37
- return;
38
- }
39
- supportedDRM.forEach(function (drm) {
40
- if (['com.apple.fps.1_0', 'com.apple.fps.2_0'].includes(drm)) {
35
+ if (tech.streaming === 'hls' || preferredDRM === 'fairplay') {
36
+ for (const drm of supportedDRM) {
37
+ if (bestDRM !== '' && (preferredDRM === '' || bestDRM === preferredDRM)) {
38
+ continue;
39
+ }
40
+ if (['com.apple.fps.1_0', 'com.apple.fps.2_0'].includes(drm) && preferredDRM === 'fairplay') {
41
41
  bestDRM = 'fairplay';
42
42
  }
43
- });
43
+ }
44
44
  if (bestDRM !== '')
45
45
  bestWithDRM = 'hls';
46
46
  }
47
- });
47
+ }
48
48
  if (bestWithDRM !== '')
49
49
  return { profileName: bestWithDRM, drm: bestDRM };
50
50
  return { profileName: bestWithoutDRM, drm: bestDRM };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import { decideProfile } from './decideprofile';
2
+ jest.mock("bitmovin-player");
3
+ describe("decideProfile", () => {
4
+ let mockPlayerAPI;
5
+ beforeEach(() => {
6
+ mockPlayerAPI = {
7
+ getSupportedTech: jest.fn().mockReturnValue([
8
+ { streaming: 'dash' },
9
+ { streaming: 'hls' },
10
+ ]),
11
+ getSupportedDRM: jest.fn().mockResolvedValue([
12
+ 'com.widevine.alpha',
13
+ 'com.microsoft.playready',
14
+ 'com.apple.fps.1_0',
15
+ ]),
16
+ };
17
+ });
18
+ afterEach(() => {
19
+ jest.clearAllMocks();
20
+ });
21
+ it("should return empty profile when player is null", async () => {
22
+ const result = await decideProfile(null);
23
+ expect(result).toEqual({ profileName: '', drm: '' });
24
+ });
25
+ it("should return profile with dash streaming and widevine DRM", async () => {
26
+ const result = await decideProfile(mockPlayerAPI);
27
+ expect(result).toEqual({ profileName: 'dash', drm: 'widevine' });
28
+ });
29
+ it("should return profile with hls streaming and fairplay DRM", async () => {
30
+ const result = await decideProfile(mockPlayerAPI, 'fairplay');
31
+ expect(result).toEqual({ profileName: 'hls', drm: 'fairplay' });
32
+ });
33
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { removeFragments } from './removefragments';
2
+ describe("removeFragments", () => {
3
+ it("should remove all timeline markers", () => {
4
+ const mockUIManager = {
5
+ getTimelineMarkers: jest.fn().mockReturnValue([
6
+ { id: 1, time: 10 },
7
+ { id: 2, time: 20 },
8
+ { id: 3, time: 30 },
9
+ ]),
10
+ removeTimelineMarker: jest.fn(),
11
+ };
12
+ removeFragments(mockUIManager);
13
+ expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledTimes(3);
14
+ expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledWith({ id: 1, time: 10 });
15
+ expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledWith({ id: 2, time: 20 });
16
+ expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledWith({ id: 3, time: 30 });
17
+ });
18
+ it("should not remove any timeline markers if there are none", () => {
19
+ const mockUIManager = {
20
+ getTimelineMarkers: jest.fn().mockReturnValue([]),
21
+ removeTimelineMarker: jest.fn(),
22
+ };
23
+ removeFragments(mockUIManager);
24
+ expect(mockUIManager.removeTimelineMarker).not.toHaveBeenCalled();
25
+ });
26
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { setFragments } from './setfragments';
2
+ import { PlayerEvent } from 'bitmovin-player';
3
+ const SEEKBAR_MARKER_UNPLAYABLE = 'seekbar-marker-unplayable';
4
+ describe("setFragments", () => {
5
+ let mockPlayer;
6
+ let mockUIManager;
7
+ let fragments;
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ mockPlayer = {
11
+ seek: jest.fn(),
12
+ isPaused: jest.fn().mockReturnValue(true),
13
+ play: jest.fn(),
14
+ pause: jest.fn(),
15
+ off: jest.fn(),
16
+ on: jest.fn().mockImplementation((event, handler) => {
17
+ if (event === PlayerEvent.Ready) {
18
+ handler();
19
+ }
20
+ }),
21
+ getContainer: jest.fn().mockReturnValue({
22
+ querySelector: jest.fn().mockReturnValue({
23
+ textContent: '',
24
+ classList: {
25
+ add: jest.fn()
26
+ }
27
+ })
28
+ }),
29
+ getConfig: jest.fn().mockReturnValue({
30
+ playback: {
31
+ autoplay: true
32
+ }
33
+ })
34
+ };
35
+ mockUIManager = {
36
+ addTimelineMarker: jest.fn(),
37
+ getTimelineMarkers: jest.fn().mockReturnValue([]),
38
+ removeTimelineMarker: jest.fn()
39
+ };
40
+ fragments = {
41
+ sections: [
42
+ {
43
+ start: 10,
44
+ end: 20,
45
+ title: 'Test Fragment',
46
+ cssClasses: [SEEKBAR_MARKER_UNPLAYABLE]
47
+ }
48
+ ]
49
+ };
50
+ });
51
+ it("should set fragments correctly and handle autoplay", async () => {
52
+ setFragments(mockPlayer, mockUIManager, fragments);
53
+ expect(mockPlayer.seek).toHaveBeenCalledWith(fragments.sections[0].start);
54
+ expect(mockPlayer.play).toHaveBeenCalled();
55
+ expect(mockUIManager.addTimelineMarker).toHaveBeenCalledTimes(2);
56
+ });
57
+ it("should correctly set the title of the fragment", async () => {
58
+ setFragments(mockPlayer, mockUIManager, fragments);
59
+ expect(mockUIManager.addTimelineMarker).toHaveBeenCalledWith({
60
+ time: 0,
61
+ title: "Niet beschikbaar",
62
+ cssClasses: [SEEKBAR_MARKER_UNPLAYABLE],
63
+ duration: 10
64
+ });
65
+ expect(mockUIManager.addTimelineMarker).toHaveBeenCalledWith({
66
+ time: 20,
67
+ duration: 10000000,
68
+ title: "Niet beschikbaar",
69
+ cssClasses: [SEEKBAR_MARKER_UNPLAYABLE],
70
+ });
71
+ });
72
+ });
@@ -36,13 +36,19 @@ function setDrm(sourceConfig, drm, _streamObject) {
36
36
  };
37
37
  break;
38
38
  case 'widevine':
39
- sourceConfig.drm = {
40
- widevine: {
41
- LA_URL: npoDrmGateway + _streamObject.stream.drmToken,
42
- audioRobustness: 'SW_SECURE_CRYPTO',
43
- videoRobustness: 'SW_SECURE_CRYPTO'
44
- }
45
- };
39
+ fetch('https://npo-drm-gateway.samgcloud.nepworldwide.nl/widevine_player_cert.bin')
40
+ .then(response => response.arrayBuffer())
41
+ .then(certificate => {
42
+ sourceConfig.drm = {
43
+ widevine: {
44
+ serverCertificate: certificate,
45
+ LA_URL: npoDrmGateway + _streamObject.stream.drmToken,
46
+ audioRobustness: 'SW_SECURE_CRYPTO',
47
+ videoRobustness: 'SW_SECURE_CRYPTO'
48
+ }
49
+ };
50
+ })
51
+ .catch(error => console.error('Error:', error));
46
52
  break;
47
53
  case 'playready':
48
54
  sourceConfig.drm = {
@@ -0,0 +1,28 @@
1
+ import { removeReplayClass } from './removereplayclass';
2
+ import { PlayerEvent } from 'bitmovin-player';
3
+ describe('removeReplayClass', () => {
4
+ let mockPlayer;
5
+ let mockPlayerContainer;
6
+ beforeEach(() => {
7
+ jest.clearAllMocks();
8
+ mockPlayerContainer = document.createElement('div');
9
+ mockPlayerContainer.classList.add('bmpui-player-state-finished', 'bmpui-player-state-replay');
10
+ mockPlayer = {
11
+ off: jest.fn(),
12
+ getContainer: jest.fn().mockReturnValue(mockPlayerContainer),
13
+ };
14
+ });
15
+ it('should remove "bmpui-player-state-finished" and "bmpui-player-state-replay" classes from the player container', () => {
16
+ removeReplayClass(mockPlayer);
17
+ expect(mockPlayerContainer.classList.contains('bmpui-player-state-finished')).toBe(false);
18
+ expect(mockPlayerContainer.classList.contains('bmpui-player-state-replay')).toBe(false);
19
+ });
20
+ it('should unregister the Seek event listener', () => {
21
+ removeReplayClass(mockPlayer);
22
+ expect(mockPlayer.off).toHaveBeenCalledWith(PlayerEvent.Seek, expect.any(Function));
23
+ });
24
+ it('should not unregister the Seek event listener if the player is null', () => {
25
+ removeReplayClass(null);
26
+ expect(mockPlayer.off).not.toHaveBeenCalled();
27
+ });
28
+ });
@@ -0,0 +1,80 @@
1
+ import { resolveKeyPress } from './resolvekeypress';
2
+ import { ViewMode } from 'bitmovin-player';
3
+ import { mockNpoPlayer } from '../../../../tests/mocks/mockNpoplayer';
4
+ describe('resolveKeyPress', () => {
5
+ let mockPlayer;
6
+ let mockNpoplayer = mockNpoPlayer;
7
+ let e;
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ mockPlayer = {
11
+ seek: jest.fn(),
12
+ isPaused: jest.fn().mockReturnValue(true),
13
+ play: jest.fn(),
14
+ pause: jest.fn(),
15
+ isMuted: jest.fn(),
16
+ unmute: jest.fn(),
17
+ getViewMode: jest.fn(),
18
+ setViewMode: jest.fn(),
19
+ getContainer: jest.fn().mockReturnValue({
20
+ querySelector: jest.fn().mockReturnValue({
21
+ textContent: '',
22
+ classList: {
23
+ add: jest.fn()
24
+ }
25
+ })
26
+ }),
27
+ getConfig: jest.fn().mockReturnValue({
28
+ playback: {
29
+ autoplay: true
30
+ }
31
+ })
32
+ };
33
+ e = new KeyboardEvent('keydown');
34
+ });
35
+ it('should mute/unmute the player when "KeyM" is pressed', () => {
36
+ mockPlayer.isMuted.mockReturnValue(true);
37
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'KeyM' });
38
+ expect(mockPlayer.unmute).toHaveBeenCalled();
39
+ });
40
+ it('should enter/exit fullscreen mode when "KeyF" is pressed', () => {
41
+ mockPlayer.getViewMode.mockReturnValue(ViewMode.Inline);
42
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'KeyF' });
43
+ expect(mockPlayer.setViewMode).toHaveBeenCalledWith(ViewMode.Fullscreen);
44
+ });
45
+ it('should play/pause the player when "Space" is pressed', () => {
46
+ mockPlayer.isPaused.mockReturnValue(true);
47
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'Space' });
48
+ expect(mockPlayer.play).toHaveBeenCalled();
49
+ });
50
+ it('should increase the volume when "ArrowUp" is pressed', () => {
51
+ mockPlayer.isMuted.mockReturnValue(true);
52
+ mockNpoplayer.increaseVolume = jest.fn();
53
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowUp' });
54
+ expect(mockPlayer.unmute).toHaveBeenCalled();
55
+ expect(mockNpoplayer.increaseVolume).toHaveBeenCalled();
56
+ });
57
+ it('should decrease the volume when "ArrowDown" is pressed', () => {
58
+ mockPlayer.isMuted.mockReturnValue(true);
59
+ mockNpoplayer.decreaseVolume = jest.fn();
60
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowDown' });
61
+ expect(mockPlayer.unmute).toHaveBeenCalled();
62
+ expect(mockNpoplayer.decreaseVolume).toHaveBeenCalled();
63
+ });
64
+ it('should go backwards when "ArrowLeft" is pressed', () => {
65
+ mockNpoplayer.goBackwards = jest.fn();
66
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowLeft' });
67
+ expect(mockNpoplayer.goBackwards).toHaveBeenCalledWith(10);
68
+ });
69
+ it('should go forward when "ArrowRight" is pressed', () => {
70
+ mockNpoplayer.goForward = jest.fn();
71
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowRight' });
72
+ expect(mockNpoplayer.goForward).toHaveBeenCalledWith(10);
73
+ });
74
+ it('should hide the settings panel when "Escape" is pressed', () => {
75
+ if (mockNpoplayer.uiComponents.settingsPanel) {
76
+ resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'Escape' });
77
+ expect(mockNpoplayer.uiComponents.settingsPanel.hide).toHaveBeenCalled();
78
+ }
79
+ });
80
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { logEvent } from './eventlogging';
2
+ import { mockNpoPlayer } from '../../../../tests/mocks/mockNpoplayer';
3
+ describe('logEvent', () => {
4
+ let npoplayer;
5
+ beforeEach(() => {
6
+ npoplayer = mockNpoPlayer;
7
+ });
8
+ it('should not log event if player is not available', () => {
9
+ npoplayer.player = null;
10
+ if (npoplayer.streamTracker && npoplayer.player) {
11
+ logEvent(npoplayer, 'start');
12
+ expect(npoplayer.streamTracker.start).not.toHaveBeenCalled();
13
+ expect(npoplayer.logEmitter.emit).not.toHaveBeenCalled();
14
+ }
15
+ });
16
+ it('should not log event if streamTracker is not available', () => {
17
+ npoplayer.streamTracker = null;
18
+ if (npoplayer.streamTracker) {
19
+ logEvent(npoplayer, 'start');
20
+ expect(npoplayer.logEmitter.emit).not.toHaveBeenCalled();
21
+ }
22
+ });
23
+ it('should not log event if adBreakActive is true', () => {
24
+ npoplayer.adBreakActive = true;
25
+ if (npoplayer.streamTracker) {
26
+ logEvent(npoplayer, 'start');
27
+ expect(npoplayer.streamTracker.start).not.toHaveBeenCalled();
28
+ expect(npoplayer.logEmitter.emit).not.toHaveBeenCalled();
29
+ }
30
+ });
31
+ it('should log event and call the corresponding streamTracker handler', () => {
32
+ if (npoplayer.streamTracker) {
33
+ logEvent(npoplayer, 'start');
34
+ expect(npoplayer.streamTracker.start).toHaveBeenCalledWith({ stream_position: 10 });
35
+ expect(npoplayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'start');
36
+ }
37
+ });
38
+ it('should log event and call the seek streamTracker handler with data', () => {
39
+ const data = { position: 20 };
40
+ if (npoplayer.streamTracker) {
41
+ logEvent(npoplayer, 'seek', data);
42
+ expect(npoplayer.streamTracker.seek).toHaveBeenCalledWith(data);
43
+ expect(npoplayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'seek');
44
+ }
45
+ });
46
+ });
@@ -5,7 +5,6 @@ function closeSettingsPanel(settingsPanel, mainSettingsPage) {
5
5
  }, 100);
6
6
  }
7
7
  export function handleSubtitleListBoxItemSelected(subtitleSettingsOpenButton, settingsPanel, mainSettingsPage) {
8
- console.log('handleSubtitleListBoxItemSelected');
9
8
  return (e) => {
10
9
  e.getItems().forEach((item) => {
11
10
  if (item.key === e.getSelectedItem()) {
@@ -1,3 +1,6 @@
1
+ import { PlayerAPI } from 'bitmovin-player';
2
+ import { UIManager } from 'bitmovin-player-ui';
1
3
  import { StreamObject, StreamOptions } from 'types/interfaces';
2
4
  export declare function processNicam(streamObject: StreamObject, nicamElement: HTMLElement | null, streamOptions: StreamOptions | null): void;
3
5
  export declare function addNicamIcon(character: string, nicamElement: Element): void;
6
+ export declare function showNicamAfterUiDelay(player: PlayerAPI, uiManager: UIManager | null): void;
@@ -1,3 +1,4 @@
1
+ import { PlayerEvent } from 'bitmovin-player';
1
2
  export function processNicam(streamObject, nicamElement, streamOptions) {
2
3
  const effectiveMetadata = streamOptions ? { ...streamObject.metadata, ...streamOptions } : streamObject.metadata;
3
4
  if (nicamElement) {
@@ -24,3 +25,18 @@ export function addNicamIcon(character, nicamElement) {
24
25
  span.classList.add('nicam-icon', iconClass);
25
26
  nicamElement.appendChild(span);
26
27
  }
28
+ export function showNicamAfterUiDelay(player, uiManager) {
29
+ player.off(PlayerEvent.Play, () => { showNicamAfterUiDelay(player, uiManager); });
30
+ if (uiManager === null)
31
+ return;
32
+ const playerContainer = player.getContainer().querySelector('.bmpui-npo-player.bmpui-layout-max-width-400') || player.getContainer().querySelector('.bmpui-npo-player.bmpui-layout-max-width-600');
33
+ if (!playerContainer)
34
+ return;
35
+ uiManager.activeUi.onControlsHide.subscribeOnce(() => {
36
+ const className = 'show-nicam';
37
+ playerContainer.classList.add(className);
38
+ setTimeout(() => {
39
+ playerContainer.classList.remove(className);
40
+ }, 3000);
41
+ });
42
+ }
@@ -10,6 +10,7 @@ const defaultData = {
10
10
  defaultActionRequired: true,
11
11
  };
12
12
  export function sendCustomMessage(message, data) {
13
+ console.log('sendCustomMessage', message, data);
13
14
  try {
14
15
  if (data !== undefined) {
15
16
  return JSON.parse(window.bitmovin.customMessageHandler.sendSynchronous(message, JSON.stringify(data)));
@@ -18,8 +19,7 @@ export function sendCustomMessage(message, data) {
18
19
  return JSON.parse(window.bitmovin.customMessageHandler.sendSynchronous(message));
19
20
  }
20
21
  }
21
- catch (e) {
22
- console.warn(`Custom message ${message} may not be send, as it returned no valid data (JSON as a string)`, e);
22
+ catch {
23
23
  return defaultData;
24
24
  }
25
25
  }
@@ -18,18 +18,18 @@ export function nativeMobileUiFactory(player, config = {}) {
18
18
  let _streamObject = {};
19
19
  const removeFinishedClass = () => {
20
20
  player.off(PlayerEvent.Seeked, removeFinishedClass);
21
- const playerFinishedElement = document.querySelector('.bmpui-npo-player.bmpui-player-state-finished');
21
+ const playerFinishedElement = player.getContainer().querySelector('.bmpui-npo-player.bmpui-player-state-finished');
22
22
  if (playerFinishedElement) {
23
23
  playerFinishedElement.classList.remove('bmpui-player-state-finished');
24
24
  }
25
25
  };
26
26
  const clearUI = () => {
27
- const nicamElement = document.querySelector('#ui-container .bmpui-nicam');
27
+ const nicamElement = player.getContainer().querySelector('.bmpui-nicam');
28
28
  if (nicamElement) {
29
29
  nicamElement.innerHTML = '';
30
30
  }
31
- document.querySelector('.bmpui-npo-player')?.classList.remove('livestream-dvr');
32
- const element = document.querySelector('.bmpui-npo-player .bmpui-seekbar-label-title');
31
+ player.getContainer().querySelector('.bmpui-npo-player')?.classList.remove('livestream-dvr');
32
+ const element = player.getContainer().querySelector('.bmpui-seekbar-label-title');
33
33
  if (element) {
34
34
  element.textContent = '';
35
35
  }
@@ -37,11 +37,11 @@ export function nativeMobileUiFactory(player, config = {}) {
37
37
  };
38
38
  const updateUI = () => {
39
39
  if (_streamObject.metadata?.ageRating) {
40
- processNicam(_streamObject, document.querySelector('#ui-container .bmpui-nicam'), null);
40
+ processNicam(_streamObject, player.getContainer().querySelector('.bmpui-nicam'), null);
41
41
  }
42
42
  const isLiveStream = _streamObject.stream?.isLiveStream && _streamObject.stream?.hasDvrWindow;
43
43
  if (isLiveStream) {
44
- document.querySelector('.bmpui-npo-player')?.classList.add('livestream-dvr');
44
+ player.getContainer().querySelector('.bmpui-npo-player')?.classList.add('livestream-dvr');
45
45
  }
46
46
  if (_streamObject.segment) {
47
47
  const seg = _streamObject.segment;
@@ -55,7 +55,7 @@ export function nativeMobileUiFactory(player, config = {}) {
55
55
  else {
56
56
  addFragments(player, mobileUIManager, null);
57
57
  }
58
- const element = document.querySelector('.bmpui-npo-player .bmpui-seekbar-label-title');
58
+ const element = player.getContainer().querySelector('.bmpui-seekbar-label-title');
59
59
  if (element) {
60
60
  element.textContent = _streamObject.title || '';
61
61
  }
@@ -72,12 +72,12 @@ export function nativeMobileUiFactory(player, config = {}) {
72
72
  const addEventListeners = () => {
73
73
  player.off(PlayerEvent.Ready, addEventListeners);
74
74
  sendCustomMessage(CustomMessages.JAVASCRIPT_READY);
75
- const playbackToggleElement = document.getElementById('native-mobile-playbacktoggleoverlay');
75
+ const playbackToggleElement = player.getContainer().querySelector('#native-mobile-playbacktoggleoverlay');
76
76
  if (playbackToggleElement) {
77
- const playbackToggleButton = playbackToggleElement.querySelector('button');
78
- playbackToggleElement.addEventListener('click', () => {
79
- const isPlaying = playbackToggleButton?.getAttribute('aria-pressed') === 'true';
80
- sendCustomMessage(CustomMessages.TOGGLE_PLAY_PAUSE, { isChecked: !isPlaying });
77
+ playbackToggleElement.addEventListener('touchend', (event) => {
78
+ event.preventDefault();
79
+ player?.play();
80
+ sendCustomMessage(CustomMessages.TOGGLE_PLAY_PAUSE, { isChecked: true });
81
81
  });
82
82
  }
83
83
  };
@@ -32,6 +32,8 @@ export default class NpoPlayer {
32
32
  createUIManager(player: PlayerAPI, variant: NpoPlayerUIVariants): Promise<void>;
33
33
  doError(input: any, status?: number): void;
34
34
  keyPress(e: KeyboardEvent): void;
35
+ play(): void;
36
+ pause(): void;
35
37
  setVolume(volume: number): void;
36
38
  increaseVolume(): void;
37
39
  decreaseVolume(): void;
@@ -42,7 +44,7 @@ export default class NpoPlayer {
42
44
  cancelPlayNextScreen(): void;
43
45
  hidePlayNextScreen(): void;
44
46
  doPlayNext(): void;
45
- destroy(asyncMode?: boolean): Promise<boolean>;
46
- unload(asyncMode?: boolean): Promise<boolean>;
47
+ destroy(asyncMode?: boolean): Promise<boolean> | boolean;
48
+ unload(asyncMode?: boolean): Promise<boolean> | boolean;
47
49
  printVersion(): void;
48
50
  }
package/lib/npoplayer.js CHANGED
@@ -19,6 +19,7 @@ import { nativeMobileUiFactory } from './js/ui/nativemobileuifactory';
19
19
  import { setupMediaSessionActionHandlers } from './js/playeractions/handlers/mediasessionactions';
20
20
  import { processStream } from './js/ui/handlers/streamhandler';
21
21
  import { removeReplayClass } from './js/playeractions/handlers/removereplayclass';
22
+ import { showNicamAfterUiDelay } from './js/ui/handlers/nicamhandler';
22
23
  export { NpoPlayerUIVariants, };
23
24
  export default class NpoPlayer {
24
25
  constructor(_container, _playerConfig, _npotag, _npotaginstance, _variant) {
@@ -56,6 +57,13 @@ export default class NpoPlayer {
56
57
  this.container.addEventListener('keydown', e => {
57
58
  this.keyPress(e);
58
59
  }, true);
60
+ const playbackToggleElement = this.container.querySelector('.bmpui-ui-playbacktoggle-overlay');
61
+ if (playbackToggleElement) {
62
+ playbackToggleElement.addEventListener('touchend', (event) => {
63
+ event.preventDefault();
64
+ this.player?.play();
65
+ });
66
+ }
59
67
  }
60
68
  async loadStream(source, options = {}) {
61
69
  this.streamOptions = options;
@@ -95,7 +103,16 @@ export default class NpoPlayer {
95
103
  const payload = {
96
104
  baseURL: endpoint,
97
105
  jwt: source,
98
- data: { profileName: profile.profileName, drmType: profile.drm, referrerUrl: window.location.href }
106
+ data: {
107
+ profileName: profile.profileName,
108
+ drmType: profile.drm,
109
+ referrerUrl: window.location.href,
110
+ ster: {
111
+ identifier: options?.ster?.identifier ?? 'npo-app-desktop',
112
+ deviceType: options?.ster?.deviceType ?? 4,
113
+ player: 'web'
114
+ }
115
+ }
99
116
  };
100
117
  try {
101
118
  _streamObject = await getStreamObject(this, payload);
@@ -156,6 +173,7 @@ export default class NpoPlayer {
156
173
  this.hidePlayNextScreen();
157
174
  });
158
175
  }
176
+ this.player.on(PlayerEvent.Play, () => { showNicamAfterUiDelay(this.player, this.uiManager); });
159
177
  if (options?.startOffset != null)
160
178
  playerAction.handleStartOffset(this.player, options.startOffset);
161
179
  const setLiveOffsetListener = function () {
@@ -194,6 +212,16 @@ export default class NpoPlayer {
194
212
  return;
195
213
  playerAction.resolveKeyPress(this.player, this, e);
196
214
  }
215
+ play() {
216
+ if (this.player == null)
217
+ return;
218
+ this.player.play();
219
+ }
220
+ pause() {
221
+ if (this.player == null)
222
+ return;
223
+ this.player.pause();
224
+ }
197
225
  setVolume(volume) {
198
226
  if (this.player == null)
199
227
  return;
@@ -290,13 +318,7 @@ export default class NpoPlayer {
290
318
  }
291
319
  };
292
320
  if (asyncMode) {
293
- try {
294
- destroyLogic();
295
- return Promise.resolve(true);
296
- }
297
- catch (error) {
298
- return Promise.resolve(false);
299
- }
321
+ return destroyLogic().then(() => true).catch(() => false);
300
322
  }
301
323
  else {
302
324
  return destroyLogic();
@@ -324,9 +346,12 @@ export default class NpoPlayer {
324
346
  return unloadLogic();
325
347
  }
326
348
  else {
327
- return new Promise((resolve, reject) => {
328
- unloadLogic().then(resolve).catch(reject);
329
- });
349
+ try {
350
+ return unloadLogic();
351
+ }
352
+ catch (error) {
353
+ return false;
354
+ }
330
355
  }
331
356
  }
332
357
  printVersion() {