stormcloud-video-player 0.7.9 → 0.7.11

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.
package/lib/index.js CHANGED
@@ -367,7 +367,7 @@ function _ts_values(o) {
367
367
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
368
368
  }
369
369
  // src/ui/StormcloudVideoPlayer.tsx
370
- import React2, { useEffect as useEffect2, useRef as useRef2, useMemo, useCallback as useCallback2 } from "react";
370
+ import React2, { useEffect as useEffect2, useRef as useRef2, useMemo as useMemo2, useCallback as useCallback2 } from "react";
371
371
  // src/player/StormcloudVideoPlayer.ts
372
372
  import Hls from "hls.js";
373
373
  // src/sdk/vastParser.ts
@@ -2802,6 +2802,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2802
2802
  this.isInAdTransition = false;
2803
2803
  this.maxPlaceholderDurationMs = 5e3;
2804
2804
  this.isShowingPlaceholder = false;
2805
+ this.lastAdInsertionPoint = null;
2806
+ this.processedAdInsertionUpdatedAt = null;
2805
2807
  this.totalAdRequestsInBreak = 0;
2806
2808
  this.maxTotalAdRequestsPerBreak = 20;
2807
2809
  this.pendingAdBreak = null;
@@ -2819,6 +2821,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2819
2821
  this.preloadedTokens = [];
2820
2822
  this.debugLogEntries = [];
2821
2823
  this.scteMarkerHistory = [];
2824
+ this.adInsertionDebugHistory = [];
2822
2825
  initializePolyfills();
2823
2826
  var browserOverrides = getBrowserConfigOverrides();
2824
2827
  this.config = _object_spread({}, browserOverrides, config);
@@ -3003,6 +3006,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3003
3006
  _state.sent();
3004
3007
  _state.label = 2;
3005
3008
  case 2:
3009
+ if (!this.config.disableAds && this.config.projectId) {
3010
+ this.startAdInsertionPolling();
3011
+ }
3006
3012
  return [
3007
3013
  2
3008
3014
  ];
@@ -3010,78 +3016,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3010
3016
  });
3011
3017
  }).call(_this);
3012
3018
  });
