@srgssr/pillarbox-web 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,7 +111,7 @@
111
111
  return target;
112
112
  }
113
113
 
114
- var version$8 = "1.12.2";
114
+ var version$8 = "1.13.1";
115
115
 
116
116
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
117
117
 
@@ -39488,7 +39488,7 @@
39488
39488
  * @type {Object}
39489
39489
  */
39490
39490
  Player$1.players = {};
39491
- const navigator = window$4.navigator;
39491
+ const navigator$1 = window$4.navigator;
39492
39492
 
39493
39493
  /*
39494
39494
  * Player instance options, surfaced using options
@@ -39513,7 +39513,7 @@
39513
39513
  liveui: false,
39514
39514
  // Included control sets
39515
39515
  children: ['mediaLoader', 'posterImage', 'titleBar', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
39516
- language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
39516
+ language: navigator$1 && (navigator$1.languages && navigator$1.languages[0] || navigator$1.userLanguage || navigator$1.language) || 'en',
39517
39517
  // locales and their language translations
39518
39518
  languages: {},
39519
39519
  // Default message to show when a video cannot be played.
@@ -69661,6 +69661,833 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
69661
69661
  }
69662
69662
  }
69663
69663
 
69664
+ /* eslint max-statements: ["error", 25]*/
69665
+
69666
+ /**
69667
+ * The PillarboxMonitoring class retrieves data about media playback.
69668
+ *
69669
+ * This data can be used to :
69670
+ * - help investigate playback problems
69671
+ * - measure the quality of our service
69672
+ *
69673
+ * The sending of this data tries to respect as much as possible the
69674
+ * specification described in the link below.
69675
+ *
69676
+ * However, some platforms may have certain limitations.
69677
+ * In this case, only the data available will be sent.
69678
+ *
69679
+ * @see https://github.com/SRGSSR/pillarbox-documentation/blob/main/Specifications/monitoring.md
69680
+ */
69681
+ class PillarboxMonitoring {
69682
+ constructor(player, {
69683
+ playerName = 'none',
69684
+ playerVersion = 'none',
69685
+ platform = 'Web',
69686
+ schemaVersion = 1,
69687
+ heartbeatInterval = 30000,
69688
+ beaconUrl = 'https://monitoring.pillarbox.ch/api/events'
69689
+ } = {}) {
69690
+ /**
69691
+ * @type {import('video.js/dist/types/player').default}
69692
+ */
69693
+ this.player = player;
69694
+ /**
69695
+ * @type {string}
69696
+ */
69697
+ this.playerName = playerName;
69698
+ /**
69699
+ * @type {string}
69700
+ */
69701
+ this.playerVersion = playerVersion;
69702
+ /**
69703
+ * @type {string}
69704
+ */
69705
+ this.platform = platform;
69706
+ /**
69707
+ * @type {string}
69708
+ */
69709
+ this.schemaVersion = schemaVersion;
69710
+ /**
69711
+ * @type {Number}
69712
+ */
69713
+ this.heartbeatInterval = heartbeatInterval;
69714
+ /**
69715
+ * @type {string}
69716
+ */
69717
+ this.beaconUrl = beaconUrl;
69718
+ /**
69719
+ * @type {string}
69720
+ */
69721
+ this.currentSessionId = undefined;
69722
+ /**
69723
+ * @type {Number}
69724
+ */
69725
+ this.lastPlaybackDuration = 0;
69726
+ /**
69727
+ * @type {Number}
69728
+ */
69729
+ this.lastPlaybackStartTimestamp = 0;
69730
+ /**
69731
+ * @type {Number}
69732
+ */
69733
+ this.lastStallCount = 0;
69734
+ /**
69735
+ * @type {Number}
69736
+ */
69737
+ this.lastStallDuration = 0;
69738
+ /**
69739
+ * @type {Number}
69740
+ */
69741
+ this.loadStartTimestamp = undefined;
69742
+ /**
69743
+ * @type {Number}
69744
+ */
69745
+ this.metadataRequestTime = 0;
69746
+ /**
69747
+ * @type {string}
69748
+ */
69749
+ this.mediaAssetUrl = undefined;
69750
+ /**
69751
+ * @type {string}
69752
+ */
69753
+ this.mediaId = undefined;
69754
+ /**
69755
+ * @type {string}
69756
+ */
69757
+ this.mediaMetadataUrl = undefined;
69758
+ /**
69759
+ * @type {string}
69760
+ */
69761
+ this.mediaOrigin = undefined;
69762
+ /**
69763
+ * @type {Number}
69764
+ */
69765
+ this.tokenRequestTime = 0;
69766
+ this.addListeners();
69767
+ }
69768
+
69769
+ /**
69770
+ * Adds event listeners to the player and the window.
69771
+ */
69772
+ addListeners() {
69773
+ this.bindCallBacks();
69774
+ this.player.on('loadstart', this.loadStart);
69775
+ this.player.on('loadeddata', this.loadedData);
69776
+ this.player.on('playing', this.playbackStart);
69777
+ this.player.on('pause', this.playbackStop);
69778
+ this.player.on('error', this.error);
69779
+ this.player.on(['playerreset', 'dispose', 'ended'], this.sessionStop);
69780
+ this.player.on(['waiting', 'stalled'], this.stalled);
69781
+ window.addEventListener('beforeunload', this.sessionStop);
69782
+ }
69783
+
69784
+ /**
69785
+ * The current bandwidth of the last segment download.
69786
+ *
69787
+ * @returns {number|undefined} The current bandwidth in bits per second,
69788
+ * undefined otherwise.
69789
+ */
69790
+ bandwidth() {
69791
+ const playerStats = this.player.tech(true).vhs ? this.player.tech(true).vhs.stats : undefined;
69792
+ return playerStats ? playerStats.bandwidth : undefined;
69793
+ }
69794
+
69795
+ /**
69796
+ * Binds the callback functions to the current instance.
69797
+ */
69798
+ bindCallBacks() {
69799
+ this.error = this.error.bind(this);
69800
+ this.loadedData = this.loadedData.bind(this);
69801
+ this.loadStart = this.loadStart.bind(this);
69802
+ this.playbackStart = this.playbackStart.bind(this);
69803
+ this.playbackStop = this.playbackStop.bind(this);
69804
+ this.stalled = this.stalled.bind(this);
69805
+ this.sessionStop = this.sessionStop.bind(this);
69806
+ }
69807
+
69808
+ /**
69809
+ * Get the buffer duration in milliseconds.
69810
+ *
69811
+ * @returns {Number} The buffer duration
69812
+ */
69813
+ bufferDuration() {
69814
+ const buffered = this.player.buffered();
69815
+ let bufferDuration = 0;
69816
+ for (let i = 0; i < buffered.length; i++) {
69817
+ const start = buffered.start(i);
69818
+ const end = buffered.end(i);
69819
+ bufferDuration += end - start;
69820
+ }
69821
+ return PillarboxMonitoring.secondsToMilliseconds(bufferDuration);
69822
+ }
69823
+
69824
+ /**
69825
+ * Get the current representation when playing a Dash or Hls media.
69826
+ *
69827
+ * @typedef {Object} Representation
69828
+ * @property {number|undefined} bandwidth The bandwidth of the current
69829
+ * representation
69830
+ * @property {number|undefined} programDateTime The program date time of the
69831
+ * current representation
69832
+ * @property {string|undefined} uri The URL of the current representation
69833
+ *
69834
+ * @returns {Representation|undefined} The current representation object
69835
+ * undefined otherwise
69836
+ */
69837
+ currentRepresentation() {
69838
+ const {
69839
+ activeCues: {
69840
+ cues_: [cue]
69841
+ } = {
69842
+ cues_: []
69843
+ }
69844
+ } = Array.from(this.player.textTracks()).find(({
69845
+ label,
69846
+ kind
69847
+ }) => kind === 'metadata' && label === 'segment-metadata') || {};
69848
+ return cue ? cue.value : undefined;
69849
+ }
69850
+
69851
+ /**
69852
+ * Get the current resource information including bitrate and URL when available.
69853
+ *
69854
+ * @typedef {Object} Resource
69855
+ * @property {number|undefined} bitrate The bitrate of the current resource
69856
+ * @property {string|undefined} url The URL of the current resource
69857
+ *
69858
+ * @returns {Resource} The current resource information.
69859
+ */
69860
+ currentResource() {
69861
+ let {
69862
+ bandwidth: bitrate,
69863
+ uri: url
69864
+ } = this.currentRepresentation() || {};
69865
+ if (pillarbox.browser.IS_ANY_SAFARI) {
69866
+ const {
69867
+ configuration
69868
+ } = Array.from(this.player.videoTracks()).find(track => track.selected) || {};
69869
+ bitrate = configuration ? configuration.bitrate : undefined;
69870
+ url = this.player.currentSource().src;
69871
+ }
69872
+ return {
69873
+ bitrate,
69874
+ url
69875
+ };
69876
+ }
69877
+
69878
+ /**
69879
+ * The media data of the current source.
69880
+ *
69881
+ * @returns {Object} The media data of the current source, or an empty object
69882
+ * if no media data is available.
69883
+ */
69884
+ currentSourceMediaData() {
69885
+ if (!this.player.currentSource().mediaData) return {};
69886
+ return this.player.currentSource().mediaData;
69887
+ }
69888
+
69889
+ /**
69890
+ * Handles player errors by sending an `ERROR` event, then resets the session.
69891
+ */
69892
+ error() {
69893
+ const error = this.player.error();
69894
+ const playbackPosition = this.playbackPosition();
69895
+ const representation = this.currentRepresentation();
69896
+ const url = representation ? representation.uri : this.player.currentSource().src;
69897
+ if (!this.player.hasStarted()) {
69898
+ this.sendEvent('START', this.startEventData());
69899
+ }
69900
+ this.sendEvent('ERROR', _objectSpread2(_objectSpread2({
69901
+ log: JSON.stringify(error.metadata || pillarbox.log.history().slice(-15)),
69902
+ message: error.message,
69903
+ name: error.code
69904
+ }, playbackPosition), {}, {
69905
+ severity: 'Fatal',
69906
+ url
69907
+ }));
69908
+ this.reset();
69909
+ }
69910
+
69911
+ /**
69912
+ * Get the DRM license request duration from performance API.
69913
+ *
69914
+ * @returns {number|undefined} The request duration
69915
+ */
69916
+ getDrmRequestDuration() {
69917
+ const keySystems = Object.values(this.player.currentSource().keySystems || {}).map(keySystem => keySystem.url);
69918
+ if (!keySystems.length) return;
69919
+ const resource = performance.getEntriesByType('resource').filter(({
69920
+ initiatorType,
69921
+ name
69922
+ }) => initiatorType === 'xmlhttprequest' && keySystems.includes(name)).pop();
69923
+ return resource && resource.duration;
69924
+ }
69925
+
69926
+ /**
69927
+ * Get metadata information from the performance API for a given id.
69928
+ *
69929
+ * @typedef {Object} MetadataInfo
69930
+ * @property {string} name The URL of the resource
69931
+ * @property {number} duration The duration of the resource fetch in milliseconds
69932
+ *
69933
+ * @param {string} id The id to search for in the resource entries
69934
+ *
69935
+ * @returns {MetadataInfo|undefined} An object containing metadata
69936
+ * information, or undefined otherwise
69937
+ */
69938
+ getMetadataInfo(id) {
69939
+ const resource = performance.getEntriesByType('resource').filter(({
69940
+ initiatorType,
69941
+ name
69942
+ }) => initiatorType === 'fetch' && name.includes(id)).pop();
69943
+ if (!resource) return {};
69944
+ return {
69945
+ name: resource.name,
69946
+ duration: resource.duration
69947
+ };
69948
+ }
69949
+
69950
+ /**
69951
+ * Get the Akamai token request duration from performance API.
69952
+ *
69953
+ * @returns {number|undefined} The request duration
69954
+ */
69955
+ getTokenRequestDuration(tokenType) {
69956
+ if (!tokenType) return;
69957
+ const resource = performance.getEntriesByType('resource').filter(({
69958
+ initiatorType,
69959
+ name
69960
+ }) => initiatorType === 'fetch' && name.includes('/akahd/token')).pop();
69961
+ return resource && resource.duration;
69962
+ }
69963
+
69964
+ /**
69965
+ * Send an 'HEARTBEAT' event with the date of the current playback state at
69966
+ * regular intervals.
69967
+ */
69968
+ heartbeat() {
69969
+ this.heartbeatIntervalId = setInterval(() => {
69970
+ this.sendEvent('HEARTBEAT', this.statusEventData());
69971
+ }, this.heartbeatInterval);
69972
+ }
69973
+
69974
+ /**
69975
+ * Check if the tracker is disabled.
69976
+ *
69977
+ * @returns {Boolean} __true__ if disabled __false__ otherwise.
69978
+ */
69979
+ isTrackerDisabled() {
69980
+ const currentSource = this.player.currentSource();
69981
+ if (!Array.isArray(currentSource.disableTrackers)) {
69982
+ return Boolean(currentSource.disableTrackers);
69983
+ }
69984
+ return Boolean(currentSource.disableTrackers.find(tracker => tracker.toLowerCase() === PillarboxMonitoring.name.toLowerCase()));
69985
+ }
69986
+
69987
+ /**
69988
+ * Handles the session start by sending a `START` event immediately followed
69989
+ * by a `HEARTBEAT` when the `loadeddata` event is triggered.
69990
+ */
69991
+ loadedData() {
69992
+ this.sendEvent('START', this.startEventData());
69993
+ this.sendEvent('HEARTBEAT', this.statusEventData());
69994
+ // starts the heartbeat interval
69995
+ this.heartbeat();
69996
+ }
69997
+
69998
+ /**
69999
+ * Handles `loadstart` event and captures the current timestamp. Will be used
70000
+ * to calculate the media loading time.
70001
+ */
70002
+ loadStart() {
70003
+ // if the content is a plain old URL
70004
+ if (!Object.keys(this.currentSourceMediaData()).length && this.currentSessionId) {
70005
+ this.sessionStop();
70006
+ // Reference timestamp used to calculate the different time metrics.
70007
+ this.sessionStartTimestamp = PillarboxMonitoring.timestamp();
70008
+ }
70009
+ this.loadStartTimestamp = PillarboxMonitoring.timestamp();
70010
+ }
70011
+
70012
+ /**
70013
+ * The media information.
70014
+ *
70015
+ * @typedef {Object} MediaInfo
70016
+ * @property {string} asset_url The URL of the media
70017
+ * @property {string} id The ID of the media
70018
+ * @property {string} metadata_url The URL of the media metadata
70019
+ * @property {string} origin The origin of the media
70020
+ *
70021
+ * @returns {MediaInfo} An object container the media information
70022
+ */
70023
+ mediaInfo() {
70024
+ return {
70025
+ asset_url: this.mediaAssetUrl,
70026
+ id: this.mediaId,
70027
+ metadata_url: this.mediaMetadataUrl,
70028
+ origin: this.mediaOrigin
70029
+ };
70030
+ }
70031
+
70032
+ /**
70033
+ * The total playback duration for the current session.
70034
+ *
70035
+ * @returns {number} The total playback duration in milliseconds.
70036
+ */
70037
+ playbackDuration() {
70038
+ if (!this.lastPlaybackStartTimestamp) {
70039
+ return this.lastPlaybackDuration;
70040
+ }
70041
+ return PillarboxMonitoring.timestamp() + this.lastPlaybackDuration - this.lastPlaybackStartTimestamp;
70042
+ }
70043
+
70044
+ /**
70045
+ * The current playback position and position timestamp.
70046
+ *
70047
+ * @typedef {Object} PlaybackPosition
70048
+ * @property {number} position The current playback position in milliseconds
70049
+ * @property {number|undefined} position_timestamp The timestamp of the
70050
+ * current playback position, or undefined if not available
70051
+ *
70052
+ * @returns {PlaybackPosition} The playback position object.
70053
+ */
70054
+ playbackPosition() {
70055
+ const currentRepresentation = this.currentRepresentation();
70056
+ const position = PillarboxMonitoring.secondsToMilliseconds(this.player.currentTime());
70057
+ let position_timestamp;
70058
+
70059
+ // Get the position timestamp from the program date time when VHS is used
70060
+ // or undefined if there is no value
70061
+ if (currentRepresentation) {
70062
+ position_timestamp = currentRepresentation.programDateTime;
70063
+ }
70064
+
70065
+ // Calculate the position timestamp from the start date on Safari
70066
+ if (pillarbox.browser.IS_ANY_SAFARI) {
70067
+ const startDate = Date.parse(this.player.$('video').getStartDate());
70068
+ position_timestamp = !isNaN(startDate) ? startDate + position : undefined;
70069
+ }
70070
+ return {
70071
+ position,
70072
+ position_timestamp
70073
+ };
70074
+ }
70075
+
70076
+ /**
70077
+ * Assign the timestamp each time the playback starts.
70078
+ */
70079
+ playbackStart() {
70080
+ this.lastPlaybackStartTimestamp = PillarboxMonitoring.timestamp();
70081
+ }
70082
+
70083
+ /**
70084
+ * Calculates and accumulates the duration of the playback session each time
70085
+ * the playback stops for the current media.
70086
+ */
70087
+ playbackStop() {
70088
+ this.lastPlaybackDuration += PillarboxMonitoring.timestamp() - this.lastPlaybackStartTimestamp;
70089
+ this.lastPlaybackStartTimestamp = 0;
70090
+ }
70091
+
70092
+ /**
70093
+ * The current dimensions of the player.
70094
+ *
70095
+ * @typedef {Object} PlayerCurrentDimensions
70096
+ * @property {number} width The current width of the player
70097
+ * @property {number} height The current height of the player
70098
+ *
70099
+ * @returns {PlayerCurrentDimensions} The current dimensions of the player object.
70100
+ */
70101
+ playerCurrentDimensions() {
70102
+ return this.player.currentDimensions();
70103
+ }
70104
+
70105
+ /**
70106
+ * Information about the player.
70107
+ *
70108
+ * @typedef {Object} PlayerInfo
70109
+ * @property {string} name The name of the player
70110
+ * @property {string} version The version of the player
70111
+ * @property {string} platform The platform on which the player is running
70112
+ *
70113
+ * @returns {PlayerInfo} An object containing player information.
70114
+ */
70115
+ playerInfo() {
70116
+ return {
70117
+ name: this.playerName,
70118
+ version: this.playerVersion,
70119
+ platform: this.platform
70120
+ };
70121
+ }
70122
+
70123
+ /**
70124
+ * Generates the QoE timings object.
70125
+ *
70126
+ * @typedef {Object} QoeTimings
70127
+ * @property {number} metadata The time taken to load metadata
70128
+ * @property {number} asset The time taken to load the asset
70129
+ * @property {number} total The total time taken from session start to data load
70130
+ *
70131
+ * @param {number} timeToLoadedData The time taken to load the data
70132
+ * @param {number} timestamp The current timestamp
70133
+ *
70134
+ * @returns {QoeTimings} The QoE timings
70135
+ */
70136
+ qoeTimings(timeToLoadedData, timestamp) {
70137
+ return {
70138
+ metadata: this.metadataRequestTime,
70139
+ asset: timeToLoadedData,
70140
+ total: timestamp - this.sessionStartTimestamp
70141
+ };
70142
+ }
70143
+
70144
+ /**
70145
+ * Generates the QoS timings object.
70146
+ *
70147
+ * @typedef {Object} QosTimings
70148
+ * @property {number} asset The time taken to load the asset
70149
+ * @property {number} drm The time taken for DRM processing
70150
+ * @property {number} metadata The time taken to load metadata
70151
+ * @property {number} token The time taken to request the token
70152
+ *
70153
+ * @param {number} timeToLoadedData The time taken to load the data
70154
+ *
70155
+ * @returns {QosTimings} The QoS timings
70156
+ */
70157
+ qosTimings(timeToLoadedData) {
70158
+ return {
70159
+ asset: timeToLoadedData,
70160
+ drm: this.getDrmRequestDuration(),
70161
+ metadata: this.metadataRequestTime,
70162
+ token: this.tokenRequestTime
70163
+ };
70164
+ }
70165
+
70166
+ /**
70167
+ * Removes all event listeners from the player and the window.
70168
+ */
70169
+ removeListeners() {
70170
+ this.player.off('loadstart', this.loadStart);
70171
+ this.player.off('loadeddata', this.loadedData);
70172
+ this.player.off('playing', this.playbackStart);
70173
+ this.player.off('pause', this.playbackStop);
70174
+ this.player.off('error', this.error);
70175
+ this.player.off(['playerreset', 'dispose', 'ended'], this.sessionStop);
70176
+ this.player.off(['waiting', 'stalled'], this.stalled);
70177
+ window.removeEventListener('beforeunload', this.sessionStop);
70178
+ }
70179
+
70180
+ /**
70181
+ * Remove the token from the asset URL.
70182
+ *
70183
+ * @param {string} assetUrl The URL of the asset
70184
+ *
70185
+ * @returns {string|undefined} The URL without the token, or undefined if the
70186
+ * input URL is invalid
70187
+ */
70188
+ removeTokenFromAssetUrl(assetUrl) {
70189
+ if (!assetUrl) return;
70190
+ try {
70191
+ const url = new URL(assetUrl);
70192
+ url.searchParams.delete('hdnts');
70193
+ return url.href;
70194
+ } catch (e) {
70195
+ return;
70196
+ }
70197
+ }
70198
+
70199
+ /**
70200
+ * Resets the playback session and clears relevant properties.
70201
+ *
70202
+ * @param {Event} event The event that triggered the reset. If the event type
70203
+ * is not 'ended' or 'playerreset', listeners will be removed.
70204
+ */
70205
+ reset(event) {
70206
+ this.currentSessionId = undefined;
70207
+ this.lastPlaybackDuration = 0;
70208
+ this.lastPlaybackStartTimestamp = 0;
70209
+ this.lastStallCount = 0;
70210
+ this.lastStallDuration = 0;
70211
+ this.loadStartTimestamp = 0;
70212
+ this.metadataRequestTime = 0;
70213
+ this.mediaAssetUrl = undefined;
70214
+ this.mediaId = undefined;
70215
+ this.mediaMetadataUrl = undefined;
70216
+ this.mediaOrigin = undefined;
70217
+ this.sessionStartTimestamp = undefined;
70218
+ this.tokenRequestTime = 0;
70219
+ clearInterval(this.heartbeatIntervalId);
70220
+ if (event && !['ended', 'playerreset'].includes(event.type)) {
70221
+ this.removeListeners();
70222
+ }
70223
+ }
70224
+
70225
+ /**
70226
+ * Sends an event to the server using the Beacon API.
70227
+ *
70228
+ * @param {string} eventName Either START, STOP, ERROR, HEARTBEAT
70229
+ * @param {Object} [data={}] The payload object to be sent. Defaults to an
70230
+ * empty object if not provided
70231
+ */
70232
+ sendEvent(eventName, data = {}) {
70233
+ // If the tracker is disabled for the current session, and there has been no
70234
+ // previous session, no event is sent. However, if a session was already
70235
+ // active, we still want to send the STOP event so that it is properly
70236
+ // stopped.
70237
+ if (this.isTrackerDisabled() && !this.currentSessionId || !this.currentSessionId) return;
70238
+ const payload = JSON.stringify({
70239
+ event_name: eventName,
70240
+ session_id: this.currentSessionId,
70241
+ timestamp: PillarboxMonitoring.timestamp(),
70242
+ version: this.schemaVersion,
70243
+ data
70244
+ });
70245
+ navigator.sendBeacon(this.beaconUrl, payload);
70246
+ }
70247
+
70248
+ /**
70249
+ * Starts a new session by first stopping the previous session, then resetting
70250
+ * the session start timestamp and media ID to their new values.
70251
+ */
70252
+ sessionStart() {
70253
+ if (this.sessionStartTimestamp) {
70254
+ this.sessionStop();
70255
+ }
70256
+
70257
+ // Reference timestamp used to calculate the different time metrics.
70258
+ this.sessionStartTimestamp = PillarboxMonitoring.timestamp();
70259
+ // At this stage currentSource().src is the media identifier
70260
+ // and not the playable source.
70261
+ this.mediaId = this.player.currentSource().src || undefined;
70262
+ }
70263
+
70264
+ /**
70265
+ * Stops the current session by sending a `STOP` event and resetting the
70266
+ * session.
70267
+ *
70268
+ * @param {Event} [event] The event that triggered the stop. This is passed
70269
+ * to the reset function.
70270
+ */
70271
+ sessionStop(event) {
70272
+ this.sendEvent('STOP', this.statusEventData());
70273
+ this.reset(event);
70274
+ }
70275
+
70276
+ /**
70277
+ * Handles the stalled state of the player. Sets the stalled state and listens
70278
+ * for the event that indicates the player is no longer stalled.
70279
+ */
70280
+ stalled() {
70281
+ if (!this.player.hasStarted() || this.player.seeking() || this.isStalled) return;
70282
+ this.isStalled = true;
70283
+ const stallStart = PillarboxMonitoring.timestamp();
70284
+ const unstalled = () => {
70285
+ const stallEnd = PillarboxMonitoring.timestamp();
70286
+ this.isStalled = false;
70287
+ this.lastStallCount += 1;
70288
+ this.lastStallDuration += stallEnd - stallStart;
70289
+ };
70290
+
70291
+ // As Safari is not consistent with its playing event, it is better to use
70292
+ // the timeupdate event.
70293
+ if (pillarbox.browser.IS_ANY_SAFARI) {
70294
+ this.player.one('timeupdate', unstalled);
70295
+ } else {
70296
+ // As Chromium-based browsers are not consistent with their timeupdate
70297
+ // event, it is better to use the playing event.
70298
+ //
70299
+ // Firefox is consistent with its playing event.
70300
+ this.player.one('playing', unstalled);
70301
+ }
70302
+ }
70303
+
70304
+ /**
70305
+ * Information about the player's stall events.
70306
+ *
70307
+ * @typedef {Object} StallInfo
70308
+ * @property {number} count The number of stall events
70309
+ * @property {number} duration The total duration of stall events in
70310
+ * milliseconds
70311
+ *
70312
+ * @returns {StallInfo} An object containing the stall information
70313
+ */
70314
+ stallInfo() {
70315
+ return {
70316
+ count: this.lastStallCount,
70317
+ duration: this.lastStallDuration
70318
+ };
70319
+ }
70320
+
70321
+ /**
70322
+ * Get data on the current playback state. Will be used when sending `HEARTBEAT` or `STOP` events.
70323
+ *
70324
+ * @typedef {Object} StatusEventData
70325
+ * @property {number} bandwidth The current bandwidth
70326
+ * @property {number|undefined} bitrate The bitrate of the current resource
70327
+ * @property {number} buffered_duration The duration of the buffered content
70328
+ * @property {number} frame_drops The number of dropped frames
70329
+ * @property {number} playback_duration The duration of the playback
70330
+ * @property {number} position The current playback position
70331
+ * @property {number} position_timestamp The timestamp of the current playback position
70332
+ * @property {Object} stall Information about any stalls
70333
+ * @property {string} stream_type The type of stream, either 'on-demand' or 'live'
70334
+ * @property {string|undefined} url The URL of the current resource
70335
+ *
70336
+ * @returns {StatusEventData} The current event data
70337
+ */
70338
+ statusEventData() {
70339
+ const bandwidth = this.bandwidth();
70340
+ const buffered_duration = this.bufferDuration();
70341
+ const {
70342
+ bitrate,
70343
+ url
70344
+ } = this.currentResource();
70345
+ const {
70346
+ droppedVideoFrames: frame_drops
70347
+ } = this.player.getVideoPlaybackQuality();
70348
+ const playback_duration = this.playbackDuration();
70349
+ const {
70350
+ position,
70351
+ position_timestamp
70352
+ } = this.playbackPosition();
70353
+ const stream_type = isFinite(this.player.duration()) ? 'On-demand' : 'Live';
70354
+ const stall = this.stallInfo();
70355
+ const data = {
70356
+ bandwidth,
70357
+ bitrate,
70358
+ buffered_duration,
70359
+ frame_drops,
70360
+ playback_duration,
70361
+ position,
70362
+ position_timestamp,
70363
+ stall,
70364
+ stream_type,
70365
+ url
70366
+ };
70367
+ return data;
70368
+ }
70369
+
70370
+ /**
70371
+ * Generates the data for the start event.
70372
+ *
70373
+ * @typedef {Object} Device
70374
+ * @property {string} id The device ID.
70375
+ *
70376
+ * @typedef {Object} StartEventData
70377
+ * @property {string} browser The user agent string of the browser.
70378
+ * @property {Device} device Information about the device.
70379
+ * @property {MediaInfo} media Information about the media.
70380
+ * @property {PlayerInfo} player Information about the player.
70381
+ * @property {QoeTimings} qoe_timings Quality of Experience timings.
70382
+ * @property {QosTimings} qos_timings Quality of Service timings.
70383
+ * @property {PlayerCurrentDimensions} screen The current dimensions of the
70384
+ * player.
70385
+ *
70386
+ * @returns {StartEventData} An object containing the start event data.
70387
+ */
70388
+ startEventData() {
70389
+ const timestamp = PillarboxMonitoring.timestamp();
70390
+ // This avoids false subtraction results when loadStartTimestamp is not
70391
+ // initialized.
70392
+ // loadStartTimestamp will be 0 if loadstart is not triggered.
70393
+ // This is the case when a STARTDATE error occurs.
70394
+ const timeToLoadedData = this.loadStartTimestamp ? timestamp - this.loadStartTimestamp : 0;
70395
+ if (!this.isTrackerDisabled()) {
70396
+ this.currentSessionId = PillarboxMonitoring.sessionId();
70397
+ }
70398
+ this.mediaAssetUrl = this.removeTokenFromAssetUrl(this.player.currentSource().src);
70399
+ this.mediaMetadataUrl = this.getMetadataInfo(this.mediaId).name;
70400
+ this.metadataRequestTime = this.getMetadataInfo(this.mediaId).duration;
70401
+ this.mediaOrigin = window.location.href;
70402
+ this.tokenRequestTime = this.getTokenRequestDuration(this.currentSourceMediaData().tokenType);
70403
+ return {
70404
+ browser: PillarboxMonitoring.userAgent(),
70405
+ device: {
70406
+ id: PillarboxMonitoring.deviceId()
70407
+ },
70408
+ media: this.mediaInfo(),
70409
+ player: this.playerInfo(),
70410
+ qoe_timings: this.qoeTimings(timeToLoadedData, timestamp),
70411
+ qos_timings: this.qosTimings(timeToLoadedData),
70412
+ screen: this.playerCurrentDimensions()
70413
+ };
70414
+ }
70415
+
70416
+ /**
70417
+ * Generates a new session ID.
70418
+ *
70419
+ * @returns {string} random UUID
70420
+ */
70421
+ static sessionId() {
70422
+ return PillarboxMonitoring.randomUUID();
70423
+ }
70424
+
70425
+ /**
70426
+ * Retrieve or generate a unique device ID and stores it in localStorage.
70427
+ *
70428
+ * @returns {string|undefined} The device ID if localStorage is available,
70429
+ * otherwise `undefined`
70430
+ */
70431
+ static deviceId() {
70432
+ if (!localStorage) return;
70433
+ const deviceIdKey = 'pillarbox_device_id';
70434
+ let deviceId = localStorage.getItem(deviceIdKey);
70435
+ if (!deviceId) {
70436
+ deviceId = PillarboxMonitoring.randomUUID();
70437
+ localStorage.setItem(deviceIdKey, deviceId);
70438
+ }
70439
+ return deviceId;
70440
+ }
70441
+
70442
+ /**
70443
+ * Generate a cryptographically secure random UUID.
70444
+ *
70445
+ * @returns {string}
70446
+ */
70447
+ static randomUUID() {
70448
+ if (!crypto.randomUUID) {
70449
+ // Polyfill from the author of uuid js which is simple and
70450
+ // cryptographically secure.
70451
+ // https://stackoverflow.com/a/2117523
70452
+ return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>
70453
+ // eslint-disable-next-line
70454
+ (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16));
70455
+ }
70456
+ return crypto.randomUUID();
70457
+ }
70458
+
70459
+ /**
70460
+ * converts seconds into milliseconds.
70461
+ *
70462
+ * @param {number} seconds
70463
+ *
70464
+ * @returns {number} milliseconds as an integer value
70465
+ */
70466
+ static secondsToMilliseconds(seconds) {
70467
+ return parseInt(seconds * 1000);
70468
+ }
70469
+
70470
+ /**
70471
+ * The timestamp in milliseconds.
70472
+ *
70473
+ * @return {number} milliseconds as an integer value
70474
+ */
70475
+ static timestamp() {
70476
+ return Date.now();
70477
+ }
70478
+
70479
+ /**
70480
+ * The browser's user agent.
70481
+ *
70482
+ * @returns {string}
70483
+ */
70484
+ static userAgent() {
70485
+ return {
70486
+ user_agent: navigator.userAgent
70487
+ };
70488
+ }
70489
+ }
70490
+
69664
70491
  /**
69665
70492
  * Represents the composition of media content.
69666
70493
  *
@@ -71182,8 +72009,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
71182
72009
  * @returns {Array.<import('./typedef').MainResourceWithKeySystems>}
71183
72010
  */
