@gcorevideo/player 2.24.1 → 2.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/assets/big-mute-button/big-mute-button.ejs +2 -2
  2. package/assets/bottom-gear/gear-sub-menu.scss +1 -0
  3. package/dist/core.js +1 -1
  4. package/dist/index.css +754 -753
  5. package/dist/index.js +151 -130
  6. package/dist/player.d.ts +72 -21
  7. package/docs/api/player.bigmutebutton.md +13 -1
  8. package/docs/api/player.clapprstatssettings.md +51 -4
  9. package/docs/api/player.clapprstatssettings.runeach.md +16 -0
  10. package/docs/api/player.clipspluginsettings.md +1 -1
  11. package/docs/api/player.clipspluginsettings.text.md +1 -1
  12. package/docs/api/player.cmcdconfig.exportids.md +4 -0
  13. package/docs/api/player.cmcdconfig.md +19 -105
  14. package/docs/api/{player.cmcdconfig.version.md → player.cmcdconfigoptions.contentid.md} +5 -3
  15. package/docs/api/player.cmcdconfigoptions.md +79 -0
  16. package/docs/api/{player.cmcdconfigpluginsettings.md → player.cmcdconfigoptions.sessionid.md} +4 -6
  17. package/docs/api/player.extendedevents.md +9 -0
  18. package/docs/api/player.md +37 -31
  19. package/docs/api/player.mediacontrol.getavailableheight.md +24 -0
  20. package/docs/api/player.mediacontrol.md +14 -0
  21. package/docs/api/{player.cmcdconfig.name.md → player.posterpluginsettings.custom.md} +4 -3
  22. package/docs/api/player.posterpluginsettings.md +108 -7
  23. package/docs/api/player.posterpluginsettings.showfornoop.md +16 -0
  24. package/docs/api/player.posterpluginsettings.showonvideoend.md +16 -0
  25. package/docs/api/{player.cmcdconfig.bindevents.md → player.posterpluginsettings.url.md} +4 -7
  26. package/lib/plugins/big-mute-button/BigMuteButton.d.ts +15 -13
  27. package/lib/plugins/big-mute-button/BigMuteButton.d.ts.map +1 -1
  28. package/lib/plugins/big-mute-button/BigMuteButton.js +68 -83
  29. package/lib/plugins/bottom-gear/BottomGear.d.ts +1 -0
  30. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
  31. package/lib/plugins/bottom-gear/BottomGear.js +17 -17
  32. package/lib/plugins/clappr-stats/ClapprStats.d.ts +6 -2
  33. package/lib/plugins/clappr-stats/ClapprStats.d.ts.map +1 -1
  34. package/lib/plugins/clips/Clips.d.ts +1 -1
  35. package/lib/plugins/clips/Clips.d.ts.map +1 -1
  36. package/lib/plugins/clips/Clips.js +2 -1
  37. package/lib/plugins/cmcd-config/CmcdConfig.d.ts +34 -11
  38. package/lib/plugins/cmcd-config/CmcdConfig.d.ts.map +1 -1
  39. package/lib/plugins/cmcd-config/CmcdConfig.js +28 -18
  40. package/lib/plugins/media-control/MediaControl.d.ts +11 -0
  41. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  42. package/lib/plugins/media-control/MediaControl.js +19 -5
  43. package/lib/plugins/poster/Poster.d.ts +7 -3
  44. package/lib/plugins/poster/Poster.d.ts.map +1 -1
  45. package/lib/plugins/source-controller/SourceController.d.ts +1 -0
  46. package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
  47. package/lib/plugins/source-controller/SourceController.js +20 -9
  48. package/lib/testUtils.d.ts +1 -0
  49. package/lib/testUtils.d.ts.map +1 -1
  50. package/lib/testUtils.js +3 -0
  51. package/package.json +1 -1
  52. package/src/plugins/big-mute-button/BigMuteButton.ts +75 -110
  53. package/src/plugins/big-mute-button/__tests__/BigMuteButton.test.ts +38 -0
  54. package/src/plugins/big-mute-button/__tests__/__snapshots__/BigMuteButton.test.ts.snap +8 -0
  55. package/src/plugins/bottom-gear/BottomGear.ts +40 -28
  56. package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +34 -7
  57. package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +5 -2
  58. package/src/plugins/clappr-stats/ClapprStats.ts +5 -1
  59. package/src/plugins/clips/Clips.ts +3 -2
  60. package/src/plugins/cmcd-config/CmcdConfig.ts +33 -27
  61. package/src/plugins/media-control/MediaControl.ts +23 -6
  62. package/src/plugins/poster/Poster.ts +6 -2
  63. package/src/plugins/source-controller/SourceController.ts +25 -9
  64. package/src/plugins/source-controller/__tests__/SourceController.test.ts +28 -8
  65. package/src/testUtils.ts +3 -0
  66. package/temp/player.api.json +229 -154
  67. package/tsconfig.tsbuildinfo +1 -1
  68. package/docs/api/player.cmcdconfig.supportedversion.md +0 -14
