@npo/player 1.20.4 → 1.21.1
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 +5 -0
- package/lib/js/api/getstreamobject.test.js +2 -2
- package/lib/js/markers/updateLiveMarkers.d.ts +4 -0
- package/lib/js/markers/updateLiveMarkers.js +21 -0
- package/lib/js/markers/updateLiveMarkers.test.d.ts +1 -0
- package/lib/js/markers/updateLiveMarkers.test.js +53 -0
- package/lib/js/playeractions/handlers/error.d.ts +2 -2
- package/lib/js/playeractions/handlers/processsourceconfig.d.ts +1 -1
- package/lib/js/playeractions/handlers/processsourceconfig.js +7 -7
- package/lib/js/ui/components/adbutton.js +1 -1
- package/lib/js/ui/components/buttons.js +2 -2
- package/lib/js/ui/components/nativemobile/buttons.d.ts +7 -1
- package/lib/js/ui/components/nativemobile/buttons.js +27 -4
- package/lib/js/ui/components/nativemobile/controlbar.js +1 -2
- package/lib/js/ui/components/nativemobile/playnext.d.ts +2 -1
- package/lib/js/ui/components/nativemobile/playnext.js +5 -4
- package/lib/js/ui/components/settingspanel.js +30 -23
- package/lib/js/ui/components/shared/playnextscreen.d.ts +2 -2
- package/lib/js/ui/components/shared/playnextscreen.js +14 -2
- package/lib/js/ui/components/shared/playnextstreen.test.d.ts +6 -0
- package/lib/js/ui/components/shared/playnextstreen.test.js +56 -0
- package/lib/js/ui/handlers/accessibilityhandler.js +26 -2
- package/lib/js/ui/handlers/accessibilityhandler.test.d.ts +1 -0
- package/lib/js/ui/handlers/accessibilityhandler.test.js +63 -0
- package/lib/js/ui/handlers/domhandlers.d.ts +3 -0
- package/lib/js/ui/handlers/domhandlers.js +7 -0
- package/lib/js/ui/handlers/domhandlers.test.d.ts +1 -0
- package/lib/js/ui/handlers/domhandlers.test.js +49 -0
- package/lib/js/ui/handlers/nicamhandler.js +1 -1
- package/lib/js/ui/handlers/streamhandler.js +4 -4
- package/lib/js/ui/nativemobileuicontainer.js +5 -5
- package/lib/js/ui/nativemobileuifactory.js +82 -42
- package/lib/js/ui/nativemobileuifactory.test.d.ts +1 -0
- package/lib/js/ui/nativemobileuifactory.test.js +66 -0
- package/lib/js/ui/uicontainer.js +4 -3
- package/lib/js/utilities/utilities.d.ts +3 -0
- package/lib/js/utilities/utilities.js +9 -0
- package/lib/lang/nl.json +1 -1
- package/lib/npoplayer-bridge.test.d.ts +1 -0
- package/lib/npoplayer-bridge.test.js +23 -0
- package/lib/npoplayer.d.ts +2 -1
- package/lib/npoplayer.js +13 -4
- package/lib/npoplayer.test.js +4 -4
- package/lib/package.json +3 -2
- package/lib/src/js/markers/updateLiveMarkers.d.ts +4 -0
- package/lib/src/js/markers/updateLiveMarkers.test.d.ts +1 -0
- package/lib/src/js/playeractions/handlers/error.d.ts +2 -2
- package/lib/src/js/playeractions/handlers/processsourceconfig.d.ts +1 -1
- package/lib/src/js/ui/components/nativemobile/buttons.d.ts +7 -1
- package/lib/src/js/ui/components/nativemobile/playnext.d.ts +2 -1
- package/lib/src/js/ui/components/shared/playnextscreen.d.ts +2 -2
- package/lib/src/js/ui/components/shared/playnextstreen.test.d.ts +6 -0
- package/lib/src/js/ui/handlers/accessibilityhandler.test.d.ts +1 -0
- package/lib/src/js/ui/handlers/domhandlers.d.ts +3 -0
- package/lib/src/js/ui/handlers/domhandlers.test.d.ts +1 -0
- package/lib/src/js/ui/nativemobileuifactory.test.d.ts +1 -0
- package/lib/src/js/utilities/utilities.d.ts +3 -0
- package/lib/src/npoplayer-bridge.test.d.ts +1 -0
- package/lib/src/npoplayer.d.ts +2 -1
- package/lib/src/types/interfaces.d.ts +26 -2
- package/lib/tests/mocks/mockNpoplayer.js +4 -1
- package/lib/types/interfaces.d.ts +26 -2
- package/lib/types/interfaces.js +12 -2
- package/package.json +3 -2
- package/src/scss/components/_advert.scss +34 -68
- package/src/scss/components/_hugeplaybacktogglebutton.scss +11 -0
- package/src/scss/components/_icons.scss +50 -40
- package/src/scss/components/_nicam.scss +9 -2
- package/src/scss/components/_playnext.scss +5 -1
- package/src/scss/components/_replay.scss +1 -1
- package/src/scss/components/_seekbar.scss +13 -0
- package/src/scss/components/_settingspanel.scss +37 -12
- package/src/scss/components/_textbuttons.scss +12 -11
- package/src/scss/components/_volumeslider.scss +5 -0
- package/src/scss/components/audio/_playbutton.scss +6 -0
- package/src/scss/components/audio/_volumeslider.scss +6 -1
- package/src/scss/npoplayer.css +85 -56
- package/src/scss/npoplayer.scss +3 -0
- package/src/scss/variants/_player-base.scss +1 -9
- package/src/scss/variants/_player-large.scss +20 -0
- package/src/scss/variants/_player-medium.scss +31 -1
- package/src/scss/variants/_player-native-mobile.scss +4 -0
- package/src/scss/variants/_player-small.scss +41 -8
- package/src/scss/vars/_colors.scss +1 -0
package/README.md
CHANGED
|
@@ -21,3 +21,8 @@ MIT
|
|
|
21
21
|
|
|
22
22
|
[JWT.IO - JSON Web Tokens Introduction]: https://jwt.io/introduction
|
|
23
23
|
[self generated JWT]: https://www.npmjs.com/package/jsonwebtoken
|
|
24
|
+
|
|
25
|
+
## Native mobile development
|
|
26
|
+
|
|
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
|
+
|
|
@@ -9,9 +9,9 @@ describe('getStreamObject', () => {
|
|
|
9
9
|
let npoplayer;
|
|
10
10
|
let apiPayload;
|
|
11
11
|
const testPlayerConfig = {
|
|
12
|
-
key:
|
|
12
|
+
key: 'dummy key',
|
|
13
13
|
};
|
|
14
|
-
const div = document.createElement(
|
|
14
|
+
const div = document.createElement('div');
|
|
15
15
|
beforeEach(() => {
|
|
16
16
|
npoplayer = new NpoPlayer(div, testPlayerConfig);
|
|
17
17
|
apiPayload = {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { PlayerAPI } from "bitmovin-player";
|
|
2
|
+
import { UIManager } from "bitmovin-player-ui";
|
|
3
|
+
import { TimeLineMarker } from "types/interfaces";
|
|
4
|
+
export declare function updateLiveMarkers(timeLineMarkers: TimeLineMarker[], player: PlayerAPI, uiManager: UIManager): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function updateLiveMarkers(timeLineMarkers, player, uiManager) {
|
|
2
|
+
if (!player || !uiManager) {
|
|
3
|
+
console.error('Player or UIManager is null');
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
timeLineMarkers.sort((a, b) => a.time - b.time);
|
|
7
|
+
let markesWithClass = timeLineMarkers.map((marker) => {
|
|
8
|
+
return {
|
|
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 => {
|
|
19
|
+
uiManager?.addTimelineMarker(marker);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { updateLiveMarkers } from './updateLiveMarkers';
|
|
2
|
+
import { UIManager } from "bitmovin-player-ui";
|
|
3
|
+
jest.mock("bitmovin-player-ui", () => {
|
|
4
|
+
return {
|
|
5
|
+
UIManager: jest.fn().mockImplementation(() => ({
|
|
6
|
+
getTimelineMarkers: jest.fn().mockReturnValue([]),
|
|
7
|
+
addTimelineMarker: jest.fn(),
|
|
8
|
+
removeTimelineMarker: jest.fn(),
|
|
9
|
+
})),
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
jest.mock("bitmovin-player", () => ({
|
|
13
|
+
PlayerAPI: jest.fn(),
|
|
14
|
+
}));
|
|
15
|
+
describe('updateLiveMarkers', () => {
|
|
16
|
+
const player = {
|
|
17
|
+
getCurrentTime: jest.fn().mockReturnValue(10),
|
|
18
|
+
};
|
|
19
|
+
let uiManager;
|
|
20
|
+
let ui;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
uiManager = new UIManager(player, ui);
|
|
23
|
+
});
|
|
24
|
+
test('should correctly update UI manager with sorted and transformed markers', () => {
|
|
25
|
+
const timeLineMarkers = [
|
|
26
|
+
{ time: 3000, title: 'Marker 2' },
|
|
27
|
+
{ time: 1000, title: 'Marker 1' },
|
|
28
|
+
];
|
|
29
|
+
updateLiveMarkers(timeLineMarkers, player, uiManager);
|
|
30
|
+
expect(uiManager.getTimelineMarkers).toHaveBeenCalled();
|
|
31
|
+
expect(uiManager.removeTimelineMarker).toHaveBeenCalledTimes(0);
|
|
32
|
+
expect(uiManager.addTimelineMarker).toHaveBeenCalledTimes(2);
|
|
33
|
+
expect(uiManager.addTimelineMarker).toHaveBeenCalledWith(expect.objectContaining({
|
|
34
|
+
time: 1000,
|
|
35
|
+
title: 'Marker 1',
|
|
36
|
+
cssClasses: ['npo-livestream-marker'],
|
|
37
|
+
}));
|
|
38
|
+
expect(uiManager.addTimelineMarker).toHaveBeenCalledWith(expect.objectContaining({
|
|
39
|
+
time: 3000,
|
|
40
|
+
title: 'Marker 2',
|
|
41
|
+
cssClasses: ['npo-livestream-marker'],
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
44
|
+
test('should remove existing markers before adding new ones', () => {
|
|
45
|
+
uiManager.getTimelineMarkers.mockReturnValue([{ time: 2000, title: 'Existing Marker' }]);
|
|
46
|
+
const timeLineMarkers = [
|
|
47
|
+
{ time: 1000, title: 'New Marker' },
|
|
48
|
+
];
|
|
49
|
+
updateLiveMarkers(timeLineMarkers, player, uiManager);
|
|
50
|
+
expect(uiManager.removeTimelineMarker).toHaveBeenCalledTimes(1);
|
|
51
|
+
expect(uiManager.addTimelineMarker).toHaveBeenCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { PlayerAPI } from
|
|
2
|
-
import { UIComponents } from
|
|
1
|
+
import { PlayerAPI } from 'bitmovin-player';
|
|
2
|
+
import { UIComponents } from '../../../types/interfaces';
|
|
3
3
|
export declare function handlePlayerError(player: PlayerAPI, uiComponents: UIComponents, input: any): void;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { StreamObject } from 'types/interfaces';
|
|
2
2
|
import { SourceConfig } from 'bitmovin-player';
|
|
3
|
-
export declare function processSourceConfig(_sourceConfig: SourceConfig | undefined, _streamObject: StreamObject, drm
|
|
3
|
+
export declare function processSourceConfig(_sourceConfig: SourceConfig | undefined, _streamObject: StreamObject, drm: string | null | undefined, useWidevineServerCertificate: boolean): Promise<SourceConfig>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HttpResponseType } from 'bitmovin-player';
|
|
2
2
|
import { npoCdnProviders } from '../../cdnproviders';
|
|
3
3
|
import { replaceSpecialChars } from '../../utilities/utilities';
|
|
4
|
-
async function setDrm(sourceConfig, drm, _streamObject) {
|
|
4
|
+
async function setDrm(sourceConfig, drm, _streamObject, useWidevineServerCertificate) {
|
|
5
5
|
const npoDrmGateway = 'https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication?custom_data=';
|
|
6
6
|
switch (drm) {
|
|
7
7
|
case 'fairplay':
|
|
@@ -37,14 +37,14 @@ async function setDrm(sourceConfig, drm, _streamObject) {
|
|
|
37
37
|
break;
|
|
38
38
|
case 'widevine':
|
|
39
39
|
try {
|
|
40
|
-
const response = await fetch('https://widevine.npoplayer.nl/widevine_player_cert.bin');
|
|
41
|
-
const certificate = await response.arrayBuffer();
|
|
42
40
|
sourceConfig.drm = {
|
|
43
41
|
widevine: {
|
|
44
|
-
serverCertificate: certificate,
|
|
45
42
|
LA_URL: npoDrmGateway + _streamObject.stream.drmToken,
|
|
46
43
|
audioRobustness: 'SW_SECURE_CRYPTO',
|
|
47
|
-
videoRobustness: 'SW_SECURE_CRYPTO'
|
|
44
|
+
videoRobustness: 'SW_SECURE_CRYPTO',
|
|
45
|
+
serverCertificate: useWidevineServerCertificate
|
|
46
|
+
? await (await fetch('https://widevine.npoplayer.nl/widevine_player_cert.bin')).arrayBuffer()
|
|
47
|
+
: undefined
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
}
|
|
@@ -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, useWidevineServerCertificate) {
|
|
95
95
|
let sourceConfig = {};
|
|
96
96
|
sourceConfig.title =
|
|
97
97
|
_sourceConfig.title ?? _streamObject.metadata.title ?? '';
|
|
@@ -125,7 +125,7 @@ export async function processSourceConfig(_sourceConfig = {}, _streamObject, drm
|
|
|
125
125
|
: undefined;
|
|
126
126
|
sourceConfig.options = _sourceConfig.options;
|
|
127
127
|
sourceConfig = setStreamProfile(sourceConfig, _sourceConfig, _streamObject);
|
|
128
|
-
sourceConfig = await setDrm(sourceConfig, drm, _streamObject);
|
|
128
|
+
sourceConfig = await setDrm(sourceConfig, drm, _streamObject, useWidevineServerCertificate);
|
|
129
129
|
const streamSource = _streamObject.stream.streamURL;
|
|
130
130
|
let cdnString = streamSource;
|
|
131
131
|
if (streamSource.startsWith('file:///')) {
|
|
@@ -3,7 +3,7 @@ export function createAdButton(npoplayer) {
|
|
|
3
3
|
npoplayer.uiComponents.adbutton = undefined;
|
|
4
4
|
const adButton = new Button({
|
|
5
5
|
cssClass: 'ui-textbutton ui-sterbutton bmpui-ui-button',
|
|
6
|
-
text: '
|
|
6
|
+
text: 'Ga naar website van adverteerder',
|
|
7
7
|
hidden: true
|
|
8
8
|
});
|
|
9
9
|
npoplayer.uiComponents.adbutton = adButton;
|
|
@@ -14,7 +14,7 @@ export function createMiddleButtons(player) {
|
|
|
14
14
|
export function createRewindButton(player) {
|
|
15
15
|
const rewindButton = new Button({
|
|
16
16
|
cssClass: 'ui-rewindbutton bmpui-ui-button',
|
|
17
|
-
ariaLabel: 'Spoel 10
|
|
17
|
+
ariaLabel: 'Spoel 10 seconden terug',
|
|
18
18
|
});
|
|
19
19
|
if (player) {
|
|
20
20
|
rewindButton.onClick.subscribe(() => {
|
|
@@ -26,7 +26,7 @@ export function createRewindButton(player) {
|
|
|
26
26
|
export function createForwardButton(player) {
|
|
27
27
|
const forwardButton = new Button({
|
|
28
28
|
cssClass: 'ui-forwardbutton bmpui-ui-button',
|
|
29
|
-
ariaLabel: 'Spoel 10
|
|
29
|
+
ariaLabel: 'Spoel 10 seconden vooruit',
|
|
30
30
|
});
|
|
31
31
|
if (player) {
|
|
32
32
|
forwardButton.onClick.subscribe(() => {
|
|
@@ -1,7 +1,13 @@
|
|
|
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(): Button<{
|
|
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<{
|
|
5
11
|
cssClass: string;
|
|
6
12
|
text: string;
|
|
7
13
|
ariaLabel: string;
|
|
@@ -3,6 +3,7 @@ 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';
|
|
6
7
|
function createBigPlayToggleButton() {
|
|
7
8
|
const bigPlayToggleButton = new PlaybackToggleButton({
|
|
8
9
|
cssClass: 'ui-playbacktogglebutton'
|
|
@@ -29,15 +30,17 @@ export function createMiddleButtons(player) {
|
|
|
29
30
|
cssClasses: ['controlbar-middle']
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
|
-
export function createPlayNextButton() {
|
|
33
|
+
export function createPlayNextButton(player) {
|
|
33
34
|
const playNextButton = new Button({
|
|
34
|
-
cssClass: 'ui-
|
|
35
|
-
text: '
|
|
35
|
+
cssClass: 'ui-textbutton ui-playNextButton bmpui-ui-button',
|
|
36
|
+
text: 'Volgende aflevering over <span class="countdown"></span>…',
|
|
36
37
|
ariaLabel: 'Speel volgende aflevering af',
|
|
37
38
|
hidden: false
|
|
38
39
|
});
|
|
40
|
+
const container = player.getContainer();
|
|
39
41
|
const playNextButtonClickHandler = () => {
|
|
40
|
-
sendCustomMessage(CustomMessages.
|
|
42
|
+
sendCustomMessage(CustomMessages.PLAY_NEXT_PROCEED_CLICK);
|
|
43
|
+
hidePlayNextScreen(container, true);
|
|
41
44
|
};
|
|
42
45
|
playNextButton.onClick.subscribe(() => {
|
|
43
46
|
playNextButtonClickHandler();
|
|
@@ -47,6 +50,26 @@ export function createPlayNextButton() {
|
|
|
47
50
|
});
|
|
48
51
|
return playNextButton;
|
|
49
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
|
+
}
|
|
50
73
|
export function createWatchFromStartButton() {
|
|
51
74
|
const watchFromStartButton = new Button({
|
|
52
75
|
cssClass: 'ui-textbutton ui-watchfromstartbutton bmpui-ui-button',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ControlBar, Container, PlaybackTimeLabel, PlaybackTimeLabelMode, SeekBar, SeekBarLabel,
|
|
1
|
+
import { ControlBar, Container, PlaybackTimeLabel, PlaybackTimeLabelMode, SeekBar, SeekBarLabel, Spacer, } from 'bitmovin-player-ui';
|
|
2
2
|
import { createForwardButton, createRewindButton } from '../buttons';
|
|
3
3
|
import { createAirPlayToggleButton, createCastToggleButton, createFullscreenToggleButton, createPictureInPictureToggleButton, createPlaybackToggleButton, createSettingsButton, createVolumeToggleButton, } from './buttons';
|
|
4
4
|
export function createControlBar(player, settingsPanel) {
|
|
@@ -26,7 +26,6 @@ export function createControlBar(player, settingsPanel) {
|
|
|
26
26
|
components: [
|
|
27
27
|
createPlaybackToggleButton(),
|
|
28
28
|
createVolumeToggleButton(player),
|
|
29
|
-
new VolumeSlider(),
|
|
30
29
|
rewindButton,
|
|
31
30
|
forwardButton,
|
|
32
31
|
new Spacer(),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Container } from 'bitmovin-player-ui';
|
|
2
|
-
|
|
2
|
+
import { PlayerAPI } from 'bitmovin-player';
|
|
3
|
+
export declare function createPlayNextScreen(player: PlayerAPI): Container<{
|
|
3
4
|
components: import("bitmovin-player-ui").Button<{
|
|
4
5
|
cssClass: string;
|
|
5
6
|
text: string;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Container } from 'bitmovin-player-ui';
|
|
2
|
-
import { createPlayNextButton } from './buttons';
|
|
3
|
-
export function createPlayNextScreen() {
|
|
4
|
-
const playNextButton = createPlayNextButton();
|
|
2
|
+
import { createPlayNextButton, createCancelPlayNextButton } from './buttons';
|
|
3
|
+
export function createPlayNextScreen(player) {
|
|
4
|
+
const playNextButton = createPlayNextButton(player);
|
|
5
|
+
const cancelButton = createCancelPlayNextButton(player);
|
|
5
6
|
return new Container({
|
|
6
|
-
components: [playNextButton],
|
|
7
|
+
components: [cancelButton, playNextButton],
|
|
7
8
|
cssClasses: ['overlay-playnext']
|
|
8
9
|
});
|
|
9
10
|
}
|
|
@@ -48,7 +48,13 @@ export function createSettingsPanel(npoplayer) {
|
|
|
48
48
|
cssClasses: [listboxPanelClass]
|
|
49
49
|
});
|
|
50
50
|
const settingsTriggerButton = new Button({ cssClass: settingTriggerClass });
|
|
51
|
-
|
|
51
|
+
class CustomSettingsPanelPage extends SettingsPanelPage {
|
|
52
|
+
constructor(config) {
|
|
53
|
+
super(config);
|
|
54
|
+
}
|
|
55
|
+
onActiveEvent() { }
|
|
56
|
+
}
|
|
57
|
+
const mainSettingsPage = new CustomSettingsPanelPage({
|
|
52
58
|
components: [
|
|
53
59
|
settingsTriggerButton,
|
|
54
60
|
new Label({ text: settingsLabel, cssClass: 'setting-header' }),
|
|
@@ -57,13 +63,11 @@ export function createSettingsPanel(npoplayer) {
|
|
|
57
63
|
],
|
|
58
64
|
cssClasses: ['main-panel']
|
|
59
65
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
qualityListBox.selectItem('auto');
|
|
66
|
-
});
|
|
66
|
+
const onSourceLoaded = () => {
|
|
67
|
+
player?.off(PlayerEvent.SourceLoaded, onSourceLoaded);
|
|
68
|
+
checkQualities();
|
|
69
|
+
checkSubtitles();
|
|
70
|
+
};
|
|
67
71
|
const settingsPanel = new SettingsPanel({
|
|
68
72
|
components: [
|
|
69
73
|
mainSettingsPage,
|
|
@@ -72,7 +76,8 @@ export function createSettingsPanel(npoplayer) {
|
|
|
72
76
|
qualityPanelPage
|
|
73
77
|
],
|
|
74
78
|
hidden: true,
|
|
75
|
-
pageTransitionAnimation: false
|
|
79
|
+
pageTransitionAnimation: false,
|
|
80
|
+
hideDelay: -1,
|
|
76
81
|
});
|
|
77
82
|
const subtitleSettingsOpenButton = new SettingsPanelPageOpenButton({
|
|
78
83
|
targetPage: subtitlePanelPage,
|
|
@@ -131,9 +136,6 @@ export function createSettingsPanel(npoplayer) {
|
|
|
131
136
|
npoplayer.uiComponents.settingsPanel = settingsPanel;
|
|
132
137
|
}
|
|
133
138
|
const checkSubtitles = () => {
|
|
134
|
-
player?.off(PlayerEvent.SourceLoaded, checkSubtitles);
|
|
135
|
-
player?.off(PlayerEvent.AdBreakFinished, checkSubtitles);
|
|
136
|
-
player?.off(PlayerEvent.AdError, checkSubtitles);
|
|
137
139
|
const subtitles = player?.subtitles?.list();
|
|
138
140
|
if (subtitles != null && subtitles.length > 0) {
|
|
139
141
|
subtitlesPanelItem.show();
|
|
@@ -142,22 +144,27 @@ export function createSettingsPanel(npoplayer) {
|
|
|
142
144
|
subtitlesPanelItem.hide();
|
|
143
145
|
}
|
|
144
146
|
};
|
|
145
|
-
const
|
|
146
|
-
player?.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
const checkQualities = () => {
|
|
148
|
+
const availableVideoQualities = player?.getAvailableVideoQualities();
|
|
149
|
+
qualityListBox.clearItems();
|
|
150
|
+
if (availableVideoQualities?.length) {
|
|
151
|
+
player?.getAvailableVideoQualities().forEach(quality => {
|
|
152
|
+
qualityListBox.addItem(quality.id, quality.label ?? 'test');
|
|
153
|
+
});
|
|
154
|
+
qualityListBox.addItem('auto', autoLabel);
|
|
155
|
+
qualityListBox.selectItem('auto');
|
|
150
156
|
qualityPanelItem.show();
|
|
151
157
|
}
|
|
152
158
|
else {
|
|
153
159
|
qualityPanelItem.hide();
|
|
154
160
|
}
|
|
155
161
|
};
|
|
156
|
-
player?.on(PlayerEvent.SourceLoaded,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
player?.on(PlayerEvent.SourceLoaded, onSourceLoaded);
|
|
163
|
+
settingsPanel.onHide.subscribe(() => {
|
|
164
|
+
player?.getContainer().querySelector('.bmpui-npo-player')?.classList.remove('bmpui-settings-panel-shown');
|
|
165
|
+
});
|
|
166
|
+
settingsPanel.onShow.subscribe(() => {
|
|
167
|
+
player?.getContainer().querySelector('.bmpui-npo-player')?.classList.add('bmpui-settings-panel-shown');
|
|
168
|
+
});
|
|
162
169
|
return settingsPanel;
|
|
163
170
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function
|
|
2
|
-
export declare function hidePlayNextScreen(containerEl: HTMLElement): void;
|
|
1
|
+
export declare function showPlayNextScreenIfNeeded(overlayDuration: number, containerEl: HTMLElement, proceedCallBack?: () => void, nativeUI?: boolean): void;
|
|
2
|
+
export declare function hidePlayNextScreen(containerEl: HTMLElement, nativeUI?: boolean): void;
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import { sendCustomMessage } from '../../nativemobileuicontainer';
|
|
2
|
+
import { CustomMessages } from '../../../../types/interfaces';
|
|
1
3
|
let countdownInterval = null;
|
|
2
|
-
export function
|
|
4
|
+
export function showPlayNextScreenIfNeeded(overlayDuration, containerEl, proceedCallBack, nativeUI = false) {
|
|
3
5
|
const playnextOverlay = containerEl.querySelector('.bmpui-overlay-playnext');
|
|
4
6
|
const countdownLabel = playnextOverlay?.querySelector('.countdown');
|
|
7
|
+
if (playnextOverlay?.classList.contains('show'))
|
|
8
|
+
return;
|
|
5
9
|
if (countdownLabel) {
|
|
6
10
|
countdownLabel.innerHTML = overlayDuration.toString();
|
|
7
11
|
}
|
|
8
12
|
playnextOverlay?.classList.add('show');
|
|
13
|
+
if (nativeUI)
|
|
14
|
+
sendCustomMessage(CustomMessages.PLAY_NEXT_OVERLAY_SHOWN);
|
|
9
15
|
const playNextButton = playnextOverlay?.querySelector('.ui-playNextButton');
|
|
10
16
|
if (playNextButton) {
|
|
11
17
|
playNextButton.focus();
|
|
@@ -15,18 +21,24 @@ export function showPlayNextScreen(overlayDuration, containerEl, proceedCallBack
|
|
|
15
21
|
if (playnextOverlay?.classList.contains('show') && countdownLabel) {
|
|
16
22
|
countdownLabel.innerHTML = countdown.toString();
|
|
17
23
|
countdown -= 1;
|
|
24
|
+
if (nativeUI)
|
|
25
|
+
sendCustomMessage(CustomMessages.PLAY_NEXT_COUNTDOWN_PROGRESS, { playNext: { remainingCountDownDuration: countdown } });
|
|
18
26
|
}
|
|
19
27
|
if (countdown < 0) {
|
|
20
28
|
clearInterval(countdownInterval);
|
|
21
29
|
if (playnextOverlay?.classList.contains('show')) {
|
|
22
30
|
proceedCallBack?.();
|
|
23
31
|
playnextOverlay?.classList.remove('show');
|
|
32
|
+
if (nativeUI)
|
|
33
|
+
sendCustomMessage(CustomMessages.PLAY_NEXT_COUNTDOWN_FINISHED);
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
}, 1000);
|
|
27
37
|
}
|
|
28
|
-
export function hidePlayNextScreen(containerEl) {
|
|
38
|
+
export function hidePlayNextScreen(containerEl, nativeUI = false) {
|
|
29
39
|
containerEl.querySelector('.bmpui-overlay-playnext')?.classList.remove('show');
|
|
40
|
+
if (nativeUI)
|
|
41
|
+
sendCustomMessage(CustomMessages.PLAY_NEXT_OVERLAY_HIDDEN);
|
|
30
42
|
if (countdownInterval !== null) {
|
|
31
43
|
clearInterval(countdownInterval);
|
|
32
44
|
countdownInterval = null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { showPlayNextScreenIfNeeded } from './playnextscreen';
|
|
2
|
+
import { sendCustomMessage } from '../../nativemobileuicontainer';
|
|
3
|
+
import { CustomMessages } from '../../../../types/interfaces';
|
|
4
|
+
jest.mock('../../nativemobileuicontainer');
|
|
5
|
+
describe('showPlayNextScreenIfNeeded', () => {
|
|
6
|
+
let containerEl;
|
|
7
|
+
let playnextOverlay;
|
|
8
|
+
let countdownLabel;
|
|
9
|
+
let playNextButton;
|
|
10
|
+
let proceedCallBack;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
sendCustomMessage.mockClear();
|
|
13
|
+
playnextOverlay = document.createElement('div');
|
|
14
|
+
playnextOverlay.classList.add('bmpui-overlay-playnext');
|
|
15
|
+
countdownLabel = document.createElement('div');
|
|
16
|
+
countdownLabel.classList.add('countdown');
|
|
17
|
+
playnextOverlay.appendChild(countdownLabel);
|
|
18
|
+
playNextButton = document.createElement('button');
|
|
19
|
+
playNextButton.classList.add('ui-playNextButton');
|
|
20
|
+
playnextOverlay.appendChild(playNextButton);
|
|
21
|
+
containerEl = document.createElement('div');
|
|
22
|
+
containerEl.appendChild(playnextOverlay);
|
|
23
|
+
proceedCallBack = jest.fn();
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
jest.clearAllTimers();
|
|
27
|
+
});
|
|
28
|
+
it('should not show overlay if it is already shown', () => {
|
|
29
|
+
playnextOverlay.classList.add('show');
|
|
30
|
+
showPlayNextScreenIfNeeded(10, containerEl, proceedCallBack, false);
|
|
31
|
+
expect(playnextOverlay.classList.contains('show')).toBe(true);
|
|
32
|
+
expect(proceedCallBack).not.toHaveBeenCalled();
|
|
33
|
+
});
|
|
34
|
+
it('should show overlay and start countdown', () => {
|
|
35
|
+
jest.useFakeTimers();
|
|
36
|
+
showPlayNextScreenIfNeeded(10, containerEl, proceedCallBack, false);
|
|
37
|
+
expect(playnextOverlay.classList.contains('show')).toBe(true);
|
|
38
|
+
expect(countdownLabel.innerHTML).toBe('10');
|
|
39
|
+
jest.advanceTimersByTime(1000);
|
|
40
|
+
expect(countdownLabel.innerHTML).toBe('9');
|
|
41
|
+
});
|
|
42
|
+
it('should call proceedCallBack and hide overlay when countdown finishes', () => {
|
|
43
|
+
jest.useFakeTimers();
|
|
44
|
+
showPlayNextScreenIfNeeded(1, containerEl, proceedCallBack, false);
|
|
45
|
+
jest.advanceTimersByTime(2000);
|
|
46
|
+
expect(proceedCallBack).toHaveBeenCalled();
|
|
47
|
+
expect(playnextOverlay.classList.contains('show')).toBe(false);
|
|
48
|
+
});
|
|
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
|
+
});
|
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
export function addAccessibilityAttributes(container, metadata) {
|
|
2
|
+
const label = 'aria-label';
|
|
2
3
|
const videoEl = container.querySelector('video');
|
|
3
|
-
|
|
4
|
-
videoEl?.setAttribute(
|
|
4
|
+
const hugePlayBackToggleButton = container.querySelector('.bmpui-ui-hugeplaybacktogglebutton');
|
|
5
|
+
videoEl?.setAttribute(label, metadata.title);
|
|
6
|
+
hugePlayBackToggleButton?.setAttribute(label, metadata.title);
|
|
7
|
+
const description = container.querySelector('.bmpui-label-metadata-description');
|
|
8
|
+
if (description) {
|
|
9
|
+
videoEl?.setAttribute('aria-describedby', description.id);
|
|
10
|
+
hugePlayBackToggleButton?.setAttribute('aria-describedby', description.id);
|
|
11
|
+
}
|
|
12
|
+
const imgRoles = container.querySelectorAll('[role="img"]');
|
|
13
|
+
imgRoles.forEach(imgRole => {
|
|
14
|
+
imgRole.setAttribute(label, metadata.title);
|
|
15
|
+
});
|
|
16
|
+
const popupButtons = container.querySelectorAll('[role="pop-up button"]');
|
|
17
|
+
popupButtons.forEach(popupButton => {
|
|
18
|
+
popupButton.setAttribute('role', 'button');
|
|
19
|
+
popupButton.setAttribute('aria-haspopup', 'menu');
|
|
20
|
+
});
|
|
5
21
|
const menuItems = container.querySelectorAll('[role="menuitem"][aria-pressed]');
|
|
6
22
|
menuItems.forEach(menuitem => {
|
|
7
23
|
menuitem.removeAttribute('aria-pressed');
|
|
8
24
|
});
|
|
25
|
+
const panelItems = container.querySelectorAll('.bmpui-ui-settings-panel-item[role="menuitem"]');
|
|
26
|
+
panelItems.forEach(panelItem => {
|
|
27
|
+
panelItem.setAttribute('role', 'group');
|
|
28
|
+
});
|
|
29
|
+
const panelPages = container.querySelectorAll('.bmpui-ui-settings-panel-page.bmpui-listbox-panel[role="menu"]');
|
|
30
|
+
panelPages.forEach(panelPage => {
|
|
31
|
+
panelPage.setAttribute('role', 'group');
|
|
32
|
+
});
|
|
9
33
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { addAccessibilityAttributes } from './accessibilityhandler';
|
|
2
|
+
import { JSDOM } from 'jsdom';
|
|
3
|
+
describe('addAccessibilityAttributes', () => {
|
|
4
|
+
let container;
|
|
5
|
+
const mockMetadata = { title: 'Mocked Video Title', description: 'Mocked Description' };
|
|
6
|
+
const label = 'aria-label';
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
const { window } = new JSDOM(`
|
|
9
|
+
<div>
|
|
10
|
+
<video></video>
|
|
11
|
+
<div class="bmpui-ui-hugeplaybacktogglebutton"></div>
|
|
12
|
+
<div class="bmpui-label-metadata-description" id="desc">Description</div>
|
|
13
|
+
<div role="img"></div>
|
|
14
|
+
<div role="pop-up button"></div>
|
|
15
|
+
<div role="menuitem" aria-pressed="true"></div>
|
|
16
|
+
<div class="bmpui-ui-settings-panel-item" role="menuitem"></div>
|
|
17
|
+
<div class="bmpui-ui-settings-panel-page bmpui-listbox-panel" role="menu"></div>
|
|
18
|
+
</div>
|
|
19
|
+
`);
|
|
20
|
+
container = window.document.body.firstChild;
|
|
21
|
+
});
|
|
22
|
+
function expectAttribute(selector, attribute, expectedValue) {
|
|
23
|
+
const element = container?.querySelector(selector);
|
|
24
|
+
expect(element?.getAttribute(attribute)).toBe(expectedValue);
|
|
25
|
+
}
|
|
26
|
+
it('adds correct aria-labels', () => {
|
|
27
|
+
if (!container)
|
|
28
|
+
return;
|
|
29
|
+
addAccessibilityAttributes(container, mockMetadata);
|
|
30
|
+
expectAttribute('video', label, mockMetadata.title);
|
|
31
|
+
expectAttribute('.bmpui-ui-hugeplaybacktogglebutton', label, mockMetadata.title);
|
|
32
|
+
expectAttribute('[role="img"]', label, mockMetadata.title);
|
|
33
|
+
});
|
|
34
|
+
it('handles aria-describedby correctly', () => {
|
|
35
|
+
if (!container)
|
|
36
|
+
return;
|
|
37
|
+
addAccessibilityAttributes(container, mockMetadata);
|
|
38
|
+
const descriptionId = container.querySelector('.bmpui-label-metadata-description')?.id;
|
|
39
|
+
expectAttribute('video', 'aria-describedby', descriptionId ? descriptionId : '');
|
|
40
|
+
expectAttribute('.bmpui-ui-hugeplaybacktogglebutton', 'aria-describedby', descriptionId ? descriptionId : '');
|
|
41
|
+
});
|
|
42
|
+
it('correctly adjusts role and aria-haspopup attributes', () => {
|
|
43
|
+
if (!container)
|
|
44
|
+
return;
|
|
45
|
+
addAccessibilityAttributes(container, mockMetadata);
|
|
46
|
+
const popupButton = container.querySelector('[role="button"]');
|
|
47
|
+
expect(popupButton).toBeTruthy();
|
|
48
|
+
expect(popupButton?.getAttribute('aria-haspopup')).toBe('menu');
|
|
49
|
+
});
|
|
50
|
+
it('removes aria-pressed from menuitems', () => {
|
|
51
|
+
if (!container)
|
|
52
|
+
return;
|
|
53
|
+
addAccessibilityAttributes(container, mockMetadata);
|
|
54
|
+
expect(container.querySelector('[role="menuitem"][aria-pressed]')).toBeFalsy();
|
|
55
|
+
});
|
|
56
|
+
it('changes the role of menuitems and menus correctly', () => {
|
|
57
|
+
if (!container)
|
|
58
|
+
return;
|
|
59
|
+
addAccessibilityAttributes(container, mockMetadata);
|
|
60
|
+
expectAttribute('.bmpui-ui-settings-panel-item', 'role', 'group');
|
|
61
|
+
expectAttribute('.bmpui-ui-settings-panel-page', 'role', 'group');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getPlayerElement } from "../../utilities/utilities";
|
|
2
|
+
export function handleLiveStreamNoDvr(player, streamObject, container) {
|
|
3
|
+
const isLiveStreamNoDvr = streamObject.stream.isLiveStream && streamObject.stream.hasDvrWindow === false;
|
|
4
|
+
const playerElement = getPlayerElement(player, '.bmpui-npo-player');
|
|
5
|
+
const noDvrClass = 'livestream-no-dvr';
|
|
6
|
+
playerElement?.classList[isLiveStreamNoDvr ? 'add' : 'remove'](noDvrClass);
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|