@npo/player 1.23.2 → 1.24.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 (140) hide show
  1. package/README.md +1 -1
  2. package/lib/js/playeractions/handlers/error.js +2 -2
  3. package/lib/js/playeractions/handlers/error.test.js +2 -2
  4. package/lib/js/playeractions/handlers/handleoffsets.d.ts +4 -6
  5. package/lib/js/playeractions/handlers/handleoffsets.js +16 -17
  6. package/lib/js/playeractions/handlers/handleoffsets.test.js +73 -29
  7. package/lib/js/playeractions/handlers/mediasessionactions.js +24 -12
  8. package/lib/js/playeractions/handlers/processsourceconfig.js +16 -3
  9. package/lib/js/tracking/handlers/eventbinding.d.ts +1 -1
  10. package/lib/js/tracking/handlers/eventbinding.js +17 -23
  11. package/lib/js/tracking/handlers/eventlogging.d.ts +1 -1
  12. package/lib/js/tracking/handlers/eventlogging.js +18 -18
  13. package/lib/js/tracking/handlers/eventlogging.test.js +24 -24
  14. package/lib/js/tracking/handlers/playertrackerstart.js +4 -2
  15. package/lib/lang/nl.json +3 -1
  16. package/lib/npoplayer.d.ts +1 -2
  17. package/lib/npoplayer.js +25 -43
  18. package/lib/package.json +5 -5
  19. package/lib/services/a11y/setup.js +2 -2
  20. package/lib/services/a11y/setup.test.js +7 -8
  21. package/lib/services/advertHandlers/handlePreRolls.d.ts +2 -0
  22. package/lib/services/advertHandlers/handlePreRolls.js +132 -0
  23. package/lib/services/advertHandlers/handlePrerolls.test.js +52 -0
  24. package/lib/services/cdnProviders/cdnProviders.d.ts +4 -0
  25. package/lib/services/cdnProviders/cdnProviders.js +22 -0
  26. package/lib/services/cdnProviders/cndProviders.test.js +22 -0
  27. package/lib/services/eventListenerHandlers/removeEventListeners.d.ts +1 -1
  28. package/lib/services/eventListenerHandlers/removeEventListeners.js +4 -4
  29. package/lib/services/eventListenerHandlers/removeEventListeners.test.js +28 -13
  30. package/lib/services/liveStreamHandlers/handleLiveStreamControls.d.ts +1 -1
  31. package/lib/services/liveStreamHandlers/handleLiveStreamControls.js +3 -3
  32. package/lib/services/liveStreamHandlers/handleLiveStreamControls.test.d.ts +2 -0
  33. package/lib/services/liveStreamHandlers/handleLiveStreamControls.test.js +65 -0
  34. package/lib/services/localStorageHandlers/localStorageHandlers.d.ts +1 -1
  35. package/lib/services/localStorageHandlers/localStorageHandlers.test.js +0 -1
  36. package/lib/services/npoPlayerAPI/contants.d.ts +5 -0
  37. package/lib/services/npoPlayerAPI/contants.js +5 -0
  38. package/lib/services/npoPlayerAPI/npoPlayerAPI.d.ts +16 -4
  39. package/lib/services/npoPlayerAPI/npoPlayerAPI.js +65 -3
  40. package/lib/services/npoPlayerAPI/npoPlayerAPI.test.js +66 -1
  41. package/lib/services/segmentHandlers/addSegmentEventListeners.js +3 -3
  42. package/lib/services/segmentHandlers/addSegmentEventListeners.test.js +10 -9
  43. package/lib/services/segmentHandlers/convertFragmentToSegment.d.ts +1 -1
  44. package/lib/services/segmentHandlers/handleSegmentSeek.d.ts +1 -1
  45. package/lib/services/segmentHandlers/handleSegmentSeek.test.js +4 -4
  46. package/lib/services/segmentHandlers/handleSegmentTimeChanged.d.ts +1 -1
  47. package/lib/services/segmentHandlers/handleSegmentTimeChanged.test.js +33 -24
  48. package/lib/services/segmentHandlers/initSegment.d.ts +1 -1
  49. package/lib/services/segmentHandlers/initSegment.test.js +3 -1
  50. package/lib/services/segmentHandlers/setSegmentMarkers.d.ts +1 -1
  51. package/lib/services/services.d.ts +3 -1
  52. package/lib/services/services.js +8 -0
  53. package/lib/services/verticalVideoHandlers/handleVerticalVideoControls.d.ts +2 -0
  54. package/lib/services/verticalVideoHandlers/handleVerticalVideoControls.js +8 -0
  55. package/lib/services/verticalVideoHandlers/handleVerticalVideoControls.test.d.ts +1 -0
  56. package/lib/services/verticalVideoHandlers/handleVerticalVideoControls.test.js +36 -0
  57. package/lib/src/js/playeractions/handlers/handleoffsets.d.ts +4 -6
  58. package/lib/src/js/tracking/handlers/eventbinding.d.ts +1 -1
  59. package/lib/src/js/tracking/handlers/eventlogging.d.ts +1 -1
  60. package/lib/src/npoplayer.d.ts +1 -2
  61. package/lib/src/services/advertHandlers/handlePreRolls.d.ts +2 -0
  62. package/lib/src/services/advertHandlers/handlePrerolls.test.d.ts +1 -0
  63. package/lib/src/services/cdnProviders/cdnProviders.d.ts +4 -0
  64. package/lib/src/services/cdnProviders/cndProviders.test.d.ts +1 -0
  65. package/lib/src/services/eventListenerHandlers/removeEventListeners.d.ts +1 -1
  66. package/lib/src/services/liveStreamHandlers/handleLiveStreamControls.d.ts +1 -1
  67. package/lib/src/services/liveStreamHandlers/handleLiveStreamControls.test.d.ts +2 -0
  68. package/lib/src/services/localStorageHandlers/localStorageHandlers.d.ts +1 -1
  69. package/lib/src/services/npoPlayerAPI/contants.d.ts +5 -0
  70. package/lib/src/services/npoPlayerAPI/npoPlayerAPI.d.ts +16 -4
  71. package/lib/src/services/segmentHandlers/convertFragmentToSegment.d.ts +1 -1
  72. package/lib/src/services/segmentHandlers/handleSegmentSeek.d.ts +1 -1
  73. package/lib/src/services/segmentHandlers/handleSegmentTimeChanged.d.ts +1 -1
  74. package/lib/src/services/segmentHandlers/initSegment.d.ts +1 -1
  75. package/lib/src/services/segmentHandlers/setSegmentMarkers.d.ts +1 -1
  76. package/lib/src/services/services.d.ts +3 -1
  77. package/lib/src/services/verticalVideoHandlers/handleVerticalVideoControls.d.ts +2 -0
  78. package/lib/src/services/verticalVideoHandlers/handleVerticalVideoControls.test.d.ts +1 -0
  79. package/lib/src/types/events.d.ts +8 -1
  80. package/lib/src/types/interfaces.d.ts +8 -12
  81. package/lib/src/ui/components/audio/controlbar.d.ts +1 -1
  82. package/lib/src/ui/components/controlbar.d.ts +1 -1
  83. package/lib/src/ui/components/seekbar.d.ts +1 -1
  84. package/lib/src/ui/components/settingspanel.d.ts +1 -1
  85. package/lib/src/ui/components/verticalvideo/controlbar.d.ts +3 -0
  86. package/lib/src/ui/handlers/streamhandler.d.ts +2 -4
  87. package/lib/src/ui/uicontainer.d.ts +2 -2
  88. package/lib/tests/mocks/mockNpoplayer.js +0 -1
  89. package/lib/tests/mocks/playerContextMock.d.ts +67 -0
  90. package/lib/tests/mocks/playerContextMock.js +117 -0
  91. package/lib/types/events.d.ts +8 -1
  92. package/lib/types/events.js +184 -1
  93. package/lib/types/interfaces.d.ts +8 -12
  94. package/lib/types/interfaces.js +1 -0
  95. package/lib/ui/components/audio/controlbar.d.ts +1 -1
  96. package/lib/ui/components/audio/controlbar.js +6 -6
  97. package/lib/ui/components/controlbar.d.ts +1 -1
  98. package/lib/ui/components/controlbar.js +13 -11
  99. package/lib/ui/components/nativemobile/controlbar.js +0 -1
  100. package/lib/ui/components/seekbar.js +1 -1
  101. package/lib/ui/components/settingspanel.d.ts +1 -1
  102. package/lib/ui/components/settingspanel.js +68 -84
  103. package/lib/ui/components/topbar.js +6 -3
  104. package/lib/ui/components/verticalvideo/controlbar.d.ts +3 -0
  105. package/lib/ui/components/verticalvideo/controlbar.js +34 -0
  106. package/lib/ui/handlers/streamhandler.d.ts +2 -4
  107. package/lib/ui/handlers/streamhandler.js +16 -7
  108. package/lib/ui/nativemobileuifactory.js +2 -0
  109. package/lib/ui/uicontainer.d.ts +2 -2
  110. package/lib/ui/uicontainer.js +35 -29
  111. package/package.json +5 -5
  112. package/src/style/components/_settingspanel.scss +35 -3
  113. package/src/style/components/_subtitles.scss +29 -25
  114. package/src/style/components/_textbuttons.scss +2 -2
  115. package/src/style/components/_volumeslider.scss +1 -0
  116. package/src/style/components/vertical-video/_bottombar.scss +19 -0
  117. package/src/style/components/vertical-video/_buttons.scss +23 -0
  118. package/src/style/components/vertical-video/_hugeplaybacktogglebutton.scss +14 -0
  119. package/src/style/components/vertical-video/_seekbar.scss +19 -0
  120. package/src/style/components/vertical-video/_settingsbutton.scss +7 -0
  121. package/src/style/components/vertical-video/_settingspanel.scss +14 -0
  122. package/src/style/components/vertical-video/_shortvideo.scss +14 -0
  123. package/src/style/components/vertical-video/_subtitles.scss +3 -0
  124. package/src/style/components/vertical-video/_topbar.scss +17 -0
  125. package/src/style/components/vertical-video/_volumeslider.scss +9 -0
  126. package/src/style/npoplayer.css +74 -34
  127. package/src/style/npoplayer.scss +2 -1
  128. package/src/style/variants/_player-small.scss +18 -10
  129. package/src/style/variants/_player-vertical.scss +23 -0
  130. package/lib/js/ads/ster.d.ts +0 -4
  131. package/lib/js/ads/ster.js +0 -126
  132. package/lib/js/ads/ster.test.js +0 -63
  133. package/lib/js/cdnproviders.d.ts +0 -1
  134. package/lib/js/cdnproviders.js +0 -16
  135. package/lib/src/js/ads/ster.d.ts +0 -4
  136. package/lib/src/js/cdnproviders.d.ts +0 -1
  137. package/lib/tests/mocks/mockPlayerContext.d.ts +0 -2
  138. package/lib/tests/mocks/mockPlayerContext.js +0 -40
  139. /package/lib/{js/ads/ster.test.d.ts → services/advertHandlers/handlePrerolls.test.d.ts} +0 -0
  140. /package/lib/{src/js/ads/ster.test.d.ts → services/cdnProviders/cndProviders.test.d.ts} +0 -0
