@srgssr/pillarbox-web 1.13.1 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,7 +111,7 @@
111
111
  return target;
112
112
  }
113
113
 
114
- var version$8 = "1.13.0";
114
+ var version$8 = "1.14.0";
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,832 @@ ${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
+ url
69906
+ }));
69907
+ this.reset();
69908
+ }
69909
+
69910
+ /**
69911
+ * Get the DRM license request duration from performance API.
69912
+ *
69913
+ * @returns {number|undefined} The request duration
69914
+ */
69915
+ getDrmRequestDuration() {
69916
+ const keySystems = Object.values(this.player.currentSource().keySystems || {}).map(keySystem => keySystem.url);
69917
+ if (!keySystems.length) return;
69918
+ const resource = performance.getEntriesByType('resource').filter(({
69919
+ initiatorType,
69920
+ name
69921
+ }) => initiatorType === 'xmlhttprequest' && keySystems.includes(name)).pop();
69922
+ return resource && resource.duration;
69923
+ }
69924
+
69925
+ /**
69926
+ * Get metadata information from the performance API for a given id.
69927
+ *
69928
+ * @typedef {Object} MetadataInfo
69929
+ * @property {string} name The URL of the resource
69930
+ * @property {number} duration The duration of the resource fetch in milliseconds
69931
+ *
69932
+ * @param {string} id The id to search for in the resource entries
69933
+ *
69934
+ * @returns {MetadataInfo|undefined} An object containing metadata
69935
+ * information, or undefined otherwise
69936
+ */
69937
+ getMetadataInfo(id) {
69938
+ const resource = performance.getEntriesByType('resource').filter(({
69939
+ initiatorType,
69940
+ name
69941
+ }) => initiatorType === 'fetch' && name.includes(id)).pop();
69942
+ if (!resource) return {};
69943
+ return {
69944
+ name: resource.name,
69945
+ duration: resource.duration
69946
+ };
69947
+ }
69948
+
69949
+ /**
69950
+ * Get the Akamai token request duration from performance API.
69951
+ *
69952
+ * @returns {number|undefined} The request duration
69953
+ */
69954
+ getTokenRequestDuration(tokenType) {
69955
+ if (!tokenType) return;
69956
+ const resource = performance.getEntriesByType('resource').filter(({
69957
+ initiatorType,
69958
+ name
69959
+ }) => initiatorType === 'fetch' && name.includes('/akahd/token')).pop();
69960
+ return resource && resource.duration;
69961
+ }
69962
+
69963
+ /**
69964
+ * Send an 'HEARTBEAT' event with the date of the current playback state at
69965
+ * regular intervals.
69966
+ */
69967
+ heartbeat() {
69968
+ this.heartbeatIntervalId = setInterval(() => {
69969
+ this.sendEvent('HEARTBEAT', this.statusEventData());
69970
+ }, this.heartbeatInterval);
69971
+ }
69972
+
69973
+ /**
69974
+ * Check if the tracker is disabled.
69975
+ *
69976
+ * @returns {Boolean} __true__ if disabled __false__ otherwise.
69977
+ */
69978
+ isTrackerDisabled() {
69979
+ const currentSource = this.player.currentSource();
69980
+ if (!Array.isArray(currentSource.disableTrackers)) {
69981
+ return Boolean(currentSource.disableTrackers);
69982
+ }
69983
+ return Boolean(currentSource.disableTrackers.find(tracker => tracker.toLowerCase() === PillarboxMonitoring.name.toLowerCase()));
69984
+ }
69985
+
69986
+ /**
69987
+ * Handles the session start by sending a `START` event immediately followed
69988
+ * by a `HEARTBEAT` when the `loadeddata` event is triggered.
69989
+ */
69990
+ loadedData() {
69991
+ this.sendEvent('START', this.startEventData());
69992
+ this.sendEvent('HEARTBEAT', this.statusEventData());
69993
+ // starts the heartbeat interval
69994
+ this.heartbeat();
69995
+ }
69996
+
69997
+ /**
69998
+ * Handles `loadstart` event and captures the current timestamp. Will be used
69999
+ * to calculate the media loading time.
70000
+ */
70001
+ loadStart() {
70002
+ // if the content is a plain old URL
70003
+ if (!Object.keys(this.currentSourceMediaData()).length && this.currentSessionId) {
70004
+ this.sessionStop();
70005
+ // Reference timestamp used to calculate the different time metrics.
70006
+ this.sessionStartTimestamp = PillarboxMonitoring.timestamp();
70007
+ }
70008
+ this.loadStartTimestamp = PillarboxMonitoring.timestamp();
70009
+ }
70010
+
70011
+ /**
70012
+ * The media information.
70013
+ *
70014
+ * @typedef {Object} MediaInfo
70015
+ * @property {string} asset_url The URL of the media
70016
+ * @property {string} id The ID of the media
70017
+ * @property {string} metadata_url The URL of the media metadata
70018
+ * @property {string} origin The origin of the media
70019
+ *
70020
+ * @returns {MediaInfo} An object container the media information
70021
+ */
70022
+ mediaInfo() {
70023
+ return {
70024
+ asset_url: this.mediaAssetUrl,
70025
+ id: this.mediaId,
70026
+ metadata_url: this.mediaMetadataUrl,
70027
+ origin: this.mediaOrigin
70028
+ };
70029
+ }
70030
+
70031
+ /**
70032
+ * The total playback duration for the current session.
70033
+ *
70034
+ * @returns {number} The total playback duration in milliseconds.
70035
+ */
70036
+ playbackDuration() {
70037
+ if (!this.lastPlaybackStartTimestamp) {
70038
+ return this.lastPlaybackDuration;
70039
+ }
70040
+ return PillarboxMonitoring.timestamp() + this.lastPlaybackDuration - this.lastPlaybackStartTimestamp;
70041
+ }
70042
+
70043
+ /**
70044
+ * The current playback position and position timestamp.
70045
+ *
70046
+ * @typedef {Object} PlaybackPosition
70047
+ * @property {number} position The current playback position in milliseconds
70048
+ * @property {number|undefined} position_timestamp The timestamp of the
70049
+ * current playback position, or undefined if not available
70050
+ *
70051
+ * @returns {PlaybackPosition} The playback position object.
70052
+ */
70053
+ playbackPosition() {
70054
+ const currentRepresentation = this.currentRepresentation();
70055
+ const position = PillarboxMonitoring.secondsToMilliseconds(this.player.currentTime());
70056
+ let position_timestamp;
70057
+
70058
+ // Get the position timestamp from the program date time when VHS is used
70059
+ // or undefined if there is no value
70060
+ if (currentRepresentation) {
70061
+ position_timestamp = currentRepresentation.programDateTime;
70062
+ }
70063
+
70064
+ // Calculate the position timestamp from the start date on Safari
70065
+ if (pillarbox.browser.IS_ANY_SAFARI) {
70066
+ const startDate = Date.parse(this.player.$('video').getStartDate());
70067
+ position_timestamp = !isNaN(startDate) ? startDate + position : undefined;
70068
+ }
70069
+ return {
70070
+ position,
70071
+ position_timestamp
70072
+ };
70073
+ }
70074
+
70075
+ /**
70076
+ * Assign the timestamp each time the playback starts.
70077
+ */
70078
+ playbackStart() {
70079
+ this.lastPlaybackStartTimestamp = PillarboxMonitoring.timestamp();
70080
+ }
70081
+
70082
+ /**
70083
+ * Calculates and accumulates the duration of the playback session each time
70084
+ * the playback stops for the current media.
70085
+ */
70086
+ playbackStop() {
70087
+ this.lastPlaybackDuration += PillarboxMonitoring.timestamp() - this.lastPlaybackStartTimestamp;
70088
+ this.lastPlaybackStartTimestamp = 0;
70089
+ }
70090
+
70091
+ /**
70092
+ * The current dimensions of the player.
70093
+ *
70094
+ * @typedef {Object} PlayerCurrentDimensions
70095
+ * @property {number} width The current width of the player
70096
+ * @property {number} height The current height of the player
70097
+ *
70098
+ * @returns {PlayerCurrentDimensions} The current dimensions of the player object.
70099
+ */
70100
+ playerCurrentDimensions() {
70101
+ return this.player.currentDimensions();
70102
+ }
70103
+
70104
+ /**
70105
+ * Information about the player.
70106
+ *
70107
+ * @typedef {Object} PlayerInfo
70108
+ * @property {string} name The name of the player
70109
+ * @property {string} version The version of the player
70110
+ * @property {string} platform The platform on which the player is running
70111
+ *
70112
+ * @returns {PlayerInfo} An object containing player information.
70113
+ */
70114
+ playerInfo() {
70115
+ return {
70116
+ name: this.playerName,
70117
+ version: this.playerVersion,
70118
+ platform: this.platform
70119
+ };
70120
+ }
70121
+
70122
+ /**
70123
+ * Generates the QoE timings object.
70124
+ *
70125
+ * @typedef {Object} QoeTimings
70126
+ * @property {number} metadata The time taken to load metadata
70127
+ * @property {number} asset The time taken to load the asset
70128
+ * @property {number} total The total time taken from session start to data load
70129
+ *
70130
+ * @param {number} timeToLoadedData The time taken to load the data
70131
+ * @param {number} timestamp The current timestamp
70132
+ *
70133
+ * @returns {QoeTimings} The QoE timings
70134
+ */
70135
+ qoeTimings(timeToLoadedData, timestamp) {
70136
+ return {
70137
+ metadata: this.metadataRequestTime,
70138
+ asset: timeToLoadedData,
70139
+ total: timestamp - this.sessionStartTimestamp
70140
+ };
70141
+ }
70142
+
70143
+ /**
70144
+ * Generates the QoS timings object.
70145
+ *
70146
+ * @typedef {Object} QosTimings
70147
+ * @property {number} asset The time taken to load the asset
70148
+ * @property {number} drm The time taken for DRM processing
70149
+ * @property {number} metadata The time taken to load metadata
70150
+ * @property {number} token The time taken to request the token
70151
+ *
70152
+ * @param {number} timeToLoadedData The time taken to load the data
70153
+ *
70154
+ * @returns {QosTimings} The QoS timings
70155
+ */
70156
+ qosTimings(timeToLoadedData) {
70157
+ return {
70158
+ asset: timeToLoadedData,
70159
+ drm: this.getDrmRequestDuration(),
70160
+ metadata: this.metadataRequestTime,
70161
+ token: this.tokenRequestTime
70162
+ };
70163
+ }
70164
+
70165
+ /**
70166
+ * Removes all event listeners from the player and the window.
70167
+ */
70168
+ removeListeners() {
70169
+ this.player.off('loadstart', this.loadStart);
70170
+ this.player.off('loadeddata', this.loadedData);
70171
+ this.player.off('playing', this.playbackStart);
70172
+ this.player.off('pause', this.playbackStop);
70173
+ this.player.off('error', this.error);
70174
+ this.player.off(['playerreset', 'dispose', 'ended'], this.sessionStop);
70175
+ this.player.off(['waiting', 'stalled'], this.stalled);
70176
+ window.removeEventListener('beforeunload', this.sessionStop);
70177
+ }
70178
+
70179
+ /**
70180
+ * Remove the token from the asset URL.
70181
+ *
70182
+ * @param {string} assetUrl The URL of the asset
70183
+ *
70184
+ * @returns {string|undefined} The URL without the token, or undefined if the
70185
+ * input URL is invalid
70186
+ */
70187
+ removeTokenFromAssetUrl(assetUrl) {
70188
+ if (!assetUrl) return;
70189
+ try {
70190
+ const url = new URL(assetUrl);
70191
+ url.searchParams.delete('hdnts');
70192
+ return url.href;
70193
+ } catch (e) {
70194
+ return;
70195
+ }
70196
+ }
70197
+
70198
+ /**
70199
+ * Resets the playback session and clears relevant properties.
70200
+ *
70201
+ * @param {Event} event The event that triggered the reset. If the event type
70202
+ * is not 'ended' or 'playerreset', listeners will be removed.
70203
+ */
70204
+ reset(event) {
70205
+ this.currentSessionId = undefined;
70206
+ this.lastPlaybackDuration = 0;
70207
+ this.lastPlaybackStartTimestamp = 0;
70208
+ this.lastStallCount = 0;
70209
+ this.lastStallDuration = 0;
70210
+ this.loadStartTimestamp = 0;
70211
+ this.metadataRequestTime = 0;
70212
+ this.mediaAssetUrl = undefined;
70213
+ this.mediaId = undefined;
70214
+ this.mediaMetadataUrl = undefined;
70215
+ this.mediaOrigin = undefined;
70216
+ this.sessionStartTimestamp = undefined;
70217
+ this.tokenRequestTime = 0;
70218
+ clearInterval(this.heartbeatIntervalId);
70219
+ if (event && !['ended', 'playerreset'].includes(event.type)) {
70220
+ this.removeListeners();
70221
+ }
70222
+ }
70223
+
70224
+ /**
70225
+ * Sends an event to the server using the Beacon API.
70226
+ *
70227
+ * @param {string} eventName Either START, STOP, ERROR, HEARTBEAT
70228
+ * @param {Object} [data={}] The payload object to be sent. Defaults to an
70229
+ * empty object if not provided
70230
+ */
70231
+ sendEvent(eventName, data = {}) {
70232
+ // If the tracker is disabled for the current session, and there has been no
70233
+ // previous session, no event is sent. However, if a session was already
70234
+ // active, we still want to send the STOP event so that it is properly
70235
+ // stopped.
70236
+ if (this.isTrackerDisabled() && !this.currentSessionId || !this.currentSessionId) return;
70237
+ const payload = JSON.stringify({
70238
+ event_name: eventName,
70239
+ session_id: this.currentSessionId,
70240
+ timestamp: PillarboxMonitoring.timestamp(),
70241
+ version: this.schemaVersion,
70242
+ data
70243
+ });
70244
+ navigator.sendBeacon(this.beaconUrl, payload);
70245
+ }
70246
+
70247
+ /**
70248
+ * Starts a new session by first stopping the previous session, then resetting
70249
+ * the session start timestamp and media ID to their new values.
70250
+ */
70251
+ sessionStart() {
70252
+ if (this.sessionStartTimestamp) {
70253
+ this.sessionStop();
70254
+ }
70255
+
70256
+ // Reference timestamp used to calculate the different time metrics.
70257
+ this.sessionStartTimestamp = PillarboxMonitoring.timestamp();
70258
+ // At this stage currentSource().src is the media identifier
70259
+ // and not the playable source.
70260
+ this.mediaId = this.player.currentSource().src || undefined;
70261
+ }
70262
+
70263
+ /**
70264
+ * Stops the current session by sending a `STOP` event and resetting the
70265
+ * session.
70266
+ *
70267
+ * @param {Event} [event] The event that triggered the stop. This is passed
70268
+ * to the reset function.
70269
+ */
70270
+ sessionStop(event) {
70271
+ this.sendEvent('STOP', this.statusEventData());
70272
+ this.reset(event);
70273
+ }
70274
+
70275
+ /**
70276
+ * Handles the stalled state of the player. Sets the stalled state and listens
70277
+ * for the event that indicates the player is no longer stalled.
70278
+ */
70279
+ stalled() {
70280
+ if (!this.player.hasStarted() || this.player.seeking() || this.isStalled) return;
70281
+ this.isStalled = true;
70282
+ const stallStart = PillarboxMonitoring.timestamp();
70283
+ const unstalled = () => {
70284
+ const stallEnd = PillarboxMonitoring.timestamp();
70285
+ this.isStalled = false;
70286
+ this.lastStallCount += 1;
70287
+ this.lastStallDuration += stallEnd - stallStart;
70288
+ };
70289
+
70290
+ // As Safari is not consistent with its playing event, it is better to use
70291
+ // the timeupdate event.
70292
+ if (pillarbox.browser.IS_ANY_SAFARI) {
70293
+ this.player.one('timeupdate', unstalled);
70294
+ } else {
70295
+ // As Chromium-based browsers are not consistent with their timeupdate
70296
+ // event, it is better to use the playing event.
70297
+ //
70298
+ // Firefox is consistent with its playing event.
70299
+ this.player.one('playing', unstalled);
70300
+ }
70301
+ }
70302
+
70303
+ /**
70304
+ * Information about the player's stall events.
70305
+ *
70306
+ * @typedef {Object} StallInfo
70307
+ * @property {number} count The number of stall events
70308
+ * @property {number} duration The total duration of stall events in
70309
+ * milliseconds
70310
+ *
70311
+ * @returns {StallInfo} An object containing the stall information
70312
+ */
70313
+ stallInfo() {
70314
+ return {
70315
+ count: this.lastStallCount,
70316
+ duration: this.lastStallDuration
70317
+ };
70318
+ }
70319
+
70320
+ /**
70321
+ * Get data on the current playback state. Will be used when sending `HEARTBEAT` or `STOP` events.
70322
+ *
70323
+ * @typedef {Object} StatusEventData
70324
+ * @property {number} bandwidth The current bandwidth
70325
+ * @property {number|undefined} bitrate The bitrate of the current resource
70326
+ * @property {number} buffered_duration The duration of the buffered content
70327
+ * @property {number} frame_drops The number of dropped frames
70328
+ * @property {number} playback_duration The duration of the playback
70329
+ * @property {number} position The current playback position
70330
+ * @property {number} position_timestamp The timestamp of the current playback position
70331
+ * @property {Object} stall Information about any stalls
70332
+ * @property {string} stream_type The type of stream, either 'on-demand' or 'live'
70333
+ * @property {string|undefined} url The URL of the current resource
70334
+ *
70335
+ * @returns {StatusEventData} The current event data
70336
+ */
70337
+ statusEventData() {
70338
+ const bandwidth = this.bandwidth();
70339
+ const buffered_duration = this.bufferDuration();
70340
+ const {
70341
+ bitrate,
70342
+ url
70343
+ } = this.currentResource();
70344
+ const {
70345
+ droppedVideoFrames: frame_drops
70346
+ } = this.player.getVideoPlaybackQuality();
70347
+ const playback_duration = this.playbackDuration();
70348
+ const {
70349
+ position,
70350
+ position_timestamp
70351
+ } = this.playbackPosition();
70352
+ const stream_type = isFinite(this.player.duration()) ? 'On-demand' : 'Live';
70353
+ const stall = this.stallInfo();
70354
+ const data = {
70355
+ bandwidth,
70356
+ bitrate,
70357
+ buffered_duration,
70358
+ frame_drops,
70359
+ playback_duration,
70360
+ position,
70361
+ position_timestamp,
70362
+ stall,
70363
+ stream_type,
70364
+ url
70365
+ };
70366
+ return data;
70367
+ }
70368
+
70369
+ /**
70370
+ * Generates the data for the start event.
70371
+ *
70372
+ * @typedef {Object} Device
70373
+ * @property {string} id The device ID.
70374
+ *
70375
+ * @typedef {Object} StartEventData
70376
+ * @property {string} browser The user agent string of the browser.
70377
+ * @property {Device} device Information about the device.
70378
+ * @property {MediaInfo} media Information about the media.
70379
+ * @property {PlayerInfo} player Information about the player.
70380
+ * @property {QoeTimings} qoe_timings Quality of Experience timings.
70381
+ * @property {QosTimings} qos_timings Quality of Service timings.
70382
+ * @property {PlayerCurrentDimensions} screen The current dimensions of the
70383
+ * player.
70384
+ *
70385
+ * @returns {StartEventData} An object containing the start event data.
70386
+ */
70387
+ startEventData() {
70388
+ const timestamp = PillarboxMonitoring.timestamp();
70389
+ // This avoids false subtraction results when loadStartTimestamp is not
70390
+ // initialized.
70391
+ // loadStartTimestamp will be 0 if loadstart is not triggered.
70392
+ // This is the case when a STARTDATE error occurs.
70393
+ const timeToLoadedData = this.loadStartTimestamp ? timestamp - this.loadStartTimestamp : 0;
70394
+ if (!this.isTrackerDisabled()) {
70395
+ this.currentSessionId = PillarboxMonitoring.sessionId();
70396
+ }
70397
+ this.mediaAssetUrl = this.removeTokenFromAssetUrl(this.player.currentSource().src);
70398
+ this.mediaMetadataUrl = this.getMetadataInfo(this.mediaId).name;
70399
+ this.metadataRequestTime = this.getMetadataInfo(this.mediaId).duration;
70400
+ this.mediaOrigin = window.location.href;
70401
+ this.tokenRequestTime = this.getTokenRequestDuration(this.currentSourceMediaData().tokenType);
70402
+ return {
70403
+ browser: PillarboxMonitoring.userAgent(),
70404
+ device: {
70405
+ id: PillarboxMonitoring.deviceId()
70406
+ },
70407
+ media: this.mediaInfo(),
70408
+ player: this.playerInfo(),
70409
+ qoe_timings: this.qoeTimings(timeToLoadedData, timestamp),
70410
+ qos_timings: this.qosTimings(timeToLoadedData),
70411
+ screen: this.playerCurrentDimensions()
70412
+ };
70413
+ }
70414
+
70415
+ /**
70416
+ * Generates a new session ID.
70417
+ *
70418
+ * @returns {string} random UUID
70419
+ */
70420
+ static sessionId() {
70421
+ return PillarboxMonitoring.randomUUID();
70422
+ }
70423
+
70424
+ /**
70425
+ * Retrieve or generate a unique device ID and stores it in localStorage.
70426
+ *
70427
+ * @returns {string|undefined} The device ID if localStorage is available,
70428
+ * otherwise `undefined`
70429
+ */
70430
+ static deviceId() {
70431
+ if (!localStorage) return;
70432
+ const deviceIdKey = 'pillarbox_device_id';
70433
+ let deviceId = localStorage.getItem(deviceIdKey);
70434
+ if (!deviceId) {
70435
+ deviceId = PillarboxMonitoring.randomUUID();
70436
+ localStorage.setItem(deviceIdKey, deviceId);
70437
+ }
70438
+ return deviceId;
70439
+ }
70440
+
70441
+ /**
70442
+ * Generate a cryptographically secure random UUID.
70443
+ *
70444
+ * @returns {string}
70445
+ */
70446
+ static randomUUID() {
70447
+ if (!crypto.randomUUID) {
70448
+ // Polyfill from the author of uuid js which is simple and
70449
+ // cryptographically secure.
70450
+ // https://stackoverflow.com/a/2117523
70451
+ return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>
70452
+ // eslint-disable-next-line
70453
+ (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16));
70454
+ }
70455
+ return crypto.randomUUID();
70456
+ }
70457
+
70458
+ /**
70459
+ * converts seconds into milliseconds.
70460
+ *
70461
+ * @param {number} seconds
70462
+ *
70463
+ * @returns {number} milliseconds as an integer value
70464
+ */
70465
+ static secondsToMilliseconds(seconds) {
70466
+ return parseInt(seconds * 1000);
70467
+ }
70468
+
70469
+ /**
70470
+ * The timestamp in milliseconds.
70471
+ *
70472
+ * @return {number} milliseconds as an integer value
70473
+ */
70474
+ static timestamp() {
70475
+ return Date.now();
70476
+ }
70477
+
70478
+ /**
70479
+ * The browser's user agent.
70480
+ *
70481
+ * @returns {string}
70482
+ */
70483
+ static userAgent() {
70484
+ return {
70485
+ user_agent: navigator.userAgent
70486
+ };
70487
+ }
70488
+ }
70489
+
69664
70490
  /**
69665
70491
  * Represents the composition of media content.
69666
70492
  *
@@ -71425,6 +72251,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
71425
72251
  */