71184
72011
  static composeKeySystemsResources(resources = []) {
71185
- if (!Drm.hasDrm(resources)) ;
71186
- return resources.map(resource => _objectSpread2(_objectSpread2({}, resource), Drm.buildKeySystems(resource.drmList)));
72012
+ if (!Drm.hasDrm(resources)) return resources;
72013
+ const resourcesWithKeySystems = resources.map(resource => _objectSpread2(_objectSpread2({}, resource), Drm.buildKeySystems(resource.drmList)));
72014
+ return resourcesWithKeySystems;
71187
72015
  }
71188
72016
 
71189
72017
  /**
@@ -71424,6 +72252,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
71424
72252
  */
71425
72253
  static getSrcMediaObj(player, srcObj) {
71426
72254
  return _asyncToGenerator(function* () {
72255
+ if (SrgSsr.pillarboxMonitoring(player)) {
72256
+ SrgSsr.pillarboxMonitoring(player).sessionStart();
72257
+ }
71427
72258
  const {
71428
72259
  src: urn
71429
72260
  } = srcObj,
@@ -71541,6 +72372,30 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
71541
72372
  }
71542
72373
  }
71543
72374
 
72375
+ /**
72376
+ * PillarboxMonitoring monitoring singleton.
72377
+ *
72378
+ * @param {import('video.js/dist/types/player').default} player
72379
+ *
72380
+ * @returns {PillarboxMonitoring} instance of PillarboxMonitoring
72381
+ */
72382
+ static pillarboxMonitoring(player) {
72383
+ if (player.options().trackers.pillarboxMonitoring === false) return;
72384
+ if (!player.options().trackers.pillarboxMonitoring) {
72385
+ const pillarboxMonitoring = new PillarboxMonitoring(player, {
72386
+ debug: player.debug(),
72387
+ playerVersion: pillarbox.VERSION.pillarbox,
72388
+ playerName: 'Pillarbox'
72389
+ });
72390
+ player.options({
72391
+ trackers: {
72392
+ pillarboxMonitoring
72393
+ }
72394
+ });
72395
+ }
72396
+ return player.options().trackers.pillarboxMonitoring;
72397
+ }
72398
+
71544
72399
  /**
71545
72400
  * Update player's poster.
71546
72401
  *
@@ -71576,6 +72431,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
71576
72431
  * @returns {Object}
71577
72432
  */
71578
72433
  static middleware(player) {
72434
+ SrgSsr.pillarboxMonitoring(player);
71579
72435
  SrgSsr.cuechangeEventProxy(player);
71580
72436
  return {
71581
72437
  currentTime: currentTime => SrgSsr.handleCurrentTime(player, currentTime),