@npo/player 1.19.0 → 1.20.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 (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 +37 -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 +10 -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 +10 -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,17 @@ 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
+ site: options?.ster?.site ?? 'npo',
114
+ player: 'web'
115
+ }
116
+ }
99
117
  };
100
118
  try {
101
119
  _streamObject = await getStreamObject(this, payload);
@@ -156,6 +174,7 @@ export default class NpoPlayer {
156
174
  this.hidePlayNextScreen();
157
175
  });
158
176
  }
177
+ this.player.on(PlayerEvent.Play, () => { showNicamAfterUiDelay(this.player, this.uiManager); });
159
178
  if (options?.startOffset != null)
160
179
  playerAction.handleStartOffset(this.player, options.startOffset);
161
180
  const setLiveOffsetListener = function () {
@@ -194,6 +213,16 @@ export default class NpoPlayer {
194
213
  return;
195
214
  playerAction.resolveKeyPress(this.player, this, e);
196
215
  }
216
+ play() {
217
+ if (this.player == null)
218
+ return;
219
+ this.player.play();
220
+ }
221
+ pause() {
222
+ if (this.player == null)
223
+ return;
224
+ this.player.pause();
225
+ }
197
226
  setVolume(volume) {
198
227
  if (this.player == null)
199
228
  return;
@@ -290,13 +319,7 @@ export default class NpoPlayer {
290
319
  }
291
320
  };
292
321
  if (asyncMode) {
293
- try {
294
- destroyLogic();
295
- return Promise.resolve(true);
296
- }
297
- catch (error) {
298
- return Promise.resolve(false);
299
- }
322
+ return destroyLogic().then(() => true).catch(() => false);
300
323
  }
301
324
  else {
302
325
  return destroyLogic();
@@ -324,9 +347,12 @@ export default class NpoPlayer {
324
347
  return unloadLogic();
325
348
  }
326
349
  else {
327
- return new Promise((resolve, reject) => {
328
- unloadLogic().then(resolve).catch(reject);
329
- });
350
+ try {
351
+ return unloadLogic();
352
+ }
353
+ catch (error) {
354
+ return false;
355
+ }
330
356
  }
331
357
  }
332
358
  printVersion() {