@npo/player 1.18.4 → 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 (67) hide show
  1. package/README.md +4 -1
  2. package/lib/js/ads/ster.js +2 -0
  3. package/lib/js/drm/handlers/decideprofile.js +17 -17
  4. package/lib/js/drm/handlers/decideprofile.test.d.ts +1 -0
  5. package/lib/js/drm/handlers/decideprofile.test.js +33 -0
  6. package/lib/js/fragments/removefragments.d.ts +1 -2
  7. package/lib/js/fragments/removefragments.js +1 -5
  8. package/lib/js/fragments/removefragments.test.d.ts +1 -0
  9. package/lib/js/fragments/removefragments.test.js +26 -0
  10. package/lib/js/fragments/setfragments.d.ts +1 -1
  11. package/lib/js/fragments/setfragments.js +19 -13
  12. package/lib/js/fragments/setfragments.test.d.ts +1 -0
  13. package/lib/js/fragments/setfragments.test.js +72 -0
  14. package/lib/js/playeractions/handlers/handleoffsets.js +8 -5
  15. package/lib/js/playeractions/handlers/processsourceconfig.js +13 -7
  16. package/lib/js/playeractions/handlers/removereplayclass.d.ts +2 -0
  17. package/lib/js/playeractions/handlers/removereplayclass.js +11 -0
  18. package/lib/js/playeractions/handlers/removereplayclass.test.d.ts +1 -0
  19. package/lib/js/playeractions/handlers/removereplayclass.test.js +28 -0
  20. package/lib/js/playeractions/handlers/resolvekeypress.test.d.ts +1 -0
  21. package/lib/js/playeractions/handlers/resolvekeypress.test.js +80 -0
  22. package/lib/js/tracking/handlers/eventlogging.test.d.ts +1 -0
  23. package/lib/js/tracking/handlers/eventlogging.test.js +46 -0
  24. package/lib/js/ui/components/controlbar.js +2 -1
  25. package/lib/js/ui/components/nativemobile/buttons.js +0 -1
  26. package/lib/js/ui/components/settingspanel.js +4 -0
  27. package/lib/js/ui/handlers/listboxhandlers.js +1 -0
  28. package/lib/js/ui/handlers/nicamhandler.d.ts +3 -0
  29. package/lib/js/ui/handlers/nicamhandler.js +16 -0
  30. package/lib/js/ui/handlers/streamhandler.js +1 -1
  31. package/lib/js/ui/nativemobileuicontainer.js +2 -2
  32. package/lib/js/ui/nativemobileuifactory.js +18 -26
  33. package/lib/js/ui/uicontainer.d.ts +1 -0
  34. package/lib/js/ui/uicontainer.js +8 -0
  35. package/lib/js/utilities/utilities.js +3 -4
  36. package/lib/npoplayer.d.ts +4 -2
  37. package/lib/npoplayer.js +117 -36
  38. package/lib/package.json +7 -7
  39. package/lib/src/js/drm/handlers/decideprofile.test.d.ts +1 -0
  40. package/lib/src/js/fragments/removefragments.d.ts +1 -2
  41. package/lib/src/js/fragments/removefragments.test.d.ts +1 -0
  42. package/lib/src/js/fragments/setfragments.d.ts +1 -1
  43. package/lib/src/js/fragments/setfragments.test.d.ts +1 -0
  44. package/lib/src/js/playeractions/handlers/removereplayclass.d.ts +2 -0
  45. package/lib/src/js/playeractions/handlers/removereplayclass.test.d.ts +1 -0
  46. package/lib/src/js/playeractions/handlers/resolvekeypress.test.d.ts +1 -0
  47. package/lib/src/js/tracking/handlers/eventlogging.test.d.ts +1 -0
  48. package/lib/src/js/ui/handlers/nicamhandler.d.ts +3 -0
  49. package/lib/src/js/ui/uicontainer.d.ts +1 -0
  50. package/lib/src/npoplayer.d.ts +4 -2
  51. package/lib/src/types/interfaces.d.ts +10 -0
  52. package/lib/tests/mocks/mockNpoplayer.d.ts +2 -0
  53. package/lib/tests/mocks/mockNpoplayer.js +115 -0
  54. package/lib/types/interfaces.d.ts +10 -0
  55. package/package.json +7 -7
  56. package/src/scss/components/_advert.scss +5 -5
  57. package/src/scss/components/_hugeplaybacktogglebutton.scss +1 -0
  58. package/src/scss/components/_metadata.scss +17 -0
  59. package/src/scss/components/_nicam.scss +20 -0
  60. package/src/scss/components/_replay.scss +2 -2
  61. package/src/scss/components/_seekbarthumbnail.scss +0 -1
  62. package/src/scss/components/_subtitles.scss +1 -1
  63. package/src/scss/npoplayer.css +39 -34
  64. package/src/scss/variants/_player-base.scss +7 -2
  65. package/lib/js/ui/handlers/playnexthandlers.d.ts +0 -0
  66. package/lib/js/ui/handlers/playnexthandlers.js +0 -1
  67. package/lib/src/js/ui/handlers/playnexthandlers.d.ts +0 -0
