box-content-preview 3.75.0 → 3.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib/index.js CHANGED
@@ -15388,7 +15388,7 @@ function util_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var
15388
15388
  const CLIENT_NAME = "box-content-preview"; // eslint-disable-line no-undef
15389
15389
  const CLIENT_NAME_KEY = 'box_client_name';
15390
15390
  const CLIENT_VERSION_KEY = 'box_client_version';
15391
- const CLIENT_VERSION = "3.75.0"; // eslint-disable-line no-undef
15391
+ const CLIENT_VERSION = "3.77.0"; // eslint-disable-line no-undef
15392
15392
  const HEADER_CLIENT_NAME = 'X-Box-Client-Name';
15393
15393
  const HEADER_CLIENT_VERSION = 'X-Box-Client-Version';
15394
15394
  const PROMISE_MAP = {};
@@ -17085,7 +17085,7 @@ class Browser {
17085
17085
  ;// ./src/lib/Logger.js
17086
17086
  /* eslint-disable no-undef */
17087
17087
  const Logger_CLIENT_NAME = "box-content-preview";
17088
- const Logger_CLIENT_VERSION = "3.75.0";
17088
+ const Logger_CLIENT_VERSION = "3.77.0";
17089
17089
  /* eslint-enable no-undef */
17090
17090
 
17091
17091
  class Logger {
@@ -31890,6 +31890,42 @@ const play_48px_namespaceObject = "<svg fill=\"#fff\" height=\"48\" viewBox=\"0
31890
31890
  const arrow_curved_back_24px_namespaceObject = "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12.707 2.29346C12.3165 1.90293 11.6835 1.90293 11.293 2.29346L8.64648 4.93994C8.06097 5.52574 8.06083 6.47534 8.64648 7.06104L11.293 9.70752C11.6834 10.0976 12.3166 10.0976 12.707 9.70752C13.0974 9.3171 13.0972 8.68399 12.707 8.29346L11.4131 6.99951H12C15.3136 6.99951 17.9998 9.68597 18 12.9995C18 16.3132 15.3137 18.9995 12 18.9995C10.7239 18.9995 9.54372 18.6019 8.57227 17.9243C8.11945 17.6086 7.49569 17.7198 7.17969 18.1724C6.8638 18.6252 6.97502 19.2489 7.42773 19.5649C8.72394 20.4691 10.3016 20.9995 12 20.9995C16.4183 20.9995 20 17.4178 20 12.9995C19.9998 8.5814 16.4182 4.99951 12 4.99951H11.415L12.707 3.70752C13.0975 3.31707 13.0973 2.684 12.707 2.29346ZM3.2793 8.99951C2.67882 8.99951 2.32725 9.26833 2.27832 9.9126L2.09277 12.3882V12.4126C2.06348 12.8862 2.33203 13.2964 2.92285 13.2964C3.31348 13.2964 3.49414 13.2085 3.76758 12.9692C3.99217 12.7691 4.29991 12.5933 4.68555 12.5933C5.35925 12.5933 5.83283 13.0476 5.83301 13.6919C5.83301 14.3511 5.33984 14.8394 4.6709 14.8394C4.10449 14.8394 3.7627 14.5708 3.48926 14.1606C3.27447 13.8923 3.05948 13.7798 2.72266 13.7798C2.26868 13.7798 2.00015 14.0437 2 14.4829C2 14.6537 2.04404 14.8199 2.12207 14.9761C2.4834 15.6938 3.51855 16.2212 4.68555 16.2212C6.36035 16.2212 7.52246 15.2007 7.52246 13.6528C7.52229 12.3151 6.56531 11.3774 5.23242 11.3774C4.49526 11.3774 3.92389 11.6852 3.65039 12.1489H3.61621L3.75293 10.3765H6.44824C6.87793 10.3765 7.15137 10.1079 7.15137 9.68799C7.15121 9.26828 6.87292 8.99951 6.44824 8.99951H3.2793Z\" fill=\"white\"/>\n</svg>\n";
31891
31891
  ;// ./src/lib/icons/arrow_curved_forward_24px.svg
31892
31892
  const arrow_curved_forward_24px_namespaceObject = "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M11.293 2.29346C11.6835 1.90293 12.3165 1.90293 12.707 2.29346L15.3535 4.93994C15.939 5.52574 15.9392 6.47535 15.3535 7.06104L12.707 9.70752C12.3166 10.0976 11.6834 10.0976 11.293 9.70752C10.9026 9.31711 10.9028 8.684 11.293 8.29346L12.5869 6.99951H12C8.68642 6.99951 6.00021 9.68598 6 12.9995C6 16.3132 8.68629 18.9995 12 18.9995C13.2761 18.9995 14.4563 18.6019 15.4277 17.9243C15.8805 17.6086 16.5043 17.7198 16.8203 18.1724C17.1362 18.6252 17.025 19.2489 16.5723 19.5649C15.2761 20.4691 13.6984 20.9995 12 20.9995C7.58172 20.9995 4 17.4178 4 12.9995C4.00021 8.58141 7.58185 4.99951 12 4.99951H12.585L11.293 3.70752C10.9025 3.31708 10.9027 2.684 11.293 2.29346ZM20.9258 8.99951C21.3505 8.99951 21.6287 9.26828 21.6289 9.68799C21.6289 10.1079 21.3555 10.3765 20.9258 10.3765H18.2305L18.0938 12.1489H18.1279C18.4014 11.6852 18.9728 11.3774 19.71 11.3774C21.0428 11.3774 21.9998 12.3151 22 13.6528C22 15.2007 20.8379 16.2212 19.1631 16.2212C17.9961 16.2212 16.9609 15.6938 16.5996 14.9761C16.5216 14.8199 16.4775 14.6537 16.4775 14.4829C16.4777 14.0437 16.7462 13.7798 17.2002 13.7798C17.537 13.7798 17.752 13.8923 17.9668 14.1606C18.2402 14.5708 18.582 14.8394 19.1484 14.8394C19.8174 14.8394 20.3105 14.3511 20.3105 13.6919C20.3104 13.0476 19.8368 12.5933 19.1631 12.5933C18.7774 12.5933 18.4697 12.7691 18.2451 12.9692C17.9717 13.2085 17.791 13.2964 17.4004 13.2964C16.8096 13.2964 16.541 12.8862 16.5703 12.4126V12.3882L16.7559 9.9126C16.8048 9.26833 17.1564 8.99951 17.7568 8.99951H20.9258Z\" fill=\"white\"/>\n</svg>\n";
31893
+ ;// ./src/lib/viewers/media/fitFrameToViewport.js
31894
+ /**
31895
+ * Contain-fits a frame into the viewport (same model as object-fit: contain).
31896
+ * Never exceeds the viewport; does not enforce a minimum width.
31897
+ *
31898
+ * @param {number} aspectRatio - width / height
31899
+ * @param {Object} viewport - { width, height }
31900
+ * @return {{ width: number, height: number }}
31901
+ */
31902
+ function fitFrameToViewport(aspectRatio, viewport = {}) {
31903
+ const {
31904
+ width: viewportWidth = 0,
31905
+ height: viewportHeight = 0
31906
+ } = viewport;
31907
+ const ratio = aspectRatio || 1;
31908
+ if (viewportWidth <= 0 || viewportHeight <= 0) {
31909
+ return {
31910
+ width: 0,
31911
+ height: 0
31912
+ };
31913
+ }
31914
+ let width = viewportWidth;
31915
+ let height = width / ratio;
31916
+ if (height > viewportHeight) {
31917
+ height = viewportHeight;
31918
+ width = height * ratio;
31919
+ }
31920
+ if (width > viewportWidth) {
31921
+ width = viewportWidth;
31922
+ height = width / ratio;
31923
+ }
31924
+ return {
31925
+ width,
31926
+ height
31927
+ };
31928
+ }
31893
31929
  ;// ./src/lib/viewers/media/VideoPreloader.js
31894
31930
  function VideoPreloader_defineProperty(e, r, t) { return (r = VideoPreloader_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
31895
31931
  function VideoPreloader_toPropertyKey(t) { var i = VideoPreloader_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
@@ -31899,6 +31935,7 @@ function VideoPreloader_toPrimitive(t, r) { if ("object" != typeof t || !t) retu
31899
31935
 
31900
31936
 
31901
31937
 
31938
+
31902
31939
  class VideoPreloader extends (events_default()) {
31903
31940
  /**
31904
31941
  * [constructor]
@@ -31927,6 +31964,12 @@ class VideoPreloader extends (events_default()) {
31927
31964
  VideoPreloader_defineProperty(this, "wrapperClassName", void 0);
31928
31965
  /** @property {HTMLElement} - Preload wrapper element */
31929
31966
  VideoPreloader_defineProperty(this, "wrapperEl", void 0);
31967
+ /** @property {boolean} - When true, never create/paint a new Instant Preview poster */
31968
+ VideoPreloader_defineProperty(this, "blockPosterPaint", false);
31969
+ /** @property {number} - Monotonic id so overlapping showPreload fetches cannot append orphans */
31970
+ VideoPreloader_defineProperty(this, "preloadRequestId", 0);
31971
+ /** @property {Promise|null} - In-flight showPreload promise (single-flight) */
31972
+ VideoPreloader_defineProperty(this, "showPreloadPromise", null);
31930
31973
  /**
31931
31974
  * Cleans up preload DOM.
31932
31975
  *
@@ -31940,9 +31983,13 @@ class VideoPreloader extends (events_default()) {
31940
31983
  }
31941
31984
  this.wrapperEl = undefined;
31942
31985
  }
31986
+
31987
+ // Remove any Instant Preview wrappers left by overlapping showPreload races.
31988
+ this.removeOrphanPreloadWrappers();
31943
31989
  this.preloadEl = undefined;
31944
31990
  this.imageEl = undefined;
31945
31991
  this.placeholderEl = undefined;
31992
+ this.showPreloadPromise = null;
31946
31993
  if (this.srcUrl) {
31947
31994
  URL.revokeObjectURL(this.srcUrl);
31948
31995
  this.srcUrl = undefined;
@@ -31955,16 +32002,17 @@ class VideoPreloader extends (events_default()) {
31955
32002
  * @return {void}
31956
32003
  */
31957
32004
  VideoPreloader_defineProperty(this, "loadHandler", () => {
31958
- if (!this.preloadEl || !this.imageEl) {
32005
+ if (!this.preloadEl || !this.imageEl || !this.preloadEl.classList.contains(CLASS_INVISIBLE)) {
31959
32006
  return;
31960
32007
  }
31961
- this.sizeContainerToViewport(this.preloadOptions?.viewport);
32008
+ if (this.checkVideoLoaded()) {
32009
+ return;
32010
+ }
32011
+ this.sizeContainerToViewport(this.resolveViewport());
31962
32012
  this.preloadEl.classList.remove(CLASS_INVISIBLE);
31963
- if (this.containerEl && this.containerEl.parentNode) {
31964
- const mediaWrapper = this.containerEl.parentNode;
31965
- if (mediaWrapper && mediaWrapper.classList && mediaWrapper.classList.contains('bp-media')) {
31966
- mediaWrapper.classList.add(CLASS_IS_VISIBLE);
31967
- }
32013
+ const mediaWrapper = this.containerEl?.closest('.bp-media');
32014
+ if (mediaWrapper) {
32015
+ mediaWrapper.classList.add(CLASS_IS_VISIBLE);
31968
32016
  }
31969
32017
  const onImageClick = this.preloadOptions?.onImageClick;
31970
32018
  if (onImageClick && this.wrapperEl) {
@@ -31993,6 +32041,37 @@ class VideoPreloader extends (events_default()) {
31993
32041
  this.wrapperClassName = CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_VIDEO;
31994
32042
  }
31995
32043
 
32044
+ /**
32045
+ * Stop any in-flight or future poster from painting. Used once the video is ready
32046
+ * (or playback has dismissed Instant Preview) so a late JPG cannot overlay the video.
32047
+ * Does not hide an Instant Preview that is already painted — play still dismisses that.
32048
+ *
32049
+ * @return {void}
32050
+ */
32051
+ blockFuturePosterPaint() {
32052
+ this.blockPosterPaint = true;
32053
+ // Invalidate in-flight showPreload work so a late fetch cannot append another wrapper.
32054
+ this.preloadRequestId += 1;
32055
+ }
32056
+
32057
+ /**
32058
+ * Removes Instant Preview wrapper nodes in the container that are not the tracked wrapper.
32059
+ * Overlapping showPreload calls can leave orphans that dismiss would otherwise miss.
32060
+ *
32061
+ * @private
32062
+ * @return {void}
32063
+ */
32064
+ removeOrphanPreloadWrappers() {
32065
+ if (!this.containerEl) {
32066
+ return;
32067
+ }
32068
+ this.containerEl.querySelectorAll(`.${this.wrapperClassName}`).forEach(el => {
32069
+ if (el !== this.wrapperEl) {
32070
+ el.remove();
32071
+ }
32072
+ });
32073
+ }
32074
+
31996
32075
  /**
31997
32076
  * Shows a preload of the video by showing a thumbnail image. This should be called
31998
32077
  * while the full video loads to give the user visual feedback on the file as soon as possible.
@@ -32007,16 +32086,27 @@ class VideoPreloader extends (events_default()) {
32007
32086
  showPreload(preloadUrlWithAuth, containerEl, options = {}) {
32008
32087
  this.containerEl = containerEl;
32009
32088
  this.preloadOptions = options;
32010
- return this.api.get(preloadUrlWithAuth, {
32089
+ if (this.shouldAbortPosterPaint()) {
32090
+ return Promise.resolve();
32091
+ }
32092
+
32093
+ // load() may call showPreload again before wrapperEl is set (async fetch). Only one
32094
+ // in-flight create is allowed so dismiss cannot leave an orphan wrapper behind.
32095
+ if (this.wrapperEl || this.showPreloadPromise) {
32096
+ return this.showPreloadPromise || Promise.resolve();
32097
+ }
32098
+ this.preloadRequestId += 1;
32099
+ const requestId = this.preloadRequestId;
32100
+ this.showPreloadPromise = this.api.get(preloadUrlWithAuth, {
32011
32101
  type: 'blob'
32012
32102
  }).then(handleRepresentationBlobFetch).then(imgBlob => {
32013
- if (this.checkVideoLoaded()) {
32103
+ if (requestId !== this.preloadRequestId || this.checkVideoLoaded()) {
32014
32104
  return;
32015
32105
  }
32016
32106
  this.srcUrl = URL.createObjectURL(imgBlob);
32017
- this.wrapperEl = document.createElement('div');
32018
- this.wrapperEl.className = this.wrapperClassName;
32019
- this.wrapperEl.innerHTML = `
32107
+ const wrapperEl = document.createElement('div');
32108
+ wrapperEl.className = this.wrapperClassName;
32109
+ wrapperEl.innerHTML = `
32020
32110
  <div class="${CLASS_BOX_PREVIEW_PRELOAD} ${CLASS_INVISIBLE}">
32021
32111
  <div class="${CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER}">
32022
32112
  <img class="${CLASS_BOX_PREVIEW_PRELOAD_CONTENT}" src="${this.srcUrl}" />
@@ -32024,6 +32114,17 @@ class VideoPreloader extends (events_default()) {
32024
32114
  </div>
32025
32115
  </div>
32026
32116
  `.trim();
32117
+
32118
+ // Video may have become ready while we built the DOM — do not attach an unpainted poster.
32119
+ if (requestId !== this.preloadRequestId || this.checkVideoLoaded()) {
32120
+ URL.revokeObjectURL(this.srcUrl);
32121
+ this.srcUrl = undefined;
32122
+ return;
32123
+ }
32124
+
32125
+ // Drop any stray Instant Preview nodes before attaching the tracked one.
32126
+ this.wrapperEl = wrapperEl;
32127
+ this.removeOrphanPreloadWrappers();
32027
32128
  this.containerEl.appendChild(this.wrapperEl);
32028
32129
  this.placeholderEl = this.wrapperEl.querySelector(`.${CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER}`);
32029
32130
  this.preloadEl = this.wrapperEl.querySelector(`.${CLASS_BOX_PREVIEW_PRELOAD}`);
@@ -32037,7 +32138,10 @@ class VideoPreloader extends (events_default()) {
32037
32138
  }
32038
32139
  }).catch(() => {
32039
32140
  // Silently fail if preload image can't be loaded
32141
+ }).finally(() => {
32142
+ this.showPreloadPromise = null;
32040
32143
  });
32144
+ return this.showPreloadPromise;
32041
32145
  }
32042
32146
 
32043
32147
  /**
@@ -32046,6 +32150,8 @@ class VideoPreloader extends (events_default()) {
32046
32150
  * @return {void}
32047
32151
  */
32048
32152
  hidePreload() {
32153
+ // Orphans are never faded via wrapperEl; remove them immediately on dismiss.
32154
+ this.removeOrphanPreloadWrappers();
32049
32155
  if (!this.wrapperEl) {
32050
32156
  return;
32051
32157
  }
@@ -32124,6 +32230,16 @@ class VideoPreloader extends (events_default()) {
32124
32230
  this.placeholderEl.appendChild(spinner);
32125
32231
  }
32126
32232
  }
32233
+
32234
+ /**
32235
+ * Whether the poster is still the authoritative painted frame.
32236
+ * False once hide/dismiss starts (transparent fade) so resize uses video geometry.
32237
+ *
32238
+ * @return {boolean} true when the poster has painted and is not dismissing
32239
+ */
32240
+ isVisible() {
32241
+ return !!this.preloadEl && !!this.wrapperEl && !this.preloadEl.classList.contains(CLASS_INVISIBLE) && !this.wrapperEl.classList.contains(CLASS_IS_TRANSPARENT);
32242
+ }
32127
32243
  /**
32128
32244
  * Binds event listeners for preload
32129
32245
  *
@@ -32154,7 +32270,25 @@ class VideoPreloader extends (events_default()) {
32154
32270
  }
32155
32271
  }
32156
32272
  /**
32157
- * Sizes the container based on viewport dimensions and image aspect ratio to match video player sizing.
32273
+ * Resolves the viewport to size against, preferring a live getter when provided
32274
+ * so layout changes after showPreload() are reflected at paint time.
32275
+ *
32276
+ * @private
32277
+ * @return {Object|undefined} viewport width/height
32278
+ */
32279
+ resolveViewport() {
32280
+ const {
32281
+ getViewport,
32282
+ viewport
32283
+ } = this.preloadOptions || {};
32284
+ if (typeof getViewport === 'function') {
32285
+ return getViewport();
32286
+ }
32287
+ return viewport;
32288
+ }
32289
+
32290
+ /**
32291
+ * Sizes the target based on viewport dimensions and image aspect ratio to match video player sizing.
32158
32292
  * This prevents the thumbnail from appearing small and then jumping to the correct size.
32159
32293
  *
32160
32294
  * @param {Object} [viewportOverride] - Optional { width, height }; when provided, use instead of walking DOM (same as video viewport)
@@ -32190,52 +32324,87 @@ class VideoPreloader extends (events_default()) {
32190
32324
  };
32191
32325
  }
32192
32326
 
32193
- // Apply minimum width to match video sizing logic
32194
- // This ensures controls don't overflow
32195
- const containerWidth = Math.max(MIN_VIDEO_WIDTH_PX, viewport.width);
32196
-
32197
- // Calculate container height based on image aspect ratio
32198
32327
  // Use natural dimensions if available (image has loaded), otherwise use current dimensions
32199
32328
  const imageWidth = this.imageEl.naturalWidth || this.imageEl.width || 1;
32200
32329
  const imageHeight = this.imageEl.naturalHeight || this.imageEl.height || 1;
32201
- const aspectRatio = imageWidth / imageHeight;
32330
+ if (this.preloadOptions?.earlyPaint) {
32331
+ const {
32332
+ width,
32333
+ height
32334
+ } = fitFrameToViewport(imageWidth / imageHeight, viewport);
32335
+ this.containerEl.style.width = `${width}px`;
32336
+ this.containerEl.style.height = `${height}px`;
32337
+ return;
32338
+ }
32202
32339
 
32203
- // Calculate height based on width and aspect ratio
32340
+ // Legacy path: enforce minimum width to match V1 / pre-early-paint V2 video sizing
32341
+ const containerWidth = Math.max(MIN_VIDEO_WIDTH_PX, viewport.width);
32342
+ const aspectRatio = imageWidth / imageHeight;
32204
32343
  let containerHeight = containerWidth / aspectRatio;
32205
32344
  let finalWidth = containerWidth;
32206
-
32207
- // Ensure height doesn't exceed viewport height
32208
32345
  if (containerHeight > viewport.height) {
32209
32346
  containerHeight = viewport.height;
32210
- // If height is constrained, recalculate width to maintain aspect ratio
32211
32347
  finalWidth = containerHeight * aspectRatio;
32212
- // Ensure we still meet minimum width requirement
32213
32348
  if (finalWidth < MIN_VIDEO_WIDTH_PX) {
32214
32349
  finalWidth = MIN_VIDEO_WIDTH_PX;
32215
32350
  containerHeight = finalWidth / aspectRatio;
32216
32351
  }
32217
32352
  }
32218
-
32219
- // Set container dimensions
32220
32353
  this.containerEl.style.width = `${finalWidth}px`;
32221
32354
  this.containerEl.style.height = `${containerHeight}px`;
32222
32355
  }
32223
32356
  /**
32224
- * Check if video is already loaded - if so, hide the preload.
32357
+ * Whether Instant Preview should stop creating/painting a poster.
32358
+ * True once the viewer marks the video ready, while playing, or once metadata exists.
32225
32359
  *
32226
32360
  * @private
32227
- * @return {boolean} Whether video is already loaded
32361
+ * @return {boolean}
32362
+ */
32363
+ shouldAbortPosterPaint() {
32364
+ if (this.blockPosterPaint) {
32365
+ return true;
32366
+ }
32367
+ if (!this.containerEl) {
32368
+ return false;
32369
+ }
32370
+ const videoEl = this.containerEl.querySelector('video');
32371
+ if (!videoEl) {
32372
+ return false;
32373
+ }
32374
+
32375
+ // Playing: never let a late poster land on top of playback.
32376
+ if (videoEl.paused === false) {
32377
+ return true;
32378
+ }
32379
+
32380
+ // Metadata available: same gate Instant Preview has always used.
32381
+ return videoEl.readyState >= 1;
32382
+ }
32383
+
32384
+ /**
32385
+ * Check if video is already loaded / poster paint should be aborted.
32386
+ * Unpainted poster DOM is removed immediately; an already-visible Instant Preview is left
32387
+ * for dismiss-on-play. Reveals the video when tearing down an unpainted poster.
32388
+ *
32389
+ * @private
32390
+ * @return {boolean} Whether poster create/paint should abort
32228
32391
  */
32229
32392
  checkVideoLoaded() {
32230
- // If video element exists and has loaded metadata, hide the preload
32231
- if (this.containerEl) {
32232
- const videoEl = this.containerEl.querySelector('video');
32233
- if (videoEl && videoEl.readyState >= 1) {
32234
- this.hidePreload();
32235
- return true;
32236
- }
32393
+ if (!this.shouldAbortPosterPaint()) {
32394
+ return false;
32237
32395
  }
32238
- return false;
32396
+ const painted = this.wrapperEl && this.preloadEl && !this.preloadEl.classList.contains(CLASS_INVISIBLE);
32397
+ if (painted) {
32398
+ // Instant Preview is already up — play/dismiss owns hiding it.
32399
+ return true;
32400
+ }
32401
+
32402
+ // Never painted (or no DOM): drop it and make sure the video can show.
32403
+ if (this.wrapperEl) {
32404
+ this.cleanupPreload();
32405
+ }
32406
+ this.containerEl?.querySelector('video')?.classList.remove(CLASS_INVISIBLE);
32407
+ return true;
32239
32408
  }
32240
32409
  }
32241
32410
  /* harmony default export */ const media_VideoPreloader = (VideoPreloader);
@@ -32254,6 +32423,7 @@ function VideoBaseViewer_toPrimitive(t, r) { if ("object" != typeof t || !t) ret
32254
32423
 
32255
32424
 
32256
32425
 
32426
+
32257
32427
  const MOUSE_MOVE_TIMEOUT_IN_MILLIS = 1000;
32258
32428
  const CLASS_PLAY_BUTTON = 'bp-media-play-button';
32259
32429
  const CLASS_PLAY_CONTAINER = 'bp-media-overlay-play-container';
@@ -32274,6 +32444,8 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32274
32444
  VideoBaseViewer_defineProperty(this, "videoAnnotationsEnabled", false);
32275
32445
  /** @property {VideoPreloader} - Video preloader instance */
32276
32446
  VideoBaseViewer_defineProperty(this, "preloader", void 0);
32447
+ /** @property {HTMLElement} - Full-size padded stage around the shared V2 media frame */
32448
+ VideoBaseViewer_defineProperty(this, "mediaStageEl", void 0);
32277
32449
  /**
32278
32450
  * Auto-play was prevented, try muted play
32279
32451
  *
@@ -32337,6 +32509,18 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32337
32509
 
32338
32510
  // Call super() to set up common layout
32339
32511
  super.setup();
32512
+ this.isVideoPlayerV2 = this.featureEnabled('videoPlayerV2.enabled') && this.supportsVideoPlayerV2();
32513
+ this.isVideoPosterEarlyPaint = this.isVideoPlayerV2 && this.featureEnabled('videoPosterEarlyPaint.enabled');
32514
+ if (this.isVideoPlayerV2) {
32515
+ this.wrapperEl.classList.add('bp-media--v2');
32516
+ this.mediaContainerEl.classList.add('bp-media-container--v2');
32517
+ if (this.isVideoPosterEarlyPaint) {
32518
+ this.mediaStageEl = document.createElement('div');
32519
+ this.mediaStageEl.classList.add('bp-media-stage--v2');
32520
+ this.wrapperEl.insertBefore(this.mediaStageEl, this.mediaContainerEl);
32521
+ this.mediaStageEl.appendChild(this.mediaContainerEl);
32522
+ }
32523
+ }
32340
32524
  this.videoAnnotationsEnabled = this.featureEnabled(VIDEO_ANNOTATIONS_ENABLED);
32341
32525
  this.isNarrowVideo = false;
32342
32526
  this.userRequestedPlay = false;
@@ -32359,17 +32543,22 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32359
32543
  this.bufferingSpinnerEl = this.mediaContainerEl.appendChild(document.createElement('div'));
32360
32544
  this.bufferingSpinnerEl.classList.add('bp-media-buffering-spinner');
32361
32545
  this.bufferingSpinnerEl.classList.add(CLASS_HIDDEN);
32362
- if (this.useReactControls() && !this.featureEnabled('videoPlayerV2.enabled')) {
32546
+ if (this.useReactControls() && !this.isVideoPlayerV2) {
32363
32547
  // Shift up by half the control bar + half the spinner to visually center above the controls
32364
32548
  this.bufferingSpinnerEl.style.marginTop = `-${VIDEO_PLAYER_CONTROL_BAR_HEIGHT / 2 + SPINNER_HALF_SIZE}px`;
32365
32549
  }
32366
- if (this.featureEnabled('videoPlayerV2.enabled')) {
32367
- this.wrapperEl.classList.add('bp-media--v2');
32368
- this.mediaContainerEl.classList.add('bp-media-container--v2');
32369
- }
32370
32550
  this.lowerLights();
32371
32551
  }
32372
32552
 
32553
+ /**
32554
+ * Whether this viewer supports the V2 video player structure.
32555
+ *
32556
+ * @return {boolean} true when V2 is supported
32557
+ */
32558
+ supportsVideoPlayerV2() {
32559
+ return true;
32560
+ }
32561
+
32373
32562
  /**
32374
32563
  * Loads a media source.
32375
32564
  *
@@ -32386,14 +32575,21 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32386
32575
  /**
32387
32576
  * Dismisses the preload thumbnail if it is currently visible. Safe to call
32388
32577
  * multiple times — hidePreload() guards on wrapperEl internally.
32578
+ * Always reveals the video: the poster wrapper may already be gone after fade/cleanup
32579
+ * while mediaEl is still bp-is-invisible from showPreload().
32389
32580
  *
32390
32581
  * @private
32391
32582
  * @return {void}
32392
32583
  */
32393
32584
  dismissPreload() {
32394
- if (this.preloader?.wrapperEl) {
32585
+ const hadPoster = Boolean(this.preloader?.wrapperEl);
32586
+ this.preloader?.blockFuturePosterPaint?.();
32587
+ if (hadPoster) {
32395
32588
  this.hidePreload();
32396
- this.mediaEl.classList.remove(CLASS_INVISIBLE);
32589
+ }
32590
+ this.mediaEl?.classList.remove(CLASS_INVISIBLE);
32591
+ // Avoid extra resize on no-op calls (e.g. play with no Instant Preview).
32592
+ if (hadPoster) {
32397
32593
  this.resize();
32398
32594
  }
32399
32595
  }
@@ -32404,9 +32600,7 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32404
32600
  * @return {void}
32405
32601
  */
32406
32602
  handlePlayRequest() {
32407
- if (this.preloader) {
32408
- this.preloader.showLoading();
32409
- }
32603
+ this.preloader?.showLoading();
32410
32604
  this.userRequestedPlay = true;
32411
32605
  this.togglePlay();
32412
32606
  }
@@ -32453,7 +32647,12 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32453
32647
  const options = {
32454
32648
  onImageClick: () => this.handlePlayRequest()
32455
32649
  };
32456
- if (this.wrapperEl) {
32650
+ if (this.isVideoPosterEarlyPaint) {
32651
+ // Resolve viewport at paint time so sidebar/layout changes after showPreload
32652
+ // don't leave the poster sized to a stale, oversized box.
32653
+ options.getViewport = () => this.getVideoViewport();
32654
+ options.earlyPaint = true;
32655
+ } else if (this.wrapperEl) {
32457
32656
  const controlsHeight = this.useReactControls() ? VIDEO_PLAYER_CONTROL_BAR_HEIGHT : 0;
32458
32657
  options.viewport = {
32459
32658
  width: this.wrapperEl.clientWidth,
@@ -32482,9 +32681,7 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32482
32681
  * @return {void}
32483
32682
  */
32484
32683
  hidePreload() {
32485
- if (this.preloader) {
32486
- this.preloader.hidePreload();
32487
- }
32684
+ this.preloader?.hidePreload();
32488
32685
  }
32489
32686
 
32490
32687
  /**
@@ -32586,7 +32783,12 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32586
32783
  destroy() {
32587
32784
  if (this.preloadBlobUrl) {
32588
32785
  URL.revokeObjectURL(this.preloadBlobUrl);
32786
+ this.preloadBlobUrl = undefined;
32589
32787
  }
32788
+
32789
+ // Stop in-flight Instant Preview from painting after navigate-away, and drop any poster DOM.
32790
+ this.preloader?.blockFuturePosterPaint?.();
32791
+ this.preloader?.cleanupPreload?.();
32590
32792
  if (this.mediaEl) {
32591
32793
  this.mediaEl.removeEventListener('mousemove', this.mousemoveHandler);
32592
32794
  this.mediaEl.removeEventListener('click', this.pointerHandler);
@@ -32606,6 +32808,20 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32606
32808
  super.destroy();
32607
32809
  }
32608
32810
 
32811
+ /**
32812
+ * Once the video has data: stop late Instant Preview painting, and reveal the
32813
+ * video element when no poster wrapper is covering it.
32814
+ *
32815
+ * @protected
32816
+ * @return {void}
32817
+ */
32818
+ syncInstantPreviewWithLoadedVideo() {
32819
+ this.preloader?.blockFuturePosterPaint?.();
32820
+ if (!this.preloader?.wrapperEl) {
32821
+ this.mediaEl?.classList.remove(CLASS_INVISIBLE);
32822
+ }
32823
+ }
32824
+
32609
32825
  /**
32610
32826
  * Handler for meta data load for the media element.
32611
32827
  *
@@ -32614,9 +32830,7 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32614
32830
  */
32615
32831
  loadeddataHandler() {
32616
32832
  super.loadeddataHandler();
32617
- if (!this.preloader?.wrapperEl) {
32618
- this.mediaEl.classList.remove(CLASS_INVISIBLE);
32619
- }
32833
+ this.syncInstantPreviewWithLoadedVideo();
32620
32834
  this.showPlayButton();
32621
32835
  if (this.userRequestedPlay) {
32622
32836
  this.play();
@@ -32651,7 +32865,7 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32651
32865
 
32652
32866
  // For the V2 player, mount the controls on the wrapper (above the media container)
32653
32867
  // so their width is constrained by the viewport rather than the video
32654
- const controlsContainerEl = this.featureEnabled('videoPlayerV2.enabled') ? this.wrapperEl : this.mediaContainerEl;
32868
+ const controlsContainerEl = this.isVideoPlayerV2 ? this.wrapperEl : this.mediaContainerEl;
32655
32869
  this.controls = new ControlsRoot({
32656
32870
  className: 'bp-VideoControlsRoot',
32657
32871
  containerEl: controlsContainerEl,
@@ -32789,10 +33003,11 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32789
33003
  */
32790
33004
  resize() {
32791
33005
  // Reset any prior set widths and heights
32792
- // We are only going to modify the widths and not heights
32793
- // This is because in Chrome its not possible to set a height
32794
- // that larger than the current videoHeight.
33006
+ // V1 only modifies widths: Chrome can't set a height larger than the current videoHeight
32795
33007
  this.mediaEl.style.width = '';
33008
+ if (this.isVideoPosterEarlyPaint) {
33009
+ this.mediaEl.style.height = '';
33010
+ }
32796
33011
  if (this.mediaContainerEl) {
32797
33012
  this.mediaContainerEl.style.width = '';
32798
33013
  this.mediaContainerEl.style.height = '';
@@ -32810,6 +33025,47 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32810
33025
  super.resize();
32811
33026
  }
32812
33027
 
33028
+ /**
33029
+ * Returns the usable V2 stage content box (inside padding).
33030
+ *
33031
+ * Prefer the stage element's box over the wrapper: the stage is what actually
33032
+ * clips/centers the shared media frame. V2 controls are position:absolute overlays
33033
+ * and do not consume flex space, so do not reserve control-bar height here.
33034
+ *
33035
+ * @private
33036
+ * @return {Object} viewport width and height
33037
+ */
33038
+ getVideoViewport() {
33039
+ if (!this.mediaStageEl) {
33040
+ return {
33041
+ height: Math.max(0, this.wrapperEl.clientHeight),
33042
+ width: Math.max(0, this.wrapperEl.clientWidth)
33043
+ };
33044
+ }
33045
+ const stageStyle = window.getComputedStyle(this.mediaStageEl);
33046
+ const horizontalPadding = (parseFloat(stageStyle.paddingLeft) || 0) + (parseFloat(stageStyle.paddingRight) || 0);
33047
+ const verticalPadding = (parseFloat(stageStyle.paddingTop) || 0) + (parseFloat(stageStyle.paddingBottom) || 0);
33048
+ return {
33049
+ width: Math.max(0, this.mediaStageEl.clientWidth - horizontalPadding),
33050
+ height: Math.max(0, this.mediaStageEl.clientHeight - verticalPadding)
33051
+ };
33052
+ }
33053
+
33054
+ /**
33055
+ * Applies width/height to the shared V2 media frame (container + video element).
33056
+ *
33057
+ * @private
33058
+ * @param {number} width
33059
+ * @param {number} height
33060
+ * @return {void}
33061
+ */
33062
+ applySharedMediaFrame(width, height) {
33063
+ this.mediaEl.style.width = `${width}px`;
33064
+ this.mediaEl.style.height = `${height}px`;
33065
+ this.mediaContainerEl.style.width = `${width}px`;
33066
+ this.mediaContainerEl.style.height = `${height}px`;
33067
+ }
33068
+
32813
33069
  /**
32814
33070
  * Calculates and applies video dimensions based on viewport constraints.
32815
33071
  * Handles three cases: video fits in viewport, fullscreen mode, or overflow.
@@ -32821,15 +33077,26 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32821
33077
  let width = this.videoWidth || 0;
32822
33078
  let height = this.videoHeight || 0;
32823
33079
  const controlsHeight = this.useReactControls() ? VIDEO_PLAYER_CONTROL_BAR_HEIGHT : 0;
32824
-
32825
- // Calculate the viewport height minus the control bar height if using react controls
32826
- // This is necessary to prevent the control bar from overflowing the viewport when the video scale
32827
- // is expanded.
32828
- const viewport = {
33080
+ const viewport = this.isVideoPosterEarlyPaint ? this.getVideoViewport() : {
32829
33081
  height: this.wrapperEl.clientHeight - controlsHeight,
32830
33082
  width: this.wrapperEl.clientWidth
32831
33083
  };
32832
33084
 
33085
+ // Early-paint V2: one contain-fit into the stage for poster and video so Instant Preview
33086
+ // and playback share the same frame (V2 controls overlay and do not change the stage).
33087
+ if (this.isVideoPosterEarlyPaint) {
33088
+ if (this.preloader?.isVisible()) {
33089
+ this.preloader.sizeContainerToViewport(viewport);
33090
+ this.mediaEl.style.width = this.mediaContainerEl.style.width;
33091
+ this.mediaEl.style.height = this.mediaContainerEl.style.height;
33092
+ return;
33093
+ }
33094
+ const aspectRatio = this.aspect || width / (height || 1) || 1;
33095
+ const frame = fitFrameToViewport(aspectRatio, viewport);
33096
+ this.applySharedMediaFrame(frame.width, frame.height);
33097
+ return;
33098
+ }
33099
+
32833
33100
  // We need the width to be atleast wide enough for the controls
32834
33101
  // to not overflow and fit properly
32835
33102
  if (width < MIN_VIDEO_WIDTH_PX) {
@@ -32869,7 +33136,9 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32869
33136
  if (this.mediaContainerEl && this.mediaEl.style.width) {
32870
33137
  this.mediaContainerEl.style.width = this.mediaEl.style.width;
32871
33138
  }
32872
- if (this.featureEnabled('videoPlayerV2.enabled')) {
33139
+
33140
+ // Legacy V2: let the padded container fill the stage; size Instant Preview to the video box.
33141
+ if (this.isVideoPlayerV2) {
32873
33142
  this.mediaContainerEl.style.width = '';
32874
33143
  if (this.preloader?.wrapperEl && this.mediaEl.style.width) {
32875
33144
  const videoWidth = parseInt(this.mediaEl.style.width, 10);
@@ -32892,7 +33161,7 @@ class VideoBaseViewer extends media_MediaBaseViewer {
32892
33161
  */
32893
33162
  handleNarrowVideoUI() {
32894
33163
  if (this.useReactControls()) {
32895
- const widthNumber = this.featureEnabled('videoPlayerV2.enabled') ? this.wrapperEl.clientWidth : parseInt(this.mediaEl.style.width, 10);
33164
+ const widthNumber = this.isVideoPlayerV2 ? this.wrapperEl.clientWidth : parseInt(this.mediaEl.style.width, 10);
32896
33165
  if (!Number.isNaN(widthNumber)) {
32897
33166
  // check if play and seek buttons exist in the dom
32898
33167
  if (this.playContainerEl && widthNumber >= SMALL_VIDEO_WIDTH_THRESHOLD) {
@@ -34117,7 +34386,6 @@ class MP4Viewer extends media_VideoBaseViewer {
34117
34386
 
34118
34387
  // Call super() first to set up common layout
34119
34388
  super.setup();
34120
- this.isVideoPlayerV2 = this.featureEnabled('videoPlayerV2.enabled');
34121
34389
 
34122
34390
  // mp4 specific class
34123
34391
  this.wrapperEl.classList.add(CSS_CLASS_MP4);
@@ -34153,9 +34421,7 @@ class MP4Viewer extends media_VideoBaseViewer {
34153
34421
  if (this.isDestroyed()) {
34154
34422
  return;
34155
34423
  }
34156
- if (!this.preloader?.wrapperEl) {
34157
- this.mediaEl.classList.remove(CLASS_INVISIBLE);
34158
- }
34424
+ this.syncInstantPreviewWithLoadedVideo();
34159
34425
  if (this.useReactControls()) {
34160
34426
  this.loadUIReact();
34161
34427
  } else {
@@ -34514,7 +34780,6 @@ class DashViewer extends media_VideoBaseViewer {
34514
34780
 
34515
34781
  // dash specific class
34516
34782
  this.wrapperEl.classList.add(CSS_CLASS_DASH);
34517
- this.isVideoPlayerV2 = this.featureEnabled('videoPlayerV2.enabled');
34518
34783
  }
34519
34784
 
34520
34785
  /**
@@ -34576,7 +34841,7 @@ class DashViewer extends media_VideoBaseViewer {
34576
34841
  this.showPreload();
34577
34842
  return Promise.resolve();
34578
34843
  }
34579
- if (!this.preloader?.wrapperEl) this.showPreload();
34844
+ if (!this.preloader?.wrapperEl && !this.preloader?.showPreloadPromise) this.showPreload();
34580
34845
  this.mediaUrl = this.options.representation.content.url_template;
34581
34846
  this.watermarkCacheBust = Date.now();
34582
34847
  this.addEventListenersForMediaLoad();
@@ -35265,9 +35530,7 @@ class DashViewer extends media_VideoBaseViewer {
35265
35530
  if (this.isDestroyed()) {
35266
35531
  return;
35267
35532
  }
35268
- if (!this.preloader?.wrapperEl) {
35269
- this.mediaEl.classList.remove(CLASS_INVISIBLE);
35270
- }
35533
+ this.syncInstantPreviewWithLoadedVideo();
35271
35534
  this.calculateVideoDimensions();
35272
35535
  if (this.useReactControls()) {
35273
35536
  this.loadUIReact();
@@ -36166,6 +36429,11 @@ class Video360Viewer extends media_DashViewer {
36166
36429
  this.onCanvasMouseUp = this.onCanvasMouseUp.bind(this);
36167
36430
  }
36168
36431
 
36432
+ /** @inheritdoc */
36433
+ supportsVideoPlayerV2() {
36434
+ return false;
36435
+ }
36436
+
36169
36437
  /** @inheritdoc */
36170
36438
  setup() {
36171
36439
  if (this.isSetup) {
@@ -36174,11 +36442,6 @@ class Video360Viewer extends media_DashViewer {
36174
36442
 
36175
36443
  // Call super() to set up common layout
36176
36444
  super.setup();
36177
-
36178
- // 360 videos do not support v2 video player yet, so remove the v2 classes
36179
- this.isVideoPlayerV2 = false;
36180
- this.wrapperEl.classList.remove('bp-media--v2');
36181
- this.mediaContainerEl.classList.remove('bp-media-container--v2');
36182
36445
  this.destroyed = false;
36183
36446
 
36184
36447
  // Hide video element