@srgssr/pillarbox-web 1.12.2

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/dist/pillarbox-core.cjs.js +307 -0
  3. package/dist/pillarbox-core.cjs.min.js +2 -0
  4. package/dist/pillarbox-core.cjs.min.js.map +1 -0
  5. package/dist/pillarbox-core.es.js +305 -0
  6. package/dist/pillarbox-core.es.min.js +2 -0
  7. package/dist/pillarbox-core.es.min.js.map +1 -0
  8. package/dist/pillarbox-core.umd.js +68327 -0
  9. package/dist/pillarbox-core.umd.min.js +35 -0
  10. package/dist/pillarbox-core.umd.min.js.map +1 -0
  11. package/dist/pillarbox.cjs.js +3571 -0
  12. package/dist/pillarbox.cjs.min.js +2 -0
  13. package/dist/pillarbox.cjs.min.js.map +1 -0
  14. package/dist/pillarbox.es.js +3569 -0
  15. package/dist/pillarbox.es.min.js +2 -0
  16. package/dist/pillarbox.es.min.js.map +1 -0
  17. package/dist/pillarbox.min.css +1 -0
  18. package/dist/pillarbox.min.css.map +1 -0
  19. package/dist/pillarbox.umd.js +71591 -0
  20. package/dist/pillarbox.umd.min.js +35 -0
  21. package/dist/pillarbox.umd.min.js.map +1 -0
  22. package/dist/types/build.es.d.ts +5 -0
  23. package/dist/types/build.es.d.ts.map +1 -0
  24. package/dist/types/src/analytics/SRGAnalytics.d.ts +414 -0
  25. package/dist/types/src/analytics/SRGAnalytics.d.ts.map +1 -0
  26. package/dist/types/src/components/player.d.ts +116 -0
  27. package/dist/types/src/components/player.d.ts.map +1 -0
  28. package/dist/types/src/components/typedef.d.ts +14 -0
  29. package/dist/types/src/components/typedef.d.ts.map +1 -0
  30. package/dist/types/src/dataProvider/model/MediaComposition.d.ts +154 -0
  31. package/dist/types/src/dataProvider/model/MediaComposition.d.ts.map +1 -0
  32. package/dist/types/src/dataProvider/model/typedef.d.ts +1485 -0
  33. package/dist/types/src/dataProvider/model/typedef.d.ts.map +1 -0
  34. package/dist/types/src/dataProvider/services/DataProvider.d.ts +40 -0
  35. package/dist/types/src/dataProvider/services/DataProvider.d.ts.map +1 -0
  36. package/dist/types/src/lang/de.d.ts +2 -0
  37. package/dist/types/src/lang/de.d.ts.map +1 -0
  38. package/dist/types/src/lang/en.d.ts +2 -0
  39. package/dist/types/src/lang/en.d.ts.map +1 -0
  40. package/dist/types/src/lang/fr.d.ts +2 -0
  41. package/dist/types/src/lang/fr.d.ts.map +1 -0
  42. package/dist/types/src/lang/it.d.ts +2 -0
  43. package/dist/types/src/lang/it.d.ts.map +1 -0
  44. package/dist/types/src/lang/rm.d.ts +2 -0
  45. package/dist/types/src/lang/rm.d.ts.map +1 -0
  46. package/dist/types/src/middleware/srgssr.d.ts +271 -0
  47. package/dist/types/src/middleware/srgssr.d.ts.map +1 -0
  48. package/dist/types/src/middleware/typedef.d.ts +67 -0
  49. package/dist/types/src/middleware/typedef.d.ts.map +1 -0
  50. package/dist/types/src/pillarbox.d.ts +11 -0
  51. package/dist/types/src/pillarbox.d.ts.map +1 -0
  52. package/dist/types/src/utils/AkamaiTokenService.d.ts +73 -0
  53. package/dist/types/src/utils/AkamaiTokenService.d.ts.map +1 -0
  54. package/dist/types/src/utils/Drm.d.ts +31 -0
  55. package/dist/types/src/utils/Drm.d.ts.map +1 -0
  56. package/dist/types/src/utils/Image.d.ts +33 -0
  57. package/dist/types/src/utils/Image.d.ts.map +1 -0
  58. package/dist/types/src/utils/PlayerEvents.d.ts +177 -0
  59. package/dist/types/src/utils/PlayerEvents.d.ts.map +1 -0
  60. package/dist/types/src/utils/typedef.d.ts +17 -0
  61. package/dist/types/src/utils/typedef.d.ts.map +1 -0
  62. package/package.json +109 -0
  63. package/scss/components/_big-play.scss +30 -0
  64. package/scss/components/_captions-settings.scss +71 -0
  65. package/scss/components/_control-bar.scss +15 -0
  66. package/scss/components/_control-spacer.scss +9 -0
  67. package/scss/components/_control.scss +8 -0
  68. package/scss/components/_error.scss +10 -0
  69. package/scss/components/_layout.scss +63 -0
  70. package/scss/components/_loading.scss +34 -0
  71. package/scss/components/_progress.scss +70 -0
  72. package/scss/components/_slider.scss +9 -0
  73. package/scss/components/_text-track.scss +8 -0
  74. package/scss/components/_time.scss +29 -0
  75. package/scss/components/_title-bar.scss +9 -0
  76. package/scss/components/_volume.scss +16 -0
  77. package/scss/components/menu/_menu-popup.scss +30 -0
  78. package/scss/components/menu/_menu.scss +26 -0
  79. package/scss/pillarbox.scss +26 -0