package/README.md CHANGED
@@ -7,8 +7,11 @@ The video player library for NPO (Nederlandse Publieke Omroep) and other broadca
7
7
  # Documentation
8
8
  Extensive and up-to-date documentation is available at https://docs.npoplayer.nl
9
9
 
10
+ # Code quality
11
+ Code quality is analysed by SonarCloud. The project can be found at https://sonarcloud.io/project/overview?id=NPOstart_npo-player
12
+
10
13
  # Changelog
11
- Current version: v1.18.0
14
+ Current version: v1.20.0
12
15
 
13
16
  The changelog is available at https://docs.npoplayer.nl/implementation/web/changelog/
14
17
 
@@ -107,6 +107,7 @@ export async function handlePreRolls(playerapi, streamObject, npoplayer) {
107
107
  playerapi.on(PlayerEvent.AdFinished, handleAdFinished);
108
108
  function handleAdBreakFinished() {
109
109
  playerapi.off(PlayerEvent.AdBreakFinished, handleAdBreakFinished);
110
+ playerapi.off(PlayerEvent.AdError, handleAdBreakFinished);
110
111
  npoplayer.adBreakActive = false;
111
112
  npoplayer.uiComponents.adbutton?.hide();
112
113
  npoplayer.uiComponents.adlabel?.hide();
@@ -117,5 +118,6 @@ export async function handlePreRolls(playerapi, streamObject, npoplayer) {
117
118
  resolve();
118
119
  }
119
120
  playerapi.on(PlayerEvent.AdBreakFinished, handleAdBreakFinished);
121
+ playerapi.on(PlayerEvent.AdError, handleAdBreakFinished);
120
122
  });
121
123
  }
@@ -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
+ });
@@ -1,3 +1,2 @@
1
- import { PlayerAPI } from 'bitmovin-player';
2
1
  import { UIManager } from 'bitmovin-player-ui';
3
- export declare function removeFragments(player: PlayerAPI, uiManager: UIManager): Promise<void>;
2
+ export declare function removeFragments(uiManager: UIManager): void;
@@ -1,9 +1,5 @@
1
- import { PlayerEvent } from 'bitmovin-player';
2
- import { checkFunction, seekFunction } from './setfragments';
3
- export async function removeFragments(player, uiManager) {
1
+ export function removeFragments(uiManager) {
4
2
  uiManager.getTimelineMarkers().forEach(marker => {
5
3
  uiManager.removeTimelineMarker(marker);
6
4
  });
7
- player.off(PlayerEvent.TimeChanged, checkFunction());
8
- player.off(PlayerEvent.Seek, seekFunction());
9
5
  }
@@ -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
+ });
@@ -3,4 +3,4 @@ import { type Fragments } from '../../types/interfaces';
3
3
  import { UIManager } from 'bitmovin-player-ui';
4
4
  export declare const checkFunction: () => (e: any) => void;
5
5
  export declare const seekFunction: () => (e: any) => void;
6
- export declare function setFragments(player: PlayerAPI, uiManager: UIManager, fragments: Fragments): Promise<void>;
6
+ export declare function setFragments(player: PlayerAPI, uiManager: UIManager, fragments: Fragments): void;
@@ -2,20 +2,25 @@ import { PlayerEvent } from 'bitmovin-player';
2
2
  import { removeFragments } from './removefragments';
3
3
  let fragment = null;
4
4
  let playerInstance = null;
