box-content-preview 3.82.0 → 3.84.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
@@ -955,6 +955,7 @@ __webpack_require__.d(__webpack_exports__, {
955
955
  wU: () => (/* binding */ decodeKeydown),
956
956
  Is: () => (/* binding */ findScriptLocation),
957
957
  kd: () => (/* binding */ getClosestPageToPinch),
958
+ RU: () => (/* binding */ getCurrentTimeMs),
958
959
  Yf: () => (/* binding */ getDistance),
959
960
  dJ: () => (/* binding */ getHeaders),
960
961
  t9: () => (/* binding */ getMidpoint),
@@ -1009,7 +1010,7 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
1009
1010
  const CLIENT_NAME = "box-content-preview"; // eslint-disable-line no-undef
1010
1011
  const CLIENT_NAME_KEY = 'box_client_name';
1011
1012
  const CLIENT_VERSION_KEY = 'box_client_version';
1012
- const CLIENT_VERSION = "3.82.0"; // eslint-disable-line no-undef
1013
+ const CLIENT_VERSION = "3.84.0"; // eslint-disable-line no-undef
1013
1014
  const HEADER_CLIENT_NAME = 'X-Box-Client-Name';
1014
1015
  const HEADER_CLIENT_VERSION = 'X-Box-Client-Version';
1015
1016
  const PROMISE_MAP = {};
@@ -1617,6 +1618,16 @@ function getDistance(x1, y1, x2, y2) {
1617
1618
  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
1618
1619
  }
1619
1620
 
1621
+ /**
1622
+ * Monotonic clock in milliseconds. Falls back to wall time when performance is missing.
1623
+ *
1624
+ * @public
1625
+ * @return {number} Current time in milliseconds
1626
+ */
1627
+ function getCurrentTimeMs() {
1628
+ return typeof performance !== 'undefined' ? performance.now() : Date.now();
1629
+ }
1630
+
1620
1631
  /**
1621
1632
  * Returns the closest visible page to a pinch event
1622
1633
  *
@@ -3929,6 +3940,172 @@ let Menu = /*#__PURE__*/function (Menu) {
3929
3940
 
3930
3941
  /***/ },
3931
3942
 
3943
+ /***/ 4937
3944
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
3945
+
3946
+
3947
+ // EXPORTS
3948
+ __webpack_require__.d(__webpack_exports__, {
3949
+ A: () => (/* binding */ SliderControl)
3950
+ });
3951
+
3952
+ // EXTERNAL MODULE: external "react"
3953
+ var external_react_ = __webpack_require__(1649);
3954
+ // EXTERNAL MODULE: ./node_modules/classnames/index.js
3955
+ var classnames = __webpack_require__(2485);
3956
+ var classnames_default = /*#__PURE__*/__webpack_require__.n(classnames);
3957
+ // EXTERNAL MODULE: ./node_modules/lodash/noop.js
3958
+ var noop = __webpack_require__(3950);
3959
+ var noop_default = /*#__PURE__*/__webpack_require__.n(noop);
3960
+ // EXTERNAL MODULE: ./src/lib/util.js + 1 modules
3961
+ var util = __webpack_require__(4410);
3962
+ ;// ./src/lib/viewers/controls/slider/SliderControl.scss
3963
+ // extracted by mini-css-extract-plugin
3964
+
3965
+ ;// ./src/lib/viewers/controls/slider/SliderControl.tsx
3966
+ const _excluded = ["className", "max", "min", "onMove", "onUpdate", "step", "title", "track", "value"];
3967
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
3968
+ function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
3969
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
3970
+
3971
+
3972
+
3973
+
3974
+
3975
+ function SliderControl(_ref) {
3976
+ let {
3977
+ className,
3978
+ max = 100,
3979
+ min = 0,
3980
+ onMove = (noop_default()),
3981
+ onUpdate = (noop_default()),
3982
+ step = 1,
3983
+ title,
3984
+ track,
3985
+ value
3986
+ } = _ref,
3987
+ rest = _objectWithoutProperties(_ref, _excluded);
3988
+ const [isScrubbing, setIsScrubbing] = external_react_["default"].useState(false);
3989
+ const sliderElRef = external_react_["default"].useRef(null);
3990
+ const getPosition = external_react_["default"].useCallback(pageX => {
3991
+ const {
3992
+ current: sliderEl
3993
+ } = sliderElRef;
3994
+ if (!sliderEl) return 0;
3995
+ const {
3996
+ left: sliderLeft,
3997
+ width: sliderWidth
3998
+ } = sliderEl.getBoundingClientRect();
3999
+ return Math.max(0, Math.min(pageX - sliderLeft, sliderWidth));
4000
+ }, []);
4001
+ const getPositionValue = external_react_["default"].useCallback(pageX => {
4002
+ const {
4003
+ current: sliderEl
4004
+ } = sliderElRef;
4005
+ if (!sliderEl) return 0;
4006
+ const {
4007
+ width: sliderWidth
4008
+ } = sliderEl.getBoundingClientRect();
4009
+ const newValue = getPosition(pageX) / sliderWidth * max;
4010
+ return Math.max(min, Math.min(newValue, max));
4011
+ }, [getPosition, max, min]);
4012
+ const handleKeydown = event => {
4013
+ const key = (0,util/* decodeKeydown */.wU)(event);
4014
+ if (key === 'ArrowLeft') {
4015
+ event.stopPropagation(); // Prevents global key handling
4016
+ onUpdate(Math.max(min, Math.min(value - step, max)));
4017
+ }
4018
+ if (key === 'ArrowRight') {
4019
+ event.stopPropagation(); // Prevents global key handling
4020
+ onUpdate(Math.max(min, Math.min(value + step, max)));
4021
+ }
4022
+ };
4023
+ const handleMouseDown = ({
4024
+ button,
4025
+ ctrlKey,
4026
+ metaKey,
4027
+ pageX
4028
+ }) => {
4029
+ if (button > 1 || ctrlKey || metaKey) return;
4030
+ onUpdate(getPositionValue(pageX));
4031
+ setIsScrubbing(true);
4032
+ };
4033
+ const handleMouseMove = ({
4034
+ pageX
4035
+ }) => {
4036
+ const {
4037
+ current: sliderEl
4038
+ } = sliderElRef;
4039
+ const {
4040
+ width: sliderWidth
4041
+ } = sliderEl ? sliderEl.getBoundingClientRect() : {
4042
+ width: 0
4043
+ };
4044
+ onMove(getPositionValue(pageX), getPosition(pageX), sliderWidth);
4045
+ };
4046
+ const handleTouchStart = ({
4047
+ touches
4048
+ }) => {
4049
+ onUpdate(getPositionValue(touches[0].pageX));
4050
+ setIsScrubbing(true);
4051
+ };
4052
+ external_react_["default"].useEffect(() => {
4053
+ const handleDocumentMoveStop = () => setIsScrubbing(false);
4054
+ const handleDocumentMouseMove = event => {
4055
+ if (!isScrubbing || event.button > 1 || event.ctrlKey || event.metaKey) return;
4056
+ event.preventDefault();
4057
+ onUpdate(getPositionValue(event.pageX));
4058
+ };
4059
+ const handleDocumentTouchMove = event => {
4060
+ if (!isScrubbing || !event.touches || !event.touches[0]) return;
4061
+ event.preventDefault();
4062
+ onUpdate(getPositionValue(event.touches[0].pageX));
4063
+ };
4064
+ if (isScrubbing) {
4065
+ document.addEventListener('mousemove', handleDocumentMouseMove);
4066
+ document.addEventListener('mouseup', handleDocumentMoveStop);
4067
+ document.addEventListener('touchend', handleDocumentMoveStop);
4068
+ document.addEventListener('touchmove', handleDocumentTouchMove);
4069
+ }
4070
+ return () => {
4071
+ document.removeEventListener('mousemove', handleDocumentMouseMove);
4072
+ document.removeEventListener('mouseup', handleDocumentMoveStop);
4073
+ document.removeEventListener('touchend', handleDocumentMoveStop);
4074
+ document.removeEventListener('touchmove', handleDocumentTouchMove);
4075
+ };
4076
+ }, [isScrubbing, getPositionValue, onUpdate]);
4077
+ return /*#__PURE__*/external_react_["default"].createElement("div", _extends({
4078
+ ref: sliderElRef,
4079
+ "aria-label": title,
4080
+ "aria-valuemax": max,
4081
+ "aria-valuemin": min,
4082
+ "aria-valuenow": value,
4083
+ className: classnames_default()('bp-SliderControl', className, {
4084
+ 'bp-is-scrubbing': isScrubbing
4085
+ }),
4086
+ onKeyDown: handleKeydown,
4087
+ onMouseDown: handleMouseDown,
4088
+ onMouseMove: handleMouseMove,
4089
+ onTouchStart: handleTouchStart,
4090
+ role: "slider",
4091
+ tabIndex: 0
4092
+ }, rest), /*#__PURE__*/external_react_["default"].createElement("div", {
4093
+ className: "bp-SliderControl-track",
4094
+ "data-testid": "bp-slider-control-track",
4095
+ style: {
4096
+ backgroundImage: track
4097
+ }
4098
+ }), /*#__PURE__*/external_react_["default"].createElement("div", {
4099
+ className: "bp-SliderControl-thumb",
4100
+ "data-testid": "bp-slider-control-thumb",
4101
+ style: {
4102
+ left: `${value / max * 100}%`
4103
+ }
4104
+ }));
4105
+ }
4106
+
4107
+ /***/ },
4108
+
3932
4109
  /***/ 9974
3933
4110
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
3934
4111
 
@@ -3941,7 +4118,7 @@ function getPdfjsWorkerSrc() {
3941
4118
 
3942
4119
  /***/ },
3943
4120
 
3944
- /***/ 7893
4121
+ /***/ 3845
3945
4122
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
3946
4123
 
3947
4124
 
@@ -3967,6 +4144,8 @@ var TimestampControl = __webpack_require__(3904);
3967
4144
  var VolumeControls = __webpack_require__(3945);
3968
4145
  // EXTERNAL MODULE: ./src/lib/icons/play_48px.svg
3969
4146
  var play_48px = __webpack_require__(3493);
4147
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/constants.ts
4148
+ var constants = __webpack_require__(4929);
3970
4149
  ;// ./src/lib/viewers/media/waveform/peaks.ts
3971
4150
  const PLACEHOLDER_PEAK_AMPLITUDE = 0;
3972
4151
  const PLACEHOLDER_PEAK_COUNT = 2000;
@@ -4063,10 +4242,126 @@ function morphPeaks(from, to, elapsedMs) {
4063
4242
  }
4064
4243
  return out;
4065
4244
  }
