@npo/player 1.18.4 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/lib/js/ads/ster.js +2 -0
- package/lib/js/drm/handlers/decideprofile.js +17 -17
- package/lib/js/drm/handlers/decideprofile.test.d.ts +1 -0
- package/lib/js/drm/handlers/decideprofile.test.js +33 -0
- package/lib/js/fragments/removefragments.d.ts +1 -2
- package/lib/js/fragments/removefragments.js +1 -5
- package/lib/js/fragments/removefragments.test.d.ts +1 -0
- package/lib/js/fragments/removefragments.test.js +26 -0
- package/lib/js/fragments/setfragments.d.ts +1 -1
- package/lib/js/fragments/setfragments.js +19 -13
- package/lib/js/fragments/setfragments.test.d.ts +1 -0
- package/lib/js/fragments/setfragments.test.js +72 -0
- package/lib/js/playeractions/handlers/handleoffsets.js +8 -5
- package/lib/js/playeractions/handlers/processsourceconfig.js +13 -7
- package/lib/js/playeractions/handlers/removereplayclass.d.ts +2 -0
- package/lib/js/playeractions/handlers/removereplayclass.js +11 -0
- package/lib/js/playeractions/handlers/removereplayclass.test.d.ts +1 -0
- package/lib/js/playeractions/handlers/removereplayclass.test.js +28 -0
- package/lib/js/playeractions/handlers/resolvekeypress.test.d.ts +1 -0
- package/lib/js/playeractions/handlers/resolvekeypress.test.js +80 -0
- package/lib/js/tracking/handlers/eventlogging.test.d.ts +1 -0
- package/lib/js/tracking/handlers/eventlogging.test.js +46 -0
- package/lib/js/ui/components/controlbar.js +2 -1
- package/lib/js/ui/components/nativemobile/buttons.js +0 -1
- package/lib/js/ui/components/settingspanel.js +4 -0
- package/lib/js/ui/handlers/listboxhandlers.js +1 -0
- package/lib/js/ui/handlers/nicamhandler.d.ts +3 -0
- package/lib/js/ui/handlers/nicamhandler.js +16 -0
- package/lib/js/ui/handlers/streamhandler.js +1 -1
- package/lib/js/ui/nativemobileuicontainer.js +2 -2
- package/lib/js/ui/nativemobileuifactory.js +18 -26
- package/lib/js/ui/uicontainer.d.ts +1 -0
- package/lib/js/ui/uicontainer.js +8 -0
- package/lib/js/utilities/utilities.js +3 -4
- package/lib/npoplayer.d.ts +4 -2
- package/lib/npoplayer.js +117 -36
- package/lib/package.json +7 -7
- package/lib/src/js/drm/handlers/decideprofile.test.d.ts +1 -0
- package/lib/src/js/fragments/removefragments.d.ts +1 -2
- package/lib/src/js/fragments/removefragments.test.d.ts +1 -0
- package/lib/src/js/fragments/setfragments.d.ts +1 -1
- package/lib/src/js/fragments/setfragments.test.d.ts +1 -0
- package/lib/src/js/playeractions/handlers/removereplayclass.d.ts +2 -0
- package/lib/src/js/playeractions/handlers/removereplayclass.test.d.ts +1 -0
- package/lib/src/js/playeractions/handlers/resolvekeypress.test.d.ts +1 -0
- package/lib/src/js/tracking/handlers/eventlogging.test.d.ts +1 -0
- package/lib/src/js/ui/handlers/nicamhandler.d.ts +3 -0
- package/lib/src/js/ui/uicontainer.d.ts +1 -0
- package/lib/src/npoplayer.d.ts +4 -2
- package/lib/src/types/interfaces.d.ts +10 -0
- package/lib/tests/mocks/mockNpoplayer.d.ts +2 -0
- package/lib/tests/mocks/mockNpoplayer.js +115 -0
- package/lib/types/interfaces.d.ts +10 -0
- package/package.json +7 -7
- package/src/scss/components/_advert.scss +5 -5
- package/src/scss/components/_hugeplaybacktogglebutton.scss +1 -0
- package/src/scss/components/_metadata.scss +17 -0
- package/src/scss/components/_nicam.scss +20 -0
- package/src/scss/components/_replay.scss +2 -2
- package/src/scss/components/_seekbarthumbnail.scss +0 -1
- package/src/scss/components/_subtitles.scss +1 -1
- package/src/scss/npoplayer.css +39 -34
- package/src/scss/variants/_player-base.scss +7 -2
- package/lib/js/ui/handlers/playnexthandlers.d.ts +0 -0
- package/lib/js/ui/handlers/playnexthandlers.js +0 -1
- package/lib/src/js/ui/handlers/playnexthandlers.d.ts +0 -0
package/README.md
CHANGED
|
@@ -7,8 +7,11 @@ The video player library for NPO (Nederlandse Publieke Omroep) and other broadca
|
|
|
7
7
|
# Documentation
|
|
8
8
|
Extensive and up-to-date documentation is available at https://docs.npoplayer.nl
|
|
9
9
|
|
|
10
|
+
# Code quality
|
|
11
|
+
Code quality is analysed by SonarCloud. The project can be found at https://sonarcloud.io/project/overview?id=NPOstart_npo-player
|
|
12
|
+
|
|
10
13
|
# Changelog
|
|
11
|
-
Current version: v1.
|
|
14
|
+
Current version: v1.20.0
|
|
12
15
|
|
|
13
16
|
The changelog is available at https://docs.npoplayer.nl/implementation/web/changelog/
|
|
14
17
|
|
package/lib/js/ads/ster.js
CHANGED
|
@@ -107,6 +107,7 @@ export async function handlePreRolls(playerapi, streamObject, npoplayer) {
|
|
|
107
107
|
playerapi.on(PlayerEvent.AdFinished, handleAdFinished);
|
|
108
108
|
function handleAdBreakFinished() {
|
|
109
109
|
playerapi.off(PlayerEvent.AdBreakFinished, handleAdBreakFinished);
|
|
110
|
+
playerapi.off(PlayerEvent.AdError, handleAdBreakFinished);
|
|
110
111
|
npoplayer.adBreakActive = false;
|
|
111
112
|
npoplayer.uiComponents.adbutton?.hide();
|
|
112
113
|
npoplayer.uiComponents.adlabel?.hide();
|
|
@@ -117,5 +118,6 @@ export async function handlePreRolls(playerapi, streamObject, npoplayer) {
|
|
|
117
118
|
resolve();
|
|
118
119
|
}
|
|
119
120
|
playerapi.on(PlayerEvent.AdBreakFinished, handleAdBreakFinished);
|
|
121
|
+
playerapi.on(PlayerEvent.AdError, handleAdBreakFinished);
|
|
120
122
|
});
|
|
121
123
|
}
|
|
@@ -12,39 +12,39 @@ export async function decideProfile(player, preferredDRM = '') {
|
|
|
12
12
|
supportedTech = player.getSupportedTech();
|
|
13
13
|
supportedDRM = await player.getSupportedDRM();
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
for (const tech of supportedTech) {
|
|
16
16
|
if (bestWithoutDRM === '')
|
|
17
17
|
bestWithoutDRM = tech.streaming;
|
|
18
18
|
if (bestWithDRM !== '')
|
|
19
|
-
|
|
20
|
-
if (tech.streaming === 'dash') {
|
|
21
|
-
|
|
19
|
+
continue;
|
|
20
|
+
if (tech.streaming === 'dash' && preferredDRM !== 'fairplay') {
|
|
21
|
+
for (const drm of supportedDRM) {
|
|
22
22
|
if (bestDRM !== '' && (preferredDRM === '' || preferredDRM.length > 0)) {
|
|
23
|
-
|
|
23
|
+
continue;
|
|
24
24
|
}
|
|
25
|
-
if (['com.widevine.alpha'].includes(drm)) {
|
|
25
|
+
if (['com.widevine.alpha', 'com.microsoft.playready'].includes(drm) && preferredDRM === '') {
|
|
26
26
|
bestDRM = 'widevine';
|
|
27
27
|
}
|
|
28
|
-
if (['com.
|
|
29
|
-
bestDRM = '
|
|
28
|
+
if (['com.apple.fps.1_0', 'com.apple.fps.2_0'].includes(drm) && preferredDRM === 'fairplay') {
|
|
29
|
+
bestDRM = 'fairplay';
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|
|
32
32
|
if (bestDRM !== '')
|
|
33
33
|
bestWithDRM = 'dash';
|
|
34
34
|
}
|
|
35
|
-
if (tech.streaming === 'hls') {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (['com.apple.fps.1_0', 'com.apple.fps.2_0'].includes(drm)) {
|
|
35
|
+
if (tech.streaming === 'hls' || preferredDRM === 'fairplay') {
|
|
36
|
+
for (const drm of supportedDRM) {
|
|
37
|
+
if (bestDRM !== '' && (preferredDRM === '' || bestDRM === preferredDRM)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (['com.apple.fps.1_0', 'com.apple.fps.2_0'].includes(drm) && preferredDRM === 'fairplay') {
|
|
41
41
|
bestDRM = 'fairplay';
|
|
42
42
|
}
|
|
43
|
-
}
|
|
43
|
+
}
|
|
44
44
|
if (bestDRM !== '')
|
|
45
45
|
bestWithDRM = 'hls';
|
|
46
46
|
}
|
|
47
|
-
}
|
|
47
|
+
}
|
|
48
48
|
if (bestWithDRM !== '')
|
|
49
49
|
return { profileName: bestWithDRM, drm: bestDRM };
|
|
50
50
|
return { profileName: bestWithoutDRM, drm: bestDRM };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { decideProfile } from './decideprofile';
|
|
2
|
+
jest.mock("bitmovin-player");
|
|
3
|
+
describe("decideProfile", () => {
|
|
4
|
+
let mockPlayerAPI;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
mockPlayerAPI = {
|
|
7
|
+
getSupportedTech: jest.fn().mockReturnValue([
|
|
8
|
+
{ streaming: 'dash' },
|
|
9
|
+
{ streaming: 'hls' },
|
|
10
|
+
]),
|
|
11
|
+
getSupportedDRM: jest.fn().mockResolvedValue([
|
|
12
|
+
'com.widevine.alpha',
|
|
13
|
+
'com.microsoft.playready',
|
|
14
|
+
'com.apple.fps.1_0',
|
|
15
|
+
]),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
it("should return empty profile when player is null", async () => {
|
|
22
|
+
const result = await decideProfile(null);
|
|
23
|
+
expect(result).toEqual({ profileName: '', drm: '' });
|
|
24
|
+
});
|
|
25
|
+
it("should return profile with dash streaming and widevine DRM", async () => {
|
|
26
|
+
const result = await decideProfile(mockPlayerAPI);
|
|
27
|
+
expect(result).toEqual({ profileName: 'dash', drm: 'widevine' });
|
|
28
|
+
});
|
|
29
|
+
it("should return profile with hls streaming and fairplay DRM", async () => {
|
|
30
|
+
const result = await decideProfile(mockPlayerAPI, 'fairplay');
|
|
31
|
+
expect(result).toEqual({ profileName: 'hls', drm: 'fairplay' });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import { checkFunction, seekFunction } from './setfragments';
|
|
3
|
-
export async function removeFragments(player, uiManager) {
|
|
1
|
+
export function removeFragments(uiManager) {
|
|
4
2
|
uiManager.getTimelineMarkers().forEach(marker => {
|
|
5
3
|
uiManager.removeTimelineMarker(marker);
|
|
6
4
|
});
|
|
7
|
-
player.off(PlayerEvent.TimeChanged, checkFunction());
|
|
8
|
-
player.off(PlayerEvent.Seek, seekFunction());
|
|
9
5
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { removeFragments } from './removefragments';
|
|
2
|
+
describe("removeFragments", () => {
|
|
3
|
+
it("should remove all timeline markers", () => {
|
|
4
|
+
const mockUIManager = {
|
|
5
|
+
getTimelineMarkers: jest.fn().mockReturnValue([
|
|
6
|
+
{ id: 1, time: 10 },
|
|
7
|
+
{ id: 2, time: 20 },
|
|
8
|
+
{ id: 3, time: 30 },
|
|
9
|
+
]),
|
|
10
|
+
removeTimelineMarker: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
removeFragments(mockUIManager);
|
|
13
|
+
expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledTimes(3);
|
|
14
|
+
expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledWith({ id: 1, time: 10 });
|
|
15
|
+
expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledWith({ id: 2, time: 20 });
|
|
16
|
+
expect(mockUIManager.removeTimelineMarker).toHaveBeenCalledWith({ id: 3, time: 30 });
|
|
17
|
+
});
|
|
18
|
+
it("should not remove any timeline markers if there are none", () => {
|
|
19
|
+
const mockUIManager = {
|
|
20
|
+
getTimelineMarkers: jest.fn().mockReturnValue([]),
|
|
21
|
+
removeTimelineMarker: jest.fn(),
|
|
22
|
+
};
|
|
23
|
+
removeFragments(mockUIManager);
|
|
24
|
+
expect(mockUIManager.removeTimelineMarker).not.toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -3,4 +3,4 @@ import { type Fragments } from '../../types/interfaces';
|
|
|
3
3
|
import { UIManager } from 'bitmovin-player-ui';
|
|
4
4
|
export declare const checkFunction: () => (e: any) => void;
|
|
5
5
|
export declare const seekFunction: () => (e: any) => void;
|
|
6
|
-
export declare function setFragments(player: PlayerAPI, uiManager: UIManager, fragments: Fragments):
|
|
6
|
+
export declare function setFragments(player: PlayerAPI, uiManager: UIManager, fragments: Fragments): void;
|
|
@@ -2,20 +2,25 @@ import { PlayerEvent } from 'bitmovin-player';
|
|
|
2
2
|
import { removeFragments } from './removefragments';
|
|
3
3
|
let fragment = null;
|
|
4
4
|
let playerInstance = null;
|
|
5
|
+
let isFinished = false;
|
|
5
6
|
const setTitle = () => {
|
|
6
|
-
const element =
|
|
7
|
-
if (element !== null && element
|
|
7
|
+
const element = playerInstance?.getContainer()?.querySelector('.bmpui-npo-player .bmpui-seekbar-label-title');
|
|
8
|
+
if (element !== null && element?.textContent === '') {
|
|
8
9
|
element.textContent = fragment?.title || '';
|
|
9
10
|
}
|
|
10
11
|
};
|
|
11
12
|
export const checkFunction = () => (e) => {
|
|
12
13
|
if (!fragment || !playerInstance)
|
|
13
14
|
return;
|
|
14
|
-
|
|
15
|
+
playerInstance.off(PlayerEvent.TimeChanged, checkFunction());
|
|
16
|
+
if (e.time > fragment.end && !isFinished) {
|
|
17
|
+
isFinished = true;
|
|
15
18
|
playerInstance.seek(Math.max(fragment.start, fragment.end));
|
|
16
19
|
playerInstance.pause();
|
|
20
|
+
playerInstance?.getContainer().querySelector('.bmpui-npo-player')?.classList.add('bmpui-player-state-replay');
|
|
17
21
|
}
|
|
18
|
-
else if (e.time < fragment.start) {
|
|
22
|
+
else if (e.time < fragment.start || (e.time > fragment.end && isFinished)) {
|
|
23
|
+
isFinished = false;
|
|
19
24
|
playerInstance.seek(fragment.start);
|
|
20
25
|
}
|
|
21
26
|
setTitle();
|
|
@@ -23,13 +28,14 @@ export const checkFunction = () => (e) => {
|
|
|
23
28
|
export const seekFunction = () => (e) => {
|
|
24
29
|
if (!fragment || !playerInstance)
|
|
25
30
|
return;
|
|
31
|
+
playerInstance.off(PlayerEvent.Seek, seekFunction());
|
|
26
32
|
const seekTarget = e.seekTarget ? e.seekTarget : e.to.time;
|
|
27
33
|
if (seekTarget > fragment.end || seekTarget < fragment.start) {
|
|
28
34
|
playerInstance.seek(Math.max(fragment.start, Math.min(fragment.end, seekTarget)));
|
|
29
35
|
}
|
|
30
36
|
setTitle();
|
|
31
37
|
};
|
|
32
|
-
export
|
|
38
|
+
export function setFragments(player, uiManager, fragments) {
|
|
33
39
|
fragment = fragments.sections[0] || null;
|
|
34
40
|
playerInstance = player;
|
|
35
41
|
const unplayableClass = 'seekbar-marker-unplayable';
|
|
@@ -37,12 +43,16 @@ export async function setFragments(player, uiManager, fragments) {
|
|
|
37
43
|
if (!fragment)
|
|
38
44
|
return;
|
|
39
45
|
player.seek(fragment.start);
|
|
40
|
-
if (player.isPaused())
|
|
46
|
+
if (player.isPaused() && playerInstance?.getConfig().playback?.autoplay === true) {
|
|
41
47
|
void player.play();
|
|
48
|
+
}
|
|
42
49
|
};
|
|
43
50
|
const addMarkers = () => {
|
|
44
51
|
if (!fragment)
|
|
45
52
|
return;
|
|
53
|
+
playerInstance?.off(PlayerEvent.Ready, addMarkers);
|
|
54
|
+
removeFragments(uiManager);
|
|
55
|
+
setTitle();
|
|
46
56
|
const timelineMarkers = [
|
|
47
57
|
{
|
|
48
58
|
time: 0,
|
|
@@ -61,12 +71,8 @@ export async function setFragments(player, uiManager, fragments) {
|
|
|
61
71
|
uiManager.addTimelineMarker(marker);
|
|
62
72
|
});
|
|
63
73
|
playFragmentFromStart();
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
playerInstance?.on(PlayerEvent.TimeChanged, checkFunction());
|
|
75
|
+
playerInstance?.on(PlayerEvent.Seek, seekFunction());
|
|
66
76
|
};
|
|
67
|
-
|
|
68
|
-
player.off(PlayerEvent.Seek, seekFunction());
|
|
69
|
-
await removeFragments(player, uiManager);
|
|
70
|
-
addMarkers();
|
|
71
|
-
setTitle();
|
|
77
|
+
playerInstance?.on(PlayerEvent.Ready, addMarkers);
|
|
72
78
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { setFragments } from './setfragments';
|
|
2
|
+
import { PlayerEvent } from 'bitmovin-player';
|
|
3
|
+
const SEEKBAR_MARKER_UNPLAYABLE = 'seekbar-marker-unplayable';
|
|
4
|
+
describe("setFragments", () => {
|
|
5
|
+
let mockPlayer;
|
|
6
|
+
let mockUIManager;
|
|
7
|
+
let fragments;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
mockPlayer = {
|
|
11
|
+
seek: jest.fn(),
|
|
12
|
+
isPaused: jest.fn().mockReturnValue(true),
|
|
13
|
+
play: jest.fn(),
|
|
14
|
+
pause: jest.fn(),
|
|
15
|
+
off: jest.fn(),
|
|
16
|
+
on: jest.fn().mockImplementation((event, handler) => {
|
|
17
|
+
if (event === PlayerEvent.Ready) {
|
|
18
|
+
handler();
|
|
19
|
+
}
|
|
20
|
+
}),
|
|
21
|
+
getContainer: jest.fn().mockReturnValue({
|
|
22
|
+
querySelector: jest.fn().mockReturnValue({
|
|
23
|
+
textContent: '',
|
|
24
|
+
classList: {
|
|
25
|
+
add: jest.fn()
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
getConfig: jest.fn().mockReturnValue({
|
|
30
|
+
playback: {
|
|
31
|
+
autoplay: true
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
};
|
|
35
|
+
mockUIManager = {
|
|
36
|
+
addTimelineMarker: jest.fn(),
|
|
37
|
+
getTimelineMarkers: jest.fn().mockReturnValue([]),
|
|
38
|
+
removeTimelineMarker: jest.fn()
|
|
39
|
+
};
|
|
40
|
+
fragments = {
|
|
41
|
+
sections: [
|
|
42
|
+
{
|
|
43
|
+
start: 10,
|
|
44
|
+
end: 20,
|
|
45
|
+
title: 'Test Fragment',
|
|
46
|
+
cssClasses: [SEEKBAR_MARKER_UNPLAYABLE]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
it("should set fragments correctly and handle autoplay", async () => {
|
|
52
|
+
setFragments(mockPlayer, mockUIManager, fragments);
|
|
53
|
+
expect(mockPlayer.seek).toHaveBeenCalledWith(fragments.sections[0].start);
|
|
54
|
+
expect(mockPlayer.play).toHaveBeenCalled();
|
|
55
|
+
expect(mockUIManager.addTimelineMarker).toHaveBeenCalledTimes(2);
|
|
56
|
+
});
|
|
57
|
+
it("should correctly set the title of the fragment", async () => {
|
|
58
|
+
setFragments(mockPlayer, mockUIManager, fragments);
|
|
59
|
+
expect(mockUIManager.addTimelineMarker).toHaveBeenCalledWith({
|
|
60
|
+
time: 0,
|
|
61
|
+
title: "Niet beschikbaar",
|
|
62
|
+
cssClasses: [SEEKBAR_MARKER_UNPLAYABLE],
|
|
63
|
+
duration: 10
|
|
64
|
+
});
|
|
65
|
+
expect(mockUIManager.addTimelineMarker).toHaveBeenCalledWith({
|
|
66
|
+
time: 20,
|
|
67
|
+
duration: 10000000,
|
|
68
|
+
title: "Niet beschikbaar",
|
|
69
|
+
cssClasses: [SEEKBAR_MARKER_UNPLAYABLE],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -6,12 +6,15 @@ export function handleStartOffset(player, offset) {
|
|
|
6
6
|
}
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
const seek = () => {
|
|
10
|
+
player.off(PlayerEvent.SourceLoaded, seek);
|
|
11
|
+
player.off(PlayerEvent.AdBreakFinished, seek);
|
|
12
|
+
player.off(PlayerEvent.AdError, seek);
|
|
10
13
|
player.seek(offset);
|
|
11
|
-
}
|
|
12
|
-
player.on(PlayerEvent.
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
};
|
|
15
|
+
player.on(PlayerEvent.SourceLoaded, seek);
|
|
16
|
+
player.on(PlayerEvent.AdBreakFinished, seek);
|
|
17
|
+
player.on(PlayerEvent.AdError, seek);
|
|
15
18
|
}
|
|
16
19
|
export function shiftToProgramStart(player, timestamp) {
|
|
17
20
|
if (player == null)
|
|
@@ -36,13 +36,19 @@ function setDrm(sourceConfig, drm, _streamObject) {
|
|
|
36
36
|
};
|
|
37
37
|
break;
|
|
38
38
|
case 'widevine':
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
fetch('https://npo-drm-gateway.samgcloud.nepworldwide.nl/widevine_player_cert.bin')
|
|
40
|
+
.then(response => response.arrayBuffer())
|
|
41
|
+
.then(certificate => {
|
|
42
|
+
sourceConfig.drm = {
|
|
43
|
+
widevine: {
|
|
44
|
+
serverCertificate: certificate,
|
|
45
|
+
LA_URL: npoDrmGateway + _streamObject.stream.drmToken,
|
|
46
|
+
audioRobustness: 'SW_SECURE_CRYPTO',
|
|
47
|
+
videoRobustness: 'SW_SECURE_CRYPTO'
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
})
|
|
51
|
+
.catch(error => console.error('Error:', error));
|
|
46
52
|
break;
|
|
47
53
|
case 'playready':
|
|
48
54
|
sourceConfig.drm = {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PlayerEvent } from 'bitmovin-player';
|
|
2
|
+
export function removeReplayClass(player) {
|
|
3
|
+
if (!player)
|
|
4
|
+
return;
|
|
5
|
+
player.off(PlayerEvent.Seek, () => {
|
|
6
|
+
removeReplayClass(player);
|
|
7
|
+
});
|
|
8
|
+
const playerContainer = player.getContainer();
|
|
9
|
+
playerContainer.classList.remove('bmpui-player-state-finished');
|
|
10
|
+
playerContainer.classList.remove('bmpui-player-state-replay');
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { removeReplayClass } from './removereplayclass';
|
|
2
|
+
import { PlayerEvent } from 'bitmovin-player';
|
|
3
|
+
describe('removeReplayClass', () => {
|
|
4
|
+
let mockPlayer;
|
|
5
|
+
let mockPlayerContainer;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.clearAllMocks();
|
|
8
|
+
mockPlayerContainer = document.createElement('div');
|
|
9
|
+
mockPlayerContainer.classList.add('bmpui-player-state-finished', 'bmpui-player-state-replay');
|
|
10
|
+
mockPlayer = {
|
|
11
|
+
off: jest.fn(),
|
|
12
|
+
getContainer: jest.fn().mockReturnValue(mockPlayerContainer),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
it('should remove "bmpui-player-state-finished" and "bmpui-player-state-replay" classes from the player container', () => {
|
|
16
|
+
removeReplayClass(mockPlayer);
|
|
17
|
+
expect(mockPlayerContainer.classList.contains('bmpui-player-state-finished')).toBe(false);
|
|
18
|
+
expect(mockPlayerContainer.classList.contains('bmpui-player-state-replay')).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
it('should unregister the Seek event listener', () => {
|
|
21
|
+
removeReplayClass(mockPlayer);
|
|
22
|
+
expect(mockPlayer.off).toHaveBeenCalledWith(PlayerEvent.Seek, expect.any(Function));
|
|
23
|
+
});
|
|
24
|
+
it('should not unregister the Seek event listener if the player is null', () => {
|
|
25
|
+
removeReplayClass(null);
|
|
26
|
+
expect(mockPlayer.off).not.toHaveBeenCalled();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { resolveKeyPress } from './resolvekeypress';
|
|
2
|
+
import { ViewMode } from 'bitmovin-player';
|
|
3
|
+
import { mockNpoPlayer } from '../../../../tests/mocks/mockNpoplayer';
|
|
4
|
+
describe('resolveKeyPress', () => {
|
|
5
|
+
let mockPlayer;
|
|
6
|
+
let mockNpoplayer = mockNpoPlayer;
|
|
7
|
+
let e;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
mockPlayer = {
|
|
11
|
+
seek: jest.fn(),
|
|
12
|
+
isPaused: jest.fn().mockReturnValue(true),
|
|
13
|
+
play: jest.fn(),
|
|
14
|
+
pause: jest.fn(),
|
|
15
|
+
isMuted: jest.fn(),
|
|
16
|
+
unmute: jest.fn(),
|
|
17
|
+
getViewMode: jest.fn(),
|
|
18
|
+
setViewMode: jest.fn(),
|
|
19
|
+
getContainer: jest.fn().mockReturnValue({
|
|
20
|
+
querySelector: jest.fn().mockReturnValue({
|
|
21
|
+
textContent: '',
|
|
22
|
+
classList: {
|
|
23
|
+
add: jest.fn()
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}),
|
|
27
|
+
getConfig: jest.fn().mockReturnValue({
|
|
28
|
+
playback: {
|
|
29
|
+
autoplay: true
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
e = new KeyboardEvent('keydown');
|
|
34
|
+
});
|
|
35
|
+
it('should mute/unmute the player when "KeyM" is pressed', () => {
|
|
36
|
+
mockPlayer.isMuted.mockReturnValue(true);
|
|
37
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'KeyM' });
|
|
38
|
+
expect(mockPlayer.unmute).toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
it('should enter/exit fullscreen mode when "KeyF" is pressed', () => {
|
|
41
|
+
mockPlayer.getViewMode.mockReturnValue(ViewMode.Inline);
|
|
42
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'KeyF' });
|
|
43
|
+
expect(mockPlayer.setViewMode).toHaveBeenCalledWith(ViewMode.Fullscreen);
|
|
44
|
+
});
|
|
45
|
+
it('should play/pause the player when "Space" is pressed', () => {
|
|
46
|
+
mockPlayer.isPaused.mockReturnValue(true);
|
|
47
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'Space' });
|
|
48
|
+
expect(mockPlayer.play).toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
it('should increase the volume when "ArrowUp" is pressed', () => {
|
|
51
|
+
mockPlayer.isMuted.mockReturnValue(true);
|
|
52
|
+
mockNpoplayer.increaseVolume = jest.fn();
|
|
53
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowUp' });
|
|
54
|
+
expect(mockPlayer.unmute).toHaveBeenCalled();
|
|
55
|
+
expect(mockNpoplayer.increaseVolume).toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
it('should decrease the volume when "ArrowDown" is pressed', () => {
|
|
58
|
+
mockPlayer.isMuted.mockReturnValue(true);
|
|
59
|
+
mockNpoplayer.decreaseVolume = jest.fn();
|
|
60
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowDown' });
|
|
61
|
+
expect(mockPlayer.unmute).toHaveBeenCalled();
|
|
62
|
+
expect(mockNpoplayer.decreaseVolume).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
it('should go backwards when "ArrowLeft" is pressed', () => {
|
|
65
|
+
mockNpoplayer.goBackwards = jest.fn();
|
|
66
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowLeft' });
|
|
67
|
+
expect(mockNpoplayer.goBackwards).toHaveBeenCalledWith(10);
|
|
68
|
+
});
|
|
69
|
+
it('should go forward when "ArrowRight" is pressed', () => {
|
|
70
|
+
mockNpoplayer.goForward = jest.fn();
|
|
71
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'ArrowRight' });
|
|
72
|
+
expect(mockNpoplayer.goForward).toHaveBeenCalledWith(10);
|
|
73
|
+
});
|
|
74
|
+
it('should hide the settings panel when "Escape" is pressed', () => {
|
|
75
|
+
if (mockNpoplayer.uiComponents.settingsPanel) {
|
|
76
|
+
resolveKeyPress(mockPlayer, mockNpoplayer, { ...e, code: 'Escape' });
|
|
77
|
+
expect(mockNpoplayer.uiComponents.settingsPanel.hide).toHaveBeenCalled();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { logEvent } from './eventlogging';
|
|
2
|
+
import { mockNpoPlayer } from '../../../../tests/mocks/mockNpoplayer';
|
|
3
|
+
describe('logEvent', () => {
|
|
4
|
+
let npoplayer;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
npoplayer = mockNpoPlayer;
|
|
7
|
+
});
|
|
8
|
+
it('should not log event if player is not available', () => {
|
|
9
|
+
npoplayer.player = null;
|
|
10
|
+
if (npoplayer.streamTracker && npoplayer.player) {
|
|
11
|
+
logEvent(npoplayer, 'start');
|
|
12
|
+
expect(npoplayer.streamTracker.start).not.toHaveBeenCalled();
|
|
13
|
+
expect(npoplayer.logEmitter.emit).not.toHaveBeenCalled();
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
it('should not log event if streamTracker is not available', () => {
|
|
17
|
+
npoplayer.streamTracker = null;
|
|
18
|
+
if (npoplayer.streamTracker) {
|
|
19
|
+
logEvent(npoplayer, 'start');
|
|
20
|
+
expect(npoplayer.logEmitter.emit).not.toHaveBeenCalled();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
it('should not log event if adBreakActive is true', () => {
|
|
24
|
+
npoplayer.adBreakActive = true;
|
|
25
|
+
if (npoplayer.streamTracker) {
|
|
26
|
+
logEvent(npoplayer, 'start');
|
|
27
|
+
expect(npoplayer.streamTracker.start).not.toHaveBeenCalled();
|
|
28
|
+
expect(npoplayer.logEmitter.emit).not.toHaveBeenCalled();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
it('should log event and call the corresponding streamTracker handler', () => {
|
|
32
|
+
if (npoplayer.streamTracker) {
|
|
33
|
+
logEvent(npoplayer, 'start');
|
|
34
|
+
expect(npoplayer.streamTracker.start).toHaveBeenCalledWith({ stream_position: 10 });
|
|
35
|
+
expect(npoplayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'start');
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
it('should log event and call the seek streamTracker handler with data', () => {
|
|
39
|
+
const data = { position: 20 };
|
|
40
|
+
if (npoplayer.streamTracker) {
|
|
41
|
+
logEvent(npoplayer, 'seek', data);
|
|
42
|
+
expect(npoplayer.streamTracker.seek).toHaveBeenCalledWith(data);
|
|
43
|
+
expect(npoplayer.logEmitter.emit).toHaveBeenCalledWith('logEvent', 'seek');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -12,8 +12,9 @@ export function createControlBar(npoplayer, settingsPanel) {
|
|
|
12
12
|
hidden: true
|
|
13
13
|
});
|
|
14
14
|
player?.on(PlayerEvent.CastAvailable, () => {
|
|
15
|
-
if (player.isCastAvailable())
|
|
15
|
+
if (player.isCastAvailable()) {
|
|
16
16
|
chromeCastButton.show();
|
|
17
|
+
}
|
|
17
18
|
});
|
|
18
19
|
const controlbar = new ControlBar({
|
|
19
20
|
components: [
|
|
@@ -124,7 +124,6 @@ export function createPlaybackToggleButton() {
|
|
|
124
124
|
const playbackClickHandler = () => {
|
|
125
125
|
if (!customPlayerbackToggleButton)
|
|
126
126
|
return;
|
|
127
|
-
console.log('TOGGLE_PLAY_PAUSE', !customPlayerbackToggleButton.isOn());
|
|
128
127
|
sendCustomMessage(CustomMessages.TOGGLE_PLAY_PAUSE, { isChecked: !customPlayerbackToggleButton.isOn() });
|
|
129
128
|
};
|
|
130
129
|
customPlayerbackToggleButton.onClick.unsubscribe(() => {
|
|
@@ -133,6 +133,7 @@ export function createSettingsPanel(npoplayer) {
|
|
|
133
133
|
const checkSubtitles = () => {
|
|
134
134
|
player?.off(PlayerEvent.SourceLoaded, checkSubtitles);
|
|
135
135
|
player?.off(PlayerEvent.AdBreakFinished, checkSubtitles);
|
|
136
|
+
player?.off(PlayerEvent.AdError, checkSubtitles);
|
|
136
137
|
const subtitles = player?.subtitles?.list();
|
|
137
138
|
if (subtitles != null && subtitles.length > 0) {
|
|
138
139
|
subtitlesPanelItem.show();
|
|
@@ -144,6 +145,7 @@ export function createSettingsPanel(npoplayer) {
|
|
|
144
145
|
const checkQuality = () => {
|
|
145
146
|
player?.off(PlayerEvent.SourceLoaded, checkQuality);
|
|
146
147
|
player?.off(PlayerEvent.AdBreakFinished, checkQuality);
|
|
148
|
+
player?.off(PlayerEvent.AdError, checkQuality);
|
|
147
149
|
if (player?.getAvailableVideoQualities() !== undefined && player?.getAvailableVideoQualities().length > 0) {
|
|
148
150
|
qualityPanelItem.show();
|
|
149
151
|
}
|
|
@@ -155,5 +157,7 @@ export function createSettingsPanel(npoplayer) {
|
|
|
155
157
|
player?.on(PlayerEvent.SourceLoaded, checkQuality);
|
|
156
158
|
player?.on(PlayerEvent.AdBreakFinished, checkSubtitles);
|
|
157
159
|
player?.on(PlayerEvent.AdBreakFinished, checkQuality);
|
|
160
|
+
player?.on(PlayerEvent.AdError, checkSubtitles);
|
|
161
|
+
player?.on(PlayerEvent.AdError, checkQuality);
|
|
158
162
|
return settingsPanel;
|
|
159
163
|
}
|
|
@@ -9,6 +9,7 @@ export function handleSubtitleListBoxItemSelected(subtitleSettingsOpenButton, se
|
|
|
9
9
|
e.getItems().forEach((item) => {
|
|
10
10
|
if (item.key === e.getSelectedItem()) {
|
|
11
11
|
subtitleSettingsOpenButton.setText(item.label);
|
|
12
|
+
e.selectItem(item.key);
|
|
12
13
|
}
|
|
13
14
|
});
|
|
14
15
|
closeSettingsPanel(settingsPanel, mainSettingsPage);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { PlayerAPI } from 'bitmovin-player';
|
|
2
|
+
import { UIManager } from 'bitmovin-player-ui';
|
|
1
3
|
import { StreamObject, StreamOptions } from 'types/interfaces';
|
|
2
4
|
export declare function processNicam(streamObject: StreamObject, nicamElement: HTMLElement | null, streamOptions: StreamOptions | null): void;
|
|
3
5
|
export declare function addNicamIcon(character: string, nicamElement: Element): void;
|
|
6
|
+
export declare function showNicamAfterUiDelay(player: PlayerAPI, uiManager: UIManager | null): void;
|