@npo/player 1.26.0 → 1.27.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 (175) hide show
  1. package/README.md +1 -1
  2. package/lib/js/playeractions/handlers/mediasessionactions.js +2 -2
  3. package/lib/js/playeractions/playeractions.d.ts +0 -1
  4. package/lib/js/playeractions/playeractions.js +0 -1
  5. package/lib/js/utilities/utilities.prid.d.ts +6 -0
  6. package/lib/js/utilities/utilities.prid.js +8 -0
  7. package/lib/js/utilities/utilities.prid.test.js +46 -0
  8. package/lib/npoplayer.d.ts +1 -1
  9. package/lib/npoplayer.js +50 -59
  10. package/lib/npoplayer.test.js +2 -2
  11. package/lib/package.json +1 -1
  12. package/lib/services/a11y/setup.test.js +0 -1
  13. package/lib/services/advertHandlers/discardAdBreak.js +2 -0
  14. package/lib/services/advertHandlers/discardAdBreak.test.js +6 -1
  15. package/lib/services/advertHandlers/handlePreRolls.js +2 -4
  16. package/lib/services/advertHandlers/handlePrerolls.test.js +62 -1
  17. package/lib/services/drmHandlers/decideprofile.js +7 -1
  18. package/lib/services/drmHandlers/decideprofile.test.js +4 -0
  19. package/lib/services/drmHandlers/verifydrm.js +5 -6
  20. package/lib/services/drmHandlers/verifydrm.test.js +28 -5
  21. package/lib/services/errors/errorBackground.test.js +48 -0
  22. package/lib/services/errors/errorHandler.d.ts +3 -0
  23. package/lib/services/errors/errorHandler.js +45 -0
  24. package/lib/services/errors/errorText.d.ts +2 -0
  25. package/lib/services/errors/errorText.js +59 -0
  26. package/lib/services/errors/errorText.test.js +66 -0
  27. package/lib/services/keyboardHandlers/resolvekeypress.js +21 -17
  28. package/lib/services/localStorageHandlers/localStorageHandlers.js +5 -2
  29. package/lib/services/localStorageHandlers/localStorageHandlers.test.js +0 -1
  30. package/lib/services/nicamHandlers/nicamhandler.js +0 -4
  31. package/lib/services/nicamHandlers/nicamhandler.test.js +0 -5
  32. package/lib/services/npoPlayerAPI/contants.d.ts +20 -0
  33. package/lib/services/npoPlayerAPI/contants.js +6 -0
  34. package/lib/services/npoPlayerAPI/npoPlayerAPI.d.ts +4 -1
  35. package/lib/services/npoPlayerAPI/npoPlayerAPI.js +29 -5
  36. package/lib/services/npoPlayerAPI/npoPlayerAPI.test.js +11 -0
  37. package/lib/services/services.d.ts +8 -2
  38. package/lib/services/services.js +28 -2
  39. package/lib/services/streamFetchHandler/fetchStream.d.ts +2 -0
  40. package/lib/services/streamFetchHandler/fetchStream.js +48 -0
  41. package/lib/services/streamFetchHandler/fetchstream.test.js +70 -0
  42. package/lib/services/trackingHandlers/eventBinding.d.ts +2 -0
  43. package/lib/{js/tracking/handlers/eventbinding.js → services/trackingHandlers/eventBinding.js} +16 -14
  44. package/lib/services/trackingHandlers/eventBinding.test.js +89 -0
  45. package/lib/services/trackingHandlers/eventLogging.d.ts +2 -0
  46. package/lib/{js/tracking/handlers/eventlogging.js → services/trackingHandlers/eventLogging.js} +18 -7
  47. package/lib/services/trackingHandlers/eventLogging.test.js +63 -0
  48. package/lib/services/trackingHandlers/index.d.ts +3 -0
  49. package/lib/services/trackingHandlers/index.js +3 -0
  50. package/lib/services/trackingHandlers/playerTrackerInit.d.ts +2 -0
  51. package/lib/{js/tracking/handlers/playertrackerinit.js → services/trackingHandlers/playerTrackerInit.js} +6 -3
  52. package/lib/services/trackingHandlers/playerTrackerInit.test.js +75 -0
  53. package/lib/services/trackingHandlers/playerTrackerStart.d.ts +2 -0
  54. package/lib/services/trackingHandlers/playerTrackerStart.js +25 -0
  55. package/lib/services/trackingHandlers/playerTrackerStart.test.js +59 -0
  56. package/lib/services/trackingHandlers/streamTrackerInit.d.ts +3 -0
  57. package/lib/services/trackingHandlers/streamTrackerInit.js +27 -0
  58. package/lib/services/trackingHandlers/streamTrackerInit.test.js +84 -0
  59. package/lib/services/verticalVideoHandlers/handleVerticalVideoControls.d.ts +2 -2
  60. package/lib/services/verticalVideoHandlers/handleVerticalVideoControls.js +4 -6
  61. package/lib/services/verticalVideoHandlers/handleVerticalVideoControls.test.js +4 -17
  62. package/lib/services/verticalVideoHandlers/handleVerticalVideoSettings.d.ts +2 -0
  63. package/lib/services/verticalVideoHandlers/handleVerticalVideoSettings.js +26 -0
  64. package/lib/services/verticalVideoHandlers/handleVerticalVideoSettings.test.js +82 -0
  65. package/lib/src/js/playeractions/playeractions.d.ts +0 -1
  66. package/lib/src/js/utilities/utilities.prid.d.ts +6 -0
  67. package/lib/src/js/utilities/utilities.prid.test.d.ts +1 -0
  68. package/lib/src/npoplayer.d.ts +1 -1
  69. package/lib/src/services/errors/errorBackground.test.d.ts +1 -0
  70. package/lib/src/services/errors/errorHandler.d.ts +3 -0
  71. package/lib/src/services/errors/errorText.d.ts +2 -0
  72. package/lib/src/services/errors/errorText.test.d.ts +1 -0
  73. package/lib/src/services/npoPlayerAPI/contants.d.ts +20 -0
  74. package/lib/src/services/npoPlayerAPI/npoPlayerAPI.d.ts +4 -1
  75. package/lib/src/services/services.d.ts +8 -2
  76. package/lib/src/services/streamFetchHandler/fetchStream.d.ts +2 -0
  77. package/lib/src/services/streamFetchHandler/fetchstream.test.d.ts +1 -0
  78. package/lib/src/services/trackingHandlers/eventBinding.d.ts +2 -0
  79. package/lib/src/services/trackingHandlers/eventBinding.test.d.ts +1 -0
  80. package/lib/src/services/trackingHandlers/eventLogging.d.ts +2 -0
  81. package/lib/src/services/trackingHandlers/eventLogging.test.d.ts +1 -0
  82. package/lib/src/services/trackingHandlers/index.d.ts +3 -0
  83. package/lib/src/services/trackingHandlers/playerTrackerInit.d.ts +2 -0
  84. package/lib/src/services/trackingHandlers/playerTrackerInit.test.d.ts +1 -0
  85. package/lib/src/services/trackingHandlers/playerTrackerStart.d.ts +2 -0
  86. package/lib/src/services/trackingHandlers/playerTrackerStart.test.d.ts +1 -0
  87. package/lib/src/services/trackingHandlers/streamTrackerInit.d.ts +3 -0
  88. package/lib/src/services/trackingHandlers/streamTrackerInit.test.d.ts +1 -0
  89. package/lib/src/services/verticalVideoHandlers/handleVerticalVideoControls.d.ts +2 -2
  90. package/lib/src/services/verticalVideoHandlers/handleVerticalVideoSettings.d.ts +2 -0
  91. package/lib/src/services/verticalVideoHandlers/handleVerticalVideoSettings.test.d.ts +1 -0
  92. package/lib/src/types/interfaces.d.ts +15 -1
  93. package/lib/src/ui/components/seekbar.d.ts +2 -1
  94. package/lib/src/ui/components/verticalvideo/controlbar.d.ts +3 -3
  95. package/lib/src/ui/components/verticalvideo/settingspanel.d.ts +2 -0
  96. package/lib/tests/mocks/mockNpoplayer.js +1 -1
  97. package/lib/tests/mocks/mockStreamObject.d.ts +2 -0
  98. package/lib/tests/mocks/mockStreamObject.js +21 -0
  99. package/lib/tests/mocks/playerContextMock.d.ts +1 -0
  100. package/lib/tests/mocks/playerContextMock.js +5 -1
  101. package/lib/types/interfaces.d.ts +15 -1
  102. package/lib/ui/components/audio/controlbar.js +2 -1
  103. package/lib/ui/components/controlbar.js +1 -1
  104. package/lib/ui/components/seekbar.d.ts +2 -1
  105. package/lib/ui/components/seekbar.js +2 -2
  106. package/lib/ui/components/settingspanel.js +1 -3
  107. package/lib/ui/components/verticalvideo/controlbar.d.ts +3 -3
  108. package/lib/ui/components/verticalvideo/controlbar.js +14 -11
  109. package/lib/ui/components/verticalvideo/settingspanel.d.ts +2 -0
  110. package/lib/ui/components/verticalvideo/settingspanel.js +21 -0
  111. package/lib/ui/handlers/domhandlers.test.js +6 -26
  112. package/lib/ui/nativemobileuifactory.js +1 -1
  113. package/lib/ui/uicontainer.js +4 -1
  114. package/lib/ui/uicontainer.test.js +4 -1
  115. package/package.json +1 -1
  116. package/src/style/components/_advert.scss +3 -3
  117. package/src/style/components/_buffering.scss +14 -8
  118. package/src/style/components/_error.scss +44 -1
  119. package/src/style/components/_hugeplaybacktogglebutton.scss +1 -0
  120. package/src/style/components/_metadata.scss +13 -12
  121. package/src/style/components/_nicam.scss +11 -6
  122. package/src/style/components/_playnext.scss +1 -1
  123. package/src/style/components/_replay.scss +1 -6
  124. package/src/style/components/_settingspanel.scss +74 -79
  125. package/src/style/components/audio/_errors.scss +1 -1
  126. package/src/style/components/audio/_metadata.scss +14 -8
  127. package/src/style/components/audio/_playbutton.scss +1 -0
  128. package/src/style/components/audio/_topbar.scss +10 -2
  129. package/src/style/components/audio/_volumeslider.scss +3 -3
  130. package/src/style/components/vertical-video/_bottombar.scss +42 -1
  131. package/src/style/components/vertical-video/_hugeplaybacktogglebutton.scss +24 -4
  132. package/src/style/components/vertical-video/_settingspanel.scss +20 -3
  133. package/src/style/components/vertical-video/_topbar.scss +47 -6
  134. package/src/style/npoplayer.css +86 -53
  135. package/src/style/npoplayer.scss +4 -7
  136. package/src/style/variants/_player-base.scss +10 -2
  137. package/src/style/variants/_player-small.scss +15 -1
  138. package/src/style/vars/_fonts.scss +13 -7
  139. package/src/style/vars/_z-index.scss +1 -0
  140. package/lib/js/api/getstreamobject.d.ts +0 -3
  141. package/lib/js/api/getstreamobject.js +0 -38
  142. package/lib/js/api/getstreamobject.test.js +0 -48
  143. package/lib/js/playeractions/customerrors.test.js +0 -51
  144. package/lib/js/playeractions/handlers/customerrors.d.ts +0 -50
  145. package/lib/js/playeractions/handlers/customerrors.js +0 -56
  146. package/lib/js/playeractions/handlers/error.d.ts +0 -3
  147. package/lib/js/playeractions/handlers/error.js +0 -14
  148. package/lib/js/playeractions/handlers/error.test.js +0 -44
  149. package/lib/js/tracking/handlers/eventbinding.d.ts +0 -3
  150. package/lib/js/tracking/handlers/eventlogging.d.ts +0 -2
  151. package/lib/js/tracking/handlers/eventlogging.test.js +0 -46
  152. package/lib/js/tracking/handlers/playertrackerinit.d.ts +0 -4
  153. package/lib/js/tracking/handlers/playertrackerinit.test.js +0 -74
  154. package/lib/js/tracking/handlers/playertrackerstart.d.ts +0 -1
  155. package/lib/js/tracking/handlers/playertrackerstart.js +0 -25
  156. package/lib/js/tracking/playertracker.d.ts +0 -4
  157. package/lib/js/tracking/playertracker.js +0 -4
  158. package/lib/src/js/api/getstreamobject.d.ts +0 -3
  159. package/lib/src/js/playeractions/handlers/customerrors.d.ts +0 -50
  160. package/lib/src/js/playeractions/handlers/error.d.ts +0 -3
  161. package/lib/src/js/tracking/handlers/eventbinding.d.ts +0 -3
  162. package/lib/src/js/tracking/handlers/eventlogging.d.ts +0 -2
  163. package/lib/src/js/tracking/handlers/playertrackerinit.d.ts +0 -4
  164. package/lib/src/js/tracking/handlers/playertrackerstart.d.ts +0 -1
  165. package/lib/src/js/tracking/playertracker.d.ts +0 -4
  166. /package/lib/js/{api/getstreamobject.test.d.ts → utilities/utilities.prid.test.d.ts} +0 -0
  167. /package/lib/{js/playeractions/customerrors.test.d.ts → services/errors/errorBackground.test.d.ts} +0 -0
  168. /package/lib/{js/playeractions/handlers/error.test.d.ts → services/errors/errorText.test.d.ts} +0 -0
  169. /package/lib/{js/tracking/handlers/eventlogging.test.d.ts → services/streamFetchHandler/fetchstream.test.d.ts} +0 -0
  170. /package/lib/{js/tracking/handlers/playertrackerinit.test.d.ts → services/trackingHandlers/eventBinding.test.d.ts} +0 -0
  171. /package/lib/{src/js/api/getstreamobject.test.d.ts → services/trackingHandlers/eventLogging.test.d.ts} +0 -0
  172. /package/lib/{src/js/playeractions/customerrors.test.d.ts → services/trackingHandlers/playerTrackerInit.test.d.ts} +0 -0
  173. /package/lib/{src/js/playeractions/handlers/error.test.d.ts → services/trackingHandlers/playerTrackerStart.test.d.ts} +0 -0
  174. /package/lib/{src/js/tracking/handlers/eventlogging.test.d.ts → services/trackingHandlers/streamTrackerInit.test.d.ts} +0 -0
  175. /package/lib/{src/js/tracking/handlers/playertrackerinit.test.d.ts → services/verticalVideoHandlers/handleVerticalVideoSettings.test.d.ts} +0 -0