@@ -4,7 +4,6 @@ import { $, Container, Core, CorePlugin, Events } from '@clappr/core'
4
4
 
5
5
  import { generateSessionId } from './utils'
6
6
  import { CLAPPR_VERSION } from '../../build.js'
7
- import { CoreOptions } from 'src/internal.types'
8
7
 
9
8
  const CMCD_KEYS = [
10
9
  'br',
@@ -28,15 +27,16 @@ const CMCD_KEYS = [
28
27
  ]
29
28
 
30
29
  /**
30
+ * Config options for the {@link CmcdConfig} plugin
31
31
  * @beta
32
32
  */
33
- export type CmcdConfigPluginSettings = {
33
+ export interface CmcdConfigOptions {
34
34
  /**
35
- * Session ID. If ommitted, a random UUID will be generated
35
+ * `sid` value. If ommitted, a random UUID will be generated
36
36
  */
37
- sessionId: string
37
+ sessionId?: string
38
38
  /**
39
- * Content ID,
39
+ * `cid` value.
40
40
  * If ommitted, the pathname part of the first source URL will be used
41
41
  */
42
42
  contentId?: string
@@ -45,11 +45,23 @@ export type CmcdConfigPluginSettings = {
45
45
  // const T = 'plugins.cmcd'
46
46
 
47
47
  /**
48
- * A `PLUGIN` that configures CMCD for playback
48
+ * A `PLUGIN` that configures {@link https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf | CMCD} for playback
49
49
  * @beta
50
50
  * @remarks
51
- * Configuration options
52
- * `cmcd`: {@link CmcdConfigPluginSettings}
51
+ * Configuration options - {@link CmcdConfigOptions}.
52
+ * @example
53
+ * ```ts
54
+ * import { CmcdConfig } from '@gcorevideo/player'
55
+ * Player.registerPlugin(CmcdConfig)
56
+ *
57
+ * const player = new Player({
58
+ * source: 'https://example.com/video.mp4',
59
+ * cmcd: {
60
+ * sessionId: '1234567890',
61
+ * contentId: 'f572d396fae9206628714fb2ce00f72e94f2258f',
62
+ * },
63
+ * })
64
+ * ```
53
65
  */
54
66
  export class CmcdConfig extends CorePlugin {
55
67
  private sid: string
@@ -57,16 +69,22 @@ export class CmcdConfig extends CorePlugin {
57
69
  private cid = ''
58
70
 
59
71
  /**
60
- * @inheritdocs
72
+ * @internal
61
73
  */
62
74
  get name() {
63
75
  return 'cmcd'
64
76
  }
65
77
 
78
+ /**
79
+ * @internal
80
+ */
66
81
  get version() {
67
82
  return '0.1.0'
68
83
  }
69
84
 
85
+ /**
86
+ * @internal
87
+ */
70
88
  get supportedVersion() {
71
89
  return CLAPPR_VERSION
72
90
  }
@@ -78,7 +96,7 @@ export class CmcdConfig extends CorePlugin {
78
96
  }
79
97
 
80
98
  /**
81
- * @inheritdocs
99
+ * @internal
82
100
  */
83
101
  override bindEvents() {
84
102
  this.listenTo(this.core, Events.CORE_CONTAINERS_CREATED, () =>
@@ -86,6 +104,11 @@ export class CmcdConfig extends CorePlugin {
86
104
  )
87
105
  }
88
106
 
107
+ /**
108
+ * Returns the current `sid` and `cid` values.
109
+ * Useful when the auto-generated values need to be known.
110
+ * @returns `sid` and `cid` values
111
+ */
89
112
  exportIds(): { sid: string; cid: string } {
90
113
  return {
91
114
  sid: this.sid,
@@ -123,23 +146,6 @@ export class CmcdConfig extends CorePlugin {
123
146
  }
124
147
  }
125
148
 
126
- private updateHlsjsSettings(
127
- options: CoreOptions,
128
- { cid, sid }: { cid: string; sid: string },
129
- ) {
130
- $.extend(true, options, {
131
- playback: {
132
- hlsjsConfig: {
133
- cmcd: {
134
- includeKeys: CMCD_KEYS,
135
- sessionId: sid,
136
- contentId: cid,
137
- },
138
- },
139
- },
140
- })
141
- }
142
-
143
149
  private generateContentId() {
144
150
  return new URL(
145
151
  this.core.options.source ?? this.core.options.sources[0].source,
@@ -136,6 +136,10 @@ const LEFT_ORDER = [
136
136
  'dvr',
137
137
  ]
138
138
 
139
+ /**
140
+ * Extended events for the {@link MediaControl} plugin
141
+ * @beta
142
+ */
139
143
  export enum ExtendedEvents {
140
144
  MEDIACONTROL_VOLUME = 'mediacontrol:volume',
141
145
  MEDIACONTROL_MENU_COLLAPSE = 'mediacontrol:menu:collapse',
@@ -531,6 +535,18 @@ export class MediaControl extends UICorePlugin {
531
535
  this.show()
532
536
  }
533
537
 
538
+ /**
539
+ *
540
+ * @returns Vertical space available to render something on top of the container.
541
+ * @remarks
542
+ * This takes into account the container height and excludes the height of the controls bar
543
+ */
544
+ getAvailableHeight() {
545
+ return (
546
+ this.core.$el.height() - this.$el.find('.media-control-layer').height()
547
+ )
548
+ }
549
+
534
550
  /**
535
551
  * Set the initial volume, which is preserved when playback is interrupted by an advertisement
536
552
  */
@@ -545,6 +561,7 @@ export class MediaControl extends UICorePlugin {
545
561
  this.updateVolumeUI()
546
562
  }
547
563
 
564
+ // TODO check if CONTAINER_SETTINGSUPDATE handler is sufficient
548
565
  private onLoadedMetadata() {
549
566
  const video = this.core.activePlayback?.el
550
567
 
@@ -645,12 +662,12 @@ export class MediaControl extends UICorePlugin {
645
662
  }
646
663
 
647
664
  private mousemoveOnSeekBar(event: MouseEvent) {
648
- const offset = MediaControl.getPageX(event) -
649
- (this.$seekBarContainer.offset().left ?? 0) // TODO check if the result can be negative
650
- const hoverOffset =
651
- offset -
652
- (this.$seekBarHover.width() ?? 0) / 2
653
- const pos = offset ? Math.min(1, Math.max(offset / this.$seekBarContainer.width(), 0)) : 0
665
+ const offset =
666
+ MediaControl.getPageX(event) - (this.$seekBarContainer.offset().left ?? 0) // TODO check if the result can be negative
667
+ const hoverOffset = offset - (this.$seekBarHover.width() ?? 0) / 2
668
+ const pos = offset
669
+ ? Math.min(1, Math.max(offset / this.$seekBarContainer.width(), 0))
670
+ : 0
654
671
  if (this.settings.seekEnabled) {
655
672
  // TODO test that it works when the element does not exist
656
673
  this.$seekBarHover.css({ left: hoverOffset })
@@ -19,7 +19,11 @@ import posterHTML from '../../../assets/poster/poster.ejs'
19
19
  import playIcon from '../../../assets/icons/new/play.svg'
20
20
  import { PlaybackError } from '../../playback.types.js'
21
21
 
22
- export type PosterPluginSettings = {
22
+ /**
23
+ * Config options for the {@link Poster} plugin
24
+ * @beta
25
+ */
26
+ export interface PosterPluginSettings {
23
27
  /**
24
28
  * Custom CSS background
25
29
  */
@@ -33,7 +37,7 @@ export type PosterPluginSettings = {
33
37
  */
34
38
  url?: string
35
39
  /**
36
- * Whether to show the poster after playback has ended @default true
40
+ * Whether to show the poster after playback has ended, by default `true`
37
41
  */
38
42
  showOnVideoEnd?: boolean
39
43
  }
@@ -1,5 +1,5 @@
1
1
  import {
2
- Events as ClapprEvents,
2
+ Events as Events,
3
3
  CorePlugin,
4
4
  type Core as ClapprCore,
5
5
  } from '@clappr/core'
@@ -127,6 +127,8 @@ export class SourceController extends CorePlugin {
127
127
 
128
128
  private active = false
129
129
 
130
+ private autoPlay = false
131
+
130
132
  private switching = false
131
133
 
132
134
  private sync: SyncFn = noSync
@@ -165,10 +167,10 @@ export class SourceController extends CorePlugin {
165
167
  override bindEvents() {
166
168
  super.bindEvents()
167
169
 
168
- this.listenTo(this.core, ClapprEvents.CORE_READY, this.onCoreReady)
170
+ this.listenTo(this.core, Events.CORE_READY, this.onCoreReady)
169
171
  this.listenTo(
170
172
  this.core,
171
- ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED,
173
+ Events.CORE_ACTIVE_CONTAINER_CHANGED,
172
174
  this.onActiveContainerChanged,
173
175
  )
174
176
  }
@@ -200,7 +202,7 @@ export class SourceController extends CorePlugin {
200
202
 
201
203
  private bindContainerEventListeners() {
202
204
  this.core.activePlayback.on(
203
- ClapprEvents.PLAYBACK_ERROR,
205
+ Events.PLAYBACK_ERROR,
204
206
  (error: PlaybackError) => {
205
207
  trace(`${T} on PLAYBACK_ERROR`, {
206
208
  error: {
@@ -215,6 +217,7 @@ export class SourceController extends CorePlugin {
215
217
  if (this.switching) {
216
218
  return
217
219
  }
220
+ this.autoPlay = !!this.core.activeContainer.actionsMetadata.playEvent?.autoPlay
218
221
  switch (error.code) {
219
222
  case PlaybackErrorCode.MediaSourceUnavailable:
220
223
  this.core.activeContainer?.getPlugin('poster')?.disable()
@@ -225,7 +228,7 @@ export class SourceController extends CorePlugin {
225
228
  }
226
229
  },
227
230
  )
228
- this.core.activePlayback.on(ClapprEvents.PLAYBACK_PLAY, () => {
231
+ this.core.activePlayback.on(Events.PLAYBACK_PLAY, () => {
229
232
  trace(`${T} on PLAYBACK_PLAY`, {
230
233
  currentSource: this.sourcesList[this.currentSourceIndex],
231
234
  retrying: this.active,
@@ -236,6 +239,16 @@ export class SourceController extends CorePlugin {
236
239
  this.core.activeContainer?.getPlugin('spinner')?.hide()
237
240
  }
238
241
  })
242
+ this.listenTo(
243
+ this.core.activeContainer,
244
+ Events.CONTAINER_PLAY,
245
+ (_: string, { autoPlay }: { autoPlay?: boolean}) => {
246
+ trace(`${T} onContainerPlay`, {
247
+ autoPlay,
248
+ })
249
+ this.autoPlay = !!autoPlay
250
+ },
251
+ )
239
252
  }
240
253
 
241
254
  private reset() {
@@ -255,17 +268,20 @@ export class SourceController extends CorePlugin {
255
268
  trace(`${T} retryPlayback syncing...`, {
256
269
  nextSource,
257
270
  })
258
- const rnd = RETRY_DELAY_BLUR * Math.random()
271
+ const rnd = Math.round(RETRY_DELAY_BLUR * Math.random())
259
272
  this.sync(() => {
260
- trace(`${T} retryPlayback loading...`)
261
273
  this.switching = false
262
274
  this.core.load(nextSource.source, nextSource.mimeType)
263
275
  trace(`${T} retryPlayback loaded`, {
264
276
  nextSource,
265
277
  })
266
278
  setTimeout(() => {
267
- this.core.activePlayback.play()
268
- trace(`${T} retryPlayback playing`)
279
+ trace(`${T} retryPlayback playing`, {
280
+ autoPlay: this.autoPlay,
281
+ })
282
+ this.core.activeContainer.play({
283
+ autoPlay: this.autoPlay,
284
+ })
269
285
  }, rnd)
270
286
  })
271
287
  })
@@ -1,5 +1,4 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
-
3
2
  import FakeTimers from '@sinonjs/fake-timers'
4
3
 
5
4
  import { SourceController } from '../SourceController'
@@ -11,6 +10,11 @@ import {
11
10
  createSpinnerPlugin,
12
11
  } from '../../../testUtils.js'
13
12
 
13
+ // import { LogTracer, Logger, setTracer } from '@gcorevideo/utils'
14
+
15
+ // setTracer(new LogTracer('SourceController.test'))
16
+ // Logger.enable('*')
17
+
14
18
  const MOCK_SOURCES = [
15
19
  {
16
20
  source: 'http://0eab.cdn.globo.com/1932-1447.mpd',
@@ -85,8 +89,6 @@ describe('SourceController', () => {
85
89
  code: PlaybackErrorCode.MediaSourceUnavailable,
86
90
  })
87
91
  nextPlayback = createMockPlayback()
88
- vi.spyOn(nextPlayback, 'consent')
89
- vi.spyOn(nextPlayback, 'play')
90
92
  core.activePlayback = nextPlayback
91
93
  })
92
94
  it('should load the next source after a delay', async () => {
@@ -101,7 +103,7 @@ describe('SourceController', () => {
101
103
  await clock.tickAsync(1000)
102
104
  expect(nextPlayback.play).not.toHaveBeenCalled()
103
105
  await clock.tickAsync(500) // TODO check randomness
104
- expect(nextPlayback.play).toHaveBeenCalled()
106
+ expect(core.activeContainer.play).toHaveBeenCalled()
105
107
  })
106
108
  it('should not call consent', async () => {
107
109
  await clock.tickAsync(1500)
@@ -151,8 +153,6 @@ describe('SourceController', () => {
151
153
  code: PlaybackErrorCode.MediaSourceUnavailable,
152
154
  })
153
155
  nextPlayback = createMockPlayback()
154
- vi.spyOn(nextPlayback, 'consent')
155
- vi.spyOn(nextPlayback, 'play')
156
156
  core.activePlayback = nextPlayback
157
157
  })
158
158
  it('should sync with the spinner before reloading the source', async () => {
@@ -234,8 +234,6 @@ describe('SourceController', () => {
234
234
  })
235
235
  await clock.tickAsync(1)
236
236
  nextPlayback = createMockPlayback()
237
- vi.spyOn(nextPlayback, 'consent')
238
- vi.spyOn(nextPlayback, 'play')
239
237
  core.activePlayback = nextPlayback
240
238
  })
241
239
  it('should run handler only once', async () => {
@@ -248,5 +246,27 @@ describe('SourceController', () => {
248
246
  )
249
247
  })
250
248
  })
249
+ describe('when playback was started with autoPlay', () => {
250
+ beforeEach(async () => {
251
+ core = createMockCore({
252
+ sources: MOCK_SOURCES,
253
+ })
254
+ const _ = new SourceController(core)
255
+ core.emit('core:ready')
256
+ core.emit('core:active:container:changed')
257
+ core.activeContainer.actionsMetadata.playEvent = {
258
+ autoPlay: true,
259
+ }
260
+ core.activePlayback.emit('playback:error', {
261
+ code: PlaybackErrorCode.MediaSourceUnavailable,
262
+ })
263
+ await clock.tickAsync(1500)
264
+ })
265
+ it('should call play with autoPlay metadata', () => {
266
+ expect(core.activeContainer.play).toHaveBeenCalledWith({
267
+ autoPlay: true,
268
+ })
269
+ })
270
+ })
251
271
  })
252
272
  })
package/src/testUtils.ts CHANGED
@@ -87,6 +87,7 @@ export function createMockContainer(
87
87
  const el = playback.el
88
88
  const emitter = new Events()
89
89
  return Object.assign(emitter, {
90
+ actionsMetadata: {},
90
91
  el,
91
92
  playback,
92
93
  options: {
@@ -126,6 +127,8 @@ export function createMockMediaControl(core: any) {
126
127
  // @ts-ignore
127
128
  mediaControl.container = core.activeContainer
128
129
  // @ts-ignore
130
+ mediaControl.getAvailableHeight = vi.fn().mockReturnValue(300)
131
+ // @ts-ignore
129
132
  mediaControl.toggleElement = vi.fn()
130
133
  vi.spyOn(mediaControl, 'trigger')
131
134
  core.$el.append(mediaControl.$el)