3013
- this.hls.on(Hls.Events.LEVEL_LOADED, function(_evt, data) {
3019
+ this.hls.on(Hls.Events.LEVEL_LOADED, function() {
3014
3020
  if (_this.inAdBreak || _this.pendingAdBreak) {
3015
3021
  return;
3016
3022
  }
3017
- var details = data === null || data === void 0 ? void 0 : data.details;
3018
- if (!details || !details.fragments || details.fragments.length === 0) {
3019
- return;
3020
- }
3021
- var fragmentsToScan = Math.min(5, details.fragments.length);
3022
- for(var i = 0; i < fragmentsToScan; i++){
3023
- var frag = details.fragments[i];
3024
- var tagList = frag === null || frag === void 0 ? void 0 : frag.tagList;
3025
- if (!Array.isArray(tagList)) continue;
3026
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
3027
- try {
3028
- for(var _iterator = tagList[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
3029
- var entry = _step.value;
3030
- var tag = "";
3031
- var value = "";
3032
- if (Array.isArray(entry)) {
3033
- var _entry_, _entry_1;
3034
- tag = String((_entry_ = entry[0]) !== null && _entry_ !== void 0 ? _entry_ : "");
3035
- value = String((_entry_1 = entry[1]) !== null && _entry_1 !== void 0 ? _entry_1 : "");
3036
- } else if (typeof entry === "string") {
3037
- var idx = entry.indexOf(":");
3038
- if (idx >= 0) {
3039
- tag = entry.substring(0, idx);
3040
- value = entry.substring(idx + 1);
3041
- } else {
3042
- tag = entry;
3043
- }
3044
- }
3045
- if (!tag) continue;
3046
- if (tag.includes("EXT-X-CUE-OUT") || tag.includes("EXT-X-DATERANGE")) {
3047
- var attrs = tag.includes("EXT-X-DATERANGE") ? _this.parseAttributeList(value) : {};
3048
- var hasScteOut = tag.includes("EXT-X-CUE-OUT") || "SCTE35-OUT" in attrs || attrs["SCTE35-OUT"] !== void 0;
3049
- if (hasScteOut) {
3050
- var durationSeconds = _this.parseCueOutDuration(value);
3051
- var marker = _object_spread_props(_object_spread({
3052
- type: "start"
3053
- }, durationSeconds !== void 0 ? {
3054
- durationSeconds: durationSeconds
3055
- } : {}), {
3056
- raw: {
3057
- tag: tag,
3058
- value: value,
3059
- earlyDetection: true
3060
- }
3061
- });
3062
- if (_this.config.debugAdTiming) {
3063
- console.log("[StormcloudVideoPlayer] \uD83C\uDFAF EARLY SCTE-35 DETECTION: Ad break marker found in fragment", i, "- starting pre-fetch (NOT playing yet)");
3064
- }
3065
- _this.startAdPrefetch(marker, frag === null || frag === void 0 ? void 0 : frag.sn);
3066
- return;
3067
- }
3068
- }
3069
- }
3070
- } catch (err) {
3071
- _didIteratorError = true;
3072
- _iteratorError = err;
3073
- } finally{
3074
- try {
3075
- if (!_iteratorNormalCompletion && _iterator.return != null) {
3076
- _iterator.return();
3077
- }
3078
- } finally{
3079
- if (_didIteratorError) {
3080
- throw _iteratorError;
3081
- }
3082
- }
3083
- }
3084
- }
3023
+ _this.checkAdInsertionInManifest();
3085
3024
  });
3086
3025
  this.hls.on(Hls.Events.FRAG_BUFFERED, function(_evt, data) {
3087
3026
  return _async_to_generator(function() {
@@ -3143,124 +3082,42 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3143
3082
  });
3144
3083
  }).call(_this);
3145
3084
  });
3146
- this.hls.on(Hls.Events.FRAG_PARSING_METADATA, function(_evt, data) {
3147
- var id3Tags = ((data === null || data === void 0 ? void 0 : data.samples) || []).map(function(s) {
3148
- return {
3149
- key: "ID3",
3150
- value: s === null || s === void 0 ? void 0 : s.data,
3151
- ptsSeconds: s === null || s === void 0 ? void 0 : s.pts
3152
- };
3153
- });
3154
- id3Tags.forEach(function(tag) {
3155
- return _this.onId3Tag(tag);
3156
- });
3157
- });
3158
3085
  this.hls.on(Hls.Events.FRAG_CHANGED, function(_evt, data) {
3159
3086
  var frag = data === null || data === void 0 ? void 0 : data.frag;
3160
- var tagList = frag === null || frag === void 0 ? void 0 : frag.tagList;
3161
- if (!Array.isArray(tagList)) return;
3162
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
3163
- try {
3164
- for(var _iterator = tagList[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
3165
- var entry = _step.value;
3166
- var tag = "";
3167
- var value = "";
3168
- if (Array.isArray(entry)) {
3169
- var _entry_, _entry_1;
3170
- tag = String((_entry_ = entry[0]) !== null && _entry_ !== void 0 ? _entry_ : "");
3171
- value = String((_entry_1 = entry[1]) !== null && _entry_1 !== void 0 ? _entry_1 : "");
3172
- } else if (typeof entry === "string") {
3173
- var idx = entry.indexOf(":");
3174
- if (idx >= 0) {
3175
- tag = entry.substring(0, idx);
3176
- value = entry.substring(idx + 1);
3177
- } else {
3178
- tag = entry;
3179
- value = "";
3180
- }
3087
+ if (!frag) return;
3088
+ if (_this.lastAdInsertionPoint && !_this.inAdBreak && _this.lastAdInsertionPoint.updated_at !== _this.processedAdInsertionUpdatedAt) {
3089
+ var segmentName = _this.lastAdInsertionPoint.segment_ts_name;
3090
+ if (_this.fragmentMatchesSegment(frag, segmentName)) {
3091
+ var _ref;
3092
+ _this.processedAdInsertionUpdatedAt = _this.lastAdInsertionPoint.updated_at;
3093
+ var offsetMs = (_this.lastAdInsertionPoint.offset_seconds || 0) * 1e3;
3094
+ _this.pushAdInsertionDebug("segment_playing", segmentName, {
3095
+ offsetSeconds: _this.lastAdInsertionPoint.offset_seconds,
3096
+ detail: "sn=".concat((_ref = frag === null || frag === void 0 ? void 0 : frag.sn) !== null && _ref !== void 0 ? _ref : "?")
3097
+ });
3098
+ if (_this.config.debugAdTiming) {
3099
+ console.log('[StormcloudVideoPlayer] Ad insertion segment "'.concat(segmentName, '" now playing — scheduling ad start in ').concat(offsetMs, "ms"));
3181
3100
  }
3182
- if (!tag) continue;
3183
- if (tag.includes("EXT-X-CUE-OUT-CONT")) {
3184
- var prog = _this.parseCueOutCont(value);
3185
- var marker = _object_spread_props(_object_spread({
3186
- type: "progress"
3187
- }, (prog === null || prog === void 0 ? void 0 : prog.duration) !== void 0 ? {
3188
- durationSeconds: prog.duration
3189
- } : {}, (prog === null || prog === void 0 ? void 0 : prog.elapsed) !== void 0 ? {
3190
- ptsSeconds: prog.elapsed
3191
- } : {}), {
3192
- raw: {
3193
- tag: tag,
3194
- value: value
3195
- }
3196
- });
3197
- _this.onScte35Marker(marker);
3198
- } else if (tag.includes("EXT-X-CUE-OUT")) {
3199
- var durationSeconds = _this.parseCueOutDuration(value);
3200
- var marker1 = _object_spread_props(_object_spread({
3201
- type: "start"
3202
- }, durationSeconds !== void 0 ? {
3203
- durationSeconds: durationSeconds
3204
- } : {}), {
3205
- raw: {
3206
- tag: tag,
3207
- value: value
3208
- }
3101
+ _this.pushAdInsertionDebug("ad_scheduled", segmentName, {
3102
+ offsetSeconds: _this.lastAdInsertionPoint.offset_seconds,
3103
+ detail: "in ".concat(offsetMs, "ms, dur=60s")
3104
+ });
3105
+ _this.clearAdInsertionOffsetTimer();
3106
+ _this.adInsertionOffsetTimerId = window.setTimeout(function() {
3107
+ _this.adInsertionOffsetTimerId = void 0;
3108
+ if (_this.inAdBreak) return;
3109
+ _this.pushAdInsertionDebug("ad_triggered", segmentName, {
3110
+ detail: "ad break started (60s)"
3209
3111
  });
3210
- _this.onScte35Marker(marker1);
3211
- } else if (tag.includes("EXT-X-CUE-IN")) {
3212
- _this.onScte35Marker({
3213
- type: "end",
3112
+ var marker = {
3113
+ type: "start",
3114
+ durationSeconds: 60,
3214
3115
  raw: {
3215
- tag: tag,
3216
- value: value
3116
+ apiInsertionPoint: _this.lastAdInsertionPoint
3217
3117
  }
3218
- });
3219
- } else if (tag.includes("EXT-X-DATERANGE")) {
3220
- var _attrs_CLASS;
3221
- var attrs = _this.parseAttributeList(value);
3222
- var hasScteOut = "SCTE35-OUT" in attrs || attrs["SCTE35-OUT"] !== void 0;
3223
- var hasScteIn = "SCTE35-IN" in attrs || attrs["SCTE35-IN"] !== void 0;
3224
- var klass = String((_attrs_CLASS = attrs["CLASS"]) !== null && _attrs_CLASS !== void 0 ? _attrs_CLASS : "");
3225
- var duration = _this.toNumber(attrs["DURATION"]);
3226
- if (hasScteOut || /com\.apple\.hls\.cue/i.test(klass)) {
3227
- var marker2 = _object_spread_props(_object_spread({
3228
- type: "start"
3229
- }, duration !== void 0 ? {
3230
- durationSeconds: duration
3231
- } : {}), {
3232
- raw: {
3233
- tag: tag,
3234
- value: value,
3235
- attrs: attrs
3236
- }
3237
- });
3238
- _this.onScte35Marker(marker2);
3239
- }
3240
- if (hasScteIn) {
3241
- _this.onScte35Marker({
3242
- type: "end",
3243
- raw: {
3244
- tag: tag,
3245
- value: value,
3246
- attrs: attrs
3247
- }
3248
- });
3249
- }
3250
- }
3251
- }
3252
- } catch (err) {
3253
- _didIteratorError = true;
3254
- _iteratorError = err;
3255
- } finally{
3256
- try {
3257
- if (!_iteratorNormalCompletion && _iterator.return != null) {
3258
- _iterator.return();
3259
- }
3260
- } finally{
3261
- if (_didIteratorError) {
3262
- throw _iteratorError;
3263
- }
3118
+ };
3119
+ _this.onScte35Marker(marker);
3120
+ }, offsetMs);
3264
3121
  }
3265
3122
  }
3266
3123
  });
@@ -4460,6 +4317,182 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4460
4317
  this.preloadedTokens = [];
4461
4318
  }
4462
4319
  },
4320
+ {
4321
+ key: "startAdInsertionPolling",
4322
+ value: function startAdInsertionPolling() {
4323
+ var _this = this;
4324
+ if (this.adInsertionPollingId != null) return;
4325
+ this.fetchAdInsertionPoint();
4326
+ this.adInsertionPollingId = window.setInterval(function() {
4327
+ _this.fetchAdInsertionPoint();
4328
+ }, 1e3);
4329
+ }
4330
+ },
4331
+ {
4332
+ key: "stopAdInsertionPolling",
4333
+ value: function stopAdInsertionPolling() {
4334
+ if (this.adInsertionPollingId != null) {
4335
+ clearInterval(this.adInsertionPollingId);
4336
+ this.adInsertionPollingId = void 0;
4337
+ }
4338
+ }
4339
+ },
4340
+ {
4341
+ key: "fetchAdInsertionPoint",
4342
+ value: function fetchAdInsertionPoint() {
4343
+ return _async_to_generator(function() {
4344
+ var _this_lastAdInsertionPoint, resp, data, isNew, unused;
4345
+ return _ts_generator(this, function(_state) {
4346
+ switch(_state.label){
4347
+ case 0:
4348
+ if (!this.config.projectId) return [
4349
+ 2
4350
+ ];
4351
+ _state.label = 1;
4352
+ case 1:
4353
+ _state.trys.push([
4354
+ 1,
4355
+ 4,
4356
+ ,
4357
+ 5
4358
+ ]);
4359
+ return [
4360
+ 4,
4361
+ fetch("https://adstorm.co/api-adstorm-dev/adstorm/swirl/projects/".concat(encodeURIComponent(this.config.projectId), "/ad-insertion-point"))
4362
+ ];
4363
+ case 2:
4364
+ resp = _state.sent();
4365
+ if (!resp.ok) return [
4366
+ 2
4367
+ ];
4368
+ return [
4369
+ 4,
4370
+ resp.json()
4371
+ ];
4372
+ case 3:
4373
+ data = _state.sent();
4374
+ isNew = data.updated_at !== ((_this_lastAdInsertionPoint = this.lastAdInsertionPoint) === null || _this_lastAdInsertionPoint === void 0 ? void 0 : _this_lastAdInsertionPoint.updated_at);
4375
+ this.lastAdInsertionPoint = data;
4376
+ if (isNew) {
4377
+ this.pushAdInsertionDebug("api_response", data.segment_ts_name, {
4378
+ offsetSeconds: data.offset_seconds,
4379
+ updatedAt: data.updated_at,
4380
+ detail: "project=".concat(data.project_id)
4381
+ });
4382
+ }
4383
+ if (this.config.debugAdTiming) {
4384
+ console.log("[StormcloudVideoPlayer] Ad insertion point API response:", data);
4385
+ }
4386
+ return [
4387
+ 3,
4388
+ 5
4389
+ ];
4390
+ case 4:
4391
+ unused = _state.sent();
4392
+ if (this.config.debugAdTiming) {
4393
+ console.warn("[StormcloudVideoPlayer] Ad insertion point API fetch failed");
4394
+ }
4395
+ return [
4396
+ 3,
4397
+ 5
4398
+ ];
4399
+ case 5:
4400
+ return [
4401
+ 2
4402
+ ];
4403
+ }
4404
+ });
4405
+ }).call(this);
4406
+ }
4407
+ },
4408
+ {
4409
+ key: "checkAdInsertionInManifest",
4410
+ value: function checkAdInsertionInManifest() {
4411
+ var _this_hls;
4412
+ if (!this.lastAdInsertionPoint) return;
4413
+ if (this.inAdBreak || this.pendingAdBreak) return;
4414
+ if (this.lastAdInsertionPoint.updated_at === this.processedAdInsertionUpdatedAt) return;
4415
+ var segmentName = this.lastAdInsertionPoint.segment_ts_name;
4416
+ var levels = (_this_hls = this.hls) === null || _this_hls === void 0 ? void 0 : _this_hls.levels;
4417
+ if (!levels) return;
4418
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
4419
+ try {
4420
+ for(var _iterator = levels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
4421
+ var level = _step.value;
4422
+ var _level_details;
4423
+ var fragments = (_level_details = level.details) === null || _level_details === void 0 ? void 0 : _level_details.fragments;
4424
+ if (!Array.isArray(fragments)) continue;
4425
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
4426
+ try {
4427
+ for(var _iterator1 = fragments[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
4428
+ var frag = _step1.value;
4429
+ if (this.fragmentMatchesSegment(frag, segmentName)) {
4430
+ var _ref;
4431
+ this.pushAdInsertionDebug("segment_found", segmentName, {
4432
+ detail: "sn=".concat((_ref = frag === null || frag === void 0 ? void 0 : frag.sn) !== null && _ref !== void 0 ? _ref : "?")
4433
+ });
4434
+ if (this.config.debugAdTiming) {
4435
+ console.log('[StormcloudVideoPlayer] Ad insertion segment "'.concat(segmentName, '" found in manifest — starting pre-fetch'));
4436
+ }
4437
+ var marker = {
4438
+ type: "start",
4439
+ durationSeconds: 60,
4440
+ raw: {
4441
+ apiInsertionPoint: this.lastAdInsertionPoint,
4442
+ earlyDetection: true
4443
+ }
4444
+ };
4445
+ this.startAdPrefetch(marker, frag === null || frag === void 0 ? void 0 : frag.sn);
4446
+ return;
4447
+ }
4448
+ }
4449
+ } catch (err) {
4450
+ _didIteratorError1 = true;
4451
+ _iteratorError1 = err;
4452
+ } finally{
4453
+ try {
4454
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
4455
+ _iterator1.return();
4456
+ }
4457
+ } finally{
4458
+ if (_didIteratorError1) {
4459
+ throw _iteratorError1;
4460
+ }
4461
+ }
4462
+ }
4463
+ }
4464
+ } catch (err) {
4465
+ _didIteratorError = true;
4466
+ _iteratorError = err;
4467
+ } finally{
4468
+ try {
4469
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
4470
+ _iterator.return();
4471
+ }
4472
+ } finally{
4473
+ if (_didIteratorError) {
4474
+ throw _iteratorError;
4475
+ }
4476
+ }
4477
+ }
4478
+ }
4479
+ },
4480
+ {
4481
+ key: "fragmentMatchesSegment",
4482
+ value: function fragmentMatchesSegment(frag, segmentName) {
4483
+ var url = (frag === null || frag === void 0 ? void 0 : frag.url) || (frag === null || frag === void 0 ? void 0 : frag.relurl) || "";
4484
+ return url.endsWith(segmentName) || url.includes("/" + segmentName);
4485
+ }
4486
+ },
4487
+ {
4488
+ key: "clearAdInsertionOffsetTimer",
4489
+ value: function clearAdInsertionOffsetTimer() {
4490
+ if (this.adInsertionOffsetTimerId != null) {
4491
+ clearTimeout(this.adInsertionOffsetTimerId);
4492
+ this.adInsertionOffsetTimerId = void 0;
4493
+ }
4494
+ }
4495
+ },
4463
4496
  {
4464
4497
  key: "startContinuousFetchLoop",
4465
4498
  value: function startContinuousFetchLoop() {
@@ -5381,6 +5414,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5381
5414
  }
5382
5415
  this.clearAdRequestWatchdog();
5383
5416
  this.clearAdFailsafeTimer();
5417
+ this.clearAdInsertionOffsetTimer();
5384
5418
  this.activeAdRequestToken = null;
5385
5419
  this.isInAdTransition = false;
5386
5420
  this.stopContinuousFetching();
@@ -5711,6 +5745,32 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5711
5745
  });
5712
5746
  }
5713
5747
  },
5748
+ {
5749
+ key: "pushAdInsertionDebug",
5750
+ value: function pushAdInsertionDebug(event, segmentName, opts) {
5751
+ if (!this.config.debugAdTiming) return;
5752
+ this.adInsertionDebugHistory.push(_object_spread({
5753
+ timestampMs: Date.now(),
5754
+ event: event,
5755
+ segmentName: segmentName
5756
+ }, (opts === null || opts === void 0 ? void 0 : opts.offsetSeconds) !== void 0 ? {
5757
+ offsetSeconds: opts.offsetSeconds
5758
+ } : {}, (opts === null || opts === void 0 ? void 0 : opts.updatedAt) ? {
5759
+ updatedAt: opts.updatedAt
5760
+ } : {}, (opts === null || opts === void 0 ? void 0 : opts.detail) ? {
5761
+ detail: opts.detail
5762
+ } : {}));
5763
+ if (this.adInsertionDebugHistory.length > DEBUG_HISTORY_LIMIT) {
5764
+ this.adInsertionDebugHistory = this.adInsertionDebugHistory.slice(-DEBUG_HISTORY_LIMIT);
5765
+ }
5766
+ }
5767
+ },
5768
+ {
5769
+ key: "getAdInsertionDebugLog",
5770
+ value: function getAdInsertionDebugLog() {
5771
+ return this.adInsertionDebugHistory.slice();
5772
+ }
5773
+ },
5714
5774
  {
5715
5775
  key: "getDebugLogs",
5716
5776
  value: function getDebugLogs() {
@@ -5977,6 +6037,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5977
6037
  key: "destroy",
5978
6038
  value: function destroy() {
5979
6039
  var _this_hls, _this_adLayer;
6040
+ this.stopAdInsertionPolling();
6041
+ this.clearAdInsertionOffsetTimer();
5980
6042
  this.stopContinuousFetching();
5981
6043
  this.stopFillerBreakTimer();
5982
6044
  this.clearAdStartTimer();
@@ -6008,6 +6070,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6008
6070
  this.consecutiveFailures = 0;
6009
6071
  this.debugLogEntries = [];
6010
6072
  this.scteMarkerHistory = [];
6073
+ this.adInsertionDebugHistory = [];
6011
6074
  }
6012
6075
  }
6013
6076
  ]);