@@ -0,0 +1,48 @@
1
+ import { removeTrailingSlash } from '../../js/utilities/utilities.url';
2
+ export async function fetchStream(playerContext, payload) {
3
+ const { npoPlayer } = playerContext;
4
+ const genericError = 'Dit item on niet worden geladen.';
5
+ let hasHandledError = false;
6
+ const handleStreamError = async (status, message) => {
7
+ if (!hasHandledError) {
8
+ hasHandledError = true;
9
+ await npoPlayer.npoPlayerServices.handleError(playerContext, status);
10
+ }
11
+ throw new Error(message);
12
+ };
13
+ try {
14
+ const controller = new AbortController();
15
+ const { signal } = controller;
16
+ const timeout = setTimeout(() => {
17
+ controller.abort();
18
+ }, 5000);
19
+ const response = await fetch(removeTrailingSlash(payload.baseURL) + '/stream-link', {
20
+ method: 'post',
21
+ headers: {
22
+ 'Accept': '*/*',
23
+ 'Authorization': payload.jwt,
24
+ 'Content-Type': 'application/json'
25
+ },
26
+ body: JSON.stringify(payload.data),
27
+ signal
28
+ });
29
+ clearTimeout(timeout);
30
+ const streamApiResponse = await response.json();
31
+ if (response.ok && streamApiResponse && typeof streamApiResponse === 'object') {
32
+ return streamApiResponse;
33
+ }
34
+ else {
35
+ const status = streamApiResponse?.status || 500;
36
+ return await handleStreamError(status, genericError);
37
+ }
38
+ }
39
+ catch (error) {
40
+ if (!hasHandledError) {
41
+ const errorMessage = error instanceof Error && error.message === 'AbortError'
42
+ ? 'aborterror'
43
+ : 'Dit item on niet worden geladen. Het ophalen van de stream duurde te lang.';
44
+ return await handleStreamError(500, errorMessage);
45
+ }
46
+ throw error;
47
+ }
48
+ }
@@ -0,0 +1,70 @@
1
+ import { fetchStream } from './fetchStream';
2
+ import { createMockNpoPlayer, createPlayerContextMock } from '../../../tests/mocks/playerContextMock';
3
+ describe('fetchStream', () => {
4
+ let mockPlayerContext;
5
+ let mockNpoPlayer;
6
+ const mockPayload = {
7
+ baseURL: 'https://api.example.com',
8
+ jwt: 'mock-jwt-token',
9
+ data: { some: 'data' }
10
+ };
11
+ beforeEach(() => {
12
+ jest.clearAllMocks();
13
+ mockNpoPlayer = createMockNpoPlayer({
14
+ npoPlayerServices: {
15
+ handleError: jest.fn()
16
+ }
17
+ });
18
+ mockPlayerContext = createPlayerContextMock({
19
+ npoPlayer: mockNpoPlayer
20
+ });
21
+ });
22
+ it('returns a StreamObject when the fetch is successful', async () => {
23
+ const mockStreamObject = { streamUrl: 'https://stream.example.com' };
24
+ global.fetch = jest.fn().mockResolvedValue({
25
+ ok: true,
26
+ json: jest.fn().mockResolvedValue(mockStreamObject)
27
+ });
28
+ const result = await fetchStream(mockPlayerContext, mockPayload);
29
+ expect(result).toEqual(mockStreamObject);
30
+ expect(global.fetch).toHaveBeenCalledWith('https://api.example.com/stream-link', expect.objectContaining({
31
+ method: 'post',
32
+ headers: expect.objectContaining({
33
+ 'Authorization': mockPayload.jwt,
34
+ 'Content-Type': 'application/json'
35
+ }),
36
+ body: JSON.stringify(mockPayload.data)
37
+ }));
38
+ expect(mockNpoPlayer.npoPlayerServices.handleError).not.toHaveBeenCalled();
39
+ });
40
+ it('calls handleError with 402 status and throws an error', async () => {
41
+ global.fetch = jest.fn().mockResolvedValue({
42
+ ok: false,
43
+ json: jest.fn().mockResolvedValue({ status: 402 })
44
+ });
45
+ await expect(fetchStream(mockPlayerContext, mockPayload)).rejects.toThrow('Dit item on niet worden geladen.');
46
+ expect(mockNpoPlayer.npoPlayerServices.handleError).toHaveBeenCalledWith(mockPlayerContext, 402);
47
+ });
48
+ it('calls handleError with 500 for timeout (AbortError)', async () => {
49
+ global.fetch = jest.fn().mockImplementation(() => new Promise((_, reject) => reject(new Error('AbortError'))));
50
+ await expect(fetchStream(mockPlayerContext, mockPayload)).rejects.toThrow('aborterror');
51
+ expect(mockNpoPlayer.npoPlayerServices.handleError).toHaveBeenCalledWith(mockPlayerContext, 500);
52
+ });
53
+ it('calls handleError with 500 for unexpected errors', async () => {
54
+ global.fetch = jest.fn().mockRejectedValue(new Error('Unexpected error'));
55
+ await expect(fetchStream(mockPlayerContext, mockPayload)).rejects.toThrow('Dit item on niet worden geladen. Het ophalen van de stream duurde te lang.');
56
+ expect(mockNpoPlayer.npoPlayerServices.handleError).toHaveBeenCalledWith(mockPlayerContext, 500);
57
+ });
58
+ it('clears the timeout if fetch resolves before timeout', async () => {
59
+ jest.useFakeTimers();
60
+ const mockStreamObject = { streamUrl: 'https://stream.example.com' };
61
+ global.fetch = jest.fn().mockResolvedValue({
62
+ ok: true,
63
+ json: jest.fn().mockResolvedValue(mockStreamObject)
64
+ });
65
+ const result = await fetchStream(mockPlayerContext, mockPayload);
66
+ jest.runOnlyPendingTimers();
67
+ expect(result).toEqual(mockStreamObject);
68
+ jest.useRealTimers();
69
+ });
70
+ });
@@ -0,0 +1,2 @@
1
+ import { PlayerContext } from 'types/interfaces';
2
+ export declare function bindPlayerEvents(playerContext: PlayerContext): void;
@@ -1,10 +1,12 @@
1
1
  import { PlayerEvent, ViewMode } from 'bitmovin-player';