71426
72252
  static getSrcMediaObj(player, srcObj) {
71427
72253
  return _asyncToGenerator(function* () {
72254
+ if (SrgSsr.pillarboxMonitoring(player)) {
72255
+ SrgSsr.pillarboxMonitoring(player).sessionStart();
72256
+ }
71428
72257
  const {
71429
72258
  src: urn
71430
72259
  } = srcObj,
@@ -71542,6 +72371,30 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
71542
72371
  }
71543
72372
  }
71544
72373
 
72374
+ /**
72375
+ * PillarboxMonitoring monitoring singleton.
72376
+ *
72377
+ * @param {import('video.js/dist/types/player').default} player
72378
+ *
72379
+ * @returns {PillarboxMonitoring} instance of PillarboxMonitoring
72380
+ */
72381
+ static pillarboxMonitoring(player) {
72382
+ if (player.options().trackers.pillarboxMonitoring === false) return;
72383
+ if (!player.options().trackers.pillarboxMonitoring) {
72384
+ const pillarboxMonitoring = new PillarboxMonitoring(player, {
72385
+ debug: player.debug(),
72386
+ playerVersion: pillarbox.VERSION.pillarbox,
72387
+ playerName: 'Pillarbox'
72388
+ });
72389
+ player.options({
72390
+ trackers: {
72391
+ pillarboxMonitoring
72392
+ }
72393
+ });
72394
+ }
72395
+ return player.options().trackers.pillarboxMonitoring;
72396
+ }
72397
+
71545
72398
  /**
71546
72399
  * Update player's poster.
71547
72400
  *
@@ -71577,6 +72430,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
71577
72430
  * @returns {Object}
71578
72431
  */
71579
72432
  static middleware(player) {
72433
+ SrgSsr.pillarboxMonitoring(player);
71580
72434
  SrgSsr.cuechangeEventProxy(player);
71581
72435
  return {
71582
72436
  currentTime: currentTime => SrgSsr.handleCurrentTime(player, currentTime),