stormcloud-video-player 0.2.31 → 0.2.32

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.
@@ -28,6 +28,9 @@ declare class StormcloudVideoPlayer {
28
28
  private hasInitialBufferCompleted;
29
29
  private adPodAllUrls;
30
30
  private preloadingAdUrls;
31
+ private vastToMediaUrlMap;
32
+ private preloadedMediaUrls;
33
+ private preloadingMediaUrls;
31
34
  constructor(config: StormcloudVideoPlayerConfig);
32
35
  private createAdPlayer;
33
36
  load(): Promise<void>;
@@ -73,7 +76,12 @@ declare class StormcloudVideoPlayer {
73
76
  private logQueuedAdUrls;
74
77
  private enforceAdHoldState;
75
78
  private releaseAdHoldState;
76
- private preloadUpcomingAds;
79
+ private fetchAndParseVastXml;
80
+ private extractMediaUrlsFromVast;
81
+ private preloadMediaFile;
82
+ private preloadAllAdsInBackground;
83
+ private preloadSingleAd;
84
+ private findNextPreloadedAd;
77
85
  private getRemainingAdMs;
78
86
  private findBreakForTime;
79
87
  toggleMute(): void;
@@ -642,6 +642,15 @@ function createImaController(video, options) {
642
642
  "[IMA] Content video continues in background (Live mode)"
643
643
  );
644
644
  }
645
+ hideContentVideo();
646
+ if (adContainerEl) {
647
+ adContainerEl.style.pointerEvents = "auto";
648
+ adContainerEl.style.display = "flex";
649
+ adContainerEl.style.backgroundColor = "#000";
650
+ adContainerEl.offsetHeight;
651
+ adContainerEl.style.opacity = "1";
652
+ console.log("[IMA] Ad container shown on content pause");
653
+ }
645
654
  adPlaying = true;
646
655
  setAdPlayingFlag(true);
647
656
  emit("content_pause");
@@ -2176,6 +2185,9 @@ var StormcloudVideoPlayer = class {
2176
2185
  this.hasInitialBufferCompleted = false;
2177
2186
  this.adPodAllUrls = [];
2178
2187
  this.preloadingAdUrls = /* @__PURE__ */ new Set();
2188
+ this.vastToMediaUrlMap = /* @__PURE__ */ new Map();
2189
+ this.preloadedMediaUrls = /* @__PURE__ */ new Set();
2190
+ this.preloadingMediaUrls = /* @__PURE__ */ new Set();
2179
2191
  initializePolyfills();
2180
2192
  const browserOverrides = getBrowserConfigOverrides();
2181
2193
  this.config = { ...config, ...browserOverrides };
@@ -2448,27 +2460,44 @@ var StormcloudVideoPlayer = class {
2448
2460
  });
2449
2461
  this.ima.on("ad_error", () => {
2450
2462
  if (this.config.debugAdTiming) {
2451
- console.log("[StormcloudVideoPlayer] IMA ad_error event received");
2463
+ console.log("[StormcloudVideoPlayer] IMA ad_error event received", {
2464
+ showAds: this.showAds,
2465
+ inAdBreak: this.inAdBreak,
2466
+ remainingAds: this.adPodQueue.length
2467
+ });
2452
2468
  }
2453
- if (this.showAds) {
2454
- if (this.inAdBreak) {
2455
- const remaining = this.getRemainingAdMs();
2456
- if (remaining > 500 && this.adPodQueue.length > 0) {
2457
- const next = this.adPodQueue.shift();
2469
+ if (this.inAdBreak) {
2470
+ const remaining = this.getRemainingAdMs();
2471
+ if (remaining > 500 && this.adPodQueue.length > 0) {
2472
+ const nextPreloaded = this.findNextPreloadedAd();
2473
+ if (nextPreloaded) {
2458
2474
  this.currentAdIndex++;
2459
- this.playSingleAd(next).catch(() => {
2475
+ if (this.config.debugAdTiming) {
2476
+ console.log(
2477
+ `[StormcloudVideoPlayer] Skipping to next preloaded ad after error`
2478
+ );
2479
+ }
2480
+ this.playSingleAd(nextPreloaded).catch(() => {
2481
+ this.handleAdFailure();
2460
2482
  });
2461
2483
  } else {
2484
+ if (this.config.debugAdTiming) {
2485
+ console.log(
2486
+ "[StormcloudVideoPlayer] No preloaded ads available, ending ad break"
2487
+ );
2488
+ }
2462
2489
  this.handleAdFailure();
2463
2490
  }
2464
2491
  } else {
2465
- if (this.config.debugAdTiming) {
2466
- console.log(
2467
- "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2468
- );
2469
- }
2470
2492
  this.handleAdFailure();
2471
2493
  }
2494
+ } else {
2495
+ if (this.config.debugAdTiming) {
2496
+ console.log(
2497
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
2498
+ );
2499
+ }
2500
+ this.handleAdFailure();
2472
2501
  }
2473
2502
  });
2474
2503
  this.ima.on("content_pause", () => {
@@ -2499,22 +2528,31 @@ var StormcloudVideoPlayer = class {
2499
2528
  }
2500
2529
  const remaining = this.getRemainingAdMs();
2501
2530
  if (remaining > 500 && this.adPodQueue.length > 0) {
2502
- const next = this.adPodQueue.shift();
2503
- this.currentAdIndex++;
2504
- this.enforceAdHoldState();
2505
- if (this.config.debugAdTiming) {
2506
- console.log(
2507
- `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak}) - IMMEDIATELY starting next ad`
2508
- );
2509
- }
2510
- this.playSingleAd(next).catch(() => {
2531
+ const nextPreloaded = this.findNextPreloadedAd();
2532
+ if (nextPreloaded) {
2533
+ this.currentAdIndex++;
2534
+ this.enforceAdHoldState();
2511
2535
  if (this.config.debugAdTiming) {
2512
- console.error(
2513
- "[StormcloudVideoPlayer] Failed to play next ad in pod"
2536
+ console.log(
2537
+ `[StormcloudVideoPlayer] Playing next preloaded ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
2538
+ );
2539
+ }
2540
+ this.playSingleAd(nextPreloaded).catch(() => {
2541
+ if (this.config.debugAdTiming) {
2542
+ console.error(
2543
+ "[StormcloudVideoPlayer] Failed to play next ad in pod"
2544
+ );
2545
+ }
2546
+ this.handleAdPodComplete();
2547
+ });
2548
+ } else {
2549
+ if (this.config.debugAdTiming) {
2550
+ console.log(
2551
+ "[StormcloudVideoPlayer] No preloaded ads available - completing ad break"
2514
2552
  );
2515
2553
  }
2516
2554
  this.handleAdPodComplete();
2517
- });
2555
+ }
2518
2556
  } else {
2519
2557
  if (this.config.debugAdTiming) {
2520
2558
  console.log(
@@ -2759,6 +2797,9 @@ var StormcloudVideoPlayer = class {
2759
2797
  const first = tags[0];
2760
2798
  const rest = tags.slice(1);
2761
2799
  this.adPodQueue = rest;
2800
+ if (!this.showAds) {
2801
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
2802
+ }
2762
2803
  this.playSingleAd(first).catch(() => {
2763
2804
  });
2764
2805
  }
@@ -3115,19 +3156,38 @@ var StormcloudVideoPlayer = class {
3115
3156
  if (vastTagUrls.length > 0) {
3116
3157
  this.adPodAllUrls = [...vastTagUrls];
3117
3158
  this.preloadingAdUrls.clear();
3159
+ this.vastToMediaUrlMap.clear();
3160
+ this.preloadedMediaUrls.clear();
3161
+ this.preloadingMediaUrls.clear();
3118
3162
  this.logQueuedAdUrls(this.adPodAllUrls);
3163
+ if (this.config.debugAdTiming) {
3164
+ console.log(
3165
+ `[StormcloudVideoPlayer] Capturing original audio state before ad break:`,
3166
+ {
3167
+ videoMuted: this.video.muted,
3168
+ videoVolume: this.video.volume
3169
+ }
3170
+ );
3171
+ }
3172
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3119
3173
  this.inAdBreak = true;
3120
- this.showAds = true;
3121
3174
  this.currentAdIndex = 0;
3122
3175
  this.totalAdsInBreak = vastTagUrls.length;
3123
3176
  this.adPodQueue = [...vastTagUrls];
3124
3177
  this.enforceAdHoldState();
3125
- this.preloadUpcomingAds();
3126
3178
  if (this.config.debugAdTiming) {
3127
3179
  console.log(
3128
- `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - will play continuously`
3180
+ `[StormcloudVideoPlayer] Starting ad pod with ${vastTagUrls.length} ads - preloading all ads in parallel`
3129
3181
  );
3130
3182
  }
3183
+ this.preloadAllAdsInBackground().catch((error) => {
3184
+ if (this.config.debugAdTiming) {
3185
+ console.warn(
3186
+ "[StormcloudVideoPlayer] Error in background preloading:",
3187
+ error
3188
+ );
3189
+ }
3190
+ });
3131
3191
  try {
3132
3192
  await this.playAdPod();
3133
3193
  } catch (error) {
@@ -3153,14 +3213,28 @@ var StormcloudVideoPlayer = class {
3153
3213
  }
3154
3214
  return;
3155
3215
  }
3156
- const firstAd = this.adPodQueue.shift();
3216
+ await new Promise((resolve) => setTimeout(resolve, 500));
3217
+ const firstPreloaded = this.findNextPreloadedAd();
3218
+ if (!firstPreloaded) {
3219
+ if (this.config.debugAdTiming) {
3220
+ console.log(
3221
+ "[StormcloudVideoPlayer] No preloaded ads available after waiting, trying first ad anyway"
3222
+ );
3223
+ }
3224
+ const firstAd = this.adPodQueue.shift();
3225
+ if (firstAd) {
3226
+ this.currentAdIndex++;
3227
+ await this.playSingleAd(firstAd);
3228
+ }
3229
+ return;
3230
+ }
3157
3231
  this.currentAdIndex++;
3158
3232
  if (this.config.debugAdTiming) {
3159
3233
  console.log(
3160
- `[StormcloudVideoPlayer] Playing ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3234
+ `[StormcloudVideoPlayer] Playing first preloaded ad ${this.currentAdIndex}/${this.totalAdsInBreak}`
3161
3235
  );
3162
3236
  }
3163
- await this.playSingleAd(firstAd);
3237
+ await this.playSingleAd(firstPreloaded);
3164
3238
  }
3165
3239
  findCurrentOrNextBreak(nowMs) {
3166
3240
  var _a;
@@ -3193,6 +3267,7 @@ var StormcloudVideoPlayer = class {
3193
3267
  const first = tags[0];
3194
3268
  const rest = tags.slice(1);
3195
3269
  this.adPodQueue = rest;
3270
+ this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3196
3271
  this.enforceAdHoldState();
3197
3272
  await this.playSingleAd(first);
3198
3273
  this.inAdBreak = true;
@@ -3313,27 +3388,9 @@ var StormcloudVideoPlayer = class {
3313
3388
  `[StormcloudVideoPlayer] IMA SDK preloaded this ad already: ${vastTagUrl}`
3314
3389
  );
3315
3390
  }
3316
- if (!this.showAds) {
3317
- if (this.config.debugAdTiming) {
3318
- console.log(
3319
- `[StormcloudVideoPlayer] Capturing original state before ad request:`,
3320
- {
3321
- videoMuted: this.video.muted,
3322
- videoVolume: this.video.volume,
3323
- showAds: this.showAds
3324
- }
3325
- );
3326
- }
3327
- this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
3328
- } else if (this.config.debugAdTiming) {
3329
- console.log(
3330
- `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
3331
- );
3332
- }
3333
3391
  this.startAdFailsafeTimer();
3334
3392
  try {
3335
3393
  await this.ima.requestAds(vastTagUrl);
3336
- this.preloadUpcomingAds();
3337
3394
  try {
3338
3395
  if (this.config.debugAdTiming) {
3339
3396
  console.log(
@@ -3342,9 +3399,10 @@ var StormcloudVideoPlayer = class {
3342
3399
  }
3343
3400
  this.enforceAdHoldState();
3344
3401
  await this.ima.play();
3402
+ this.showAds = true;
3345
3403
  if (this.config.debugAdTiming) {
3346
3404
  console.log(
3347
- "[StormcloudVideoPlayer] Ad playback started successfully"
3405
+ "[StormcloudVideoPlayer] Ad playback started successfully, showAds = true"
3348
3406
  );
3349
3407
  }
3350
3408
  } catch (playError) {
@@ -3372,6 +3430,9 @@ var StormcloudVideoPlayer = class {
3372
3430
  }
3373
3431
  this.releaseAdHoldState();
3374
3432
  this.preloadingAdUrls.clear();
3433
+ this.vastToMediaUrlMap.clear();
3434
+ this.preloadedMediaUrls.clear();
3435
+ this.preloadingMediaUrls.clear();
3375
3436
  this.inAdBreak = false;
3376
3437
  this.expectedAdBreakDurationMs = void 0;
3377
3438
  this.currentAdBreakStartWallClockMs = void 0;
@@ -3481,44 +3542,204 @@ var StormcloudVideoPlayer = class {
3481
3542
  this.ima.hidePlaceholder();
3482
3543
  }
3483
3544
  }
3484
- preloadUpcomingAds() {
3485
- if (!this.ima.preloadAds || this.adPodQueue.length === 0) {
3486
- return;
3545
+ async fetchAndParseVastXml(vastTagUrl) {
3546
+ try {
3547
+ const response = await fetch(vastTagUrl, { mode: "cors" });
3548
+ if (!response.ok) {
3549
+ throw new Error(`Failed to fetch VAST: ${response.status}`);
3550
+ }
3551
+ const xmlText = await response.text();
3552
+ return this.extractMediaUrlsFromVast(xmlText);
3553
+ } catch (error) {
3554
+ if (this.config.debugAdTiming) {
3555
+ console.warn(
3556
+ `[StormcloudVideoPlayer] Failed to fetch/parse VAST XML: ${vastTagUrl}`,
3557
+ error
3558
+ );
3559
+ }
3560
+ return [];
3487
3561
  }
3488
- const upcoming = this.adPodQueue.slice(0, 2);
3489
- for (const url of upcoming) {
3490
- if (!url) continue;
3491
- if (this.ima.hasPreloadedAd(url)) {
3492
- this.preloadingAdUrls.delete(url);
3493
- continue;
3562
+ }
3563
+ extractMediaUrlsFromVast(xmlText) {
3564
+ var _a;
3565
+ const mediaUrls = [];
3566
+ try {
3567
+ const parser = new DOMParser();
3568
+ const xmlDoc = parser.parseFromString(xmlText, "text/xml");
3569
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
3570
+ for (const mediaFile of Array.from(mediaFileElements)) {
3571
+ const url = (_a = mediaFile.textContent) == null ? void 0 : _a.trim();
3572
+ if (url) {
3573
+ const lowerUrl = url.toLowerCase();
3574
+ if (lowerUrl.endsWith(".mp4") || lowerUrl.endsWith(".webm") || lowerUrl.endsWith(".mov") || lowerUrl.endsWith(".avi") || lowerUrl.includes(".mp4?") || lowerUrl.includes(".webm?") || lowerUrl.includes("/mp4/") || lowerUrl.includes("type=video")) {
3575
+ mediaUrls.push(url);
3576
+ }
3577
+ }
3494
3578
  }
3495
- if (this.preloadingAdUrls.has(url)) {
3496
- continue;
3579
+ if (this.config.debugAdTiming && mediaUrls.length > 0) {
3580
+ console.log(
3581
+ `[StormcloudVideoPlayer] Extracted ${mediaUrls.length} media URLs from VAST:`,
3582
+ mediaUrls
3583
+ );
3584
+ }
3585
+ } catch (error) {
3586
+ if (this.config.debugAdTiming) {
3587
+ console.warn(
3588
+ "[StormcloudVideoPlayer] Failed to parse VAST XML:",
3589
+ error
3590
+ );
3497
3591
  }
3592
+ }
3593
+ return mediaUrls;
3594
+ }
3595
+ async preloadMediaFile(mediaUrl) {
3596
+ if (this.preloadedMediaUrls.has(mediaUrl)) {
3597
+ return;
3598
+ }
3599
+ if (this.preloadingMediaUrls.has(mediaUrl)) {
3600
+ return;
3601
+ }
3602
+ this.preloadingMediaUrls.add(mediaUrl);
3603
+ try {
3498
3604
  if (this.config.debugAdTiming) {
3499
3605
  console.log(
3500
- `[StormcloudVideoPlayer] Scheduling IMA preload for upcoming ad: ${url}`
3606
+ `[StormcloudVideoPlayer] Preloading video file: ${mediaUrl}`
3501
3607
  );
3502
3608
  }
3503
- this.preloadingAdUrls.add(url);
3504
- this.ima.preloadAds(url).then(() => {
3609
+ const response = await fetch(mediaUrl, {
3610
+ mode: "cors",
3611
+ method: "GET",
3612
+ headers: {
3613
+ Range: "bytes=0-1048576"
3614
+ }
3615
+ });
3616
+ if (response.ok || response.status === 206) {
3617
+ this.preloadedMediaUrls.add(mediaUrl);
3505
3618
  if (this.config.debugAdTiming) {
3506
3619
  console.log(
3507
- `[StormcloudVideoPlayer] IMA preload complete for ad: ${url}`
3620
+ `[StormcloudVideoPlayer] Successfully preloaded video file: ${mediaUrl}`
3508
3621
  );
3509
3622
  }
3510
- }).catch((error) => {
3623
+ }
3624
+ } catch (error) {
3625
+ if (this.config.debugAdTiming) {
3626
+ console.warn(
3627
+ `[StormcloudVideoPlayer] Failed to preload video file: ${mediaUrl}`,
3628
+ error
3629
+ );
3630
+ }
3631
+ } finally {
3632
+ this.preloadingMediaUrls.delete(mediaUrl);
3633
+ }
3634
+ }
3635
+ async preloadAllAdsInBackground() {
3636
+ if (this.adPodAllUrls.length === 0) {
3637
+ return;
3638
+ }
3639
+ if (this.config.debugAdTiming) {
3640
+ console.log(
3641
+ `[StormcloudVideoPlayer] Starting parallel preload of ${this.adPodAllUrls.length} ads`
3642
+ );
3643
+ }
3644
+ const preloadPromises = this.adPodAllUrls.map(
3645
+ (vastTagUrl) => this.preloadSingleAd(vastTagUrl).catch((error) => {
3511
3646
  if (this.config.debugAdTiming) {
3512
3647
  console.warn(
3513
- `[StormcloudVideoPlayer] IMA preload failed for ad: ${url}`,
3648
+ `[StormcloudVideoPlayer] Preload failed for ${vastTagUrl}:`,
3514
3649
  error
3515
3650
  );
3516
3651
  }
3517
- }).finally(() => {
3518
- this.preloadingAdUrls.delete(url);
3519
- });
3652
+ })
3653
+ );
3654
+ await Promise.all(preloadPromises);
3655
+ if (this.config.debugAdTiming) {
3656
+ console.log(
3657
+ `[StormcloudVideoPlayer] Background preloading completed for all ads`
3658
+ );
3520
3659
  }
3521
3660
  }
3661
+ async preloadSingleAd(vastTagUrl) {
3662
+ if (!vastTagUrl) return;
3663
+ try {
3664
+ if (this.ima.preloadAds && !this.ima.hasPreloadedAd(vastTagUrl)) {
3665
+ if (!this.preloadingAdUrls.has(vastTagUrl)) {
3666
+ if (this.config.debugAdTiming) {
3667
+ console.log(
3668
+ `[StormcloudVideoPlayer] Preloading VAST: ${vastTagUrl}`
3669
+ );
3670
+ }
3671
+ this.preloadingAdUrls.add(vastTagUrl);
3672
+ await this.ima.preloadAds(vastTagUrl).then(() => {
3673
+ if (this.config.debugAdTiming) {
3674
+ console.log(
3675
+ `[StormcloudVideoPlayer] IMA VAST preload complete: ${vastTagUrl}`
3676
+ );
3677
+ }
3678
+ }).catch((error) => {
3679
+ if (this.config.debugAdTiming) {
3680
+ console.warn(
3681
+ `[StormcloudVideoPlayer] IMA VAST preload failed: ${vastTagUrl}`,
3682
+ error
3683
+ );
3684
+ }
3685
+ }).finally(() => {
3686
+ this.preloadingAdUrls.delete(vastTagUrl);
3687
+ });
3688
+ }
3689
+ }
3690
+ let mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3691
+ if (!mediaUrls) {
3692
+ if (this.config.debugAdTiming) {
3693
+ console.log(
3694
+ `[StormcloudVideoPlayer] Fetching and parsing VAST to extract media URLs: ${vastTagUrl}`
3695
+ );
3696
+ }
3697
+ mediaUrls = await this.fetchAndParseVastXml(vastTagUrl);
3698
+ if (mediaUrls.length > 0) {
3699
+ this.vastToMediaUrlMap.set(vastTagUrl, mediaUrls);
3700
+ }
3701
+ }
3702
+ if (mediaUrls && mediaUrls.length > 0) {
3703
+ const primaryMediaUrl = mediaUrls[0];
3704
+ if (primaryMediaUrl && !this.preloadedMediaUrls.has(primaryMediaUrl)) {
3705
+ await this.preloadMediaFile(primaryMediaUrl);
3706
+ }
3707
+ }
3708
+ } catch (error) {
3709
+ if (this.config.debugAdTiming) {
3710
+ console.warn(
3711
+ `[StormcloudVideoPlayer] Failed to preload ad: ${vastTagUrl}`,
3712
+ error
3713
+ );
3714
+ }
3715
+ }
3716
+ }
3717
+ findNextPreloadedAd() {
3718
+ var _a, _b, _c;
3719
+ for (let i = 0; i < this.adPodQueue.length; i++) {
3720
+ const vastTagUrl = this.adPodQueue[i];
3721
+ if (!vastTagUrl) continue;
3722
+ const hasImaPreload = (_c = (_b = (_a = this.ima).hasPreloadedAd) == null ? void 0 : _b.call(_a, vastTagUrl)) != null ? _c : false;
3723
+ const mediaUrls = this.vastToMediaUrlMap.get(vastTagUrl);
3724
+ const hasMediaPreload = mediaUrls && mediaUrls.length > 0 ? this.preloadedMediaUrls.has(mediaUrls[0]) : false;
3725
+ if (hasImaPreload || hasMediaPreload) {
3726
+ if (this.config.debugAdTiming) {
3727
+ console.log(
3728
+ `[StormcloudVideoPlayer] Found preloaded ad at index ${i}: ${vastTagUrl}`,
3729
+ { hasImaPreload, hasMediaPreload }
3730
+ );
3731
+ }
3732
+ this.adPodQueue.splice(0, i + 1);
3733
+ return vastTagUrl;
3734
+ }
3735
+ }
3736
+ if (this.config.debugAdTiming) {
3737
+ console.log(
3738
+ "[StormcloudVideoPlayer] No preloaded ads found in queue"
3739
+ );
3740
+ }
3741
+ return void 0;
3742
+ }
3522
3743
  getRemainingAdMs() {
3523
3744
  if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
3524
3745
  return 0;
@@ -3687,6 +3908,9 @@ var StormcloudVideoPlayer = class {
3687
3908
  (_b = this.ima) == null ? void 0 : _b.destroy();
3688
3909
  this.releaseAdHoldState();
3689
3910
  this.preloadingAdUrls.clear();
3911
+ this.vastToMediaUrlMap.clear();
3912
+ this.preloadedMediaUrls.clear();
3913
+ this.preloadingMediaUrls.clear();
3690
3914
  this.adPodAllUrls = [];
3691
3915
  }
3692
3916
  };