2
- import { logEvent } from './eventlogging';
2
+ import { logEvent } from './eventLogging';
3
3
  const logEventHandlers = new Map();
4
- export function bindPlayerEvents(npoPlayer, player) {
4
+ export function bindPlayerEvents(playerContext) {
5
+ const { npoPlayer } = playerContext;
6
+ const { player, streamObject } = npoPlayer;
5
7
  if (player == undefined)
6
8
  return;
7
- const isLiveStream = npoPlayer.streamObject.stream.isLiveStream;
9
+ const isLiveStream = streamObject.stream.isLiveStream;
8
10
  let isNewSource = true;
9
11
  let data = {};
10
12
  const timeDifference = (time) => {
@@ -16,10 +18,10 @@ export function bindPlayerEvents(npoPlayer, player) {
16
18
  const currentTime = isLiveStream ? +timeOffset : +threeDecimalTime;
17
19
  data = { stream_position: currentTime };
18
20
  };
19
- const logEventHandler = (eventName) => {
21
+ const logEventHandler = (event) => {
20
22
  return (e) => {
21
23
  timeDifference(e.time);
22
- logEvent(npoPlayer, eventName, data);
24
+ logEvent({ playerContext, event, data });
23
25
  };
24
26
  };
25
27
  const seekHandler = (e) => {
@@ -37,50 +39,50 @@ export function bindPlayerEvents(npoPlayer, player) {
37
39
  stream_position: e.seekTarget
38
40
  };
39
41
  }
40
- logEvent(npoPlayer, 'seek', data);
42
+ logEvent({ playerContext, event: 'seek', data });
41
43
  };
42
44
  const handleSourceLoaded = (e) => {
43
45
  timeDifference(e.time);
44
46
  isNewSource = true;
45
47
  if (isLiveStream) {
46
- logEvent(npoPlayer, 'stop', data);
48
+ logEvent({ playerContext, event: 'stop', data });
47
49
  }
48
50
  };
49
51
  const handlePlay = (e) => {
50
52
  timeDifference(e.time);
51
53
  if (!isNewSource && e.issuer !== 'ui-seek') {
52
- logEvent(npoPlayer, 'resume', data);
54
+ logEvent({ playerContext, event: 'resume', data });
53
55
  }
54
56
  };
55
57
  const pausedHandler = (e) => {
56
58
  timeDifference(e.time);
57
59
  if (e.issuer !== 'ui-seek') {
58
- logEvent(npoPlayer, 'pause', data);
60
+ logEvent({ playerContext, event: 'pause', data });
59
61
  }
60
62
  };
61
63
  const handleTime = (e) => {
62
64
  timeDifference(e.time);
63
65
  if (isNewSource && !npoPlayer.adBreakActive) {
64
- logEvent(npoPlayer, 'start', data);
66
+ logEvent({ playerContext, event: 'start', data });
65
67
  isNewSource = false;
66
68
  }
67
69
  else {
68
- logEvent(npoPlayer, 'time', data);
70
+ logEvent({ playerContext, event: 'time', data });
69
71
  }
70
72
  };
71
73
  const handleViewModeChange = (e) => {
72
74
  const newViewMode = player.getViewMode();
73
75
  timeDifference(e.time);
74
76
  if (newViewMode === ViewMode.Fullscreen) {
75
- logEvent(npoPlayer, 'fullscreen', data);
77
+ logEvent({ playerContext, event: 'fullscreen', data });
76
78
  }
77
79
  else {
78
- logEvent(npoPlayer, 'windowed', data);
80
+ logEvent({ playerContext, event: 'windowed', data });
79
81
  }
80
82
  };
81
83
  const stopBeforeUnload = (e) => {
82
84
  timeDifference(e.time);
83
- logEvent(npoPlayer, 'stop', data);
85
+ logEvent({ playerContext, event: 'stop', data });
84
86
  };
85
87
  for (const [key, value] of logEventHandlers) {
86
88
  player.off(key, value);
@@ -0,0 +1,89 @@
1
+ import { PlayerEvent, ViewMode } from 'bitmovin-player';
2
+ import { bindPlayerEvents } from './eventBinding';
3
+ import { logEvent } from './eventLogging';
4
+ import { createPlayerContextMock } from '../../../tests/mocks/playerContextMock';
5
+ jest.mock('./eventLogging', () => ({
6
+ logEvent: jest.fn()
7
+ }));
8
+ describe('bindPlayerEvents', () => {
9
+ let mockPlayer;
10
+ let playerContextMock;
11
+ beforeEach(() => {
12
+ mockPlayer = {
13
+ on: jest.fn(),
14
+ off: jest.fn(),
15
+ getViewMode: jest.fn().mockReturnValue(ViewMode.Inline)
16
+ };
17
+ playerContextMock = createPlayerContextMock({
18
+ npoPlayer: {
19
+ player: mockPlayer,
20
+ streamObject: {
21
+ stream: { isLiveStream: false }
22
+ },
23
+ adBreakActive: false,
24
+ logEmitter: { emit: jest.fn() }
25
+ }
26
+ });
27
+ jest.clearAllMocks();
28
+ });
29
+ it('should handle the "beforeunload" event correctly', () => {
30
+ const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
31
+ bindPlayerEvents(playerContextMock);
32
+ expect(addEventListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function));
33
+ const beforeUnloadHandler = addEventListenerSpy.mock.calls.find(([event]) => event === 'beforeunload')?.[1];
34
+ if (beforeUnloadHandler && typeof beforeUnloadHandler === 'function') {
35
+ beforeUnloadHandler({ time: 123 });
36
+ }
37
+ addEventListenerSpy.mockRestore();
38
+ });
39
+ it('should log events when handlers are triggered', () => {
40
+ bindPlayerEvents(playerContextMock);
41
+ const timeChangedHandler = mockPlayer.on.mock.calls.find(([event]) => event === PlayerEvent.TimeChanged)?.[1];
42
+ if (timeChangedHandler && typeof timeChangedHandler === 'function') {
43
+ timeChangedHandler({ time: 456 });
44
+ expect(logEvent).toHaveBeenCalledWith({
45
+ playerContext: playerContextMock,
46
+ event: 'start',
47
+ data: expect.objectContaining({ stream_position: expect.any(Number) })
48
+ });
49
+ }
50
+ });
51
+ it('should handle seek events correctly', () => {
52
+ bindPlayerEvents(playerContextMock);
53
+ const seekHandler = mockPlayer.on.mock.calls.find(([event]) => event === PlayerEvent.Seek)?.[1];
54
+ if (seekHandler && typeof seekHandler === 'function') {
55
+ seekHandler({
56
+ type: 'seek',
57
+ position: 100,
58
+ seekTarget: 200
59
+ });
60
+ expect(logEvent).toHaveBeenCalledWith({
61
+ playerContext: playerContextMock,
62
+ event: 'seek',
63
+ data: {
64
+ seek_from: 100,
65
+ stream_position: 200
66
+ }
67
+ });
68
+ }
69
+ });
70
+ it('should handle ViewModeChanged events', () => {
71
+ mockPlayer.getViewMode = jest.fn().mockReturnValueOnce(ViewMode.Fullscreen).mockReturnValueOnce(ViewMode.Inline);
72
+ bindPlayerEvents(playerContextMock);
73
+ const viewModeChangedHandler = mockPlayer.on.mock.calls.find(([event]) => event === PlayerEvent.ViewModeChanged)?.[1];
74
+ if (viewModeChangedHandler && typeof viewModeChangedHandler === 'function') {
75
+ viewModeChangedHandler({ time: 300 });
76
+ expect(logEvent).toHaveBeenCalledWith({
77
+ playerContext: playerContextMock,
78
+ event: 'fullscreen',
79
+ data: expect.objectContaining({ stream_position: expect.any(Number) })
80
+ });
81
+ viewModeChangedHandler({ time: 301 });
82
+ expect(logEvent).toHaveBeenCalledWith({
83
+ playerContext: playerContextMock,
84
+ event: 'windowed',
85
+ data: expect.objectContaining({ stream_position: expect.any(Number) })
86
+ });
87
+ }
88
+ });
89
+ });
@@ -0,0 +1,2 @@
1
+ import { LogEventParams } from 'types/interfaces';
2
+ export declare function logEvent({ playerContext, event, data }: LogEventParams): void;
@@ -1,8 +1,14 @@
1
- export function logEvent(npoPlayer, event, data) {
2
- if (!npoPlayer.player || !npoPlayer.streamTracker || npoPlayer.adBreakActive || npoPlayer.player.isCasting())
1
+ export function logEvent({ playerContext, event, data }) {
2
+ const { npoPlayer } = playerContext;
3
+ if (!npoPlayer.player)
3
4
  return;
4
- if (data === undefined || Object.keys(data).length === 0)
5
- data = { stream_position: 0 };
5
+ if (!npoPlayer.streamTracker) {
6
+ console.error('No streamTracker available, no tracking will be performed.');
7
+ return;
8
+ }
9
+ if (npoPlayer.adBreakActive || npoPlayer.player.isCasting())
10
+ return;
11
+ const eventData = data ?? { stream_position: 0 };
6
12
  const eventHandlers = {
7
13
  start: npoPlayer.streamTracker.start,
8
14
  buffering: npoPlayer.streamTracker.buffering,
@@ -17,10 +23,15 @@ export function logEvent(npoPlayer, event, data) {
17
23
  windowed: npoPlayer.streamTracker.windowed,
18
24
  time: npoPlayer.streamTracker.time,
19
25
  seek: () => {
20
- npoPlayer.streamTracker?.seek(data);
26
+ npoPlayer.streamTracker?.seek(eventData);
21
27
  }
22
28
  };
23
29
  const streamTrackerHandler = eventHandlers[event];
24
- npoPlayer.logEmitter.emit('logEvent', event, data);
25
- streamTrackerHandler(data);
30
+ npoPlayer.logEmitter.emit('logEvent', event, eventData);
31
+ if (streamTrackerHandler) {
32
+ streamTrackerHandler(eventData);
33
+ }
34
+ else {
35
+ console.warn(`No handler found for event: ${event}`);
36
+ }
26
37
  }
@@ -0,0 +1,63 @@
1
+ import { logEvent } from './eventLogging';
2
+ import { createPlayerContextMock, createMockNpoPlayerAPI } from '../../../tests/mocks/playerContextMock';
3
+ describe('logEvent', () => {
4
+ let playerContextMock;
5
+ beforeEach(() => {
6
+ playerContextMock = createPlayerContextMock({
7
+ npoPlayer: {
8
+ player: createMockNpoPlayerAPI(),
9
+ streamTracker: {
10
+ start: jest.fn(),
11
+ seek: jest.fn()
12
+ },
13
+ logEmitter: {
14
+ emit: jest.fn()
15
+ },
16
+ adBreakActive: false
17
+ }
18
+ });
19
+ });
20
+ it('should not log event if player is not available', () => {
21
+ playerContextMock.npoPlayer.player = undefined;
22
+ logEvent({ playerContext: playerContextMock, event: 'start' });
23
+ expect(playerContextMock.npoPlayer.streamTracker?.start).not.toHaveBeenCalled();
24
+ expect(playerContextMock.npoPlayer.logEmitter.emit).not.toHaveBeenCalled();
25
+ });
26
+ it('should not log event if streamTracker is not available', () => {
27
+ playerContextMock.npoPlayer.streamTracker = undefined;
28
+ logEvent({ playerContext: playerContextMock, event: 'start' });
29
+ expect(playerContextMock.npoPlayer.logEmitter.emit).not.toHaveBeenCalled();
30
+ });
31
+ it('should not log event if adBreakActive is true', () => {
32
+ playerContextMock.npoPlayer.adBreakActive = true;
33
+ logEvent({ playerContext: playerContextMock, event: 'start' });
34
+ expect(playerContextMock.npoPlayer.streamTracker?.start).not.toHaveBeenCalled();
35
+ expect(playerContextMock.npoPlayer.logEmitter.emit).not.toHaveBeenCalled();
36
+ });
37
+ it('should log event and call the corresponding streamTracker handler', () => {
38
+ logEvent({ playerContext: playerContextMock, event: 'start' });
39
+ expect(playerContextMock.npoPlayer.streamTracker?.start).toHaveBeenCalledWith({ stream_position: 0 });
40
+ expect(playerContextMock.npoPlayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'start', {
41
+ stream_position: 0
42
+ });
43
+ });
44
+ it('should log event and call the seek streamTracker handler with data', () => {
45
+ const data = { position: 20 };
46
+ logEvent({ playerContext: playerContextMock, event: 'seek', data });
47
+ expect(playerContextMock.npoPlayer.streamTracker?.seek).toHaveBeenCalledWith(data);
48
+ expect(playerContextMock.npoPlayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'seek', data);
49
+ });
50
+ it('should log event with default data if data is undefined', () => {
51
+ logEvent({ playerContext: playerContextMock, event: 'seek' });
52
+ expect(playerContextMock.npoPlayer.streamTracker?.seek).toHaveBeenCalledWith({ stream_position: 0 });
53
+ expect(playerContextMock.npoPlayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'seek', { stream_position: 0 });
54
+ });
55
+ it('should not throw but log a warning for unsupported events', () => {
56
+ console.warn = jest.fn();
57
+ logEvent({ playerContext: playerContextMock, event: 'unsupportedEvent' });
58
+ expect(console.warn).toHaveBeenCalledWith('No handler found for event: unsupportedEvent');
59
+ expect(playerContextMock.npoPlayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'unsupportedEvent', {
60
+ stream_position: 0
61
+ });
62
+ });
63
+ });
@@ -0,0 +1,3 @@
1
+ export { startPlayerTracker } from './playerTrackerStart';
2
+ export { initPlayerTracker } from './playerTrackerInit';
3
+ export { logEvent } from './eventLogging';
@@ -0,0 +1,3 @@
1
+ export { startPlayerTracker } from './playerTrackerStart';
2
+ export { initPlayerTracker } from './playerTrackerInit';
3
+ export { logEvent } from './eventLogging';
@@ -0,0 +1,2 @@
1
+ import { PlayerContext } from 'types/interfaces';
2
+ export declare function initPlayerTracker(playerContext: PlayerContext): void;
@@ -1,6 +1,7 @@
1
1
  import { newATInternetPlugin, newGovoltePlugin, newPageTracker, newTag } from '@npotag/tag';
2
2
  const npoTagPlugins = [newGovoltePlugin({ maxRetryCount: 5, delayBetweenRetriesInMs: 3000 }), newATInternetPlugin()];
3
- export function initPlayerTracker(npoPlayer, _npoTag, _npoTagInstance, _npoTagPageTracker) {
3
+ export function initPlayerTracker(playerContext) {
4
+ const { npoPlayer } = playerContext;
4
5
  if (!npoPlayer.npoTag) {
5
6
  npoPlayer.npoTag = {
6
7
  npoTagInstance: undefined,
@@ -9,9 +10,11 @@ export function initPlayerTracker(npoPlayer, _npoTag, _npoTagInstance, _npoTagPa
9
10
  };
10
11
  }
11
12
  if (!npoPlayer.npoTag?.npoTagInstance) {
12
- npoPlayer.npoTag.npoTagInstance = _npoTagInstance ?? (_npoTag ? newTag(_npoTag, npoTagPlugins) : undefined);
13
+ npoPlayer.npoTag.npoTagInstance =
14
+ npoPlayer.npoTagInstance ??
15
+ (npoPlayer.npoTagInitialisation ? newTag(npoPlayer.npoTagInitialisation, npoTagPlugins) : undefined);
13
16
  }
14
17
  if (!npoPlayer.npoTag?.pageTracker && npoPlayer.npoTag?.npoTagInstance) {
15
- npoPlayer.npoTag.pageTracker = _npoTagPageTracker ?? newPageTracker(npoPlayer.npoTag.npoTagInstance);
18
+ npoPlayer.npoTag.pageTracker = npoPlayer.npoTagPageTracker ?? newPageTracker(npoPlayer.npoTag.npoTagInstance);
16
19
  }
17
20
  }
@@ -0,0 +1,75 @@
1
+ import { newPageTracker, newTag } from '@npotag/tag';
2
+ import { initPlayerTracker } from './playerTrackerInit';
3
+ import { createPlayerContextMock } from '../../../tests/mocks/playerContextMock';
4
+ jest.mock('@npotag/tag', () => ({
5
+ newATInternetPlugin: jest.fn(),
6
+ newGovoltePlugin: jest.fn(),
7
+ newPageTracker: jest.fn(),
8
+ newTag: jest.fn()
9
+ }));
10
+ describe('initPlayerTracker', () => {
11
+ let playerContextMock;
12
+ beforeEach(() => {
13
+ playerContextMock = createPlayerContextMock({
14
+ npoPlayer: {
15
+ npoTag: undefined,
16
+ npoTagInitialisation: undefined,
17
+ npoTagInstance: undefined,
18
+ npoTagPageTracker: undefined
19
+ }
20
+ });
21
+ jest.clearAllMocks();
22
+ });
23
+ it('should initialize npoTag if it is undefined', () => {
24
+ initPlayerTracker(playerContextMock);
25
+ expect(playerContextMock.npoPlayer.npoTag).toBeDefined();
26
+ expect(playerContextMock.npoPlayer.npoTag?.npoTagInstance).toBeUndefined();
27
+ expect(playerContextMock.npoPlayer.npoTag?.pageTracker).toBeUndefined();
28
+ });
29
+ it('should initialize npoTagInstance if npoTagInstance is already provided', () => {
30
+ const mockNpoTagInstance = {};
31
+ playerContextMock.npoPlayer.npoTagInstance = mockNpoTagInstance;
32
+ initPlayerTracker(playerContextMock);
33
+ expect(playerContextMock.npoPlayer.npoTag?.npoTagInstance).toBe(mockNpoTagInstance);
34
+ expect(playerContextMock.npoPlayer.npoTag?.pageTracker).toBeUndefined();
35
+ });
36
+ it('should create a new npoTagInstance if npoTagInitialisation is provided and npoTagInstance is undefined', () => {
37
+ const mockNpoTag = {};
38
+ const mockNewTagInstance = {};
39
+ newTag.mockReturnValue(mockNewTagInstance);
40
+ playerContextMock.npoPlayer.npoTagInitialisation = mockNpoTag;
41
+ initPlayerTracker(playerContextMock);
42
+ expect(newTag).toHaveBeenCalledWith(mockNpoTag, expect.any(Array));
43
+ expect(playerContextMock.npoPlayer.npoTag?.npoTagInstance).toBe(mockNewTagInstance);
44
+ expect(playerContextMock.npoPlayer.npoTag?.pageTracker).toBeUndefined();
45
+ });
46
+ it('should initialize pageTracker if npoTagInstance is present and npoTagPageTracker is provided', () => {
47
+ const mockPageTracker = {};
48
+ const mockNpoTagInstance = {};
49
+ playerContextMock.npoPlayer.npoTagInstance = mockNpoTagInstance;
50
+ playerContextMock.npoPlayer.npoTagPageTracker = mockPageTracker;
51
+ initPlayerTracker(playerContextMock);
52
+ expect(playerContextMock.npoPlayer.npoTag?.pageTracker).toBe(mockPageTracker);
53
+ });
54
+ it('should create a new pageTracker if npoTagPageTracker is undefined', () => {
55
+ const mockNpoTagInstance = {};
56
+ const mockNewPageTracker = {};
57
+ newPageTracker.mockReturnValue(mockNewPageTracker);
58
+ playerContextMock.npoPlayer.npoTagInstance = mockNpoTagInstance;
59
+ initPlayerTracker(playerContextMock);
60
+ expect(newPageTracker).toHaveBeenCalledWith(mockNpoTagInstance);
61
+ expect(playerContextMock.npoPlayer.npoTag?.pageTracker).toBe(mockNewPageTracker);
62
+ });
63
+ it('should not overwrite existing pageTracker', () => {
64
+ const mockPageTracker = {};
65
+ const mockNpoTagInstance = {};
66
+ playerContextMock.npoPlayer.npoTag = {
67
+ npoTagInstance: mockNpoTagInstance,
68
+ pageTracker: mockPageTracker,
69
+ heartbeatInterval: undefined
70
+ };
71
+ initPlayerTracker(playerContextMock);
72
+ expect(playerContextMock.npoPlayer.npoTag?.pageTracker).toBe(mockPageTracker);
73
+ expect(newPageTracker).not.toHaveBeenCalled();
74
+ });
75
+ });
@@ -0,0 +1,2 @@
1
+ import { PlayerTrackerParams } from 'types/interfaces';
2
+ export declare const startPlayerTracker: ({ playerContext, source, duration }: PlayerTrackerParams) => void;
@@ -0,0 +1,25 @@
1
+ import { getAnalyticsPrid } from '../../js/utilities/utilities.prid';
2
+ import { getStreamDurationInSeconds } from '../../js/utilities/utilities.stream';
3
+ import { initStreamTracker } from './streamTrackerInit';
4
+ import { bindPlayerEvents } from './eventBinding';
5
+ import { logEvent } from './eventLogging';
6
+ export const startPlayerTracker = function ({ playerContext, source, duration }) {
7
+ const { npoPlayer } = playerContext;
8
+ const { player, sourceConfig, streamObject } = npoPlayer;
9
+ const playerDuration = duration || player?.getDuration() || undefined;
10
+ if (!source) {
11
+ throw new Error('Could not initialize streamTracker, no source specified.');
12
+ }
13
+ const analyticsPrid = getAnalyticsPrid({
14
+ source,
15
+ sourceConfig,
16
+ streamObject
17
+ }) || 'unknown';
18
+ npoPlayer.streamTracker = initStreamTracker({
19
+ playerContext: playerContext,
20
+ duration: getStreamDurationInSeconds({ duration: playerDuration }),
21
+ source: analyticsPrid
22
+ });
23
+ bindPlayerEvents(playerContext);
24
+ logEvent({ playerContext, event: 'load' });
25
+ };
@@ -0,0 +1,59 @@
1
+ import { getAnalyticsPrid } from '../../js/utilities/utilities.prid';
2
+ import { getStreamDurationInSeconds } from '../../js/utilities/utilities.stream';
3
+ import { startPlayerTracker } from './playerTrackerStart';
4
+ import { initStreamTracker } from './streamTrackerInit';
5
+ import { bindPlayerEvents } from './eventBinding';
6
+ import { logEvent } from './eventLogging';
7
+ import { createPlayerContextMock } from '../../../tests/mocks/playerContextMock';
8
+ jest.mock('../../js/utilities/utilities.stream', () => ({
9
+ getStreamDurationInSeconds: jest.fn()
10
+ }));
11
+ jest.mock('../../js/utilities/utilities.prid', () => ({
12
+ getAnalyticsPrid: jest.fn()
13
+ }));
14
+ jest.mock('./streamTrackerInit', () => ({
15
+ initStreamTracker: jest.fn()
16
+ }));
17
+ jest.mock('./eventBinding', () => ({
18
+ bindPlayerEvents: jest.fn()
19
+ }));
20
+ jest.mock('./eventLogging', () => ({
21
+ logEvent: jest.fn()
22
+ }));
23
+ describe('startPlayerTracker', () => {
24
+ let playerContextMock;
25
+ beforeEach(() => {
26
+ playerContextMock = createPlayerContextMock({
27
+ npoPlayer: {
28
+ npoTag: undefined,
29
+ npoTagInitialisation: undefined,
30
+ npoTagInstance: undefined,
31
+ npoTagPageTracker: undefined
32
+ }
33
+ });
34
+ jest.clearAllMocks();
35
+ });
36
+ it('should initialize the stream tracker with correct parameters', () => {
37
+ const source = 'some-source';
38
+ const duration = 150;
39
+ getAnalyticsPrid.mockReturnValue('some-prid');
40
+ getStreamDurationInSeconds.mockReturnValue(duration);
41
+ startPlayerTracker({
42
+ playerContext: playerContextMock,
43
+ source,
44
+ duration
45
+ });
46
+ expect(getAnalyticsPrid).toHaveBeenCalledWith({
47
+ source,
48
+ sourceConfig: undefined,
49
+ streamObject: undefined
50
+ });
51
+ expect(initStreamTracker).toHaveBeenCalledWith({
52
+ playerContext: playerContextMock,
53
+ duration,
54
+ source: 'some-prid'
55
+ });
56
+ expect(bindPlayerEvents).toHaveBeenCalledWith(playerContextMock);
57
+ expect(logEvent).toHaveBeenCalledWith({ playerContext: playerContextMock, event: 'load' });
58
+ });
59
+ });
@@ -0,0 +1,3 @@
1
+ import { StreamTracker } from '@npotag/tag';
2
+ import { PlayerTrackerParams } from 'types/interfaces';
3
+ export declare function initStreamTracker({ playerContext, duration, source }: PlayerTrackerParams): StreamTracker;
@@ -0,0 +1,27 @@
1
+ import { newStreamTracker } from '@npotag/tag';
2
+ import { validatePrid } from '../../js/utilities/utilities.stream';
3
+ export function initStreamTracker({ playerContext, duration, source }) {
4
+ const { npoPlayer } = playerContext;
5
+ const { npoTag, npoPlayerServices, version } = npoPlayer;
6
+ if (!npoTag?.npoTagInstance) {
7
+ throw new Error('No npoTagInstance available. Could not create streamtracker.');
8
+ }
9
+ if (!npoTag?.pageTracker) {
10
+ throw new Error('No pageTracker available. Could not create streamtracker.');
11
+ }
12
+ if (!duration) {
13
+ throw new Error('No duration specified. Could not create streamtracker.');
14
+ }
15
+ const streamTrackerConfig = {
16
+ stream_length: duration,
17
+ stream_id: validatePrid(source),
18
+ player_id: 'npoplayer-web',
19
+ av_type: npoPlayerServices.getAVType(npoPlayer.streamObject.stream.avType),
20
+ player_version: version,
21
+ sko_player_version: '1.0.0'
22
+ };
23
+ const isLiveStream = npoPlayer.streamObject.stream.isLiveStream || false;
24
+ return newStreamTracker(npoTag.pageTracker, streamTrackerConfig, {
25
+ isLive: isLiveStream
26
+ });
27
+ }