@@ -0,0 +1,65 @@
1
+ import '@testing-library/jest-dom';
2
+ import { NpoPlayerEvent } from '../../types/events';
3
+ import { handleLiveStreamControls, updateForwardButtonState, toggleForwardButtons } from './handleLiveStreamControls';
4
+ import createPlayerContextMock from '../../../tests/mocks/playerContextMock';
5
+ export const TIME_SHIFT_THRESHOLD = -10;
6
+ describe('handleLiveStreamControls', () => {
7
+ let mockPlayerContext;
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ mockPlayerContext = createPlayerContextMock();
11
+ });
12
+ it('should add TimeChanged event listener and store it in eventListeners', () => {
13
+ handleLiveStreamControls(mockPlayerContext);
14
+ expect(mockPlayerContext.player.on).toHaveBeenCalledWith(NpoPlayerEvent.TimeChanged, mockPlayerContext.npoplayer.eventListeners?.liveStreamHandleTimeChangedCallback);
15
+ expect(mockPlayerContext.npoplayer.eventListeners?.liveStreamHandleTimeChangedCallback).toBeDefined();
16
+ });
17
+ });
18
+ describe('updateForwardButtonState', () => {
19
+ let mockPlayerContext;
20
+ let mockForwardButtons;
21
+ beforeEach(() => {
22
+ jest.clearAllMocks();
23
+ mockPlayerContext = createPlayerContextMock();
24
+ document.body.innerHTML = `
25
+ <div class="bmpui-ui-forwardbutton"></div>
26
+ <div class="bmpui-ui-forwardbutton"></div>
27
+ `;
28
+ mockForwardButtons = document.querySelectorAll('.bmpui-ui-forwardbutton');
29
+ jest.spyOn(mockPlayerContext.player, 'getTimeShift').mockReturnValue(TIME_SHIFT_THRESHOLD - 5);
30
+ jest.spyOn(mockPlayerContext.player, 'getContainer').mockReturnValue({
31
+ querySelectorAll: jest.fn().mockReturnValue(mockForwardButtons)
32
+ });
33
+ });
34
+ it('should update forward buttons state based on timeShift', () => {
35
+ updateForwardButtonState(mockPlayerContext.player);
36
+ expect(mockPlayerContext.player.off).toHaveBeenCalledWith(NpoPlayerEvent.TimeChanged, expect.any(Function));
37
+ for (const forwardButton of mockForwardButtons) {
38
+ expect(forwardButton).toBeEnabled();
39
+ }
40
+ });
41
+ });
42
+ describe('toggleForwardButtons', () => {
43
+ let mockForwardButtons;
44
+ beforeEach(() => {
45
+ document.body.innerHTML = `
46
+ <div class="bmpui-ui-forwardbutton"></div>
47
+ <div class="bmpui-ui-forwardbutton"></div>
48
+ `;
49
+ mockForwardButtons = document.querySelectorAll('.bmpui-ui-forwardbutton');
50
+ });
51
+ it('should disable forward buttons if timeShift > TIME_SHIFT_THRESHOLD', () => {
52
+ toggleForwardButtons(mockForwardButtons, TIME_SHIFT_THRESHOLD + 5);
53
+ for (const forwardButton of mockForwardButtons) {
54
+ expect(forwardButton).toHaveAttribute('disabled', 'true');
55
+ }
56
+ });
57
+ it('should enable forward buttons if timeShift <= TIME_SHIFT_THRESHOLD', () => {
58
+ for (const button of mockForwardButtons)
59
+ button.setAttribute('disabled', 'true');
60
+ toggleForwardButtons(mockForwardButtons, TIME_SHIFT_THRESHOLD - 5);
61
+ for (const forwardButton of mockForwardButtons) {
62
+ expect(forwardButton).toBeEnabled();
63
+ }
64
+ });
65
+ });
@@ -1,4 +1,4 @@
1
- import { LocalStorageData, LocalStorageValues, PlayerContext } from 'types/interfaces';
1
+ import { LocalStorageData, LocalStorageValues, PlayerContext } from '../../types/interfaces';
2
2
  export declare function setLocalStorage(key: LocalStorageValues, value: any): void;