@@ -0,0 +1,3569 @@
1
+ import videojs from 'video.js';
2
+ import 'videojs-contrib-eme';
3
+
4
+ function ownKeys(e, r) {
5
+ var t = Object.keys(e);
6
+ if (Object.getOwnPropertySymbols) {
7
+ var o = Object.getOwnPropertySymbols(e);
8
+ r && (o = o.filter(function (r) {
9
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
10
+ })), t.push.apply(t, o);
11
+ }
12
+ return t;
13
+ }
14
+ function _objectSpread2(e) {
15
+ for (var r = 1; r < arguments.length; r++) {
16
+ var t = null != arguments[r] ? arguments[r] : {};
17
+ r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
18
+ _defineProperty(e, r, t[r]);
19
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
20
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
21
+ });
22
+ }
23
+ return e;
24
+ }
25
+ function _toPrimitive(t, r) {
26
+ if ("object" != typeof t || !t) return t;
27
+ var e = t[Symbol.toPrimitive];
28
+ if (void 0 !== e) {
29
+ var i = e.call(t, r || "default");
30
+ if ("object" != typeof i) return i;
31
+ throw new TypeError("@@toPrimitive must return a primitive value.");
32
+ }
33
+ return ("string" === r ? String : Number)(t);
34
+ }
35
+ function _toPropertyKey(t) {
36
+ var i = _toPrimitive(t, "string");
37
+ return "symbol" == typeof i ? i : i + "";
38
+ }
39
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
40
+ try {
41
+ var info = gen[key](arg);
42
+ var value = info.value;
43
+ } catch (error) {
44
+ reject(error);
45
+ return;
46
+ }
47
+ if (info.done) {
48
+ resolve(value);
49
+ } else {
50
+ Promise.resolve(value).then(_next, _throw);
51
+ }
52
+ }
53
+ function _asyncToGenerator(fn) {
54
+ return function () {
55
+ var self = this,
56
+ args = arguments;
57
+ return new Promise(function (resolve, reject) {
58
+ var gen = fn.apply(self, args);
59
+ function _next(value) {
60
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
61
+ }
62
+ function _throw(err) {
63
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
64
+ }
65
+ _next(undefined);
66
+ });
67
+ };
68
+ }
69
+ function _defineProperty(obj, key, value) {
70
+ key = _toPropertyKey(key);
71
+ if (key in obj) {
72
+ Object.defineProperty(obj, key, {
73
+ value: value,
74
+ enumerable: true,
75
+ configurable: true,
76
+ writable: true
77
+ });
78
+ } else {
79
+ obj[key] = value;
80
+ }
81
+ return obj;
82
+ }
83
+ function _objectWithoutPropertiesLoose(source, excluded) {
84
+ if (source == null) return {};
85
+ var target = {};
86
+ var sourceKeys = Object.keys(source);
87
+ var key, i;
88
+ for (i = 0; i < sourceKeys.length; i++) {
89
+ key = sourceKeys[i];
90
+ if (excluded.indexOf(key) >= 0) continue;
91
+ target[key] = source[key];
92
+ }
93
+ return target;
94
+ }
95
+ function _objectWithoutProperties(source, excluded) {
96
+ if (source == null) return {};
97
+ var target = _objectWithoutPropertiesLoose(source, excluded);
98
+ var key, i;
99
+ if (Object.getOwnPropertySymbols) {
100
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
101
+ for (i = 0; i < sourceSymbolKeys.length; i++) {
102
+ key = sourceSymbolKeys[i];
103
+ if (excluded.indexOf(key) >= 0) continue;
104
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
105
+ target[key] = source[key];
106
+ }
107
+ }
108
+ return target;
109
+ }
110
+
111
+ var version = "1.12.1";
112
+
113
+ /**
114
+ * @ignore
115
+ * @type {typeof import('video.js/dist/types/player').default}
116
+ */
117
+ const vjsPlayer = videojs.getComponent('player');
118
+
119
+ /**
120
+ * This class extends the video.js Player.
121
+ *
122
+ * @class Player
123
+ * @see https://docs.videojs.com/player
124
+ */
125
+ class Player extends vjsPlayer {
126
+ constructor(tag, options, ready) {
127
+ /**
128
+ * Configuration for plugins.
129
+ *
130
+ * @see [Video.js Plugins Option]{@link https://videojs.com/guides/options/#plugins}
131
+ * @type {Object}
132
+ * @property {boolean} eme - Enable the EME (Encrypted Media Extensions) plugin.
133
+ */
134
+ options = videojs.obj.merge(options, {
135
+ plugins: {
136
+ eme: true
137
+ }
138
+ });
139
+ super(tag, options, ready);
140
+ }
141
+
142
+ /**
143
+ * A getter/setter for the media's audio track.
144
+ * Activates the audio track according to the language and kind properties.
145
+ * Falls back on the first audio track found if the kind property is not satisfied.
146
+ *
147
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/AudioTrack/kind
148
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/AudioTrack/language
149
+ *
150
+ * @param {import('./typedef').TrackSelector} [trackSelector]
151
+ *
152
+ * @example
153
+ * // Get the current audio track
154
+ * player.audioTrack();
155
+ *
156
+ * @example
157
+ * // Activate an audio track based on language and kind properties
158
+ * player.audioTrack({language:'en', kind:'description'});
159
+ *
160
+ * @example
161
+ * // Activate first audio track found corresponding to language
162
+ * player.audioTrack({language:'fr'});
163
+ *
164
+ * @return {import('video.js/dist/types/tracks/audio-track').default | undefined} The
165
+ * currently enabled audio track. See {@link https://docs.videojs.com/audiotrack}.
166
+ */
167
+ audioTrack(trackSelector) {
168
+ const audioTracks = Array.from(this.player().audioTracks());
169
+ if (!trackSelector) {
170
+ return audioTracks.find(audioTrack => audioTrack.enabled);
171
+ }
172
+ const {
173
+ kind,
174
+ language
175
+ } = trackSelector;
176
+ const audioTrack = audioTracks.find(audioTrack => audioTrack.language === language && audioTrack.kind === kind) || audioTracks.find(audioTrack => audioTrack.language === language);
177
+ if (audioTrack) {
178
+ audioTrack.enabled = true;
179
+ }
180
+ return audioTrack;
181
+ }
182
+
183
+ /**
184
+ * Calculates an array of ranges based on the `buffered()` data.
185
+ *
186
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered
187
+ *
188
+ * @returns {Array<{start: number, end: number}>} An array of objects representing start and end points of buffered ranges.
189
+ */
190
+ bufferedRanges() {
191
+ const ranges = [];
192
+ for (let i = 0; i < this.buffered().length; i++) {
193
+ const start = this.buffered().start(i);
194
+ const end = this.buffered().end(i);
195
+ ranges.push({
196
+ start,
197
+ end
198
+ });
199
+ }
200
+ return ranges;
201
+ }
202
+
203
+ /**
204
+ * Get the percent (as a decimal) of the media that's been played.
205
+ * This method is not a part of the native HTML video API.
206
+ *
207
+ * Live streams with DVR are not currently supported.
208
+ *
209
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#htmlmediaelement.played
210
+ *
211
+ * @return {number}
212
+ * A decimal between 0 and 1 representing the percent
213
+ * that is played 0 being 0% and 1 being 100%
214
+ */
215
+ playedPercent() {
216
+ if (!Number.isFinite(this.duration())) return NaN;
217
+ let timePlayed = 0;
218
+ for (let i = 0; i != this.played().length; i++) {
219
+ timePlayed += this.played().end(i) - this.played().start(i);
220
+ }
221
+ const percentPlayed = timePlayed / this.duration();
222
+ return percentPlayed;
223
+ }
224
+
225
+ /**
226
+ * Get an array of ranges based on the `played` data.
227
+ *
228
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#htmlmediaelement.played
229
+ *
230
+ * @returns {Array<{start: number, end: number}>} An array of objects representing start and end points of played ranges.
231
+ */
232
+ playedRanges() {
233
+ const ranges = [];
234
+ for (let i = 0; i < this.played().length; i++) {
235
+ const start = this.played().start(i);
236
+ const end = this.played().end(i);
237
+ ranges.push({
238
+ start,
239
+ end
240
+ });
241
+ }
242
+ return ranges;
243
+ }
244
+
245
+ /**
246
+ * Calculates an array of ranges based on the `seekable()` data.
247
+ *
248
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seekable
249
+ *
250
+ * @returns {Array<{start: number, end: number}>} An array of objects representing start and end points of seekable ranges.
251
+ */
252
+ seekableRanges() {
253
+ const ranges = [];
254
+ for (let i = 0; i < this.seekable().length; i++) {
255
+ const start = this.seekable().start(i);
256
+ const end = this.seekable().end(i);
257
+ ranges.push({
258
+ start,
259
+ end
260
+ });
261
+ }
262
+ return ranges;
263
+ }
264
+
265
+ /**
266
+ * A getter/setter for the media's text track.
267
+ * Activates the text track according to the language and kind properties.
268
+ * Falls back on the first text track found if the kind property is not satisfied.
269
+ * Disables all subtitle tracks that are `showing` if the `trackSelector` is truthy but does not satisfy any condition.
270
+ *
271
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack/kind
272
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/textTrack/language
273
+ *
274
+ * @param {import('./typedef').TrackSelector} [trackSelector]
275
+ *
276
+ * @example
277
+ * // Get the current text track
278
+ * player.textTrack();
279
+ *
280
+ * @example
281
+ * // Disable all text tracks has a side effect
282
+ * player.textTrack('off');
283
+ * player.textTrack({});
284
+ *
285
+ * @example
286
+ * // Activate an text track based on language and kind properties
287
+ * player.textTrack({language:'en', kind:'captions'});
288
+ *
289
+ * @example
290
+ * // Activate first text track found corresponding to language
291
+ * player.textTrack({language:'fr'});
292
+ *
293
+ * @return {import('video.js/dist/types/tracks/text-track').default | undefined} The
294
+ * currently enabled text track. See {@link https://docs.videojs.com/texttrack}.
295
+ */
296
+ textTrack(trackSelector) {
297
+ const textTracks = Array.from(this.player().textTracks()).filter(textTrack => !['chapters', 'metadata'].includes(textTrack.kind));
298
+ if (!trackSelector) {
299
+ return textTracks.find(textTrack => textTrack.mode === 'showing');
300
+ }
301
+ textTracks.forEach(textTrack => textTrack.mode = 'disabled');
302
+ const {
303
+ kind,
304
+ language
305
+ } = trackSelector;
306
+ const textTrack = textTracks.find(textTrack => {
307
+ if (textTrack.language === language && textTrack.kind === kind) {
308
+ textTrack.mode = 'showing';
309
+ }
310
+ return textTrack.mode === 'showing';
311
+ }) || textTracks.find(textTrack => {
312
+ if (textTrack.language === language) {
313
+ textTrack.mode = 'showing';
314
+ }
315
+ return textTrack.mode === 'showing';
316
+ });
317
+ return textTrack;
318
+ }
319
+ }
320
+ videojs.registerComponent('player', Player);
321
+
322
+ /**
323
+ * Pillarbox is an alias for the video.js namespace with additional options.
324
+ *
325
+ * @namespace
326
+ * @see https://docs.videojs.com/module-videojs-videojs
327
+ * @type {videojs}
328
+ */
329
+ const pillarbox = videojs;
330
+ pillarbox.VERSION = {
331
+ pillarbox: version,
332
+ videojs: videojs.VERSION,
333
+ [videojs.VhsSourceHandler.name]: videojs.VhsSourceHandler.VERSION,
334
+ eme: videojs.getPlugin('eme').VERSION
335
+ };
336
+
337
+ /**
338
+ * Enable smooth seeking for Pillarbox.
339
+ *
340
+ * @see [Video.js enableSmoothSeeking Option]{@link https://videojs.com/guides/options/#enablesmoothseeking}
341
+ * @type {boolean}
342
+ * @default true
343
+ */
344
+ pillarbox.options.enableSmoothSeeking = true;
345
+ /**
346
+ * Enable fill mode for the video player, allowing it to expand to fill the container.
347
+ *
348
+ * @see [Video.js Fill Option]{@link https://videojs.com/guides/layout/#fill-mode}
349
+ * @type {boolean}
350
+ * @default true
351
+ */
352
+ pillarbox.options.fill = true;
353
+ /**
354
+ * Configuration options for HTML5 settings in Pillarbox.
355
+ *
356
+ * @see [VHS useForcedSubtitles Option]{@link https://github.com/videojs/http-streaming/blob/main/README.md#useforcedsubtitles}
357
+ * @type {Object}
358
+ * @property {Object} vhs - Configuration for the Video.js HTTP Streaming.
359
+ * @property {boolean} useForcedSubtitles - Enables the player to display forced subtitles by default.
360
+ * Forced subtitles are pieces of information intended for display when no other text representation
361
+ * is selected. They are used to clarify dialogue, provide alternate languages, display texted graphics,
362
+ * or present location/person IDs that are not otherwise covered in the dubbed/localized audio.
363
+ */
364
+ pillarbox.options.html5 = {
365
+ vhs: {
366
+ useForcedSubtitles: true
367
+ }
368
+ };
369
+ /**
370
+ * Configuration for the live tracker.
371
+ *
372
+ * @see [Video.js liveTracker Option]{@link https://videojs.com/guides/options/#livetrackertrackingthreshold}
373
+ * @type {Object}
374
+ * @property {number} trackingThreshold - A threshold that controls when the liveui should be shown.
375
+ * @property {number} liveTolerance - An option that controls how far from the seekable end should be considered live playback.
376
+ */
377
+ pillarbox.options.liveTracker = {
378
+ trackingThreshold: 120,
379
+ liveTolerance: 15
380
+ };
381
+ /**
382
+ * Allows the player to use the live ui that includes:
383
+ *
384
+ * - A progress bar for seeking within the live window
385
+ * - A button that can be clicked to seek to the live edge with a circle indicating if you are at the live edge or not.
386
+ *
387
+ * @see [Video.js liveui Option]{@link https://videojs.com/guides/options/#liveui}
388
+ * @type {boolean}
389
+ */
390
+ pillarbox.options.liveui = true;
391
+ /**
392
+ * Indicates that the video is to be played "inline", that is within the element's playback area.
393
+ *
394
+ * @see [Video element playsinline attribute]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#playsinline}
395
+ * @type {boolean}
396
+ */
397
+ pillarbox.options.playsinline = true;
398
+ /**
399
+ * Enable responsive mode, this will cause the player to customize itself based on responsive breakpoints.
400
+ *
401
+ * @see [Video.js Responsive Option]{@link https://videojs.com/guides/options/#responsive}
402
+ * @type {boolean}
403
+ */
404
+ pillarbox.options.responsive = true;
405
+ /**
406
+ * A placeholder for accessing trackers directly from the player.
407
+ *
408
+ * @type {Object}
409
+ */
410
+ pillarbox.options.trackers = {};
411
+
412
+ /**
413
+ * Represents a data provider for constructing URLs and handling requests.
414
+ * @class
415
+ * @ignore
416
+ */
417
+ class DataProvider {
418
+ /**
419
+ * Creates an instance of DataProvider.
420
+ *
421
+ * @param {string} [hostName='il.srgssr.ch'] The base host name for constructing URLs
422
+ */
423
+ constructor(hostName = 'il.srgssr.ch') {
424
+ this.setIlHost(hostName);
425
+ }
426
+
427
+ /**
428
+ * Sets the integration layer host name.
429
+ *
430
+ * @param {string} hostName The host name to set
431
+ */
432
+ setIlHost(hostName) {
433
+ this.baseUrl = `${hostName}/integrationlayer/2.1/`;
434
+ }
435
+
436
+ /**
437
+ * Handles requests by constructing URLs and fetching data.
438
+ *
439
+ * This provides unified error handling, regardless of the urlHandler used.
440
+ *
441
+ * @param {Function} urlHandler A function that constructs the URL
442
+ *
443
+ * @returns {Promise<import('../model/MediaComposition.js').default>} A promise with the fetched data
444
+ */
445
+ handleRequest(urlHandler) {
446
+ var _this = this;
447
+ return /*#__PURE__*/function () {
448
+ var _ref = _asyncToGenerator(function* (urn) {
449
+ const url = typeof urlHandler === 'function' ? urlHandler(urn) : _this.mediaCompositionUrlHandler(urn);
450
+ const response = yield fetch(url);
451
+ if (!response.ok) {
452
+ throw response;
453
+ }
454
+
455
+ /** @type {import('../model/MediaComposition.js').default} */
456
+ const data = yield response.json();
457
+ return data;
458
+ });
459
+ return function (_x) {
460
+ return _ref.apply(this, arguments);
461
+ };
462
+ }();
463
+ }
464
+
465
+ /**
466
+ * Gets the media composition URL by URN.
467
+ *
468
+ * @param {string} urn The URN for the media composition
469
+ *
470
+ * @returns {string} The constructed URL
471
+ */
472
+ mediaCompositionUrlHandler(urn) {
473
+ return `https://${this.baseUrl}mediaComposition/byUrn/${urn}?onlyChapters=true&vector=portalplay`;
474
+ }
475
+ }
476
+
477
+ const SCALE = {
478
+ WIDTH_240: '240',
479
+ WIDTH_320: '320',
480
+ WIDTH_480: '480',
481
+ WIDTH_960: '960',
482
+ WIDTH_1920: '1920'
483
+ };
484
+ const FORMAT = {
485
+ JPG: 'jpg',
486
+ WEBP: 'webp',
487
+ PNG: 'png'
488
+ };
489
+ const IMAGE_SERVICE_URL = 'https://il.srgssr.ch/images/';
490
+
491
+ /**
492
+ * @class Image
493
+ */
494
+ class Image {
495
+ /**
496
+ * Generates the image scaling URL.
497
+ *
498
+ * @property {Object} image is the object representation of an image.
499
+ * @property {String} [image.url] is the image URL.
500
+ * @property {String} [image.width=960] is the width of the image, default value 960.
501
+ * @property {String} [image.format=jpg] is the format of the image, default value jpg.
502
+ * @property {String} [imageServiceUrl] Url of the image service that needs to comply with the specification defined by the IL.
503
+ *
504
+ * @see https://confluence.srg.beecollaboration.com/pages/viewpage.action?spaceKey=SRGPLAY&title=Project+-+Image+Service
505
+ *
506
+ * @returns {String|undefined} the image scaling URL.
507
+ */
508
+ static scale({
509
+ url,
510
+ width = SCALE.WIDTH_960,
511
+ format = FORMAT.JPG
512
+ } = {}, imageServiceUrl = IMAGE_SERVICE_URL) {
513
+ if (!url) return;
514
+ const scaleUrl = new URL(imageServiceUrl);
515
+ scaleUrl.searchParams.set('imageUrl', url);
516
+ scaleUrl.searchParams.set('format', format);
517
+ scaleUrl.searchParams.set('width', width);
518
+ return decodeURIComponent(scaleUrl.href);
519
+ }
520
+ static get JPG() {
521
+ return FORMAT.JPG;
522
+ }
523
+ static get PNG() {
524
+ return FORMAT.PNG;
525
+ }
526
+ static get WEBP() {
527
+ return FORMAT.WEBP;
528
+ }
529
+ static get WIDTH_240() {
530
+ return SCALE.WIDTH_240;
531
+ }
532
+ static get WIDTH_320() {
533
+ return SCALE.WIDTH_320;
534
+ }
535
+ static get WIDTH_480() {
536
+ return SCALE.WIDTH_480;
537
+ }
538
+ static get WIDTH_960() {
539
+ return SCALE.WIDTH_960;
540
+ }
541
+ static get WIDTH_1920() {
542
+ return SCALE.WIDTH_1920;
543
+ }
544
+ }
545
+
546
+ const DRM_VENDORS = {
547
+ WIDEVINE: 'com.widevine.alpha',
548
+ FAIRPLAY: 'com.apple.fps.1_0',
549
+ PLAYREADY: 'com.microsoft.playready'
550
+ };
551
+
552
+ /**
553
+ * @class Drm
554
+ */
555
+ class Drm {
556
+ /**
557
+ * Build the keySystems object according to the DRM vendor.
558
+ *
559
+ * @param {Array.<import('../dataProvider/model/typedef').DrmMetadata>} drmList The DRM list from the media composition.
560
+ *
561
+ * @returns {import('./typedef').KeySystems} The resulting keySystems.
562
+ */
563
+ static buildKeySystems(drmList = []) {
564
+ const keySystems = {};
565
+ drmList.forEach(drmVendor => {
566
+ const type = Drm.vendors[drmVendor.type];
567
+ if (Drm.vendors.FAIRPLAY === type) {
568
+ const {
569
+ certificateUrl: certificateUri,
570
+ licenseUrl: licenseUri
571
+ } = drmVendor;
572
+ keySystems[type] = {
573
+ certificateUri,
574
+ licenseUri
575
+ };
576
+ } else {
577
+ keySystems[type] = drmVendor.licenseUrl;
578
+ }
579
+ });
580
+ return {
581
+ keySystems
582
+ };
583
+ }
584
+
585
+ /**
586
+ * Check if some of the resources have DRM.
587
+ *
588
+ * @param {Array.<import('../dataProvider/model/typedef').MainResource>} resources
589
+ *
590
+ * @returns {boolean} true if some of the resources have DRM, false otherwise.
591
+ */
592
+ static hasDrm(resources) {
593
+ return resources.some(({
594
+ drmList
595
+ }) => drmList && drmList.length > 0);
596
+ }
597
+
598
+ /**
599
+ * Get DRM vendors.
600
+ */
601
+ static get vendors() {
602
+ return DRM_VENDORS;
603
+ }
604
+ }
605
+
606
+ const TOKEN_TYPES = {
607
+ AKAMAI: 'AKAMAI',
608
+ NONE: 'NONE'
609
+ };
610
+
611
+ /**
612
+ * @class AkamaiTokenService
613
+ */
614
+ class AkamaiTokenService {
615
+ /**
616
+ * Get the acl path.
617
+ *
618
+ * @param {URL} streamUrl
619
+ *
620
+ * @returns {String}
621
+ */
622
+ static aclPath(streamUrl) {
623
+ const path = streamUrl.pathname;
624
+ return `${path.substring(0, path.lastIndexOf('/') + 1)}*`;
625
+ }
626
+
627
+ /**
628
+ * AKAMAI
629
+ *
630
+ * @type {String}
631
+ */
632
+ static get AKAMAI() {
633
+ return TOKEN_TYPES.AKAMAI;
634
+ }
635
+
636
+ /**
637
+ * Check if the resources are protected by an Akamai token.
638
+ * Keep in mind, as we are using the 'some' function,
639
+ * if the resources have at least one resource
640
+ * protected by a token it returns true!
641
+ *
642
+ * @param {Array.<import('../dataProvider/model/typedef').MainResource>} resources
643
+ *
644
+ * @returns {Boolean}
645
+ */
646
+ static hasToken(resources) {
647
+ return resources.some(resource => AkamaiTokenService.isAkamai(resource.tokenType));
648
+ }
649
+
650
+ /**
651
+ * Check if the token type is AKAMAI.
652
+ *
653
+ * @param {String} tokentype
654
+ *
655
+ * @returns {Boolean}
656
+ */
657
+ static isAkamai(tokentype) {
658
+ return TOKEN_TYPES.AKAMAI === tokentype;
659
+ }
660
+
661
+ /**
662
+ * Check if the token type is NONE.
663
+ *
664
+ * @param {String} tokentype
665
+ *
666
+ * @returns {Boolean}
667
+ */
668
+ static isNone(tokentype) {
669
+ return TOKEN_TYPES.NONE === tokentype;
670
+ }
671
+
672
+ /**
673
+ * NONE
674
+ *
675
+ * @type {String}
676
+ */
677
+ static get NONE() {
678
+ return TOKEN_TYPES.NONE;
679
+ }
680
+
681
+ /**
682
+ * Generate the stream URL with the akamai token.
683
+ *
684
+ * @param {import('../dataProvider/model/typedef').MainResource} source
685
+ * @param {String} tokenServerUrl
686
+ *
687
+ * @returns {Promise.<import('../dataProvider/model/typedef').MainResource>}
688
+ */
689
+ static tokenize(source, tokenServerUrl) {
690
+ const streamUrlToTokenize = new URL(`${source.url}`);
691
+ const acl = AkamaiTokenService.aclPath(streamUrlToTokenize);
692
+ const url = `${tokenServerUrl}${encodeURIComponent(acl)}`;
693
+ return fetch(url).then(response => {
694
+ if (response.ok) {
695
+ return response.json();
696
+ }
697
+ return Promise.reject({
698
+ status: response.status,
699
+ statusText: response.statusText
700
+ });
701
+ }).then(({
702
+ token: {
703
+ authparams
704
+ }
705
+ }) => {
706
+ const akamaiAuthParams = new URLSearchParams(authparams);
707
+ akamaiAuthParams.forEach((v, k) => streamUrlToTokenize.searchParams.set(k, v));
708
+ return Object.assign({}, source, {
709
+ url: streamUrlToTokenize.toString()
710
+ });
711
+ }).catch(reason => {
712
+ return Promise.reject(reason);
713
+ });
714
+ }
715
+
716
+ /**
717
+ * Generate a token for each source
718
+ *
719
+ * @template {import('../dataProvider/model/typedef').MainResource} T
720
+ * @param {Array.<T>} sources
721
+ * @param {String} tokenServerUrl
722
+ *
723
+ * @returns {Promise.<Array.<T>>}
724
+ */
725
+ static tokenizeSources(sources, tokenServerUrl = 'https://tp.srgssr.ch/akahd/token?acl=') {
726
+ const tokenizedSources = [];
727
+ sources.forEach(source => {
728
+ const tokenizedSource = AkamaiTokenService.tokenize(source, tokenServerUrl);
729
+ tokenizedSources.push(tokenizedSource);
730
+ });
731
+ return Promise.all(tokenizedSources).then(values => values).catch(reason => Promise.reject(reason));
732
+ }
733
+ }
734
+
735
+ /**
736
+ * Exhaustive list of player events.
737
+ *
738
+ * See below the documentation related to the media events
739
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#events
740
+ *
741
+ * @namespace {Object} PlayerEvents
742
+ */
743
+
744
+
745
+ /**
746
+ * Triggered when the media element is emptied (e.g., reset as part of the seeking process).
747
+ *
748
+ * @event PlayerEvents#EMPTIED
749
+ * @type {string}
750
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/emptied_event
751
+ */
752
+ const EMPTIED = 'emptied';
753
+
754
+ /**
755
+ * Triggered when the media playback has ended.
756
+ *
757
+ * @event PlayerEvents#ENDED
758
+ * @type {string}
759
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended_event
760
+ */
761
+ const ENDED = 'ended';
762
+
763
+ /**
764
+ * Triggered when the media data has been loaded.
765
+ *
766
+ * @event PlayerEvents#LOADED_DATA
767
+ * @type {string}
768
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadeddata_event
769
+ */
770
+ const LOADED_DATA = 'loadeddata';
771
+
772
+ /**
773
+ * Triggered when the browser starts looking for media data.
774
+ *
775
+ * @event PlayerEvents#LOAD_START
776
+ * @type {string}
777
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadstart_event
778
+ */
779
+ const LOAD_START = 'loadstart';
780
+
781
+ /**
782
+ * Triggered when the media playback is paused.
783
+ *
784
+ * @event PlayerEvents#PAUSE
785
+ * @type {string}
786
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause_event
787
+ */
788
+ const PAUSE = 'pause';
789
+
790
+ /**
791
+ * Triggered when the media playback is resumed or started.
792
+ *
793
+ * @event PlayerEvents#PLAY
794
+ * @type {string}
795
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play_event
796
+ */
797
+ const PLAY = 'play';
798
+
799
+ /**
800
+ * Triggered when the media playback is in progress.
801
+ *
802
+ * @event PlayerEvents#PLAYING
803
+ * @type {string}
804
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playing_event
805
+ */
806
+ const PLAYING = 'playing';
807
+
808
+ /**
809
+ * Triggered when the playback rate changes.
810
+ *
811
+ * @event PlayerEvents#RATE_CHANGE
812
+ * @type {string}
813
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ratechange_event
814
+ */
815
+ const RATE_CHANGE = 'ratechange';
816
+
817
+ /**
818
+ * Triggered when a seek operation is in progress.
819
+ *
820
+ * @event PlayerEvents#SEEKING
821
+ * @type {string}
822
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeking_event
823
+ */
824
+ const SEEKING = 'seeking';
825
+
826
+ /**
827
+ * Triggered when the current playback position is updated.
828
+ *
829
+ * @event PlayerEvents#TIME_UPDATE
830
+ * @type {string}
831
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/timeupdate_event
832
+ */
833
+ const TIME_UPDATE = 'timeupdate';
834
+
835
+ /**
836
+ * Triggered when the media playback is waiting for data.
837
+ *
838
+ * @event PlayerEvents#WAITING
839
+ * @type {string}
840
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/waiting_event
841
+ */
842
+ const WAITING = 'waiting';
843
+
844
+ /* eslint max-lines-per-function: ["error", 200] */
845
+ /* eslint max-statements: ["error", 20]*/
846
+ /* eslint complexity: ["error", 10]*/
847
+ /**
848
+ * SRG analytics
849
+ * @class SRGAnalytics
850
+ * @ignore
851
+ *
852
+ * ### Script URL
853
+ * JS script : https://colibri-js.akamaized.net/penguin/tc_SRGGD_11.js
854
+ *
855
+ * ### Official documentation
856
+ * Variables list
857
+ * @see https://confluence.srg.beecollaboration.com/display/INTFORSCHUNG/Datalayer+for+media+players
858
+ *
859
+ * Standard event sequences
860
+ * @see https://confluence.srg.beecollaboration.com/display/INTFORSCHUNG/standard+streaming+events%3A+sequence+of+events+for+media+player+actions
861
+ *
862
+ * Review of Standard Media Actions
863
+ * @see https://confluence.srg.beecollaboration.com/display/INTFORSCHUNG/Implementation+Concept+-+draft
864
+ *
865
+ * ComScore Implementation Guide
866
+ * @see https://www.dropbox.com/sh/cdwuikq0abxi21m/AABmSyXYKUTWSAwRZgQA9Ujna/JavaScript%20Latest%20Version?dl=0&preview=Comscore_Library-JavaScript-Streaming_Tag-Implementation_Guide-International.pdf&subfolder_nav_tracking=1
867
+ *
868
+ * ### Variables list
869
+ * - 'event_id', // init | play | stop | pos | pause | seek | uptime | eof
870
+ * - 'event_timestamp', // Seems to be generated automatically from the documentation, but the TP overrides it
871
+ * - 'event_name', // NA TP seems to not sending this variable
872
+ * - 'event_source', // NA TP seems to not sending this variable
873
+ * - 'event_name', // NA TP seems to not sending this variable
874
+ * - 'event_value', // NA TP seems to not sending this variable
875
+ * - 'navigation_environment', // prod | preprod
876
+ * - 'media_subtitles_on', // string true | false
877
+ * - 'media_timeshift', // need better description
878
+ * - 'media_quality', // SD | HD ?
879
+ * - 'media_bandwidth', // NA for the web, 64000
880
+ * - 'media_volume', // from 0 to 100
881
+ * - 'media_embedding_url', //
882
+ * - 'media_player_name', // videojs | letterbox-web ?
883
+ * - 'media_chromecast_selected', // boolean true | false
884
+ * - 'media_player_version', // player's version
885
+ * - 'media_player_display', // is the player mode, on the TP : inline, embed etc..
886
+ * - 'media_audio_track', // NA
887
+ * - 'media_position_real', // NA
888
+ * - 'media_time_spent', // NA
889
+ * - 'device_id', // NA
890
+ * - 'user_id_log_in', // NA only RTS has log in today
891
+ * - 'media_thumbnail', // Not required by the spec but sended by the TP
892
+ * - 'media_bu_distributer', // Not required by the spec but sended by the TP
893
+ *
894
+ *
895
+ * ### Sequence stories
896
+ *
897
+ * __Story 1 (AoD/VOD-basics)__: A VoD is played. The user does not interact with the player. The VoD plays to its end.
898
+ *
899
+ * Hints:
900
+ * - Media sessions always start with PLAY. They end with STOP or EOF (or with PAUSE or last POS)
901
+ * - POS is sent ever 30s
902
+ *
903
+ *
904
+ * __Story 2 (livestream-basics A)__: A Livestream is played. The user does not interact with the player. After 61 seconds, playback is paused.
905
+ *
906
+ * Hints:
907
+ * - Media sessions always start with PLAY. They end with STOP (or, worse for data quailty, with PAUSE or last POS/UPTIME)
908
+ * - UPTIME is sent only for livestreams
909
+ * - POS is sent ever 30s, UPTIME every 60s with inital UPTIME after 30s.
910
+ * - This is the interval: 30s: POS + UPTIME; 60s: POS; 90s: POS + UPTIME; ...
911
+ *
912
+ *
913
+ * __Story 3 (Seeking a VoD/AoD)__: A VoD is played. User seeks in the VoD/AoD.
914
+ *
915
+ * Hints:
916
+ * - Once the Media Player slider is released (seek is over), another action to finish up the seeking is initiated. Typically this is PLAY. For that second PLAY, the media position has altered.
917
+ *
918
+ *
919
+ * __Story 4 (Seeking a livestream)__: A Livestream is played. User goes back in the livestream.
920
+ *
921
+ * Hints:
922
+ * - Once the Media Player slider is released (seek is over), another action to finish up the seeking is initiated. Typically this is PLAY. For that second PLAY, the a new variable, media_timeshift is passed.
923
+ * - For livestreams media_position is always the "time passed on your watch" - regardless of the SEEK event. So, if 1 second after PLAY the slider is moved back 600 seconds, then:
924
+ * 1. The the value of media_timeshift is '600'.
925
+ * 2. The value of media_position is '1'.
926
+ */
927
+ class SRGAnalytics {
928
+ constructor(player, {
929
+ debug = false,
930
+ environment = 'prod',
931
+ playerVersion = 'none',
932
+ tagCommanderScriptURL = '//colibri-js.akamaized.net/penguin/tc_SRGGD_11.js'
933
+ } = {}) {
934
+ this.isDebugEnabled = debug;
935
+ this.elapsedPlaybackTime = 0;
936
+ this.environment = environment;
937
+ this.hasStarted = false;
938
+ this.heartBeatIntervalId = undefined;
939
+ /* Set to true when 'init' event is sent or queued. */
940
+ this.initialized = false;
941
+ this.isSeeking = false;
942
+ this.isWaiting = false;
943
+ this.mediaSession = 0;
944
+ this.pendingQueue = [];
945
+ this.pendingTagCommanderReload = false;
946
+ this.player = player;
947
+ this.playerVersion = playerVersion;
948
+ this.srcMediaData = undefined;
949
+ this.startPlaybackSession = 0;
950
+ this.tagCommanderScriptURL = tagCommanderScriptURL;
951
+ this.trackedCurrentTime = 0;
952
+ this.uptimeIntervalId = undefined;
953
+ this.initScript();
954
+ this.initListeners();
955
+ }
956
+
957
+ /**
958
+ * Sent when the window, the document and its resources are about to be unloaded.
959
+ *
960
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
961
+ */
962
+ beforeunload() {
963
+ this.notify('stop');
964
+ }
965
+
966
+ /**
967
+ * Clear timers used to send uptime and heartbeat.
968
+ */
969
+ clearTimers() {
970
+ clearInterval(this.heartBeatIntervalId);
971
+ clearInterval(this.uptimeIntervalId);
972
+ clearTimeout(this.uptimeTimeoutId);
973
+ }
974
+
975
+ /**
976
+ * Get the tracked current time in seconds.
977
+ *
978
+ * @returns {Number} current time in seconds
979
+ */
980
+ currentTime() {
981
+ // see PLAYRTS-2771
982
+ return Math.round(this.trackedCurrentTime);
983
+ }
984
+
985
+ /**
986
+ * Get or set debug mode.
987
+ *
988
+ * @returns {Boolean|undefined}
989
+ */
990
+ debug(enabled) {
991
+ if (enabled === undefined) {
992
+ return this.isDebugEnabled || this.player.debug();
993
+ }
994
+ this.isDebugEnabled = Boolean(enabled);
995
+ }
996
+
997
+ /**
998
+ * Destroy all properties and setIntervals to avoid mixing media sessions.
999
+ */
1000
+ destroy() {
1001
+ this.clearTimers();
1002
+ if (!window.tc_vars) {
1003
+ window.tc_vars = {};
1004
+ }
1005
+ this.elapsedPlaybackTime = 0;
1006
+ this.hasStarted = false;
1007
+ this.heartBeatIntervalId = undefined;
1008
+ this.initialized = false;
1009
+ this.isWaiting = false;
1010
+ this.mediaSession = 0;
1011
+ this.pendingQueue = [];
1012
+ this.srcMediaData = undefined;
1013
+ this.startPlaybackSession = 0;
1014
+ this.trackedCurrentTime = 0;
1015
+ this.uptimeIntervalId = undefined;
1016
+ }
1017
+
1018
+ /**
1019
+ * Dispose all listeners used to send analytics data to TagCommander.
1020
+ *
1021
+ * Calls `beforeunload` to send a notify stop.
1022
+ * Clear intervals and timeouts.
1023
+ *
1024
+ * __Used events__
1025
+ * - beforeunload
1026
+ * - emptied
1027
+ * - ended
1028
+ * - loadstart
1029
+ * - loadeddata
1030
+ * - play
1031
+ * - pause
1032
+ * - timeupdate
1033
+ */
1034
+ dispose() {
1035
+ this.beforeunload();
1036
+ this.clearTimers();
1037
+ window.removeEventListener('beforeunload', this.beforeunloadListener);
1038
+ this.player.off(EMPTIED, this.emptiedListener);
1039
+ this.player.off(ENDED, this.endedListener);
1040
+ this.player.off(LOAD_START, this.loadstartListener);
1041
+ this.player.off(LOADED_DATA, this.loadeddataListener);
1042
+ this.player.off(PLAYING, this.playListener);
1043
+ this.player.off(PAUSE, this.pauseListener);
1044
+ this.player.off(RATE_CHANGE, this.rateChangeListener);
1045
+ this.player.off(SEEKING, this.seekingListener);
1046
+ this.player.off(TIME_UPDATE, this.timeUpdateListener);
1047
+ this.player.off(WAITING, this.waitingListener);
1048
+ }
1049
+
1050
+ /**
1051
+ * Sent before a new media is loading.
1052
+ * - Destroy all properties.
1053
+ * - Send a notify stop if the media is not ended and new media is about to be loaded.
1054
+ */
1055
+ emptied() {
1056
+ if (!this.player.ended()) {
1057
+ this.notify('stop');
1058
+ }
1059
+ }
1060
+
1061
+ /**
1062
+ * Sent when playback completes.
1063
+ *
1064
+ * @see https://docs.videojs.com/player#event:ended
1065
+ */
1066
+ ended() {
1067
+ this.notify('eof');
1068
+ this.mediaSession = 0;
1069
+ this.clearTimers();
1070
+ }
1071
+
1072
+ /**
1073
+ * Flush the queued events when tc event script is loaded.
1074
+ */
1075
+ flush() {
1076
+ if (this.isTrackerDisabled()) return;
1077
+ if (this.pendingTagCommanderReload && window.tC) {
1078
+ window.tC.container.reload();
1079
+ this.pendingTagCommanderReload = false;
1080
+ }
1081
+ if (window.tc_events_11 && this.pendingQueue.length > 0) {
1082
+ this.pendingQueue.forEach(notification => {
1083
+ window.tc_events_11(this.player.el(), notification.action, notification.labels);
1084
+ });
1085
+ this.pendingQueue = [];
1086
+ }
1087
+ }
1088
+
1089
+ /**
1090
+ * Get the language of the current audio track.
1091
+ *
1092
+ * @returns {String} empty string or uppercase language.
1093
+ */
1094
+ getCurrentAudioTrack() {
1095
+ const currentTrack = Array.from(this.player.audioTracks()).find(track => track.enabled);
1096
+ let language = 'und';
1097
+ if (currentTrack && !!currentTrack.language) {
1098
+ // eslint-disable-next-line prefer-destructuring
1099
+ language = currentTrack.language;
1100
+ }
1101
+ return currentTrack ? language.toUpperCase() : '';
1102
+ }
1103
+
1104
+ /**
1105
+ * Get the language of the current text track.
1106
+ *
1107
+ * @returns {String} empty string or uppercase language.
1108
+ */
1109
+ getCurrentTextTrack() {
1110
+ const currentTrack = this.player.textTrack();
1111
+ let language = 'und';
1112
+ if (currentTrack && !!currentTrack.language) {
1113
+ // eslint-disable-next-line prefer-destructuring
1114
+ language = currentTrack.language;
1115
+ }
1116
+ return currentTrack ? language.toUpperCase() : '';
1117
+ }
1118
+
1119
+ /**
1120
+ * Get the position inside the dvr window where the 0 represents the live edge
1121
+ *
1122
+ * @return {Number} 0 or the position in milliseconds
1123
+ */
1124
+ getDvrWindowPosition() {
1125
+ const {
1126
+ liveTracker
1127
+ } = this.player;
1128
+ const ct = this.currentTime() - liveTracker.seekableStart() | 0;
1129
+ const position = liveTracker.liveWindow() - ct;
1130
+ return position < 0 || position === Infinity ? 0 : position * 1000;
1131
+ }
1132
+
1133
+ /**
1134
+ * Get the size of the live DVR window in milliseconds.
1135
+ *
1136
+ * @return {Number} DVR window size in milliseconds
1137
+ */
1138
+ getDvrWindowSize() {
1139
+ const isInfinity = this.player.liveTracker.liveWindow() === Infinity;
1140
+ const windowSize = this.player.liveTracker.liveWindow() * 1000;
1141
+ return isInfinity ? 0 : windowSize;
1142
+ }
1143
+
1144
+ /**
1145
+ * Get the elapsed playback time in seconds.
1146
+ *
1147
+ * @returns {Number} elapsed time in seconds
1148
+ */
1149
+ getElapsedPlaybackTime() {
1150
+ if (this.startPlaybackSession) {
1151
+ return this.getElapsedPlayingTime();
1152
+ }
1153
+ return this.elapsedPlaybackTime;
1154
+ }
1155
+
1156
+ /**
1157
+ * Get the elapsed playing time in seconds.
1158
+ *
1159
+ * @returns {Number} elapsed time in seconds
1160
+ */
1161
+ getElapsedPlayingTime() {
1162
+ const playingSession = SRGAnalytics.now() - this.startPlaybackSession | 0;
1163
+ return this.elapsedPlaybackTime + playingSession;
1164
+ }
1165
+
1166
+ /**
1167
+ * Set all event labels to be sent to TagCommander. The event labels are updated whenever a new event occurs.
1168
+ *
1169
+ * @param {String} eventName init | play | stop | pos | pause | seek | uptime | eof
1170
+ *
1171
+ * @returns {Object} JSON to be sent to TagCommander
1172
+ */
1173
+ getEventLabels(eventName) {
1174
+ const labels = {
1175
+ event_id: eventName,
1176
+ event_timestamp: SRGAnalytics.now(),
1177
+ media_dvr_window_length: 0,
1178
+ media_dvr_window_offset: 0,
1179
+ media_is_dvr: false,
1180
+ media_is_live: false,
1181
+ media_mute: this.player.muted() ? '1' : '0',
1182
+ media_playback_rate: this.player.playbackRate(),
1183
+ media_position: this.currentTime(),
1184
+ media_quality: this.srcMediaData.mediaData.quality,
1185
+ // TODO use media_is_dvr, media_is_live to define peach media_stream_type
1186
+ media_subtitles_on: this.isTextTrackEnabled(),
1187
+ media_volume: (this.player.volume() * 100).toFixed(0),
1188
+ navigation_environment: this.environment
1189
+ };
1190
+ if (this.isAudioTrackEnabled()) {
1191
+ labels.media_audio_track = this.getCurrentAudioTrack();
1192
+ }
1193
+ if (this.isTextTrackEnabled()) {
1194
+ labels.media_subtitle_selection = this.getCurrentTextTrack();
1195
+ }
1196
+
1197
+ // DVR or Live related labels
1198
+ if (!this.isMediaOnDemand()) {
1199
+ labels.media_is_live = true;
1200
+ labels.media_position = this.getElapsedPlaybackTime();
1201
+ }
1202
+
1203
+ // DVR related labels
1204
+ if (this.isMediaDvr()) {
1205
+ labels.media_dvr_window_offset = this.getDvrWindowPosition() | 0;
1206
+ labels.media_dvr_window_length = this.getDvrWindowSize() | 0;
1207
+ labels.media_is_dvr = true;
1208
+ labels.media_timeshift = [PLAY, PAUSE].includes(eventName) ? this.timeShifted() : 0;
1209
+ }
1210
+ return labels;
1211
+ }
1212
+
1213
+ /**
1214
+ * Set all internal labels to be sent to TagCommander. Internal labels are assigned once at initialisation time.
1215
+ */
1216
+ getInternalLabels() {
1217
+ const data = {
1218
+ media_bu_distributer: this.srcMediaData.mediaData.vendor,
1219
+ media_chromecast_selected: Boolean(this.player.tech(true).isCasting),
1220
+ media_embedding_url: document.referrer,
1221
+ media_player_display: 'default',
1222
+ // TODO implement if it still relevant
1223
+ media_player_name: 'pillarbox-web',
1224
+ // TODO add a property playerName in the constructor with a default value ?
1225
+ media_player_version: this.playerVersion,
1226
+ media_url: this.srcMediaData.src
1227
+ };
1228
+ const analyticsMetadata = this.srcMediaData.mediaData.analyticsMetadata || {};
1229
+ window.tc_vars = Object.assign({}, window.tc_vars, data, analyticsMetadata);
1230
+ }
1231
+
1232
+ /**
1233
+ * Heart beat, current position of a AoD/VoD (every 30s).
1234
+ *
1235
+ * @description The action pos should be sent regularly every 30 seconds.
1236
+ * It is used for tracking the viewed chapters of a video and the last position of the video, in case the user ends the video by closing the browser tab/window.
1237
+ *
1238
+ * - pos should be sent when the media player is in "play mode".
1239
+ * - once the video is paused or stopped, the timer for sending these actions must be stopped.
1240
+ *
1241
+ * @see https://confluence.srg.beecollaboration.com/display/INTFORSCHUNG/standard+streaming+events%3A+sequence+of+events+for+media+player+actions#standardstreamingevents:sequenceofeventsformediaplayeractions-mandatoryplayerevents
1242
+ */
1243
+ heartBeat() {
1244
+ this.heartBeatIntervalId = setInterval(() => {
1245
+ // Send only when playing
1246
+ if (!this.player.paused()) {
1247
+ this.notify('pos');
1248
+ }
1249
+ }, 30000);
1250
+ }
1251
+
1252
+ /**
1253
+ * Initialize callbacks used to send analytics data to TagCommander.
1254
+ *
1255
+ * __Used events__
1256
+ * - beforeunload
1257
+ * - emptied
1258
+ * - ended
1259
+ * - loadstart
1260
+ * - loadeddata
1261
+ * - play
1262
+ * - pause
1263
+ * - ratechange
1264
+ * - seeking
1265
+ * - timeupdate
1266
+ * - waiting
1267
+ */
1268
+ initCallbacks() {
1269
+ this.beforeunloadListener = this.beforeunload.bind(this);
1270
+ this.emptiedListener = this.emptied.bind(this);
1271
+ this.endedListener = this.ended.bind(this);
1272
+ this.loadstartListener = this.loadstart.bind(this);
1273
+ this.loadeddataListener = this.loadeddata.bind(this);
1274
+ this.playListener = this.play.bind(this);
1275
+ this.pauseListener = this.pause.bind(this);
1276
+ this.rateChangeListener = this.rateChange.bind(this);
1277
+ this.seekingListener = this.seeking.bind(this);
1278
+ this.timeUpdateListener = this.timeUpdate.bind(this);
1279
+ this.waitingListener = this.waiting.bind(this);
1280
+ }
1281
+
1282
+ /**
1283
+ * Initialize all listeners used to send analytics data to TagCommander.
1284
+ *
1285
+ * __Used events__
1286
+ * - beforeunload
1287
+ * - dispose
1288
+ * - emptied
1289
+ * - ended
1290
+ * - loadstart
1291
+ * - loadeddata
1292
+ * - play
1293
+ * - pause
1294
+ * - timeupdate
1295
+ * - waiting
1296
+ */
1297
+ initListeners() {
1298
+ this.initCallbacks();
1299
+ window.addEventListener('beforeunload', this.beforeunloadListener);
1300
+ this.player.on(EMPTIED, this.emptiedListener);
1301
+ this.player.on(ENDED, this.endedListener);
1302
+ this.player.on(LOAD_START, this.loadstartListener);
1303
+ this.player.on(LOADED_DATA, this.loadeddataListener);
1304
+ this.player.on(PLAYING, this.playListener);
1305
+ this.player.on(PAUSE, this.pauseListener);
1306
+ this.player.on(RATE_CHANGE, this.rateChangeListener);
1307
+ this.player.on(SEEKING, this.seekingListener);
1308
+ this.player.on(TIME_UPDATE, this.timeUpdateListener);
1309
+ this.player.on(WAITING, this.waitingListener);
1310
+ this.player.one('dispose', this.dispose.bind(this));
1311
+ }
1312
+
1313
+ /**
1314
+ * Initialize TagCommander script dynamically and add it to the DOM
1315
+ */
1316
+ initScript() {
1317
+ const scriptId = 'tc_script__11';
1318
+ if (!document.querySelector(`#${scriptId}`)) {
1319
+ const script = document.createElement('script');
1320
+ const src = this.tagCommanderScriptURL;
1321
+ script.defer = true;
1322
+ script.id = scriptId;
1323
+ script.src = src;
1324
+ script.type = 'text/javascript';
1325
+ script.onload = _e => {
1326
+ this.flush();
1327
+ };
1328
+ document.body.appendChild(script);
1329
+ }
1330
+ }
1331
+
1332
+ /**
1333
+ * Check if the audio track is enabled.
1334
+ *
1335
+ * @returns {Boolean} __true__ if enabled __false__ otherwise.
1336
+ */
1337
+ isAudioTrackEnabled() {
1338
+ return !!this.getCurrentAudioTrack();
1339
+ }
1340
+
1341
+ /**
1342
+ * Check if the media is a live with DVR.
1343
+ *
1344
+ * @returns {Boolean} __true__ if it DVR __false__ otherwise.
1345
+ */
1346
+ isMediaDvr() {
1347
+ const {
1348
+ trackingThreshold
1349
+ } = this.player.liveTracker.options();
1350
+ return !this.isMediaOnDemand() && trackingThreshold < this.player.liveTracker.liveWindow();
1351
+ }
1352
+
1353
+ /**
1354
+ * Check if the media is a live.
1355
+ *
1356
+ * @returns {Boolean} __true__ if live __false__ otherwise.
1357
+ */
1358
+ isMediaLive() {
1359
+ const {
1360
+ trackingThreshold
1361
+ } = this.player.liveTracker.options();
1362
+ return !this.isMediaOnDemand() && trackingThreshold > this.player.liveTracker.liveWindow();
1363
+ }
1364
+
1365
+ /**
1366
+ * Check if the media is an on demand.
1367
+ *
1368
+ * @returns {Boolean} __true__ if on demand __false__ otherwise.
1369
+ */
1370
+ isMediaOnDemand() {
1371
+ return Number.isFinite(this.player.duration());
1372
+ }
1373
+
1374
+ /**
1375
+ * Check if the text track is enabled.
1376
+ *
1377
+ * @returns {Boolean} __true__ if enabled __false__ otherwise.
1378
+ */
1379
+ isTextTrackEnabled() {
1380
+ return !!this.getCurrentTextTrack();
1381
+ }
1382
+
1383
+ /**
1384
+ * Check if the tracker is disabled.
1385
+ *
1386
+ * @returns {Boolean} __true__ if disabled __false__ otherwise.
1387
+ */
1388
+ isTrackerDisabled() {
1389
+ if (!this.srcMediaData || !this.srcMediaData.mediaData) return true;
1390
+ if (!Array.isArray(this.srcMediaData.disableTrackers)) {
1391
+ return Boolean(this.srcMediaData.disableTrackers);
1392
+ }
1393
+ return Boolean(this.srcMediaData.disableTrackers.find(tracker => tracker.toLowerCase() === SRGAnalytics.name.toLowerCase()));
1394
+ }
1395
+
1396
+ /**
1397
+ * Sent when loading of the media begins.
1398
+ *
1399
+ * @see https://docs.videojs.com/player#event:loadstart
1400
+ */
1401
+ loadstart() {
1402
+ this.destroy();
1403
+ this.updateSrcMediaData(this.player.currentSource());
1404
+ if (this.isTrackerDisabled()) return;
1405
+ this.getInternalLabels();
1406
+ // Set ComScore labels
1407
+ this.reloadTagCommanderContainer();
1408
+ this.notify('buffer_start');
1409
+ this.hasStarted = false;
1410
+ }
1411
+
1412
+ /**
1413
+ * The first frame of the media has finished loading.
1414
+ *
1415
+ * @see https://docs.videojs.com/player#event:loadeddata
1416
+ */
1417
+ loadeddata() {
1418
+ this.notify('init');
1419
+ this.initialized = true;
1420
+ this.notify('buffer_stop');
1421
+ }
1422
+
1423
+ /**
1424
+ * Event logger that prints the current event, event labels and internal labels in the browser's console.
1425
+ *
1426
+ * @param {String} eventName init | play | stop | pos | pause | seek | uptime | eof
1427
+ * @param {Object} eventMetadata event metadata object
1428
+ * @param {String} severity log | warn | error
1429
+ */
1430
+ log(eventName, eventMetadata, severity = 'log') {
1431
+ if (this.debug()) {
1432
+ // eslint-disable-next-line
1433
+ console[severity](`SRGAnalytics:${eventName}`, eventMetadata, window.tc_vars);
1434
+ }
1435
+ }
1436
+
1437
+ /**
1438
+ * Notify TagCommander all event and internal labels. If tc script is not available it queues all pending events.
1439
+ *
1440
+ * @param {String} eventName init | play | stop | pos | pause | seek | uptime | eof
1441
+ */
1442
+ notify(eventName, eventMetadata) {
1443
+ if (this.isTrackerDisabled()) return;
1444
+ try {
1445
+ this.flush();
1446
+ } catch (error) {
1447
+ this.log(eventName, error, 'error');
1448
+ }
1449
+ const labels = Object.assign({}, this.getEventLabels(eventName), eventMetadata);
1450
+ this.log(eventName, labels);
1451
+ try {
1452
+ if (window.tc_events_11) {
1453
+ window.tc_events_11(this.player.el(), eventName, labels);
1454
+ } else {
1455
+ this.pendingQueue.push({
1456
+ action: eventName,
1457
+ labels
1458
+ });
1459
+ }
1460
+ } catch (error) {
1461
+ this.log(eventName, error, 'error');
1462
+ }
1463
+ }
1464
+
1465
+ /**
1466
+ * Return the current timestamp in seconds.
1467
+ *
1468
+ * @returns {Number} Timestamp in seconds
1469
+ */
1470
+ static now() {
1471
+ return (Date.now() / 1000).toFixed(0);
1472
+ }
1473
+
1474
+ /**
1475
+ * Sent when the playback state is no longer paused, as a result of the play method, or the autoplay attribute.
1476
+ *
1477
+ * @see https://docs.videojs.com/player#event:play
1478
+ */
1479
+ play() {
1480
+ if (!this.hasStarted) this.hasStarted = true;
1481
+ if (!this.startPlaybackSession && !this.isMediaOnDemand()) {
1482
+ this.startPlaybackSession = SRGAnalytics.now();
1483
+ }
1484
+ if (this.mediaSession === 0) {
1485
+ this.mediaSession = SRGAnalytics.now();
1486
+ this.heartBeat();
1487
+ this.uptime();
1488
+ }
1489
+ this.timeUpdate();
1490
+ this.notify('play');
1491
+ if (this.isSeeking) this.isSeeking = false;
1492
+ }
1493
+
1494
+ /**
1495
+ * Sent when the playback state is changed to paused (paused property is true).
1496
+ * Pause event is sent if :
1497
+ * - The player is not scrubbing
1498
+ * - The stream is not a live only
1499
+ * - The current time is strictly inferior to the duration
1500
+ *
1501
+ * @see https://docs.videojs.com/player#event:pause
1502
+ */
1503
+ pause() {
1504
+ if (!this.isMediaOnDemand()) {
1505
+ this.elapsedPlaybackTime = this.getElapsedPlayingTime();
1506
+ this.startPlaybackSession = 0;
1507
+ }
1508
+ if (!this.player.seeking() && !this.isMediaLive() && this.player.currentTime() < this.player.duration()) {
1509
+ this.notify('pause');
1510
+ return;
1511
+ }
1512
+ if (this.hasStarted && !this.isSeeking) {
1513
+ this.notify('seek');
1514
+ this.isSeeking = true;
1515
+ }
1516
+ }
1517
+
1518
+ /**
1519
+ * Sent to ComScore when the playback rate changes.
1520
+ *
1521
+ * @see https://github.com/SRGSSR/srgletterbox-web/issues/761
1522
+ * @see https://jira.srg.beecollaboration.com/browse/ADI-256
1523
+ */
1524
+ rateChange() {
1525
+ this.notify('change_playback_rate');
1526
+ }
1527
+
1528
+ /**
1529
+ * Reload the tagCommander container and set all ComScore labels
1530
+ */
1531
+ reloadTagCommanderContainer() {
1532
+ if (window.tC) {
1533
+ window.tC.container.reload();
1534
+ this.pendingTagCommanderReload = false;
1535
+ } else {
1536
+ this.pendingTagCommanderReload = true;
1537
+ }
1538
+ }
1539
+
1540
+ /**
1541
+ * Sent when the current time is modified by the player's currentTime API.
1542
+ *
1543
+ * @see https://docs.videojs.com/player#event:seeking
1544
+ */
1545
+ seeking() {
1546
+ if (this.hasStarted && !this.player.paused() && !this.isSeeking) {
1547
+ this.notify('seek');
1548
+ this.isSeeking = true;
1549
+ }
1550
+ }
1551
+
1552
+ /**
1553
+ * Track current time updates delayed by a tick.
1554
+ *
1555
+ * @see https://docs.videojs.com/player#event:timeupdate
1556
+ */
1557
+ timeUpdate() {
1558
+ if (!this.player.paused()) {
1559
+ this.trackedCurrentTime = this.player.currentTime();
1560
+ }
1561
+ }
1562
+
1563
+ /**
1564
+ * Gets the number of seconds that separate from the live edge that is represented by 0.
1565
+ *
1566
+ * @returns {String}
1567
+ */
1568
+ timeShifted() {
1569
+ const isAtLiveEdge = this.player.liveTracker.atLiveEdge();
1570
+ const liveCurrentTime = this.player.liveTracker.liveCurrentTime();
1571
+ const currentTime = this.player.currentTime();
1572
+ const timeShifted = isAtLiveEdge ? 0 : (liveCurrentTime - currentTime).toFixed(0);
1573
+ return timeShifted;
1574
+ }
1575
+
1576
+ /**
1577
+ * Update the src media data.
1578
+ */
1579
+ updateSrcMediaData(srcMediaData) {
1580
+ this.srcMediaData = srcMediaData;
1581
+ }
1582
+
1583
+ /**
1584
+ * Calculate the uptime when playing a live stream with or without DVR
1585
+ *
1586
+ * __Rules__:
1587
+ * - Send the first uptime after 30 seconds
1588
+ * - Send uptime each 60 seconds after the 30 seconds
1589
+ * - Uptime is sent only when playing
1590
+ */
1591
+ uptime() {
1592
+ const notifyUptime = () => {
1593
+ if (!this.player.paused() && !this.isMediaOnDemand()) {
1594
+ this.notify('uptime');
1595
+ }
1596
+ };
1597
+
1598
+ // Send the first uptime after 30 seconds
1599
+ this.uptimeTimeoutId = setTimeout(() => {
1600
+ // Send only when playing
1601
+ notifyUptime();
1602
+
1603
+ // Initialize the uptime interval after 30 seconds
1604
+ this.uptimeIntervalId = setInterval(() => {
1605
+ // Send only when playing
1606
+ notifyUptime();
1607
+ }, 60000);
1608
+ }, 30000);
1609
+ }
1610
+
1611
+ /**
1612
+ * __ComScore__:
1613
+ * It's expected notifyBufferStart() to be called when the player starts buffering
1614
+ * and a call to notifyBufferStop() when content resumes after buffering.
1615
+ *
1616
+ * @see Item 2: https://jira.srg.beecollaboration.com/browse/PLAY-2628
1617
+ *
1618
+ * After the issue PLAYRTS-321
1619
+ * @see Fix: https://jira.srg.beecollaboration.com/browse/PLAYRTS-321?focusedCommentId=201023&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-201023
1620
+ * @see Fix: https://jira.srg.beecollaboration.com/browse/PLAYRTS-3065
1621
+ */
1622
+ waiting() {
1623
+ if (!this.initialized || this.isWaiting) {
1624
+ return;
1625
+ }
1626
+ const bufferStop = () => {
1627
+ this.isWaiting = false;
1628
+ this.notify('buffer_stop');
1629
+ };
1630
+ this.isWaiting = true;
1631
+ this.notify('buffer_start');
1632
+
1633
+ // As Safari is not consistent with its playing event, it is better to use the timeupdate event.
1634
+ if (pillarbox.browser.IS_ANY_SAFARI) {
1635
+ this.player.one(TIME_UPDATE, bufferStop);
1636
+ } else {
1637
+ // As Chromium-based browsers are not consistent with their timeupdate event, it is better to use the playing event.
1638
+ // Firefox is consistent with its playing event.
1639
+ this.player.one(PLAYING, bufferStop);
1640
+ }
1641
+ }
1642
+ }
1643
+
1644
+ /**
1645
+ * Represents the composition of media content.
1646
+ *
1647
+ * @class MediaComposition
1648
+ * @property {string} chapterUrn URN (Uniform Resource Name) of the associated chapter.
1649
+ * @property {string} segmentUrn URN of the associated segment.
1650
+ * @property {Episode} episode Associated episode.
1651
+ * @property {Show} show Associated show.
1652
+ * @property {Channel} channel Associated channel.
1653
+ * @property {Array.<Chapter>} chapterList List of associated chapters.
1654
+ * @property {Array.<Topic>} topicList List of associated topics.
1655
+ * @property {Object.<String, String>} analyticsData Analytics data associated with the media composition.
1656
+ * @property {Object.<String, String>} analyticsMetadata Metadata associated with analytics for the media composition.
1657
+ */
1658
+ class MediaComposition {
1659
+ /**
1660
+ * Find a chapter by its URN.
1661
+ *
1662
+ * @param {String} urn
1663
+ *
1664
+ * @returns {Chapter} chapter
1665
+ */
1666
+ findChapterByUrn(urn) {
1667
+ if (this.chapterList) {
1668
+ const [chapter] = this.chapterList.filter(element => element.urn === urn);
1669
+ return chapter;
1670
+ }
1671
+ return undefined;
1672
+ }
1673
+
1674
+ /**
1675
+ * Return a segment from main chapter following segmentUrn in mediaComposition.
1676
+ *
1677
+ * @returns {Segment|undefined} main segment
1678
+ */
1679
+ findMainSegment() {
1680
+ if (!this.segmentUrn) {
1681
+ return undefined;
1682
+ }
1683
+ const segmentList = this.getMainSegments();
1684
+ const [segment] = segmentList.filter(element => element.urn === this.segmentUrn);
1685
+ return segment;
1686
+ }
1687
+
1688
+ /**
1689
+ * Find resource list by URN.
1690
+ *
1691
+ * @param {String} urn
1692
+ * @returns {Array.<Resource>|undefined} of resources
1693
+ */
1694
+ findResourceListByUrn(urn) {
1695
+ const chapterByUrn = this.findChapterByUrn(urn);
1696
+ if (chapterByUrn) {
1697
+ return chapterByUrn.resourceList || [];
1698
+ }
1699
+ return undefined;
1700
+ }
1701
+
1702
+ /**
1703
+ * A list of chapters.
1704
+ *
1705
+ * @returns {Array.<Chapter>} of chapters
1706
+ */
1707
+ getChapters() {
1708
+ const AUDIO = 'AUDIO';
1709
+ if (this.getMainChapter().mediaType === AUDIO) return [];
1710
+ return this.chapterList.filter(({
1711
+ mediaType
1712
+ }) => mediaType !== AUDIO);
1713
+ }
1714
+
1715
+ /**
1716
+ * Filter external text tracks that are already available internally.
1717
+ *
1718
+ * __Rules:__
1719
+ * 1. TTML format is filtered
1720
+ *
1721
+ * 2. If both are empty that means only internal text tracks will be displayed
1722
+ * to the user as they are automatically loaded by the player.
1723
+ *
1724
+ * 3. If subtitleInformationList is missing from the MediaComposition and subtitleList
1725
+ * is available but the media contains internal text tracks that are also available internally.
1726
+ * It will result on a duplication client side.
1727
+ *
1728
+ * 4. If subtitleList and subtitleInformationList a merge between both will be operated,
1729
+ * removing the external text tracks already available internally.
1730
+ *
1731
+ *
1732
+ * @returns {Array.<Subtitle>} external text tracks
1733
+ */
1734
+ getFilteredExternalSubtitles() {
1735
+ const {
1736
+ subtitleList
1737
+ } = this.getMainChapter();
1738
+ const [{
1739
+ subtitleInformationList
1740
+ } = {}] = this.getResourceList().filter(({
1741
+ subtitleInformationList
1742
+ }) => subtitleInformationList);
1743
+ const onlyHasExternalSubtitles = subtitleList && !subtitleInformationList;
1744
+ if (!subtitleList) {
1745
+ return [];
1746
+ }
1747
+
1748
+ // TTML format is not supported
1749
+ const subtitles = subtitleList.filter(subtitle => subtitle.format !== 'TTML');
1750
+ if (onlyHasExternalSubtitles) {
1751
+ return subtitles;
1752
+ }
1753
+ return subtitles.filter(subtitle => {
1754
+ const addSubtitle = !subtitleInformationList.find(subtitleInformation => subtitleInformation.locale === subtitle.locale && subtitle.type === subtitleInformation.type);
1755
+ return addSubtitle;
1756
+ });
1757
+ }
1758
+
1759
+ /**
1760
+ * Block reason for main chapter. This also uses current date for STARTDATE.
1761
+ *
1762
+ * @see BlockReason
1763
+ *
1764
+ * @returns {string | undefined} undefined if main chapter is not blocked
1765
+ */
1766
+ getMainBlockReason() {
1767
+ const mainChapter = this.getMainChapter();
1768
+ if (!mainChapter) {
1769
+ return undefined;
1770
+ }
1771
+ let {
1772
+ blockReason
1773
+ } = mainChapter;
1774
+ if (!blockReason && new Date() < this.getMainValidFromDate()) {
1775
+ blockReason = 'STARTDATE';
1776
+ }
1777
+ return blockReason;
1778
+ }
1779
+
1780
+ /**
1781
+ * Get blocked segments from the main chapter.
1782
+ *
1783
+ * @returns {Array.<Segment>} of blocked segments
1784
+ */
1785
+ getMainBlockedSegments() {
1786
+ return this.getMainSegments().filter(segment => segment.blockReason);
1787
+ }
1788
+
1789
+ /**
1790
+ * Get the mediaComposition's main chapter.
1791
+ *
1792
+ * @returns {Chapter}
1793
+ */
1794
+ getMainChapter() {
1795
+ if (!this.mainChapter) {
1796
+ this.mainChapter = this.findChapterByUrn(this.chapterUrn);
1797
+ }
1798
+ if (!this.mainChapter && this.chapterList && this.chapterList.length > 0) {
1799
+ [this.mainChapter] = this.chapterList;
1800
+ }
1801
+ return this.mainChapter;
1802
+ }
1803
+
1804
+ /**
1805
+ * Get the main chapter's image URL decorated with default width and format.
1806
+ *
1807
+ * @returns {String|undefined} image URL
1808
+ */
1809
+ getMainChapterImageUrl() {
1810
+ const mainChapter = this.getMainChapter();
1811
+ if (!mainChapter || !mainChapter.imageUrl) {
1812
+ return undefined;
1813
+ }
1814
+ return mainChapter.imageUrl;
1815
+ }
1816
+
1817
+ /**
1818
+ * Get main resources.
1819
+ *
1820
+ * @returns {Array.<MainResource>} array of sources.
1821
+ */
1822
+ // eslint-disable-next-line max-lines-per-function
1823
+ getMainResources() {
1824
+ const resourceList = this.getResourceList();
1825
+ if (!resourceList || !resourceList.length) {
1826
+ return undefined;
1827
+ }
1828
+ return resourceList.map(resource => ({
1829
+ analyticsData: this.getMergedAnalyticsData(resource.analyticsData),
1830
+ analyticsMetadata: this.getMergedAnalyticsMetadata(resource.analyticsMetadata),
1831
+ blockReason: this.getMainChapter().blockReason,
1832
+ blockedSegments: this.getMainBlockedSegments(),
1833
+ imageUrl: this.getMainChapterImageUrl(),
1834
+ chapters: this.getChapters(),
1835
+ drmList: resource.drmList,
1836
+ dvr: resource.dvr,
1837
+ eventData: this.getMainChapter().eventData,
1838
+ id: this.getMainChapter().id,
1839
+ imageCopyright: this.getMainChapter().imageCopyright,
1840
+ intervals: this.getMainTimeIntervals(),
1841
+ live: resource.live,
1842
+ mediaType: this.getMainChapter().mediaType,
1843
+ mimeType: resource.mimeType,
1844
+ presentation: resource.presentation,
1845
+ quality: resource.quality,
1846
+ streaming: resource.streaming,
1847
+ streamOffset: resource.streamOffset,
1848
+ subtitles: this.getFilteredExternalSubtitles(),
1849
+ title: this.getMainChapter().title,
1850
+ tokenType: resource.tokenType,
1851
+ url: resource.url,
1852
+ urn: this.chapterUrn,
1853
+ vendor: this.getMainChapter().vendor
1854
+ }));
1855
+ }
1856
+
1857
+ /**
1858
+ * Get segments of the main chapter ordered by markIn.
1859
+ *
1860
+ * @returns {Array.<Segment>} of segments
1861
+ */
1862
+ getMainSegments() {
1863
+ const mainChapter = this.getMainChapter();
1864
+ if (!this.mainSegments && mainChapter && mainChapter.segmentList) {
1865
+ this.mainSegments = mainChapter.segmentList;
1866
+ }
1867
+ return this.mainSegments || [];
1868
+ }
1869
+
1870
+ /**
1871
+ * Retrieves an array of time intervals associated with the main chapter.
1872
+ *
1873
+ * @returns {Array.<TimeInterval>} An array of time intervals.
1874
+ */
1875
+ getMainTimeIntervals() {
1876
+ const {
1877
+ timeIntervalList = []
1878
+ } = this.getMainChapter() || {};
1879
+ return timeIntervalList;
1880
+ }
1881
+
1882
+ /**
1883
+ * Compute a date from which this content is valid. Always return a date object.
1884
+ *
1885
+ * @returns {Date} date specified in media composition or EPOCH when no date present.
1886
+ */
1887
+ getMainValidFromDate() {
1888
+ const mainChapter = this.getMainChapter();
1889
+ if (!mainChapter) {
1890
+ return new Date(0);
1891
+ }
1892
+ const {
1893
+ validFrom
1894
+ } = mainChapter;
1895
+ if (validFrom) {
1896
+ return new Date(validFrom);
1897
+ }
1898
+ }
1899
+
1900
+ /**
1901
+ * Get merged analytics data.
1902
+ *
1903
+ * @param {Object.<string, string>} analyticsData
1904
+ * @returns {Object.<string, string>} Merged analytics data.
1905
+ */
1906
+ getMergedAnalyticsData(analyticsData) {
1907
+ return _objectSpread2(_objectSpread2(_objectSpread2({}, this.analyticsData), this.getMainChapter().analyticsData), analyticsData);
1908
+ }
1909
+
1910
+ /**
1911
+ * Get merged analytics metadata.
1912
+ *
1913
+ * @param {Object.<string, string>} analyticsMetadata
1914
+ * @returns {Object.<string, string>} Merged analytics metadata.
1915
+ */
1916
+ getMergedAnalyticsMetadata(analyticsMetadata) {
1917
+ return _objectSpread2(_objectSpread2(_objectSpread2({}, this.analyticsMetadata), this.getMainChapter().analyticsMetadata), analyticsMetadata);
1918
+ }
1919
+
1920
+ /**
1921
+ * Get the chapter's resource list
1922
+ * @returns {Array.<Resource>} of resources
1923
+ */
1924
+ getResourceList() {
1925
+ const {
1926
+ resourceList
1927
+ } = this.getMainChapter();
1928
+ return resourceList || [];
1929
+ }
1930
+ }
1931
+
1932
+ /**
1933
+ * @typedef {import('./typedef').Channel} Channel
1934
+ * @typedef {import('./typedef').Chapter} Chapter
1935
+ * @typedef {import('./typedef').Episode} Episode
1936
+ * @typedef {import('./typedef').Resource} Resource
1937
+ * @typedef {import('./typedef').Segment} Segment
1938
+ * @typedef {import('./typedef').Show} Show
1939
+ * @typedef {import('./typedef').Subtitle} Subtitle
1940
+ * @typedef {import('./typedef').TimeInterval} TimeInterval
1941
+ * @typedef {import('./typedef').Topic} Topic
1942
+ * @typedef {import('./typedef').MainResource} MainResource
1943
+ */
1944
+
1945
+ var Play$4 = "Wiedergabe";
1946
+ var Pause$4 = "Pause";
1947
+ var Replay$4 = "Erneut abspielen";
1948
+ var Duration$4 = "Dauer";
1949
+ var LIVE$4 = "LIVE";
1950
+ var Loaded$4 = "Geladen";
1951
+ var Progress$4 = "Status";
1952
+ var Fullscreen$4 = "Vollbild";
1953
+ var Mute$4 = "Stumm schalten";
1954
+ var Unmute$4 = "Ton einschalten";
1955
+ var Subtitles$4 = "Untertitel";
1956
+ var Captions$4 = "Untertitel";
1957
+ var Chapters$4 = "Kapitel";
1958
+ var Close$4 = "Schließen";
1959
+ var Descriptions$4 = "Beschreibungen";
1960
+ var Text$4 = "Schrift";
1961
+ var White$4 = "Weiß";
1962
+ var Black$4 = "Schwarz";
1963
+ var Red$4 = "Rot";
1964
+ var Green$4 = "Grün";
1965
+ var Blue$4 = "Blau";
1966
+ var Yellow$4 = "Gelb";
1967
+ var Magenta$4 = "Magenta";
1968
+ var Cyan$4 = "Türkis";
1969
+ var Background$4 = "Hintergrund";
1970
+ var Window$4 = "Fenster";
1971
+ var Transparent$4 = "Durchsichtig";
1972
+ var Opaque$4 = "Undurchsichtig";
1973
+ var None$4 = "Kein";
1974
+ var Raised$3 = "Erhoben";
1975
+ var Depressed$3 = "Gedrückt";
1976
+ var Uniform$4 = "Uniform";
1977
+ var Casual$3 = "Zwanglos";
1978
+ var Script$3 = "Schreibschrift";
1979
+ var Reset$4 = "Zurücksetzen";
1980
+ var Done$4 = "Fertig";
1981
+ var Color$3 = "Farbe";
1982
+ var Opacity$3 = "Deckkraft";
1983
+ var de$1 = {
1984
+ Play: Play$4,
1985
+ Pause: Pause$4,
1986
+ Replay: Replay$4,
1987
+ "Current Time": "Aktueller Zeitpunkt",
1988
+ Duration: Duration$4,
1989
+ "Remaining Time": "Verbleibende Zeit",
1990
+ "Stream Type": "Streamtyp",
1991
+ LIVE: LIVE$4,
1992
+ Loaded: Loaded$4,
1993
+ Progress: Progress$4,
1994
+ Fullscreen: Fullscreen$4,
1995
+ "Exit Fullscreen": "Vollbildmodus beenden",
1996
+ Mute: Mute$4,
1997
+ Unmute: Unmute$4,
1998
+ "Playback Rate": "Wiedergabegeschwindigkeit",
1999
+ Subtitles: Subtitles$4,
2000
+ "subtitles off": "Untertitel aus",
2001
+ Captions: Captions$4,
2002
+ "captions off": "Untertitel aus",
2003
+ Chapters: Chapters$4,
2004
+ "You aborted the media playback": "Sie haben die Videowiedergabe abgebrochen.",
2005
+ "A network error caused the media download to fail part-way.": "Der Videodownload ist aufgrund eines Netzwerkfehlers fehlgeschlagen.",
2006
+ "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.",
2007
+ "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Die Videowiedergabe wurde entweder wegen eines Problems mit einem beschädigten Video oder wegen verwendeten Funktionen, die vom Browser nicht unterstützt werden, abgebrochen.",
2008
+ "No compatible source was found for this media.": "Für dieses Video wurde keine kompatible Quelle gefunden.",
2009
+ "Play Video": "Video abspielen",
2010
+ Close: Close$4,
2011
+ "Modal Window": "Modales Fenster",
2012
+ "This is a modal window": "Dies ist ein modales Fenster",
2013
+ "This modal can be closed by pressing the Escape key or activating the close button.": "Durch Drücken der Esc-Taste bzw. Betätigung der Schaltfläche \"Schließen\" wird dieses modale Fenster geschlossen.",
2014
+ ", opens captions settings dialog": ", öffnet Einstellungen für Untertitel",
2015
+ ", opens subtitles settings dialog": ", öffnet Einstellungen für Untertitel",
2016
+ ", selected": ", ausgewählt",
2017
+ "captions settings": "Untertiteleinstellungen",
2018
+ "subtitles settings": "Untertiteleinstellungen",
2019
+ "descriptions settings": "Einstellungen für Beschreibungen",
2020
+ "Close Modal Dialog": "Modales Fenster schließen",
2021
+ Descriptions: Descriptions$4,
2022
+ "descriptions off": "Beschreibungen aus",
2023
+ "The media is encrypted and we do not have the keys to decrypt it.": "Die Entschlüsselungsschlüssel für den verschlüsselten Medieninhalt sind nicht verfügbar.",
2024
+ ", opens descriptions settings dialog": ", öffnet Einstellungen für Beschreibungen",
2025
+ "Audio Track": "Tonspur",
2026
+ Text: Text$4,
2027
+ White: White$4,
2028
+ Black: Black$4,
2029
+ Red: Red$4,
2030
+ Green: Green$4,
2031
+ Blue: Blue$4,
2032
+ Yellow: Yellow$4,
2033
+ Magenta: Magenta$4,
2034
+ Cyan: Cyan$4,
2035
+ Background: Background$4,
2036
+ Window: Window$4,
2037
+ Transparent: Transparent$4,
2038
+ "Semi-Transparent": "Halbdurchsichtig",
2039
+ Opaque: Opaque$4,
2040
+ "Font Size": "Schriftgröße",
2041
+ "Text Edge Style": "Textkantenstil",
2042
+ None: None$4,
2043
+ Raised: Raised$3,
2044
+ Depressed: Depressed$3,
2045
+ Uniform: Uniform$4,
2046
+ "Drop shadow": "Schlagschatten",
2047
+ "Font Family": "Schriftfamilie",
2048
+ "Proportional Sans-Serif": "Proportionale Sans-Serif",
2049
+ "Monospace Sans-Serif": "Monospace Sans-Serif",
2050
+ "Proportional Serif": "Proportionale Serif",
2051
+ "Monospace Serif": "Monospace Serif",
2052
+ Casual: Casual$3,
2053
+ Script: Script$3,
2054
+ "Small Caps": "Small-Caps",
2055
+ Reset: Reset$4,
2056
+ "restore all settings to the default values": "Alle Einstellungen auf die Standardwerte zurücksetzen",
2057
+ Done: Done$4,
2058
+ "Caption Settings Dialog": "Einstellungsdialog für Untertitel",
2059
+ "Beginning of dialog window. Escape will cancel and close the window.": "Anfang des Dialogfensters. Esc bricht ab und schließt das Fenster.",
2060
+ "End of dialog window.": "Ende des Dialogfensters.",
2061
+ "Audio Player": "Audio-Player",
2062
+ "Video Player": "Video-Player",
2063
+ "Progress Bar": "Fortschrittsbalken",
2064
+ "progress bar timing: currentTime={1} duration={2}": "{1} von {2}",
2065
+ "Volume Level": "Lautstärke",
2066
+ "{1} is loading.": "{1} wird geladen.",
2067
+ "Seek to live, currently behind live": "Zur Live-Übertragung wechseln. Aktuell wird es nicht live abgespielt.",
2068
+ "Seek to live, currently playing live": "Zur Live-Übertragung wechseln. Es wird aktuell live abgespielt.",
2069
+ "Exit Picture-in-Picture": "Bild-im-Bild-Modus beenden",
2070
+ "Picture-in-Picture": "Bild-im-Bild-Modus",
2071
+ "No content": "Kein Inhalt",
2072
+ Color: Color$3,
2073
+ Opacity: Opacity$3,
2074
+ "Text Background": "Texthintergrund",
2075
+ "Caption Area Background": "Hintergrund des Untertitelbereichs",
2076
+ "Playing in Picture-in-Picture": "Wird im Bild-im-Bild-Modus wiedergegeben",
2077
+ "Skip forward {1} seconds": "{1} Sekunden vorwärts",
2078
+ "Skip backward {1} seconds": "{1} Sekunden zurück"
2079
+ };
2080
+
2081
+ var vjsLang$3 = /*#__PURE__*/Object.freeze({
2082
+ __proto__: null,
2083
+ Background: Background$4,
2084
+ Black: Black$4,
2085
+ Blue: Blue$4,
2086
+ Captions: Captions$4,
2087
+ Casual: Casual$3,
2088
+ Chapters: Chapters$4,
2089
+ Close: Close$4,
2090
+ Color: Color$3,
2091
+ Cyan: Cyan$4,
2092
+ Depressed: Depressed$3,
2093
+ Descriptions: Descriptions$4,
2094
+ Done: Done$4,
2095
+ Duration: Duration$4,
2096
+ Fullscreen: Fullscreen$4,
2097
+ Green: Green$4,
2098
+ LIVE: LIVE$4,
2099
+ Loaded: Loaded$4,
2100
+ Magenta: Magenta$4,
2101
+ Mute: Mute$4,
2102
+ None: None$4,
2103
+ Opacity: Opacity$3,
2104
+ Opaque: Opaque$4,
2105
+ Pause: Pause$4,
2106
+ Play: Play$4,
2107
+ Progress: Progress$4,
2108
+ Raised: Raised$3,
2109
+ Red: Red$4,
2110
+ Replay: Replay$4,
2111
+ Reset: Reset$4,
2112
+ Script: Script$3,
2113
+ Subtitles: Subtitles$4,
2114
+ Text: Text$4,
2115
+ Transparent: Transparent$4,
2116
+ Uniform: Uniform$4,
2117
+ Unmute: Unmute$4,
2118
+ White: White$4,
2119
+ Window: Window$4,
2120
+ Yellow: Yellow$4,
2121
+ default: de$1
2122
+ });
2123
+
2124
+ var AGERATING12$4 = "Aus Gründen des Jugendschutzes steht dieser Inhalt nur zwischen 20:00 und 06:00 Uhr zur Verfügung.";
2125
+ var AGERATING18$4 = "Aus Gründen des Jugendschutzes steht dieser Inhalt nur zwischen 23:00 und 05:00 Uhr zur Verfügung.";
2126
+ var COMMERCIAL$4 = "Die Werbung wurde übersprungen.";
2127
+ var ENDDATE$4 = "Dieser Inhalt ist nicht mehr verfügbar.";
2128
+ var GEOBLOCK$4 = "Dieser Inhalt ist ausserhalb der Schweiz nicht verfügbar.";
2129
+ var LEGAL$4 = "Dieser Inhalt ist aus rechtlichen Gründen nicht verfügbar.";
2130
+ var STARTDATE$4 = "Dieser Inhalt ist noch nicht verfügbar. Bitte probieren Sie es später noch einmal.";
2131
+ var UNKNOWN$4 = "Dieser Inhalt ist nicht verfügbar.";
2132
+ var de = {
2133
+ AGERATING12: AGERATING12$4,
2134
+ AGERATING18: AGERATING18$4,
2135
+ COMMERCIAL: COMMERCIAL$4,
2136
+ ENDDATE: ENDDATE$4,
2137
+ GEOBLOCK: GEOBLOCK$4,
2138
+ LEGAL: LEGAL$4,
2139
+ STARTDATE: STARTDATE$4,
2140
+ UNKNOWN: UNKNOWN$4
2141
+ };
2142
+
2143
+ var pillarboxLang$4 = /*#__PURE__*/Object.freeze({
2144
+ __proto__: null,
2145
+ AGERATING12: AGERATING12$4,
2146
+ AGERATING18: AGERATING18$4,
2147
+ COMMERCIAL: COMMERCIAL$4,
2148
+ ENDDATE: ENDDATE$4,
2149
+ GEOBLOCK: GEOBLOCK$4,
2150
+ LEGAL: LEGAL$4,
2151
+ STARTDATE: STARTDATE$4,
2152
+ UNKNOWN: UNKNOWN$4,
2153
+ default: de
2154
+ });
2155
+
2156
+ pillarbox.addLanguage('de', _objectSpread2(_objectSpread2({}, vjsLang$3), pillarboxLang$4));
2157
+
2158
+ var Play$3 = "Play";
2159
+ var Pause$3 = "Pause";
2160
+ var Replay$3 = "Replay";
2161
+ var Duration$3 = "Duration";
2162
+ var LIVE$3 = "LIVE";
2163
+ var Loaded$3 = "Loaded";
2164
+ var Progress$3 = "Progress";
2165
+ var Fullscreen$3 = "Fullscreen";
2166
+ var Mute$3 = "Mute";
2167
+ var Unmute$3 = "Unmute";
2168
+ var Subtitles$3 = "Subtitles";
2169
+ var Captions$3 = "Captions";
2170
+ var Chapters$3 = "Chapters";
2171
+ var Descriptions$3 = "Descriptions";
2172
+ var Close$3 = "Close";
2173
+ var Text$3 = "Text";
2174
+ var White$3 = "White";
2175
+ var Black$3 = "Black";
2176
+ var Red$3 = "Red";
2177
+ var Green$3 = "Green";
2178
+ var Blue$3 = "Blue";
2179
+ var Yellow$3 = "Yellow";
2180
+ var Magenta$3 = "Magenta";
2181
+ var Cyan$3 = "Cyan";
2182
+ var Background$3 = "Background";
2183
+ var Window$3 = "Window";
2184
+ var Transparent$3 = "Transparent";
2185
+ var Opaque$3 = "Opaque";
2186
+ var None$3 = "None";
2187
+ var Raised$2 = "Raised";
2188
+ var Depressed$2 = "Depressed";
2189
+ var Uniform$3 = "Uniform";
2190
+ var Casual$2 = "Casual";
2191
+ var Script$2 = "Script";
2192
+ var Reset$3 = "Reset";
2193
+ var Done$3 = "Done";
2194
+ var Color$2 = "Color";
2195
+ var Opacity$2 = "Opacity";
2196
+ var en$1 = {
2197
+ "Audio Player": "Audio Player",
2198
+ "Video Player": "Video Player",
2199
+ Play: Play$3,
2200
+ Pause: Pause$3,
2201
+ Replay: Replay$3,
2202
+ "Current Time": "Current Time",
2203
+ Duration: Duration$3,
2204
+ "Remaining Time": "Remaining Time",
2205
+ "Stream Type": "Stream Type",
2206
+ LIVE: LIVE$3,
2207
+ "Seek to live, currently behind live": "Seek to live, currently behind live",
2208
+ "Seek to live, currently playing live": "Seek to live, currently playing live",
2209
+ Loaded: Loaded$3,
2210
+ Progress: Progress$3,
2211
+ "Progress Bar": "Progress Bar",
2212
+ "progress bar timing: currentTime={1} duration={2}": "{1} of {2}",
2213
+ Fullscreen: Fullscreen$3,
2214
+ "Exit Fullscreen": "Exit Fullscreen",
2215
+ Mute: Mute$3,
2216
+ Unmute: Unmute$3,
2217
+ "Playback Rate": "Playback Rate",
2218
+ Subtitles: Subtitles$3,
2219
+ "subtitles off": "subtitles off",
2220
+ Captions: Captions$3,
2221
+ "captions off": "captions off",
2222
+ Chapters: Chapters$3,
2223
+ Descriptions: Descriptions$3,
2224
+ "descriptions off": "descriptions off",
2225
+ "Audio Track": "Audio Track",
2226
+ "Volume Level": "Volume Level",
2227
+ "You aborted the media playback": "You aborted the media playback",
2228
+ "A network error caused the media download to fail part-way.": "A network error caused the media download to fail part-way.",
2229
+ "The media could not be loaded, either because the server or network failed or because the format is not supported.": "The media could not be loaded, either because the server or network failed or because the format is not supported.",
2230
+ "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.",
2231
+ "No compatible source was found for this media.": "No compatible source was found for this media.",
2232
+ "The media is encrypted and we do not have the keys to decrypt it.": "The media is encrypted and we do not have the keys to decrypt it.",
2233
+ "Play Video": "Play Video",
2234
+ Close: Close$3,
2235
+ "Close Modal Dialog": "Close Modal Dialog",
2236
+ "Modal Window": "Modal Window",
2237
+ "This is a modal window": "This is a modal window",
2238
+ "This modal can be closed by pressing the Escape key or activating the close button.": "This modal can be closed by pressing the Escape key or activating the close button.",
2239
+ ", opens captions settings dialog": ", opens captions settings dialog",
2240
+ ", opens subtitles settings dialog": ", opens subtitles settings dialog",
2241
+ ", opens descriptions settings dialog": ", opens descriptions settings dialog",
2242
+ ", selected": ", selected",
2243
+ "captions settings": "captions settings",
2244
+ "subtitles settings": "subtitles settings",
2245
+ "descriptions settings": "descriptions settings",
2246
+ Text: Text$3,
2247
+ White: White$3,
2248
+ Black: Black$3,
2249
+ Red: Red$3,
2250
+ Green: Green$3,
2251
+ Blue: Blue$3,
2252
+ Yellow: Yellow$3,
2253
+ Magenta: Magenta$3,
2254
+ Cyan: Cyan$3,
2255
+ Background: Background$3,
2256
+ Window: Window$3,
2257
+ Transparent: Transparent$3,
2258
+ "Semi-Transparent": "Semi-Transparent",
2259
+ Opaque: Opaque$3,
2260
+ "Font Size": "Font Size",
2261
+ "Text Edge Style": "Text Edge Style",
2262
+ None: None$3,
2263
+ Raised: Raised$2,
2264
+ Depressed: Depressed$2,
2265
+ Uniform: Uniform$3,
2266
+ "Drop shadow": "Drop shadow",
2267
+ "Font Family": "Font Family",
2268
+ "Proportional Sans-Serif": "Proportional Sans-Serif",
2269
+ "Monospace Sans-Serif": "Monospace Sans-Serif",
2270
+ "Proportional Serif": "Proportional Serif",
2271
+ "Monospace Serif": "Monospace Serif",
2272
+ Casual: Casual$2,
2273
+ Script: Script$2,
2274
+ "Small Caps": "Small Caps",
2275
+ Reset: Reset$3,
2276
+ "restore all settings to the default values": "restore all settings to the default values",
2277
+ Done: Done$3,
2278
+ "Caption Settings Dialog": "Caption Settings Dialog",
2279
+ "Beginning of dialog window. Escape will cancel and close the window.": "Beginning of dialog window. Escape will cancel and close the window.",
2280
+ "End of dialog window.": "End of dialog window.",
2281
+ "{1} is loading.": "{1} is loading.",
2282
+ "Exit Picture-in-Picture": "Exit Picture-in-Picture",
2283
+ "Picture-in-Picture": "Picture-in-Picture",
2284
+ "No content": "No content",
2285
+ Color: Color$2,
2286
+ Opacity: Opacity$2,
2287
+ "Text Background": "Text Background",
2288
+ "Caption Area Background": "Caption Area Background",
2289
+ "Playing in Picture-in-Picture": "Playing in Picture-in-Picture",
2290
+ "Skip backward {1} seconds": "Skip backward {1} seconds",
2291
+ "Skip forward {1} seconds": "Skip forward {1} seconds"
2292
+ };
2293
+
2294
+ var vjsLang$2 = /*#__PURE__*/Object.freeze({
2295
+ __proto__: null,
2296
+ Background: Background$3,
2297
+ Black: Black$3,
2298
+ Blue: Blue$3,
2299
+ Captions: Captions$3,
2300
+ Casual: Casual$2,
2301
+ Chapters: Chapters$3,
2302
+ Close: Close$3,
2303
+ Color: Color$2,
2304
+ Cyan: Cyan$3,
2305
+ Depressed: Depressed$2,
2306
+ Descriptions: Descriptions$3,
2307
+ Done: Done$3,
2308
+ Duration: Duration$3,
2309
+ Fullscreen: Fullscreen$3,
2310
+ Green: Green$3,
2311
+ LIVE: LIVE$3,
2312
+ Loaded: Loaded$3,
2313
+ Magenta: Magenta$3,
2314
+ Mute: Mute$3,
2315
+ None: None$3,
2316
+ Opacity: Opacity$2,
2317
+ Opaque: Opaque$3,
2318
+ Pause: Pause$3,
2319
+ Play: Play$3,
2320
+ Progress: Progress$3,
2321
+ Raised: Raised$2,
2322
+ Red: Red$3,
2323
+ Replay: Replay$3,
2324
+ Reset: Reset$3,
2325
+ Script: Script$2,
2326
+ Subtitles: Subtitles$3,
2327
+ Text: Text$3,
2328
+ Transparent: Transparent$3,
2329
+ Uniform: Uniform$3,
2330
+ Unmute: Unmute$3,
2331
+ White: White$3,
2332
+ Window: Window$3,
2333
+ Yellow: Yellow$3,
2334
+ default: en$1
2335
+ });
2336
+
2337
+ var AGERATING12$3 = "To protect children this content is only available between 8PM and 6AM.";
2338
+ var AGERATING18$3 = "To protect children this content is only available between 10PM and 5AM.";
2339
+ var COMMERCIAL$3 = "This commercial content is not available.";
2340
+ var ENDDATE$3 = "This content is not available anymore.";
2341
+ var GEOBLOCK$3 = "This content is not available outside Switzerland.";
2342
+ var LEGAL$3 = "This content is not available due to legal restrictions.";
2343
+ var STARTDATE$3 = "This content is not available yet.";
2344
+ var UNKNOWN$3 = "This content is not available.";
2345
+ var en = {
2346
+ AGERATING12: AGERATING12$3,
2347
+ AGERATING18: AGERATING18$3,
2348
+ COMMERCIAL: COMMERCIAL$3,
2349
+ ENDDATE: ENDDATE$3,
2350
+ GEOBLOCK: GEOBLOCK$3,
2351
+ LEGAL: LEGAL$3,
2352
+ STARTDATE: STARTDATE$3,
2353
+ UNKNOWN: UNKNOWN$3
2354
+ };
2355
+
2356
+ var pillarboxLang$3 = /*#__PURE__*/Object.freeze({
2357
+ __proto__: null,
2358
+ AGERATING12: AGERATING12$3,
2359
+ AGERATING18: AGERATING18$3,
2360
+ COMMERCIAL: COMMERCIAL$3,
2361
+ ENDDATE: ENDDATE$3,
2362
+ GEOBLOCK: GEOBLOCK$3,
2363
+ LEGAL: LEGAL$3,
2364
+ STARTDATE: STARTDATE$3,
2365
+ UNKNOWN: UNKNOWN$3,
2366
+ default: en
2367
+ });
2368
+
2369
+ pillarbox.addLanguage('en', _objectSpread2(_objectSpread2({}, vjsLang$2), pillarboxLang$3));
2370
+
2371
+ var Play$2 = "Lecture";
2372
+ var Pause$2 = "Pause";
2373
+ var Replay$2 = "Revoir";
2374
+ var Duration$2 = "Durée";
2375
+ var LIVE$2 = "EN DIRECT";
2376
+ var Loaded$2 = "Chargé";
2377
+ var Progress$2 = "Progression";
2378
+ var Fullscreen$2 = "Plein écran";
2379
+ var Mute$2 = "Mettre en sourdine";
2380
+ var Unmute$2 = "Activer le son";
2381
+ var Subtitles$2 = "Sous-titres";
2382
+ var Captions$2 = "Sous-titres transcrits";
2383
+ var Chapters$2 = "Chapitres";
2384
+ var Descriptions$2 = "Descriptions";
2385
+ var Close$2 = "Fermer";
2386
+ var Text$2 = "Texte";
2387
+ var White$2 = "Blanc";
2388
+ var Black$2 = "Noir";
2389
+ var Red$2 = "Rouge";
2390
+ var Green$2 = "Vert";
2391
+ var Blue$2 = "Bleu";
2392
+ var Yellow$2 = "Jaune";
2393
+ var Magenta$2 = "Magenta";
2394
+ var Cyan$2 = "Cyan";
2395
+ var Background$2 = "Arrière-plan";
2396
+ var Window$2 = "Fenêtre";
2397
+ var Transparent$2 = "Transparent";
2398
+ var Opaque$2 = "Opaque";
2399
+ var None$2 = "Aucun";
2400
+ var Raised$1 = "Élevé";
2401
+ var Depressed$1 = "Enfoncé";
2402
+ var Uniform$2 = "Uniforme";
2403
+ var Casual$1 = "Manuscrite";
2404
+ var Script$1 = "Scripte";
2405
+ var Reset$2 = "Réinitialiser";
2406
+ var Done$2 = "Terminé";
2407
+ var Color$1 = "Couleur";
2408
+ var Opacity$1 = "Opacité";
2409
+ var fr$1 = {
2410
+ "Audio Player": "Lecteur audio",
2411
+ "Video Player": "Lecteur vidéo",
2412
+ Play: Play$2,
2413
+ Pause: Pause$2,
2414
+ Replay: Replay$2,
2415
+ "Current Time": "Temps actuel",
2416
+ Duration: Duration$2,
2417
+ "Remaining Time": "Temps restant",
2418
+ "Stream Type": "Type de flux",
2419
+ LIVE: LIVE$2,
2420
+ "Seek to live, currently behind live": "Rechercher le direct, actuellement après le direct",
2421
+ "Seek to live, currently playing live": "Rechercher le direct, le direct actuellement en cours de lecture",
2422
+ Loaded: Loaded$2,
2423
+ Progress: Progress$2,
2424
+ "Progress Bar": "Barre de progression",
2425
+ "progress bar timing: currentTime={1} duration={2}": "{1} de {2}",
2426
+ Fullscreen: Fullscreen$2,
2427
+ "Exit Fullscreen": "Fenêtré",
2428
+ Mute: Mute$2,
2429
+ Unmute: Unmute$2,
2430
+ "Playback Rate": "Vitesse de lecture",
2431
+ Subtitles: Subtitles$2,
2432
+ "subtitles off": "Sous-titres désactivés",
2433
+ Captions: Captions$2,
2434
+ "captions off": "Sous-titres transcrits désactivés",
2435
+ Chapters: Chapters$2,
2436
+ Descriptions: Descriptions$2,
2437
+ "descriptions off": "descriptions désactivées",
2438
+ "Audio Track": "Piste audio",
2439
+ "Volume Level": "Niveau de volume",
2440
+ "You aborted the media playback": "Vous avez interrompu la lecture de la vidéo.",
2441
+ "A network error caused the media download to fail part-way.": "Une erreur de réseau a interrompu le téléchargement de la vidéo.",
2442
+ "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.",
2443
+ "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non prises en charge par votre navigateur.",
2444
+ "No compatible source was found for this media.": "Aucune source compatible n'a été trouvée pour cette vidéo.",
2445
+ "The media is encrypted and we do not have the keys to decrypt it.": "Le média est chiffré et nous n'avons pas les clés pour le déchiffrer.",
2446
+ "Play Video": "Lire la vidéo",
2447
+ Close: Close$2,
2448
+ "Close Modal Dialog": "Fermer la boîte de dialogue modale",
2449
+ "Modal Window": "Fenêtre modale",
2450
+ "This is a modal window": "Ceci est une fenêtre modale",
2451
+ "This modal can be closed by pressing the Escape key or activating the close button.": "Ce modal peut être fermé en appuyant sur la touche Échap ou activer le bouton de fermeture.",
2452
+ ", opens captions settings dialog": ", ouvrir les paramètres des sous-titres transcrits",
2453
+ ", opens subtitles settings dialog": ", ouvrir les paramètres des sous-titres",
2454
+ ", opens descriptions settings dialog": ", ouvrir les paramètres des descriptions",
2455
+ ", selected": ", sélectionné",
2456
+ "captions settings": "Paramètres des sous-titres transcrits",
2457
+ "subtitles settings": "Paramètres des sous-titres",
2458
+ "descriptions settings": "Paramètres des descriptions",
2459
+ Text: Text$2,
2460
+ White: White$2,
2461
+ Black: Black$2,
2462
+ Red: Red$2,
2463
+ Green: Green$2,
2464
+ Blue: Blue$2,
2465
+ Yellow: Yellow$2,
2466
+ Magenta: Magenta$2,
2467
+ Cyan: Cyan$2,
2468
+ Background: Background$2,
2469
+ Window: Window$2,
2470
+ Transparent: Transparent$2,
2471
+ "Semi-Transparent": "Semi-transparent",
2472
+ Opaque: Opaque$2,
2473
+ "Font Size": "Taille des caractères",
2474
+ "Text Edge Style": "Style des contours du texte",
2475
+ None: None$2,
2476
+ Raised: Raised$1,
2477
+ Depressed: Depressed$1,
2478
+ Uniform: Uniform$2,
2479
+ "Drop shadow": "Ombre portée",
2480
+ "Font Family": "Famille de polices",
2481
+ "Proportional Sans-Serif": "Polices à chasse variable sans empattement (Proportional Sans-Serif)",
2482
+ "Monospace Sans-Serif": "Polices à chasse fixe sans empattement (Monospace Sans-Serif)",
2483
+ "Proportional Serif": "Polices à chasse variable avec empattement (Proportional Serif)",
2484
+ "Monospace Serif": "Polices à chasse fixe avec empattement (Monospace Serif)",
2485
+ Casual: Casual$1,
2486
+ Script: Script$1,
2487
+ "Small Caps": "Petites capitales",
2488
+ Reset: Reset$2,
2489
+ "restore all settings to the default values": "Restaurer tous les paramètres aux valeurs par défaut",
2490
+ Done: Done$2,
2491
+ "Caption Settings Dialog": "Boîte de dialogue des paramètres des sous-titres transcrits",
2492
+ "Beginning of dialog window. Escape will cancel and close the window.": "Début de la fenêtre de dialogue. La touche d'échappement annulera et fermera la fenêtre.",
2493
+ "End of dialog window.": "Fin de la fenêtre de dialogue.",
2494
+ "Exit Picture-in-Picture": "Quitter le mode image dans l'image",
2495
+ "Picture-in-Picture": "Image dans l'image",
2496
+ "{1} is loading.": "{1} en cours de chargement.",
2497
+ "No content": "Aucun contenu",
2498
+ Color: Color$1,
2499
+ Opacity: Opacity$1,
2500
+ "Text Background": "Arrière-plan du texte",
2501
+ "Caption Area Background": "Arrière-plan de la zone de sous-titre",
2502
+ "Skip backward {1} seconds": "Reculer de {1} secondes",
2503
+ "Skip forward {1} seconds": "Avancer de {1} secondes"
2504
+ };
2505
+
2506
+ var vjsLang$1 = /*#__PURE__*/Object.freeze({
2507
+ __proto__: null,
2508
+ Background: Background$2,
2509
+ Black: Black$2,
2510
+ Blue: Blue$2,
2511
+ Captions: Captions$2,
2512
+ Casual: Casual$1,
2513
+ Chapters: Chapters$2,
2514
+ Close: Close$2,
2515
+ Color: Color$1,
2516
+ Cyan: Cyan$2,
2517
+ Depressed: Depressed$1,
2518
+ Descriptions: Descriptions$2,
2519
+ Done: Done$2,
2520
+ Duration: Duration$2,
2521
+ Fullscreen: Fullscreen$2,
2522
+ Green: Green$2,
2523
+ LIVE: LIVE$2,
2524
+ Loaded: Loaded$2,
2525
+ Magenta: Magenta$2,
2526
+ Mute: Mute$2,
2527
+ None: None$2,
2528
+ Opacity: Opacity$1,
2529
+ Opaque: Opaque$2,
2530
+ Pause: Pause$2,
2531
+ Play: Play$2,
2532
+ Progress: Progress$2,
2533
+ Raised: Raised$1,
2534
+ Red: Red$2,
2535
+ Replay: Replay$2,
2536
+ Reset: Reset$2,
2537
+ Script: Script$1,
2538
+ Subtitles: Subtitles$2,
2539
+ Text: Text$2,
2540
+ Transparent: Transparent$2,
2541
+ Uniform: Uniform$2,
2542
+ Unmute: Unmute$2,
2543
+ White: White$2,
2544
+ Window: Window$2,
2545
+ Yellow: Yellow$2,
2546
+ default: fr$1
2547
+ });
2548
+
2549
+ var AGERATING12$2 = "Pour protéger les enfants, ce contenu est accessible entre 20h et 6h.";
2550
+ var AGERATING18$2 = "Pour protéger les enfants, ce contenu est accessible entre 23h et 5h.";
2551
+ var COMMERCIAL$2 = "Ce contenu n'est actuellement pas disponible.";
2552
+ var ENDDATE$2 = "Ce contenu n'est plus disponible.";
2553
+ var GEOBLOCK$2 = "La RTS ne dispose pas des droits de diffusion en dehors de la Suisse.";
2554
+ var LEGAL$2 = "Pour des raisons juridiques, ce contenu n'est pas disponible.";
2555
+ var STARTDATE$2 = "Ce contenu n'est pas encore disponible. Veuillez réessayer plus tard.";
2556
+ var UNKNOWN$2 = "Ce contenu n'est actuellement pas disponible.";
2557
+ var fr = {
2558
+ AGERATING12: AGERATING12$2,
2559
+ AGERATING18: AGERATING18$2,
2560
+ COMMERCIAL: COMMERCIAL$2,
2561
+ ENDDATE: ENDDATE$2,
2562
+ GEOBLOCK: GEOBLOCK$2,
2563
+ LEGAL: LEGAL$2,
2564
+ STARTDATE: STARTDATE$2,
2565
+ UNKNOWN: UNKNOWN$2
2566
+ };
2567
+
2568
+ var pillarboxLang$2 = /*#__PURE__*/Object.freeze({
2569
+ __proto__: null,
2570
+ AGERATING12: AGERATING12$2,
2571
+ AGERATING18: AGERATING18$2,
2572
+ COMMERCIAL: COMMERCIAL$2,
2573
+ ENDDATE: ENDDATE$2,
2574
+ GEOBLOCK: GEOBLOCK$2,
2575
+ LEGAL: LEGAL$2,
2576
+ STARTDATE: STARTDATE$2,
2577
+ UNKNOWN: UNKNOWN$2,
2578
+ default: fr
2579
+ });
2580
+
2581
+ pillarbox.addLanguage('fr', _objectSpread2(_objectSpread2({}, vjsLang$1), pillarboxLang$2));
2582
+
2583
+ var Play$1 = "Play";
2584
+ var Pause$1 = "Pausa";
2585
+ var Replay$1 = "Replay";
2586
+ var Duration$1 = "Durata";
2587
+ var LIVE$1 = "LIVE";
2588
+ var Loaded$1 = "Caricato";
2589
+ var Progress$1 = "Stato";
2590
+ var Fullscreen$1 = "Schermo intero";
2591
+ var Mute$1 = "Disattiva l’audio";
2592
+ var Unmute$1 = "Attiva l’audio";
2593
+ var Subtitles$1 = "Sottotitoli";
2594
+ var Captions$1 = "Sottotitoli non udenti";
2595
+ var Chapters$1 = "Capitolo";
2596
+ var Descriptions$1 = "Descrizioni";
2597
+ var Close$1 = "Chiudi";
2598
+ var Text$1 = "Testo";
2599
+ var White$1 = "Bianco";
2600
+ var Black$1 = "Nero";
2601
+ var Red$1 = "Rosso";
2602
+ var Green$1 = "Verde";
2603
+ var Blue$1 = "Blu";
2604
+ var Yellow$1 = "Giallo";
2605
+ var Magenta$1 = "Magenta";
2606
+ var Cyan$1 = "Ciano";
2607
+ var Background$1 = "Sfondo";
2608
+ var Window$1 = "Finestra";
2609
+ var Transparent$1 = "Trasparente";
2610
+ var Opaque$1 = "Opaco";
2611
+ var None$1 = "Nessuno";
2612
+ var Uniform$1 = "Uniforme";
2613
+ var Reset$1 = "Reinizializza";
2614
+ var Done$1 = "Fatto";
2615
+ var Color = "Colore";
2616
+ var Opacity = "Opacità";
2617
+ var it$1 = {
2618
+ "Audio Player": "Lettore audio",
2619
+ "Video Player": "Lettore video",
2620
+ Play: Play$1,
2621
+ Pause: Pause$1,
2622
+ Replay: Replay$1,
2623
+ "Current Time": "Orario attuale",
2624
+ Duration: Duration$1,
2625
+ "Remaining Time": "Tempo rimanente",
2626
+ "Stream Type": "Tipo di streaming",
2627
+ LIVE: LIVE$1,
2628
+ Loaded: Loaded$1,
2629
+ Progress: Progress$1,
2630
+ "Progress Bar": "Barra di avanzamento",
2631
+ "progress bar timing: currentTime={1} duration={2}": "{1} di {2}",
2632
+ Fullscreen: Fullscreen$1,
2633
+ "Exit Fullscreen": "Chiudi Schermo intero",
2634
+ Mute: Mute$1,
2635
+ Unmute: Unmute$1,
2636
+ "Playback Rate": "Velocità di riproduzione",
2637
+ Subtitles: Subtitles$1,
2638
+ "subtitles off": "Senza sottotitoli",
2639
+ Captions: Captions$1,
2640
+ "captions off": "Senza sottotitoli non udenti",
2641
+ Chapters: Chapters$1,
2642
+ Descriptions: Descriptions$1,
2643
+ "descriptions off": "Descrizioni disattivate",
2644
+ "Audio Track": "Traccia audio",
2645
+ "Volume Level": "Livello del volume",
2646
+ "You aborted the media playback": "La riproduzione del contenuto multimediale è stata interrotta.",
2647
+ "A network error caused the media download to fail part-way.": "Il download del contenuto multimediale è stato interrotto a causa di un problema rete.",
2648
+ "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Il contenuto multimediale non può essere caricato a causa di un errore nel server o nella rete o perché il formato non viene supportato.",
2649
+ "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La riproduzione del contenuto multimediale è stata interrotta a causa di un file danneggiato o per l’utilizzo di impostazioni non supportate dal browser.",
2650
+ "No compatible source was found for this media.": "Non ci sono fonti compatibili per questo contenuto multimediale.",
2651
+ "The media is encrypted and we do not have the keys to decrypt it.": "Il contenuto multimediale è criptato e non disponiamo delle chiavi per decifrarlo.",
2652
+ "Play Video": "Riproduci il video",
2653
+ Close: Close$1,
2654
+ "Close Modal Dialog": "Chiudi la finestra di dialogo",
2655
+ "Modal Window": "Finestra di dialogo",
2656
+ "This is a modal window": "Questa è una finestra di dialogo",
2657
+ "This modal can be closed by pressing the Escape key or activating the close button.": "Questa finestra di dialogo può essere chiusa premendo sul tasto Esc o attivando il pulsante di chiusura.",
2658
+ ", opens captions settings dialog": ", aprire i parametri della trascrizione dei sottotitoli",
2659
+ ", opens subtitles settings dialog": ", aprire i parametri dei sottotitoli",
2660
+ ", opens descriptions settings dialog": ", aprire i parametri delle descrizioni",
2661
+ ", selected": ", selezionato",
2662
+ "captions settings": "Parametri sottotitoli non udenti",
2663
+ "subtitles settings": "Parametri sottotitoli",
2664
+ "descriptions settings": "Parametri descrizioni",
2665
+ Text: Text$1,
2666
+ White: White$1,
2667
+ Black: Black$1,
2668
+ Red: Red$1,
2669
+ Green: Green$1,
2670
+ Blue: Blue$1,
2671
+ Yellow: Yellow$1,
2672
+ Magenta: Magenta$1,
2673
+ Cyan: Cyan$1,
2674
+ Background: Background$1,
2675
+ Window: Window$1,
2676
+ Transparent: Transparent$1,
2677
+ "Semi-Transparent": "Semi-Trasparente",
2678
+ Opaque: Opaque$1,
2679
+ "Font Size": "Dimensione dei caratteri",
2680
+ "Text Edge Style": "Stile dei bordi del testo",
2681
+ None: None$1,
2682
+ Uniform: Uniform$1,
2683
+ "Drop shadow": "Ombra",
2684
+ "Font Family": "Carattere",
2685
+ "Proportional Sans-Serif": "Sans-Serif proporzionale",
2686
+ "Monospace Sans-Serif": "Sans-Serif monospaziato",
2687
+ "Proportional Serif": "Serif proporzionale",
2688
+ "Monospace Serif": "Serif monospaziato",
2689
+ "Small Caps": "Maiuscoletto",
2690
+ Reset: Reset$1,
2691
+ "restore all settings to the default values": "Ripristina i valori predefiniti per tutti i parametri",
2692
+ Done: Done$1,
2693
+ "Caption Settings Dialog": "Finestra di dialogo dei parametri della trascrizione dei sottotitoli",
2694
+ "Beginning of dialog window. Escape will cancel and close the window.": "Inizio della finestra di dialogo. Il tasto Esc annullerà l’operazione e chiuderà la finestra.",
2695
+ "End of dialog window.": "Fine della finestra di dialogo.",
2696
+ "{1} is loading.": "{1} in fase di caricamento.",
2697
+ "Exit Picture-in-Picture": "Esci dalla modalità Picture-in-Picture",
2698
+ "Picture-in-Picture": "Picture-in-Picture",
2699
+ Color: Color,
2700
+ Opacity: Opacity,
2701
+ "Text Background": "Sfondo testo",
2702
+ "Caption Area Background": "Sfondo area sottotitoli",
2703
+ "Skip forward {1} seconds": "Avanti {1} secondi",
2704
+ "Skip backward {1} seconds": "Indietro {1} secondi"
2705
+ };
2706
+
2707
+ var vjsLang = /*#__PURE__*/Object.freeze({
2708
+ __proto__: null,
2709
+ Background: Background$1,
2710
+ Black: Black$1,
2711
+ Blue: Blue$1,
2712
+ Captions: Captions$1,
2713
+ Chapters: Chapters$1,
2714
+ Close: Close$1,
2715
+ Color: Color,
2716
+ Cyan: Cyan$1,
2717
+ Descriptions: Descriptions$1,
2718
+ Done: Done$1,
2719
+ Duration: Duration$1,
2720
+ Fullscreen: Fullscreen$1,
2721
+ Green: Green$1,
2722
+ LIVE: LIVE$1,
2723
+ Loaded: Loaded$1,
2724
+ Magenta: Magenta$1,
2725
+ Mute: Mute$1,
2726
+ None: None$1,
2727
+ Opacity: Opacity,
2728
+ Opaque: Opaque$1,
2729
+ Pause: Pause$1,
2730
+ Play: Play$1,
2731
+ Progress: Progress$1,
2732
+ Red: Red$1,
2733
+ Replay: Replay$1,
2734
+ Reset: Reset$1,
2735
+ Subtitles: Subtitles$1,
2736
+ Text: Text$1,
2737
+ Transparent: Transparent$1,
2738
+ Uniform: Uniform$1,
2739
+ Unmute: Unmute$1,
2740
+ White: White$1,
2741
+ Window: Window$1,
2742
+ Yellow: Yellow$1,
2743
+ default: it$1
2744
+ });
2745
+
2746
+ var AGERATING12$1 = "Per proteggere i bambini, questo media è disponibile solo fra le 20 e le 6.";
2747
+ var AGERATING18$1 = "Per proteggere i bambini, questo media è disponibile solo fra le 23 le 5.";
2748
+ var COMMERCIAL$1 = "Questo contenuto commerciale non è disponibile.";
2749
+ var ENDDATE$1 = "Questo media non è più disponibile.";
2750
+ var GEOBLOCK$1 = "Questo media non è disponibile fuori dalla Svizzera.";
2751
+ var LEGAL$1 = "Il contenuto non è fruibile a causa di restrizioni legali.";
2752
+ var STARTDATE$1 = "Il contenuto non è ancora disponibile. Per cortesia prova più tardi.";
2753
+ var UNKNOWN$1 = "Questo media non è disponibile.";
2754
+ var it = {
2755
+ AGERATING12: AGERATING12$1,
2756
+ AGERATING18: AGERATING18$1,
2757
+ COMMERCIAL: COMMERCIAL$1,
2758
+ ENDDATE: ENDDATE$1,
2759
+ GEOBLOCK: GEOBLOCK$1,
2760
+ LEGAL: LEGAL$1,
2761
+ STARTDATE: STARTDATE$1,
2762
+ UNKNOWN: UNKNOWN$1
2763
+ };
2764
+
2765
+ var pillarboxLang$1 = /*#__PURE__*/Object.freeze({
2766
+ __proto__: null,
2767
+ AGERATING12: AGERATING12$1,
2768
+ AGERATING18: AGERATING18$1,
2769
+ COMMERCIAL: COMMERCIAL$1,
2770
+ ENDDATE: ENDDATE$1,
2771
+ GEOBLOCK: GEOBLOCK$1,
2772
+ LEGAL: LEGAL$1,
2773
+ STARTDATE: STARTDATE$1,
2774
+ UNKNOWN: UNKNOWN$1,
2775
+ default: it
2776
+ });
2777
+
2778
+ pillarbox.addLanguage('it', _objectSpread2(_objectSpread2({}, vjsLang), pillarboxLang$1));
2779
+
2780
+ var Play = "Laschar ir";
2781
+ var Pause = "Pausa";
2782
+ var Replay = "Mussar danovamain";
2783
+ var Duration = "Durada";
2784
+ var LIVE = "LIVE";
2785
+ var Loaded = "Chargià";
2786
+ var Progress = "Progress";
2787
+ var Fullscreen = "Entir visur";
2788
+ var Mute = "Senza tun";
2789
+ var Unmute = "Cun tun";
2790
+ var Subtitles = "Suttitels";
2791
+ var Captions = "Suttitels";
2792
+ var Chapters = "Chapitels";
2793
+ var Descriptions = "Descripziuns";
2794
+ var Close = "Serrar";
2795
+ var Text = "Text";
2796
+ var White = "Alv";
2797
+ var Black = "Nair";
2798
+ var Red = "Cotschn";
2799
+ var Green = "Verd";
2800
+ var Blue = "Blau";
2801
+ var Yellow = "Mellen";
2802
+ var Magenta = "Magenta";
2803
+ var Cyan = "Cyan";
2804
+ var Background = "Fund";
2805
+ var Window = "Fanestra";
2806
+ var Transparent = "Transparent";
2807
+ var Opaque = "Betg transparent";
2808
+ var None = "Nagin";
2809
+ var Raised = "Auzà";
2810
+ var Depressed = "Sbassà";
2811
+ var Uniform = "Uniform";
2812
+ var Dropshadow = "Sumbriva";
2813
+ var Casual = "Casual";
2814
+ var Script = "Script";
2815
+ var Reset = "Da nov";
2816
+ var Done = "Fatg";
2817
+ var AGERATING12 = "Per proteger uffants, è quest cuntegn disponibel mo tranter las 20.00 e las 06.00.";
2818
+ var AGERATING18 = "Per proteger uffants, è quest cuntegn disponibel mo tranter las 23.00 e las 05.00.";
2819
+ var COMMERCIAL = "Quest medium commerzial n'è betg disponibel.";
2820
+ var ENDDATE = "Quest cuntegn n'è betg pli disponibel.";
2821
+ var GEOBLOCK = "Quest cuntegn n'è betg disponibel ordaifer la Svizra.";
2822
+ var LEGAL = "Quest cuntegn n'è betg disponibel perquai ch'el è scadì.";
2823
+ var STARTDATE = "Quest cuntegn n'è betg anc disponibel. Empruvai pli tard.";
2824
+ var UNKNOWN = "Quest cuntegn n'è betg disponibel.";
2825
+ var rm = {
2826
+ "Audio Player": "Audio-Player",
2827
+ "Video Player": "Video-Player",
2828
+ Play: Play,
2829
+ Pause: Pause,
2830
+ Replay: Replay,
2831
+ "Current Time": "Temp actual",
2832
+ Duration: Duration,
2833
+ "Remaining Time": "Temp restant",
2834
+ "Stream Type": "Tip dal stream",
2835
+ LIVE: LIVE,
2836
+ Loaded: Loaded,
2837
+ Progress: Progress,
2838
+ "Progress Bar": "Bar da progessiun",
2839
+ "progress bar timing: currentTime={1} duration={2}": "{1} da {2}",
2840
+ Fullscreen: Fullscreen,
2841
+ "Non-Fullscreen": "Betg entir visur",
2842
+ Mute: Mute,
2843
+ Unmute: Unmute,
2844
+ "Playback Rate": "Tempo ",
2845
+ Subtitles: Subtitles,
2846
+ "subtitles off": "senza suttitels",
2847
+ Captions: Captions,
2848
+ "captions off": "senza suttitels",
2849
+ Chapters: Chapters,
2850
+ Descriptions: Descriptions,
2851
+ "descriptions off": "senza descripziuns",
2852
+ "Audio Track": "Piese audio",
2853
+ "Volume Level": "Nivel dal volumen",
2854
+ "You aborted the media playback": "Vus avais interrut il vdieo",
2855
+ "A network error caused the media download to fail part-way.": "In sbagl en la rait ha impedì il download",
2856
+ "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Il video n'è betg chargià - ubain per in sbagl da server / da la rait, ubain ch'il format n'è betg cumpatibel.",
2857
+ "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Il video è interrut: Ubain ch'il video è donnegià, ubain che funcziuns n'èn betg cumpatiblas.",
2858
+ "No compatible source was found for this media.": "Chattà nagina funtauna cumpatibla per quest video.",
2859
+ "The media is encrypted and we do not have the keys to decrypt it.": "Il video è codifitgà da moda nunenconuschenta.",
2860
+ "Play Video": "Aviar video",
2861
+ Close: Close,
2862
+ "Close Modal Dialog": "Serrar la fanestra modala",
2863
+ "Modal Window": "Fanestra modala",
2864
+ "This is a modal window": "Quai è ina fanestra modala",
2865
+ "This modal can be closed by pressing the Escape key or activating the close button.": "Questa fanestra modala pudais serrar cun la tasta \"Escape\" ubain cun il buttun.",
2866
+ ", opens captions settings dialog": ", avra opziuns per ils suttitels",
2867
+ ", opens subtitles settings dialog": ", avra opziuns per ils suttitels",
2868
+ ", opens descriptions settings dialog": ", avra opziuns per la descripziun",
2869
+ ", selected": ", selecziunà",
2870
+ "captions settings": "opziuns per ils suttitels",
2871
+ "subtitles settings": "opziuns per ils suttitels",
2872
+ "descriptions settings": "opziuns per la descripziun",
2873
+ Text: Text,
2874
+ White: White,
2875
+ Black: Black,
2876
+ Red: Red,
2877
+ Green: Green,
2878
+ Blue: Blue,
2879
+ Yellow: Yellow,
2880
+ Magenta: Magenta,
2881
+ Cyan: Cyan,
2882
+ Background: Background,
2883
+ Window: Window,
2884
+ Transparent: Transparent,
2885
+ "Semi-Transparent": "Mez transparent",
2886
+ Opaque: Opaque,
2887
+ "Font Size": "Grandezza dal text",
2888
+ "Text Edge Style": "Stil dal text",
2889
+ None: None,
2890
+ Raised: Raised,
2891
+ Depressed: Depressed,
2892
+ Uniform: Uniform,
2893
+ Dropshadow: Dropshadow,
2894
+ "Font Family": "Scrittira",
2895
+ "Proportional Sans-Serif": "Proportionale Sans-Serif",
2896
+ "Monospace Sans-Serif": "Monospace Sans-Serif",
2897
+ "Proportional Serif": "Proportionale Serif",
2898
+ "Monospace Serif": "Monospace Serif",
2899
+ Casual: Casual,
2900
+ Script: Script,
2901
+ "Small Caps": "Bustabs pitschens",
2902
+ Reset: Reset,
2903
+ "restore all settings to the default values": "Enavos tar las opziuns da standard",
2904
+ Done: Done,
2905
+ "Caption Settings Dialog": "Opziuns per suttitels",
2906
+ "Beginning of dialog window. Escape will cancel and close the window.": "Entschatta da la fanestra da dialog. Escape stizza e serra la fanestra.",
2907
+ "End of dialog window.": "Fin da la fanestra da dialog.",
2908
+ AGERATING12: AGERATING12,
2909
+ AGERATING18: AGERATING18,
2910
+ COMMERCIAL: COMMERCIAL,
2911
+ ENDDATE: ENDDATE,
2912
+ GEOBLOCK: GEOBLOCK,
2913
+ LEGAL: LEGAL,
2914
+ STARTDATE: STARTDATE,
2915
+ UNKNOWN: UNKNOWN
2916
+ };
2917
+
2918
+ var pillarboxLang = /*#__PURE__*/Object.freeze({
2919
+ __proto__: null,
2920
+ AGERATING12: AGERATING12,
2921
+ AGERATING18: AGERATING18,
2922
+ Background: Background,
2923
+ Black: Black,
2924
+ Blue: Blue,
2925
+ COMMERCIAL: COMMERCIAL,
2926
+ Captions: Captions,
2927
+ Casual: Casual,
2928
+ Chapters: Chapters,
2929
+ Close: Close,
2930
+ Cyan: Cyan,
2931
+ Depressed: Depressed,
2932
+ Descriptions: Descriptions,
2933
+ Done: Done,
2934
+ Dropshadow: Dropshadow,
2935
+ Duration: Duration,
2936
+ ENDDATE: ENDDATE,
2937
+ Fullscreen: Fullscreen,
2938
+ GEOBLOCK: GEOBLOCK,
2939
+ Green: Green,
2940
+ LEGAL: LEGAL,
2941
+ LIVE: LIVE,
2942
+ Loaded: Loaded,
2943
+ Magenta: Magenta,
2944
+ Mute: Mute,
2945
+ None: None,
2946
+ Opaque: Opaque,
2947
+ Pause: Pause,
2948
+ Play: Play,
2949
+ Progress: Progress,
2950
+ Raised: Raised,
2951
+ Red: Red,
2952
+ Replay: Replay,
2953
+ Reset: Reset,
2954
+ STARTDATE: STARTDATE,
2955
+ Script: Script,
2956
+ Subtitles: Subtitles,
2957
+ Text: Text,
2958
+ Transparent: Transparent,
2959
+ UNKNOWN: UNKNOWN,
2960
+ Uniform: Uniform,
2961
+ Unmute: Unmute,
2962
+ White: White,
2963
+ Window: Window,
2964
+ Yellow: Yellow,
2965
+ default: rm
2966
+ });
2967
+
2968
+ pillarbox.addLanguage('rm', _objectSpread2({}, pillarboxLang));
2969
+
2970
+ const _excluded = ["url", "mimeType", "keySystems"],
2971
+ _excluded2 = ["src"];
2972
+
2973
+ /**
2974
+ * @class SrgSsr
2975
+ */
2976
+ class SrgSsr {
2977
+ /**
2978
+ * Adds blocked segments to the player.
2979
+ *
2980
+ * @param {import('video.js/dist/types/player').default} player
2981
+ * @param {Array<import('../dataProvider/model/typedef').Segment>} [segments=[]]
2982
+ */
2983
+ static addBlockedSegments(player, segments = []) {
2984
+ const trackId = 'srgssr-blocked-segments';
2985
+ const removeTrack = player.textTracks().getTrackById(trackId);
2986
+ if (removeTrack) {
2987
+ player.textTracks().removeTrack(removeTrack);
2988
+ }
2989
+ if (!Array.isArray(segments) || !segments.length) return;
2990
+ const blockedSegments = segments.filter(segment => segment.blockReason);
2991
+ if (!blockedSegments.length) return;
2992
+ SrgSsr.createTextTrack(player, trackId).then(segmentTrack => {
2993
+ blockedSegments.forEach(segment => {
2994
+ SrgSsr.addTextTrackCue(segmentTrack, segment);
2995
+ });
2996
+ player.textTracks().addTrack(segmentTrack);
2997
+ });
2998
+ }
2999
+
3000
+ /**
3001
+ * Adds remote text tracks from an array of subtitles.
3002
+ *
3003
+ * @param {import('video.js/dist/types/player').default} player
3004
+ * @param {Array<import('../dataProvider/model/typedef').Subtitle>} [subtitles=[]]
3005
+ */
3006
+ static addRemoteTextTracks(player, subtitles = []) {
3007
+ if (!Array.isArray(subtitles)) return;
3008
+ subtitles.forEach(({
3009
+ type,
3010
+ language: label,
3011
+ locale: language,
3012
+ url: src
3013
+ }) => {
3014
+ player.addRemoteTextTrack({
3015
+ kind: type === 'SDH' ? 'captions' : 'subtitles',
3016
+ label,
3017
+ language,
3018
+ src
3019
+ });
3020
+ });
3021
+ }
3022
+
3023
+ /**
3024
+ * Add a new cue to a text track with the given data.
3025
+ *
3026
+ * @param {TextTrack} textTrack
3027
+ * @param {
3028
+ * import('../dataProvider/model/typedef').Segment |
3029
+ * import('../dataProvider/model/typedef').Chapter |
3030
+ * import('../dataProvider/model/typedef').TimeInterval
3031
+ * } data SRG SSR's cue-like representation
3032
+ */
3033
+ static addTextTrackCue(textTrack, data) {
3034
+ const startTime = (Number.isFinite(data.markIn) ? data.markIn : data.fullLengthMarkIn) / 1000;
3035
+ const endTime = (Number.isFinite(data.markOut) ? data.markOut : data.fullLengthMarkOut) / 1000;
3036
+ textTrack.addCue(new VTTCue(startTime, endTime, JSON.stringify(data)));
3037
+ }
3038
+
3039
+ /**
3040
+ * Add multiple text tracks to the player.
3041
+ *
3042
+ * @param {import('video.js/dist/types/player').default} player
3043
+ * @param {import('./typedef').ComposedSrcMediaData} srcMediaObj
3044
+ */
3045
+ static addTextTracks(player, {
3046
+ mediaData
3047
+ }) {
3048
+ SrgSsr.addRemoteTextTracks(player, mediaData.subtitles);
3049
+ SrgSsr.addChapters(player, mediaData.urn, mediaData.chapters);
3050
+ SrgSsr.addBlockedSegments(player, mediaData.blockedSegments);
3051
+ SrgSsr.addIntervals(player, mediaData.intervals);
3052
+ }
3053
+
3054
+ /**
3055
+ * Adds chapters to the player.
3056
+ *
3057
+ * @param {import('video.js/dist/types/player').default} player
3058
+ * @param {string} chapterUrn The URN of the main chapter.
3059
+ * @param {Array.<import('../dataProvider/model/typedef').Chapter>} [chapters=[]]
3060
+ */
3061
+ static addChapters(player, chapterUrn, chapters = []) {
3062
+ const trackId = 'srgssr-chapters';
3063
+ const removeTrack = player.textTracks().getTrackById(trackId);
3064
+ if (removeTrack) {
3065
+ player.textTracks().removeTrack(removeTrack);
3066
+ }
3067
+ if (!Array.isArray(chapters) || !chapters.length) return;
3068
+ SrgSsr.createTextTrack(player, trackId).then(chapterTrack => {
3069
+ chapters.forEach(chapter => {
3070
+ if (chapterUrn !== chapter.fullLengthUrn) return;
3071
+ SrgSsr.addTextTrackCue(chapterTrack, chapter);
3072
+ });
3073
+ player.textTracks().addTrack(chapterTrack);
3074
+ });
3075
+ }
3076
+
3077
+ /**
3078
+ * Adds intervals to the player.
3079
+ *
3080
+ * @param {import('video.js/dist/types/player').default} player
3081
+ * @param {Array.<import('../dataProvider/model/typedef').TimeInterval>} [intervals=[]]
3082
+ */
3083
+ static addIntervals(player, intervals = []) {
3084
+ const trackId = 'srgssr-intervals';
3085
+ const removeTrack = player.textTracks().getTrackById(trackId);
3086
+ if (removeTrack) {
3087
+ player.textTracks().removeTrack(removeTrack);
3088
+ }
3089
+ if (!Array.isArray(intervals) || !intervals.length) return;
3090
+ SrgSsr.createTextTrack(player, trackId).then(intervalTrack => {
3091
+ intervals.forEach(interval => {
3092
+ SrgSsr.addTextTrackCue(intervalTrack, interval);
3093
+ });
3094
+ player.textTracks().addTrack(intervalTrack);
3095
+ });
3096
+ }
3097
+
3098
+ /**
3099
+ * Set a blocking reason according to the block reason returned
3100
+ * by mediaData.
3101
+ *
3102
+ * @param {import('video.js/dist/types/player').default} player
3103
+ * @param {import('./typedef').ComposedSrcMediaData} srcMediaObj
3104
+ *
3105
+ * @returns {undefined|Boolean}
3106
+ */
3107
+ static blockingReason(player, srcMediaObj) {
3108
+ if (!srcMediaObj.mediaData.blockReason) return;
3109
+ const message = player.localize(srcMediaObj.mediaData.blockReason);
3110
+ SrgSsr.error(player, {
3111
+ code: MediaError.MEDIA_ERR_ABORTED,
3112
+ message,
3113
+ metadata: {
3114
+ errorType: srcMediaObj.mediaData.blockReason,
3115
+ src: srcMediaObj
3116
+ }
3117
+ });
3118
+ return true;
3119
+ }
3120
+
3121
+ /**
3122
+ * Add the Akamai token to all resources
3123
+ * if at least one of them has tokenType
3124
+ * set to Akamai.
3125
+ *
3126
+ * @param {Array.<import('./typedef').MainResourceWithKeySystems>} resources
3127
+ *
3128
+ * @returns {Promise<Array.<import('./typedef').MainResourceWithKeySystems>>}
3129
+ */
3130
+ static composeAkamaiResources(resources = []) {
3131
+ return _asyncToGenerator(function* () {
3132
+ if (!AkamaiTokenService.hasToken(resources)) {
3133
+ return Promise.resolve(resources);
3134
+ }
3135
+
3136
+ // TODO allow to modify the Akamai URL
3137
+ return AkamaiTokenService.tokenizeSources(resources);
3138
+ })();
3139
+ }
3140
+
3141
+ /**
3142
+ * Add the keySystems property to all resources
3143
+ * if at least one of them has DRM.
3144
+ *
3145
+ * @param {Array.<import('../dataProvider/model/typedef').MainResource>} resources
3146
+ *
3147
+ * @returns {Array.<import('./typedef').MainResourceWithKeySystems>}
3148
+ */
3149
+ static composeKeySystemsResources(resources = []) {
3150
+ if (!Drm.hasDrm(resources)) ;
3151
+ return resources.map(resource => _objectSpread2(_objectSpread2({}, resource), Drm.buildKeySystems(resource.drmList)));
3152
+ }
3153
+
3154
+ /**
3155
+ * Get the main resources from a mediaComposition.
3156
+ * May add an Akamai token or key systems if required by the resource.
3157
+ *
3158
+ * @param {MediaComposition} mediaComposition
3159
+ *
3160
+ * @returns {Promise<Array.<import('./typedef').MainResourceWithKeySystems>>}
3161
+ */
3162
+ static composeMainResources(mediaComposition) {
3163
+ return SrgSsr.composeAkamaiResources(SrgSsr.composeKeySystemsResources(SrgSsr.filterIncompatibleResources(mediaComposition.getMainResources())));
3164
+ }
3165
+
3166
+ /**
3167
+ * Compose source options with media data.
3168
+ * MediaData properties from source options overwrite mediaData from IL.
3169
+ *
3170
+ * @param {any} srcObj
3171
+ * @param {any} srcObj.mediaData overrides or adds metadata to the composed mediaData.
3172
+ * @param {boolean} srcObj.disableTrackers
3173
+ * @param {import('./typedef').MainResourceWithKeySystems} resource
3174
+ *
3175
+ * @returns {import('./typedef').ComposedSrcMediaData}
3176
+ */
3177
+ static composeSrcMediaData({
3178
+ mediaData: srcMediaData,
3179
+ disableTrackers
3180
+ }, resource) {
3181
+ const _Pillarbox$obj$merge = pillarbox.obj.merge(resource, srcMediaData),
3182
+ {
3183
+ url,
3184
+ mimeType,
3185
+ keySystems
3186
+ } = _Pillarbox$obj$merge,
3187
+ mediaData = _objectWithoutProperties(_Pillarbox$obj$merge, _excluded);
3188
+ return {
3189
+ src: url,
3190
+ type: mimeType,
3191
+ keySystems,
3192
+ disableTrackers,
3193
+ mediaData
3194
+ };
3195
+ }
3196
+
3197
+ /**
3198
+ * Create a new metadata text track.
3199
+ *
3200
+ * @param {import('video.js/dist/types/player').default} player
3201
+ * @param {String} trackId Text track unique ID
3202
+ *
3203
+ * @returns {Promise<TextTrack>}
3204
+ */
3205
+ static createTextTrack(player, trackId) {
3206
+ // See https://github.com/videojs/video.js/issues/8519
3207
+ return new Promise(resolve => {
3208
+ setTimeout(() => {
3209
+ resolve(new pillarbox.TextTrack({
3210
+ id: trackId,
3211
+ kind: 'metadata',
3212
+ label: trackId,
3213
+ tech: player.tech(true)
3214
+ }));
3215
+ }, 100);
3216
+ });
3217
+ }
3218
+
3219
+ /**
3220
+ * Proxy SRG SSR chapters and intervals cuechange events at player level.
3221
+ *
3222
+ * @param {import('video.js/dist/types/player').default} player
3223
+ */
3224
+ static cuechangeEventProxy(player) {
3225
+ player.textTracks().on('addtrack', ({
3226
+ track
3227
+ }) => {
3228
+ if (!['srgssr-chapters', 'srgssr-intervals'].includes(track.id)) return;
3229
+ track.on('cuechange', () => {
3230
+ const [cue] = Array.from(track.activeCues);
3231
+ const type = track.id.includes('srgssr-chapters') ? 'srgssr/chapter' : 'srgssr/interval';
3232
+ player.trigger({
3233
+ type,
3234
+ data: cue
3235
+ });
3236
+ });
3237
+ });
3238
+ }
3239
+
3240
+ /**
3241
+ * SRG SSR data provider singleton.
3242
+ *
3243
+ * @param {import('video.js/dist/types/player').default} player
3244
+ *
3245
+ * @returns {DataProvider}
3246
+ */
3247
+ static dataProvider(player) {
3248
+ if (!player.options().srgOptions.dataProvider) {
3249
+ const {
3250
+ dataProviderHost,
3251
+ dataProviderUrlHandler
3252
+ } = player.options().srgOptions;
3253
+ const dataProvider = new DataProvider(dataProviderHost);
3254
+ const requestHandler = dataProvider.handleRequest(dataProviderUrlHandler);
3255
+ player.options({
3256
+ srgOptions: {
3257
+ dataProvider: requestHandler
3258
+ }
3259
+ });
3260
+ }
3261
+ return player.options().srgOptions.dataProvider;
3262
+ }
3263
+
3264
+ /**
3265
+ * Set an error if something goes wrong with the data provider.
3266
+ *
3267
+ * @param {import('video.js/dist/types/player').default} player
3268
+ * @param {Object} error
3269
+ *
3270
+ * @returns {undefined|true}
3271
+ */
3272
+ static dataProviderError(player, error) {
3273
+ if (!error) return;
3274
+ const statusText = error.statusText ? error.statusText : error.message;
3275
+ SrgSsr.error(player, {
3276
+ code: 0,
3277
+ message: player.localize('UNKNOWN'),
3278
+ metadata: {
3279
+ errorType: 'UNKNOWN',
3280
+ urn: player.src(),
3281
+ status: error.status,
3282
+ statusText,
3283
+ url: error.url
3284
+ }
3285
+ });
3286
+ return true;
3287
+ }
3288
+
3289
+ /**
3290
+ * Set player error.
3291
+ *
3292
+ * @param {import('video.js/dist/types/player').default} player
3293
+ * @param {Object} error
3294
+ */
3295
+ static error(player, {
3296
+ code,
3297
+ message,
3298
+ metadata
3299
+ }) {
3300
+ player.error(null);
3301
+ player.error({
3302
+ code,
3303
+ message,
3304
+ metadata
3305
+ });
3306
+ }
3307
+
3308
+ /**
3309
+ * Filter out incompatible resources such as `RTMP` and `HDS`.
3310
+ *
3311
+ * @param {Array.<import('../dataProvider/model/typedef').MainResource>} resources Resources to filter
3312
+ *
3313
+ * @returns {Array.<import('../dataProvider/model/typedef').MainResource>} The filtered resources
3314
+ */
3315
+ static filterIncompatibleResources(resources = []) {
3316
+ return resources.filter(resource => !['RTMP', 'HDS'].includes(resource.streaming));
3317
+ }
3318
+
3319
+ /**
3320
+ * Get the current blocked segment from the player.
3321
+ *
3322
+ * @param {import('video.js/dist/types/player').default} player
3323
+ *
3324
+ * @returns {VTTCue|undefined} The blocked segment cue object, or undefined
3325
+ */
3326
+ static getBlockedSegment(player) {
3327
+ const trackId = 'srgssr-blocked-segments';
3328
+ const blockedSegmentsTrack = player.textTracks().getTrackById(trackId);
3329
+ if (!blockedSegmentsTrack) return;
3330
+
3331
+ /** @type {VTTCue} */
3332
+ const [blockedCue] = Array.from(blockedSegmentsTrack.activeCues);
3333
+ return blockedCue;
3334
+ }
3335
+
3336
+ /**
3337
+ * Get the VTT cue of a blocked segment.
3338
+ *
3339
+ * @param {import('video.js/dist/types/player').default} player
3340
+ * @param {Number} currentTime
3341
+ *
3342
+ * @returns {VTTCue|undefined} The VTT cue of a blocked segment, or undefined
3343
+ */
3344
+ static getBlockedSegmentByTime(player, currentTime) {
3345
+ const blockedSegment = SrgSsr.getBlockedSegment(player);
3346
+ if (!blockedSegment) return;
3347
+ const isBlocked = currentTime >= blockedSegment.startTime && currentTime < blockedSegment.endTime;
3348
+ return isBlocked ? blockedSegment : undefined;
3349
+ }
3350
+
3351
+ /**
3352
+ * Get mediaComposition from an URN.
3353
+ *
3354
+ * @param {String} urn
3355
+ * @param {Function} requestHandler
3356
+ *
3357
+ * @returns {Promise<MediaComposition>}
3358
+ */
3359
+ static getMediaComposition(urn, handleRequest = new DataProvider().handleRequest()) {
3360
+ return _asyncToGenerator(function* () {
3361
+ const data = yield handleRequest(urn);
3362
+ return Object.assign(new MediaComposition(), data);
3363
+ })();
3364
+ }
3365
+
3366
+ /**
3367
+ * Get the mediaData most likely to be compatible depending on the browser.
3368
+ *
3369
+ * @param {Array.<import('./typedef').MainResourceWithKeySystems>} resources
3370
+ *
3371
+ * @returns {import('./typedef').MainResourceWithKeySystems} By default, the first entry is used if none is compatible.
3372
+ */
3373
+ static getMediaData(resources = []) {
3374
+ if (AkamaiTokenService.hasToken(resources)) return resources[0];
3375
+ const type = pillarbox.browser.IS_ANY_SAFARI ? 'HLS' : 'DASH';
3376
+ const resource = resources.find(({
3377
+ streaming
3378
+ }) => streaming === type);
3379
+ return resource || resources[0];
3380
+ }
3381
+
3382
+ /**
3383
+ * Get the source media object.
3384
+ *
3385
+ * @param {import('video.js/dist/types/player').default} player
3386
+ * @param {any} srcObj
3387
+ *
3388
+ * @returns {Promise<import('./typedef').ComposedSrcMediaData>} - The composed source media data.
3389
+ */
3390
+ static getSrcMediaObj(player, srcObj) {
3391
+ return _asyncToGenerator(function* () {
3392
+ const {
3393
+ src: urn
3394
+ } = srcObj,
3395
+ srcOptions = _objectWithoutProperties(srcObj, _excluded2);
3396
+ const mediaComposition = yield SrgSsr.getMediaComposition(urn, SrgSsr.dataProvider(player));
3397
+ const mainResources = yield SrgSsr.composeMainResources(mediaComposition);
3398
+ const mediaData = SrgSsr.getMediaData(mainResources);
3399
+ return SrgSsr.composeSrcMediaData(srcOptions, mediaData);
3400
+ })();
3401
+ }
3402
+
3403
+ /**
3404
+ * Handles the middleware's currentTime function.
3405
+ * - If the current time is between the start and end of a blocked segment,
3406
+ * the blocked portion will be skipped.
3407
+ *
3408
+ * _Note_: This function should disappear as soon as this behavior is
3409
+ * supported on the packaging side.
3410
+ *
3411
+ * @param {import('video.js/dist/types/player').default} player
3412
+ * @param {number} currentTime
3413
+ *
3414
+ * @returns {number}
3415
+ */
3416
+ static handleCurrentTime(player, currentTime) {
3417
+ const blockedSegment = SrgSsr.getBlockedSegmentByTime(player, currentTime);
3418
+ if (!blockedSegment || !Number.isFinite(blockedSegment.endTime)) {
3419
+ return currentTime;
3420
+ }
3421
+
3422
+ // as a workaround, add 0.1 seconds to avoid getting stuck on endTime on
3423
+ // some safaris.
3424
+ const endTimeWithTolerance = blockedSegment.endTime + 0.1;
3425
+
3426
+ // proxy for handling cuechange events at the player level
3427
+ player.trigger({
3428
+ type: 'srgssr/blocked-segment',
3429
+ data: blockedSegment
3430
+ });
3431
+ player.currentTime(endTimeWithTolerance);
3432
+ return endTimeWithTolerance;
3433
+ }
3434
+
3435
+ /**
3436
+ * Handles the middleware's setCurrentTime function.
3437
+ * - Modify the current time if the value is between the start and end of a
3438
+ * blocked segment.
3439
+ *
3440
+ * _Note_: This function should disappear as soon as this behavior is
3441
+ * supported on the packaging side.
3442
+ *
3443
+ * @param {import('video.js/dist/types/player').default} player
3444
+ * @param {number} currentTime
3445
+ *
3446
+ * @returns {number}
3447
+ */
3448
+ static handleSetCurrentTime(player, currentTime) {
3449
+ const {
3450
+ endTime: blockedSegmentEndTime
3451
+ } = SrgSsr.getBlockedSegmentByTime(player, currentTime) || {};
3452
+ return Number.isFinite(blockedSegmentEndTime) ? blockedSegmentEndTime : currentTime;
3453
+ }
3454
+
3455
+ /**
3456
+ * Handles the middleware's setSource function.
3457
+ *
3458
+ * This function allows to:
3459
+ * - resolve a URN into media that can be played by the player
3460
+ * - initialize media playback tracking
3461
+ * - update the title bar
3462
+ * - handle blocking reasons
3463
+ * - add remote subtitles
3464
+ *
3465
+ * @param {import('video.js/dist/types/player').default} player
3466
+ * @param {any} srcObj
3467
+ * @param {function} next
3468
+ *
3469
+ * @returns {Promise<any>}
3470
+ */
3471
+ static handleSetSource(player, srcObj, next) {
3472
+ return _asyncToGenerator(function* () {
3473
+ try {
3474
+ const srcMediaObj = yield SrgSsr.getSrcMediaObj(player, srcObj);
3475
+ SrgSsr.srgAnalytics(player);
3476
+ SrgSsr.updateTitleBar(player, srcMediaObj);
3477
+ SrgSsr.updatePoster(player, srcMediaObj);
3478
+ if (SrgSsr.blockingReason(player, srcMediaObj)) return;
3479
+ SrgSsr.addTextTracks(player, srcMediaObj);
3480
+ return next(null, srcMediaObj);
3481
+ } catch (error) {
3482
+ if (SrgSsr.dataProviderError(player, error)) return;
3483
+ return next(error);
3484
+ }
3485
+ })();
3486
+ }
3487
+
3488
+ /**
3489
+ * SRG SSR analytics singleton.
3490
+ *
3491
+ * @param {import('video.js/dist/types/player').default} player
3492
+ */
3493
+ static srgAnalytics(player) {
3494
+ if (player.options().trackers.srgAnalytics === false) return;
3495
+ if (!player.options().trackers.srgAnalytics) {
3496
+ const srgAnalytics = new SRGAnalytics(player, {
3497
+ debug: player.debug(),
3498
+ playerVersion: pillarbox.VERSION.pillarbox,
3499
+ tagCommanderScriptURL: player.options().srgOptions.tagCommanderScriptURL
3500
+ });
3501
+ player.options({
3502
+ trackers: {
3503
+ srgAnalytics
3504
+ }
3505
+ });
3506
+ }
3507
+ }
3508
+
3509
+ /**
3510
+ * Update player's poster.
3511
+ *
3512
+ * @param {import('video.js/dist/types/player').default} player
3513
+ * @param {import('./typedef').ComposedSrcMediaData} srcMediaObj
3514
+ * @param {Image} imageService
3515
+ */
3516
+ static updatePoster(player, srcMediaObj, imageService = Image) {
3517
+ player.poster(imageService.scale({
3518
+ url: srcMediaObj.mediaData.imageUrl
3519
+ }));
3520
+ }
3521
+
3522
+ /**
3523
+ * Update player titleBar with title and description.
3524
+ *
3525
+ * @param {import('video.js/dist/types/player').default} player
3526
+ * @param {import('./typedef').ComposedSrcMediaData} srcMediaObj
3527
+ */
3528
+ static updateTitleBar(player, srcMediaObj) {
3529
+ if (!player.titleBar) return;
3530
+ player.titleBar.update({
3531
+ title: srcMediaObj.mediaData.vendor,
3532
+ description: srcMediaObj.mediaData.title
3533
+ });
3534
+ }
3535
+
3536
+ /**
3537
+ * Middleware to resolve SRG SSR URNs into playable media.
3538
+ *
3539
+ * @param {import('video.js/dist/types/player').default} player
3540
+ *
3541
+ * @returns {Object}
3542
+ */
3543
+ static middleware(player) {
3544
+ SrgSsr.cuechangeEventProxy(player);
3545
+ return {
3546
+ currentTime: currentTime => SrgSsr.handleCurrentTime(player, currentTime),
3547
+ setCurrentTime: currentTime => SrgSsr.handleSetCurrentTime(player, currentTime),
3548
+ setSource: function () {
3549
+ var _ref = _asyncToGenerator(function* (srcObj, next) {
3550
+ return SrgSsr.handleSetSource(player, srcObj, next);
3551
+ });
3552
+ return function setSource(_x, _x2) {
3553
+ return _ref.apply(this, arguments);
3554
+ };
3555
+ }()
3556
+ };
3557
+ }
3558
+ }
3559
+ pillarbox.use('srgssr/urn', SrgSsr.middleware);
3560
+
3561
+ // Add Middleware specific options
3562
+ pillarbox.options.srgOptions = {
3563
+ dataProvider: undefined,
3564
+ dataProviderHost: undefined,
3565
+ dataProviderUrlHandler: undefined,
3566
+ tagCommanderScriptURL: undefined
3567
+ };
3568
+
3569
+ export { Player, SrgSsr, pillarbox as default };