@gcorevideo/player 2.20.5 → 2.20.7
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/dist/core.js +3 -3
- package/dist/index.css +1181 -1181
- package/dist/index.js +3757 -3732
- package/dist/plugins/index.css +704 -704
- package/dist/plugins/index.js +5113 -5093
- package/lib/playback/BasePlayback.d.ts +1 -1
- package/lib/playback/BasePlayback.d.ts.map +1 -1
- package/lib/playback/BasePlayback.js +2 -2
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
- package/lib/plugins/bottom-gear/BottomGear.js +5 -8
- package/lib/plugins/error-screen/ErrorScreen.d.ts.map +1 -1
- package/lib/plugins/error-screen/ErrorScreen.js +1 -2
- package/lib/plugins/level-selector/LevelSelector.d.ts +15 -5
- package/lib/plugins/level-selector/LevelSelector.d.ts.map +1 -1
- package/lib/plugins/level-selector/LevelSelector.js +22 -22
- package/lib/plugins/media-control/MediaControl.d.ts +10 -0
- package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
- package/lib/plugins/media-control/MediaControl.js +20 -3
- package/lib/plugins/source-controller/SourceController.d.ts +1 -0
- package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
- package/lib/plugins/source-controller/SourceController.js +8 -4
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts +7 -3
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts.map +1 -1
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.js +35 -27
- package/lib/testUtils.d.ts +5 -8
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +15 -9
- package/package.json +1 -1
- package/src/playback/BasePlayback.ts +2 -2
- package/src/playback/dash-playback/DashPlayback.ts +1 -2
- package/src/playback/hls-playback/HlsPlayback.ts +2 -2
- package/src/plugins/bottom-gear/BottomGear.ts +5 -7
- package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +36 -0
- package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +41 -0
- package/src/plugins/error-screen/ErrorScreen.ts +1 -2
- package/src/plugins/level-selector/LevelSelector.ts +50 -29
- package/src/plugins/level-selector/__tests__/LevelSelector.test.ts +15 -16
- package/src/plugins/media-control/MediaControl.ts +19 -3
- package/src/plugins/source-controller/SourceController.ts +9 -4
- package/src/plugins/source-controller/__tests__/SourceController.test.ts +35 -1
- package/src/plugins/spinner-three-bounce/SpinnerThreeBounce.ts +80 -57
- package/src/testUtils.ts +16 -9
- package/tsconfig.tsbuildinfo +1 -1
- package/src/playback/utils.ts +0 -2
package/lib/testUtils.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UICorePlugin } from '@clappr/core';
|
|
1
2
|
import Events from 'eventemitter3';
|
|
2
3
|
/**
|
|
3
4
|
* @internal
|
|
@@ -35,10 +36,7 @@ export declare class _MockPlayback extends Events {
|
|
|
35
36
|
}
|
|
36
37
|
export declare function createMockCore(options?: Record<string, unknown>, container?: any): Events<string | symbol, any> & {
|
|
37
38
|
el: HTMLDivElement;
|
|
38
|
-
$el:
|
|
39
|
-
0: HTMLDivElement;
|
|
40
|
-
append: import("vitest").Mock<(...args: any[]) => any>;
|
|
41
|
-
};
|
|
39
|
+
$el: any;
|
|
42
40
|
activePlayback: any;
|
|
43
41
|
activeContainer: any;
|
|
44
42
|
options: {
|
|
@@ -47,6 +45,7 @@ export declare function createMockCore(options?: Record<string, unknown>, contai
|
|
|
47
45
|
configure: import("vitest").Mock<(...args: any[]) => any>;
|
|
48
46
|
getPlugin: import("vitest").Mock<(...args: any[]) => any>;
|
|
49
47
|
load: import("vitest").Mock<(...args: any[]) => any>;
|
|
48
|
+
trigger: <T extends string | symbol>(event: T, ...args: any[]) => boolean;
|
|
50
49
|
};
|
|
51
50
|
export declare function createMockPlugin(): Events<string | symbol, any> & {
|
|
52
51
|
enable: import("vitest").Mock<(...args: any[]) => any>;
|
|
@@ -87,12 +86,10 @@ export declare function createMockPlayback(name?: string): Events<string | symbo
|
|
|
87
86
|
trigger(event: string, ...args: any[]): void;
|
|
88
87
|
};
|
|
89
88
|
export declare function createMockContainer(playback?: any): Events<string | symbol, any> & {
|
|
90
|
-
$el:
|
|
91
|
-
html: import("vitest").Mock<(...args: any[]) => any>;
|
|
92
|
-
0: HTMLDivElement;
|
|
93
|
-
};
|
|
89
|
+
$el: any;
|
|
94
90
|
el: HTMLDivElement;
|
|
95
91
|
getPlugin: import("vitest").Mock<(...args: any[]) => any>;
|
|
96
92
|
playback: any;
|
|
97
93
|
};
|
|
94
|
+
export declare function createMockMediaControl(core: any): UICorePlugin;
|
|
98
95
|
//# sourceMappingURL=testUtils.d.ts.map
|
package/lib/testUtils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,eAAe,CAAA;AAElC;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,MAAM;IAErC,SAAS,CAAC,OAAO,EAAE,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,GAAG;IAClB,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG;gBAFjB,OAAO,EAAE,GAAG,EACb,IAAI,EAAE,GAAG,EACR,WAAW,CAAC,EAAE,GAAG,YAAA;IAK7B,IAAI,IAAI,WAEP;IAED,OAAO;IAEP,IAAI;IAEJ,KAAK;IAEL,IAAI;IAEJ,OAAO;IAEP,IAAI;IAEJ,cAAc;IAEd,WAAW;IAIX,QAAQ;IAER,OAAO;IAEP,eAAe;IAIf,kBAAkB;IAIlB,cAAc;IAId,qBAAqB;IAIrB,IAAI;IAEJ,MAAM;IAEN,MAAM;IAEN,SAAS;IAET,eAAe;IAIf,WAAW;IAIX,QAAQ;IAIR,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;CAGtC;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAAE,SAAS,GAAE,GAA2B
|
|
1
|
+
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,MAAM,MAAM,eAAe,CAAA;AAElC;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,MAAM;IAErC,SAAS,CAAC,OAAO,EAAE,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,GAAG;IAClB,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG;gBAFjB,OAAO,EAAE,GAAG,EACb,IAAI,EAAE,GAAG,EACR,WAAW,CAAC,EAAE,GAAG,YAAA;IAK7B,IAAI,IAAI,WAEP;IAED,OAAO;IAEP,IAAI;IAEJ,KAAK;IAEL,IAAI;IAEJ,OAAO;IAEP,IAAI;IAEJ,cAAc;IAEd,WAAW;IAIX,QAAQ;IAER,OAAO;IAEP,eAAe;IAIf,kBAAkB;IAIlB,cAAc;IAId,qBAAqB;IAIrB,IAAI;IAEJ,MAAM;IAEN,MAAM;IAEN,SAAS;IAET,eAAe;IAIf,WAAW;IAIX,QAAQ;IAIR,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;CAGtC;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAAE,SAAS,GAAE,GAA2B;;;;;;;;;;;;EAgB3G;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;mBA2C7B,MAAM,WAAW,GAAG,EAAE;EAIxC;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,GAA0B;;;;;EAQvE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,gBAQ/C"}
|
package/lib/testUtils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { $, UICorePlugin } from '@clappr/core';
|
|
1
2
|
import Events from 'eventemitter3';
|
|
2
3
|
import { vi } from 'vitest';
|
|
3
4
|
/**
|
|
@@ -61,12 +62,10 @@ export class _MockPlayback extends Events {
|
|
|
61
62
|
}
|
|
62
63
|
export function createMockCore(options = {}, container = createMockContainer()) {
|
|
63
64
|
const el = document.createElement('div');
|
|
64
|
-
|
|
65
|
+
const emitter = new Events();
|
|
66
|
+
return Object.assign(emitter, {
|
|
65
67
|
el,
|
|
66
|
-
$el:
|
|
67
|
-
[0]: el,
|
|
68
|
-
append: vi.fn(),
|
|
69
|
-
},
|
|
68
|
+
$el: $(el),
|
|
70
69
|
activePlayback: container.playback,
|
|
71
70
|
activeContainer: container,
|
|
72
71
|
options: {
|
|
@@ -75,6 +74,7 @@ export function createMockCore(options = {}, container = createMockContainer())
|
|
|
75
74
|
configure: vi.fn(),
|
|
76
75
|
getPlugin: vi.fn(),
|
|
77
76
|
load: vi.fn(),
|
|
77
|
+
trigger: emitter.emit,
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
export function createMockPlugin() {
|
|
@@ -140,12 +140,18 @@ export function createMockPlayback(name = 'mock') {
|
|
|
140
140
|
export function createMockContainer(playback = createMockPlayback()) {
|
|
141
141
|
const el = document.createElement('div');
|
|
142
142
|
return Object.assign(new Events(), {
|
|
143
|
-
$el:
|
|
144
|
-
html: vi.fn(),
|
|
145
|
-
[0]: el,
|
|
146
|
-
},
|
|
143
|
+
$el: $(el),
|
|
147
144
|
el,
|
|
148
145
|
getPlugin: vi.fn(),
|
|
149
146
|
playback,
|
|
150
147
|
});
|
|
151
148
|
}
|
|
149
|
+
export function createMockMediaControl(core) {
|
|
150
|
+
const mediaControl = new UICorePlugin(core);
|
|
151
|
+
const elements = {
|
|
152
|
+
gear: $(document.createElement('div')),
|
|
153
|
+
};
|
|
154
|
+
// @ts-ignore
|
|
155
|
+
mediaControl.getElement = vi.fn().mockImplementation((name) => elements[name]);
|
|
156
|
+
return mediaControl;
|
|
157
|
+
}
|
package/package.json
CHANGED
|
@@ -34,8 +34,8 @@ export class BasePlayback extends HTML5Video {
|
|
|
34
34
|
return super.createError(errorData, options)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
override
|
|
38
|
-
super.
|
|
37
|
+
override _onPlaying() {
|
|
38
|
+
super._onPlaying()
|
|
39
39
|
this.trigger(Events.PLAYBACK_MEDIACONTROL_ENABLE)
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -521,7 +521,7 @@ export default class DashPlayback extends BasePlayback {
|
|
|
521
521
|
)
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
_onProgress() {
|
|
524
|
+
override _onProgress() {
|
|
525
525
|
if (!this._dash) {
|
|
526
526
|
return
|
|
527
527
|
}
|
|
@@ -554,7 +554,6 @@ export default class DashPlayback extends BasePlayback {
|
|
|
554
554
|
if (!this._dash) {
|
|
555
555
|
return
|
|
556
556
|
}
|
|
557
|
-
|
|
558
557
|
super.pause()
|
|
559
558
|
if (this.dvrEnabled) {
|
|
560
559
|
this._updateDvr(true)
|
|
@@ -763,7 +763,7 @@ export default class HlsPlayback extends BasePlayback {
|
|
|
763
763
|
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
764
764
|
}
|
|
765
765
|
|
|
766
|
-
_onDurationChange() {
|
|
766
|
+
override _onDurationChange() {
|
|
767
767
|
const duration = this.getDuration()
|
|
768
768
|
|
|
769
769
|
if (this._lastDuration === duration) {
|
|
@@ -773,7 +773,7 @@ export default class HlsPlayback extends BasePlayback {
|
|
|
773
773
|
super._onDurationChange() // will call _onTimeUpdate
|
|
774
774
|
}
|
|
775
775
|
|
|
776
|
-
_onProgress() {
|
|
776
|
+
override _onProgress() {
|
|
777
777
|
if (!(this.el as HTMLMediaElement).buffered.length) {
|
|
778
778
|
return
|
|
779
779
|
}
|
|
@@ -10,6 +10,7 @@ import '../../../assets/bottom-gear/gear-sub-menu.scss';
|
|
|
10
10
|
import gearIcon from '../../../assets/icons/new/gear.svg';
|
|
11
11
|
import gearHdIcon from '../../../assets/icons/new/gear-hd.svg';
|
|
12
12
|
import { ZeptoResult } from '../../utils/types.js';
|
|
13
|
+
import { MediaControlEvents } from '../media-control/MediaControl';
|
|
13
14
|
|
|
14
15
|
const VERSION = '2.19.12';
|
|
15
16
|
|
|
@@ -127,13 +128,9 @@ export class BottomGear extends UICorePlugin {
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
private highDefinitionUpdate(isHd: boolean) {
|
|
130
|
-
trace(`${
|
|
131
|
+
trace(`${T} highDefinitionUpdate`, { isHd });
|
|
131
132
|
this.isHd = isHd;
|
|
132
|
-
|
|
133
|
-
this.$el.find('.gear-icon').html(gearHdIcon);
|
|
134
|
-
} else {
|
|
135
|
-
this.$el.find('.gear-icon').html(gearIcon);
|
|
136
|
-
}
|
|
133
|
+
this.$el.find('.gear-icon').html(isHd ? gearHdIcon : gearIcon);
|
|
137
134
|
}
|
|
138
135
|
|
|
139
136
|
/**
|
|
@@ -154,7 +151,8 @@ export class BottomGear extends UICorePlugin {
|
|
|
154
151
|
|
|
155
152
|
mediaControl.getElement('gear')?.html(this.el);
|
|
156
153
|
this.core.trigger('gear:rendered'); // @deprecated
|
|
157
|
-
mediaControl.trigger(GearEvents.MEDIACONTROL_GEAR_RENDERED);
|
|
154
|
+
mediaControl.trigger(GearEvents.MEDIACONTROL_GEAR_RENDERED); // TODO drop
|
|
155
|
+
mediaControl.trigger(MediaControlEvents.MEDIACONTROL_GEAR_RENDERED);
|
|
158
156
|
return this;
|
|
159
157
|
}
|
|
160
158
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { MockedFunction, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { BottomGear } from '../BottomGear'
|
|
4
|
+
import { createMockCore, createMockMediaControl } from '../../../testUtils'
|
|
5
|
+
import { MediaControlEvents } from '../../media-control/MediaControl'
|
|
6
|
+
|
|
7
|
+
describe('BottomGear', () => {
|
|
8
|
+
let mediaControl: any
|
|
9
|
+
let core: any
|
|
10
|
+
let bottomGear: BottomGear
|
|
11
|
+
let onGearRendered: MockedFunction<() => void>
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
core = createMockCore()
|
|
14
|
+
mediaControl = createMockMediaControl(core)
|
|
15
|
+
core.getPlugin = vi
|
|
16
|
+
.fn()
|
|
17
|
+
.mockImplementation((name) =>
|
|
18
|
+
name === 'media_control' ? mediaControl : null,
|
|
19
|
+
)
|
|
20
|
+
bottomGear = new BottomGear(core)
|
|
21
|
+
onGearRendered = vi.fn()
|
|
22
|
+
mediaControl.on(MediaControlEvents.MEDIACONTROL_GEAR_RENDERED, onGearRendered, null)
|
|
23
|
+
bottomGear.render()
|
|
24
|
+
})
|
|
25
|
+
it('should render', () => {
|
|
26
|
+
expect(bottomGear.el.innerHTML).toMatchSnapshot()
|
|
27
|
+
})
|
|
28
|
+
it('should attach to media control', () => {
|
|
29
|
+
const gearElement = mediaControl.getElement('gear')
|
|
30
|
+
expect(gearElement[0].innerHTML).not.toEqual('')
|
|
31
|
+
expect(gearElement[0].innerHTML).toMatchSnapshot()
|
|
32
|
+
})
|
|
33
|
+
it('should emit event', () => {
|
|
34
|
+
expect(onGearRendered).toHaveBeenCalled()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`BottomGear > should attach to media control 1`] = `
|
|
4
|
+
"<div class="bottom_gear" data-track-selector=""><div class="media-control-gear" data-="">
|
|
5
|
+
<button type="button" class="button-gear gplayer-lite-btn gcore-skin-button-color" data-gear-button="-1">
|
|
6
|
+
<span class="gear-icon">/assets/icons/new/gear.svg</span>
|
|
7
|
+
</button>
|
|
8
|
+
<div class="gear-wrapper gcore-skin-bg-color">
|
|
9
|
+
<ul class="gear-options-list">
|
|
10
|
+
|
|
11
|
+
<li data-quality=""></li>
|
|
12
|
+
|
|
13
|
+
<li data-rate=""></li>
|
|
14
|
+
|
|
15
|
+
<li data-nerd=""></li>
|
|
16
|
+
|
|
17
|
+
</ul>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>"
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
exports[`BottomGear > should render 1`] = `
|
|
24
|
+
"<div class="media-control-gear" data-="">
|
|
25
|
+
<button type="button" class="button-gear gplayer-lite-btn gcore-skin-button-color" data-gear-button="-1">
|
|
26
|
+
<span class="gear-icon">/assets/icons/new/gear.svg</span>
|
|
27
|
+
</button>
|
|
28
|
+
<div class="gear-wrapper gcore-skin-bg-color">
|
|
29
|
+
<ul class="gear-options-list">
|
|
30
|
+
|
|
31
|
+
<li data-quality=""></li>
|
|
32
|
+
|
|
33
|
+
<li data-rate=""></li>
|
|
34
|
+
|
|
35
|
+
<li data-nerd=""></li>
|
|
36
|
+
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
"
|
|
41
|
+
`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Events, template, UICorePlugin } from '@clappr/core'
|
|
2
2
|
import { reportError, trace } from '@gcorevideo/utils'
|
|
3
|
+
import assert from 'assert'
|
|
3
4
|
|
|
4
5
|
import { type QualityLevel } from '../../playback.types.js'
|
|
5
6
|
import { CLAPPR_VERSION } from '../../build.js'
|
|
@@ -14,12 +15,26 @@ import arrowRightIcon from '../../../assets/icons/new/arrow-right.svg'
|
|
|
14
15
|
import arrowLeftIcon from '../../../assets/icons/new/arrow-left.svg'
|
|
15
16
|
import checkIcon from '../../../assets/icons/new/check.svg'
|
|
16
17
|
import '../../../assets/level-selector/style.scss'
|
|
17
|
-
import
|
|
18
|
-
|
|
18
|
+
import { MediaControl } from '../media-control/MediaControl.js'
|
|
19
19
|
|
|
20
20
|
const T = 'plugins.level_selector'
|
|
21
21
|
const VERSION = '2.19.4'
|
|
22
22
|
|
|
23
|
+
export interface LevelSelectorPluginSettings {
|
|
24
|
+
/**
|
|
25
|
+
* The maximum resolution to allow in the level selector.
|
|
26
|
+
*/
|
|
27
|
+
restrictResolution?: number
|
|
28
|
+
/**
|
|
29
|
+
* The labels to show in the level selector.
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* { 360: 'SD', 720: 'HD' }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
labels?: Record<number, string>
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
/**
|
|
24
39
|
* A {@link MediaControl | media control} plugin that provides a UI to control the quality level of the playback.
|
|
25
40
|
* @beta
|
|
@@ -35,11 +50,7 @@ const VERSION = '2.19.4'
|
|
|
35
50
|
*
|
|
36
51
|
* When clicked, it shows a list of quality levels to choose from.
|
|
37
52
|
*
|
|
38
|
-
* Configuration options
|
|
39
|
-
*
|
|
40
|
-
* - `labels`: The labels to show in the level selector. [video resolution]: string
|
|
41
|
-
*
|
|
42
|
-
* - `restrictResolution`: The maximum resolution to allow in the level selector.
|
|
53
|
+
* Configuration options - {@link LevelSelectorPluginSettings}
|
|
43
54
|
*
|
|
44
55
|
* @example
|
|
45
56
|
* ```ts
|
|
@@ -62,7 +73,8 @@ export class LevelSelector extends UICorePlugin {
|
|
|
62
73
|
|
|
63
74
|
private isOpen = false
|
|
64
75
|
|
|
65
|
-
private static readonly buttonTemplate: TemplateFunction =
|
|
76
|
+
private static readonly buttonTemplate: TemplateFunction =
|
|
77
|
+
template(buttonHtml)
|
|
66
78
|
|
|
67
79
|
private static readonly listTemplate: TemplateFunction = template(listHtml)
|
|
68
80
|
|
|
@@ -113,8 +125,13 @@ export class LevelSelector extends UICorePlugin {
|
|
|
113
125
|
* @internal
|
|
114
126
|
*/
|
|
115
127
|
override bindEvents() {
|
|
116
|
-
this.
|
|
117
|
-
|
|
128
|
+
const mediaControl = this.core.getPlugin('media_control') as MediaControl
|
|
129
|
+
assert(mediaControl, 'media_control plugin is required')
|
|
130
|
+
this.listenTo(
|
|
131
|
+
this.core,
|
|
132
|
+
Events.CORE_ACTIVE_CONTAINER_CHANGED,
|
|
133
|
+
this.bindPlaybackEvents,
|
|
134
|
+
)
|
|
118
135
|
}
|
|
119
136
|
|
|
120
137
|
private bindPlaybackEvents() {
|
|
@@ -123,8 +140,10 @@ export class LevelSelector extends UICorePlugin {
|
|
|
123
140
|
|
|
124
141
|
const activePlayback = this.core.activePlayback
|
|
125
142
|
|
|
126
|
-
this.listenTo(
|
|
127
|
-
|
|
143
|
+
this.listenTo(
|
|
144
|
+
activePlayback,
|
|
145
|
+
Events.PLAYBACK_LEVELS_AVAILABLE,
|
|
146
|
+
this.fillLevels,
|
|
128
147
|
)
|
|
129
148
|
this.listenTo(
|
|
130
149
|
activePlayback,
|
|
@@ -150,32 +169,27 @@ export class LevelSelector extends UICorePlugin {
|
|
|
150
169
|
this.deferRender()
|
|
151
170
|
},
|
|
152
171
|
)
|
|
153
|
-
if (activePlayback
|
|
172
|
+
if (activePlayback.levels?.length > 0) {
|
|
154
173
|
this.fillLevels(activePlayback.levels)
|
|
155
174
|
}
|
|
156
175
|
}
|
|
157
176
|
|
|
158
177
|
private onStop() {
|
|
159
178
|
trace(`${T} onStop`)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (
|
|
179
|
+
this.listenToOnce(this.core.activePlayback, Events.PLAYBACK_PLAY, () => {
|
|
180
|
+
trace(`${T} on PLAYBACK_PLAY after stop`, {
|
|
181
|
+
selectedLevelId: this.selectedLevelId,
|
|
182
|
+
})
|
|
183
|
+
if (this.core.activePlayback.getPlaybackType() === 'live') {
|
|
165
184
|
if (this.selectedLevelId !== -1) {
|
|
166
|
-
|
|
185
|
+
this.core.activePlayback.currentLevel = this.selectedLevelId
|
|
167
186
|
}
|
|
168
187
|
}
|
|
169
188
|
})
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
private shouldRender() {
|
|
173
|
-
if (!this.core.activeContainer) {
|
|
174
|
-
return false
|
|
175
|
-
}
|
|
176
|
-
|
|
177
192
|
const activePlayback = this.core.activePlayback
|
|
178
|
-
|
|
179
193
|
if (!activePlayback) {
|
|
180
194
|
return false
|
|
181
195
|
}
|
|
@@ -192,8 +206,6 @@ export class LevelSelector extends UICorePlugin {
|
|
|
192
206
|
* @internal
|
|
193
207
|
*/
|
|
194
208
|
override render() {
|
|
195
|
-
assert(this.core.getPlugin('bottom_gear'), 'bottom_gear plugin is required')
|
|
196
|
-
|
|
197
209
|
if (!this.shouldRender()) {
|
|
198
210
|
return this
|
|
199
211
|
}
|
|
@@ -213,7 +225,10 @@ export class LevelSelector extends UICorePlugin {
|
|
|
213
225
|
})
|
|
214
226
|
this.$el.html(html)
|
|
215
227
|
const gear = this.core.getPlugin('bottom_gear') as BottomGear
|
|
216
|
-
gear
|
|
228
|
+
if (!gear) {
|
|
229
|
+
trace(`${T} renderButton: bottom_gear plugin not found`)
|
|
230
|
+
}
|
|
231
|
+
gear?.getElement('quality')?.html(this.el)
|
|
217
232
|
}
|
|
218
233
|
}
|
|
219
234
|
|
|
@@ -228,6 +243,7 @@ export class LevelSelector extends UICorePlugin {
|
|
|
228
243
|
})
|
|
229
244
|
this.$el.html(html)
|
|
230
245
|
const gear = this.core.getPlugin('bottom_gear') as BottomGear
|
|
246
|
+
trace(`${T} renderDropdown: bottom_gear plugin not found`)
|
|
231
247
|
gear?.setContent(this.el)
|
|
232
248
|
}
|
|
233
249
|
|
|
@@ -236,7 +252,8 @@ export class LevelSelector extends UICorePlugin {
|
|
|
236
252
|
return maxRes
|
|
237
253
|
? this.levels.findIndex(
|
|
238
254
|
(level) =>
|
|
239
|
-
(level.height > level.width ? level.width : level.height) ===
|
|
255
|
+
(level.height > level.width ? level.width : level.height) ===
|
|
256
|
+
maxRes,
|
|
240
257
|
)
|
|
241
258
|
: -1
|
|
242
259
|
}
|
|
@@ -248,7 +265,11 @@ export class LevelSelector extends UICorePlugin {
|
|
|
248
265
|
if (maxResolution) {
|
|
249
266
|
this.removeAuto = true
|
|
250
267
|
const initialLevel = levels
|
|
251
|
-
.filter(
|
|
268
|
+
.filter(
|
|
269
|
+
(level) =>
|
|
270
|
+
(level.width > level.height ? level.height : level.width) <=
|
|
271
|
+
maxResolution,
|
|
272
|
+
)
|
|
252
273
|
.pop()
|
|
253
274
|
this.setLevel(initialLevel?.level ?? 0)
|
|
254
275
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { UICorePlugin } from '@clappr/core'
|
|
2
|
+
import { $, UICorePlugin } from '@clappr/core'
|
|
3
3
|
import FakeTimers from '@sinonjs/fake-timers'
|
|
4
4
|
import { Logger, LogTracer, setTracer } from '@gcorevideo/utils'
|
|
5
5
|
import { LevelSelector } from '../LevelSelector.js'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createMockCore,
|
|
8
|
+
createMockMediaControl,
|
|
9
|
+
createMockPlayback,
|
|
10
|
+
} from '../../../testUtils.js'
|
|
11
|
+
import { MediaControlEvents } from '../../media-control/MediaControl.js'
|
|
7
12
|
|
|
8
13
|
setTracer(new LogTracer('LevelSelector.test'))
|
|
9
14
|
Logger.enable('*')
|
|
@@ -33,6 +38,8 @@ describe('LevelSelector', () => {
|
|
|
33
38
|
let core: any
|
|
34
39
|
let levelSelector: LevelSelector
|
|
35
40
|
let activePlayback: any
|
|
41
|
+
let mediaControl: UICorePlugin
|
|
42
|
+
let bottomGear: UICorePlugin | null
|
|
36
43
|
beforeEach(() => {
|
|
37
44
|
clock = FakeTimers.install()
|
|
38
45
|
})
|
|
@@ -41,10 +48,6 @@ describe('LevelSelector', () => {
|
|
|
41
48
|
})
|
|
42
49
|
describe('basically', () => {
|
|
43
50
|
beforeEach(() => {
|
|
44
|
-
// const activeContainer = createMockContainer()
|
|
45
|
-
let mediaControl: UICorePlugin | null = null
|
|
46
|
-
let bottomGear: UICorePlugin | null = null
|
|
47
|
-
// TODO create mock core
|
|
48
51
|
core = createMockCore({
|
|
49
52
|
levelSelector: {
|
|
50
53
|
// restrictResolution: 360,
|
|
@@ -61,7 +64,7 @@ describe('LevelSelector', () => {
|
|
|
61
64
|
}
|
|
62
65
|
return null
|
|
63
66
|
})
|
|
64
|
-
mediaControl =
|
|
67
|
+
mediaControl = createMockMediaControl(core)
|
|
65
68
|
bottomGear = createBottomGear(core)
|
|
66
69
|
levelSelector = new LevelSelector(core)
|
|
67
70
|
})
|
|
@@ -126,7 +129,7 @@ describe('LevelSelector', () => {
|
|
|
126
129
|
}
|
|
127
130
|
return null
|
|
128
131
|
})
|
|
129
|
-
mediaControl =
|
|
132
|
+
mediaControl = createMockMediaControl(core)
|
|
130
133
|
bottomGear = createBottomGear(core)
|
|
131
134
|
levelSelector = new LevelSelector(core)
|
|
132
135
|
})
|
|
@@ -219,17 +222,13 @@ expect.extend({
|
|
|
219
222
|
},
|
|
220
223
|
})
|
|
221
224
|
|
|
222
|
-
function createMediaControl(core: any) {
|
|
223
|
-
const mediaControl = new UICorePlugin(core)
|
|
224
|
-
// @ts-ignore
|
|
225
|
-
mediaControl.getElement = vi.fn().mockReturnValue(null)
|
|
226
|
-
return mediaControl
|
|
227
|
-
}
|
|
228
|
-
|
|
229
225
|
function createBottomGear(core: any) {
|
|
230
226
|
const bottomGear = new UICorePlugin(core)
|
|
227
|
+
const elemets = {
|
|
228
|
+
quality: $(document.createElement('div')),
|
|
229
|
+
}
|
|
231
230
|
// @ts-ignore
|
|
232
|
-
bottomGear.getElement = vi.fn().
|
|
231
|
+
bottomGear.getElement = vi.fn().mockImplementation((name) => elemets[name])
|
|
233
232
|
// @ts-ignore
|
|
234
233
|
bottomGear.setContent = vi.fn()
|
|
235
234
|
return bottomGear
|
|
@@ -49,6 +49,17 @@ export type MediaControlElement =
|
|
|
49
49
|
| 'seekBarContainer'
|
|
50
50
|
| 'subtitlesSelector'
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Custom events emitted by the plugins to communicate with one another
|
|
54
|
+
* @beta
|
|
55
|
+
*/
|
|
56
|
+
export enum MediaControlEvents {
|
|
57
|
+
/**
|
|
58
|
+
* Emitted when the gear menu is rendered
|
|
59
|
+
*/
|
|
60
|
+
MEDIACONTROL_GEAR_RENDERED = 'mediacontrol:gear:rendered',
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
const T = 'plugins.media_control'
|
|
53
64
|
|
|
54
65
|
const LEFT_ORDER = [
|
|
@@ -422,7 +433,7 @@ export class MediaControl extends UICorePlugin {
|
|
|
422
433
|
*/
|
|
423
434
|
override disable() {
|
|
424
435
|
trace(`${T} disable`)
|
|
425
|
-
this.userDisabled = true
|
|
436
|
+
this.userDisabled = true // TODO distinguish between user and system (e.g., unplayable) disabled?
|
|
426
437
|
this.hide()
|
|
427
438
|
this.unbindKeyEvents()
|
|
428
439
|
this.$el.hide()
|
|
@@ -740,8 +751,13 @@ export class MediaControl extends UICorePlugin {
|
|
|
740
751
|
Events.CONTAINER_PLAYBACKDVRSTATECHANGED,
|
|
741
752
|
this.core.activeContainer.isDvrInUse(),
|
|
742
753
|
)
|
|
743
|
-
|
|
744
|
-
this.
|
|
754
|
+
// TODO test
|
|
755
|
+
if (this.core.activeContainer.mediaControlDisabled) {
|
|
756
|
+
this.disable()
|
|
757
|
+
} else {
|
|
758
|
+
this.enable()
|
|
759
|
+
}
|
|
760
|
+
this.trigger(Events.MEDIACONTROL_CONTAINERCHANGED) // TODO check
|
|
745
761
|
|
|
746
762
|
if (this.core.activeContainer.$el) {
|
|
747
763
|
this.core.activeContainer.$el.addClass('container-skin-1')
|
|
@@ -90,6 +90,8 @@ export class SourceController extends CorePlugin {
|
|
|
90
90
|
|
|
91
91
|
private active = false
|
|
92
92
|
|
|
93
|
+
private switching = false
|
|
94
|
+
|
|
93
95
|
private sync: SyncFn = noSync
|
|
94
96
|
|
|
95
97
|
/**
|
|
@@ -166,15 +168,18 @@ export class SourceController extends CorePlugin {
|
|
|
166
168
|
description: error?.description,
|
|
167
169
|
level: error?.level,
|
|
168
170
|
},
|
|
171
|
+
switching: this.switching,
|
|
169
172
|
retrying: this.active,
|
|
170
173
|
currentSource: this.sourcesList[this.currentSourceIndex],
|
|
171
174
|
})
|
|
175
|
+
if (this.switching) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
172
178
|
switch (error.code) {
|
|
173
179
|
case PlaybackErrorCode.MediaSourceUnavailable:
|
|
174
180
|
this.core.activeContainer?.getPlugin('poster_custom')?.disable()
|
|
175
|
-
|
|
181
|
+
this.retryPlayback()
|
|
176
182
|
break
|
|
177
|
-
// TODO handle other errors
|
|
178
183
|
default:
|
|
179
184
|
break
|
|
180
185
|
}
|
|
@@ -187,7 +192,6 @@ export class SourceController extends CorePlugin {
|
|
|
187
192
|
})
|
|
188
193
|
if (this.active) {
|
|
189
194
|
this.reset()
|
|
190
|
-
// TODO make poster reset its state on enable
|
|
191
195
|
this.core.activeContainer?.getPlugin('poster_custom')?.enable()
|
|
192
196
|
this.core.activeContainer?.getPlugin('spinner')?.hide()
|
|
193
197
|
}
|
|
@@ -205,6 +209,7 @@ export class SourceController extends CorePlugin {
|
|
|
205
209
|
currentSource: this.sourcesList[this.currentSourceIndex],
|
|
206
210
|
})
|
|
207
211
|
this.active = true
|
|
212
|
+
this.switching = true
|
|
208
213
|
this.core.activeContainer?.getPlugin('spinner')?.show(0)
|
|
209
214
|
this.getNextMediaSource().then((nextSource: PlayerMediaSourceDesc) => {
|
|
210
215
|
trace(`${T} retryPlayback syncing...`, {
|
|
@@ -213,12 +218,12 @@ export class SourceController extends CorePlugin {
|
|
|
213
218
|
const rnd = RETRY_DELAY_BLUR * Math.random()
|
|
214
219
|
this.sync(() => {
|
|
215
220
|
trace(`${T} retryPlayback loading...`)
|
|
221
|
+
this.switching = false
|
|
216
222
|
this.core.load(nextSource.source, nextSource.mimeType)
|
|
217
223
|
trace(`${T} retryPlayback loaded`, {
|
|
218
224
|
nextSource,
|
|
219
225
|
})
|
|
220
226
|
setTimeout(() => {
|
|
221
|
-
// this.core.activePlayback.consent()
|
|
222
227
|
this.core.activePlayback.play()
|
|
223
228
|
trace(`${T} retryPlayback playing`)
|
|
224
229
|
}, rnd)
|