3
3
  export declare function getLocalStorage(): LocalStorageData;
4
4
  export declare function setValuesBasedOnLocalStorage(playerContext: PlayerContext): void;
@@ -67,7 +67,6 @@ describe('localStorageHandler', () => {
67
67
  playerConfig: {},
68
68
  initPlayer: () => { },
69
69
  loadStream: () => Promise.resolve(),
70
- createUIManager: async () => { },
71
70
  doError: () => { },
72
71
  play: async () => { },
73
72
  pause: () => { },
@@ -0,0 +1,5 @@
1
+ export declare const DEFAULT_UI_DELAY = 3000;
2
+ export declare const PREVENT_UI_DELAY = -1;
3
+ export declare const VOLUME_STEP = 10;
4
+ export declare const VOLUME_MIN = 0;
5
+ export declare const VOLUME_MAX = 100;
@@ -0,0 +1,5 @@
1
+ export const DEFAULT_UI_DELAY = 3000;
2
+ export const PREVENT_UI_DELAY = -1;
3
+ export const VOLUME_STEP = 10;
4
+ export const VOLUME_MIN = 0;
5
+ export const VOLUME_MAX = 100;
@@ -1,5 +1,7 @@
1
- import { PlayerAPI, PlayerEvent, PlayerEventCallback, SourceConfig, TimeMode, ViewMode } from 'bitmovin-player';
2
- import { Technology } from 'types/interfaces';
1
+ import { AdBreak, AdConfig, PlayerAPI, PlayerConfig, SourceConfig, TimeMode, ViewMode } from 'bitmovin-player';
2
+ import { UIManager } from 'bitmovin-player-ui';
3
+ import { NpoPlayerUIVariants, PlayerContext, Technology } from 'types/interfaces';
4
+ import { NpoPlayerEventCallback, NpoPlayerEvent } from '../../types/events';
3
5
  export declare class NpoPlayerAPI {
4
6
  playerAPI: PlayerAPI;
5
7
  constructor(playerAPI: PlayerAPI);
@@ -10,21 +12,31 @@ export declare class NpoPlayerAPI {
10
12
  unmute(): void;
11
13
  getVolume(): number;
12
14
  setVolume(level: number): void;
15
+ increaseVolume(): void;
16
+ decreaseVolume(): void;
13
17
  setViewMode(viewMode: ViewMode): void;
14
18
  getViewMode(): ViewMode;
15
19
  areSubtitlesEnabled(): boolean;
16
20
  enableSubtitles(): void;
17
21
  isPaused(): boolean;
18
22
  isMuted(): boolean;
23
+ isLive(): boolean;
19
24
  getSupportedDRM(): Promise<string[]>;
20
25
  getSupportedTech(): Technology[];
21
- off(eventType: PlayerEvent, callback: PlayerEventCallback): void;
22
- on(eventType: PlayerEvent, callback: PlayerEventCallback): void;
26
+ off(eventType: NpoPlayerEvent, callback: NpoPlayerEventCallback): void;
27
+ on(eventType: NpoPlayerEvent, callback: NpoPlayerEventCallback): void;
23
28
  seek(time: number): void;
29
+ timeShift(time: number): void;
24
30
  getContainer(): HTMLElement;
25
31
  getNpoPlayerElement(): Element | null;
26
32
  addClassToNpoPlayerElement(className: string): void | undefined;
27
33
  removeClassFromNpoPlayerElement(className: string): void | undefined;
34
+ toggleClassOnNpoPlayerElement(className: string, toggle: boolean): void | undefined;
28
35
  getCurrentTime(mode?: TimeMode | undefined): number;
36
+ getDuration(): number;
29
37
  getTimeShift(): number;
38
+ scheduleAds(adConfig: AdConfig): Promise<AdBreak[]>;
39
+ getActiveAdBreak(): AdBreak | null;
40
+ getConfig(mergedConfig?: boolean): PlayerConfig;
41
+ createUIManager(playerContext: PlayerContext, variant: NpoPlayerUIVariants): Promise<void | UIManager>;
30
42
  }
@@ -1,3 +1,10 @@
1
+ import { UIManager } from 'bitmovin-player-ui';
2
+ import { customSpecificErrorMessageOverlayConfig } from '../../js/playeractions/handlers/customerrors';
3
+ import { processStream } from '../../ui/handlers/streamhandler';
4
+ import { createUIContainer } from '../../ui/uicontainer';
5
+ import { playerEventMap } from '../../types/events';
6
+ import { VOLUME_MAX, VOLUME_MIN, VOLUME_STEP } from './contants';
7
+ const externalCallbacksMap = new WeakMap();
1
8
  export class NpoPlayerAPI {
2
9
  constructor(playerAPI) {
3
10
  this.playerAPI = playerAPI;
@@ -21,7 +28,16 @@ export class NpoPlayerAPI {
21
28
  return this.playerAPI.getVolume();
22
29
  }
23
30
  setVolume(level) {
24
- this.playerAPI.setVolume(level);
31
+ const volume = Math.max(VOLUME_MIN, Math.min(VOLUME_MAX, level));
32
+ this.playerAPI.setVolume(volume);
33
+ }
34
+ increaseVolume() {
35
+ const currentVolume = this.getVolume();
36
+ this.setVolume(currentVolume + VOLUME_STEP);
37
+ }
38
+ decreaseVolume() {
39
+ const currentVolume = this.getVolume();
40
+ this.setVolume(currentVolume - VOLUME_STEP);
25
41
  }
26
42
  setViewMode(viewMode) {
27
43
  this.playerAPI.setViewMode(viewMode);
@@ -55,6 +71,9 @@ export class NpoPlayerAPI {
55
71
  isMuted() {
56
72
  return this.playerAPI.isMuted();
57
73
  }
74
+ isLive() {
75
+ return this.playerAPI.isLive();
76
+ }
58
77
  async getSupportedDRM() {
59
78
  return (await this.playerAPI.getSupportedDRM());
60
79
  }
@@ -62,14 +81,30 @@ export class NpoPlayerAPI {
62
81
  return this.playerAPI.getSupportedTech();
63
82
  }
64
83
  off(eventType, callback) {
65
- this.playerAPI.off(eventType, callback);
84
+ const externalEventType = playerEventMap[eventType];
85
+ const externalCallback = externalCallbacksMap.get(callback);
86
+ if (externalCallback) {
87
+ this.playerAPI.off(externalEventType, externalCallback);
88
+ externalCallbacksMap.delete(callback);
89
+ }
66
90
  }
67
91
  on(eventType, callback) {
68
- this.playerAPI.on(eventType, callback);
92
+ const externalEventType = playerEventMap[eventType];
93
+ let externalCallback = externalCallbacksMap.get(callback);
94
+ if (!externalCallback) {
95
+ externalCallback = (args) => {
96
+ callback(args);
97
+ };
98
+ externalCallbacksMap.set(callback, externalCallback);
99
+ }
100
+ this.playerAPI.on(externalEventType, externalCallback);
69
101
  }
70
102
  seek(time) {
71
103
  this.playerAPI.seek(time);
72
104
  }
105
+ timeShift(time) {
106
+ this.playerAPI.timeShift(time);
107
+ }
73
108
  getContainer() {
74
109
  return this.playerAPI.getContainer();
75
110
  }
@@ -82,10 +117,37 @@ export class NpoPlayerAPI {
82
117
  removeClassFromNpoPlayerElement(className) {
83
118
  this.getNpoPlayerElement()?.classList.remove(className);
84
119
  }
120
+ toggleClassOnNpoPlayerElement(className, toggle) {
121
+ this.getNpoPlayerElement()?.classList.toggle(className, toggle);
122
+ }
85
123
  getCurrentTime(mode) {
86
124
  return this.playerAPI.getCurrentTime(mode);
87
125
  }
126
+ getDuration() {
127
+ return this.playerAPI.getDuration();
128
+ }
88
129
  getTimeShift() {
89
130
  return this.playerAPI.getTimeShift();
90
131
  }
132
+ async scheduleAds(adConfig) {
133
+ return this.playerAPI.ads.schedule(adConfig);
134
+ }
135
+ getActiveAdBreak() {
136
+ return this.playerAPI.ads.getActiveAdBreak();
137
+ }
138
+ getConfig(mergedConfig) {
139
+ return this.playerAPI.getConfig(mergedConfig);
140
+ }
141
+ async createUIManager(playerContext, variant) {
142
+ if (playerContext.npoplayer.uiManager === undefined || variant !== playerContext.npoplayer.variant) {
143
+ const uiConfig = {
144
+ errorMessages: customSpecificErrorMessageOverlayConfig,
145
+ disableAutoHideWhenHovered: true,
146
+ seekbarSnappingEnabled: false
147
+ };
148
+ playerContext.npoplayer.uiManager = new UIManager(this.playerAPI, createUIContainer(playerContext.npoplayer, this.playerAPI, variant, playerContext.npoplayer.container), uiConfig);
149
+ playerContext.npoplayer.variant = variant;
150
+ }
151
+ processStream(playerContext);
152
+ }
91
153
  }
@@ -1,4 +1,6 @@
1
1
  import { NpoPlayerAPI } from './npoPlayerAPI';
2
+ import createPlayerContextMock from '../../../tests/mocks/playerContextMock';
3
+ import { NpoPlayerEvent } from '../../types/events';
2
4
  describe('NpoPlayerAPI', () => {
3
5
  let mockPlayerAPI;
4
6
  let npoPlayerAPI;
@@ -11,7 +13,9 @@ describe('NpoPlayerAPI', () => {
11
13
  setViewMode: jest.fn(),
12
14
  getViewMode: jest.fn(),
13
15
  isPaused: jest.fn(),
14
- isMuted: jest.fn()
16
+ isMuted: jest.fn(),
17
+ on: jest.fn(),
18
+ off: jest.fn()
15
19
  };
16
20
  npoPlayerAPI = new NpoPlayerAPI(mockPlayerAPI);
17
21
  });
@@ -48,4 +52,65 @@ describe('NpoPlayerAPI', () => {
48
52
  npoPlayerAPI.isMuted();
49
53
  expect(mockPlayerAPI.isMuted).toHaveBeenCalled();
50
54
  });
55
+ describe('NpoPlayerAPI Volume Control Tests', () => {
56
+ let playerContextMock;
57
+ beforeEach(() => {
58
+ playerContextMock = createPlayerContextMock();
59
+ playerContextMock.player.setVolume(50);
60
+ });
61
+ test('should set volume to a specific value', () => {
62
+ playerContextMock.player.setVolume(30);
63
+ expect(playerContextMock.player.getVolume()).toBe(30);
64
+ });
65
+ test('should not set volume above the maximum', () => {
66
+ playerContextMock.player.setVolume(110);
67
+ expect(playerContextMock.player.getVolume()).toBe(100);
68
+ });
69
+ test('should not set volume below the minimum', () => {
70
+ playerContextMock.player.setVolume(-10);
71
+ expect(playerContextMock.player.getVolume()).toBe(0);
72
+ });
73
+ test('should increase volume by 10', () => {
74
+ playerContextMock.player.increaseVolume();
75
+ expect(playerContextMock.player.getVolume()).toBe(60);
76
+ });
77
+ test('should not increase volume above the maximum', () => {
78
+ playerContextMock.player.setVolume(95);
79
+ playerContextMock.player.increaseVolume();
80
+ expect(playerContextMock.player.getVolume()).toBe(100);
81
+ });
82
+ test('should decrease volume by 10', () => {
83
+ playerContextMock.player.decreaseVolume();
84
+ expect(playerContextMock.player.getVolume()).toBe(40);
85
+ });
86
+ test('should not decrease volume below the minimum', () => {
87
+ playerContextMock.player.setVolume(5);
88
+ playerContextMock.player.decreaseVolume();
89
+ expect(playerContextMock.player.getVolume()).toBe(0);
90
+ });
91
+ });
92
+ describe('Event handling', () => {
93
+ it('should add an event listener', () => {
94
+ const eventType = NpoPlayerEvent.TimeChanged;
95
+ const callback = jest.fn();
96
+ npoPlayerAPI.on(eventType, callback);
97
+ expect(mockPlayerAPI.on).toHaveBeenCalledWith(expect.any(String), expect.any(Function));
98
+ });
99
+ it('should remove an event listener', () => {
100
+ const eventType = NpoPlayerEvent.TimeChanged;
101
+ const callback = jest.fn();
102
+ npoPlayerAPI.on(eventType, callback);
103
+ npoPlayerAPI.off(eventType, callback);
104
+ expect(mockPlayerAPI.off).toHaveBeenCalledWith(expect.any(String), expect.any(Function));
105
+ });
106
+ it('should trigger the correct callback when event is emitted', () => {
107
+ const eventType = NpoPlayerEvent.TimeChanged;
108
+ const callback = jest.fn();
109
+ const mockArgs = { time: 123 };
110
+ npoPlayerAPI.on(eventType, callback);
111
+ const externalCallback = mockPlayerAPI.on.mock.calls[0][1];
112
+ externalCallback(mockArgs);
113
+ expect(callback).toHaveBeenCalledWith(mockArgs);
114
+ });
115
+ });
51
116
  });
@@ -1,12 +1,12 @@
1
- import { PlayerEvent } from 'bitmovin-player';
1
+ import { NpoPlayerEvent } from '../../types/events';
2
2
  import { handleSegmentTimeChanged } from './handleSegmentTimeChanged';
3
3
  import { handleSegmentSeek } from './handleSegmentSeek';
4
4
  export const addSegmentEventListeners = (playerContext, segment) => {
5
5
  const segmentHandleTimeChangedCallback = handleSegmentTimeChanged(playerContext, segment);
6
6
  const segmentSeekFunctionCallback = handleSegmentSeek(playerContext, segment);
7
7
  const { player, npoplayer } = playerContext;
8
- player.on(PlayerEvent.TimeChanged, segmentHandleTimeChangedCallback);
9
- player.on(PlayerEvent.Seek, segmentSeekFunctionCallback);
8
+ player.on(NpoPlayerEvent.TimeChanged, segmentHandleTimeChangedCallback);
9
+ player.on(NpoPlayerEvent.Seek, segmentSeekFunctionCallback);
10
10
  npoplayer.eventListeners = {
11
11
  segmentHandleTimeChangedCallback,
12
12
  segmentSeekFunctionCallback
@@ -2,7 +2,7 @@ import { PlayerEvent } from 'bitmovin-player';
2
2
  import { addSegmentEventListeners } from './addSegmentEventListeners';
3
3
  import { handleSegmentTimeChanged } from './handleSegmentTimeChanged';
4
4
  import { handleSegmentSeek } from './handleSegmentSeek';
5
- import { mockPlayerContext } from '../../../tests/mocks/mockPlayerContext';
5
+ import createPlayerContextMock from '../../../tests/mocks/playerContextMock';
6
6
  jest.mock('./handleSegmentTimeChanged', () => ({
7
7
  handleSegmentTimeChanged: jest.fn()
8
8
  }));
@@ -10,32 +10,33 @@ jest.mock('./handleSegmentSeek', () => ({
10
10
  handleSegmentSeek: jest.fn()
11
11
  }));
12
12
  describe('addSegmentEventListeners', () => {
13
+ let mockPlayerContext;
13
14
  const segment = { inpoint: 10, outpoint: 20, duration: 10 };
14
- const context = { ...mockPlayerContext };
15
15
  beforeEach(() => {
16
16
  jest.clearAllMocks();
17
+ mockPlayerContext = createPlayerContextMock();
17
18
  });
18
19
  it('should add TimeChanged event listener', () => {
19
20
  const segmentHandleTimeChangedCallback = jest.fn();
20
21
  handleSegmentTimeChanged.mockReturnValue(segmentHandleTimeChangedCallback);
21
- addSegmentEventListeners(context, segment);
22
- expect(context.player.on).toHaveBeenCalledWith(PlayerEvent.TimeChanged, segmentHandleTimeChangedCallback);
23
- expect(handleSegmentTimeChanged).toHaveBeenCalledWith(context, segment);
22
+ addSegmentEventListeners(mockPlayerContext, segment);
23
+ expect(mockPlayerContext.player.on).toHaveBeenCalledWith(PlayerEvent.TimeChanged, segmentHandleTimeChangedCallback);
24
+ expect(handleSegmentTimeChanged).toHaveBeenCalledWith(mockPlayerContext, segment);
24
25
  });
25
26
  it('should add Seek event listener', () => {
26
27
  const segmentSeekFunctionCallback = jest.fn();
27
28
  handleSegmentSeek.mockReturnValue(segmentSeekFunctionCallback);
28
29
  addSegmentEventListeners(mockPlayerContext, segment);
29
- expect(context.player.on).toHaveBeenCalledWith(PlayerEvent.Seek, segmentSeekFunctionCallback);
30
- expect(handleSegmentSeek).toHaveBeenCalledWith(context, segment);
30
+ expect(mockPlayerContext.player.on).toHaveBeenCalledWith(PlayerEvent.Seek, segmentSeekFunctionCallback);
31
+ expect(handleSegmentSeek).toHaveBeenCalledWith(mockPlayerContext, segment);
31
32
  });
32
33
  it('should set segmentEventListeners in playerContext', () => {
33
34
  const segmentHandleTimeChangedCallback = jest.fn();
34
35
  const segmentSeekFunctionCallback = jest.fn();
35
36
  handleSegmentTimeChanged.mockReturnValue(segmentHandleTimeChangedCallback);
36
37
  handleSegmentSeek.mockReturnValue(segmentSeekFunctionCallback);
37
- addSegmentEventListeners(context, segment);
38
- expect(context.npoplayer.eventListeners).toEqual({
38
+ addSegmentEventListeners(mockPlayerContext, segment);
39
+ expect(mockPlayerContext.npoplayer.eventListeners).toEqual({
39
40
  segmentHandleTimeChangedCallback,
40
41
  segmentSeekFunctionCallback
41
42
  });
@@ -1,2 +1,2 @@
1
- import { Fragment, Segment } from 'types/interfaces';
1
+ import { Fragment, Segment } from '../../types/interfaces';
2
2
  export declare function convertFragmentToSegment(fragment: Fragment): Segment;
@@ -1,4 +1,4 @@
1
- import { PlayerContext, Segment } from 'types/interfaces';
1
+ import { PlayerContext, Segment } from '../../types/interfaces';
2
2
  export declare const handleSegmentSeek: (playerContext: PlayerContext, segment: Segment) => (e: any) => void;
3
3
  export declare const getSeekTarget: (event: any) => number;
4
4
  export declare const isSeekOutsideSegment: (seekTarget: number, segment: Segment) => boolean;
@@ -1,6 +1,6 @@
1
1
  import { handleSegmentSeek, getSeekTarget, isSeekOutsideSegment, clampSeekTarget } from './handleSegmentSeek';
2
- import { mockPlayerContext } from '../../../tests/mocks/mockPlayerContext';
3
- describe('segmentHandler', () => {
2
+ import createPlayerContextMock from '../../../tests/mocks/playerContextMock';
3
+ describe('handleSegmentSeek', () => {
4
4
  const SEGMENT_INPOINT = 500;
5
5
  const SEGMENT_OUTPOINT = 1500;
6
6
  const SEGMENT_DURATION = 1000;
@@ -8,14 +8,14 @@ describe('segmentHandler', () => {
8
8
  const SEEK_TARGET_ABOVE = 1600;
9
9
  const SEEK_TARGET_BELOW = 400;
10
10
  let segment;
11
+ let mockPlayerContext;
11
12
  beforeEach(() => {
12
13
  segment = {
13
14
  inpoint: SEGMENT_INPOINT,
14
15
  outpoint: SEGMENT_OUTPOINT,
15
16
  duration: SEGMENT_DURATION
16
17
  };
17
- });
18
- afterEach(() => {
18
+ mockPlayerContext = createPlayerContextMock();
19
19
  jest.clearAllMocks();
20
20
  });
21
21
  describe('getSeekTarget', () => {
@@ -1,2 +1,2 @@
1
- import { PlayerContext, Segment } from 'types/interfaces';
1
+ import { PlayerContext, Segment } from '../../types/interfaces';
2
2
  export declare const handleSegmentTimeChanged: (playerContext: PlayerContext, segment: Segment) => (e: any) => void;
@@ -1,34 +1,43 @@
1
- import { mockPlayerContext } from '../../../tests/mocks/mockPlayerContext';
2
1
  import { handleSegmentTimeChanged } from './handleSegmentTimeChanged';
2
+ import createPlayerContextMock from '../../../tests/mocks/playerContextMock';
3
3
  describe('handleSegmentTimeChanged', () => {
4
- const segment = { inpoint: 10, outpoint: 20, duration: 10 };
5
- const context = { ...mockPlayerContext };
4
+ let mockPlayerContext;
5
+ let mockSegment;
6
+ const INPOINT = 10;
7
+ const OUTPOINT = 20;
6
8
  beforeEach(() => {
7
9
  jest.clearAllMocks();
10
+ mockPlayerContext = createPlayerContextMock();
11
+ mockSegment = { inpoint: INPOINT, outpoint: OUTPOINT, duration: OUTPOINT - INPOINT };
8
12
  });
9
- it('should seek and pause player, and add class when time is greater than outpoint and isFinished is false', () => {
10
- const handler = handleSegmentTimeChanged(context, segment);
11
- handler({ time: 30 });
12
- expect(context.player.seek).toHaveBeenCalledWith(20);
13
- expect(context.player.pause).toHaveBeenCalled();
14
- expect(context.player.addClassToNpoPlayerElement).toHaveBeenCalledWith('bmpui-player-state-replay');
13
+ it('should seek to inpoint and pause when time is greater than outpoint', () => {
14
+ const event = { time: OUTPOINT + 1 };
15
+ const handler = handleSegmentTimeChanged(mockPlayerContext, mockSegment);
16
+ handler(event);
17
+ expect(mockPlayerContext.player.seek).toHaveBeenCalledWith(Math.max(INPOINT, OUTPOINT));
18
+ expect(mockPlayerContext.player.pause).toHaveBeenCalled();
19
+ expect(mockPlayerContext.player.addClassToNpoPlayerElement).toHaveBeenCalledWith('bmpui-player-state-replay');
15
20
  });
16
- it('should reset isFinished and seek to inpoint when time is less than inpoint', () => {
17
- const handler = handleSegmentTimeChanged(context, segment);
18
- handler({ time: 30 });
19
- handler({ time: 9 });
20
- expect(context.player.seek).toHaveBeenCalledWith(10);
21
+ it('should seek to inpoint when time is less than inpoint', () => {
22
+ const event = { time: INPOINT - 1 };
23
+ const handler = handleSegmentTimeChanged(mockPlayerContext, mockSegment);
24
+ handler(event);
25
+ expect(mockPlayerContext.player.seek).toHaveBeenCalledWith(INPOINT);
21
26
  });
22
- it('should reset isFinished and seek to inpoint when time is greater than outpoint and isFinished is true', () => {
23
- const handler = handleSegmentTimeChanged(context, segment);
24
- handler({ time: 21 });
25
- handler({ time: 22 });
26
- expect(context.player.seek).toHaveBeenCalledWith(10);
27
+ it('should seek to inpoint when time is greater than outpoint and isFinished is true', () => {
28
+ const event = { time: OUTPOINT + 1 };
29
+ const handler = handleSegmentTimeChanged(mockPlayerContext, mockSegment);
30
+ handler(event);
31
+ handler({ time: INPOINT - 1 });
32
+ handler(event);
33
+ expect(mockPlayerContext.player.seek).toHaveBeenCalledWith(INPOINT);
27
34
  });
28
- it('should not call pause if time is within inpoint and outpoint', () => {
29
- const handler = handleSegmentTimeChanged(context, segment);
30
- handler({ time: 15 });
31
- expect(context.player.seek).not.toHaveBeenCalled();
32
- expect(context.player.pause).not.toHaveBeenCalled();
35
+ it('should not seek or pause when time is between inpoint and outpoint', () => {
36
+ const event = { time: (INPOINT + OUTPOINT) / 2 };
37
+ const handler = handleSegmentTimeChanged(mockPlayerContext, mockSegment);
38
+ handler(event);
39
+ expect(mockPlayerContext.player.seek).not.toHaveBeenCalled();
40
+ expect(mockPlayerContext.player.pause).not.toHaveBeenCalled();
41
+ expect(mockPlayerContext.player.addClassToNpoPlayerElement).not.toHaveBeenCalled();
33
42
  });
34
43
  });
@@ -1,4 +1,4 @@
1
- import { Fragment, PlayerContext, Segment } from 'types/interfaces';
1
+ import { Fragment, PlayerContext, Segment } from '../../types/interfaces';
2
2
  type SegmentOrFragment = {
3
3
  segment?: Segment;
4
4
  fragment?: Fragment;
@@ -1,7 +1,7 @@
1
1
  import { initSegment } from './initSegment';
2
2
  import { addSegmentEventListeners } from './addSegmentEventListeners';
3
3
  import { convertFragmentToSegment } from './convertFragmentToSegment';
4
- import { mockPlayerContext } from '../../../tests/mocks/mockPlayerContext';
4
+ import createPlayerContextMock from '../../../tests/mocks/playerContextMock';
5
5
  jest.mock('./addSegmentEventListeners');
6
6
  jest.mock('./convertFragmentToSegment');
7
7
  const createMockSegment = () => ({ inpoint: 10, outpoint: 20, duration: 10 });
@@ -9,8 +9,10 @@ const createMockFragment = () => ({
9
9
  sections: [{ time: 0, duration: 10, title: 'Dummy fragment' }]
10
10
  });
11
11
  describe('initSegment', () => {
12
+ let mockPlayerContext;
12
13
  beforeEach(() => {
13
14
  jest.clearAllMocks();
15
+ mockPlayerContext = createPlayerContextMock();
14
16
  });
15
17
  it('should convert fragment to segment and seek to inpoint', () => {
16
18
  const mockFragment = createMockFragment();
@@ -1,2 +1,2 @@
1
- import { Segment, TimeLineMarker } from 'types/interfaces';
1
+ import { Segment, TimeLineMarker } from '../../types/interfaces';
2
2
  export declare function setSegmentMarkers(segment: Segment, title: string): Array<TimeLineMarker>;
@@ -1,4 +1,4 @@
1
- import { ApiPayload, Fragment, PlayerContext, Profile, Segment, TimeLineMarker, LocalStorageData, LocalStorageValues } from 'types/interfaces';
1
+ import { ApiPayload, Fragment, PlayerContext, Profile, Segment, TimeLineMarker, LocalStorageData, LocalStorageValues, NpoPlayerUIVariants } from '../types/interfaces';
2
2
  import { AVType } from '@npotag/tag/dist/types/src/streamTracker';
3
3
  export declare class NpoPlayerServices {
4
4
  getAVType(avType: string): AVType;
@@ -11,4 +11,6 @@ export declare class NpoPlayerServices {
11
11
  getStoredUserPrefs(): LocalStorageData;
12
12
  handleUserPrefs(playerContext: PlayerContext): void;
13
13
  setAccessibilityAttributes(playerContext: PlayerContext): void;
14
+ schedulePreRolls(playerContext: PlayerContext): Promise<void>;
15
+ handleVerticalVideoControls(playerContext: PlayerContext, variant: NpoPlayerUIVariants): void;
14
16
  }
@@ -5,6 +5,8 @@ import * as DRMHandlers from './drmHandlers/drmhandlers';
5
5
  import { setupAccessibilityAttributes } from './a11y/setup';
6
6
  import { getLocalStorage, setLocalStorage, setValuesBasedOnLocalStorage } from './localStorageHandlers/localStorageHandlers';
7
7
  import { getAVType } from './avTypeHandlers/getAVType';
8
+ import { handlePreRolls } from './advertHandlers/handlePreRolls';
9
+ import { handleVerticalVideoControls } from './verticalVideoHandlers/handleVerticalVideoControls';
8
10
  export class NpoPlayerServices {
9
11
  getAVType(avType) {
10
12
  return getAVType(avType);
@@ -44,4 +46,10 @@ export class NpoPlayerServices {
44
46
  }
45
47
  return setupAccessibilityAttributes(playerContext);
46
48
  }
49
+ async schedulePreRolls(playerContext) {
50
+ await handlePreRolls(playerContext);
51
+ }
52
+ handleVerticalVideoControls(playerContext, variant) {
53
+ return handleVerticalVideoControls(playerContext, variant);
54
+ }
47
55
  }
@@ -0,0 +1,2 @@
1
+ import { PlayerContext, NpoPlayerUIVariants } from '../../types/interfaces';
2
+ export declare const handleVerticalVideoControls: (playerContext: PlayerContext, variant: NpoPlayerUIVariants) => void;
@@ -0,0 +1,8 @@
1
+ import { NpoPlayerUIVariants } from '../../types/interfaces';
2
+ export const handleVerticalVideoControls = (playerContext, variant) => {
3
+ const MAX_DURATION_IN_SECONDS = 30;
4
+ const isShortVideo = playerContext.player.getDuration() <= MAX_DURATION_IN_SECONDS;
5
+ const hasVerticalUI = variant === NpoPlayerUIVariants.VERTICAL;
6
+ const isVerticalShortVideo = hasVerticalUI && isShortVideo;
7
+ playerContext.player.toggleClassOnNpoPlayerElement('vertical-short-video', isVerticalShortVideo);
8
+ };
@@ -0,0 +1,36 @@
1
+ import { NpoPlayerUIVariants } from '../../types/interfaces';
2
+ import { handleVerticalVideoControls } from './handleVerticalVideoControls';
3
+ import createPlayerContextMock from '../../../tests/mocks/playerContextMock';
4
+ describe('handleVerticalVideoControls', () => {
5
+ const playerContextMock = createPlayerContextMock();
6
+ const shortVideoClass = 'vertical-short-video';
7
+ const shortVideoDuration = 20;
8
+ const longVideoDuration = 60;
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+ test('should add vertical-short-video class for vertical UI and short video', () => {
13
+ ;
14
+ playerContextMock.player.getDuration.mockReturnValue(shortVideoDuration);
15
+ handleVerticalVideoControls(playerContextMock, NpoPlayerUIVariants.VERTICAL);
16
+ expect(playerContextMock.player.toggleClassOnNpoPlayerElement).toHaveBeenCalledWith(shortVideoClass, true);
17
+ });
18
+ test('should not add vertical-short-video class for vertical UI and long video', () => {
19
+ ;
20
+ playerContextMock.player.getDuration.mockReturnValue(longVideoDuration);
21
+ handleVerticalVideoControls(playerContextMock, NpoPlayerUIVariants.VERTICAL);
22
+ expect(playerContextMock.player.toggleClassOnNpoPlayerElement).toHaveBeenCalledWith(shortVideoClass, false);
23
+ });
24
+ test('should not add vertical-short-video class for non-vertical UI and short video', () => {
25
+ ;
26
+ playerContextMock.player.getDuration.mockReturnValue(shortVideoDuration);
27
+ handleVerticalVideoControls(playerContextMock, NpoPlayerUIVariants.DEFAULT);
28
+ expect(playerContextMock.player.toggleClassOnNpoPlayerElement).toHaveBeenCalledWith(shortVideoClass, false);
29
+ });
30
+ test('should not add vertical-short-video class for non-vertical UI and long video', () => {
31
+ ;
32
+ playerContextMock.player.getDuration.mockReturnValue(longVideoDuration);
33
+ handleVerticalVideoControls(playerContextMock, NpoPlayerUIVariants.DEFAULT);
34
+ expect(playerContextMock.player.toggleClassOnNpoPlayerElement).toHaveBeenCalledWith(shortVideoClass, false);
35
+ });
36
+ });