stormcloud-video-player 0.7.50 → 0.7.52

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.
@@ -1,5 +1,6 @@
1
1
  import { Component } from 'react';
2
- import { S as StormcloudVideoPlayerConfig } from '../types-CIHDHY7A.cjs';
2
+ import { S as StormcloudVideoPlayerConfig } from '../types-Dobqa8vR.cjs';
3
+ import '../utils/mqttConfig.cjs';
3
4
 
4
5
  interface HlsPlayerProps extends StormcloudVideoPlayerConfig {
5
6
  onMount?: (player: any) => void;
@@ -1765,6 +1765,116 @@ function createAdStormPlayer(contentVideo, options) {
1765
1765
  }
1766
1766
  };
1767
1767
  }
1768
+ // src/utils/mqttClient.ts
1769
+ var import_mqtt = __toESM(require("mqtt"), 1);
1770
+ // src/utils/mqttConfig.ts
1771
+ var DEFAULT_MQTT_CONFIG = {
1772
+ enabled: true,
1773
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
1774
+ brokerPort: 8883,
1775
+ wsPort: 8084,
1776
+ username: "for-sonifi",
1777
+ password: "sonifi-mqtt",
1778
+ topicPrefix: "adstorm/players",
1779
+ qos: 1
1780
+ };
1781
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
1782
+ function applyMQTTConfig(overrides) {
1783
+ Object.assign(mqttConfig, overrides);
1784
+ }
1785
+ function isMQTTEnabled() {
1786
+ return mqttConfig.enabled;
1787
+ }
1788
+ function buildMQTTBrokerUrl() {
1789
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
1790
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
1791
+ }
1792
+ function buildPlayerTopic(licenseKey, channel) {
1793
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
1794
+ }
1795
+ // src/utils/mqttClient.ts
1796
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
1797
+ var client = null;
1798
+ var status = "disconnected";
1799
+ function configureMQTT(overrides) {
1800
+ applyMQTTConfig(overrides);
1801
+ disconnectMQTT();
1802
+ }
1803
+ function initMQTTClient() {
1804
+ if (client || !isMQTTEnabled()) return;
1805
+ var url = buildMQTTBrokerUrl();
1806
+ status = "connecting";
1807
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
1808
+ try {
1809
+ client = import_mqtt.default.connect(url, {
1810
+ clientId: clientId,
1811
+ username: mqttConfig.username,
1812
+ password: mqttConfig.password,
1813
+ keepalive: 60,
1814
+ clean: true,
1815
+ reconnectPeriod: 5e3,
1816
+ connectTimeout: 1e4,
1817
+ queueQoSZero: false
1818
+ });
1819
+ } catch (err) {
1820
+ status = "error";
1821
+ console.warn("".concat(LOG, " connect() threw:"), err);
1822
+ return;
1823
+ }
1824
+ client.on("connect", function() {
1825
+ status = "connected";
1826
+ console.info("".concat(LOG, " connected to ").concat(url));
1827
+ });
1828
+ client.on("reconnect", function() {
1829
+ status = "connecting";
1830
+ console.info("".concat(LOG, " reconnecting..."));
1831
+ });
1832
+ client.on("offline", function() {
1833
+ if (status !== "error") {
1834
+ status = "disconnected";
1835
+ }
1836
+ console.warn("".concat(LOG, " offline"));
1837
+ });
1838
+ client.on("error", function(err) {
1839
+ status = "error";
1840
+ console.warn("".concat(LOG, " error:"), err.message);
1841
+ });
1842
+ client.on("close", function() {
1843
+ if (status === "connected" || status === "connecting") {
1844
+ status = "disconnected";
1845
+ }
1846
+ });
1847
+ }
1848
+ function ensureMQTTClient() {
1849
+ if (isMQTTEnabled() && !client) {
1850
+ initMQTTClient();
1851
+ }
1852
+ }
1853
+ function publishMQTT(topic, payload) {
1854
+ if (!isMQTTEnabled()) {
1855
+ return false;
1856
+ }
1857
+ ensureMQTTClient();
1858
+ if (!client) {
1859
+ return false;
1860
+ }
1861
+ try {
1862
+ client.publish(topic, JSON.stringify(payload), {
1863
+ qos: mqttConfig.qos
1864
+ });
1865
+ return true;
1866
+ } catch (err) {
1867
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
1868
+ return false;
1869
+ }
1870
+ }
1871
+ function disconnectMQTT() {
1872
+ if (client) {
1873
+ client.end(true);
1874
+ client = null;
1875
+ status = "disconnected";
1876
+ }
1877
+ }
1768
1878
  // src/utils/tracking.ts