5
+ let isFinished = false;
5
6
  const setTitle = () => {
6
- const element = document.querySelector('.bmpui-npo-player .bmpui-seekbar-label-title');
7
- if (element !== null && element.textContent === '') {
7
+ const element = playerInstance?.getContainer()?.querySelector('.bmpui-npo-player .bmpui-seekbar-label-title');
8
+ if (element !== null && element?.textContent === '') {
8
9
  element.textContent = fragment?.title || '';
9
10
  }
10
11
  };
11
12
  export const checkFunction = () => (e) => {
12
13
  if (!fragment || !playerInstance)
13
14
  return;
14
- if (e.time > fragment.end) {
15
+ playerInstance.off(PlayerEvent.TimeChanged, checkFunction());
16
+ if (e.time > fragment.end && !isFinished) {
17
+ isFinished = true;
15
18
  playerInstance.seek(Math.max(fragment.start, fragment.end));
16
19
  playerInstance.pause();
20
+ playerInstance?.getContainer().querySelector('.bmpui-npo-player')?.classList.add('bmpui-player-state-replay');
17
21
  }
18
- else if (e.time < fragment.start) {
22
+ else if (e.time < fragment.start || (e.time > fragment.end && isFinished)) {
23
+ isFinished = false;
19
24
  playerInstance.seek(fragment.start);
20
25
  }
21
26
  setTitle();
@@ -23,13 +28,14 @@ export const checkFunction = () => (e) => {
23
28
  export const seekFunction = () => (e) => {
24
29
  if (!fragment || !playerInstance)
25
30
  return;
31
+ playerInstance.off(PlayerEvent.Seek, seekFunction());
26
32
  const seekTarget = e.seekTarget ? e.seekTarget : e.to.time;
27
33
  if (seekTarget > fragment.end || seekTarget < fragment.start) {
28
34
  playerInstance.seek(Math.max(fragment.start, Math.min(fragment.end, seekTarget)));
29
35
  }
30
36
  setTitle();
31
37
  };
32
- export async function setFragments(player, uiManager, fragments) {
38
+ export function setFragments(player, uiManager, fragments) {
33
39
  fragment = fragments.sections[0] || null;
34
40
  playerInstance = player;
35
41
  const unplayableClass = 'seekbar-marker-unplayable';
@@ -37,12 +43,16 @@ export async function setFragments(player, uiManager, fragments) {
37
43
  if (!fragment)
38
44
  return;
39
45
  player.seek(fragment.start);
40
- if (player.isPaused())
46
+ if (player.isPaused() && playerInstance?.getConfig().playback?.autoplay === true) {
41
47
  void player.play();
48
+ }
42
49
  };
43
50
  const addMarkers = () => {
44
51
  if (!fragment)
45
52
  return;
53
+ playerInstance?.off(PlayerEvent.Ready, addMarkers);
54
+ removeFragments(uiManager);
55
+ setTitle();
46
56
  const timelineMarkers = [
47
57
  {
48
58
  time: 0,
@@ -61,12 +71,8 @@ export async function setFragments(player, uiManager, fragments) {
61
71
  uiManager.addTimelineMarker(marker);
62
72
  });
63
73
  playFragmentFromStart();
64
- player.on(PlayerEvent.TimeChanged, checkFunction());
65
- player.on(PlayerEvent.Seek, seekFunction());
74
+ playerInstance?.on(PlayerEvent.TimeChanged, checkFunction());
75
+ playerInstance?.on(PlayerEvent.Seek, seekFunction());
66
76
  };
67
- player.off(PlayerEvent.TimeChanged, checkFunction());
68
- player.off(PlayerEvent.Seek, seekFunction());
69
- await removeFragments(player, uiManager);
70
- addMarkers();
71
- setTitle();
77
+ playerInstance?.on(PlayerEvent.Ready, addMarkers);
72
78
  }
@@ -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
+ });
@@ -6,12 +6,15 @@ export function handleStartOffset(player, offset) {
6
6
  }
7
7
  return;
8
8
  }
9
- player.on(PlayerEvent.SourceLoaded, () => {
9
+ const seek = () => {
10
+ player.off(PlayerEvent.SourceLoaded, seek);
11
+ player.off(PlayerEvent.AdBreakFinished, seek);
12
+ player.off(PlayerEvent.AdError, seek);
10
13
  player.seek(offset);
11
- });
12
- player.on(PlayerEvent.AdBreakFinished, () => {
13
- player.seek(offset);
14
- });
14
+ };
15
+ player.on(PlayerEvent.SourceLoaded, seek);
16
+ player.on(PlayerEvent.AdBreakFinished, seek);
17
+ player.on(PlayerEvent.AdError, seek);
15
18
  }
16
19
  export function shiftToProgramStart(player, timestamp) {
17
20
  if (player == null)
@@ -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,2 @@
1
+ import { PlayerAPI } from 'bitmovin-player';
2
+ export declare function removeReplayClass(player: PlayerAPI | null): void;
@@ -0,0 +1,11 @@
1
+ import { PlayerEvent } from 'bitmovin-player';
2
+ export function removeReplayClass(player) {
3
+ if (!player)
4
+ return;
5
+ player.off(PlayerEvent.Seek, () => {
6
+ removeReplayClass(player);
7
+ });
8
+ const playerContainer = player.getContainer();
9
+ playerContainer.classList.remove('bmpui-player-state-finished');
10
+ playerContainer.classList.remove('bmpui-player-state-replay');
11
+ }
@@ -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
+ });
@@ -12,8 +12,9 @@ export function createControlBar(npoplayer, settingsPanel) {
12
12
  hidden: true
13
13
  });
14
14
  player?.on(PlayerEvent.CastAvailable, () => {
15
- if (player.isCastAvailable())
15
+ if (player.isCastAvailable()) {
16
16
  chromeCastButton.show();
17
+ }
17
18
  });
18
19
  const controlbar = new ControlBar({
19
20
  components: [
@@ -124,7 +124,6 @@ export function createPlaybackToggleButton() {
124
124
  const playbackClickHandler = () => {
125
125
  if (!customPlayerbackToggleButton)
126
126
  return;
127
- console.log('TOGGLE_PLAY_PAUSE', !customPlayerbackToggleButton.isOn());
128
127
  sendCustomMessage(CustomMessages.TOGGLE_PLAY_PAUSE, { isChecked: !customPlayerbackToggleButton.isOn() });
129
128
  };
130
129
  customPlayerbackToggleButton.onClick.unsubscribe(() => {
@@ -133,6 +133,7 @@ export function createSettingsPanel(npoplayer) {
133
133
  const checkSubtitles = () => {
134
134
  player?.off(PlayerEvent.SourceLoaded, checkSubtitles);
135
135
  player?.off(PlayerEvent.AdBreakFinished, checkSubtitles);
136
+ player?.off(PlayerEvent.AdError, checkSubtitles);
136
137
  const subtitles = player?.subtitles?.list();
137
138
  if (subtitles != null && subtitles.length > 0) {
138
139
  subtitlesPanelItem.show();
@@ -144,6 +145,7 @@ export function createSettingsPanel(npoplayer) {
144
145
  const checkQuality = () => {
145
146
  player?.off(PlayerEvent.SourceLoaded, checkQuality);
146
147
  player?.off(PlayerEvent.AdBreakFinished, checkQuality);
148
+ player?.off(PlayerEvent.AdError, checkQuality);
147
149
  if (player?.getAvailableVideoQualities() !== undefined && player?.getAvailableVideoQualities().length > 0) {
148
150
  qualityPanelItem.show();
149
151
  }
@@ -155,5 +157,7 @@ export function createSettingsPanel(npoplayer) {
155
157
  player?.on(PlayerEvent.SourceLoaded, checkQuality);
156
158
  player?.on(PlayerEvent.AdBreakFinished, checkSubtitles);
157
159
  player?.on(PlayerEvent.AdBreakFinished, checkQuality);
160
+ player?.on(PlayerEvent.AdError, checkSubtitles);
161
+ player?.on(PlayerEvent.AdError, checkQuality);
158
162
  return settingsPanel;
159
163
  }
@@ -9,6 +9,7 @@ export function handleSubtitleListBoxItemSelected(subtitleSettingsOpenButton, se
9
9
  e.getItems().forEach((item) => {
10
10
  if (item.key === e.getSelectedItem()) {
11
11
  subtitleSettingsOpenButton.setText(item.label);
12
+ e.selectItem(item.key);
12
13
  }
13
14
  });
14
15
  closeSettingsPanel(settingsPanel, mainSettingsPage);
@@ -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;