@npo/player 1.21.1 → 1.22.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.
- package/README.md +1 -2
- package/lib/js/drm/handlers/verifydrm.test.js +1 -1
- package/lib/js/markers/updateLiveMarkers.d.ts +1 -1
- package/lib/js/markers/updateLiveMarkers.js +42 -13
- package/lib/js/markers/updateLiveMarkers.test.js +3 -2
- package/lib/js/playeractions/handlers/error.test.js +6 -6
- package/lib/js/playeractions/handlers/handleoffsets.test.js +7 -7
- package/lib/js/playeractions/handlers/processsourceconfig.d.ts +2 -2
- package/lib/js/playeractions/handlers/processsourceconfig.js +20 -6
- package/lib/js/playeractions/handlers/removereplayclass.js +4 -3
- package/lib/js/playeractions/handlers/removereplayclass.test.js +7 -3
- package/lib/js/tracking/handlers/eventbinding.js +41 -31
- package/lib/js/tracking/handlers/eventlogging.d.ts +1 -1
- package/lib/js/tracking/handlers/eventlogging.js +1 -1
- package/lib/js/ui/components/buttons.js +2 -2
- package/lib/js/ui/components/nativemobile/buttons.d.ts +0 -12
- package/lib/js/ui/components/nativemobile/buttons.js +0 -41
- package/lib/js/ui/handlers/domhandlers.test.js +2 -1
- package/lib/{src/js/ui/components/shared → js/ui/handlers}/playnextscreen.d.ts +2 -2
- package/lib/js/ui/handlers/playnextscreen.js +26 -0
- package/lib/js/ui/{components/shared → handlers}/playnextstreen.test.js +6 -16
- package/lib/js/ui/nativemobileuicontainer.js +1 -4
- package/lib/js/ui/nativemobileuifactory.js +4 -20
- package/lib/npoplayer.js +32 -18
- package/lib/package.json +2 -2
- package/lib/src/js/markers/updateLiveMarkers.d.ts +1 -1
- package/lib/src/js/playeractions/handlers/processsourceconfig.d.ts +2 -2
- package/lib/src/js/tracking/handlers/eventlogging.d.ts +1 -1
- package/lib/src/js/ui/components/nativemobile/buttons.d.ts +0 -12
- package/lib/{js/ui/components/shared → src/js/ui/handlers}/playnextscreen.d.ts +2 -2
- package/lib/src/types/interfaces.d.ts +8 -0
- package/lib/types/interfaces.d.ts +8 -0
- package/package.json +2 -2
- package/src/scss/components/_playnext.scss +29 -1
- package/src/scss/components/_textbuttons.scss +17 -7
- package/src/scss/npoplayer.css +9 -5
- package/src/scss/vars/_colors.scss +1 -0
- package/lib/js/ui/components/nativemobile/playnext.d.ts +0 -11
- package/lib/js/ui/components/nativemobile/playnext.js +0 -10
- package/lib/js/ui/components/shared/playnextscreen.js +0 -46
- package/lib/src/js/ui/components/nativemobile/playnext.d.ts +0 -11
- /package/lib/js/ui/{components/shared → handlers}/playnextstreen.test.d.ts +0 -0
- /package/lib/src/js/ui/{components/shared → handlers}/playnextstreen.test.d.ts +0 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Extensive and up-to-date documentation is available at https://docs.npoplayer.nl
|
|
|
11
11
|
Code quality is analysed by SonarCloud. The project can be found at https://sonarcloud.io/project/overview?id=NPOstart_npo-player
|
|
12
12
|
|
|
13
13
|
# Changelog
|
|
14
|
-
Current version: v1.
|
|
14
|
+
Current version: v1.22.0
|
|
15
15
|
|
|
16
16
|
The changelog is available at https://docs.npoplayer.nl/implementation/web/changelog/
|
|
17
17
|
|
|
@@ -25,4 +25,3 @@ MIT
|
|
|
25
25
|
## Native mobile development
|
|
26
26
|
|
|
27
27
|
For detailed information on the JavaScript player integration for ios and Android, including custom message handling, UI customization, and interaction with native mobile SDKs, see the [Bitmovin Player Integration for Native Mobile SDKs](src/js/ui/README.md) located in the `src/js/ui` directory.
|
|
28
|
-
|
|
@@ -12,6 +12,6 @@ const payload = {
|
|
|
12
12
|
describe("Test DRM verification", () => {
|
|
13
13
|
it("should call player.load when the drmToken is null", async () => {
|
|
14
14
|
await verifyDRM(npoplayer, player, payload);
|
|
15
|
-
expect(load).
|
|
15
|
+
expect(load).toHaveBeenCalled();
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { PlayerAPI } from "bitmovin-player";
|
|
2
2
|
import { UIManager } from "bitmovin-player-ui";
|
|
3
3
|
import { TimeLineMarker } from "types/interfaces";
|
|
4
|
-
export declare function updateLiveMarkers(timeLineMarkers: TimeLineMarker[], player: PlayerAPI, uiManager: UIManager): void;
|
|
4
|
+
export declare function updateLiveMarkers(timeLineMarkers: TimeLineMarker[], player: PlayerAPI, uiManager: UIManager, autoFillTimeLineMarkerDuration: boolean): void;
|
|
@@ -1,21 +1,50 @@
|
|
|
1
|
-
export function updateLiveMarkers(timeLineMarkers, player, uiManager) {
|
|
1
|
+
export function updateLiveMarkers(timeLineMarkers, player, uiManager, autoFillTimeLineMarkerDuration) {
|
|
2
2
|
if (!player || !uiManager) {
|
|
3
3
|
console.error('Player or UIManager is null');
|
|
4
4
|
return;
|
|
5
5
|
}
|
|
6
|
+
removeAllMarkers(uiManager);
|
|
7
|
+
let currentTimeEpoch = Math.floor(Date.now() / 1000);
|
|
6
8
|
timeLineMarkers.sort((a, b) => a.time - b.time);
|
|
7
|
-
let
|
|
8
|
-
|
|
9
|
-
time: marker.time,
|
|
10
|
-
title: marker.title,
|
|
11
|
-
cssClasses: ['npo-livestream-marker'],
|
|
12
|
-
};
|
|
13
|
-
});
|
|
14
|
-
const existingTimelineMarkers = uiManager.getTimelineMarkers();
|
|
15
|
-
existingTimelineMarkers.forEach((marker) => {
|
|
16
|
-
uiManager?.removeTimelineMarker(marker);
|
|
17
|
-
});
|
|
18
|
-
markesWithClass.forEach(marker => {
|
|
9
|
+
let relativeTimeMarkers = timeLineMarkers.map((marker, index) => transformMarker(marker, index, timeLineMarkers, currentTimeEpoch, player, autoFillTimeLineMarkerDuration));
|
|
10
|
+
relativeTimeMarkers.forEach(marker => {
|
|
19
11
|
uiManager?.addTimelineMarker(marker);
|
|
20
12
|
});
|
|
21
13
|
}
|
|
14
|
+
function transformMarker(marker, index, timeLineMarkers, currentTimeEpoch, player, autoFillTimeLineMarkerDuration) {
|
|
15
|
+
const DVR_WINDOW_LENGTH = Math.abs(player.getMaxTimeShift());
|
|
16
|
+
let duration = marker.duration !== undefined ? marker.duration : 0;
|
|
17
|
+
let startTimeRelativeToCurrent = marker.time - currentTimeEpoch;
|
|
18
|
+
if (autoFillTimeLineMarkerDuration) {
|
|
19
|
+
if (index === timeLineMarkers.length - 1) {
|
|
20
|
+
duration = Math.min(currentTimeEpoch, marker.time + DVR_WINDOW_LENGTH) - marker.time;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
duration = timeLineMarkers[index + 1].time - marker.time;
|
|
24
|
+
}
|
|
25
|
+
if (startTimeRelativeToCurrent < -DVR_WINDOW_LENGTH) {
|
|
26
|
+
duration = index === timeLineMarkers.length - 1 ?
|
|
27
|
+
currentTimeEpoch - (currentTimeEpoch - DVR_WINDOW_LENGTH) :
|
|
28
|
+
timeLineMarkers[index + 1].time - (currentTimeEpoch - DVR_WINDOW_LENGTH);
|
|
29
|
+
startTimeRelativeToCurrent = -DVR_WINDOW_LENGTH;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
time: marker.time,
|
|
34
|
+
duration: duration,
|
|
35
|
+
title: marker.title,
|
|
36
|
+
cssClasses: ['npo-livestream-marker'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function removeAllMarkers(uiManager) {
|
|
40
|
+
let existingTimelineMarkers = uiManager.getTimelineMarkers();
|
|
41
|
+
while (existingTimelineMarkers.length > 0) {
|
|
42
|
+
const marker = existingTimelineMarkers[0];
|
|
43
|
+
const removed = uiManager?.removeTimelineMarker(marker);
|
|
44
|
+
if (removed) {
|
|
45
|
+
existingTimelineMarkers = uiManager.getTimelineMarkers();
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -15,6 +15,7 @@ jest.mock("bitmovin-player", () => ({
|
|
|
15
15
|
describe('updateLiveMarkers', () => {
|
|
16
16
|
const player = {
|
|
17
17
|
getCurrentTime: jest.fn().mockReturnValue(10),
|
|
18
|
+
getMaxTimeShift: jest.fn().mockReturnValue(-3600)
|
|
18
19
|
};
|
|
19
20
|
let uiManager;
|
|
20
21
|
let ui;
|
|
@@ -26,7 +27,7 @@ describe('updateLiveMarkers', () => {
|
|
|
26
27
|
{ time: 3000, title: 'Marker 2' },
|
|
27
28
|
{ time: 1000, title: 'Marker 1' },
|
|
28
29
|
];
|
|
29
|
-
updateLiveMarkers(timeLineMarkers, player, uiManager);
|
|
30
|
+
updateLiveMarkers(timeLineMarkers, player, uiManager, true);
|
|
30
31
|
expect(uiManager.getTimelineMarkers).toHaveBeenCalled();
|
|
31
32
|
expect(uiManager.removeTimelineMarker).toHaveBeenCalledTimes(0);
|
|
32
33
|
expect(uiManager.addTimelineMarker).toHaveBeenCalledTimes(2);
|
|
@@ -46,7 +47,7 @@ describe('updateLiveMarkers', () => {
|
|
|
46
47
|
const timeLineMarkers = [
|
|
47
48
|
{ time: 1000, title: 'New Marker' },
|
|
48
49
|
];
|
|
49
|
-
updateLiveMarkers(timeLineMarkers, player, uiManager);
|
|
50
|
+
updateLiveMarkers(timeLineMarkers, player, uiManager, true);
|
|
50
51
|
expect(uiManager.removeTimelineMarker).toHaveBeenCalledTimes(1);
|
|
51
52
|
expect(uiManager.addTimelineMarker).toHaveBeenCalledTimes(1);
|
|
52
53
|
});
|
|
@@ -18,11 +18,11 @@ describe("Test DRM verification", () => {
|
|
|
18
18
|
});
|
|
19
19
|
it("should call player.unload", () => {
|
|
20
20
|
handlePlayerError(player, uiComponents, input);
|
|
21
|
-
expect(unload).
|
|
21
|
+
expect(unload).toHaveBeenCalled();
|
|
22
22
|
});
|
|
23
23
|
it("should hide the controlbar", () => {
|
|
24
24
|
handlePlayerError(player, uiComponents, input);
|
|
25
|
-
expect(hide).
|
|
25
|
+
expect(hide).toHaveBeenCalled();
|
|
26
26
|
});
|
|
27
27
|
it("should not hide the controlbar", () => {
|
|
28
28
|
const components = {
|
|
@@ -30,15 +30,15 @@ describe("Test DRM verification", () => {
|
|
|
30
30
|
controlbar: undefined,
|
|
31
31
|
};
|
|
32
32
|
handlePlayerError(player, components, input);
|
|
33
|
-
expect(hide).not.
|
|
33
|
+
expect(hide).not.toHaveBeenCalled();
|
|
34
34
|
});
|
|
35
35
|
it("should show the message overlay", () => {
|
|
36
36
|
handlePlayerError(player, uiComponents, input);
|
|
37
|
-
expect(show).
|
|
38
|
-
expect(setText).
|
|
37
|
+
expect(show).toHaveBeenCalled();
|
|
38
|
+
expect(setText).toHaveBeenCalled();
|
|
39
39
|
});
|
|
40
40
|
it("should not show the message overlay", () => {
|
|
41
41
|
handlePlayerError(player, { ...uiComponents, errorMessageOverlay: undefined }, input);
|
|
42
|
-
expect(show).not.
|
|
42
|
+
expect(show).not.toHaveBeenCalled();
|
|
43
43
|
});
|
|
44
44
|
});
|
|
@@ -15,15 +15,15 @@ describe("Test offset handling", () => {
|
|
|
15
15
|
});
|
|
16
16
|
it("should not seek when player is live", () => {
|
|
17
17
|
handleStartOffset(player, offset);
|
|
18
|
-
expect(onReady).not.
|
|
18
|
+
expect(onReady).not.toHaveBeenCalled();
|
|
19
19
|
});
|
|
20
20
|
it("should not seek when offset is undefined", () => {
|
|
21
21
|
handleStartOffset({ ...player, isLive: () => false });
|
|
22
|
-
expect(onReady).not.
|
|
22
|
+
expect(onReady).not.toHaveBeenCalled();
|
|
23
23
|
});
|
|
24
24
|
it("should seek when player is not live and offset is defined", () => {
|
|
25
25
|
handleStartOffset({ ...player, isLive: () => false }, offset);
|
|
26
|
-
expect(onReady).
|
|
26
|
+
expect(onReady).toHaveBeenCalled();
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
describe("Test programStart shift", () => {
|
|
@@ -32,18 +32,18 @@ describe("Test programStart shift", () => {
|
|
|
32
32
|
});
|
|
33
33
|
it("should not call timeshift when player is null", () => {
|
|
34
34
|
shiftToProgramStart(null, timestamp);
|
|
35
|
-
expect(timeShift).not.
|
|
35
|
+
expect(timeShift).not.toHaveBeenCalled();
|
|
36
36
|
});
|
|
37
37
|
it("should not call timeshift when timestamp is undefined", () => {
|
|
38
38
|
shiftToProgramStart(player);
|
|
39
|
-
expect(timeShift).not.
|
|
39
|
+
expect(timeShift).not.toHaveBeenCalled();
|
|
40
40
|
});
|
|
41
41
|
it("should not call timeshift when player is not live", () => {
|
|
42
42
|
shiftToProgramStart({ ...player, isLive: () => false }, timestamp);
|
|
43
|
-
expect(timeShift).not.
|
|
43
|
+
expect(timeShift).not.toHaveBeenCalled();
|
|
44
44
|
});
|
|
45
45
|
it("should not call timeshift when player is live and timestamp is set", () => {
|
|
46
46
|
shiftToProgramStart(player, timestamp);
|
|
47
|
-
expect(timeShift).
|
|
47
|
+
expect(timeShift).toHaveBeenCalled();
|
|
48
48
|
});
|
|
49
49
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { StreamObject } from 'types/interfaces';
|
|
1
|
+
import { StreamObject, StreamOptions } from 'types/interfaces';
|
|
2
2
|
import { SourceConfig } from 'bitmovin-player';
|
|
3
|
-
export declare function processSourceConfig(_sourceConfig: SourceConfig | undefined, _streamObject: StreamObject, drm: string | null | undefined,
|
|
3
|
+
export declare function processSourceConfig(_sourceConfig: SourceConfig | undefined, _streamObject: StreamObject, drm: string | null | undefined, streamOptions: StreamOptions, version: string, npoTagPartyId: string | undefined): Promise<SourceConfig>;
|
|
@@ -91,7 +91,7 @@ function setStreamProfile(sourceConfig, _sourceConfig, _streamObject) {
|
|
|
91
91
|
}
|
|
92
92
|
return sourceConfig;
|
|
93
93
|
}
|
|
94
|
-
export async function processSourceConfig(_sourceConfig = {}, _streamObject, drm = null,
|
|
94
|
+
export async function processSourceConfig(_sourceConfig = {}, _streamObject, drm = null, streamOptions, version, npoTagPartyId) {
|
|
95
95
|
let sourceConfig = {};
|
|
96
96
|
sourceConfig.title =
|
|
97
97
|
_sourceConfig.title ?? _streamObject.metadata.title ?? '';
|
|
@@ -99,9 +99,7 @@ export async function processSourceConfig(_sourceConfig = {}, _streamObject, drm
|
|
|
99
99
|
_sourceConfig.description ??
|
|
100
100
|
_streamObject.metadata.description ??
|
|
101
101
|
'';
|
|
102
|
-
sourceConfig.poster =
|
|
103
|
-
_sourceConfig.poster ?? _streamObject.metadata.poster ?? '';
|
|
104
|
-
sourceConfig.poster = replaceSpecialChars(sourceConfig.poster);
|
|
102
|
+
sourceConfig.poster = decidePoster(_streamObject, _sourceConfig, streamOptions);
|
|
105
103
|
sourceConfig.metadata = {
|
|
106
104
|
title: sourceConfig.title,
|
|
107
105
|
description: sourceConfig.description,
|
|
@@ -125,7 +123,7 @@ export async function processSourceConfig(_sourceConfig = {}, _streamObject, drm
|
|
|
125
123
|
: undefined;
|
|
126
124
|
sourceConfig.options = _sourceConfig.options;
|
|
127
125
|
sourceConfig = setStreamProfile(sourceConfig, _sourceConfig, _streamObject);
|
|
128
|
-
sourceConfig = await setDrm(sourceConfig, drm, _streamObject, useWidevineServerCertificate);
|
|
126
|
+
sourceConfig = await setDrm(sourceConfig, drm, _streamObject, streamOptions.useWidevineServerCertificate ?? true);
|
|
129
127
|
const streamSource = _streamObject.stream.streamURL;
|
|
130
128
|
let cdnString = streamSource;
|
|
131
129
|
if (streamSource.startsWith('file:///')) {
|
|
@@ -143,9 +141,13 @@ export async function processSourceConfig(_sourceConfig = {}, _streamObject, drm
|
|
|
143
141
|
sourceConfig.analytics.cdnProvider = cdnString;
|
|
144
142
|
sourceConfig.analytics.videoId = _streamObject.metadata.prid;
|
|
145
143
|
sourceConfig.analytics.title = _streamObject.metadata.title;
|
|
144
|
+
sourceConfig.analytics.customData2 = _streamObject.user.type;
|
|
145
|
+
sourceConfig.analytics.customData3 = _streamObject.stream.avType;
|
|
146
|
+
sourceConfig.analytics.customData4 = npoTagPartyId;
|
|
147
|
+
sourceConfig.analytics.customData5 = version;
|
|
146
148
|
sourceConfig.analytics = {
|
|
147
149
|
...sourceConfig.analytics,
|
|
148
|
-
..._sourceConfig.analytics
|
|
150
|
+
..._sourceConfig.analytics,
|
|
149
151
|
};
|
|
150
152
|
const getSubtitleLabels = function (data) {
|
|
151
153
|
if (data.label === 'nl') {
|
|
@@ -170,3 +172,15 @@ export async function processSourceConfig(_sourceConfig = {}, _streamObject, drm
|
|
|
170
172
|
}
|
|
171
173
|
return sourceConfig;
|
|
172
174
|
}
|
|
175
|
+
function decidePoster(streamObject, sourceConfig, streamOptions) {
|
|
176
|
+
if (sourceConfig.poster) {
|
|
177
|
+
return replaceSpecialChars(sourceConfig.poster);
|
|
178
|
+
}
|
|
179
|
+
if (streamObject.metadata.poster && !streamObject.metadata.posterIsDefault) {
|
|
180
|
+
return streamObject.metadata.poster;
|
|
181
|
+
}
|
|
182
|
+
if (streamObject.metadata.posterIsDefault && streamOptions.customFallbackPoster) {
|
|
183
|
+
return replaceSpecialChars(streamOptions.customFallbackPoster);
|
|
184
|
+
}
|
|
185
|
+
return streamObject.metadata.poster ?? '';
|
|
186
|
+
}
|
|
@@ -5,7 +5,8 @@ export function removeReplayClass(player) {
|
|
|
5
5
|
player.off(PlayerEvent.Seek, () => {
|
|
6
6
|
removeReplayClass(player);
|
|
7
7
|
});
|
|
8
|
-
const playerContainer = player.getContainer();
|
|
9
|
-
playerContainer
|
|
10
|
-
playerContainer
|
|
8
|
+
const playerContainer = player.getContainer().querySelector('.bmpui-npo-player');
|
|
9
|
+
playerContainer?.classList.remove('bmpui-player-state-finished');
|
|
10
|
+
playerContainer?.classList.remove('bmpui-player-state-replay');
|
|
11
|
+
playerContainer?.classList.add('bmpui-player-state-paused');
|
|
11
12
|
}
|
|
@@ -3,10 +3,13 @@ import { PlayerEvent } from 'bitmovin-player';
|
|
|
3
3
|
describe('removeReplayClass', () => {
|
|
4
4
|
let mockPlayer;
|
|
5
5
|
let mockPlayerContainer;
|
|
6
|
+
let mockPlayerWrapper;
|
|
6
7
|
beforeEach(() => {
|
|
7
8
|
jest.clearAllMocks();
|
|
8
9
|
mockPlayerContainer = document.createElement('div');
|
|
9
|
-
|
|
10
|
+
mockPlayerWrapper = document.createElement('div');
|
|
11
|
+
mockPlayerWrapper.classList.add('bmpui-npo-player', 'bmpui-player-state-finished', 'bmpui-player-state-replay');
|
|
12
|
+
mockPlayerContainer.appendChild(mockPlayerWrapper);
|
|
10
13
|
mockPlayer = {
|
|
11
14
|
off: jest.fn(),
|
|
12
15
|
getContainer: jest.fn().mockReturnValue(mockPlayerContainer),
|
|
@@ -14,8 +17,9 @@ describe('removeReplayClass', () => {
|
|
|
14
17
|
});
|
|
15
18
|
it('should remove "bmpui-player-state-finished" and "bmpui-player-state-replay" classes from the player container', () => {
|
|
16
19
|
removeReplayClass(mockPlayer);
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
20
|
+
expect(mockPlayerWrapper.classList.contains('bmpui-player-state-finished')).toBe(false);
|
|
21
|
+
expect(mockPlayerWrapper.classList.contains('bmpui-player-state-replay')).toBe(false);
|
|
22
|
+
expect(mockPlayerWrapper.classList.contains('bmpui-player-state-paused')).toBe(true);
|
|
19
23
|
});
|
|
20
24
|
it('should unregister the Seek event listener', () => {
|
|
21
25
|
removeReplayClass(mockPlayer);
|
|
@@ -4,14 +4,20 @@ const logEventHandlers = new Map();
|
|
|
4
4
|
export function bindPlayerEvents(npoplayer, player) {
|
|
5
5
|
if (player == null)
|
|
6
6
|
return;
|
|
7
|
-
let isNewSource = false;
|
|
8
7
|
let isSeeking = false;
|
|
8
|
+
let isLiveStream = npoplayer.streamObject.stream.isLiveStream;
|
|
9
|
+
let currentTime = 0;
|
|
10
|
+
let isNewSource = true;
|
|
11
|
+
const timeDifference = (time) => {
|
|
12
|
+
if (time === undefined || time === null)
|
|
13
|
+
return;
|
|
14
|
+
currentTime = isLiveStream ? +(time - Date.now() / 1000).toFixed(3) : +time.toFixed(3);
|
|
15
|
+
return currentTime;
|
|
16
|
+
};
|
|
9
17
|
const logEventHandler = (eventName) => {
|
|
10
|
-
return (
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
: event.position;
|
|
14
|
-
logEvent(npoplayer, eventName, timeDifference);
|
|
18
|
+
return (e) => {
|
|
19
|
+
const eventTime = e.time !== undefined ? timeDifference(e.time) : undefined;
|
|
20
|
+
logEvent(npoplayer, eventName, eventTime);
|
|
15
21
|
};
|
|
16
22
|
};
|
|
17
23
|
const seekHandler = (e) => {
|
|
@@ -33,33 +39,38 @@ export function bindPlayerEvents(npoplayer, player) {
|
|
|
33
39
|
}
|
|
34
40
|
logEvent(npoplayer, e.type, data);
|
|
35
41
|
};
|
|
36
|
-
const handleSourceLoaded = (
|
|
42
|
+
const handleSourceLoaded = () => {
|
|
37
43
|
isNewSource = true;
|
|
38
|
-
if (
|
|
39
|
-
logEvent(npoplayer, 'stop'
|
|
44
|
+
if (isLiveStream) {
|
|
45
|
+
logEvent(npoplayer, 'stop');
|
|
40
46
|
}
|
|
41
47
|
};
|
|
42
48
|
const handlePlay = (e) => {
|
|
43
49
|
if (!isNewSource && !isSeeking) {
|
|
44
|
-
logEvent(npoplayer, 'resume', e.
|
|
50
|
+
logEvent(npoplayer, 'resume', timeDifference(e.time));
|
|
45
51
|
}
|
|
46
52
|
};
|
|
47
|
-
const handlePlaying = (
|
|
53
|
+
const handlePlaying = () => {
|
|
48
54
|
if (isSeeking) {
|
|
49
55
|
isSeeking = false;
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
};
|
|
58
|
+
const handleTime = (e) => {
|
|
59
|
+
if (isNewSource && !npoplayer.adBreakActive) {
|
|
60
|
+
logEvent(npoplayer, 'start', timeDifference(e.time));
|
|
53
61
|
isNewSource = false;
|
|
54
62
|
}
|
|
63
|
+
else {
|
|
64
|
+
logEvent(npoplayer, 'time', timeDifference(e.time));
|
|
65
|
+
}
|
|
55
66
|
};
|
|
56
|
-
const handleViewModeChange = (
|
|
67
|
+
const handleViewModeChange = () => {
|
|
57
68
|
const newViewMode = player.getViewMode();
|
|
58
69
|
if (newViewMode === ViewMode.Fullscreen) {
|
|
59
|
-
logEvent(npoplayer, 'fullscreen',
|
|
70
|
+
logEvent(npoplayer, 'fullscreen', currentTime);
|
|
60
71
|
}
|
|
61
72
|
else {
|
|
62
|
-
logEvent(npoplayer, 'windowed',
|
|
73
|
+
logEvent(npoplayer, 'windowed', currentTime);
|
|
63
74
|
}
|
|
64
75
|
};
|
|
65
76
|
const stopBeforeUnload = () => {
|
|
@@ -71,30 +82,29 @@ export function bindPlayerEvents(npoplayer, player) {
|
|
|
71
82
|
window.removeEventListener('beforeunload', logEventHandlers.get('stopBeforeUnload'));
|
|
72
83
|
logEventHandlers.clear();
|
|
73
84
|
const playerEvents = [
|
|
85
|
+
{ event: PlayerEvent.Ready, name: 'load_complete' },
|
|
74
86
|
{ event: PlayerEvent.Paused, name: 'pause' },
|
|
87
|
+
{ event: PlayerEvent.Play, handler: handlePlay },
|
|
75
88
|
{ event: PlayerEvent.SourceLoaded, handler: handleSourceLoaded },
|
|
76
|
-
{ event: PlayerEvent.Ready, name: 'load_complete' },
|
|
77
89
|
{ event: PlayerEvent.StallStarted, name: 'buffering' },
|
|
78
90
|
{ event: PlayerEvent.StallEnded, name: 'buffering_complete' },
|
|
79
91
|
{ event: PlayerEvent.PlaybackFinished, name: 'complete' },
|
|
80
|
-
{ event: PlayerEvent.TimeChanged, name: 'time' },
|
|
81
92
|
{ event: PlayerEvent.SourceUnloaded, name: 'stop' },
|
|
93
|
+
{ event: PlayerEvent.Seek, handler: seekHandler },
|
|
94
|
+
{ event: PlayerEvent.TimeShift, handler: seekHandler },
|
|
95
|
+
{ event: PlayerEvent.TimeChanged, handler: handleTime },
|
|
96
|
+
{ event: PlayerEvent.ViewModeChanged, handler: handleViewModeChange },
|
|
82
97
|
];
|
|
83
98
|
playerEvents.forEach(({ event, name, handler }) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
const effectiveName = name || 'unnamed_event';
|
|
100
|
+
const eventHandler = handler || logEventHandler(effectiveName);
|
|
101
|
+
logEventHandlers.set(event, eventHandler);
|
|
102
|
+
player.on(event, eventHandler);
|
|
87
103
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
logEventHandlers.set(PlayerEvent.Play, handlePlay);
|
|
93
|
-
player.on(PlayerEvent.Play, handlePlay);
|
|
94
|
-
logEventHandlers.set(PlayerEvent.Playing, handlePlaying);
|
|
95
|
-
player.on(PlayerEvent.Playing, handlePlaying);
|
|
96
|
-
logEventHandlers.set(PlayerEvent.ViewModeChanged, handleViewModeChange);
|
|
97
|
-
player.on(PlayerEvent.ViewModeChanged, handleViewModeChange);
|
|
104
|
+
if (npoplayer.streamObject.assets.preroll === null) {
|
|
105
|
+
logEventHandlers.set(PlayerEvent.Playing, handlePlaying);
|
|
106
|
+
player.on(PlayerEvent.Playing, handlePlaying);
|
|
107
|
+
}
|
|
98
108
|
logEventHandlers.set('beforeunload', stopBeforeUnload);
|
|
99
109
|
window.addEventListener('beforeunload', stopBeforeUnload);
|
|
100
110
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import NpoPlayer from
|
|
1
|
+
import NpoPlayer from '../../../npoplayer';
|
|
2
2
|
export declare function logEvent(npoplayer: NpoPlayer, event: string, data?: any): void;
|
|
@@ -26,6 +26,6 @@ export function logEvent(npoplayer, event, data) {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
const streamTrackerHandler = eventHandlers[event];
|
|
29
|
-
npoplayer.logEmitter.emit('logEvent', event);
|
|
29
|
+
npoplayer.logEmitter.emit('logEvent', event, data);
|
|
30
30
|
streamTrackerHandler(streamOptions);
|
|
31
31
|
}
|
|
@@ -38,8 +38,8 @@ export function createForwardButton(player) {
|
|
|
38
38
|
export function createPlayNextButton(player, npoplayer) {
|
|
39
39
|
const playNextButton = new Button({
|
|
40
40
|
cssClass: 'ui-textbutton ui-playNextButton bmpui-ui-button',
|
|
41
|
-
text: '
|
|
42
|
-
ariaLabel: '
|
|
41
|
+
text: '<span class="bmpui-ui-textbutton-icon bmpui-ui-textbutton-icon-play"></span> Volgende aflevering',
|
|
42
|
+
ariaLabel: 'Volgende aflevering',
|
|
43
43
|
hidden: false
|
|
44
44
|
});
|
|
45
45
|
playNextButton.onClick.subscribe(() => {
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import { AirPlayToggleButton, Button, CastToggleButton, ControlBar, FullscreenToggleButton, PictureInPictureToggleButton, PlaybackToggleButton, SettingsPanel, SettingsToggleButton, VolumeToggleButton } from 'bitmovin-player-ui';
|
|
2
2
|
import { PlayerAPI } from 'bitmovin-player';
|
|
3
3
|
export declare function createMiddleButtons(player: PlayerAPI): ControlBar;
|
|
4
|
-
export declare function createPlayNextButton(player: PlayerAPI): Button<{
|
|
5
|
-
cssClass: string;
|
|
6
|
-
text: string;
|
|
7
|
-
ariaLabel: string;
|
|
8
|
-
hidden: false;
|
|
9
|
-
}>;
|
|
10
|
-
export declare function createCancelPlayNextButton(player: PlayerAPI): Button<{
|
|
11
|
-
cssClass: string;
|
|
12
|
-
text: string;
|
|
13
|
-
ariaLabel: string;
|
|
14
|
-
hidden: false;
|
|
15
|
-
}>;
|
|
16
4
|
export declare function createWatchFromStartButton(): Button<{
|
|
17
5
|
cssClass: string;
|
|
18
6
|
text: string;
|
|
@@ -3,7 +3,6 @@ import { sendCustomMessage } from '../../nativemobileuicontainer';
|
|
|
3
3
|
import { CustomMessages } from '../../../../types/interfaces';
|
|
4
4
|
import { ViewMode } from 'bitmovin-player';
|
|
5
5
|
import { createForwardButton, createRewindButton } from '../../components/buttons';
|
|
6
|
-
import { hidePlayNextScreen } from '../shared/playnextscreen';
|
|
7
6
|
function createBigPlayToggleButton() {
|
|
8
7
|
const bigPlayToggleButton = new PlaybackToggleButton({
|
|
9
8
|
cssClass: 'ui-playbacktogglebutton'
|
|
@@ -30,46 +29,6 @@ export function createMiddleButtons(player) {
|
|
|
30
29
|
cssClasses: ['controlbar-middle']
|
|
31
30
|
});
|
|
32
31
|
}
|
|
33
|
-
export function createPlayNextButton(player) {
|
|
34
|
-
const playNextButton = new Button({
|
|
35
|
-
cssClass: 'ui-textbutton ui-playNextButton bmpui-ui-button',
|
|
36
|
-
text: 'Volgende aflevering over <span class="countdown"></span>…',
|
|
37
|
-
ariaLabel: 'Speel volgende aflevering af',
|
|
38
|
-
hidden: false
|
|
39
|
-
});
|
|
40
|
-
const container = player.getContainer();
|
|
41
|
-
const playNextButtonClickHandler = () => {
|
|
42
|
-
sendCustomMessage(CustomMessages.PLAY_NEXT_PROCEED_CLICK);
|
|
43
|
-
hidePlayNextScreen(container, true);
|
|
44
|
-
};
|
|
45
|
-
playNextButton.onClick.subscribe(() => {
|
|
46
|
-
playNextButtonClickHandler();
|
|
47
|
-
});
|
|
48
|
-
playNextButton.onClick.unsubscribe(() => {
|
|
49
|
-
playNextButtonClickHandler();
|
|
50
|
-
});
|
|
51
|
-
return playNextButton;
|
|
52
|
-
}
|
|
53
|
-
export function createCancelPlayNextButton(player) {
|
|
54
|
-
const cancelPlayNextButton = new Button({
|
|
55
|
-
cssClass: 'ui-textbutton ui-cancelPlayNextButton bmpui-ui-button',
|
|
56
|
-
text: 'Annuleer',
|
|
57
|
-
ariaLabel: 'Annuleer',
|
|
58
|
-
hidden: false
|
|
59
|
-
});
|
|
60
|
-
const container = player.getContainer();
|
|
61
|
-
const cancelPlayNextButtonClickHandler = () => {
|
|
62
|
-
hidePlayNextScreen(container, true);
|
|
63
|
-
sendCustomMessage(CustomMessages.PLAY_NEXT_CANCEL_CLICK);
|
|
64
|
-
cancelPlayNextButton.onClick.unsubscribe(() => {
|
|
65
|
-
cancelPlayNextButtonClickHandler();
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
cancelPlayNextButton.onClick.subscribe(() => {
|
|
69
|
-
cancelPlayNextButtonClickHandler();
|
|
70
|
-
});
|
|
71
|
-
return cancelPlayNextButton;
|
|
72
|
-
}
|
|
73
32
|
export function createWatchFromStartButton() {
|
|
74
33
|
const watchFromStartButton = new Button({
|
|
75
34
|
cssClass: 'ui-textbutton ui-watchfromstartbutton bmpui-ui-button',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function showPlayNextScreenIfNeeded(overlayDuration: number, containerEl: HTMLElement, proceedCallBack?: () => void
|
|
2
|
-
export declare function hidePlayNextScreen(containerEl: HTMLElement
|
|
1
|
+
export declare function showPlayNextScreenIfNeeded(overlayDuration: number, containerEl: HTMLElement, proceedCallBack?: () => void): void;
|
|
2
|
+
export declare function hidePlayNextScreen(containerEl: HTMLElement): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
let countdownInterval = null;
|
|
2
|
+
export function showPlayNextScreenIfNeeded(overlayDuration, containerEl, proceedCallBack) {
|
|
3
|
+
const playnextOverlay = containerEl.querySelector('.bmpui-overlay-playnext');
|
|
4
|
+
if (playnextOverlay?.classList.contains('show'))
|
|
5
|
+
return;
|
|
6
|
+
playnextOverlay?.classList.add('show');
|
|
7
|
+
const playNextButton = playnextOverlay?.querySelector('.ui-playNextButton');
|
|
8
|
+
if (playNextButton) {
|
|
9
|
+
playNextButton.style.setProperty('--animation-duration', `${overlayDuration}s`);
|
|
10
|
+
playNextButton.focus();
|
|
11
|
+
}
|
|
12
|
+
let countdown = overlayDuration - 1;
|
|
13
|
+
countdownInterval = window.setInterval(() => {
|
|
14
|
+
if (playnextOverlay?.classList.contains('show')) {
|
|
15
|
+
countdown--;
|
|
16
|
+
if (countdown < 0) {
|
|
17
|
+
clearInterval(countdownInterval);
|
|
18
|
+
proceedCallBack?.();
|
|
19
|
+
playnextOverlay?.classList.remove('show');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}, 1000);
|
|
23
|
+
}
|
|
24
|
+
export function hidePlayNextScreen(containerEl) {
|
|
25
|
+
containerEl.querySelector('.bmpui-overlay-playnext')?.classList.remove('show');
|
|
26
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { showPlayNextScreenIfNeeded } from './playnextscreen';
|
|
2
|
-
import { sendCustomMessage } from '
|
|
3
|
-
|
|
4
|
-
jest.mock('../../nativemobileuicontainer');
|
|
2
|
+
import { sendCustomMessage } from '../nativemobileuicontainer';
|
|
3
|
+
jest.mock('../nativemobileuicontainer');
|
|
5
4
|
describe('showPlayNextScreenIfNeeded', () => {
|
|
6
5
|
let containerEl;
|
|
7
6
|
let playnextOverlay;
|
|
@@ -27,30 +26,21 @@ describe('showPlayNextScreenIfNeeded', () => {
|
|
|
27
26
|
});
|
|
28
27
|
it('should not show overlay if it is already shown', () => {
|
|
29
28
|
playnextOverlay.classList.add('show');
|
|
30
|
-
showPlayNextScreenIfNeeded(10, containerEl, proceedCallBack
|
|
29
|
+
showPlayNextScreenIfNeeded(10, containerEl, proceedCallBack);
|
|
31
30
|
expect(playnextOverlay.classList.contains('show')).toBe(true);
|
|
32
31
|
expect(proceedCallBack).not.toHaveBeenCalled();
|
|
33
32
|
});
|
|
34
33
|
it('should show overlay and start countdown', () => {
|
|
35
34
|
jest.useFakeTimers();
|
|
36
|
-
showPlayNextScreenIfNeeded(10, containerEl, proceedCallBack
|
|
35
|
+
showPlayNextScreenIfNeeded(10, containerEl, proceedCallBack);
|
|
37
36
|
expect(playnextOverlay.classList.contains('show')).toBe(true);
|
|
38
|
-
expect(
|
|
39
|
-
jest.advanceTimersByTime(1000);
|
|
40
|
-
expect(countdownLabel.innerHTML).toBe('9');
|
|
37
|
+
expect(playNextButton.style.getPropertyValue('--animation-duration')).toBe('10s');
|
|
41
38
|
});
|
|
42
39
|
it('should call proceedCallBack and hide overlay when countdown finishes', () => {
|
|
43
40
|
jest.useFakeTimers();
|
|
44
|
-
showPlayNextScreenIfNeeded(1, containerEl, proceedCallBack
|
|
41
|
+
showPlayNextScreenIfNeeded(1, containerEl, proceedCallBack);
|
|
45
42
|
jest.advanceTimersByTime(2000);
|
|
46
43
|
expect(proceedCallBack).toHaveBeenCalled();
|
|
47
44
|
expect(playnextOverlay.classList.contains('show')).toBe(false);
|
|
48
45
|
});
|
|
49
|
-
it('should send custom messages when nativeUI is true', () => {
|
|
50
|
-
jest.useFakeTimers();
|
|
51
|
-
showPlayNextScreenIfNeeded(5, containerEl, proceedCallBack, true);
|
|
52
|
-
jest.advanceTimersByTime(1000);
|
|
53
|
-
expect(sendCustomMessage).toHaveBeenCalledWith(CustomMessages.PLAY_NEXT_OVERLAY_SHOWN);
|
|
54
|
-
expect(sendCustomMessage).toHaveBeenCalledWith(CustomMessages.PLAY_NEXT_COUNTDOWN_PROGRESS, { playNext: { remainingCountDownDuration: 3 } });
|
|
55
|
-
});
|
|
56
46
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BufferingOverlay, CastStatusOverlay, ErrorMessageOverlay, PlaybackToggleOverlay, PlayerUtils, SubtitleOverlay, UIContainer, } from 'bitmovin-player-ui';
|
|
2
|
-
import { createPlayNextScreen } from './components/nativemobile/playnext';
|
|
3
2
|
import { createCTABar } from './components/nativemobile/ctabar';
|
|
4
3
|
import { createControlBar } from './components/nativemobile/controlbar';
|
|
5
4
|
import { createTopBar } from './components/nativemobile/topbar';
|
|
@@ -24,7 +23,6 @@ export function sendCustomMessage(message, data) {
|
|
|
24
23
|
}
|
|
25
24
|
export function nativeMobileUIContainer(player) {
|
|
26
25
|
const middleButtons = createMiddleButtons(player);
|
|
27
|
-
const playNextScreen = createPlayNextScreen(player);
|
|
28
26
|
const settingsPanel = createSettingsPanel(player);
|
|
29
27
|
const ctaBar = createCTABar(player);
|
|
30
28
|
const topBar = createTopBar(player, settingsPanel);
|
|
@@ -38,13 +36,12 @@ export function nativeMobileUIContainer(player) {
|
|
|
38
36
|
new CastStatusOverlay(),
|
|
39
37
|
middleButtons,
|
|
40
38
|
controlBar,
|
|
41
|
-
playNextScreen,
|
|
42
39
|
ctaBar,
|
|
43
40
|
topBar,
|
|
44
41
|
createTitleBar(),
|
|
45
42
|
errorMessageOverlay
|
|
46
43
|
],
|
|
47
44
|
cssClasses: ['npo-player', 'native-mobile'],
|
|
48
|
-
hidePlayerStateExceptions: [PlayerUtils.PlayerState.Paused],
|
|
45
|
+
hidePlayerStateExceptions: [PlayerUtils.PlayerState.Paused, PlayerUtils.PlayerState.Finished],
|
|
49
46
|
});
|
|
50
47
|
}
|