1769
1879
  var cachedBrowserId = null;
1770
1880
  function getClientInfo() {
@@ -1995,139 +2105,120 @@ function getBrowserID(clientInfo) {
1995
2105
  });
1996
2106
  })();
1997
2107
  }
1998
- var TRACK_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/track";
1999
- function sendTrackRequest(licenseKey, body) {
2108
+ function canPublish(licenseKey) {
2109
+ return Boolean(isMQTTEnabled() && licenseKey);
2110
+ }
2111
+ function buildPlayerMetricEvent() {
2000
2112
  return _async_to_generator(function() {
2001
- var headers, response;
2113
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
2114
+ var _arguments = arguments;
2002
2115
  return _ts_generator(this, function(_state) {
2003
2116
  switch(_state.label){
2004
2117
  case 0:
2005
- headers = {
2006
- "Content-Type": "application/json"
2007
- };
2008
- if (licenseKey) {
2009
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2010
- }
2118
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2119
+ clientInfo = getClientInfo();
2011
2120
  return [
2012
2121
  4,
2013
- fetch(TRACK_URL, {
2014
- method: "POST",
2015
- headers: headers,
2016
- body: JSON.stringify(body)
2017
- })
2122
+ getBrowserID(clientInfo)
2018
2123
  ];
2019
2124
  case 1:
2020
- response = _state.sent();
2021
- if (!response.ok) {
2022
- throw new Error("HTTP error! status: ".concat(response.status));
2023
- }
2024
- return [
2025
- 4,
2026
- response.json()
2027
- ];
2028
- case 2:
2029
- _state.sent();
2125
+ playerId = _state.sent();
2126
+ captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
2030
2127
  return [
2031
- 2
2128
+ 2,
2129
+ _object_spread({
2130
+ player_id: playerId,
2131
+ device_type: clientInfo.deviceType,
2132
+ os: clientInfo.os.toLowerCase(),
2133
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
2134
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
2135
+ capture_at: captureAt
2136
+ }, context.inputStreamType ? {
2137
+ input_stream_type: context.inputStreamType
2138
+ } : {})
2032
2139
  ];
2033
2140
  }
2034
2141
  });
2035
- })();
2142
+ }).apply(this, arguments);
2036
2143
  }