@@ -6016,7 +6079,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6016
6079
  // src/ui/StormcloudVideoPlayer.tsx
6017
6080
  import { FaPlay, FaPause, FaVolumeUp, FaVolumeMute, FaVolumeDown, FaExpand, FaCompress, FaCog, FaTimes } from "react-icons/fa";
6018
6081
  // src/ui/OverlayRenderer.tsx
6019
- import React, { useEffect, useRef, useState, useCallback } from "react";
6082
+ import React, { useEffect, useRef, useState, useCallback, useMemo } from "react";
6020
6083
  // src/utils/overlays.ts
6021
6084
  var OVERLAY_API_BASE = "https://adstorm.co/api-adstorm-dev";
6022
6085
  function timeStringToSeconds(timeStr) {
@@ -7065,10 +7128,13 @@ function hexToRgb(hex) {
7065
7128
  var num = parseInt(clean.length === 3 ? clean.replace(/./g, "$&$&") : clean, 16);
7066
7129
  return "".concat(num >> 16 & 255, ",").concat(num >> 8 & 255, ",").concat(num & 255);
7067
7130
  }
7131
+ var FADE_DURATION_MS = 1e3;
7068
7132
  var OverlayRenderer = function OverlayRenderer(param) {
7069
7133
  var overlays = param.overlays, currentTime = param.currentTime, videoRef = param.videoRef, coordinateSpace = param.coordinateSpace;
7070
7134
  var _useState = _sliced_to_array(useState(null), 2), dims = _useState[0], setDims = _useState[1];
7071
7135
  var rafRef = useRef(null);
7136
+ var _useState1 = _sliced_to_array(useState(/* @__PURE__ */ new Map()), 2), fadeMap = _useState1[0], setFadeMap = _useState1[1];
7137
+ var removeTimers = useRef(/* @__PURE__ */ new Map());
7072
7138
  var updateDims = useCallback(function() {
7073
7139
  var video = videoRef.current;
7074
7140
  if (video) {
@@ -7099,10 +7165,181 @@ var OverlayRenderer = function OverlayRenderer(param) {
7099
7165
  }, [
7100
7166
  updateDims
7101
7167
  ]);
7102
- var activeOverlays = overlays.filter(function(o) {
7103
- return isOverlayActive(o, currentTime);
7104
- });
7105
- if (!dims || activeOverlays.length === 0) return null;
7168
+ var activeOverlays = useMemo(function() {
7169
+ return overlays.filter(function(o) {
7170
+ return isOverlayActive(o, currentTime);
7171
+ });
7172
+ }, [
7173
+ overlays,
7174
+ currentTime
7175
+ ]);
7176
+ useEffect(function() {
7177
+ var activeIds = new Set(activeOverlays.map(function(o) {
7178
+ return o.id;
7179
+ }));
7180
+ setFadeMap(function(prev) {
7181
+ var next = new Map(prev);
7182
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7183
+ try {
7184
+ for(var _iterator = activeOverlays[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
7185
+ var overlay = _step.value;
7186
+ if (!next.has(overlay.id)) {
7187
+ next.set(overlay.id, {
7188
+ overlay: overlay,
7189
+ visible: false
7190
+ });
7191
+ } else {
7192
+ var existing = next.get(overlay.id);
7193
+ next.set(overlay.id, _object_spread_props(_object_spread({}, existing), {
7194
+ overlay: overlay
7195
+ }));
7196
+ }
7197
+ }
7198
+ } catch (err) {
7199
+ _didIteratorError = true;
7200
+ _iteratorError = err;
7201
+ } finally{
7202
+ try {
7203
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
7204
+ _iterator.return();
7205
+ }
7206
+ } finally{
7207
+ if (_didIteratorError) {
7208
+ throw _iteratorError;
7209
+ }
7210
+ }
7211
+ }
7212
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
7213
+ try {
7214
+ var _loop = function() {
7215
+ var _step_value = _sliced_to_array(_step1.value, 2), id = _step_value[0], state = _step_value[1];
7216
+ if (!activeIds.has(id) && state.visible) {
7217
+ next.set(id, _object_spread_props(_object_spread({}, state), {
7218
+ visible: false
7219
+ }));
7220
+ if (!removeTimers.current.has(id)) {
7221
+ var timer = setTimeout(function() {
7222
+ setFadeMap(function(m) {
7223
+ var updated = new Map(m);
7224
+ updated.delete(id);
7225
+ return updated;
7226
+ });
7227
+ removeTimers.current.delete(id);
7228
+ }, FADE_DURATION_MS);
7229
+ removeTimers.current.set(id, timer);
7230
+ }
7231
+ } else if (!activeIds.has(id) && !state.visible) {}
7232
+ };
7233
+ for(var _iterator1 = next[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true)_loop();
7234
+ } catch (err) {
7235
+ _didIteratorError1 = true;
7236
+ _iteratorError1 = err;
7237
+ } finally{
7238
+ try {
7239
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
7240
+ _iterator1.return();
7241
+ }
7242
+ } finally{
7243
+ if (_didIteratorError1) {
7244
+ throw _iteratorError1;
7245
+ }
7246
+ }
7247
+ }
7248
+ return next;
7249
+ });
7250
+ }, [
7251
+ activeOverlays
7252
+ ]);
7253
+ useEffect(function() {
7254
+ var toFadeIn = [];
7255
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7256
+ try {
7257
+ var _loop = function() {
7258
+ var _step_value = _sliced_to_array(_step.value, 2), id = _step_value[0], state = _step_value[1];
7259
+ if (!state.visible) {
7260
+ var isActive = activeOverlays.some(function(o) {
7261
+ return o.id === id;
7262
+ });
7263
+ if (isActive) toFadeIn.push(id);
7264
+ }
7265
+ };
7266
+ for(var _iterator = fadeMap[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
7267
+ } catch (err) {
7268
+ _didIteratorError = true;
7269
+ _iteratorError = err;
7270
+ } finally{
7271
+ try {
7272
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
7273
+ _iterator.return();
7274
+ }
7275
+ } finally{
7276
+ if (_didIteratorError) {
7277
+ throw _iteratorError;
7278
+ }
7279
+ }
7280
+ }
7281
+ if (toFadeIn.length === 0) return;
7282
+ var raf = requestAnimationFrame(function() {
7283
+ setFadeMap(function(prev) {
7284
+ var next = new Map(prev);
7285
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7286
+ try {
7287
+ for(var _iterator = toFadeIn[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
7288
+ var id = _step.value;
7289
+ var state = next.get(id);
7290
+ if (state) next.set(id, _object_spread_props(_object_spread({}, state), {
7291
+ visible: true
7292
+ }));
7293
+ }
7294
+ } catch (err) {
7295
+ _didIteratorError = true;
7296
+ _iteratorError = err;
7297
+ } finally{
7298
+ try {
7299
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
7300
+ _iterator.return();
7301
+ }
7302
+ } finally{
7303
+ if (_didIteratorError) {
7304
+ throw _iteratorError;
7305
+ }
7306
+ }
7307
+ }
7308
+ return next;
7309
+ });
7310
+ });
7311
+ return function() {
7312
+ return cancelAnimationFrame(raf);
7313
+ };
7314
+ }, [
7315
+ fadeMap,
7316
+ activeOverlays
7317
+ ]);
7318
+ useEffect(function() {
7319
+ return function() {
7320
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7321
+ try {
7322
+ for(var _iterator = removeTimers.current.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
7323
+ var timer = _step.value;
7324
+ clearTimeout(timer);
7325
+ }
7326
+ } catch (err) {
7327
+ _didIteratorError = true;
7328
+ _iteratorError = err;
7329
+ } finally{
7330
+ try {
7331
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
7332
+ _iterator.return();
7333
+ }
7334
+ } finally{
7335
+ if (_didIteratorError) {
7336
+ throw _iteratorError;
7337
+ }
7338
+ }
7339
+ }
7340
+ };
7341
+ }, []);
7342
+ if (!dims || fadeMap.size === 0) return null;
7106
7343
  return /* @__PURE__ */ jsx("div", {
7107
7344
  "aria-hidden": "true",
7108
7345
  style: {
@@ -7115,14 +7352,16 @@ var OverlayRenderer = function OverlayRenderer(param) {
7115
7352
  overflow: "hidden",
7116
7353
  zIndex: 8
7117
7354
  },
7118
- children: activeOverlays.map(function(overlay) {
7355
+ children: _to_consumable_array(fadeMap.values()).map(function(param) {
7356
+ var overlay = param.overlay, visible = param.visible;
7119
7357
  var scaleX = (coordinateSpace === null || coordinateSpace === void 0 ? void 0 : coordinateSpace.width) ? dims.displayWidth / coordinateSpace.width : dims.scaleX;
7120
7358
  var scaleY = (coordinateSpace === null || coordinateSpace === void 0 ? void 0 : coordinateSpace.height) ? dims.displayHeight / coordinateSpace.height : dims.scaleY;
7121
7359
  var left = overlay.x * scaleX;
7122
7360
  var top = overlay.y * scaleY;
7123
7361
  var width = overlay.width * scaleX;
7124
7362
  var height = overlay.height * scaleY;
7125
- var opacity = Math.max(0, Math.min(100, overlay.opacity)) / 100;
7363
+ var baseOpacity = Math.max(0, Math.min(100, overlay.opacity)) / 100;
7364
+ var opacity = visible ? baseOpacity : 0;
7126
7365
  var sz = {
7127
7366
  w: width,
7128
7367
  h: height
@@ -7135,6 +7374,7 @@ var OverlayRenderer = function OverlayRenderer(param) {
7135
7374
  width: "".concat(width, "px"),
7136
7375
  height: "".concat(height, "px"),
7137
7376
  opacity: opacity,
7377
+ transition: "opacity ".concat(FADE_DURATION_MS, "ms ease"),
7138
7378
  zIndex: overlay.z_index,
7139
7379
  overflow: "hidden"
7140
7380
  },
@@ -7207,7 +7447,7 @@ var AI_CONTEXT_MIN_POLL_MS = 800;
7207
7447
  var PANEL_BASE_RIGHT_OFFSET = 10;
7208
7448
  var StormcloudVideoPlayerComponent = React2.memo(function(props) {
7209
7449
  var _ref;
7210
- var _aiLiveContext_context, _aiLiveContext_context_keywords, _aiLiveContext_context1;
7450
+ var _aiLiveContext_context;
7211
7451
  var src = props.src, autoplay = props.autoplay, muted = props.muted, lowLatencyMode = props.lowLatencyMode, allowNativeHls = props.allowNativeHls, driftToleranceMs = props.driftToleranceMs, immediateManifestAds = props.immediateManifestAds, debugAdTiming = props.debugAdTiming, showCustomControls = props.showCustomControls, hideLoadingIndicator = props.hideLoadingIndicator, onVolumeToggle = props.onVolumeToggle, onFullscreenToggle = props.onFullscreenToggle, onControlClick = props.onControlClick, onReady = props.onReady, wrapperClassName = props.wrapperClassName, wrapperStyle = props.wrapperStyle, className = props.className, style = props.style, controls = props.controls, playsInline = props.playsInline, preload = props.preload, poster = props.poster, children = props.children, licenseKey = props.licenseKey, minSegmentsBeforePlay = props.minSegmentsBeforePlay, disableAds = props.disableAds, disableFiller = props.disableFiller, swirlProjectId = props.swirlProjectId, restVideoAttrs = _object_without_properties(props, [
7212
7452
  "src",
7213
7453
  "autoplay",
@@ -7299,13 +7539,39 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
7299
7539
  return "--:--:--";
7300
7540
  }
7301
7541
  };
7302
- var formatDebugRaw = function formatDebugRaw(raw) {
7303
- if (!raw || (typeof raw === "undefined" ? "undefined" : _type_of(raw)) !== "object") return "";
7304
- var obj = raw;
7305
- if (typeof obj.tag === "string") return obj.tag;
7306
- if (typeof obj.id3 === "string") return "ID3";
7307
- if (typeof obj.splice_command_type === "number") return "binary splice";
7308
- return "marker";
7542
+ var formatAdInsertionEvent = function formatAdInsertionEvent(event) {
7543
+ switch(event){
7544
+ case "api_response":
7545
+ return {
7546
+ label: "API",
7547
+ color: "#60a5fa"
7548
+ };
7549
+ case "segment_found":
7550
+ return {
7551
+ label: "FOUND",
7552
+ color: "#a78bfa"
7553
+ };
7554
+ case "segment_playing":
7555
+ return {
7556
+ label: "PLAYING",
7557
+ color: "#fbbf24"
7558
+ };
7559
+ case "ad_scheduled":
7560
+ return {
7561
+ label: "SCHED",
7562
+ color: "#fb923c"
7563
+ };
7564
+ case "ad_triggered":
7565
+ return {
7566
+ label: "TRIGGER",
7567
+ color: "#34d399"
7568
+ };
7569
+ default:
7570
+ return {
7571
+ label: event,
7572
+ color: "rgba(255,255,255,0.68)"
7573
+ };
7574
+ }
7309
7575
  };
7310
7576
  var formatAiRelativeTime = function formatAiRelativeTime(timestamp) {
7311
7577
  var epochMs = Date.parse(timestamp);
@@ -7404,7 +7670,7 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
7404
7670
  var debugPanelBottomOffset = shouldShowEnhancedControls ? Math.max(74, 92 * responsiveScale) : Math.max(52, 58 * responsiveScale);
7405
7671
  var panelBaseRight = PANEL_BASE_RIGHT_OFFSET * responsiveScale;
7406
7672
  var debugPanelRightOffset = showAiPanel && !shouldStackPanels ? panelBaseRight + analyzerPanelWidth + panelGap : panelBaseRight;
7407
- var criticalPropsKey = useMemo(function() {
7673
+ var criticalPropsKey = useMemo2(function() {
7408
7674
  return CRITICAL_PROPS.map(function(prop) {
7409
7675
  return "".concat(prop, ":").concat(props[prop]);
7410
7676
  }).join("|");
@@ -7718,7 +7984,7 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
7718
7984
  var updateDebugData = function updateDebugData() {
7719
7985
  var player = playerRef.current;
7720
7986
  if (!player) return;
7721
- setDebugMarkers(player.getRecentScteMarkers().slice(-DEBUG_PANEL_MARKER_LIMIT).reverse());
7987
+ setDebugMarkers(player.getAdInsertionDebugLog().slice(-DEBUG_PANEL_MARKER_LIMIT).reverse());
7722
7988
  };
7723
7989
  updateDebugData();
7724
7990
  var interval = window.setInterval(updateDebugData, 500);
@@ -8253,34 +8519,7 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
8253
8519
  whiteSpace: "pre-wrap"
8254
8520
  },
8255
8521
  children: (_ref = (_aiLiveContext_context = aiLiveContext.context) === null || _aiLiveContext_context === void 0 ? void 0 : _aiLiveContext_context.context) !== null && _ref !== void 0 ? _ref : aiLiveContext.isLoading ? "Analyzing live stream..." : "Waiting for AI context response."
8256
- }),
8257
- ((_aiLiveContext_context1 = aiLiveContext.context) === null || _aiLiveContext_context1 === void 0 ? void 0 : (_aiLiveContext_context_keywords = _aiLiveContext_context1.keywords) === null || _aiLiveContext_context_keywords === void 0 ? void 0 : _aiLiveContext_context_keywords.length) ? /* @__PURE__ */ jsx2("div", {
8258
- style: {
8259
- marginTop: "10px",
8260
- display: "flex",
8261
- flexWrap: "wrap",
8262
- gap: "6px"
8263
- },
8264
- children: aiLiveContext.context.keywords.slice(0, 12).map(function(kw) {
8265
- return /* @__PURE__ */ jsx2("span", {
8266
- style: {
8267
- fontSize: "10px",
8268
- fontWeight: 600,
8269
- padding: "4px 7px",
8270
- borderRadius: "999px",
8271
- background: "rgba(236, 72, 153, 0.2)",
8272
- border: "1px solid rgba(244, 114, 182, 0.42)",
8273
- color: "#fce7f3",
8274
- maxWidth: "100%",
8275
- overflow: "hidden",
8276
- textOverflow: "ellipsis",
8277
- whiteSpace: "nowrap"
8278
- },
8279
- title: kw,
8280
- children: kw
8281
- }, kw);
8282
- })
8283
- }) : null
8522
+ })
8284
8523
  ]
8285
8524
  })
8286
8525
  ]
@@ -8322,7 +8561,7 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
8322
8561
  fontWeight: 700,
8323
8562
  letterSpacing: "0.02em"
8324
8563
  },
8325
- children: "Debug Ad Timing"
8564
+ children: "Ad Insertion Debug"
8326
8565
  }),
8327
8566
  /* @__PURE__ */ jsx2("button", {
8328
8567
  className: "sc-ctrl-btn",
@@ -8363,7 +8602,7 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
8363
8602
  color: "rgba(255,255,255,0.68)",
8364
8603
  marginBottom: "8px"
8365
8604
  },
8366
- children: "SCTE-35 markers"
8605
+ children: "Ad Insertion Points"
8367
8606
  }),
8368
8607
  /* @__PURE__ */ jsx2("div", {
8369
8608
  style: {
@@ -8375,12 +8614,13 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
8375
8614
  fontSize: "12px",
8376
8615
  color: "rgba(255,255,255,0.55)"
8377
8616
  },
8378
- children: "No markers detected yet."
8617
+ children: "No ad insertion events yet."
8379
8618
  }) : debugMarkers.map(function(entry, idx) {
8619
+ var evt = formatAdInsertionEvent(entry.event);
8380
8620
  return /* @__PURE__ */ jsxs2("div", {
8381
8621
  style: {
8382
8622
  display: "grid",
8383
- gridTemplateColumns: "56px 52px 1fr",
8623
+ gridTemplateColumns: "56px 54px 1fr",
8384
8624
  gap: "8px",
8385
8625
  alignItems: "center",
8386
8626
  fontFamily: "'SF Mono', 'Cascadia Code', monospace",
@@ -8399,11 +8639,11 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
8399
8639
  }),
8400
8640
  /* @__PURE__ */ jsx2("span", {
8401
8641
  style: {
8402
- textTransform: "uppercase",
8403
8642
  fontWeight: 700,
8404
- color: entry.type === "start" ? "#34d399" : entry.type === "end" ? "#f87171" : "#fbbf24"
8643
+ color: evt.color,
8644
+ fontSize: "10px"
8405
8645
  },
8406
- children: entry.type
8646
+ children: evt.label
8407
8647
  }),
8408
8648
  /* @__PURE__ */ jsx2("span", {
8409
8649
  style: {
@@ -8419,11 +8659,9 @@ var StormcloudVideoPlayerComponent = React2.memo(function(props) {
8419
8659
  whiteSpace: "nowrap"
8420
8660
  },
8421
8661
  children: [
8422
- entry.durationSeconds != null ? "dur:".concat(entry.durationSeconds.toFixed(2), "s") : "dur:-",
8423
- " ",
8424
- entry.ptsSeconds != null ? "pts:".concat(entry.ptsSeconds.toFixed(2)) : "pts:-",
8425
- " ",
8426
- formatDebugRaw(entry.raw)
8662
+ entry.segmentName,
8663
+ entry.offsetSeconds != null ? " +".concat(entry.offsetSeconds, "s") : "",
8664
+ entry.detail ? " (".concat(entry.detail, ")") : ""
8427
8665
  ]
8428
8666
  })
8429
8667
  })