4245
+ ;// ./src/lib/viewers/media/waveform/viewport.ts
4246
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4247
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4248
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4249
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4250
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4251
+
4252
+ function createWaveformViewport({
4253
+ durationSec,
4254
+ heightPx,
4255
+ maxZoom,
4256
+ scrollLeftPx,
4257
+ widthPx,
4258
+ zoomLevel
4259
+ }) {
4260
+ const zoom = Number.isFinite(zoomLevel) && zoomLevel > 0 ? zoomLevel : constants/* WAVEFORM_ZOOM_MIN */.LW;
4261
+ const viewDurationSec = durationSec > 0 ? durationSec / zoom : 0; // seconds visible at this zoom
4262
+ const pixelsPerSecond = viewDurationSec > 0 && widthPx > 0 ? widthPx / viewDurationSec : 0;
4263
+ const maxStartSec = Math.max(0, durationSec - viewDurationSec); // last start that still fills the window
4264
+ const startSec = pixelsPerSecond > 0 ? Math.min(maxStartSec, Math.max(0, scrollLeftPx / pixelsPerSecond)) : 0;
4265
+ return {
4266
+ durationSec,
4267
+ endSec: startSec + viewDurationSec,
4268
+ heightPx,
4269
+ maxZoom,
4270
+ pixelsPerSecond,
4271
+ scrollLeftPx,
4272
+ startSec,
4273
+ widthPx,
4274
+ zoomLevel: zoom
4275
+ };
4276
+ }
4277
+
4278
+ /** Same viewport with a different scroll, so start/end times update. */
4279
+ function getViewportAtScroll(viewport, scrollLeftPx) {
4280
+ return createWaveformViewport(_objectSpread(_objectSpread({}, viewport), {}, {
4281
+ scrollLeftPx
4282
+ }));
4283
+ }
4284
+
4285
+ /** How many CSS pixels from the left of the visible window this time sits. */
4286
+ function positionPxFromTime(timeSec, viewport) {
4287
+ return (timeSec - viewport.startSec) * viewport.pixelsPerSecond;
4288
+ }
4289
+
4290
+ /** Media time under a point this many CSS pixels from the left of the visible window. */
4291
+ function timeFromPositionPx(positionPx, viewport) {
4292
+ if (!(viewport.pixelsPerSecond > 0)) {
4293
+ return viewport.startSec;
4294
+ }
4295
+ return viewport.startSec + positionPx / viewport.pixelsPerSecond;
4296
+ }
4297
+
4298
+ /** True when this time sits inside the visible window. */
4299
+ function isTimeInView(timeSec, viewport) {
4300
+ return timeSec >= viewport.startSec && timeSec <= viewport.endSec;
4301
+ }
4302
+
4303
+ /** UI max zoom: 24×, ~1 peak per CSS pixel, and a minimum visible window. */
4304
+ function getWaveformZoomMax({
4305
+ durationSec,
4306
+ peakCount,
4307
+ viewWidthPx
4308
+ }) {
4309
+ if (!(peakCount > 0) || !(viewWidthPx > 0)) {
4310
+ return constants/* WAVEFORM_ZOOM_MIN */.LW;
4311
+ }
4312
+ const peakLimitedMax = Math.floor(peakCount / viewWidthPx);
4313
+ const durationLimitedMax = durationSec > 0 ? durationSec / constants/* WAVEFORM_MIN_VIEW_WINDOW_SEC */.Kl : constants/* WAVEFORM_ZOOM_MIN */.LW;
4314
+ return Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, Math.min(constants/* WAVEFORM_ZOOM_MAX */.tK, peakLimitedMax, durationLimitedMax));
4315
+ }
4316
+
4317
+ /** Keep zoom between 1× and this file's max. */
4318
+ function clampWaveformZoom(zoomLevel, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4319
+ const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4320
+ if (!Number.isFinite(zoomLevel)) {
4321
+ return constants/* WAVEFORM_ZOOM_MIN */.LW;
4322
+ }
4323
+ return Math.min(max, Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, zoomLevel));
4324
+ }
4325
+
4326
+ /** WaveSurfer zoom density. 0 = fit the whole file in the view. */
4327
+ function getZoomedPixelsPerSecond({
4328
+ durationSec,
4329
+ maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW,
4330
+ viewWidthPx,
4331
+ zoomLevel
4332
+ }) {
4333
+ const zoom = clampWaveformZoom(zoomLevel, maxZoom);
4334
+ if (zoom <= constants/* WAVEFORM_ZOOM_MIN */.LW || !(durationSec > 0) || !(viewWidthPx > 0)) {
4335
+ return 0;
4336
+ }
4337
+ return viewWidthPx / durationSec * zoom;
4338
+ }
4339
+
4340
+ /** Map zoom (1…max) onto the 0–100 slider. */
4341
+ function sliderValueFromZoom(zoomLevel, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4342
+ const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4343
+ const zoom = clampWaveformZoom(zoomLevel, max);
4344
+ if (max <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4345
+ return 0;
4346
+ }
4347
+ return (zoom - constants/* WAVEFORM_ZOOM_MIN */.LW) / (max - constants/* WAVEFORM_ZOOM_MIN */.LW) * constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
4348
+ }
4349
+
4350
+ /** Inverse of sliderValueFromZoom. */
4351
+ function zoomFromSliderValue(value, maxZoom = constants/* WAVEFORM_ZOOM_MIN */.LW) {
4352
+ const max = Math.max(constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4353
+ if (max <= constants/* WAVEFORM_ZOOM_MIN */.LW) {
4354
+ return constants/* WAVEFORM_ZOOM_MIN */.LW;
4355
+ }
4356
+ const t = Math.min(constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh, Math.max(0, value)) / constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh;
4357
+ return clampWaveformZoom(constants/* WAVEFORM_ZOOM_MIN */.LW + t * (max - constants/* WAVEFORM_ZOOM_MIN */.LW), max);
4358
+ }
4066
4359
  ;// ./node_modules/wavesurfer.js/dist/wavesurfer.esm.js
4067
4360
  function t(t,e,i,n){return new(i||(i=Promise))((function(s,r){function o(t){try{l(n.next(t))}catch(t){r(t)}}function a(t){try{l(n.throw(t))}catch(t){r(t)}}function l(t){var e;t.done?s(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(o,a)}l((n=n.apply(t,e||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class e{constructor(){this.listeners={}}on(t,e,i){if(this.listeners[t]||(this.listeners[t]=new Set),null==i?void 0:i.once){const i=(...n)=>{this.un(t,i),e(...n)};return this.listeners[t].add(i),()=>this.un(t,i)}return this.listeners[t].add(e),()=>this.un(t,e)}un(t,e){var i;null===(i=this.listeners[t])||void 0===i||i.delete(e)}once(t,e){return this.on(t,e,{once:!0})}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}const i={decode:function(e,i){return t(this,void 0,void 0,(function*(){const t=new AudioContext({sampleRate:i});try{return yield t.decodeAudioData(e)}finally{"closed"!==t.state&&(yield t.close().catch((()=>{})))}}))},createBuffer:function(t,e){if(!t||0===t.length)throw new Error("channelData must be a non-empty array");if(e<=0)throw new Error("duration must be greater than 0");if("number"==typeof t[0]&&(t=[t]),!t[0]||0===t[0].length)throw new Error("channelData must contain non-empty channel arrays");!function(t){const e=t[0];if(e.some((t=>t>1||t<-1))){const i=e.length;let n=0;for(let t=0;t<i;t++){const i=Math.abs(e[t]);i>n&&(n=i)}for(const e of t)for(let t=0;t<i;t++)e[t]/=n}}(t);const i=t.map((t=>t instanceof Float32Array?t:Float32Array.from(t)));return{duration:e,length:i[0].length,sampleRate:i[0].length/e,numberOfChannels:i.length,getChannelData:t=>{const e=i[t];if(!e)throw new Error(`Channel ${t} not found`);return e},copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}};function n(t,e){const i=e.xmlns?document.createElementNS(e.xmlns,t):document.createElement(t);for(const[t,s]of Object.entries(e))if("children"===t&&s)for(const[t,e]of Object.entries(s))e instanceof Node?i.appendChild(e):"string"==typeof e?i.appendChild(document.createTextNode(e)):i.appendChild(n(t,e));else"style"===t?Object.assign(i.style,s):"textContent"===t?i.textContent=s:i.setAttribute(t,s.toString());return i}function s(t,e,i){const s=n(t,e||{});return null==i||i.appendChild(s),s}function r(t){return t instanceof HTMLElement||"object"==typeof t&&null!==t&&t.nodeType===Node.ELEMENT_NODE&&"object"==typeof t.style}var o=Object.freeze({__proto__:null,createElement:s,default:s,isHTMLElement:r});const a={fetchBlob:function(e,i,n){return t(this,void 0,void 0,(function*(){var s;const r=yield fetch(e,n);if(r.status>=400)throw new Error(`Failed to fetch ${e}: ${r.status} (${r.statusText})`);return function(e,i,n){t(this,void 0,void 0,(function*(){var t;if(!e.body||!e.headers)return;const s=e.body.getReader(),r=Number(e.headers.get("Content-Length"))||0;let o=0;const a=()=>{s.cancel()};if(n){if(n.aborted)return void s.cancel();n.addEventListener("abort",a,{once:!0})}try{for(;;){const e=yield s.read();if(e.done)break;if(o+=(null===(t=e.value)||void 0===t?void 0:t.length)||0,r>0){const t=Math.round(o/r*100);i(t)}}}catch(t){if(t instanceof DOMException&&"AbortError"===t.name)return;console.warn("Progress tracking error:",t)}finally{n&&n.removeEventListener("abort",a)}}))}(r.clone(),i,null!==(s=null==n?void 0:n.signal)&&void 0!==s?s:void 0),r.blob()}))}};function l(t){let e=t;const i=new Set;return{get value(){return e},set(t){Object.is(e,t)||(e=t,i.forEach((t=>t(e))))},update(t){this.set(t(e))},subscribe:t=>(i.add(t),()=>i.delete(t))}}function h(t,e){const i=l(t());return e.forEach((e=>e.subscribe((()=>{const e=t();Object.is(i.value,e)||i.set(e)})))),{get value(){return i.value},subscribe:t=>i.subscribe(t)}}function c(t,e){let i;const n=()=>{i&&(i(),i=void 0),i=t()},s=e.map((t=>t.subscribe(n)));return n(),()=>{i&&(i(),i=void 0),s.forEach((t=>t()))}}class u extends e{get isPlayingSignal(){return this._isPlaying}get currentTimeSignal(){return this._currentTime}get durationSignal(){return this._duration}get volumeSignal(){return this._volume}get mutedSignal(){return this._muted}get playbackRateSignal(){return this._playbackRate}get seekingSignal(){return this._seeking}constructor(t){super(),this.isExternalMedia=!1,this._ownBlobUrl=null,this.reactiveMediaEventCleanups=[],t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),this._isPlaying=l(!1),this._currentTime=l(0),this._duration=l(0),this._volume=l(this.media.volume),this._muted=l(this.media.muted),this._playbackRate=l(this.media.playbackRate||1),this._seeking=l(!1),this.setupReactiveMediaEvents(),t.mediaControls&&(this.media.controls=!0),t.autoplay&&(this.media.autoplay=!0),null!=t.playbackRate&&this.onMediaEvent("canplay",(()=>{null!=t.playbackRate&&(this.media.playbackRate=t.playbackRate)}),{once:!0})}setupReactiveMediaEvents(){this.reactiveMediaEventCleanups.push(this.onMediaEvent("play",(()=>{this._isPlaying.set(!0)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("pause",(()=>{this._isPlaying.set(!1)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("ended",(()=>{this._isPlaying.set(!1)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("timeupdate",(()=>{this._currentTime.set(this.media.currentTime)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("durationchange",(()=>{this._duration.set(this.media.duration||0)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("loadedmetadata",(()=>{this._duration.set(this.media.duration||0)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("seeking",(()=>{this._seeking.set(!0)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("seeked",(()=>{this._seeking.set(!1)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("volumechange",(()=>{this._volume.set(this.media.volume),this._muted.set(this.media.muted)}))),this.reactiveMediaEventCleanups.push(this.onMediaEvent("ratechange",(()=>{this._playbackRate.set(this.media.playbackRate)})))}onMediaEvent(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e,i)}getSrc(){return this.media.currentSrc||this.media.src||""}revokeSrc(){this._ownBlobUrl&&(URL.revokeObjectURL(this._ownBlobUrl),this._ownBlobUrl=null)}canPlayType(t){return""!==this.media.canPlayType(t)}setSrc(t,e){const i=this.getSrc();if(t&&i===t)return;this.revokeSrc();const n=e instanceof Blob&&(this.canPlayType(e.type)||!t)?URL.createObjectURL(e):t;if(n!==t&&(this._ownBlobUrl=n),i&&this.media.removeAttribute("src"),n||t)try{this.media.src=n}catch(e){this.media.src=t}}destroy(){this.reactiveMediaEventCleanups.forEach((t=>t())),this.reactiveMediaEventCleanups=[],this.revokeSrc(),this.unAll(),this.isExternalMedia||(this.media.pause(),this.media.removeAttribute("src"),this.media.load(),this.media.remove())}setMediaElement(t){this.reactiveMediaEventCleanups.forEach((t=>t())),this.reactiveMediaEventCleanups=[],this.media=t,this.setupReactiveMediaEvents()}play(){return t(this,void 0,void 0,(function*(){try{return yield this.media.play()}catch(t){if(t instanceof DOMException&&"AbortError"===t.name)return;throw t}}))}pause(){this.media.pause()}isPlaying(){return!this.media.paused&&!this.media.ended}setTime(t){this.media.currentTime=Math.max(0,Math.min(t,this.getDuration()))}getDuration(){return this.media.duration}getCurrentTime(){return this.media.currentTime}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}isSeeking(){return this.media.seeking}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}getMediaElement(){return this.media}setSinkId(t){return this.media.setSinkId(t)}}function d({maxTop:t,maxBottom:e,halfHeight:i,vScale:n,barMinHeight:s=0,barAlign:r}){let o=Math.round(t*i*n);let a=o+Math.round(e*i*n)||1;return a<s&&(a=s,r||(o=a/2)),{topHeight:o,totalHeight:a}}function p({barAlign:t,halfHeight:e,topHeight:i,totalHeight:n,canvasHeight:s}){return"top"===t?0:"bottom"===t?s-n:e-i}function m(t,e,i){const n=e-t.left,s=i-t.top;return[n/t.width,s/t.height]}function g(t){return Boolean(t.barWidth||t.barGap||t.barAlign)}function f(t,e){if(!g(e))return t;const i=e.barWidth||.5,n=i+(e.barGap||i/2);return 0===n?t:Math.floor(t/n)*n}function v({scrollLeft:t,totalWidth:e,numCanvases:i}){if(0===e)return[0];const n=t/e,s=Math.floor(n*i);return[s-1,s,s+1]}function b(t){const e=t._cleanup;"function"==typeof e&&e()}function y(t){const e=l({scrollLeft:t.scrollLeft,scrollWidth:t.scrollWidth,clientWidth:t.clientWidth}),i=h((()=>function(t){const{scrollLeft:e,scrollWidth:i,clientWidth:n}=t;if(0===i)return{startX:0,endX:1};const s=e/i,r=(e+n)/i;return{startX:Math.max(0,Math.min(1,s)),endX:Math.max(0,Math.min(1,r))}}(e.value)),[e]),n=h((()=>function(t){return{left:t.scrollLeft,right:t.scrollLeft+t.clientWidth}}(e.value)),[e]),s=()=>{e.set({scrollLeft:t.scrollLeft,scrollWidth:t.scrollWidth,clientWidth:t.clientWidth})};t.addEventListener("scroll",s,{passive:!0});return{scrollData:e,percentages:i,bounds:n,cleanup:()=>{t.removeEventListener("scroll",s),b(e)}}}class C extends e{constructor(t,e){super(),this.timeouts=[],this.isScrollable=!1,this.audioData=null,this.resizeObserver=null,this.lastContainerWidth=0,this.isDragging=!1,this.subscriptions=[],this.unsubscribeOnScroll=[],this.dragStream=null,this.scrollStream=null,this.containerInlinePadding=0,this.onClickWrapper=t=>{const e=this.wrapper.getBoundingClientRect(),[i,n]=m(e,t.clientX,t.clientY);this.emit("click",i,n)},this.onDblClickWrapper=t=>{const e=this.wrapper.getBoundingClientRect(),[i,n]=m(e,t.clientX,t.clientY);this.emit("dblclick",i,n)},this.subscriptions=[],this.options=t;const i=this.parentFromOptionsContainer(t.container);this.parent=i;const[n,s]=this.initHtml();i.appendChild(n),this.container=n,this.scrollContainer=s.querySelector(".scroll"),this.wrapper=s.querySelector(".wrapper"),this.canvasWrapper=s.querySelector(".canvases"),this.progressWrapper=s.querySelector(".progress"),this.cursor=s.querySelector(".cursor"),this.calculateInlinePadding(),e&&s.appendChild(e),this.initEvents()}parentFromOptionsContainer(t){let e;if("string"==typeof t?e=document.querySelector(t):r(t)&&(e=t),!e)throw new Error("Container not found");return e}initEvents(){this.wrapper.addEventListener("click",this.onClickWrapper),this.wrapper.addEventListener("dblclick",this.onDblClickWrapper),!0!==this.options.dragToSeek&&"object"!=typeof this.options.dragToSeek||this.initDrag(),this.scrollStream=y(this.scrollContainer);const t=c((()=>{const{startX:t,endX:e}=this.scrollStream.percentages.value,{left:i,right:n}=this.scrollStream.bounds.value;this.emit("scroll",t,e,i,n)}),[this.scrollStream.percentages,this.scrollStream.bounds]);if(this.subscriptions.push(t),"function"==typeof ResizeObserver){const t=this.createDelay(100);this.resizeObserver=new ResizeObserver((()=>{t().then((()=>this.onContainerResize())).catch((()=>{}))})),this.resizeObserver.observe(this.scrollContainer)}}onContainerResize(){const t=this.parent.clientWidth;this.calculateInlinePadding(),t===this.lastContainerWidth&&"auto"!==this.options.height||(this.lastContainerWidth=t,this.reRender(),this.emit("resize"))}initDrag(){if(this.dragStream)return;this.dragStream=function(t,e={}){const{threshold:i=3,mouseButton:n=0,touchDelay:s=100}=e,r=l(null),o=new Map,a=matchMedia("(pointer: coarse)").matches;let h=()=>{};const c=e=>{if(e.button!==n)return;if(o.has(e.pointerId))return;if(o.set(e.pointerId,e),o.size>1)return;const l=e.pointerId;let c=e.clientX,u=e.clientY,d=!1;const p=Date.now(),m=t.getBoundingClientRect(),{left:g,top:f}=m,v=t=>{if(t.pointerId!==l)return;if(t.defaultPrevented||o.size>1)return;if(a&&Date.now()-p<s)return;const e=t.clientX,n=t.clientY,h=e-c,m=n-u;(d||Math.abs(h)>i||Math.abs(m)>i)&&(t.preventDefault(),t.stopPropagation(),d||(r.set({type:"start",x:c-g,y:u-f}),d=!0),r.set({type:"move",x:e-g,y:n-f,deltaX:h,deltaY:m}),c=e,u=n)},b=t=>{if(o.delete(t.pointerId)){if(t.pointerId===l&&d){const e=t.clientX,i=t.clientY;r.set({type:"end",x:e-g,y:i-f})}0===o.size&&h()}},y=t=>{t.relatedTarget&&t.relatedTarget!==document.documentElement||b(t)},C=t=>{d&&(t.stopPropagation(),t.preventDefault())},S=t=>{t.defaultPrevented||o.size>1||d&&t.preventDefault()};document.addEventListener("pointermove",v),document.addEventListener("pointerup",b),document.addEventListener("pointerout",y),document.addEventListener("pointercancel",y),document.addEventListener("touchmove",S,{passive:!1}),document.addEventListener("click",C,{capture:!0}),h=()=>{document.removeEventListener("pointermove",v),document.removeEventListener("pointerup",b),document.removeEventListener("pointerout",y),document.removeEventListener("pointercancel",y),document.removeEventListener("touchmove",S),setTimeout((()=>{document.removeEventListener("click",C,{capture:!0})}),10)}};return t.addEventListener("pointerdown",c),{signal:r,cleanup:()=>{h(),t.removeEventListener("pointerdown",c),o.clear(),b(r)}}}(this.wrapper);const t=c((()=>{const t=this.dragStream.signal.value;if(!t)return;const e=this.wrapper.getBoundingClientRect().width,i=(n=t.x/e)<0?0:n>1?1:n;var n;"start"===t.type?(this.isDragging=!0,this.emit("dragstart",i)):"move"===t.type?this.emit("drag",i):"end"===t.type&&(this.isDragging=!1,this.emit("dragend",i))}),[this.dragStream.signal]);this.subscriptions.push(t)}calculateInlinePadding(){const{paddingLeft:t,paddingRight:e}=getComputedStyle(this.scrollContainer),i=parseFloat(t)+parseFloat(e);this.containerInlinePadding=Number.isNaN(i)?0:i}initHtml(){const t=document.createElement("div"),e=t.attachShadow({mode:"open"}),i=this.options.cspNonce&&"string"==typeof this.options.cspNonce?this.options.cspNonce.replace(/"/g,""):"";return e.innerHTML=`\n <style${i?` nonce="${i}"`:""}>\n :host {\n user-select: none;\n min-width: 1px;\n }\n :host audio {\n display: block;\n width: 100%;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: hidden;\n width: 100%;\n position: relative;\n }\n :host .noScrollbar {\n scrollbar-color: transparent;\n scrollbar-width: none;\n }\n :host .noScrollbar::-webkit-scrollbar {\n display: none;\n -webkit-appearance: none;\n }\n :host .wrapper {\n position: relative;\n overflow: visible;\n z-index: 2;\n }\n :host .canvases {\n min-height: ${this.getHeight(this.options.height,this.options.splitChannels)}px;\n pointer-events: none;\n }\n :host .canvases > div {\n position: relative;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n }\n :host .progress {\n pointer-events: none;\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n width: 0;\n height: 100%;\n overflow: hidden;\n }\n :host .progress > div {\n position: relative;\n }\n :host .cursor {\n pointer-events: none;\n position: absolute;\n z-index: 5;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n }\n </style>\n\n <div class="scroll" part="scroll">\n <div class="wrapper" part="wrapper">\n <div class="canvases" part="canvases"></div>\n <div class="progress" part="progress"></div>\n <div class="cursor" part="cursor"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){var e;if(this.options.container!==t.container){const e=this.parentFromOptionsContainer(t.container);e.appendChild(this.container),this.parent=e}!0===t.dragToSeek||"object"==typeof this.options.dragToSeek?this.initDrag():(null===(e=this.dragStream)||void 0===e||e.cleanup(),this.dragStream=null),this.options=t,this.reRender()}getWrapper(){return this.wrapper}getWidth(){return this.scrollContainer.clientWidth-this.containerInlinePadding}getScroll(){return this.scrollContainer.scrollLeft}setScroll(t){this.scrollContainer.scrollLeft=t}setScrollPercentage(t){const{scrollWidth:e}=this.scrollContainer,i=e*t;this.setScroll(i)}destroy(){var t;this.wrapper.removeEventListener("click",this.onClickWrapper),this.wrapper.removeEventListener("dblclick",this.onDblClickWrapper),this.timeouts.forEach((t=>t())),this.timeouts=[],this.subscriptions.forEach((t=>t())),this.container.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),null===(t=this.unsubscribeOnScroll)||void 0===t||t.forEach((t=>t())),this.unsubscribeOnScroll=[],this.dragStream&&(this.dragStream.cleanup(),this.dragStream=null),this.scrollStream&&(this.scrollStream.cleanup(),this.scrollStream=null)}createDelay(t=10){let e,i;const n=()=>{e&&(clearTimeout(e),e=void 0),i&&(i(),i=void 0)};return this.timeouts.push(n),()=>new Promise(((s,r)=>{n(),i=r,e=setTimeout((()=>{e=void 0,i=void 0,s()}),t)}))}getHeight(t,e){var i;const n=(null===(i=this.audioData)||void 0===i?void 0:i.numberOfChannels)||1;return function({optionsHeight:t,optionsSplitChannels:e,parentHeight:i,numberOfChannels:n,defaultHeight:s=128}){if(null==t)return s;const r=Number(t);if(!isNaN(r))return r;if("auto"===t){const t=i||s;return(null==e?void 0:e.every((t=>!t.overlay)))?t/n:t}return s}({optionsHeight:t,optionsSplitChannels:e,parentHeight:this.parent.clientHeight,numberOfChannels:n,defaultHeight:128})}convertColorValues(t,e){return function(t,e,i){if(!Array.isArray(t))return t||"";if(0===t.length)return"#999";if(t.length<2)return t[0]||"";const n=document.createElement("canvas"),s=n.getContext("2d");if(!s)return t[0]||"";const r=i||n.height*e,o=s.createLinearGradient(0,0,0,r),a=1/(t.length-1);return t.forEach(((t,e)=>{o.addColorStop(e*a,t)})),o}(t,this.getPixelRatio(),null==e?void 0:e.canvas.height)}getPixelRatio(){return t=window.devicePixelRatio,Math.max(1,t||1);// removed by dead control flow
4068
4361
  var t; }renderBarWaveform(t,e,i,n){const{width:s,height:r}=i.canvas,{halfHeight:o,barWidth:a,barRadius:l,barIndexScale:h,barSpacing:c,barMinHeight:u}=function({width:t,height:e,length:i,options:n,pixelRatio:s}){const r=e/2,o=n.barWidth?n.barWidth*s:1,a=n.barGap?n.barGap*s:n.barWidth?o/2:0,l=o+a||1;return{halfHeight:r,barWidth:o,barGap:a,barRadius:n.barRadius||0,barMinHeight:n.barMinHeight?n.barMinHeight*s:0,barIndexScale:i>0?t/l/i:0,barSpacing:l}}({width:s,height:r,length:(t[0]||[]).length,options:e,pixelRatio:this.getPixelRatio()}),m=function({channelData:t,barIndexScale:e,barSpacing:i,barWidth:n,halfHeight:s,vScale:r,canvasHeight:o,barAlign:a,barMinHeight:l}){const h=t[0]||[],c=t[1]||h,u=h.length,m=[];let g=0,f=0,v=0;for(let t=0;t<=u;t++){const u=Math.round(t*e);if(u>g){const{topHeight:t,totalHeight:e}=d({maxTop:f,maxBottom:v,halfHeight:s,vScale:r,barMinHeight:l,barAlign:a}),h=p({barAlign:a,halfHeight:s,topHeight:t,totalHeight:e,canvasHeight:o});m.push({x:g*i,y:h,width:n,height:e}),g=u,f=0,v=0}const b=Math.abs(h[t]||0),y=Math.abs(c[t]||0);b>f&&(f=b),y>v&&(v=y)}return m}({channelData:t,barIndexScale:h,barSpacing:c,barWidth:a,halfHeight:o,vScale:n,canvasHeight:r,barAlign:e.barAlign,barMinHeight:u});i.beginPath();for(const t of m)l&&"roundRect"in i?i.roundRect(t.x,t.y,t.width,t.height,l):i.rect(t.x,t.y,t.width,t.height);i.fill(),i.closePath()}renderLineWaveform(t,e,i,n){const{width:s,height:r}=i.canvas,o=function({channelData:t,width:e,height:i,vScale:n}){const s=i/2,r=t[0]||[];return[r,t[1]||r].map(((t,i)=>{const r=t.length,o=r?e/r:0,a=s,l=0===i?-1:1,h=[{x:0,y:a}];let c=0,u=0;for(let e=0;e<=r;e++){const i=Math.round(e*o);if(i>c){const t=a+(Math.round(u*s*n)||1)*l;h.push({x:c,y:t}),c=i,u=0}const r=Math.abs(t[e]||0);r>u&&(u=r)}return h.push({x:c,y:a}),h}))}({channelData:t,width:s,height:r,vScale:n});i.beginPath();for(const t of o)if(t.length){i.moveTo(t[0].x,t[0].y);for(let e=1;e<t.length;e++){const n=t[e];i.lineTo(n.x,n.y)}}i.fill(),i.closePath()}renderWaveform(t,e,i){if(i.fillStyle=this.convertColorValues(e.waveColor,i),e.renderFunction)return void e.renderFunction(t,i);const n=function({channelData:t,barHeight:e,normalize:i,maxPeak:n}){var s;const r=e||1;if(!i)return r;const o=t[0];if(!o||0===o.length)return r;let a=null!=n?n:0;if(!n)for(let t=0;t<o.length;t++){const e=null!==(s=o[t])&&void 0!==s?s:0,i=Math.abs(e);i>a&&(a=i)}return a?r/a:r}({channelData:t,barHeight:e.barHeight,normalize:e.normalize,maxPeak:e.maxPeak});g(e)?this.renderBarWaveform(t,e,i,n):this.renderLineWaveform(t,e,i,n)}renderSingleCanvas(t,e,i,n,s,r,o){const a=this.getPixelRatio(),l=document.createElement("canvas");l.width=Math.round(i*a),l.height=Math.round(n*a),l.style.width=`${i}px`,l.style.height=`${n}px`,l.style.left=`${Math.round(s)}px`,r.appendChild(l);const h=l.getContext("2d");if(e.renderFunction?(h.fillStyle=this.convertColorValues(e.waveColor,h),e.renderFunction(t,h)):this.renderWaveform(t,e,h),l.width>0&&l.height>0){const t=l.cloneNode(),i=t.getContext("2d");i.drawImage(l,0,0),i.globalCompositeOperation="source-in",i.fillStyle=this.convertColorValues(e.progressColor,i),i.fillRect(0,0,l.width,l.height),o.appendChild(t)}}renderMultiCanvas(t,e,i,n,s,r){const o=this.getPixelRatio(),{clientWidth:a}=this.scrollContainer,l=i/o,h=function({clientWidth:t,totalWidth:e,options:i}){return f(Math.min(8e3,t,e),i)}({clientWidth:a,totalWidth:l,options:e});let c={};if(0===h)return;const u=i=>{if(i<0||i>=d)return;if(c[i])return;c[i]=!0;const o=i*h;let a=Math.min(l-o,h);if(a=f(a,e),a<=0)return;const u=function({channelData:t,offset:e,clampedWidth:i,totalWidth:n}){return t.map((t=>{const s=Math.floor(e/n*t.length),r=Math.floor((e+i)/n*t.length);return t.slice(s,r)}))}({channelData:t,offset:o,clampedWidth:a,totalWidth:l});this.renderSingleCanvas(u,e,a,n,o,s,r)},d=Math.ceil(l/h);if(!this.isScrollable){for(let t=0;t<d;t++)u(t);return}if(v({scrollLeft:this.scrollContainer.scrollLeft,totalWidth:l,numCanvases:d}).forEach((t=>u(t))),d>1){const t=this.on("scroll",(()=>{const{scrollLeft:t}=this.scrollContainer;Object.keys(c).length>10&&(s.innerHTML="",r.innerHTML="",c={}),v({scrollLeft:t,totalWidth:l,numCanvases:d}).forEach((t=>u(t)))}));this.unsubscribeOnScroll.push(t)}}renderChannel(t,e,i,n){var{overlay:s}=e,r=function(t,e){var i={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&e.indexOf(n)<0&&(i[n]=t[n]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var s=0;for(n=Object.getOwnPropertySymbols(t);s<n.length;s++)e.indexOf(n[s])<0&&Object.prototype.propertyIsEnumerable.call(t,n[s])&&(i[n[s]]=t[n[s]])}return i}(e,["overlay"]);const o=document.createElement("div"),a=this.getHeight(r.height,r.splitChannels);o.style.height=`${a}px`,s&&n>0&&(o.style.marginTop=`-${a}px`),this.canvasWrapper.style.minHeight=`${a}px`,this.canvasWrapper.appendChild(o);const l=o.cloneNode();this.progressWrapper.appendChild(l),this.renderMultiCanvas(t,r,i,a,o,l)}render(e){return t(this,void 0,void 0,(function*(){var t;this.timeouts.forEach((t=>t())),this.timeouts=[],this.unsubscribeOnScroll.forEach((t=>t())),this.unsubscribeOnScroll=[],this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="",null!=this.options.width&&(this.scrollContainer.style.width="number"==typeof this.options.width?`${this.options.width}px`:this.options.width);const i=this.getPixelRatio(),n=this.scrollContainer.clientWidth-this.containerInlinePadding,{scrollWidth:s,isScrollable:r,useParentWidth:o,width:a}=function({duration:t,minPxPerSec:e=0,parentWidth:i,fillParent:n,pixelRatio:s}){const r=Math.ceil(t*e),o=r>i,a=Boolean(n&&!o);return{scrollWidth:r,isScrollable:o,useParentWidth:a,width:(a?i:r)*s}}({duration:e.duration,minPxPerSec:this.options.minPxPerSec||0,parentWidth:n,fillParent:this.options.fillParent,pixelRatio:i});if(this.isScrollable=r,this.wrapper.style.width=o?"100%":`${s}px`,this.scrollContainer.style.overflowX=this.isScrollable?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.cursor.style.backgroundColor=`${this.options.cursorColor||this.options.progressColor}`,this.cursor.style.width=`${this.options.cursorWidth}px`,this.audioData=e,this.emit("render"),this.options.splitChannels)for(let i=0;i<e.numberOfChannels;i++){const n=Object.assign(Object.assign({},this.options),null===(t=this.options.splitChannels)||void 0===t?void 0:t[i]);this.renderChannel([e.getChannelData(i)],n,a,i)}else{const t=[e.getChannelData(0)];e.numberOfChannels>1&&t.push(e.getChannelData(1)),this.renderChannel(t,this.options,a,0)}Promise.resolve().then((()=>this.emit("rendered")))}))}reRender(){if(this.unsubscribeOnScroll.forEach((t=>t())),this.unsubscribeOnScroll=[],!this.audioData)return;const{scrollWidth:t}=this.scrollContainer,{right:e}=this.progressWrapper.getBoundingClientRect();if(this.render(this.audioData),!this.isScrollable&&this.scrollContainer.scrollLeft)this.scrollContainer.scrollLeft=0;else if(this.isScrollable&&t!==this.scrollContainer.scrollWidth){const{right:t}=this.progressWrapper.getBoundingClientRect(),i=function(t){const e=2*t;return(e<0?Math.floor(e):Math.ceil(e))/2}(t-e);this.scrollContainer.scrollLeft+=i}}zoom(t){this.options.minPxPerSec=t,this.reRender()}scrollIntoView(t,e=!1){var i;const{scrollLeft:n,scrollWidth:s,clientWidth:r}=this.scrollContainer,o=t*s,a=n,l=n+r,h=r/2;if(this.isDragging){const t=30;o+t>l?this.scrollContainer.scrollLeft+=t:o-t<a&&(this.scrollContainer.scrollLeft-=t)}else{(o<a||o>l)&&(this.scrollContainer.scrollLeft=o-(this.options.autoCenter?h:0));const t=o-n-h;if(e&&this.options.autoCenter&&t>0){const e=null===(i=this.audioData)||void 0===i?void 0:i.duration;if(void 0===e||e<=0)return void(this.scrollContainer.scrollLeft+=t);const n=s/e;this.scrollContainer.scrollLeft+=n<=600?Math.min(t,10):t}}}renderProgress(t,e){if(isNaN(t))return;const i=100*t;this.canvasWrapper.style.clipPath=`polygon(${i}% 0%, 100% 0%, 100% 100%, ${i}% 100%)`,this.progressWrapper.style.width=`${i}%`,this.cursor.style.left=`${i}%`,this.cursor.style.transform=this.options.cursorWidth?`translateX(-${t*this.options.cursorWidth}px)`:"",this.isScrollable&&this.options.autoScroll&&this.audioData&&this.audioData.duration>0&&this.scrollIntoView(t,e)}exportImage(e,i,n){return t(this,void 0,void 0,(function*(){const t=this.canvasWrapper.querySelectorAll("canvas");if(!t.length)throw new Error("No waveform data");if("dataURL"===n){const n=Array.from(t).map((t=>t.toDataURL(e,i)));return Promise.resolve(n)}return Promise.all(Array.from(t).map((t=>new Promise(((n,s)=>{t.toBlob((t=>{t?n(t):s(new Error("Could not export image"))}),e,i)})))))}))}}class S extends e{constructor(){super(...arguments),this.animationFrameId=null,this.isRunning=!1}start(){if(this.isRunning)return;this.isRunning=!0;const t=()=>{this.isRunning&&(this.emit("tick"),this.animationFrameId=requestAnimationFrame(t))};t()}stop(){this.isRunning=!1,null!==this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null)}destroy(){this.stop(),this.unAll()}}class E extends e{constructor(t){super(),this.bufferNode=null,this.playStartTime=0,this.playbackPosition=0,this._muted=!1,this._playbackRate=1,this._duration=void 0,this.buffer=null,this.currentSrc="",this.paused=!0,this.crossOrigin=null,this.seeking=!1,this.autoplay=!1,this.addEventListener=this.on,this.removeEventListener=this.un,this._destroyed=!1,function(){const t=globalThis.navigator;if(null==t?void 0:t.audioSession)try{t.audioSession.type="playback"}catch(t){console.warn("Setting navigator.audioSession.type failed:",t)}}(),this.audioContext=t||new AudioContext,this.gainNode=this.audioContext.createGain(),this.gainNode.connect(this.audioContext.destination)}load(){return t(this,void 0,void 0,(function*(){}))}remove(){this.destroy()}destroy(){if(!this._destroyed){if(this._destroyed=!0,this.currentSrc="",this.bufferNode){this.bufferNode.onended=null;try{this.bufferNode.stop()}catch(t){}this.bufferNode.disconnect(),this.bufferNode=null}this.gainNode.disconnect(),"function"==typeof this.audioContext.close&&Promise.resolve(this.audioContext.close.call(this.audioContext)).catch((()=>{})),this.buffer=null,this.unAll()}}get src(){return this.currentSrc}set src(t){if(this.currentSrc=t,this._duration=void 0,!t)return this.buffer=null,void this.emit("emptied");fetch(t).then((e=>{if(e.status>=400)throw new Error(`Failed to fetch ${t}: ${e.status} (${e.statusText})`);return e.arrayBuffer()})).then((e=>this.currentSrc!==t?null:this.audioContext.decodeAudioData(e))).then((e=>{this.currentSrc===t&&(this.buffer=e,this.emit("loadedmetadata"),this.emit("canplay"),this.autoplay&&this.play())})).catch((t=>{console.error("WebAudioPlayer load error:",t)}))}_play(){if(!this.paused)return;this.paused=!1,this.bufferNode&&(this.bufferNode.onended=null,this.bufferNode.disconnect()),this.bufferNode=this.audioContext.createBufferSource(),this.buffer&&(this.bufferNode.buffer=this.buffer),this.bufferNode.playbackRate.value=this._playbackRate,this.bufferNode.connect(this.gainNode);let t=this.playbackPosition;(t>=this.duration||t<0)&&(t=0,this.playbackPosition=0),this.bufferNode.start(this.audioContext.currentTime,t),this.playStartTime=this.audioContext.currentTime,this.bufferNode.onended=()=>{!this.paused&&this.duration-this.currentTime<.01&&(this.pause(),this.emit("ended"))}}_pause(){if(this.paused=!0,this.bufferNode){this.bufferNode.onended=null;try{this.bufferNode.stop()}catch(t){}}this.playbackPosition+=(this.audioContext.currentTime-this.playStartTime)*this._playbackRate}play(){return t(this,void 0,void 0,(function*(){this.paused&&(this._play(),this.emit("play"))}))}pause(){this.paused||(this._pause(),this.emit("pause"))}stopAt(t){const e=(t-this.currentTime)/this._playbackRate,i=this.bufferNode;null==i||i.stop(this.audioContext.currentTime+e),null==i||i.addEventListener("ended",(()=>{i===this.bufferNode&&(this.bufferNode=null,this.pause(),this.playbackPosition=Math.min(t,this.duration),this.emit("timeupdate"))}),{once:!0})}setSinkId(e){return t(this,void 0,void 0,(function*(){return this.audioContext.setSinkId(e)}))}get playbackRate(){return this._playbackRate}set playbackRate(t){const e=!this.paused;e&&this._pause(),this._playbackRate=t,e&&this._play(),this.bufferNode&&(this.bufferNode.playbackRate.value=t)}get currentTime(){return this.paused?this.playbackPosition:this.playbackPosition+(this.audioContext.currentTime-this.playStartTime)*this._playbackRate}set currentTime(t){const e=!this.paused;e&&this._pause(),this.playbackPosition=t,e&&this._play(),this.emit("seeking"),this.emit("timeupdate")}get duration(){var t,e;return null!==(t=this._duration)&&void 0!==t?t:(null===(e=this.buffer)||void 0===e?void 0:e.duration)||0}set duration(t){this._duration=t}get volume(){return this.gainNode.gain.value}set volume(t){this.gainNode.gain.value=t,this.emit("volumechange")}get muted(){return this._muted}set muted(t){this._muted!==t&&(this._muted=t,this._muted?this.gainNode.disconnect():this.gainNode.connect(this.audioContext.destination))}canPlayType(t){return/^(audio|video)\//.test(t)}getGainNode(){return this.gainNode}getChannelData(){const t=[];if(!this.buffer)return t;const e=this.buffer.numberOfChannels;for(let i=0;i<e;i++)t.push(this.buffer.getChannelData(i));return t}removeAttribute(t){switch(t){case"src":this.src="";break;case"playbackRate":this.playbackRate=0;break;case"currentTime":this.currentTime=0;break;case"duration":this.duration=0;break;case"volume":this.volume=0;break;case"muted":this.muted=!1}}}const P={waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,dragToSeek:!1,autoScroll:!0,autoCenter:!0,sampleRate:8e3};class w extends u{static create(t){return new w(t)}getState(){return this.wavesurferState}getRenderer(){return this.renderer}constructor(t){const e=t.media||("WebAudio"===t.backend?new E:void 0);super({media:e,mediaControls:t.mediaControls,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.stopAtPosition=null,this.subscriptions=[],this.mediaSubscriptions=[],this.abortController=null,this._isDestroyed=!1,this._loadVersion=0,this.reactiveCleanups=[],this.options=Object.assign({},P,t);const{state:i,actions:n}=function(t){var e,i,n,s,r,o;const a=null!==(e=null==t?void 0:t.currentTime)&&void 0!==e?e:l(0),c=null!==(i=null==t?void 0:t.duration)&&void 0!==i?i:l(0),u=null!==(n=null==t?void 0:t.isPlaying)&&void 0!==n?n:l(!1),d=null!==(s=null==t?void 0:t.isSeeking)&&void 0!==s?s:l(!1),p=null!==(r=null==t?void 0:t.volume)&&void 0!==r?r:l(1),m=null!==(o=null==t?void 0:t.playbackRate)&&void 0!==o?o:l(1),g=l(null),f=l(null),v=l(""),b=l(0),y=l(0),C=h((()=>!u.value),[u]),S=h((()=>null!==g.value),[g]),E=h((()=>S.value&&c.value>0),[S,c]),P=h((()=>a.value),[a]),w=h((()=>c.value>0?a.value/c.value:0),[a,c]);return{state:{currentTime:a,duration:c,isPlaying:u,isPaused:C,isSeeking:d,volume:p,playbackRate:m,audioBuffer:g,peaks:f,url:v,zoom:b,scrollPosition:y,canPlay:S,isReady:E,progress:P,progressPercent:w},actions:{setCurrentTime:t=>{const e=Math.max(0,Math.min(c.value||1/0,t));a.set(e)},setDuration:t=>{c.set(Math.max(0,t))},setPlaying:t=>{u.set(t)},setSeeking:t=>{d.set(t)},setVolume:t=>{const e=Math.max(0,Math.min(1,t));p.set(e)},setPlaybackRate:t=>{const e=Math.max(.1,Math.min(16,t));m.set(e)},setAudioBuffer:t=>{g.set(t),t&&c.set(t.duration)},setPeaks:t=>{f.set(t)},setUrl:t=>{v.set(t)},setZoom:t=>{b.set(Math.max(0,t))},setScrollPosition:t=>{y.set(Math.max(0,t))}}}}({isPlaying:this.isPlayingSignal,currentTime:this.currentTimeSignal,duration:this.durationSignal,volume:this.volumeSignal,playbackRate:this.playbackRateSignal,isSeeking:this.seekingSignal});this.wavesurferState=i,this.wavesurferActions=n,this.timer=new S;const s=e?void 0:this.getMediaElement();this.renderer=new C(this.options,s),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReactiveState(),this.initPlugins();const r=this.options.url||this.getSrc()||"";Promise.resolve().then((()=>{this.emit("init");const{peaks:t,duration:e}=this.options;(r||t&&e)&&this.load(r,t,e).catch((()=>{}))}))}updateProgress(t=this.getCurrentTime()){return this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),t}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{if(!this.isSeeking()){const t=this.updateProgress();if(this.emit("timeupdate",t),this.emit("audioprocess",t),null!=this.stopAtPosition&&this.isPlaying()&&t>=this.stopAtPosition){const t=this.stopAtPosition;this.pause(),this.setTime(t)}}})))}initReactiveState(){this.reactiveCleanups.push(function(t,e){const i=[];i.push(c((()=>{const i=t.isPlaying.value;e.emit(i?"play":"pause")}),[t.isPlaying])),i.push(c((()=>{const i=t.currentTime.value;e.emit("timeupdate",i),t.isPlaying.value&&e.emit("audioprocess",i)}),[t.currentTime,t.isPlaying])),i.push(c((()=>{t.isSeeking.value&&e.emit("seeking",t.currentTime.value)}),[t.isSeeking,t.currentTime]));let n=!1;i.push(c((()=>{t.isReady.value&&!n&&(n=!0,e.emit("ready",t.duration.value))}),[t.isReady,t.duration])),i.push(c((()=>{null===t.audioBuffer.value&&(n=!1)}),[t.audioBuffer]));let s=!1;return i.push(c((()=>{const i=t.isPlaying.value,n=t.currentTime.value,r=t.duration.value,o=r>0&&n>=r;s&&!i&&o&&e.emit("finish"),s=i&&o}),[t.isPlaying,t.currentTime,t.duration])),i.push(c((()=>{const i=t.zoom.value;i>0&&e.emit("zoom",i)}),[t.zoom])),()=>{i.forEach((t=>t()))}}(this.wavesurferState,{emit:this.emit.bind(this)}))}initPlayerEvents(){this.isPlaying()&&(this.emit("play"),this.timer.start()),this.mediaSubscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.updateProgress();this.emit("timeupdate",t)})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop(),this.stopAtPosition=null})),this.onMediaEvent("emptied",(()=>{this.timer.stop(),this.stopAtPosition=null})),this.onMediaEvent("ended",(()=>{this.emit("timeupdate",this.getDuration()),this.emit("finish"),this.stopAtPosition=null})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})),this.onMediaEvent("error",(()=>{var t;this.emit("error",null!==(t=this.getMediaElement().error)&&void 0!==t?t:new Error("Media error")),this.stopAtPosition=null})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",((t,e)=>{this.options.interact&&(this.seekTo(t),this.emit("interaction",t*this.getDuration()),this.emit("click",t,e))})),this.renderer.on("dblclick",((t,e)=>{this.emit("dblclick",t,e)})),this.renderer.on("scroll",((t,e,i,n)=>{const s=this.getDuration();this.emit("scroll",t*s,e*s,i,n)})),this.renderer.on("render",(()=>{this.emit("redraw")})),this.renderer.on("rendered",(()=>{this.emit("redrawcomplete")})),this.renderer.on("dragstart",(t=>{this.emit("dragstart",t)})),this.renderer.on("dragend",(t=>{this.emit("dragend",t)})),this.renderer.on("resize",(()=>{this.emit("resize")})));{let t;const e=this.renderer.on("drag",(e=>{var i;if(!this.options.interact)return;this.renderer.renderProgress(e),clearTimeout(t);let n=0;const s=this.options.dragToSeek;this.isPlaying()?n=0:!0===s?n=200:s&&"object"==typeof s&&(n=null!==(i=s.debounceTime)&&void 0!==i?i:200),t=setTimeout((()=>{this.seekTo(e)}),n),this.emit("interaction",e*this.getDuration()),this.emit("drag",e)}));this.subscriptions.push((()=>{clearTimeout(t),e()}))}}initPlugins(){var t;(null===(t=this.options.plugins)||void 0===t?void 0:t.length)&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}unsubscribePlayerEvents(){this.mediaSubscriptions.forEach((t=>t())),this.mediaSubscriptions=[]}setOptions(t){this.options=Object.assign({},this.options,t),t.duration&&!t.peaks&&(this.decodedData=i.createBuffer(this.exportPeaks(),t.duration)),t.peaks&&t.duration&&(this.decodedData=i.createBuffer(t.peaks,t.duration)),this.renderer.setOptions(this.options),t.audioRate&&this.setPlaybackRate(t.audioRate),null!=t.mediaControls&&(this.getMediaElement().controls=t.mediaControls)}registerPlugin(t){if(this.plugins.includes(t))return t;t._init(this),this.plugins.push(t);const e=t.once("destroy",(()=>{this.plugins=this.plugins.filter((e=>e!==t)),this.subscriptions=this.subscriptions.filter((t=>t!==e))}));return this.subscriptions.push(e),t}unregisterPlugin(t){this.plugins=this.plugins.filter((e=>e!==t)),t.destroy()}getWrapper(){return this.renderer.getWrapper()}getWidth(){return this.renderer.getWidth()}getScroll(){return this.renderer.getScroll()}setScroll(t){return this.renderer.setScroll(t)}setScrollTime(t){const e=t/this.getDuration();this.renderer.setScrollPercentage(e)}getActivePlugins(){return this.plugins}loadAudio(e,n,s,r){return t(this,void 0,void 0,(function*(){var t;const o=++this._loadVersion;if(this._isDestroyed=!1,this.emit("load",e),!this.options.media&&this.isPlaying()&&this.pause(),this.decodedData=null,this.stopAtPosition=null,null===(t=this.abortController)||void 0===t||t.abort(),this.abortController=null,!n&&!s){const t=this.options.fetchParams||{};window.AbortController&&!t.signal&&(this.abortController=new AbortController,t.signal=this.abortController.signal);const i=t=>this.emit("loading",t);if(n=yield a.fetchBlob(e,i,t),this._isDestroyed||o!==this._loadVersion)return;const s=this.options.blobMimeType;s&&(n=new Blob([n],{type:s}))}if(this._isDestroyed||o!==this._loadVersion)return;this.setSrc(e,n);const l=yield new Promise((t=>{const e=r||this.getDuration();e?t(e):this.mediaSubscriptions.push(this.onMediaEvent("loadedmetadata",(()=>t(this.getDuration())),{once:!0}))}));if(!this._isDestroyed&&o===this._loadVersion){if(!e&&!n){const t=this.getMediaElement();t instanceof E&&(t.duration=l)}if(s)this.decodedData=i.createBuffer(s,l||0);else if(n){const t=yield n.arrayBuffer();if(this._isDestroyed||o!==this._loadVersion)return;this.decodedData=yield i.decode(t,this.options.sampleRate)}this._isDestroyed||o!==this._loadVersion||(this.decodedData&&(this.emit("decode",this.getDuration()),this.renderer.render(this.decodedData)),this.emit("ready",this.getDuration()))}}))}load(e,i,n){return t(this,void 0,void 0,(function*(){try{return yield this.loadAudio(e,void 0,i,n)}catch(t){throw this.emit("error",t),t}}))}loadBlob(e,i,n){return t(this,void 0,void 0,(function*(){try{return yield this.loadAudio("",e,i,n)}catch(t){throw this.emit("error",t),t}}))}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",t)}getDecodedData(){return this.decodedData}exportPeaks({channels:t=2,maxLength:e=8e3,precision:i=1e4}={}){if(!this.decodedData)throw new Error("The audio has not been decoded yet");const n=Math.min(t,this.decodedData.numberOfChannels),s=[];for(let t=0;t<n;t++){const n=this.decodedData.getChannelData(t),r=[],o=n.length/e;for(let t=0;t<e;t++){const e=n.slice(Math.floor(t*o),Math.ceil((t+1)*o));let s=0;for(let t=0;t<e.length;t++){const i=e[t];Math.abs(i)>Math.abs(s)&&(s=i)}r.push(Math.round(s*i)/i)}s.push(r)}return s}getDuration(){let t=super.getDuration()||0;return 0!==t&&t!==1/0||!this.decodedData||(t=this.decodedData.duration),t}toggleInteraction(t){this.options.interact=t}setTime(t){this.stopAtPosition=null,super.setTime(t),this.updateProgress(t),this.emit("timeupdate",t)}seekTo(t){const e=this.getDuration()*t;this.setTime(e)}play(e,i){const n=Object.create(null,{play:{get:()=>super.play}});return t(this,void 0,void 0,(function*(){null!=e&&this.setTime(e);const t=yield n.play.call(this);return null!=i&&(this.media instanceof E?this.media.stopAt(i):this.stopAtPosition=i),t}))}playPause(){return t(this,void 0,void 0,(function*(){return this.isPlaying()?this.pause():this.play()}))}stop(){this.pause(),this.setTime(0)}skip(t){this.setTime(this.getCurrentTime()+t)}empty(){this.load("",[[0]],.001)}setMediaElement(t){this.unsubscribePlayerEvents(),super.setMediaElement(t),this.initPlayerEvents()}exportImage(){return t(this,arguments,void 0,(function*(t="image/png",e=1,i="dataURL"){return this.renderer.exportImage(t,e,i)}))}destroy(){var t;this._isDestroyed=!0,this.emit("destroy"),null===(t=this.abortController)||void 0===t||t.abort(),this.plugins.forEach((t=>t.destroy())),this.subscriptions.forEach((t=>t())),this.unsubscribePlayerEvents(),this.reactiveCleanups.forEach((t=>t())),this.reactiveCleanups=[],this.timer.destroy(),this.renderer.destroy(),super.destroy()}}w.BasePlugin=class extends e{constructor(t){super(),this.subscriptions=[],this.isDestroyed=!1,this.options=t}onInit(){}_init(t){this.isDestroyed&&(this.subscriptions=[],this.isDestroyed=!1),this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.subscriptions=[],this.isDestroyed=!0,this.wavesurfer=void 0}},w.dom=o;
4069
4362
 
4363
+ // EXTERNAL MODULE: ./src/lib/util.js + 1 modules
4364
+ var util = __webpack_require__(4410);
4070
4365
  ;// ./src/lib/viewers/media/waveform/colors.ts
4071
4366
  /**
4072
4367
  * Figma Audio Player waveform tokens as opaque fills.
@@ -4085,14 +4380,6 @@ const WAVEFORM_COLOR_HOVER_PLAYED = whiteOnBlack(0.9);
4085
4380
  const WAVEFORM_COLOR_HOVER_AREA = whiteOnBlack(0.6);
4086
4381
  const WAVEFORM_COLOR_HOVER_UNPLAYED = whiteOnBlack(0.3);
4087
4382
  const WAVEFORM_COLOR_HOVER_BUFFER = whiteOnBlack(0.16);
4088
-
4089
- /** One canvas linear-gradient stop. `offset` is 0–1 along the bar. */
4090
-
4091
- /**
4092
- * Wavesurfer's two paints: left of the playhead (`progressColor`) and right of it (`waveColor`).
4093
- * A string is a solid fill; a stop list is a left-to-right step gradient.
4094
- */
4095
-
4096
4383
  /** Pin a value to the closed interval [0, 1]. */
4097
4384
  function clampTo0And1(value) {
4098
4385
  if (!Number.isFinite(value) || value < 0) {
@@ -4227,29 +4514,144 @@ function toCanvasFill(color, widthPx) {
4227
4514
  });
4228
4515
  return gradient;
4229
4516
  }
4517
+
4518
+ /** Untinted bar pixels for each WaveSurfer tile, so hover can re-tint without stacking. */
4519
+ const tileBarSnapshots = new WeakMap();
4520
+
4521
+ /** Restore the last bar snapshot, or take a new one after WaveSurfer redraws. */
4522
+ function restoreOrSnapshotTile(canvas, context, replaceSnapshot) {
4523
+ if (!(canvas.width > 0) || !(canvas.height > 0) || !context.getImageData) {
4524
+ return;
4525
+ }
4526
+ const stored = tileBarSnapshots.get(canvas);
4527
+ if (!replaceSnapshot && stored && context.putImageData) {
4528
+ context.putImageData(stored, 0, 0);
4529
+ return;
4530
+ }
4531
+ try {
4532
+ tileBarSnapshots.set(canvas, context.getImageData(0, 0, canvas.width, canvas.height));
4533
+ } catch {
4534
+ // Tainted or zero-size canvases cannot snapshot; tint in place.
4535
+ }
4536
+ }
4537
+
4538
+ /** CSS width of a tile canvas (style.width, then clientWidth, then bitmap width). */
4539
+ function tileWidthCss(canvas) {
4540
+ const styled = parseFloat(canvas.style.width);
4541
+ if (styled > 0) {
4542
+ return styled;
4543
+ }
4544
+ if (canvas.clientWidth > 0) {
4545
+ return canvas.clientWidth;
4546
+ }
4547
+ return canvas.width;
4548
+ }
4549
+
4550
+ /** Solid color, or a gradient aligned to the full waveform and shifted to this tile. */
4551
+ function fillForTile(context, color, totalWidthCss, offsetCss, canvas) {
4552
+ if (typeof color === 'string') {
4553
+ return color;
4554
+ }
4555
+ const cssWidth = tileWidthCss(canvas);
4556
+ const scale = cssWidth > 0 ? canvas.width / cssWidth : 1;
4557
+ if (!(totalWidthCss > 0) || !(scale > 0)) {
4558
+ return color[0] ? color[0].color : WAVEFORM_COLOR_UNPLAYED;
4559
+ }
4560
+ const x0 = -offsetCss * scale || 0;
4561
+ const x1 = (totalWidthCss - offsetCss) * scale;
4562
+ const gradient = context.createLinearGradient(x0, 0, x1, 0);
4563
+ let lastOffset = -1;
4564
+ color.forEach(stop => {
4565
+ let offset = clampTo0And1(stop.offset);
4566
+ if (offset <= lastOffset) {
4567
+ offset = Math.min(1, lastOffset + 1e-6);
4568
+ }
4569
+ lastOffset = offset;
4570
+ gradient.addColorStop(offset, stop.color);
4571
+ });
4572
+ return gradient;
4573
+ }
4574
+
4575
+ /**
4576
+ * Wavesurfer splits a zoomed waveform into viewport-sized tiles and paints each
4577
+ * from x=0. Re-tint with a gradient in global waveform space so hover/buffer
4578
+ * colors do not repeat on the last tile.
4579
+ */
4580
+ function tintWaveformTiles({
4581
+ fills,
4582
+ host,
4583
+ replaceSnapshot = false,
4584
+ totalWidthCss
4585
+ }) {
4586
+ if (!(totalWidthCss > 0) || !host.querySelectorAll) {
4587
+ return;
4588
+ }
4589
+ const groups = [{
4590
+ canvases: host.querySelectorAll('.canvases canvas'),
4591
+ color: fills.waveColor
4592
+ }, {
4593
+ canvases: host.querySelectorAll('.progress canvas'),
4594
+ color: fills.progressColor
4595
+ }];
4596
+ groups.forEach(({
4597
+ canvases,
4598
+ color
4599
+ }) => {
4600
+ canvases.forEach(canvas => {
4601
+ const context = canvas.getContext('2d');
4602
+ if (!context) {
4603
+ return;
4604
+ }
4605
+ restoreOrSnapshotTile(canvas, context, replaceSnapshot);
4606
+ const offsetCss = parseFloat(canvas.style.left) || 0;
4607
+ context.save();
4608
+ context.globalCompositeOperation = 'source-in';
4609
+ context.fillStyle = fillForTile(context, color, totalWidthCss, offsetCss, canvas);
4610
+ context.fillRect(0, 0, canvas.width, canvas.height);
4611
+ context.restore();
4612
+ });
4613
+ });
4614
+ }
4230
4615
  ;// ./src/lib/viewers/media/waveform/WaveformView.scss
4231
4616
  // extracted by mini-css-extract-plugin
4232
4617
 
4233
4618
  ;// ./src/lib/viewers/media/waveform/WaveformView.tsx
4619
+ function WaveformView_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4620
+ function WaveformView_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? WaveformView_ownKeys(Object(t), !0).forEach(function (r) { WaveformView_defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : WaveformView_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4621
+ function WaveformView_defineProperty(e, r, t) { return (r = WaveformView_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4622
+ function WaveformView_toPropertyKey(t) { var i = WaveformView_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4623
+ function WaveformView_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4234
4624
 
4235
4625
 
4236
4626
 
4237
4627
 
4238
4628
 
4239
- const WAVEFORM_BAR_GAP = 2;
4240
- const WAVEFORM_BAR_WIDTH = 2;
4241
- const WAVEFORM_BAR_RADIUS = WAVEFORM_BAR_WIDTH / 2;
4242
- /** Total bar height so the top and bottom radii meet as a circle on the mirror. */
4243
- const WAVEFORM_BAR_MIN_HEIGHT = WAVEFORM_BAR_WIDTH;
4244
- const WAVEFORM_HEIGHT = 140;
4245
- function leftPercent(timeSec, durationSec) {
4246
- const progress = durationSec > 0 ? Math.min(1, Math.max(0, timeSec / durationSec)) : 0;
4247
- return `${progress * 100}%`;
4248
- }
4249
- function fillWidth(widthCssPx) {
4629
+
4630
+
4631
+
4632
+
4633
+ /** Pointer X in the view and the media time under it; zoom keeps this point fixed. */
4634
+
4635
+ /** CSS width × devicePixelRatio, for canvas gradient fills. */
4636
+ function devicePixelWidth(widthCssPx) {
4250
4637
  const pixelRatio = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
4251
4638
  return widthCssPx * pixelRatio;
4252
4639
  }
4640
+ function prefersReducedMotion() {
4641
+ return typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
4642
+ }
4643
+ function getScrollLeft(wavesurfer, fallback = 0) {
4644
+ return wavesurfer && wavesurfer.getScroll ? wavesurfer.getScroll() : fallback;
4645
+ }
4646
+
4647
+ /** CSS left % of the playhead from the left of the visible window. */
4648
+ function timeLeftPercent(timeSec, durationSec, viewport) {
4649
+ if (viewport.widthPx > 0 && viewport.pixelsPerSecond > 0) {
4650
+ return `${positionPxFromTime(timeSec, viewport) / viewport.widthPx * 100}%`;
4651
+ }
4652
+ const progress = durationSec > 0 ? Math.min(1, Math.max(0, timeSec / durationSec)) : 0;
4653
+ return `${progress * 100}%`;
4654
+ }
4253
4655
  function applyPeaks(wavesurfer, peaks, durationSec) {
4254
4656
  if (!wavesurfer.load) {
4255
4657
  return;
@@ -4257,6 +4659,44 @@ function applyPeaks(wavesurfer, peaks, durationSec) {
4257
4659
  wavesurfer.load('', toChannels(peaks), durationSec);
4258
4660
  }
4259
4661
 
4662
+ /** Tint WaveSurfer's zoomed tiles with played/unplayed/hover/buffer colors. */
4663
+ function tintZoomedWaveform(wavesurfer, fills, replaceSnapshot = false) {
4664
+ const wrapper = wavesurfer.getWrapper ? wavesurfer.getWrapper() : null;
4665
+ if (!wrapper || !(wrapper.clientWidth > 0)) {
4666
+ return;
4667
+ }
4668
+ tintWaveformTiles({
4669
+ fills,
4670
+ host: wrapper,
4671
+ replaceSnapshot,
4672
+ totalWidthCss: wrapper.clientWidth
4673
+ });
4674
+ }
4675
+ function touchDistance(touches) {
4676
+ if (touches.length < 2) {
4677
+ return 0;
4678
+ }
4679
+ const dx = touches[0].clientX - touches[1].clientX;
4680
+ const dy = touches[0].clientY - touches[1].clientY;
4681
+ return Math.hypot(dx, dy);
4682
+ }
4683
+
4684
+ /** Time under the pointer, plus its X in the view, so zoom can keep that point fixed. */
4685
+ function zoomOriginAtPointer(pointerX, wavesurfer, durationSec, fallbackWidth) {
4686
+ if (!(fallbackWidth > 0) || !(durationSec > 0) || !Number.isFinite(pointerX)) {
4687
+ return null;
4688
+ }
4689
+ const wrapper = wavesurfer && wavesurfer.getWrapper ? wavesurfer.getWrapper() : null;
4690
+ const fullWidth = wrapper && wrapper.clientWidth ? wrapper.clientWidth : fallbackWidth;
4691
+ if (!(fullWidth > 0)) {
4692
+ return null;
4693
+ }
4694
+ return {
4695
+ pointerX,
4696
+ timeSec: (getScrollLeft(wavesurfer) + pointerX) / fullWidth * durationSec
4697
+ };
4698
+ }
4699
+
4260
4700
  /**
4261
4701
  * Renders V1 peaks with wavesurfer. Does not fetch audio or attach a media element.
4262
4702
  */
@@ -4264,42 +4704,114 @@ function WaveformView({
4264
4704
  bufferedRange,
4265
4705
  currentTime = 0,
4266
4706
  durationSec,
4267
- height = WAVEFORM_HEIGHT,
4707
+ height = constants/* WAVEFORM_HEIGHT */.oN,
4268
4708
  interactive = true,
4269
4709
  mediaEl,
4270
4710
  onSeek,
4271
- peaks
4711
+ onViewportChange,
4712
+ onZoomChange,
4713
+ peaks,
4714
+ zoomLevel: zoomLevelProp
4272
4715
  }) {
4273
4716
  const containerRef = (0,external_react_.useRef)(null);
4717
+ const trackRef = (0,external_react_.useRef)(null);
4274
4718
  const playheadRef = (0,external_react_.useRef)(null);
4275
- const playheadRafRef = (0,external_react_.useRef)(0);
4719
+ const playheadAnimationRef = (0,external_react_.useRef)(0);
4276
4720
  const wavesurferRef = (0,external_react_.useRef)(null);
4277
4721
  const onSeekRef = (0,external_react_.useRef)(onSeek);
4278
4722
  const interactiveRef = (0,external_react_.useRef)(interactive);
4723
+ const hasZoomHandlersRef = (0,external_react_.useRef)(false);
4279
4724
  const currentTimeRef = (0,external_react_.useRef)(currentTime);
4280
4725
  const peaksRef = (0,external_react_.useRef)(peaks);
4281
4726
  const displayedPeaksRef = (0,external_react_.useRef)(null);
4282
- const peakTransitionRafRef = (0,external_react_.useRef)(0);
4727
+ const peakTransitionAnimationRef = (0,external_react_.useRef)(0);
4283
4728
  const durationSecRef = (0,external_react_.useRef)(durationSec);
4729
+ const zoomOriginRef = (0,external_react_.useRef)(null);
4730
+ const pinchStartRef = (0,external_react_.useRef)(null);
4731
+ const bufferProgressRef = (0,external_react_.useRef)(0);
4732
+ const hoverProgressRef = (0,external_react_.useRef)(null);
4733
+ const programmaticScrollRef = (0,external_react_.useRef)(false);
4734
+ const onViewportChangeRef = (0,external_react_.useRef)(onViewportChange);
4284
4735
  onSeekRef.current = onSeek;
4285
4736
  interactiveRef.current = interactive;
4286
4737
  currentTimeRef.current = currentTime;
4287
4738
  peaksRef.current = peaks;
4288
4739
  durationSecRef.current = durationSec;
4740
+ onViewportChangeRef.current = onViewportChange;
4741
+ const [internalZoom, setInternalZoom] = (0,external_react_.useState)(constants/* WAVEFORM_ZOOM_MIN */.LW);
4289
4742
  const [hoverProgress, setHoverProgress] = (0,external_react_.useState)(null);
4290
4743
  const [canvasWidthPx, setCanvasWidthPx] = (0,external_react_.useState)(0);
4744
+ const [scrollLeft, setScrollLeft] = (0,external_react_.useState)(0);
4745
+ const isControlled = typeof zoomLevelProp === 'number';
4746
+ const maxZoom = getWaveformZoomMax({
4747
+ durationSec,
4748
+ peakCount: peaks.length,
4749
+ viewWidthPx: canvasWidthPx
4750
+ });
4751
+ hasZoomHandlersRef.current = maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW && (typeof onZoomChange === 'function' || typeof zoomLevelProp !== 'number');
4752
+ const zoomLevel = clampWaveformZoom(isControlled ? zoomLevelProp : internalZoom, maxZoom);
4753
+ const zoomRef = (0,external_react_.useRef)(zoomLevel);
4754
+ zoomRef.current = zoomLevel;
4755
+ const prevZoomRef = (0,external_react_.useRef)(null);
4756
+ const isZoomed = zoomLevel > constants/* WAVEFORM_ZOOM_MIN */.LW;
4291
4757
  const bufferProgress = getBufferedProgress(bufferedRange, durationSec);
4758
+ bufferProgressRef.current = bufferProgress;
4759
+ hoverProgressRef.current = hoverProgress;
4760
+ const viewport = (0,external_react_.useMemo)(() => createWaveformViewport({
4761
+ durationSec,
4762
+ heightPx: height,
4763
+ maxZoom,
4764
+ scrollLeftPx: scrollLeft,
4765
+ widthPx: canvasWidthPx,
4766
+ zoomLevel
4767
+ }), [canvasWidthPx, durationSec, height, maxZoom, scrollLeft, zoomLevel]);
4768
+ const viewportRef = (0,external_react_.useRef)(viewport);
4769
+ viewportRef.current = viewport;
4770
+ const setZoomLevel = (0,external_react_.useCallback)(nextZoom => {
4771
+ const zoom = clampWaveformZoom(nextZoom, maxZoom);
4772
+ if (!isControlled) {
4773
+ setInternalZoom(zoom);
4774
+ }
4775
+ onZoomChange?.(zoom);
4776
+ }, [isControlled, maxZoom, onZoomChange]);
4777
+ const syncViewport = (0,external_react_.useCallback)(() => {
4778
+ const wavesurfer = wavesurferRef.current;
4779
+ if (!wavesurfer) {
4780
+ return;
4781
+ }
4782
+ const scrollLeftPx = getScrollLeft(wavesurfer);
4783
+ viewportRef.current = getViewportAtScroll(viewportRef.current, scrollLeftPx);
4784
+ onViewportChangeRef.current?.(viewportRef.current);
4785
+ setScrollLeft(scrollLeftPx);
4786
+ }, []);
4292
4787
  const updatePlayheadPosition = (0,external_react_.useCallback)(timeSec => {
4293
4788
  const playhead = playheadRef.current;
4294
4789
  if (!playhead) {
4295
4790
  return;
4296
4791
  }
4297
- playhead.style.left = leftPercent(timeSec, durationSecRef.current);
4792
+ playhead.style.left = timeLeftPercent(timeSec, durationSecRef.current, viewportRef.current);
4298
4793
  const wavesurfer = wavesurferRef.current;
4299
4794
  if (wavesurfer && wavesurfer.setTime) {
4300
4795
  wavesurfer.setTime(timeSec);
4301
4796
  }
4302
4797
  }, []);
4798
+ const applyScrollLeft = (0,external_react_.useCallback)((scrollLeftPx, shouldCommitState) => {
4799
+ const wavesurfer = wavesurferRef.current;
4800
+ if (!wavesurfer || !wavesurfer.setScroll) {
4801
+ return;
4802
+ }
4803
+ programmaticScrollRef.current = true;
4804
+ wavesurfer.setScroll(scrollLeftPx);
4805
+ viewportRef.current = getViewportAtScroll(viewportRef.current, getScrollLeft(wavesurfer, scrollLeftPx));
4806
+ window.requestAnimationFrame(() => {
4807
+ // Keep the flag through delayed `scroll` after a zoom setScroll.
4808
+ programmaticScrollRef.current = false;
4809
+ });
4810
+ if (shouldCommitState) {
4811
+ onViewportChangeRef.current?.(viewportRef.current);
4812
+ setScrollLeft(getScrollLeft(wavesurfer, scrollLeftPx));
4813
+ }
4814
+ }, []);
4303
4815
  (0,external_react_.useEffect)(() => {
4304
4816
  const container = containerRef.current;
4305
4817
  if (!container) {
@@ -4307,10 +4819,10 @@ function WaveformView({
4307
4819
  }
4308
4820
  const wavesurfer = w.create({
4309
4821
  autoScroll: false,
4310
- barGap: WAVEFORM_BAR_GAP,
4311
- barMinHeight: WAVEFORM_BAR_MIN_HEIGHT,
4312
- barRadius: WAVEFORM_BAR_RADIUS,
4313
- barWidth: WAVEFORM_BAR_WIDTH,
4822
+ barGap: constants/* WAVEFORM_BAR_GAP */.Lu,
4823
+ barMinHeight: constants/* WAVEFORM_BAR_MIN_HEIGHT */.XS,
4824
+ barRadius: constants/* WAVEFORM_BAR_RADIUS */.DY,
4825
+ barWidth: constants/* WAVEFORM_BAR_WIDTH */.zo,
4314
4826
  container,
4315
4827
  cursorWidth: 0,
4316
4828
  duration: durationSecRef.current,
@@ -4329,16 +4841,39 @@ function WaveformView({
4329
4841
  }
4330
4842
  onSeekRef.current?.(relativeX * durationSecRef.current);
4331
4843
  });
4844
+ const unsubscribeScroll = wavesurfer.on('scroll', () => {
4845
+ if (programmaticScrollRef.current) {
4846
+ viewportRef.current = getViewportAtScroll(viewportRef.current, getScrollLeft(wavesurfer));
4847
+ return;
4848
+ }
4849
+ syncViewport();
4850
+ });
4851
+ const unsubscribeZoom = wavesurfer.on('zoom', () => {
4852
+ syncViewport();
4853
+ });
4854
+ const unsubscribeRedraw = wavesurfer.on('redrawcomplete', () => {
4855
+ if (!(zoomRef.current > constants/* WAVEFORM_ZOOM_MIN */.LW)) {
4856
+ return;
4857
+ }
4858
+ tintZoomedWaveform(wavesurfer, getWaveformFills({
4859
+ bufferProgress: bufferProgressRef.current,
4860
+ hoverProgress: hoverProgressRef.current
4861
+ }), true);
4862
+ });
4332
4863
  wavesurferRef.current = wavesurfer;
4333
4864
  displayedPeaksRef.current = peaksRef.current;
4865
+ syncViewport();
4334
4866
  return () => {
4335
- window.cancelAnimationFrame(peakTransitionRafRef.current);
4867
+ window.cancelAnimationFrame(peakTransitionAnimationRef.current);
4336
4868
  unsubscribeClick();
4869
+ unsubscribeScroll();
4870
+ unsubscribeZoom();
4871
+ unsubscribeRedraw();
4337
4872
  wavesurfer.destroy();
4338
4873
  wavesurferRef.current = null;
4339
4874
  displayedPeaksRef.current = null;
4340
4875
  };
4341
- }, [height]);
4876
+ }, [height, syncViewport]);
4342
4877
  (0,external_react_.useLayoutEffect)(() => {
4343
4878
  const el = containerRef.current;
4344
4879
  if (!el) {
@@ -4349,10 +4884,24 @@ function WaveformView({
4349
4884
  entries.forEach(entry => {
4350
4885
  setCanvasWidthPx(entry.contentRect.width);
4351
4886
  });
4887
+ syncViewport();
4352
4888
  });
4353
4889
  observer.observe(el);
4354
4890
  return () => observer.disconnect();
4355
- }, []);
4891
+ }, [syncViewport]);
4892
+ (0,external_react_.useEffect)(() => {
4893
+ const rawZoom = isControlled ? zoomLevelProp : internalZoom;
4894
+ const clamped = clampWaveformZoom(typeof rawZoom === 'number' ? rawZoom : constants/* WAVEFORM_ZOOM_MIN */.LW, maxZoom);
4895
+ if (clamped === rawZoom) {
4896
+ return;
4897
+ }
4898
+ if (!isControlled) {
4899
+ setInternalZoom(clamped);
4900
+ }
4901
+ }, [internalZoom, isControlled, maxZoom, zoomLevelProp]);
4902
+ (0,external_react_.useEffect)(() => {
4903
+ onViewportChange?.(viewport);
4904
+ }, [onViewportChange, viewport]);
4356
4905
  (0,external_react_.useEffect)(() => {
4357
4906
  const wavesurfer = wavesurferRef.current;
4358
4907
  if (!wavesurfer || !wavesurfer.setOptions) {
@@ -4362,13 +4911,48 @@ function WaveformView({
4362
4911
  interact: interactive
4363
4912
  });
4364
4913
  }, [interactive]);
4914
+ (0,external_react_.useEffect)(() => {
4915
+ const wavesurfer = wavesurferRef.current;
4916
+ const container = containerRef.current;
4917
+ if (!wavesurfer || !wavesurfer.setOptions || !container) {
4918
+ return;
4919
+ }
4920
+ const viewWidthPx = wavesurfer.getWidth ? wavesurfer.getWidth() : container.clientWidth;
4921
+ const minPxPerSec = getZoomedPixelsPerSecond({
4922
+ durationSec,
4923
+ maxZoom,
4924
+ viewWidthPx,
4925
+ zoomLevel
4926
+ });
4927
+ wavesurfer.setOptions(WaveformView_objectSpread({
4928
+ autoScroll: false,
4929
+ minPxPerSec
4930
+ }, zoomLevel > constants/* WAVEFORM_ZOOM_MIN */.LW ? {
4931
+ progressColor: WAVEFORM_COLOR_PLAYED,
4932
+ waveColor: WAVEFORM_COLOR_UNPLAYED
4933
+ } : {}));
4934
+ const origin = zoomOriginRef.current;
4935
+ zoomOriginRef.current = null;
4936
+ const didZoomChange = prevZoomRef.current !== zoomLevel;
4937
+ prevZoomRef.current = zoomLevel;
4938
+ if (minPxPerSec > 0) {
4939
+ if (origin) {
4940
+ applyScrollLeft(origin.timeSec * minPxPerSec - origin.pointerX, true);
4941
+ } else if (didZoomChange) {
4942
+ const maxScroll = Math.max(0, durationSec * minPxPerSec - viewWidthPx);
4943
+ applyScrollLeft(Math.min(maxScroll, Math.max(0, currentTimeRef.current * minPxPerSec - viewWidthPx / 2)), true);
4944
+ }
4945
+ }
4946
+ wavesurfer.setTime(currentTimeRef.current);
4947
+ syncViewport();
4948
+ }, [applyScrollLeft, durationSec, maxZoom, syncViewport, zoomLevel]);
4365
4949
  (0,external_react_.useLayoutEffect)(() => {
4366
4950
  if (mediaEl && !mediaEl.paused) {
4367
4951
  updatePlayheadPosition(mediaEl.currentTime);
4368
4952
  return;
4369
4953
  }
4370
4954
  updatePlayheadPosition(currentTime);
4371
- }, [currentTime, durationSec, mediaEl, updatePlayheadPosition]);
4955
+ }, [currentTime, durationSec, mediaEl, updatePlayheadPosition, viewport]);
4372
4956
  (0,external_react_.useEffect)(() => {
4373
4957
  const media = mediaEl;
4374
4958
  if (!media) {
@@ -4378,14 +4962,14 @@ function WaveformView({
4378
4962
  if (!media.paused) {
4379
4963
  updatePlayheadPosition(media.currentTime);
4380
4964
  }
4381
- playheadRafRef.current = window.requestAnimationFrame(tick);
4965
+ playheadAnimationRef.current = window.requestAnimationFrame(tick);
4382
4966
  };
4383
4967
  const startLoop = () => {
4384
- window.cancelAnimationFrame(playheadRafRef.current);
4385
- playheadRafRef.current = window.requestAnimationFrame(tick);
4968
+ window.cancelAnimationFrame(playheadAnimationRef.current);
4969
+ playheadAnimationRef.current = window.requestAnimationFrame(tick);
4386
4970
  };
4387
4971
  const stopLoop = () => {
4388
- window.cancelAnimationFrame(playheadRafRef.current);
4972
+ window.cancelAnimationFrame(playheadAnimationRef.current);
4389
4973
  updatePlayheadPosition(media.currentTime);
4390
4974
  };
4391
4975
  const handleSeeked = () => {
@@ -4395,11 +4979,13 @@ function WaveformView({
4395
4979
  startLoop();
4396
4980
  }
4397
4981
  media.addEventListener('play', startLoop);
4982
+ media.addEventListener('playing', startLoop);
4398
4983
  media.addEventListener('pause', stopLoop);
4399
4984
  media.addEventListener('seeked', handleSeeked);
4400
4985
  return () => {
4401
- window.cancelAnimationFrame(playheadRafRef.current);
4986
+ window.cancelAnimationFrame(playheadAnimationRef.current);
4402
4987
  media.removeEventListener('play', startLoop);
4988
+ media.removeEventListener('playing', startLoop);
4403
4989
  media.removeEventListener('pause', stopLoop);
4404
4990
  media.removeEventListener('seeked', handleSeeked);
4405
4991
  };
@@ -4409,28 +4995,27 @@ function WaveformView({
4409
4995
  if (!wavesurfer || !wavesurfer.setOptions || !(durationSec > 0)) {
4410
4996
  return undefined;
4411
4997
  }
4998
+ window.cancelAnimationFrame(peakTransitionAnimationRef.current);
4412
4999
  const fromPeaks = displayedPeaksRef.current;
4413
- const reduceMotion = typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
4414
- window.cancelAnimationFrame(peakTransitionRafRef.current);
4415
- if (!fromPeaks || fromPeaks === peaks || reduceMotion) {
5000
+ if (!fromPeaks || fromPeaks === peaks || prefersReducedMotion()) {
4416
5001
  displayedPeaksRef.current = peaks;
4417
5002
  applyPeaks(wavesurfer, peaks, durationSec);
4418
5003
  return undefined;
4419
5004
  }
4420
- const start = typeof performance !== 'undefined' ? performance.now() : Date.now();
5005
+ const start = (0,util/* getCurrentTimeMs */.RU)();
4421
5006
  const tick = now => {
4422
5007
  const elapsedMs = now - start;
4423
5008
  const framePeaks = morphPeaks(fromPeaks, peaks, elapsedMs);
4424
5009
  displayedPeaksRef.current = framePeaks;
4425
5010
  applyPeaks(wavesurfer, framePeaks, durationSec);
4426
5011
  if (elapsedMs < WAVEFORM_PEAK_TRANSITION_MS) {
4427
- peakTransitionRafRef.current = window.requestAnimationFrame(tick);
5012
+ peakTransitionAnimationRef.current = window.requestAnimationFrame(tick);
4428
5013
  } else {
4429
5014
  displayedPeaksRef.current = peaks;
4430
5015
  }
4431
5016
  };
4432
- peakTransitionRafRef.current = window.requestAnimationFrame(tick);
4433
- return () => window.cancelAnimationFrame(peakTransitionRafRef.current);
5017
+ peakTransitionAnimationRef.current = window.requestAnimationFrame(tick);
5018
+ return () => window.cancelAnimationFrame(peakTransitionAnimationRef.current);
4434
5019
  }, [durationSec, peaks]);
4435
5020
  (0,external_react_.useEffect)(() => {
4436
5021
  const wavesurfer = wavesurferRef.current;
@@ -4441,12 +5026,91 @@ function WaveformView({
4441
5026
  bufferProgress,
4442
5027
  hoverProgress
4443
5028
  });
5029
+ if (isZoomed) {
5030
+ tintZoomedWaveform(wavesurfer, fills);
5031
+ return;
5032
+ }
4444
5033
  wavesurfer.setOptions({
4445
- progressColor: toCanvasFill(fills.progressColor, fillWidth(canvasWidthPx)),
4446
- waveColor: toCanvasFill(fills.waveColor, fillWidth(canvasWidthPx))
5034
+ progressColor: toCanvasFill(fills.progressColor, devicePixelWidth(canvasWidthPx)),
5035
+ waveColor: toCanvasFill(fills.waveColor, devicePixelWidth(canvasWidthPx))
4447
5036
  });
4448
5037
  wavesurfer.setTime(currentTimeRef.current);
4449
- }, [bufferProgress, canvasWidthPx, hoverProgress]);
5038
+ }, [bufferProgress, canvasWidthPx, hoverProgress, isZoomed]);
5039
+ (0,external_react_.useEffect)(() => {
5040
+ const track = trackRef.current;
5041
+ if (!track) {
5042
+ return undefined;
5043
+ }
5044
+
5045
+ /** Remember the time under this pointer so pinch/wheel zoom stays anchored. */
5046
+ const captureZoomOrigin = clientX => {
5047
+ const rect = track.getBoundingClientRect();
5048
+ zoomOriginRef.current = zoomOriginAtPointer(clientX - rect.left, wavesurferRef.current, durationSec, rect.width);
5049
+ };
5050
+
5051
+ /** Zoom origin at the midpoint of a two-finger pinch. */
5052
+ const zoomOriginFromPinch = touches => {
5053
+ if (touches.length < 2) {
5054
+ return null;
5055
+ }
5056
+ const rect = track.getBoundingClientRect();
5057
+ const pointerX = (touches[0].clientX + touches[1].clientX) / 2 - rect.left;
5058
+ return zoomOriginAtPointer(pointerX, wavesurferRef.current, durationSec, rect.width);
5059
+ };
5060
+
5061
+ /** Ctrl/meta + wheel zooms around the pointer. */
5062
+ const onZoomWheel = event => {
5063
+ if (!interactiveRef.current || !hasZoomHandlersRef.current || !event.ctrlKey && !event.metaKey) {
5064
+ return;
5065
+ }
5066
+ event.preventDefault();
5067
+ captureZoomOrigin(event.clientX);
5068
+ setZoomLevel(zoomRef.current * Math.exp(-event.deltaY * 0.01));
5069
+ };
5070
+ const onTouchStart = event => {
5071
+ if (!interactiveRef.current || !hasZoomHandlersRef.current || event.touches.length !== 2) {
5072
+ pinchStartRef.current = null;
5073
+ return;
5074
+ }
5075
+ zoomOriginRef.current = zoomOriginFromPinch(event.touches);
5076
+ pinchStartRef.current = {
5077
+ distance: touchDistance(event.touches),
5078
+ zoom: zoomRef.current
5079
+ };
5080
+ };
5081
+ const onTouchMove = event => {
5082
+ const pinch = pinchStartRef.current;
5083
+ if (!interactiveRef.current || !hasZoomHandlersRef.current || !pinch || event.touches.length !== 2 || !(pinch.distance > 0)) {
5084
+ return;
5085
+ }
5086
+ event.preventDefault();
5087
+ zoomOriginRef.current = zoomOriginFromPinch(event.touches);
5088
+ setZoomLevel(pinch.zoom * (touchDistance(event.touches) / pinch.distance));
5089
+ };
5090
+ const onTouchEnd = event => {
5091
+ if (event.touches.length < 2) {
5092
+ pinchStartRef.current = null;
5093
+ }
5094
+ };
5095
+ track.addEventListener('wheel', onZoomWheel, {
5096
+ passive: false
5097
+ });
5098
+ track.addEventListener('touchstart', onTouchStart, {
5099
+ passive: true
5100
+ });
5101
+ track.addEventListener('touchmove', onTouchMove, {
5102
+ passive: false
5103
+ });
5104
+ track.addEventListener('touchend', onTouchEnd);
5105
+ track.addEventListener('touchcancel', onTouchEnd);
5106
+ return () => {
5107
+ track.removeEventListener('wheel', onZoomWheel);
5108
+ track.removeEventListener('touchstart', onTouchStart);
5109
+ track.removeEventListener('touchmove', onTouchMove);
5110
+ track.removeEventListener('touchend', onTouchEnd);
5111
+ track.removeEventListener('touchcancel', onTouchEnd);
5112
+ };
5113
+ }, [durationSec, setZoomLevel]);
4450
5114
  const onHoverMove = (0,external_react_.useCallback)(event => {
4451
5115
  if (!interactive) {
4452
5116
  return;
@@ -4455,20 +5119,26 @@ function WaveformView({
4455
5119
  if (!(rect.width > 0) || !(durationSec > 0)) {
4456
5120
  return;
4457
5121
  }
4458
- const x = event.clientX - rect.left;
4459
- if (!Number.isFinite(x)) {
5122
+ const pointerX = event.clientX - rect.left;
5123
+ if (!Number.isFinite(pointerX)) {
4460
5124
  return;
4461
5125
  }
4462
- setHoverProgress(Math.min(1, Math.max(0, x / rect.width)));
5126
+ const vp = viewportRef.current;
5127
+ if (vp.pixelsPerSecond > 0) {
5128
+ setHoverProgress(Math.min(1, Math.max(0, timeFromPositionPx(pointerX, vp) / durationSec)));
5129
+ return;
5130
+ }
5131
+ setHoverProgress(Math.min(1, Math.max(0, pointerX / rect.width)));
4463
5132
  }, [durationSec, interactive]);
4464
5133
  const onHoverLeave = (0,external_react_.useCallback)(() => {
4465
5134
  setHoverProgress(null);
4466
5135
  }, []);
4467
- const hoverLeft = hoverProgress == null ? null : `${hoverProgress * 100}%`;
5136
+ const hoverLeft = hoverProgress == null ? null : timeLeftPercent(hoverProgress * durationSec, durationSec, viewportRef.current);
4468
5137
  return /*#__PURE__*/external_react_["default"].createElement("div", {
4469
- className: `bp-WaveformView${interactive ? '' : ' bp-WaveformView--inert'}`,
5138
+ className: `bp-WaveformView${interactive ? '' : ' bp-WaveformView--inert'}${isZoomed ? ' bp-WaveformView--zoomed' : ''}`,
4470
5139
  "data-testid": "bp-waveform-view"
4471
5140
  }, /*#__PURE__*/external_react_["default"].createElement("div", {
5141
+ ref: trackRef,
4472
5142
  className: "bp-WaveformView-track",
4473
5143
  onMouseLeave: interactive ? onHoverLeave : undefined,
4474
5144
  onMouseMove: interactive ? onHoverMove : undefined
@@ -4491,6 +5161,139 @@ function WaveformView({
4491
5161
  "data-testid": "bp-waveform-hover-time"
4492
5162
  }, formatTime(hoverProgress * durationSec)))));
4493
5163
  }
5164
+ // EXTERNAL MODULE: ./node_modules/classnames/index.js
5165
+ var classnames = __webpack_require__(2485);
5166
+ var classnames_default = /*#__PURE__*/__webpack_require__.n(classnames);
5167
+ ;// ./src/lib/viewers/controls/icons/IconZoom24.tsx
5168
+
5169
+ function IconZoom24() {
5170
+ return /*#__PURE__*/external_react_.createElement("svg", {
5171
+ focusable: false,
5172
+ height: 24,
5173
+ viewBox: "0 0 24 24",
5174
+ width: 24
5175
+ }, /*#__PURE__*/external_react_.createElement("path", {
5176
+ d: "M2 12h20M6 8l-4 4 4 4M18 8l4 4-4 4",
5177
+ fill: "none",
5178
+ stroke: "currentColor",
5179
+ strokeLinecap: "round",
5180
+ strokeLinejoin: "round",
5181
+ strokeWidth: "2"
5182
+ }));
5183
+ }
5184
+ /* harmony default export */ const icons_IconZoom24 = (IconZoom24);
5185
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/media/MediaToggle.tsx
5186
+ var MediaToggle = __webpack_require__(8997);
5187
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/slider/SliderControl.tsx + 1 modules
5188
+ var SliderControl = __webpack_require__(4937);
5189
+ ;// ./src/lib/viewers/media/waveform/WaveformZoomControl.scss
5190
+ // extracted by mini-css-extract-plugin
5191
+
5192
+ ;// ./src/lib/viewers/media/waveform/WaveformZoomControl.tsx
5193
+
5194
+
5195
+
5196
+
5197
+
5198
+
5199
+
5200
+
5201
+ function WaveformZoomControl({
5202
+ isRevealed = false,
5203
+ maxZoom,
5204
+ onZoomChange,
5205
+ zoomLevel
5206
+ }) {
5207
+ const [isHovered, setHovered] = (0,external_react_.useState)(false);
5208
+ const [isFocused, setFocused] = (0,external_react_.useState)(false);
5209
+ const dismissTimerRef = (0,external_react_.useRef)(0);
5210
+ const flyoutRef = (0,external_react_.useRef)(null);
5211
+ const shouldFocusSliderRef = (0,external_react_.useRef)(false);
5212
+ const sliderId = `bp-waveform-zoom-slider${(0,external_react_.useId)()}`;
5213
+ const zoom = clampWaveformZoom(zoomLevel, maxZoom);
5214
+ const zoomValue = Math.round(sliderValueFromZoom(zoom, maxZoom));
5215
+ const isOpen = isHovered || isFocused || isRevealed;
5216
+ const clearDismiss = (0,external_react_.useCallback)(() => {
5217
+ window.clearTimeout(dismissTimerRef.current);
5218
+ dismissTimerRef.current = 0;
5219
+ }, []);
5220
+ (0,external_react_.useEffect)(() => () => window.clearTimeout(dismissTimerRef.current), []);
5221
+ (0,external_react_.useLayoutEffect)(() => {
5222
+ if (!isOpen || !shouldFocusSliderRef.current) {
5223
+ return;
5224
+ }
5225
+ shouldFocusSliderRef.current = false;
5226
+ flyoutRef.current?.querySelector('[role="slider"]')?.focus();
5227
+ }, [isOpen]);
5228
+ const handleSlider = (0,external_react_.useCallback)(newValue => {
5229
+ onZoomChange(zoomFromSliderValue(newValue, maxZoom));
5230
+ }, [maxZoom, onZoomChange]);
5231
+ const handleToggleClick = (0,external_react_.useCallback)(() => {
5232
+ clearDismiss();
5233
+ if (isOpen) {
5234
+ shouldFocusSliderRef.current = false;
5235
+ setFocused(false);
5236
+ setHovered(false);
5237
+ return;
5238
+ }
5239
+ shouldFocusSliderRef.current = true;
5240
+ setFocused(true);
5241
+ }, [clearDismiss, isOpen]);
5242
+ return /*#__PURE__*/external_react_["default"].createElement("div", {
5243
+ className: classnames_default()('bp-WaveformZoomControl', {
5244
+ 'bp-is-open': isOpen
5245
+ }),
5246
+ "data-testid": "bp-waveform-zoom",
5247
+ onBlur: event => {
5248
+ if (event.currentTarget.contains(event.relatedTarget)) {
5249
+ return;
5250
+ }
5251
+ setFocused(false);
5252
+ },
5253
+ onFocus: () => {
5254
+ clearDismiss();
5255
+ setFocused(true);
5256
+ },
5257
+ onMouseEnter: () => {
5258
+ clearDismiss();
5259
+ setHovered(true);
5260
+ },
5261
+ onMouseLeave: () => {
5262
+ clearDismiss();
5263
+ dismissTimerRef.current = window.setTimeout(() => {
5264
+ setHovered(false);
5265
+ }, constants/* WAVEFORM_ZOOM_DISMISS_MS */.m6);
5266
+ }
5267
+ }, /*#__PURE__*/external_react_["default"].createElement("div", {
5268
+ ref: flyoutRef,
5269
+ "aria-hidden": !isOpen,
5270
+ className: classnames_default()('bp-WaveformZoomControl-flyout', {
5271
+ 'bp-is-open': isOpen
5272
+ })
5273
+ }, /*#__PURE__*/external_react_["default"].createElement(SliderControl/* default */.A, {
5274
+ "aria-hidden": !isOpen,
5275
+ className: "bp-WaveformZoomControl-slider",
5276
+ "data-resin-target": "waveformZoomSlider",
5277
+ id: sliderId,
5278
+ max: constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh,
5279
+ min: 0,
5280
+ onUpdate: handleSlider,
5281
+ step: 1,
5282
+ style: {
5283
+ '--bp-zoom-t': zoomValue / constants/* WAVEFORM_ZOOM_SLIDER_MAX */.nh
5284
+ },
5285
+ tabIndex: isOpen ? 0 : -1,
5286
+ title: "Zoom Slider",
5287
+ value: zoomValue
5288
+ })), /*#__PURE__*/external_react_["default"].createElement(MediaToggle/* default */.A, {
5289
+ "aria-controls": sliderId,
5290
+ "aria-expanded": isOpen,
5291
+ className: "bp-WaveformZoomControl-toggle",
5292
+ "data-resin-target": "waveformZoom",
5293
+ onClick: handleToggleClick,
5294
+ title: "Zoom"
5295
+ }, /*#__PURE__*/external_react_["default"].createElement(icons_IconZoom24, null)));
5296
+ }
4494
5297
  ;// ./src/lib/viewers/media/MP3ControlsV2.scss
4495
5298
  // extracted by mini-css-extract-plugin
4496
5299
 
@@ -4505,6 +5308,9 @@ function WaveformView({
4505
5308
 
4506
5309
 
4507
5310
 
5311
+
5312
+
5313
+
4508
5314
  const PLACEHOLDER_PEAKS = placeholderPeaks();
4509
5315
  function MP3ControlsV2({
4510
5316
  autoplay,
@@ -4524,11 +5330,34 @@ function MP3ControlsV2({
4524
5330
  volume
4525
5331
  }) {
4526
5332
  const durationValue = typeof durationTime === 'number' && isFinite_default()(durationTime) ? durationTime : 0;
5333
+ const [zoomLevel, setZoomLevel] = (0,external_react_.useState)(constants/* WAVEFORM_ZOOM_MIN */.LW);
5334
+ const [maxZoom, setMaxZoom] = (0,external_react_.useState)(constants/* WAVEFORM_ZOOM_MIN */.LW);
5335
+ const [isZoomRevealed, setIsZoomRevealed] = (0,external_react_.useState)(false);
5336
+ const zoomRevealTimerRef = (0,external_react_.useRef)(0);
4527
5337
  const hasRealPeaks = !!(peaks && peaks.length);
4528
5338
  const waveformPeaks = hasRealPeaks ? peaks : PLACEHOLDER_PEAKS;
4529
5339
  const hasMetadata = durationValue > 0;
4530
5340
  const waveformDurationSec = hasMetadata ? durationValue : PLACEHOLDER_DURATION_SEC;
4531
5341
  const [playRequested, setPlayRequested] = (0,external_react_.useState)(false);
5342
+ const handleViewportChange = (0,external_react_.useCallback)(viewport => {
5343
+ setMaxZoom(viewport.maxZoom);
5344
+ }, []);
5345
+ const revealZoomControl = (0,external_react_.useCallback)(() => {
5346
+ setIsZoomRevealed(true);
5347
+ window.clearTimeout(zoomRevealTimerRef.current);
5348
+ zoomRevealTimerRef.current = window.setTimeout(() => {
5349
+ setIsZoomRevealed(false);
5350
+ zoomRevealTimerRef.current = 0;
5351
+ }, constants/* WAVEFORM_ZOOM_DISMISS_MS */.m6);
5352
+ }, []);
5353
+ const handleWaveformZoom = (0,external_react_.useCallback)(nextZoom => {
5354
+ setZoomLevel(nextZoom);
5355
+ revealZoomControl();
5356
+ }, [revealZoomControl]);
5357
+ (0,external_react_.useEffect)(() => {
5358
+ setZoomLevel(prev => clampWaveformZoom(prev, maxZoom));
5359
+ }, [maxZoom]);
5360
+ (0,external_react_.useEffect)(() => () => window.clearTimeout(zoomRevealTimerRef.current), []);
4532
5361
  (0,external_react_.useEffect)(() => {
4533
5362
  if (isPlaying) {
4534
5363
  setPlayRequested(true);
@@ -4541,6 +5370,8 @@ function MP3ControlsV2({
4541
5370
  const isWaveformInteractive = playRequested && hasMetadata;
4542
5371
  const isWaitingToPlay = playRequested && !hasMetadata;
4543
5372
  const showPlayOverlay = !playRequested && !isPlaying;
5373
+ const hasZoomHandlers = hasRealPeaks && !showPlayOverlay;
5374
+ const hasZoomControl = hasZoomHandlers && hasMetadata && maxZoom > constants/* WAVEFORM_ZOOM_MIN */.LW;
4544
5375
  return /*#__PURE__*/external_react_["default"].createElement("div", {
4545
5376
  className: "bp-MP3ControlsV2",
4546
5377
  "data-testid": "media-controls-wrapper-v2"
@@ -4553,8 +5384,18 @@ function MP3ControlsV2({
4553
5384
  interactive: isWaveformInteractive,
4554
5385
  mediaEl: mediaEl,
4555
5386
  onSeek: isWaveformInteractive ? onTimeChange : undefined,
4556
- peaks: waveformPeaks
4557
- }), showPlayOverlay && /*#__PURE__*/external_react_["default"].createElement("button", {
5387
+ onViewportChange: hasRealPeaks ? handleViewportChange : undefined,
5388
+ onZoomChange: hasZoomHandlers ? handleWaveformZoom : undefined,
5389
+ peaks: waveformPeaks,
5390
+ zoomLevel: hasZoomHandlers ? zoomLevel : constants/* WAVEFORM_ZOOM_MIN */.LW
5391
+ }), hasZoomControl && /*#__PURE__*/external_react_["default"].createElement("div", {
5392
+ className: "bp-MP3ControlsV2-waveformZoom"
5393
+ }, /*#__PURE__*/external_react_["default"].createElement(WaveformZoomControl, {
5394
+ isRevealed: isZoomRevealed,
5395
+ maxZoom: maxZoom,
5396
+ onZoomChange: setZoomLevel,
5397
+ zoomLevel: zoomLevel
5398
+ })), showPlayOverlay && /*#__PURE__*/external_react_["default"].createElement("button", {
4558
5399
  className: "bp-MP3ControlsV2-playOverlay"
4559
5400
  // Static SVG from the icons module, same asset video uses for the overlay.
4560
5401
  // eslint-disable-next-line react/no-danger
@@ -4667,6 +5508,780 @@ function isFpsAvailable(player) {
4667
5508
 
4668
5509
  /***/ },
4669
5510
 
5511
+ /***/ 4929
5512
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
5513
+
5514
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
5515
+ /* harmony export */ DY: () => (/* binding */ WAVEFORM_BAR_RADIUS),
5516
+ /* harmony export */ EB: () => (/* binding */ CLIENT_DECODE_MAX_COMPRESSED_BYTES),
5517
+ /* harmony export */ FY: () => (/* binding */ MAX_PEAK_COUNT),
5518
+ /* harmony export */ GQ: () => (/* binding */ DURATION_MISMATCH_TOLERANCE_SEC),
5519
+ /* harmony export */ Kl: () => (/* binding */ WAVEFORM_MIN_VIEW_WINDOW_SEC),
5520
+ /* harmony export */ LW: () => (/* binding */ WAVEFORM_ZOOM_MIN),
5521
+ /* harmony export */ Lu: () => (/* binding */ WAVEFORM_BAR_GAP),
5522
+ /* harmony export */ XS: () => (/* binding */ WAVEFORM_BAR_MIN_HEIGHT),
5523
+ /* harmony export */ f3: () => (/* binding */ CLIENT_DECODE_PEAK_COUNT),
5524
+ /* harmony export */ i8: () => (/* binding */ PEAK_UNIT_MAX),
5525
+ /* harmony export */ m6: () => (/* binding */ WAVEFORM_ZOOM_DISMISS_MS),
5526
+ /* harmony export */ mJ: () => (/* binding */ PEAK_UNIT_MIN),
5527
+ /* harmony export */ nG: () => (/* binding */ CLIENT_DECODE_MAX_DURATION_SEC),
5528
+ /* harmony export */ nh: () => (/* binding */ WAVEFORM_ZOOM_SLIDER_MAX),
5529
+ /* harmony export */ oN: () => (/* binding */ WAVEFORM_HEIGHT),
5530
+ /* harmony export */ qY: () => (/* binding */ MAX_PAYLOAD_BYTES),
5531
+ /* harmony export */ sh: () => (/* binding */ WAVEFORM_PAYLOAD_VERSION),
5532
+ /* harmony export */ tK: () => (/* binding */ WAVEFORM_ZOOM_MAX),
5533
+ /* harmony export */ zo: () => (/* binding */ WAVEFORM_BAR_WIDTH)
5534
+ /* harmony export */ });
5535
+ /** Current waveform payload schema version. Bump only with a migration path. */
5536
+ const WAVEFORM_PAYLOAD_VERSION = 1;
5537
+
5538
+ /** Maximum peaks in a single tier (overview or detail). */
5539
+ const MAX_PEAK_COUNT = 16384;
5540
+
5541
+ /** Hard limit on serialized JSON body size for a waveform payload (bytes). */
5542
+ const MAX_PAYLOAD_BYTES = 512 * 1024;
5543
+
5544
+ /** Allowed delta between payload duration and caller-supplied media duration (seconds). */
5545
+ const DURATION_MISMATCH_TOLERANCE_SEC = 1;
5546
+
5547
+ /** Peak values must live in this closed interval when peakScale is "unit". */
5548
+ const PEAK_UNIT_MIN = 0;
5549
+ const PEAK_UNIT_MAX = 1;
5550
+
5551
+ /** Skip client decode when the compressed file is larger than this. */
5552
+ const CLIENT_DECODE_MAX_COMPRESSED_BYTES = 6 * 1024 * 1024;
5553
+
5554
+ /** Skip client decode when media duration is longer than this. */
5555
+ const CLIENT_DECODE_MAX_DURATION_SEC = 5 * 60;
5556
+
5557
+ /** Default overview resolution for client-generated peaks. */
5558
+ const CLIENT_DECODE_PEAK_COUNT = 16384;
5559
+ const WAVEFORM_ZOOM_MIN = 1;
5560
+ const WAVEFORM_ZOOM_MAX = 24;
5561
+ /** Visible window never shorter than this. */
5562
+ const WAVEFORM_MIN_VIEW_WINDOW_SEC = 4;
5563
+ const WAVEFORM_ZOOM_SLIDER_MAX = 100;
5564
+ const WAVEFORM_ZOOM_DISMISS_MS = 250;
5565
+ const WAVEFORM_BAR_GAP = 2;
5566
+ const WAVEFORM_BAR_WIDTH = 2;
5567
+ const WAVEFORM_BAR_RADIUS = WAVEFORM_BAR_WIDTH / 2;
5568
+ /** Total bar height so the top and bottom radii meet as a circle on the mirror. */
5569
+ const WAVEFORM_BAR_MIN_HEIGHT = WAVEFORM_BAR_WIDTH;
5570
+ const WAVEFORM_HEIGHT = 140;
5571
+
5572
+ /***/ },
5573
+
5574
+ /***/ 546
5575
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
5576
+
5577
+
5578
+ // EXPORTS
5579
+ __webpack_require__.d(__webpack_exports__, {
5580
+ decodeToPeaks: () => (/* binding */ decodeToPeaks),
5581
+ extractPeaks: () => (/* binding */ extractPeaks),
5582
+ getDecodeDecision: () => (/* binding */ getDecodeDecision),
5583
+ loadPeaks: () => (/* binding */ loadPeaks),
5584
+ runClientDecode: () => (/* binding */ runClientDecode)
5585
+ });
5586
+ // ESM COMPAT FLAG
5587
+ __webpack_require__.r(__webpack_exports__);
5588
+
5589
+ // EXTERNAL MODULE: ./src/lib/util.js + 1 modules
5590
+ var util = __webpack_require__(4410);
5591
+ // EXTERNAL MODULE: ./src/lib/viewers/media/waveform/constants.ts
5592
+ var constants = __webpack_require__(4929);
5593
+ ;// ./src/lib/viewers/media/waveform/types.ts
5594
+ /**
5595
+ * Box V1 is the in-viewer form: unsigned mono peaks in [0, 1] (peak envelope, mono_max).
5596
+ * Wire JSON only requires version, durationSec, and peaks. Policy fields default.
5597
+ * Conversion or wavesurfer payloads should be adapted at the edge, not stored as-is.
5598
+ */
5599
+
5600
+ /** Who produced peaks */
5601
+
5602
+ /**
5603
+ * Viewer-normalized waveform JSON (version 1).
5604
+ * Required: version, durationSec, peaks. Everything else is optional and defaulted.
5605
+ */
5606
+
5607
+ /** Validated, runtime-friendly peak data passed to renderers. */
5608
+
5609
+ const WAVEFORM_ERROR_CODES = ['INVALID_PAYLOAD', 'UNSUPPORTED_VERSION', 'INVALID_DURATION', 'DURATION_MISMATCH', 'PEAK_COUNT_EXCEEDED', 'PAYLOAD_TOO_LARGE', 'NON_FINITE_PEAK', 'PEAK_OUT_OF_RANGE', 'EMPTY_PEAKS', 'CAP_EXCEEDED', 'LOAD_FAILED', 'DECODE_FAILED', 'CANCELLED', 'UNAVAILABLE'];
5610
+ const WAVEFORM_ERROR_CODE_SET = new Set(WAVEFORM_ERROR_CODES);
5611
+ function isWaveformErrorCode(value) {
5612
+ return WAVEFORM_ERROR_CODE_SET.has(value);
5613
+ }
5614
+
5615
+ /**
5616
+ * Async boundary for waveform data. Implementations may fetch fixtures, decode client-side,
5617
+ * or load Conversion reps — callers only observe WaveformLoadState.
5618
+ */
5619
+
5620
+ /** Visible slice of the timeline. Emitted whenever zoom, scroll, or width changes. */
5621
+
5622
+ /** One canvas linear-gradient stop. `offset` is 0–1 along the bar. */
5623
+
5624
+ /**
5625
+ * Wavesurfer's two paints: left of the playhead (`progressColor`) and right of it (`waveColor`).
5626
+ * A string is a solid fill; a stop list is a left-to-right step gradient.
5627
+ */
5628
+ ;// ./src/lib/viewers/media/waveform/createWaveformLoader.ts
5629
+ /* unused harmony import specifier */ var isRetryableWaveformError;
5630
+ /* unused harmony import specifier */ var validateWaveformPayload;
5631
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5632
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5633
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5634
+
5635
+
5636
+ /** Typed throw from fetchPayload (e.g. client decode). `error.name` fallback still works. */
5637
+ class WaveformLoadError extends Error {
5638
+ constructor(code, message) {
5639
+ super(message);
5640
+ _defineProperty(this, "code", void 0);
5641
+ this.name = code;
5642
+ this.code = code;
5643
+ }
5644
+ }
5645
+ function isAbortError(error) {
5646
+ return typeof DOMException !== 'undefined' && error instanceof DOMException && error.name === 'AbortError' || error instanceof Error && error.name === 'AbortError';
5647
+ }
5648
+ function errorFromUnknown(error, fallbackCode = 'LOAD_FAILED') {
5649
+ if (error instanceof WaveformLoadError) {
5650
+ return {
5651
+ code: error.code,
5652
+ message: error.message
5653
+ };
5654
+ }
5655
+ if (error instanceof SyntaxError) {
5656
+ return {
5657
+ code: 'INVALID_PAYLOAD',
5658
+ message: error.message
5659
+ };
5660
+ }
5661
+ if (error instanceof Error && isWaveformErrorCode(error.name)) {
5662
+ return {
5663
+ code: error.name,
5664
+ message: error.message
5665
+ };
5666
+ }
5667
+ const message = error instanceof Error ? error.message : 'Waveform load failed';
5668
+ return {
5669
+ code: fallbackCode,
5670
+ message
5671
+ };
5672
+ }
5673
+ function failedLoadState(error) {
5674
+ return {
5675
+ status: 'failed',
5676
+ error,
5677
+ retryable: isRetryableWaveformError(error.code)
5678
+ };
5679
+ }
5680
+ function readyLoadState(payload) {
5681
+ return {
5682
+ status: 'ready',
5683
+ payload
5684
+ };
5685
+ }
5686
+
5687
+ /**
5688
+ * Wraps an async payload fetch with abort + stale-result suppression.
5689
+ * Playback must not depend on this completing — callers treat unavailable/failed as degrade paths.
5690
+ * abort() cancels an in-flight load only; a successful ready state is left intact.
5691
+ */
5692
+ function createWaveformLoader(fetchPayload, options = {}) {
5693
+ let state = {
5694
+ status: 'unavailable'
5695
+ };
5696
+ let generation = 0;
5697
+ let abortController = null;
5698
+ const setState = next => {
5699
+ state = next;
5700
+ };
5701
+ const settle = next => {
5702
+ setState(next);
5703
+ return next;
5704
+ };
5705
+ const startLoad = () => {
5706
+ if (abortController) {
5707
+ abortController.abort();
5708
+ }
5709
+ generation += 1;
5710
+ abortController = new AbortController();
5711
+ setState({
5712
+ status: 'pending'
5713
+ });
5714
+ return {
5715
+ id: generation,
5716
+ signal: abortController.signal
5717
+ };
5718
+ };
5719
+ const cancelledIfStale = request => {
5720
+ if (!request.signal.aborted && generation === request.id) {
5721
+ return null;
5722
+ }
5723
+ if (generation === request.id) {
5724
+ setState({
5725
+ status: 'cancelled'
5726
+ });
5727
+ }
5728
+ return {
5729
+ status: 'cancelled'
5730
+ };
5731
+ };
5732
+ const settleValidation = validation => {
5733
+ if (!validation.ok) {
5734
+ return settle(failedLoadState(validation.error));
5735
+ }
5736
+ return settle(readyLoadState(validation.payload));
5737
+ };
5738
+ const settleFetchError = (error, request) => {
5739
+ if (request.signal.aborted || generation !== request.id || isAbortError(error)) {
5740
+ if (generation === request.id) {
5741
+ return settle({
5742
+ status: 'cancelled'
5743
+ });
5744
+ }
5745
+ return {
5746
+ status: 'cancelled'
5747
+ };
5748
+ }
5749
+ return settle(failedLoadState(errorFromUnknown(error)));
5750
+ };
5751
+ const load = async () => {
5752
+ const request = startLoad();
5753
+ try {
5754
+ const raw = await fetchPayload(request.signal);
5755
+ const staleAfterFetch = cancelledIfStale(request);
5756
+ if (staleAfterFetch) {
5757
+ return staleAfterFetch;
5758
+ }
5759
+ const validation = validateWaveformPayload(raw, options);
5760
+ const staleAfterValidate = cancelledIfStale(request);
5761
+ if (staleAfterValidate) {
5762
+ return staleAfterValidate;
5763
+ }
5764
+ return settleValidation(validation);
5765
+ } catch (error) {
5766
+ return settleFetchError(error, request);
5767
+ } finally {
5768
+ if (generation === request.id) {
5769
+ abortController = null;
5770
+ }
5771
+ }
5772
+ };
5773
+ const abort = () => {
5774
+ if (!abortController) {
5775
+ return;
5776
+ }
5777
+ generation += 1;
5778
+ abortController.abort();
5779
+ abortController = null;
5780
+ setState({
5781
+ status: 'cancelled'
5782
+ });
5783
+ };
5784
+ return {
5785
+ load,
5786
+ abort,
5787
+ getState: () => state
5788
+ };
5789
+ }
5790
+
5791
+ /** Returns capped state when media exceeds client decode size or duration limits. */
5792
+ function createCappedWaveformState(message) {
5793
+ return {
5794
+ status: 'capped',
5795
+ error: {
5796
+ code: 'CAP_EXCEEDED',
5797
+ message
5798
+ }
5799
+ };
5800
+ }
5801
+ ;// ./src/lib/viewers/media/waveform/validateWaveformPayload.ts
5802
+
5803
+ const DEFAULT_PEAK_SCALE = 'unit';
5804
+ const DEFAULT_CHANNEL_POLICY = 'mono_max';
5805
+ const DEFAULT_ENVELOPE = 'peak';
5806
+ function fail(code, message, retryable = false) {
5807
+ return {
5808
+ ok: false,
5809
+ error: {
5810
+ code,
5811
+ message
5812
+ },
5813
+ retryable
5814
+ };
5815
+ }
5816
+ function isPlainObject(value) {
5817
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
5818
+ }
5819
+ function payloadByteLength(raw) {
5820
+ try {
5821
+ const serialized = JSON.stringify(raw);
5822
+ if (typeof Buffer !== 'undefined') {
5823
+ return Buffer.byteLength(serialized, 'utf8');
5824
+ }
5825
+ if (typeof TextEncoder !== 'undefined') {
5826
+ return new TextEncoder().encode(serialized).length;
5827
+ }
5828
+ return serialized.length;
5829
+ } catch {
5830
+ return Number.MAX_SAFE_INTEGER;
5831
+ }
5832
+ }
5833
+ function readOptionalPolicy(raw, key, allowed) {
5834
+ if (!(key in raw) || raw[key] === undefined) {
5835
+ return undefined;
5836
+ }
5837
+ return raw[key] === allowed ? allowed : null;
5838
+ }
5839
+ function asWaveformPayloadV1(raw) {
5840
+ const {
5841
+ version,
5842
+ durationSec,
5843
+ peaks
5844
+ } = raw;
5845
+ if (version !== constants/* WAVEFORM_PAYLOAD_VERSION */.sh || typeof durationSec !== 'number' || !Array.isArray(peaks)) {
5846
+ return null;
5847
+ }
5848
+ const peakScale = readOptionalPolicy(raw, 'peakScale', DEFAULT_PEAK_SCALE);
5849
+ const channelPolicy = readOptionalPolicy(raw, 'channelPolicy', DEFAULT_CHANNEL_POLICY);
5850
+ const envelope = readOptionalPolicy(raw, 'envelope', DEFAULT_ENVELOPE);
5851
+ if (peakScale === null || channelPolicy === null || envelope === null) {
5852
+ return null;
5853
+ }
5854
+ if ('channels' in raw && raw.channels !== undefined && raw.channels !== 1) {
5855
+ return null;
5856
+ }
5857
+ return {
5858
+ version: constants/* WAVEFORM_PAYLOAD_VERSION */.sh,
5859
+ durationSec,
5860
+ peaks: peaks,
5861
+ peakScale: peakScale ?? DEFAULT_PEAK_SCALE,
5862
+ channelPolicy: channelPolicy ?? DEFAULT_CHANNEL_POLICY,
5863
+ envelope: envelope ?? DEFAULT_ENVELOPE,
5864
+ channels: 1
5865
+ };
5866
+ }
5867
+ function readPayloadObject(raw, maxPayloadBytes, isPayloadByteCheckSkipped = false) {
5868
+ if (raw === null || raw === undefined) {
5869
+ return fail('INVALID_PAYLOAD', 'Payload is null or undefined');
5870
+ }
5871
+ if (!isPayloadByteCheckSkipped) {
5872
+ const byteLength = payloadByteLength(raw);
5873
+ if (byteLength > maxPayloadBytes) {
5874
+ return fail('PAYLOAD_TOO_LARGE', `Payload size ${byteLength} exceeds limit ${maxPayloadBytes}`);
5875
+ }
5876
+ }
5877
+ if (!isPlainObject(raw)) {
5878
+ return fail('INVALID_PAYLOAD', 'Payload must be an object');
5879
+ }
5880
+ return {
5881
+ ok: true,
5882
+ value: raw
5883
+ };
5884
+ }
5885
+ function checkVersion(raw) {
5886
+ if (raw.version === constants/* WAVEFORM_PAYLOAD_VERSION */.sh) {
5887
+ return null;
5888
+ }
5889
+ const {
5890
+ version
5891
+ } = raw;
5892
+ if (typeof version === 'number' && version > constants/* WAVEFORM_PAYLOAD_VERSION */.sh) {
5893
+ return fail('UNSUPPORTED_VERSION', `Unsupported waveform version ${version}; max supported is ${constants/* WAVEFORM_PAYLOAD_VERSION */.sh}`);
5894
+ }
5895
+ return fail('INVALID_PAYLOAD', 'Missing or invalid version field');
5896
+ }
5897
+ function checkDuration(payload, expectedDurationSec) {
5898
+ if (!Number.isFinite(payload.durationSec) || payload.durationSec <= 0) {
5899
+ return fail('INVALID_DURATION', 'durationSec must be a positive finite number');
5900
+ }
5901
+ if (expectedDurationSec !== undefined && Number.isFinite(expectedDurationSec) && Math.abs(payload.durationSec - expectedDurationSec) > constants/* DURATION_MISMATCH_TOLERANCE_SEC */.GQ) {
5902
+ return fail('DURATION_MISMATCH', `Payload duration ${payload.durationSec}s differs from media duration ${expectedDurationSec}s`, true);
5903
+ }
5904
+ return null;
5905
+ }
5906
+ function normalizePeaks(peaks, maxPeakCount) {
5907
+ if (peaks.length === 0) {
5908
+ return fail('EMPTY_PEAKS', 'peaks array must not be empty');
5909
+ }
5910
+ if (peaks.length > maxPeakCount) {
5911
+ return fail('PEAK_COUNT_EXCEEDED', `peaks length ${peaks.length} exceeds max ${maxPeakCount}`);
5912
+ }
5913
+ const normalized = new Float32Array(peaks.length);
5914
+ for (let i = 0; i < peaks.length; i += 1) {
5915
+ const value = peaks[i];
5916
+ if (!Number.isFinite(value)) {
5917
+ return fail('NON_FINITE_PEAK', `Peak at index ${i} is not finite`);
5918
+ }
5919
+ if (value < constants/* PEAK_UNIT_MIN */.mJ || value > constants/* PEAK_UNIT_MAX */.i8) {
5920
+ return fail('PEAK_OUT_OF_RANGE', `Peak at index ${i} is outside [${constants/* PEAK_UNIT_MIN */.mJ}, ${constants/* PEAK_UNIT_MAX */.i8}]`);
5921
+ }
5922
+ normalized[i] = value;
5923
+ }
5924
+ return {
5925
+ ok: true,
5926
+ peaks: normalized
5927
+ };
5928
+ }
5929
+
5930
+ /**
5931
+ * Validates a version-1 waveform payload and returns normalized peaks for rendering.
5932
+ * Wire JSON requires version, durationSec, and peaks. sampleCount and source are ignored.
5933
+ */
5934
+ function validateWaveformPayload_validateWaveformPayload(raw, options = {}) {
5935
+ const maxPeakCount = options.maxPeakCount ?? constants/* MAX_PEAK_COUNT */.FY;
5936
+ const maxPayloadBytes = options.maxPayloadBytes ?? constants/* MAX_PAYLOAD_BYTES */.qY;
5937
+ const objectResult = readPayloadObject(raw, maxPayloadBytes, options.isPayloadByteCheckSkipped === true);
5938
+ if (!objectResult.ok) {
5939
+ return objectResult;
5940
+ }
5941
+ const versionFailure = checkVersion(objectResult.value);
5942
+ if (versionFailure) {
5943
+ return versionFailure;
5944
+ }
5945
+ const payload = asWaveformPayloadV1(objectResult.value);
5946
+ if (!payload) {
5947
+ return fail('INVALID_PAYLOAD', 'Payload failed structural validation');
5948
+ }
5949
+ const durationFailure = checkDuration(payload, options.expectedDurationSec);
5950
+ if (durationFailure) {
5951
+ return durationFailure;
5952
+ }
5953
+ const peaksResult = normalizePeaks(payload.peaks, maxPeakCount);
5954
+ if (!peaksResult.ok) {
5955
+ return peaksResult;
5956
+ }
5957
+ return {
5958
+ ok: true,
5959
+ payload: {
5960
+ version: constants/* WAVEFORM_PAYLOAD_VERSION */.sh,
5961
+ durationSec: payload.durationSec,
5962
+ peaks: peaksResult.peaks,
5963
+ peakScale: payload.peakScale ?? DEFAULT_PEAK_SCALE,
5964
+ channelPolicy: payload.channelPolicy ?? DEFAULT_CHANNEL_POLICY,
5965
+ envelope: payload.envelope ?? DEFAULT_ENVELOPE
5966
+ }
5967
+ };
5968
+ }
5969
+
5970
+ /** Maps validation / load failures to retry policy for loaders. */
5971
+ function validateWaveformPayload_isRetryableWaveformError(code) {
5972
+ return code === 'DURATION_MISMATCH' || code === 'DECODE_FAILED' || code === 'LOAD_FAILED';
5973
+ }
5974
+ ;// ./src/lib/viewers/media/waveform/decode.ts
5975
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5976
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { decode_defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5977
+ function decode_defineProperty(e, r, t) { return (r = decode_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5978
+ function decode_toPropertyKey(t) { var i = decode_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5979
+ function decode_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5980
+
5981
+
5982
+
5983
+
5984
+ const EMPTY_TIMINGS = {
5985
+ attemptMs: null,
5986
+ extractMs: null
5987
+ };
5988
+ function createAbortError() {
5989
+ return new DOMException('Aborted', 'AbortError');
5990
+ }
5991
+ function isPositiveFinite(value) {
5992
+ return typeof value === 'number' && Number.isFinite(value) && value > 0;
5993
+ }
5994
+ function getAudioContextConstructor() {
5995
+ if (typeof window === 'undefined') {
5996
+ return undefined;
5997
+ }
5998
+ const {
5999
+ AudioContext,
6000
+ webkitAudioContext
6001
+ } = window;
6002
+ return AudioContext || webkitAudioContext;
6003
+ }
6004
+ function getDecodeSkipMessage(reason) {
6005
+ if (reason === 'compressed_size') {
6006
+ return 'Compressed size exceeds client decode limit';
6007
+ }
6008
+ if (reason === 'duration') {
6009
+ return 'Duration exceeds client decode limit';
6010
+ }
6011
+ return 'Compressed size or duration is missing; skipping client decode';
6012
+ }
6013
+ function createSkippedDecodeResult(reason) {
6014
+ if (reason === 'missing_metadata') {
6015
+ return {
6016
+ status: 'unavailable',
6017
+ error: {
6018
+ code: 'UNAVAILABLE',
6019
+ message: getDecodeSkipMessage(reason)
6020
+ },
6021
+ isDecodeSkipped: true,
6022
+ timings: EMPTY_TIMINGS,
6023
+ reason
6024
+ };
6025
+ }
6026
+ return _objectSpread(_objectSpread({}, createCappedWaveformState(getDecodeSkipMessage(reason))), {}, {
6027
+ isDecodeSkipped: true,
6028
+ timings: EMPTY_TIMINGS,
6029
+ reason
6030
+ });
6031
+ }
6032
+ function decodeWithContext(context, buffer, signal) {
6033
+ return new Promise((resolve, reject) => {
6034
+ let isSettled = false;
6035
+ let onAbort = () => undefined;
6036
+ const onSuccess = decoded => {
6037
+ if (isSettled) {
6038
+ return;
6039
+ }
6040
+ isSettled = true;
6041
+ signal?.removeEventListener('abort', onAbort);
6042
+ resolve(decoded);
6043
+ };
6044
+ const onFailure = error => {
6045
+ if (isSettled) {
6046
+ return;
6047
+ }
6048
+ isSettled = true;
6049
+ signal?.removeEventListener('abort', onAbort);
6050
+ if (isAbortError(error)) {
6051
+ reject(error instanceof DOMException ? error : createAbortError());
6052
+ return;
6053
+ }
6054
+ reject(error instanceof WaveformLoadError ? error : new WaveformLoadError('DECODE_FAILED', 'decodeAudioData failed'));
6055
+ };
6056
+ onAbort = () => onFailure(createAbortError());
6057
+ if (signal?.aborted) {
6058
+ onFailure(createAbortError());
6059
+ return;
6060
+ }
6061
+ signal?.addEventListener('abort', onAbort, {
6062
+ once: true
6063
+ });
6064
+ try {
6065
+ const maybePromise = context.decodeAudioData(buffer, onSuccess, onFailure);
6066
+ if (maybePromise && typeof maybePromise.then === 'function') {
6067
+ maybePromise.then(onSuccess, onFailure);
6068
+ }
6069
+ } catch (error) {
6070
+ onFailure(error);
6071
+ }
6072
+ });
6073
+ }
6074
+
6075
+ /**
6076
+ * Decide whether to run decodeAudioData. Either cap failing, or unknown size/duration,
6077
+ * skips decode so playback is never blocked by expanding the full audio buffer.
6078
+ */
6079
+ function getDecodeDecision(media, caps = {}) {
6080
+ const maxCompressedBytes = caps.maxCompressedBytes ?? constants/* CLIENT_DECODE_MAX_COMPRESSED_BYTES */.EB;
6081
+ const maxDurationSec = caps.maxDurationSec ?? constants/* CLIENT_DECODE_MAX_DURATION_SEC */.nG;
6082
+ const {
6083
+ compressedBytes,
6084
+ durationSec
6085
+ } = media;
6086
+ if (!isPositiveFinite(compressedBytes) || !isPositiveFinite(durationSec)) {
6087
+ return {
6088
+ isAllowed: false,
6089
+ reason: 'missing_metadata'
6090
+ };
6091
+ }
6092
+ if (compressedBytes > maxCompressedBytes) {
6093
+ return {
6094
+ isAllowed: false,
6095
+ reason: 'compressed_size'
6096
+ };
6097
+ }
6098
+ if (durationSec > maxDurationSec) {
6099
+ return {
6100
+ isAllowed: false,
6101
+ reason: 'duration'
6102
+ };
6103
+ }
6104
+ return {
6105
+ isAllowed: true
6106
+ };
6107
+ }
6108
+
6109
+ /**
6110
+ * Collapse audio channels to one unsigned peak per time bucket (max abs, then clamp to unit).
6111
+ * Accepts an AudioBuffer so callers can extract before closing the AudioContext.
6112
+ */
6113
+ function extractPeaks(audio, peakCount = constants/* CLIENT_DECODE_PEAK_COUNT */.f3) {
6114
+ const channels = Array.isArray(audio) ? audio : Array.from({
6115
+ length: audio.numberOfChannels
6116
+ }, (_, channelIndex) => audio.getChannelData(channelIndex));
6117
+ if (peakCount <= 0 || channels.length === 0) {
6118
+ return [];
6119
+ }
6120
+ const sampleCount = channels[0].length;
6121
+ if (sampleCount === 0) {
6122
+ return [];
6123
+ }
6124
+ const peaks = new Array(peakCount);
6125
+ const bucketWidth = sampleCount / peakCount;
6126
+ for (let i = 0; i < peakCount; i += 1) {
6127
+ const start = Math.floor(i * bucketWidth);
6128
+ const end = Math.max(start + 1, Math.floor((i + 1) * bucketWidth));
6129
+ let maxAbs = 0;
6130
+ for (let sampleIndex = start; sampleIndex < end && sampleIndex < sampleCount; sampleIndex += 1) {
6131
+ for (let channelIndex = 0; channelIndex < channels.length; channelIndex += 1) {
6132
+ const value = Math.abs(channels[channelIndex][sampleIndex]);
6133
+ if (value > maxAbs) {
6134
+ maxAbs = value;
6135
+ }
6136
+ }
6137
+ }
6138
+ peaks[i] = Math.min(constants/* PEAK_UNIT_MAX */.i8, Math.max(constants/* PEAK_UNIT_MIN */.mJ, maxAbs));
6139
+ }
6140
+ return peaks;
6141
+ }
6142
+
6143
+ /**
6144
+ * Decode compressed audio and extract unit peaks while the AudioBuffer is live.
6145
+ * Does not attach a media element or fetch a URL. Does not copy channel data.
6146
+ */
6147
+ async function decodeToPeaks(arrayBuffer, signal, peakCount = constants/* CLIENT_DECODE_PEAK_COUNT */.f3) {
6148
+ if (signal?.aborted) {
6149
+ throw createAbortError();
6150
+ }
6151
+ const AudioContextConstructor = getAudioContextConstructor();
6152
+ if (!AudioContextConstructor) {
6153
+ throw new WaveformLoadError('DECODE_FAILED', 'AudioContext is not available');
6154
+ }
6155
+ const context = new AudioContextConstructor();
6156
+ try {
6157
+ const audioBuffer = await decodeWithContext(context, arrayBuffer.slice(0), signal);
6158
+ if (signal?.aborted) {
6159
+ throw createAbortError();
6160
+ }
6161
+ const extractStarted = (0,util/* getCurrentTimeMs */.RU)();
6162
+ const peaks = extractPeaks(audioBuffer, peakCount);
6163
+ return {
6164
+ durationSec: audioBuffer.duration,
6165
+ peaks,
6166
+ extractMs: (0,util/* getCurrentTimeMs */.RU)() - extractStarted
6167
+ };
6168
+ } catch (error) {
6169
+ if (signal?.aborted || isAbortError(error)) {
6170
+ throw createAbortError();
6171
+ }
6172
+ throw error;
6173
+ } finally {
6174
+ try {
6175
+ await context.close();
6176
+ } catch {
6177
+ // Context may already be closed.
6178
+ }
6179
+ }
6180
+ }
6181
+
6182
+ /**
6183
+ * Run a client-decode attempt against the V1 waveform contract.
6184
+ * Decode is not invoked when size or duration is over the cap (or unknown).
6185
+ */
6186
+ async function runClientDecode(options) {
6187
+ const decision = getDecodeDecision({
6188
+ compressedBytes: options.compressedBytes,
6189
+ durationSec: options.durationSec
6190
+ }, options.caps);
6191
+ if (!decision.isAllowed) {
6192
+ return createSkippedDecodeResult(decision.reason);
6193
+ }
6194
+ if (options.signal?.aborted) {
6195
+ return {
6196
+ status: 'cancelled',
6197
+ isDecodeSkipped: true,
6198
+ timings: EMPTY_TIMINGS
6199
+ };
6200
+ }
6201
+ const signal = options.signal ?? new AbortController().signal;
6202
+ const decodeStarted = (0,util/* getCurrentTimeMs */.RU)();
6203
+ let decodeOutput;
6204
+ try {
6205
+ decodeOutput = await options.decode(signal);
6206
+ } catch (error) {
6207
+ if (signal.aborted || isAbortError(error)) {
6208
+ return {
6209
+ status: 'cancelled',
6210
+ isDecodeSkipped: false,
6211
+ timings: EMPTY_TIMINGS
6212
+ };
6213
+ }
6214
+ const waveformError = errorFromUnknown(error, 'DECODE_FAILED');
6215
+ return {
6216
+ status: 'failed',
6217
+ error: waveformError,
6218
+ retryable: validateWaveformPayload_isRetryableWaveformError(waveformError.code),
6219
+ isDecodeSkipped: false,
6220
+ timings: {
6221
+ attemptMs: (0,util/* getCurrentTimeMs */.RU)() - decodeStarted,
6222
+ extractMs: null
6223
+ }
6224
+ };
6225
+ }
6226
+ const attemptMs = (0,util/* getCurrentTimeMs */.RU)() - decodeStarted;
6227
+ if (signal.aborted) {
6228
+ return {
6229
+ status: 'cancelled',
6230
+ isDecodeSkipped: false,
6231
+ timings: {
6232
+ attemptMs,
6233
+ extractMs: null
6234
+ }
6235
+ };
6236
+ }
6237
+ const validation = validateWaveformPayload_validateWaveformPayload({
6238
+ version: constants/* WAVEFORM_PAYLOAD_VERSION */.sh,
6239
+ durationSec: options.durationSec ?? decodeOutput.durationSec,
6240
+ peaks: decodeOutput.peaks
6241
+ }, {
6242
+ isPayloadByteCheckSkipped: true
6243
+ });
6244
+ if (!validation.ok) {
6245
+ return {
6246
+ status: 'failed',
6247
+ error: validation.error,
6248
+ retryable: validateWaveformPayload_isRetryableWaveformError(validation.error.code),
6249
+ isDecodeSkipped: false,
6250
+ timings: {
6251
+ attemptMs,
6252
+ extractMs: decodeOutput.extractMs
6253
+ }
6254
+ };
6255
+ }
6256
+ return {
6257
+ status: 'ready',
6258
+ payload: validation.payload,
6259
+ isDecodeSkipped: false,
6260
+ timings: {
6261
+ attemptMs,
6262
+ extractMs: decodeOutput.extractMs
6263
+ }
6264
+ };
6265
+ }
6266
+
6267
+ /**
6268
+ * Gate, fetch, decode, and extract in-viewer peaks. Skips decode when over cap.
6269
+ * Playback must not await this.
6270
+ */
6271
+ function loadPeaks(request) {
6272
+ return runClientDecode({
6273
+ compressedBytes: request.compressedBytes,
6274
+ durationSec: request.durationSec,
6275
+ signal: request.signal,
6276
+ decode: async signal => {
6277
+ const arrayBuffer = await request.fetchArrayBuffer(signal);
6278
+ return decodeToPeaks(arrayBuffer, signal);
6279
+ }
6280
+ });
6281
+ }
6282
+
6283
+ /***/ },
6284
+
4670
6285
  /***/ 2485
4671
6286
  (module, exports) {
4672
6287
 
@@ -11436,7 +13051,7 @@ var x = (y) => {
11436
13051
  var x = {}; __webpack_require__.d(x, y); return x
11437
13052
  }
11438
13053
  var y = (x) => (() => (x))
11439
- module.exports = x({ ["createElement"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.createElement), ["default"]: () => (__WEBPACK_EXTERNAL_MODULE_react__["default"]), ["useCallback"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useCallback), ["useEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useEffect), ["useLayoutEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useLayoutEffect), ["useRef"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useRef), ["useState"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useState) });
13054
+ module.exports = x({ ["createElement"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.createElement), ["default"]: () => (__WEBPACK_EXTERNAL_MODULE_react__["default"]), ["useCallback"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useCallback), ["useEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useEffect), ["useId"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useId), ["useLayoutEffect"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useLayoutEffect), ["useMemo"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useMemo), ["useRef"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useRef), ["useState"]: () => (__WEBPACK_EXTERNAL_MODULE_react__.useState) });
11440
13055
 
11441
13056
  /***/ },
11442
13057
 
@@ -20330,7 +21945,7 @@ class Browser {
20330
21945
  ;// ./src/lib/Logger.js
20331
21946
  /* eslint-disable no-undef */
20332
21947
  const CLIENT_NAME = "box-content-preview";
20333
- const CLIENT_VERSION = "3.82.0";
21948
+ const CLIENT_VERSION = "3.84.0";
20334
21949
  /* eslint-enable no-undef */
20335
21950
 
20336
21951
  class Logger {
@@ -32844,150 +34459,8 @@ function Filmstrip({
32844
34459
  "data-testid": "bp-Filmstrip-time"
32845
34460
  }, (0,DurationLabels/* formatTime */.f)(time)));
32846
34461
  }
32847
- ;// ./src/lib/viewers/controls/slider/SliderControl.scss
32848
- // extracted by mini-css-extract-plugin
32849
-
32850
- ;// ./src/lib/viewers/controls/slider/SliderControl.tsx
32851
- const SliderControl_excluded = ["className", "max", "min", "onMove", "onUpdate", "step", "title", "track", "value"];
32852
- function SliderControl_extends() { return SliderControl_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, SliderControl_extends.apply(null, arguments); }
32853
- function SliderControl_objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = SliderControl_objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
32854
- function SliderControl_objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
32855
-
32856
-
32857
-
32858
-
32859
-
32860
- function SliderControl(_ref) {
32861
- let {
32862
- className,
32863
- max = 100,
32864
- min = 0,
32865
- onMove = (noop_default()),
32866
- onUpdate = (noop_default()),
32867
- step = 1,
32868
- title,
32869
- track,
32870
- value
32871
- } = _ref,
32872
- rest = SliderControl_objectWithoutProperties(_ref, SliderControl_excluded);
32873
- const [isScrubbing, setIsScrubbing] = external_react_["default"].useState(false);
32874
- const sliderElRef = external_react_["default"].useRef(null);
32875
- const getPosition = external_react_["default"].useCallback(pageX => {
32876
- const {
32877
- current: sliderEl
32878
- } = sliderElRef;
32879
- if (!sliderEl) return 0;
32880
- const {
32881
- left: sliderLeft,
32882
- width: sliderWidth
32883
- } = sliderEl.getBoundingClientRect();
32884
- return Math.max(0, Math.min(pageX - sliderLeft, sliderWidth));
32885
- }, []);
32886
- const getPositionValue = external_react_["default"].useCallback(pageX => {
32887
- const {
32888
- current: sliderEl
32889
- } = sliderElRef;
32890
- if (!sliderEl) return 0;
32891
- const {
32892
- width: sliderWidth
32893
- } = sliderEl.getBoundingClientRect();
32894
- const newValue = getPosition(pageX) / sliderWidth * max;
32895
- return Math.max(min, Math.min(newValue, max));
32896
- }, [getPosition, max, min]);
32897
- const handleKeydown = event => {
32898
- const key = (0,util/* decodeKeydown */.wU)(event);
32899
- if (key === 'ArrowLeft') {
32900
- event.stopPropagation(); // Prevents global key handling
32901
- onUpdate(Math.max(min, Math.min(value - step, max)));
32902
- }
32903
- if (key === 'ArrowRight') {
32904
- event.stopPropagation(); // Prevents global key handling
32905
- onUpdate(Math.max(min, Math.min(value + step, max)));
32906
- }
32907
- };
32908
- const handleMouseDown = ({
32909
- button,
32910
- ctrlKey,
32911
- metaKey,
32912
- pageX
32913
- }) => {
32914
- if (button > 1 || ctrlKey || metaKey) return;
32915
- onUpdate(getPositionValue(pageX));
32916
- setIsScrubbing(true);
32917
- };
32918
- const handleMouseMove = ({
32919
- pageX
32920
- }) => {
32921
- const {
32922
- current: sliderEl
32923
- } = sliderElRef;
32924
- const {
32925
- width: sliderWidth
32926
- } = sliderEl ? sliderEl.getBoundingClientRect() : {
32927
- width: 0
32928
- };
32929
- onMove(getPositionValue(pageX), getPosition(pageX), sliderWidth);
32930
- };
32931
- const handleTouchStart = ({
32932
- touches
32933
- }) => {
32934
- onUpdate(getPositionValue(touches[0].pageX));
32935
- setIsScrubbing(true);
32936
- };
32937
- external_react_["default"].useEffect(() => {
32938
- const handleDocumentMoveStop = () => setIsScrubbing(false);
32939
- const handleDocumentMouseMove = event => {
32940
- if (!isScrubbing || event.button > 1 || event.ctrlKey || event.metaKey) return;
32941
- event.preventDefault();
32942
- onUpdate(getPositionValue(event.pageX));
32943
- };
32944
- const handleDocumentTouchMove = event => {
32945
- if (!isScrubbing || !event.touches || !event.touches[0]) return;
32946
- event.preventDefault();
32947
- onUpdate(getPositionValue(event.touches[0].pageX));
32948
- };
32949
- if (isScrubbing) {
32950
- document.addEventListener('mousemove', handleDocumentMouseMove);
32951
- document.addEventListener('mouseup', handleDocumentMoveStop);
32952
- document.addEventListener('touchend', handleDocumentMoveStop);
32953
- document.addEventListener('touchmove', handleDocumentTouchMove);
32954
- }
32955
- return () => {
32956
- document.removeEventListener('mousemove', handleDocumentMouseMove);
32957
- document.removeEventListener('mouseup', handleDocumentMoveStop);
32958
- document.removeEventListener('touchend', handleDocumentMoveStop);
32959
- document.removeEventListener('touchmove', handleDocumentTouchMove);
32960
- };
32961
- }, [isScrubbing, getPositionValue, onUpdate]);
32962
- return /*#__PURE__*/external_react_["default"].createElement("div", SliderControl_extends({
32963
- ref: sliderElRef,
32964
- "aria-label": title,
32965
- "aria-valuemax": max,
32966
- "aria-valuemin": min,
32967
- "aria-valuenow": value,
32968
- className: classnames_default()('bp-SliderControl', className, {
32969
- 'bp-is-scrubbing': isScrubbing
32970
- }),
32971
- onKeyDown: handleKeydown,
32972
- onMouseDown: handleMouseDown,
32973
- onMouseMove: handleMouseMove,
32974
- onTouchStart: handleTouchStart,
32975
- role: "slider",
32976
- tabIndex: 0
32977
- }, rest), /*#__PURE__*/external_react_["default"].createElement("div", {
32978
- className: "bp-SliderControl-track",
32979
- "data-testid": "bp-slider-control-track",
32980
- style: {
32981
- backgroundImage: track
32982
- }
32983
- }), /*#__PURE__*/external_react_["default"].createElement("div", {
32984
- className: "bp-SliderControl-thumb",
32985
- "data-testid": "bp-slider-control-thumb",
32986
- style: {
32987
- left: `${value / max * 100}%`
32988
- }
32989
- }));
32990
- }
34462
+ // EXTERNAL MODULE: ./src/lib/viewers/controls/slider/SliderControl.tsx + 1 modules
34463
+ var SliderControl = __webpack_require__(4937);
32991
34464
  ;// ./src/lib/viewers/controls/media/TimeControls.scss
32992
34465
  // extracted by mini-css-extract-plugin
32993
34466
 
@@ -33047,7 +34520,7 @@ function TimeControls({
33047
34520
  durationTime: durationTime,
33048
34521
  fps: fps,
33049
34522
  mediaEl: mediaEl
33050
- }), /*#__PURE__*/external_react_["default"].createElement(SliderControl, {
34523
+ }), /*#__PURE__*/external_react_["default"].createElement(SliderControl/* default */.A, {
33051
34524
  className: "bp-TimeControls-slider",
33052
34525
  "data-resin-target": "timeScrubber",
33053
34526
  max: durationValue,
@@ -33172,6 +34645,11 @@ function MP3Viewer_toPrimitive(t, r) { if ("object" != typeof t || !t) return t;
33172
34645
 
33173
34646
 
33174
34647
  const CSS_CLASS_MP3 = 'bp-media-mp3';
34648
+ function createLoadFailedError(message) {
34649
+ const error = new Error(message);
34650
+ error.name = 'LOAD_FAILED';
34651
+ return error;
34652
+ }
33175
34653
  class MP3Viewer extends media_MediaBaseViewer {
33176
34654
  constructor(...args) {
33177
34655
  super(...args);
@@ -33181,6 +34659,11 @@ class MP3Viewer extends media_MediaBaseViewer {
33181
34659
  MP3Viewer_defineProperty(this, "handlePlayRequest", () => {
33182
34660
  this.userRequestedPlay = true;
33183
34661
  this.togglePlay();
34662
+ if (this.isWaveformDecodeRetryPending && !this.hasUsedWaveformDecodePlayRetry) {
34663
+ this.hasUsedWaveformDecodePlayRetry = true;
34664
+ this.isWaveformDecodeRetryPending = false;
34665
+ this.startClientWaveformDecode();
34666
+ }
33184
34667
  });
33185
34668
  /**
33186
34669
  * Auto-play was prevented, pause the audio
@@ -33210,6 +34693,7 @@ class MP3Viewer extends media_MediaBaseViewer {
33210
34693
  this.wrapperEl.classList.add('bp-media--v2');
33211
34694
  this.mediaContainerEl.classList.add('bp-media-container--v2');
33212
34695
  this.ensureV2Controls();
34696
+ this.importWaveformDecode();
33213
34697
  }
33214
34698
 
33215
34699
  // Audio element
@@ -33268,7 +34752,30 @@ class MP3Viewer extends media_MediaBaseViewer {
33268
34752
  * @return {Promise<{ default: Function }>} MP3ControlsV2 module
33269
34753
  */
33270
34754
  importV2Controls() {
33271
- return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 7893));
34755
+ return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 3845));
34756
+ }
34757
+
34758
+ /**
34759
+ * @return {Promise<{ loadPeaks: Function }>} client-decode helpers
34760
+ */
34761
+ async importWaveformDecode() {
34762
+ if (!this.waveformDecodeImport) {
34763
+ this.waveformDecodeImport = Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 546));
34764
+ }
34765
+ try {
34766
+ return await this.waveformDecodeImport;
34767
+ } catch (error) {
34768
+ this.waveformDecodeImport = null;
34769
+ throw error;
34770
+ }
34771
+ }
34772
+
34773
+ /**
34774
+ * @inheritdoc
34775
+ */
34776
+ destroy() {
34777
+ this.abortClientWaveformDecode();
34778
+ super.destroy();
33272
34779
  }
33273
34780
 
33274
34781
  /**
@@ -33315,10 +34822,126 @@ class MP3Viewer extends media_MediaBaseViewer {
33315
34822
  */
33316
34823
  loadeddataHandler() {
33317
34824
  super.loadeddataHandler();
33318
- if (this.isAudioPlayerV2 && this.userRequestedPlay) {
34825
+ if (!this.isAudioPlayerV2) {
34826
+ return;
34827
+ }
34828
+
34829
+ // Play first so Safari has a user gesture before AudioContext is created.
34830
+ if (this.userRequestedPlay) {
33319
34831
  this.play();
33320
34832
  }
34833
+ this.startClientWaveformDecode();
33321
34834
  }
34835
+ /**
34836
+ * Fetch compressed audio bytes for client decode. Prefers an already-fetched blob URL.
34837
+ *
34838
+ * @param {AbortSignal} signal
34839
+ * @return {Promise<ArrayBuffer>}
34840
+ */
34841
+ async fetchAudioArrayBuffer(signal) {
34842
+ if (this.mediaBlobUrl) {
34843
+ const response = await fetch(this.mediaBlobUrl, {
34844
+ signal
34845
+ });
34846
+ if (!response.ok) {
34847
+ throw createLoadFailedError(`Waveform fetch failed (${response.status})`);
34848
+ }
34849
+ return response.arrayBuffer();
34850
+ }
34851
+ const template = this.options.representation && this.options.representation.content && this.options.representation.content.url_template;
34852
+ if (!template) {
34853
+ throw createLoadFailedError('Waveform fetch URL is missing');
34854
+ }
34855
+ const data = await this.api.get(this.createContentUrlV2(template), {
34856
+ headers: this.appendAuthHeader(),
34857
+ signal,
34858
+ type: 'arraybuffer'
34859
+ });
34860
+ if (data instanceof ArrayBuffer) {
34861
+ return data;
34862
+ }
34863
+ throw createLoadFailedError('Waveform fetch did not return binary data');
34864
+ }
34865
+ abortClientWaveformDecode() {
34866
+ if (!this.waveformDecodeController) {
34867
+ return;
34868
+ }
34869
+ this.waveformDecodeController.abort();
34870
+ this.waveformDecodeController = null;
34871
+ }
34872
+
34873
+ /**
34874
+ * Decode peaks in the background when the file is under size and duration caps.
34875
+ * Does not block playback. Capped or failed decode keeps the placeholder waveform.
34876
+ *
34877
+ * @return {Promise<void>}
34878
+ */
34879
+ async startClientWaveformDecode() {
34880
+ if (!this.isAudioPlayerV2 || this.destroyed) {
34881
+ return;
34882
+ }
34883
+ this.abortClientWaveformDecode();
34884
+ const controller = new AbortController();
34885
+ this.waveformDecodeController = controller;
34886
+ const {
34887
+ signal
34888
+ } = controller;
34889
+ try {
34890
+ const decodeModule = await this.importWaveformDecode();
34891
+ if (this.destroyed || signal.aborted || this.waveformDecodeController !== controller) {
34892
+ return;
34893
+ }
34894
+ const result = await decodeModule.loadPeaks({
34895
+ compressedBytes: this.options.file && this.options.file.size,
34896
+ durationSec: this.mediaEl && this.mediaEl.duration,
34897
+ fetchArrayBuffer: fetchSignal => this.fetchAudioArrayBuffer(fetchSignal),
34898
+ signal
34899
+ });
34900
+ this.handleClientWaveformDecodeResult(result, controller, signal);
34901
+ } catch (error) {
34902
+ if (error && error.name === 'AbortError') {
34903
+ this.handleClientWaveformDecodeResult({
34904
+ status: 'cancelled'
34905
+ }, controller, signal);
34906
+ return;
34907
+ }
34908
+ this.handleClientWaveformDecodeResult({
34909
+ error: {
34910
+ code: 'LOAD_FAILED',
34911
+ message: error instanceof Error ? error.message : 'Waveform decode failed'
34912
+ },
34913
+ retryable: true,
34914
+ status: 'failed'
34915
+ }, controller, signal);
34916
+ }
34917
+ }
34918
+
34919
+ /**
34920
+ * Apply peaks, or record a retryable failure for overlay play.
34921
+ *
34922
+ * @param {import('./waveform/types').ClientDecodeResult} result
34923
+ * @param {AbortController} controller in-flight controller for this attempt
34924
+ * @param {AbortSignal} signal
34925
+ * @return {void}
34926
+ */
34927
+ handleClientWaveformDecodeResult(result, controller, signal) {
34928
+ if (!result || this.destroyed || signal.aborted || this.waveformDecodeController !== controller) {
34929
+ return;
34930
+ }
34931
+ if (result.status === 'ready') {
34932
+ this.waveformPeaks = result.payload.peaks;
34933
+ this.isWaveformDecodeRetryPending = false;
34934
+ this.renderUI();
34935
+ return;
34936
+ }
34937
+ if (result.status === 'cancelled') {
34938
+ return;
34939
+ }
34940
+ if (result.status === 'failed' && result.retryable && !this.hasUsedWaveformDecodePlayRetry) {
34941
+ this.isWaveformDecodeRetryPending = true;
34942
+ }
34943
+ }
34944
+
33322
34945
  /**
33323
34946
  * @inheritdoc
33324
34947
  */
@@ -35425,7 +37048,7 @@ function TimeControlsV2({
35425
37048
  style: trackMask ? {
35426
37049
  '--bp-track-mask': trackMask
35427
37050
  } : undefined
35428
- }, /*#__PURE__*/external_react_["default"].createElement(SliderControl, {
37051
+ }, /*#__PURE__*/external_react_["default"].createElement(SliderControl/* default */.A, {
35429
37052
  className: "bp-TimeControlsV2-slider",
35430
37053
  "data-resin-target": "timeScrubber",
35431
37054
  max: durationValue,