2037
- function sendInitialTracking(licenseKey) {
2038
- return _async_to_generator(function() {
2039
- var clientInfo, browserId, trackingData, headers, response, error;
2144
+ function publishTracking(licenseKey, channel, body) {
2145
+ ensureMQTTClient();
2146
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
2147
+ }
2148
+ function sendInitialTracking(_0) {
2149
+ return _async_to_generator(function(licenseKey) {
2150
+ var context, metricEvent, error;
2151
+ var _arguments = arguments;
2040
2152
  return _ts_generator(this, function(_state) {
2041
2153
  switch(_state.label){
2042
2154
  case 0:
2155
+ context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2156
+ if (!canPublish(licenseKey)) return [
2157
+ 2
2158
+ ];
2159
+ _state.label = 1;
2160
+ case 1:
2043
2161
  _state.trys.push([
2044
- 0,
2045
- 4,
2162
+ 1,
2163
+ 3,
2046
2164
  ,
2047
- 5
2165
+ 4
2048
2166
  ]);
2049
- clientInfo = getClientInfo();
2050
- return [
2051
- 4,
2052
- getBrowserID(clientInfo)
2053
- ];
2054
- case 1:
2055
- browserId = _state.sent();
2056
- trackingData = _object_spread({
2057
- browserId: browserId
2058
- }, clientInfo);
2059
- headers = {
2060
- "Content-Type": "application/json"
2061
- };
2062
- if (licenseKey) {
2063
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2064
- }
2065
2167
  return [
2066
2168
  4,
2067
- fetch(TRACK_URL, {
2068
- method: "POST",
2069
- headers: headers,
2070
- body: JSON.stringify(trackingData)
2169
+ buildPlayerMetricEvent(context, {
2170
+ adLoaded: false,
2171
+ adDetect: false
2071
2172
  })
2072
2173
  ];
2073
2174
  case 2:
2074
- response = _state.sent();
2075
- if (!response.ok) {
2076
- throw new Error("HTTP error! status: ".concat(response.status));
2077
- }
2078
- return [
2079
- 4,
2080
- response.json()
2081
- ];
2082
- case 3:
2083
- _state.sent();
2175
+ metricEvent = _state.sent();
2176
+ publishTracking(licenseKey, "metrics", {
2177
+ events: [
2178
+ metricEvent
2179
+ ]
2180
+ });
2084
2181
  return [
2085
2182
  3,
2086
- 5
2183
+ 4
2087
2184
  ];
2088
- case 4:
2185
+ case 3:
2089
2186
  error = _state.sent();
2090
2187
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
2091
2188
  return [
2092
2189
  3,
2093
- 5
2190
+ 4
2094
2191
  ];
2095
- case 5:
2192
+ case 4:
2096
2193
  return [
2097
2194
  2
2098
2195
  ];
2099
2196
  }
2100
2197
  });
2101
- })();
2198
+ }).apply(this, arguments);
2102
2199
  }
2103
- function sendAdLoadedTracking(licenseKey, adLoadedInfo) {
2104
- return _async_to_generator(function() {
2105
- var clientInfo, browserId, trackingData, error;
2200
+ function sendAdLoadedTracking(_0, _1) {
2201
+ return _async_to_generator(function(licenseKey, adLoadedInfo) {
2202
+ var context, error;
2203
+ var _arguments = arguments;
2106
2204
  return _ts_generator(this, function(_state) {
2107
2205
  switch(_state.label){
2108
2206
  case 0:
2207
+ context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2208
+ _state.label = 1;
2209
+ case 1:
2109
2210
  _state.trys.push([
2110
- 0,
2211
+ 1,
2111
2212
  3,
2112
2213
  ,
2113
2214
  4
2114
2215
  ]);
2115
- clientInfo = getClientInfo();
2116
2216
  return [
2117
2217
  4,
2118
- getBrowserID(clientInfo)
2119
- ];
2120
- case 1:
2121
- browserId = _state.sent();
2122
- trackingData = _object_spread({
2123
- browserId: browserId
2124
- }, clientInfo);
2125
- return [
2126
- 4,
2127
- sendTrackRequest(licenseKey, _object_spread_props(_object_spread({}, trackingData), {
2128
- licenseKey: licenseKey,
2129
- adLoadedInfo: adLoadedInfo
2130
- }))
2218
+ sendHeartbeat(licenseKey, context, {
2219
+ adLoaded: true,
2220
+ captureAt: adLoadedInfo.timestamp
2221
+ })
2131
2222
  ];
2132
2223
  case 2:
2133
2224
  _state.sent();
@@ -2148,39 +2239,46 @@ function sendAdLoadedTracking(licenseKey, adLoadedInfo) {
2148
2239
  ];
2149
2240
  }
2150
2241
  });
2151
- })();
2242
+ }).apply(this, arguments);
2152
2243
  }
2153
- function sendAdImpressionTracking(licenseKey, adImpressionInfo) {
2154
- return _async_to_generator(function() {
2155
- var clientInfo, browserId, trackingData, error;
2244
+ function sendAdImpressionTracking(_0, _1) {
2245
+ return _async_to_generator(function(licenseKey, adImpressionInfo) {
2246
+ var context, metricEvent, error;
2247
+ var _arguments = arguments;
2156
2248
  return _ts_generator(this, function(_state) {
2157
2249
  switch(_state.label){
2158
2250
  case 0:
2251
+ context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2252
+ if (!canPublish(licenseKey)) return [
2253
+ 2
2254
+ ];
2255
+ _state.label = 1;
2256
+ case 1:
2159
2257
  _state.trys.push([
2160
- 0,
2258
+ 1,
2161
2259
  3,
2162
2260
  ,
2163
2261
  4
2164
2262
  ]);
2165
- clientInfo = getClientInfo();
2166
- return [
2167
- 4,
2168
- getBrowserID(clientInfo)
2169
- ];
2170
- case 1:
2171
- browserId = _state.sent();
2172
- trackingData = _object_spread({
2173
- browserId: browserId
2174
- }, clientInfo);
2175
2263
  return [
2176
2264
  4,
2177
- sendTrackRequest(licenseKey, _object_spread_props(_object_spread({}, trackingData), {
2178
- licenseKey: licenseKey,
2179
- adImpressionInfo: adImpressionInfo
2180
- }))
2265
+ buildPlayerMetricEvent(context, {
2266
+ captureAt: adImpressionInfo.timestamp
2267
+ })
2181
2268
  ];
2182
2269
  case 2:
2183
- _state.sent();
2270
+ metricEvent = _state.sent();
2271
+ publishTracking(licenseKey, "heartbeat", metricEvent);
2272
+ publishTracking(licenseKey, "impressions", {
2273
+ events: [
2274
+ {
2275
+ player_id: metricEvent.player_id,
2276
+ ad_played_count: 1,
2277
+ ad_url: adImpressionInfo.adUrl,
2278
+ capture_at: adImpressionInfo.timestamp
2279
+ }
2280
+ ]
2281
+ });
2184
2282
  return [
2185
2283
  3,
2186
2284
  4
@@ -2198,74 +2296,52 @@ function sendAdImpressionTracking(licenseKey, adImpressionInfo) {
2198
2296
  ];
2199
2297
  }
2200
2298
  });
2201
- })();
2299
+ }).apply(this, arguments);
2202
2300
  }
2203
- function sendHeartbeat(licenseKey) {
2204
- return _async_to_generator(function() {
2205
- var clientInfo, browserId, heartbeatData, headers, response, error;
2301
+ function sendHeartbeat(_0) {
2302
+ return _async_to_generator(function(licenseKey) {
2303
+ var context, flags, heartbeatData, error;
2304
+ var _arguments = arguments;
2206
2305
  return _ts_generator(this, function(_state) {
2207
2306
  switch(_state.label){
2208
2307
  case 0:
2308
+ context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2309
+ if (!canPublish(licenseKey)) return [
2310
+ 2
2311
+ ];
2312
+ _state.label = 1;
2313
+ case 1:
2209
2314
  _state.trys.push([
2210
- 0,
2211
- 4,
2315
+ 1,
2316
+ 3,
2212
2317
  ,
2213
- 5
2318
+ 4
2214
2319
  ]);
2215
- clientInfo = getClientInfo();
2216
- return [
2217
- 4,
2218
- getBrowserID(clientInfo)
2219
- ];
2220
- case 1:
2221
- browserId = _state.sent();
2222
- heartbeatData = {
2223
- browserId: browserId,
2224
- timestamp: /* @__PURE__ */ new Date().toISOString()
2225
- };
2226
- headers = {
2227
- "Content-Type": "application/json"
2228
- };
2229
- if (licenseKey) {
2230
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2231
- }
2232
2320
  return [
2233
2321
  4,
2234
- fetch("https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/heartbeat", {
2235
- method: "POST",
2236
- headers: headers,
2237
- body: JSON.stringify(heartbeatData)
2238
- })
2322
+ buildPlayerMetricEvent(context, flags)
2239
2323
  ];
2240
2324
  case 2:
2241
- response = _state.sent();
2242
- if (!response.ok) {
2243
- throw new Error("HTTP error! status: ".concat(response.status));
2244
- }
2245
- return [
2246
- 4,
2247
- response.json()
2248
- ];
2249
- case 3:
2250
- _state.sent();
2325
+ heartbeatData = _state.sent();
2326
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
2251
2327
  return [
2252
2328
  3,
2253
- 5
2329
+ 4
2254
2330
  ];
2255
- case 4:
2331
+ case 3:
2256
2332
  error = _state.sent();
2257
2333
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
2258
2334
  return [
2259
2335
  3,
2260
- 5
2336
+ 4
2261
2337
  ];
2262
- case 5:
2338
+ case 4:
2263
2339
  return [
2264
2340
  2
2265
2341
  ];
2266
2342
  }
2267
2343
  });
2268
- })();
2344
+ }).apply(this, arguments);
2269
2345
  }
2270
2346
  // src/utils/polyfills.ts
2271
2347
  function polyfillURLSearchParams() {
@@ -2827,6 +2903,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2827
2903
  this.config = _object_spread({}, browserOverrides, config);
2828
2904
  this.video = config.videoElement;
2829
2905
  this.adTransitionGapMs = (_this_config_adTransitionGapMs = this.config.adTransitionGapMs) !== null && _this_config_adTransitionGapMs !== void 0 ? _this_config_adTransitionGapMs : 100;
2906
+ if (this.config.mqttConfig) {
2907
+ configureMQTT(this.config.mqttConfig);
2908
+ }
2830
2909
  logBrowserInfo(config.debugAdTiming);
2831
2910
  var browserForAdLayer = detectBrowser();
2832
2911
  var isSinglePipeline = browserForAdLayer.isSmartTV || !!this.config.singlePipelineMode;
@@ -3194,7 +3273,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3194
3273
  source: _this.getAdSource(),
3195
3274
  adIndex: _this.currentAdIndex,
3196
3275
  timestamp: /* @__PURE__ */ new Date().toISOString()
3197
- });
3276
+ }, _this.getPlayerAnalyticsContext());
3198
3277
  }
3199
3278
  });
3200
3279
  this.adLayer.on("ad_error", function(errorPayload) {
@@ -3499,7 +3578,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3499
3578
  key: "initializeTracking",
3500
3579
  value: function initializeTracking() {
3501
3580
  var _this = this;
3502
- sendInitialTracking(this.config.licenseKey).then(function() {
3581
+ sendInitialTracking(this.config.licenseKey, this.getPlayerAnalyticsContext()).then(function() {
3503
3582
  _this.heartbeatInterval = window.setInterval(function() {
3504
3583
  _this.sendHeartbeatIfNeeded();
3505
3584
  }, 5e3);
@@ -3520,7 +3599,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3520
3599
  var now = Date.now();
3521
3600
  if (!this.lastHeartbeatTime || now - this.lastHeartbeatTime > 3e4) {
3522
3601
  this.lastHeartbeatTime = now;
3523
- sendHeartbeat(this.config.licenseKey).catch(function(error) {
3602
+ sendHeartbeat(this.config.licenseKey, this.getPlayerAnalyticsContext()).catch(function(error) {
3524
3603
  if (_this.config.debugAdTiming) {
3525
3604
  console.warn("[StormcloudVideoPlayer] Failed to send heartbeat:", error);
3526
3605
  }
@@ -3614,6 +3693,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3614
3693
  return "other";
3615
3694
  }
3616
3695
  },
3696
+ {
3697
+ key: "getPlayerAnalyticsContext",
3698
+ value: function getPlayerAnalyticsContext() {
3699
+ return {
3700
+ inputStreamType: this.getStreamType()
3701
+ };
3702
+ }
3703
+ },
3617
3704
  {
3618
3705
  key: "shouldShowNativeControls",
3619
3706
  value: function shouldShowNativeControls() {
@@ -4268,7 +4355,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4268
4355
  sendAdLoadedTracking(_this.config.licenseKey, {
4269
4356
  source: _this.getAdSource(),
4270
4357
  timestamp: /* @__PURE__ */ new Date().toISOString()
4271
- });
4358
+ }, _this.getPlayerAnalyticsContext());
4272
4359
  }
4273
4360
  return [
4274
4361
  4,
@@ -4378,7 +4465,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4378
4465
  sendAdLoadedTracking(_this.config.licenseKey, {
4379
4466
  source: _this.getAdSource(),
4380
4467
  timestamp: /* @__PURE__ */ new Date().toISOString()
4381
- });
4468
+ }, _this.getPlayerAnalyticsContext());
4382
4469
  }
4383
4470
  return [
4384
4471
  4,
@@ -4545,7 +4632,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4545
4632
  sendAdLoadedTracking(this.config.licenseKey, {
4546
4633
  source: this.getAdSource(),
4547
4634
  timestamp: /* @__PURE__ */ new Date().toISOString()
4548
- });
4635
+ }, this.getPlayerAnalyticsContext());
4549
4636
  }
4550
4637
  _state.label = 1;
4551
4638
  case 1:
@@ -4744,7 +4831,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4744
4831
  sendAdLoadedTracking(this.config.licenseKey, {
4745
4832
  source: this.getAdSource(),
4746
4833
  timestamp: /* @__PURE__ */ new Date().toISOString()
4747
- });
4834
+ }, this.getPlayerAnalyticsContext());
4748
4835
  }
4749
4836
  return [
4750
4837
  4,
@@ -5689,6 +5776,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5689
5776
  clearInterval(this.heartbeatInterval);
5690
5777
  this.heartbeatInterval = void 0;
5691
5778
  }
5779
+ if (isMQTTEnabled()) {
5780
+ disconnectMQTT();
5781
+ }
5692
5782
  (_this_hls = this.hls) === null || _this_hls === void 0 ? void 0 : _this_hls.destroy();
5693
5783
  (_this_adLayer = this.adLayer) === null || _this_adLayer === void 0 ? void 0 : _this_adLayer.destroy();
5694
5784
  this.consecutiveFailures = 0;