box-content-preview 3.82.0 → 3.83.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/README.md +7 -7
- package/dist/lib/index.css +2 -2
- package/dist/lib/index.js +878 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
[](https://mergify.io)
|
|
3
3
|
[](https://github.com/prettier/prettier)
|
|
4
4
|
[](https://travis-ci.org/box/box-content-preview)
|
|
5
|
-
[](https://github.com/box/box-content-preview)
|
|
6
6
|
[](https://www.npmjs.com/package/box-ui-elements)
|
|
7
7
|
|
|
8
8
|
# [Box Content Preview](https://developer.box.com/docs/box-content-preview)
|
|
@@ -22,11 +22,11 @@ We encourage all users to update their applications to utilize modern browsers f
|
|
|
22
22
|
|
|
23
23
|
## Current Version
|
|
24
24
|
|
|
25
|
-
- Version: v3.
|
|
25
|
+
- Version: v3.83.0
|
|
26
26
|
- Locale: en-US
|
|
27
27
|
|
|
28
|
-
https://cdn01.boxcdn.net/platform/preview/3.
|
|
29
|
-
https://cdn01.boxcdn.net/platform/preview/3.
|
|
28
|
+
https://cdn01.boxcdn.net/platform/preview/3.83.0/en-US/preview.js
|
|
29
|
+
https://cdn01.boxcdn.net/platform/preview/3.83.0/en-US/preview.css
|
|
30
30
|
|
|
31
31
|
## Supported Locales
|
|
32
32
|
|
|
@@ -52,10 +52,10 @@ You can self-host the Box Content Preview library or reference the versions avai
|
|
|
52
52
|
<title>Box Content Preview Demo</title>
|
|
53
53
|
|
|
54
54
|
<!-- Latest version of Preview SDK for your locale -->
|
|
55
|
-
<script src="https://cdn01.boxcdn.net/platform/preview/3.
|
|
55
|
+
<script src="https://cdn01.boxcdn.net/platform/preview/3.83.0/en-US/preview.js"></script>
|
|
56
56
|
<link
|
|
57
57
|
rel="stylesheet"
|
|
58
|
-
href="https://cdn01.boxcdn.net/platform/preview/3.
|
|
58
|
+
href="https://cdn01.boxcdn.net/platform/preview/3.83.0/en-US/preview.css"
|
|
59
59
|
/>
|
|
60
60
|
</head>
|
|
61
61
|
<body>
|
|
@@ -76,7 +76,7 @@ To self-host the Box Content Preview library, follow these steps:
|
|
|
76
76
|
|
|
77
77
|
1. Either fork the repo and check out the version you want to host or download the specific version as a zip:
|
|
78
78
|
|
|
79
|
-
- Check out a specific version with `git checkout v3.
|
|
79
|
+
- Check out a specific version with `git checkout v3.83.0`
|
|
80
80
|
- Download a specific version as a zip from https://github.com/box/box-content-preview/releases
|
|
81
81
|
|
|
82
82
|
2. Install dependencies and build the library with `yarn install && yarn build:i18n && yarn build:prod`
|
package/dist/lib/index.css
CHANGED
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
.bp-TimestampControl{position:relative;display:flex;align-items:center}.bp-TimestampControl-button,.bp-TimestampControl-static{display:flex;gap:4px;align-items:center;padding:6px 12px;color:#fff;font-weight:normal;font-size:14px;font-family:Lato,Arial,sans-serif;line-height:20px;letter-spacing:.3px;white-space:nowrap;-webkit-user-select:none;user-select:none}.bp-TimestampControl-button{opacity:.9;transition:opacity 150ms,background-color 150ms;will-change:opacity;outline:0;margin:0;background:transparent;border:0;border-radius:25px;cursor:pointer}.bp-TimestampControl-button:focus:not(:disabled),.bp-TimestampControl-button:hover:not(:disabled){background-color:rgba(255,255,255,.08);opacity:1}.bp-TimestampControl-button:focus:not(:disabled) svg,.bp-TimestampControl-button:hover:not(:disabled) svg,.bp-TimestampControl-button.bp-is-active svg{transform:scale(1.09);transition:transform 150ms}.bp-TimestampControl-button.bp-is-active,.bp-TimestampControl-button:active:not(:disabled){background-color:rgba(255,255,255,.16)}.bp-TimestampControl-button.bp-is-active:focus:not(:disabled),.bp-TimestampControl-button.bp-is-active:hover:not(:disabled){background-color:rgba(255,255,255,.2)}.bp-TimestampControl-button:focus:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-TimestampControl-button:focus-visible:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-TimestampControl-button:focus:not(:focus-visible){box-shadow:none}.bp-TimestampControl-button:focus:not(:disabled),.bp-TimestampControl-button.bp-is-open{background:transparent}.bp-TimestampControl-button:hover:not(:disabled){background:rgba(255,255,255,.08)}.bp-TimestampControl-separator,.bp-TimestampControl-duration{color:rgba(255,255,255,.8)}.bp-TimestampControl-chevron{width:16px;height:16px;margin-left:2px;fill:rgba(255,255,255,.8)}.bp-TimestampControl-flyout{position:absolute;bottom:calc(100% + 12px);left:50%;z-index:1;min-width:250px;padding:12px 8px;background:#fff;border-radius:16px;box-shadow:0 4px 12px 0 rgba(0,0,0,.15);transform:translateX(-50%)}.bp-TimestampControl-header{padding:6px 14px;color:#767676;font-weight:bold;font-size:14px;font-family:Lato,Arial,sans-serif;line-height:20px;letter-spacing:.3px;-webkit-user-select:none;user-select:none}.bp-TimestampControl-listitem{display:flex;gap:16px;align-items:center;justify-content:space-between;padding:6px 14px;color:#000;font-size:16px;font-family:Lato,Arial,sans-serif;line-height:24px;white-space:nowrap;border-radius:8px;outline:none;cursor:pointer}.bp-TimestampControl-listitem:hover,.bp-TimestampControl-listitem:focus{background:#f4f4f4}.bp-TimestampControl-check{flex:none;fill:#000}
|
|
58
58
|
.bp-VolumeControls{position:relative;display:flex;align-items:center}.bp-VolumeControls-flyout{position:absolute;top:-40px;left:-5px;display:none;flex:0 0 auto;max-width:0;height:40px;overflow:hidden;transition:max-width 200ms ease-in-out}.bp-VolumeControls-flyout.bp-is-open{display:block;overflow:visible}.bp-media--v2 .bp-VolumeControls-flyout{z-index:10}.bp-VolumeControls-slider{width:15px;height:100%;margin-right:6px;margin-left:6px}.bp-VolumeControls-toggle{opacity:.9;transition:opacity 150ms,background-color 150ms;will-change:opacity;outline:0;display:flex;align-items:center;justify-content:center;margin:0;padding:0;color:#fff;background:transparent;border:0;border-radius:25px;cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:manipulation;zoom:1}.bp-VolumeControls-toggle:focus:not(:disabled),.bp-VolumeControls-toggle:hover:not(:disabled){background-color:rgba(255,255,255,.08);opacity:1}.bp-VolumeControls-toggle:focus:not(:disabled) svg,.bp-VolumeControls-toggle:hover:not(:disabled) svg,.bp-VolumeControls-toggle.bp-is-active svg{transform:scale(1.09);transition:transform 150ms}.bp-VolumeControls-toggle.bp-is-active,.bp-VolumeControls-toggle:active:not(:disabled){background-color:rgba(255,255,255,.16)}.bp-VolumeControls-toggle.bp-is-active:focus:not(:disabled),.bp-VolumeControls-toggle.bp-is-active:hover:not(:disabled){background-color:rgba(255,255,255,.2)}.bp-VolumeControls-toggle:focus:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-VolumeControls-toggle:focus-visible:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-VolumeControls-toggle:focus:not(:focus-visible){box-shadow:none}.bp-VolumeControls-toggle:disabled{cursor:default;opacity:.2;pointer-events:none}.bp-VolumeControls-toggle svg{width:32px;height:32px;padding:5px}
|
|
59
59
|
.bp-VolumeSliderControl{outline:0;position:relative;cursor:pointer}.bp-VolumeSliderControl:focus:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-VolumeSliderControl:focus-visible:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-VolumeSliderControl:focus:not(:focus-visible){box-shadow:none}.bp-VolumeVerticalSliderControl{outline:0;position:absolute;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;height:140px;margin:auto;padding:16px;background:rgba(34,34,34,.9);border:1px solid rgba(255,255,255,.13);border-radius:16px;box-shadow:0 2px 5px 0 rgba(0,0,0,.25);cursor:pointer}.bp-VolumeVerticalSliderControl:focus:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-VolumeVerticalSliderControl:focus-visible:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-VolumeVerticalSliderControl:focus:not(:focus-visible){box-shadow:none}.bp-VolumeSliderControl-thumb{position:absolute;top:calc(50% - 6px);width:10px;height:12px;margin-left:-6px;background:#0061d5;border:0;border-radius:12px;cursor:pointer}.bp-VolumeSliderControl-track{position:absolute;inset:0;height:3px;margin:auto 3px;background:transparent linear-gradient(#fff, #fff) no-repeat center border-box}.bp-VolumeVerticalSliderControl-track-container{position:relative;width:8px;height:108px}.bp-VolumeVerticalSliderControl-track-background{position:relative;left:0;width:8px;height:108px;background-color:#55554f;border-radius:20px}.bp-VolumeVerticalSliderControl-track{position:absolute;bottom:0;left:0;width:8px;background:#fff;border-radius:20px}
|
|
60
|
-
.bp-WaveformView{box-sizing:border-box;width:100%;height:206px;min-height:206px;padding:0
|
|
61
|
-
.bp-MP3ControlsRoot:has(.bp-MP3ControlsV2){position:absolute;inset:0;width:100%;height:100%;transform:none;pointer-events:none}.bp-MP3ControlsV2{position:relative;width:100%;max-width:none;height:100%;background:transparent;pointer-events:none}.bp-MP3ControlsV2-stage{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;min-height:0;pointer-events:none}.bp-MP3ControlsV2-stage .bp-WaveformView{pointer-events:auto}.bp-MP3ControlsV2-playOverlay{position:absolute;top:50%;left:50%;z-index:2;display:flex;align-items:center;justify-content:center;width:80px;height:80px;padding:0;background:rgba(0,0,0,.5);border:0;border-radius:50%;transform:translate(-50%, -50%);cursor:pointer;pointer-events:auto}.bp-MP3ControlsV2-playOverlay svg{width:48px;height:48px;fill:#fff}.bp-MP3ControlsV2-stage .bp-media-buffering-spinner{z-index:2;pointer-events:none}.bp-MP3ControlsV2-bar{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;align-items:center;justify-content:space-between;min-height:32px;padding:30px
|
|
60
|
+
.bp-WaveformView{box-sizing:border-box;width:100%;height:206px;min-height:206px;padding:0 60px;overflow:visible;background:none}.bp-WaveformView-track{position:relative;display:flex;align-items:center;width:100%;height:100%;cursor:pointer}.bp-WaveformView-canvas{z-index:0;width:100%;height:140px}.bp-WaveformView-playhead{position:absolute;top:50%;left:0;z-index:1;width:2px;height:150px;pointer-events:none;background:linear-gradient(#2487ff, #006bed);border-radius:1px;box-shadow:0 0 0 1px #222,0 0 10px 0 rgba(36,135,255,.3);transform:translate(-50%, -50%)}.bp-WaveformView-hover{position:absolute;top:100%;z-index:2;margin-top:4px;pointer-events:none;transform:translateX(-50%)}.bp-WaveformView-hoverTime{padding:4px 10px;color:#fff;font-weight:normal;font-size:14px;font-family:Lato,Arial,sans-serif;line-height:20px;letter-spacing:.3px;white-space:nowrap;background:rgba(34,34,34,.92);border:1px solid rgba(255,255,255,.18);border-radius:8px}.bp-WaveformView--inert{pointer-events:none}.bp-WaveformView--inert .bp-WaveformView-track{cursor:default}
|
|
61
|
+
.bp-MP3ControlsRoot:has(.bp-MP3ControlsV2){position:absolute;inset:0;width:100%;height:100%;transform:none;pointer-events:none}.bp-MP3ControlsV2{position:relative;width:100%;max-width:none;height:100%;background:transparent;pointer-events:none}.bp-MP3ControlsV2-stage{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;min-height:0;pointer-events:none}.bp-MP3ControlsV2-stage .bp-WaveformView{pointer-events:auto}.bp-MP3ControlsV2-playOverlay{position:absolute;top:50%;left:50%;z-index:2;display:flex;align-items:center;justify-content:center;width:80px;height:80px;padding:0;background:rgba(0,0,0,.5);border:0;border-radius:50%;transform:translate(-50%, -50%);cursor:pointer;pointer-events:auto}.bp-MP3ControlsV2-playOverlay svg{width:48px;height:48px;fill:#fff}.bp-MP3ControlsV2-stage .bp-media-buffering-spinner{z-index:2;pointer-events:none}.bp-MP3ControlsV2-bar{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;align-items:center;justify-content:space-between;min-height:32px;padding:30px 58px 25px;background:linear-gradient(to top, black, rgba(0, 0, 0, 0));pointer-events:auto}.bp-MP3ControlsV2-group{display:flex;gap:12px;align-items:center}.bp-MP3ControlsV2-divider{width:1px;height:24px;background-color:rgba(255,255,255,.1);border-radius:1px}.bp-MP3ControlsV2 .bp-MP3Controls-settings{position:relative}.bp-MP3ControlsV2 .bp-MP3Controls-settings .bp-SettingsFlyout{right:15px}.bp-MP3ControlsV2 .bp-MP3Controls-settings .bp-SettingsToggle-button{width:36px;height:40px}
|
|
62
62
|
.bp-Filmstrip{position:absolute;bottom:100%;overflow:hidden;background:transparent;box-shadow:0 0 1px #464a4f;visibility:hidden}.bp-Filmstrip.bp-is-shown{visibility:visible}.bp-Filmstrip-frame{display:flex;align-items:center;justify-content:center}.bp-Filmstrip-time{height:20px;color:#fff;font-size:13px;line-height:20px;text-align:center;background-color:#222}
|
|
63
63
|
.bp-SliderControl{outline:0;position:relative;cursor:pointer}.bp-SliderControl:focus:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-SliderControl:focus-visible:not(:disabled){box-shadow:inset 0 0 0 1px rgba(211,211,211,.5),0 1px 2px rgba(0,0,0,.1)}.bp-SliderControl:focus:not(:focus-visible){box-shadow:none}.bp-SliderControl-thumb{position:absolute;top:calc(50% - 6px);width:12px;height:12px;margin-left:-6px;background:#0061d5;border:0;border-radius:12px;cursor:pointer}.bp-SliderControl-track{position:absolute;inset:0;height:3px;margin:auto 3px;background:transparent linear-gradient(#fff, #fff) no-repeat center border-box}
|
|
64
64
|
.bp-TimeControls{position:relative;margin-right:10px;margin-left:10px}.bp-TimeControls-slider{height:18px}.bp-TimeControls-slider .bp-SliderControl-thumb{transform:scale(0);transition:transform 100ms ease;will-change:transform}.bp-TimeControls-slider .bp-SliderControl-track{backface-visibility:hidden;transition:transform 100ms ease;will-change:transform}.bp-TimeControls-slider.bp-is-scrubbing .bp-SliderControl-thumb,.bp-TimeControls-slider:focus .bp-SliderControl-thumb,.bp-TimeControls-slider:hover .bp-SliderControl-thumb{transform:scale(1)}.bp-TimeControls-slider.bp-is-scrubbing .bp-SliderControl-track,.bp-TimeControls-slider:focus .bp-SliderControl-track,.bp-TimeControls-slider:hover .bp-SliderControl-track{transform:scaleY(2)}
|
package/dist/lib/index.js
CHANGED
|
@@ -1009,7 +1009,7 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
1009
1009
|
const CLIENT_NAME = "box-content-preview"; // eslint-disable-line no-undef
|
|
1010
1010
|
const CLIENT_NAME_KEY = 'box_client_name';
|
|
1011
1011
|
const CLIENT_VERSION_KEY = 'box_client_version';
|
|
1012
|
-
const CLIENT_VERSION = "3.
|
|
1012
|
+
const CLIENT_VERSION = "3.83.0"; // eslint-disable-line no-undef
|
|
1013
1013
|
const HEADER_CLIENT_NAME = 'X-Box-Client-Name';
|
|
1014
1014
|
const HEADER_CLIENT_VERSION = 'X-Box-Client-Version';
|
|
1015
1015
|
const PROMISE_MAP = {};
|
|
@@ -4667,6 +4667,731 @@ function isFpsAvailable(player) {
|
|
|
4667
4667
|
|
|
4668
4668
|
/***/ },
|
|
4669
4669
|
|
|
4670
|
+
/***/ 9514
|
|
4671
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
4672
|
+
|
|
4673
|
+
|
|
4674
|
+
// EXPORTS
|
|
4675
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
4676
|
+
decodeToPeaks: () => (/* binding */ decodeToPeaks),
|
|
4677
|
+
extractPeaks: () => (/* binding */ extractPeaks),
|
|
4678
|
+
getDecodeDecision: () => (/* binding */ getDecodeDecision),
|
|
4679
|
+
loadPeaks: () => (/* binding */ loadPeaks),
|
|
4680
|
+
runClientDecode: () => (/* binding */ runClientDecode)
|
|
4681
|
+
});
|
|
4682
|
+
// ESM COMPAT FLAG
|
|
4683
|
+
__webpack_require__.r(__webpack_exports__);
|
|
4684
|
+
|
|
4685
|
+
;// ./src/lib/viewers/media/waveform/constants.ts
|
|
4686
|
+
/** Current waveform payload schema version. Bump only with a migration path. */
|
|
4687
|
+
const WAVEFORM_PAYLOAD_VERSION = 1;
|
|
4688
|
+
|
|
4689
|
+
/** Maximum peaks in a single tier (overview or detail). */
|
|
4690
|
+
const MAX_PEAK_COUNT = 16384;
|
|
4691
|
+
|
|
4692
|
+
/** Hard limit on serialized JSON body size for a waveform payload (bytes). */
|
|
4693
|
+
const MAX_PAYLOAD_BYTES = 512 * 1024;
|
|
4694
|
+
|
|
4695
|
+
/** Allowed delta between payload duration and caller-supplied media duration (seconds). */
|
|
4696
|
+
const DURATION_MISMATCH_TOLERANCE_SEC = 1;
|
|
4697
|
+
|
|
4698
|
+
/** Peak values must live in this closed interval when peakScale is "unit". */
|
|
4699
|
+
const PEAK_UNIT_MIN = 0;
|
|
4700
|
+
const PEAK_UNIT_MAX = 1;
|
|
4701
|
+
|
|
4702
|
+
/** Skip client decode when the compressed file is larger than this. */
|
|
4703
|
+
const CLIENT_DECODE_MAX_COMPRESSED_BYTES = 6 * 1024 * 1024;
|
|
4704
|
+
|
|
4705
|
+
/** Skip client decode when media duration is longer than this. */
|
|
4706
|
+
const CLIENT_DECODE_MAX_DURATION_SEC = 5 * 60;
|
|
4707
|
+
|
|
4708
|
+
/** Default overview resolution for client-generated peaks. */
|
|
4709
|
+
const CLIENT_DECODE_PEAK_COUNT = 16384;
|
|
4710
|
+
;// ./src/lib/viewers/media/waveform/types.ts
|
|
4711
|
+
/**
|
|
4712
|
+
* Box V1 is the in-viewer form: unsigned mono peaks in [0, 1] (peak envelope, mono_max).
|
|
4713
|
+
* Wire JSON only requires version, durationSec, and peaks. Policy fields default.
|
|
4714
|
+
* Conversion or wavesurfer payloads should be adapted at the edge, not stored as-is.
|
|
4715
|
+
*/
|
|
4716
|
+
|
|
4717
|
+
/** Who produced peaks */
|
|
4718
|
+
|
|
4719
|
+
/**
|
|
4720
|
+
* Viewer-normalized waveform JSON (version 1).
|
|
4721
|
+
* Required: version, durationSec, peaks. Everything else is optional and defaulted.
|
|
4722
|
+
*/
|
|
4723
|
+
|
|
4724
|
+
/** Validated, runtime-friendly peak data passed to renderers. */
|
|
4725
|
+
|
|
4726
|
+
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'];
|
|
4727
|
+
const WAVEFORM_ERROR_CODE_SET = new Set(WAVEFORM_ERROR_CODES);
|
|
4728
|
+
function isWaveformErrorCode(value) {
|
|
4729
|
+
return WAVEFORM_ERROR_CODE_SET.has(value);
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
/**
|
|
4733
|
+
* Async boundary for waveform data. Implementations may fetch fixtures, decode client-side,
|
|
4734
|
+
* or load Conversion reps — callers only observe WaveformLoadState.
|
|
4735
|
+
*/
|
|
4736
|
+
;// ./src/lib/viewers/media/waveform/createWaveformLoader.ts
|
|
4737
|
+
/* unused harmony import specifier */ var isRetryableWaveformError;
|
|
4738
|
+
/* unused harmony import specifier */ var validateWaveformPayload;
|
|
4739
|
+
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; }
|
|
4740
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
4741
|
+
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); }
|
|
4742
|
+
|
|
4743
|
+
|
|
4744
|
+
/** Typed throw from fetchPayload (e.g. client decode). `error.name` fallback still works. */
|
|
4745
|
+
class WaveformLoadError extends Error {
|
|
4746
|
+
constructor(code, message) {
|
|
4747
|
+
super(message);
|
|
4748
|
+
_defineProperty(this, "code", void 0);
|
|
4749
|
+
this.name = code;
|
|
4750
|
+
this.code = code;
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
function isAbortError(error) {
|
|
4754
|
+
return typeof DOMException !== 'undefined' && error instanceof DOMException && error.name === 'AbortError' || error instanceof Error && error.name === 'AbortError';
|
|
4755
|
+
}
|
|
4756
|
+
function errorFromUnknown(error, fallbackCode = 'LOAD_FAILED') {
|
|
4757
|
+
if (error instanceof WaveformLoadError) {
|
|
4758
|
+
return {
|
|
4759
|
+
code: error.code,
|
|
4760
|
+
message: error.message
|
|
4761
|
+
};
|
|
4762
|
+
}
|
|
4763
|
+
if (error instanceof SyntaxError) {
|
|
4764
|
+
return {
|
|
4765
|
+
code: 'INVALID_PAYLOAD',
|
|
4766
|
+
message: error.message
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
if (error instanceof Error && isWaveformErrorCode(error.name)) {
|
|
4770
|
+
return {
|
|
4771
|
+
code: error.name,
|
|
4772
|
+
message: error.message
|
|
4773
|
+
};
|
|
4774
|
+
}
|
|
4775
|
+
const message = error instanceof Error ? error.message : 'Waveform load failed';
|
|
4776
|
+
return {
|
|
4777
|
+
code: fallbackCode,
|
|
4778
|
+
message
|
|
4779
|
+
};
|
|
4780
|
+
}
|
|
4781
|
+
function failedLoadState(error) {
|
|
4782
|
+
return {
|
|
4783
|
+
status: 'failed',
|
|
4784
|
+
error,
|
|
4785
|
+
retryable: isRetryableWaveformError(error.code)
|
|
4786
|
+
};
|
|
4787
|
+
}
|
|
4788
|
+
function readyLoadState(payload) {
|
|
4789
|
+
return {
|
|
4790
|
+
status: 'ready',
|
|
4791
|
+
payload
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
/**
|
|
4796
|
+
* Wraps an async payload fetch with abort + stale-result suppression.
|
|
4797
|
+
* Playback must not depend on this completing — callers treat unavailable/failed as degrade paths.
|
|
4798
|
+
* abort() cancels an in-flight load only; a successful ready state is left intact.
|
|
4799
|
+
*/
|
|
4800
|
+
function createWaveformLoader(fetchPayload, options = {}) {
|
|
4801
|
+
let state = {
|
|
4802
|
+
status: 'unavailable'
|
|
4803
|
+
};
|
|
4804
|
+
let generation = 0;
|
|
4805
|
+
let abortController = null;
|
|
4806
|
+
const setState = next => {
|
|
4807
|
+
state = next;
|
|
4808
|
+
};
|
|
4809
|
+
const settle = next => {
|
|
4810
|
+
setState(next);
|
|
4811
|
+
return next;
|
|
4812
|
+
};
|
|
4813
|
+
const startLoad = () => {
|
|
4814
|
+
if (abortController) {
|
|
4815
|
+
abortController.abort();
|
|
4816
|
+
}
|
|
4817
|
+
generation += 1;
|
|
4818
|
+
abortController = new AbortController();
|
|
4819
|
+
setState({
|
|
4820
|
+
status: 'pending'
|
|
4821
|
+
});
|
|
4822
|
+
return {
|
|
4823
|
+
id: generation,
|
|
4824
|
+
signal: abortController.signal
|
|
4825
|
+
};
|
|
4826
|
+
};
|
|
4827
|
+
const cancelledIfStale = request => {
|
|
4828
|
+
if (!request.signal.aborted && generation === request.id) {
|
|
4829
|
+
return null;
|
|
4830
|
+
}
|
|
4831
|
+
if (generation === request.id) {
|
|
4832
|
+
setState({
|
|
4833
|
+
status: 'cancelled'
|
|
4834
|
+
});
|
|
4835
|
+
}
|
|
4836
|
+
return {
|
|
4837
|
+
status: 'cancelled'
|
|
4838
|
+
};
|
|
4839
|
+
};
|
|
4840
|
+
const settleValidation = validation => {
|
|
4841
|
+
if (!validation.ok) {
|
|
4842
|
+
return settle(failedLoadState(validation.error));
|
|
4843
|
+
}
|
|
4844
|
+
return settle(readyLoadState(validation.payload));
|
|
4845
|
+
};
|
|
4846
|
+
const settleFetchError = (error, request) => {
|
|
4847
|
+
if (request.signal.aborted || generation !== request.id || isAbortError(error)) {
|
|
4848
|
+
if (generation === request.id) {
|
|
4849
|
+
return settle({
|
|
4850
|
+
status: 'cancelled'
|
|
4851
|
+
});
|
|
4852
|
+
}
|
|
4853
|
+
return {
|
|
4854
|
+
status: 'cancelled'
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
return settle(failedLoadState(errorFromUnknown(error)));
|
|
4858
|
+
};
|
|
4859
|
+
const load = async () => {
|
|
4860
|
+
const request = startLoad();
|
|
4861
|
+
try {
|
|
4862
|
+
const raw = await fetchPayload(request.signal);
|
|
4863
|
+
const staleAfterFetch = cancelledIfStale(request);
|
|
4864
|
+
if (staleAfterFetch) {
|
|
4865
|
+
return staleAfterFetch;
|
|
4866
|
+
}
|
|
4867
|
+
const validation = validateWaveformPayload(raw, options);
|
|
4868
|
+
const staleAfterValidate = cancelledIfStale(request);
|
|
4869
|
+
if (staleAfterValidate) {
|
|
4870
|
+
return staleAfterValidate;
|
|
4871
|
+
}
|
|
4872
|
+
return settleValidation(validation);
|
|
4873
|
+
} catch (error) {
|
|
4874
|
+
return settleFetchError(error, request);
|
|
4875
|
+
} finally {
|
|
4876
|
+
if (generation === request.id) {
|
|
4877
|
+
abortController = null;
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
};
|
|
4881
|
+
const abort = () => {
|
|
4882
|
+
if (!abortController) {
|
|
4883
|
+
return;
|
|
4884
|
+
}
|
|
4885
|
+
generation += 1;
|
|
4886
|
+
abortController.abort();
|
|
4887
|
+
abortController = null;
|
|
4888
|
+
setState({
|
|
4889
|
+
status: 'cancelled'
|
|
4890
|
+
});
|
|
4891
|
+
};
|
|
4892
|
+
return {
|
|
4893
|
+
load,
|
|
4894
|
+
abort,
|
|
4895
|
+
getState: () => state
|
|
4896
|
+
};
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
/** Returns capped state when media exceeds client decode size or duration limits. */
|
|
4900
|
+
function createCappedWaveformState(message) {
|
|
4901
|
+
return {
|
|
4902
|
+
status: 'capped',
|
|
4903
|
+
error: {
|
|
4904
|
+
code: 'CAP_EXCEEDED',
|
|
4905
|
+
message
|
|
4906
|
+
}
|
|
4907
|
+
};
|
|
4908
|
+
}
|
|
4909
|
+
;// ./src/lib/viewers/media/waveform/validateWaveformPayload.ts
|
|
4910
|
+
|
|
4911
|
+
const DEFAULT_PEAK_SCALE = 'unit';
|
|
4912
|
+
const DEFAULT_CHANNEL_POLICY = 'mono_max';
|
|
4913
|
+
const DEFAULT_ENVELOPE = 'peak';
|
|
4914
|
+
function fail(code, message, retryable = false) {
|
|
4915
|
+
return {
|
|
4916
|
+
ok: false,
|
|
4917
|
+
error: {
|
|
4918
|
+
code,
|
|
4919
|
+
message
|
|
4920
|
+
},
|
|
4921
|
+
retryable
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
function isPlainObject(value) {
|
|
4925
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4926
|
+
}
|
|
4927
|
+
function payloadByteLength(raw) {
|
|
4928
|
+
try {
|
|
4929
|
+
const serialized = JSON.stringify(raw);
|
|
4930
|
+
if (typeof Buffer !== 'undefined') {
|
|
4931
|
+
return Buffer.byteLength(serialized, 'utf8');
|
|
4932
|
+
}
|
|
4933
|
+
if (typeof TextEncoder !== 'undefined') {
|
|
4934
|
+
return new TextEncoder().encode(serialized).length;
|
|
4935
|
+
}
|
|
4936
|
+
return serialized.length;
|
|
4937
|
+
} catch {
|
|
4938
|
+
return Number.MAX_SAFE_INTEGER;
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
function readOptionalPolicy(raw, key, allowed) {
|
|
4942
|
+
if (!(key in raw) || raw[key] === undefined) {
|
|
4943
|
+
return undefined;
|
|
4944
|
+
}
|
|
4945
|
+
return raw[key] === allowed ? allowed : null;
|
|
4946
|
+
}
|
|
4947
|
+
function asWaveformPayloadV1(raw) {
|
|
4948
|
+
const {
|
|
4949
|
+
version,
|
|
4950
|
+
durationSec,
|
|
4951
|
+
peaks
|
|
4952
|
+
} = raw;
|
|
4953
|
+
if (version !== WAVEFORM_PAYLOAD_VERSION || typeof durationSec !== 'number' || !Array.isArray(peaks)) {
|
|
4954
|
+
return null;
|
|
4955
|
+
}
|
|
4956
|
+
const peakScale = readOptionalPolicy(raw, 'peakScale', DEFAULT_PEAK_SCALE);
|
|
4957
|
+
const channelPolicy = readOptionalPolicy(raw, 'channelPolicy', DEFAULT_CHANNEL_POLICY);
|
|
4958
|
+
const envelope = readOptionalPolicy(raw, 'envelope', DEFAULT_ENVELOPE);
|
|
4959
|
+
if (peakScale === null || channelPolicy === null || envelope === null) {
|
|
4960
|
+
return null;
|
|
4961
|
+
}
|
|
4962
|
+
if ('channels' in raw && raw.channels !== undefined && raw.channels !== 1) {
|
|
4963
|
+
return null;
|
|
4964
|
+
}
|
|
4965
|
+
return {
|
|
4966
|
+
version: WAVEFORM_PAYLOAD_VERSION,
|
|
4967
|
+
durationSec,
|
|
4968
|
+
peaks: peaks,
|
|
4969
|
+
peakScale: peakScale ?? DEFAULT_PEAK_SCALE,
|
|
4970
|
+
channelPolicy: channelPolicy ?? DEFAULT_CHANNEL_POLICY,
|
|
4971
|
+
envelope: envelope ?? DEFAULT_ENVELOPE,
|
|
4972
|
+
channels: 1
|
|
4973
|
+
};
|
|
4974
|
+
}
|
|
4975
|
+
function readPayloadObject(raw, maxPayloadBytes, isPayloadByteCheckSkipped = false) {
|
|
4976
|
+
if (raw === null || raw === undefined) {
|
|
4977
|
+
return fail('INVALID_PAYLOAD', 'Payload is null or undefined');
|
|
4978
|
+
}
|
|
4979
|
+
if (!isPayloadByteCheckSkipped) {
|
|
4980
|
+
const byteLength = payloadByteLength(raw);
|
|
4981
|
+
if (byteLength > maxPayloadBytes) {
|
|
4982
|
+
return fail('PAYLOAD_TOO_LARGE', `Payload size ${byteLength} exceeds limit ${maxPayloadBytes}`);
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
if (!isPlainObject(raw)) {
|
|
4986
|
+
return fail('INVALID_PAYLOAD', 'Payload must be an object');
|
|
4987
|
+
}
|
|
4988
|
+
return {
|
|
4989
|
+
ok: true,
|
|
4990
|
+
value: raw
|
|
4991
|
+
};
|
|
4992
|
+
}
|
|
4993
|
+
function checkVersion(raw) {
|
|
4994
|
+
if (raw.version === WAVEFORM_PAYLOAD_VERSION) {
|
|
4995
|
+
return null;
|
|
4996
|
+
}
|
|
4997
|
+
const {
|
|
4998
|
+
version
|
|
4999
|
+
} = raw;
|
|
5000
|
+
if (typeof version === 'number' && version > WAVEFORM_PAYLOAD_VERSION) {
|
|
5001
|
+
return fail('UNSUPPORTED_VERSION', `Unsupported waveform version ${version}; max supported is ${WAVEFORM_PAYLOAD_VERSION}`);
|
|
5002
|
+
}
|
|
5003
|
+
return fail('INVALID_PAYLOAD', 'Missing or invalid version field');
|
|
5004
|
+
}
|
|
5005
|
+
function checkDuration(payload, expectedDurationSec) {
|
|
5006
|
+
if (!Number.isFinite(payload.durationSec) || payload.durationSec <= 0) {
|
|
5007
|
+
return fail('INVALID_DURATION', 'durationSec must be a positive finite number');
|
|
5008
|
+
}
|
|
5009
|
+
if (expectedDurationSec !== undefined && Number.isFinite(expectedDurationSec) && Math.abs(payload.durationSec - expectedDurationSec) > DURATION_MISMATCH_TOLERANCE_SEC) {
|
|
5010
|
+
return fail('DURATION_MISMATCH', `Payload duration ${payload.durationSec}s differs from media duration ${expectedDurationSec}s`, true);
|
|
5011
|
+
}
|
|
5012
|
+
return null;
|
|
5013
|
+
}
|
|
5014
|
+
function normalizePeaks(peaks, maxPeakCount) {
|
|
5015
|
+
if (peaks.length === 0) {
|
|
5016
|
+
return fail('EMPTY_PEAKS', 'peaks array must not be empty');
|
|
5017
|
+
}
|
|
5018
|
+
if (peaks.length > maxPeakCount) {
|
|
5019
|
+
return fail('PEAK_COUNT_EXCEEDED', `peaks length ${peaks.length} exceeds max ${maxPeakCount}`);
|
|
5020
|
+
}
|
|
5021
|
+
const normalized = new Float32Array(peaks.length);
|
|
5022
|
+
for (let i = 0; i < peaks.length; i += 1) {
|
|
5023
|
+
const value = peaks[i];
|
|
5024
|
+
if (!Number.isFinite(value)) {
|
|
5025
|
+
return fail('NON_FINITE_PEAK', `Peak at index ${i} is not finite`);
|
|
5026
|
+
}
|
|
5027
|
+
if (value < PEAK_UNIT_MIN || value > PEAK_UNIT_MAX) {
|
|
5028
|
+
return fail('PEAK_OUT_OF_RANGE', `Peak at index ${i} is outside [${PEAK_UNIT_MIN}, ${PEAK_UNIT_MAX}]`);
|
|
5029
|
+
}
|
|
5030
|
+
normalized[i] = value;
|
|
5031
|
+
}
|
|
5032
|
+
return {
|
|
5033
|
+
ok: true,
|
|
5034
|
+
peaks: normalized
|
|
5035
|
+
};
|
|
5036
|
+
}
|
|
5037
|
+
|
|
5038
|
+
/**
|
|
5039
|
+
* Validates a version-1 waveform payload and returns normalized peaks for rendering.
|
|
5040
|
+
* Wire JSON requires version, durationSec, and peaks. sampleCount and source are ignored.
|
|
5041
|
+
*/
|
|
5042
|
+
function validateWaveformPayload_validateWaveformPayload(raw, options = {}) {
|
|
5043
|
+
const maxPeakCount = options.maxPeakCount ?? MAX_PEAK_COUNT;
|
|
5044
|
+
const maxPayloadBytes = options.maxPayloadBytes ?? MAX_PAYLOAD_BYTES;
|
|
5045
|
+
const objectResult = readPayloadObject(raw, maxPayloadBytes, options.isPayloadByteCheckSkipped === true);
|
|
5046
|
+
if (!objectResult.ok) {
|
|
5047
|
+
return objectResult;
|
|
5048
|
+
}
|
|
5049
|
+
const versionFailure = checkVersion(objectResult.value);
|
|
5050
|
+
if (versionFailure) {
|
|
5051
|
+
return versionFailure;
|
|
5052
|
+
}
|
|
5053
|
+
const payload = asWaveformPayloadV1(objectResult.value);
|
|
5054
|
+
if (!payload) {
|
|
5055
|
+
return fail('INVALID_PAYLOAD', 'Payload failed structural validation');
|
|
5056
|
+
}
|
|
5057
|
+
const durationFailure = checkDuration(payload, options.expectedDurationSec);
|
|
5058
|
+
if (durationFailure) {
|
|
5059
|
+
return durationFailure;
|
|
5060
|
+
}
|
|
5061
|
+
const peaksResult = normalizePeaks(payload.peaks, maxPeakCount);
|
|
5062
|
+
if (!peaksResult.ok) {
|
|
5063
|
+
return peaksResult;
|
|
5064
|
+
}
|
|
5065
|
+
return {
|
|
5066
|
+
ok: true,
|
|
5067
|
+
payload: {
|
|
5068
|
+
version: WAVEFORM_PAYLOAD_VERSION,
|
|
5069
|
+
durationSec: payload.durationSec,
|
|
5070
|
+
peaks: peaksResult.peaks,
|
|
5071
|
+
peakScale: payload.peakScale ?? DEFAULT_PEAK_SCALE,
|
|
5072
|
+
channelPolicy: payload.channelPolicy ?? DEFAULT_CHANNEL_POLICY,
|
|
5073
|
+
envelope: payload.envelope ?? DEFAULT_ENVELOPE
|
|
5074
|
+
}
|
|
5075
|
+
};
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
/** Maps validation / load failures to retry policy for loaders. */
|
|
5079
|
+
function validateWaveformPayload_isRetryableWaveformError(code) {
|
|
5080
|
+
return code === 'DURATION_MISMATCH' || code === 'DECODE_FAILED' || code === 'LOAD_FAILED';
|
|
5081
|
+
}
|
|
5082
|
+
;// ./src/lib/viewers/media/waveform/decode.ts
|
|
5083
|
+
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; }
|
|
5084
|
+
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; }
|
|
5085
|
+
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; }
|
|
5086
|
+
function decode_toPropertyKey(t) { var i = decode_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
5087
|
+
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); }
|
|
5088
|
+
|
|
5089
|
+
|
|
5090
|
+
|
|
5091
|
+
const EMPTY_TIMINGS = {
|
|
5092
|
+
attemptMs: null,
|
|
5093
|
+
extractMs: null
|
|
5094
|
+
};
|
|
5095
|
+
function getCurrentTimeMs() {
|
|
5096
|
+
return typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
5097
|
+
}
|
|
5098
|
+
function createAbortError() {
|
|
5099
|
+
return new DOMException('Aborted', 'AbortError');
|
|
5100
|
+
}
|
|
5101
|
+
function isPositiveFinite(value) {
|
|
5102
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
5103
|
+
}
|
|
5104
|
+
function getAudioContextConstructor() {
|
|
5105
|
+
if (typeof window === 'undefined') {
|
|
5106
|
+
return undefined;
|
|
5107
|
+
}
|
|
5108
|
+
const {
|
|
5109
|
+
AudioContext,
|
|
5110
|
+
webkitAudioContext
|
|
5111
|
+
} = window;
|
|
5112
|
+
return AudioContext || webkitAudioContext;
|
|
5113
|
+
}
|
|
5114
|
+
function getDecodeSkipMessage(reason) {
|
|
5115
|
+
if (reason === 'compressed_size') {
|
|
5116
|
+
return 'Compressed size exceeds client decode limit';
|
|
5117
|
+
}
|
|
5118
|
+
if (reason === 'duration') {
|
|
5119
|
+
return 'Duration exceeds client decode limit';
|
|
5120
|
+
}
|
|
5121
|
+
return 'Compressed size or duration is missing; skipping client decode';
|
|
5122
|
+
}
|
|
5123
|
+
function createSkippedDecodeResult(reason) {
|
|
5124
|
+
if (reason === 'missing_metadata') {
|
|
5125
|
+
return {
|
|
5126
|
+
status: 'unavailable',
|
|
5127
|
+
error: {
|
|
5128
|
+
code: 'UNAVAILABLE',
|
|
5129
|
+
message: getDecodeSkipMessage(reason)
|
|
5130
|
+
},
|
|
5131
|
+
isDecodeSkipped: true,
|
|
5132
|
+
timings: EMPTY_TIMINGS,
|
|
5133
|
+
reason
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
return _objectSpread(_objectSpread({}, createCappedWaveformState(getDecodeSkipMessage(reason))), {}, {
|
|
5137
|
+
isDecodeSkipped: true,
|
|
5138
|
+
timings: EMPTY_TIMINGS,
|
|
5139
|
+
reason
|
|
5140
|
+
});
|
|
5141
|
+
}
|
|
5142
|
+
function decodeWithContext(context, buffer, signal) {
|
|
5143
|
+
return new Promise((resolve, reject) => {
|
|
5144
|
+
let isSettled = false;
|
|
5145
|
+
let onAbort = () => undefined;
|
|
5146
|
+
const onSuccess = decoded => {
|
|
5147
|
+
if (isSettled) {
|
|
5148
|
+
return;
|
|
5149
|
+
}
|
|
5150
|
+
isSettled = true;
|
|
5151
|
+
signal?.removeEventListener('abort', onAbort);
|
|
5152
|
+
resolve(decoded);
|
|
5153
|
+
};
|
|
5154
|
+
const onFailure = error => {
|
|
5155
|
+
if (isSettled) {
|
|
5156
|
+
return;
|
|
5157
|
+
}
|
|
5158
|
+
isSettled = true;
|
|
5159
|
+
signal?.removeEventListener('abort', onAbort);
|
|
5160
|
+
if (isAbortError(error)) {
|
|
5161
|
+
reject(error instanceof DOMException ? error : createAbortError());
|
|
5162
|
+
return;
|
|
5163
|
+
}
|
|
5164
|
+
reject(error instanceof WaveformLoadError ? error : new WaveformLoadError('DECODE_FAILED', 'decodeAudioData failed'));
|
|
5165
|
+
};
|
|
5166
|
+
onAbort = () => onFailure(createAbortError());
|
|
5167
|
+
if (signal?.aborted) {
|
|
5168
|
+
onFailure(createAbortError());
|
|
5169
|
+
return;
|
|
5170
|
+
}
|
|
5171
|
+
signal?.addEventListener('abort', onAbort, {
|
|
5172
|
+
once: true
|
|
5173
|
+
});
|
|
5174
|
+
try {
|
|
5175
|
+
const maybePromise = context.decodeAudioData(buffer, onSuccess, onFailure);
|
|
5176
|
+
if (maybePromise && typeof maybePromise.then === 'function') {
|
|
5177
|
+
maybePromise.then(onSuccess, onFailure);
|
|
5178
|
+
}
|
|
5179
|
+
} catch (error) {
|
|
5180
|
+
onFailure(error);
|
|
5181
|
+
}
|
|
5182
|
+
});
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
/**
|
|
5186
|
+
* Decide whether to run decodeAudioData. Either cap failing, or unknown size/duration,
|
|
5187
|
+
* skips decode so playback is never blocked by expanding the full audio buffer.
|
|
5188
|
+
*/
|
|
5189
|
+
function getDecodeDecision(media, caps = {}) {
|
|
5190
|
+
const maxCompressedBytes = caps.maxCompressedBytes ?? CLIENT_DECODE_MAX_COMPRESSED_BYTES;
|
|
5191
|
+
const maxDurationSec = caps.maxDurationSec ?? CLIENT_DECODE_MAX_DURATION_SEC;
|
|
5192
|
+
const {
|
|
5193
|
+
compressedBytes,
|
|
5194
|
+
durationSec
|
|
5195
|
+
} = media;
|
|
5196
|
+
if (!isPositiveFinite(compressedBytes) || !isPositiveFinite(durationSec)) {
|
|
5197
|
+
return {
|
|
5198
|
+
isAllowed: false,
|
|
5199
|
+
reason: 'missing_metadata'
|
|
5200
|
+
};
|
|
5201
|
+
}
|
|
5202
|
+
if (compressedBytes > maxCompressedBytes) {
|
|
5203
|
+
return {
|
|
5204
|
+
isAllowed: false,
|
|
5205
|
+
reason: 'compressed_size'
|
|
5206
|
+
};
|
|
5207
|
+
}
|
|
5208
|
+
if (durationSec > maxDurationSec) {
|
|
5209
|
+
return {
|
|
5210
|
+
isAllowed: false,
|
|
5211
|
+
reason: 'duration'
|
|
5212
|
+
};
|
|
5213
|
+
}
|
|
5214
|
+
return {
|
|
5215
|
+
isAllowed: true
|
|
5216
|
+
};
|
|
5217
|
+
}
|
|
5218
|
+
|
|
5219
|
+
/**
|
|
5220
|
+
* Collapse audio channels to one unsigned peak per time bucket (max abs, then clamp to unit).
|
|
5221
|
+
* Accepts an AudioBuffer so callers can extract before closing the AudioContext.
|
|
5222
|
+
*/
|
|
5223
|
+
function extractPeaks(audio, peakCount = CLIENT_DECODE_PEAK_COUNT) {
|
|
5224
|
+
const channels = Array.isArray(audio) ? audio : Array.from({
|
|
5225
|
+
length: audio.numberOfChannels
|
|
5226
|
+
}, (_, channelIndex) => audio.getChannelData(channelIndex));
|
|
5227
|
+
if (peakCount <= 0 || channels.length === 0) {
|
|
5228
|
+
return [];
|
|
5229
|
+
}
|
|
5230
|
+
const sampleCount = channels[0].length;
|
|
5231
|
+
if (sampleCount === 0) {
|
|
5232
|
+
return [];
|
|
5233
|
+
}
|
|
5234
|
+
const peaks = new Array(peakCount);
|
|
5235
|
+
const bucketWidth = sampleCount / peakCount;
|
|
5236
|
+
for (let i = 0; i < peakCount; i += 1) {
|
|
5237
|
+
const start = Math.floor(i * bucketWidth);
|
|
5238
|
+
const end = Math.max(start + 1, Math.floor((i + 1) * bucketWidth));
|
|
5239
|
+
let maxAbs = 0;
|
|
5240
|
+
for (let sampleIndex = start; sampleIndex < end && sampleIndex < sampleCount; sampleIndex += 1) {
|
|
5241
|
+
for (let channelIndex = 0; channelIndex < channels.length; channelIndex += 1) {
|
|
5242
|
+
const value = Math.abs(channels[channelIndex][sampleIndex]);
|
|
5243
|
+
if (value > maxAbs) {
|
|
5244
|
+
maxAbs = value;
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
peaks[i] = Math.min(PEAK_UNIT_MAX, Math.max(PEAK_UNIT_MIN, maxAbs));
|
|
5249
|
+
}
|
|
5250
|
+
return peaks;
|
|
5251
|
+
}
|
|
5252
|
+
|
|
5253
|
+
/**
|
|
5254
|
+
* Decode compressed audio and extract unit peaks while the AudioBuffer is live.
|
|
5255
|
+
* Does not attach a media element or fetch a URL. Does not copy channel data.
|
|
5256
|
+
*/
|
|
5257
|
+
async function decodeToPeaks(arrayBuffer, signal, peakCount = CLIENT_DECODE_PEAK_COUNT) {
|
|
5258
|
+
if (signal?.aborted) {
|
|
5259
|
+
throw createAbortError();
|
|
5260
|
+
}
|
|
5261
|
+
const AudioContextConstructor = getAudioContextConstructor();
|
|
5262
|
+
if (!AudioContextConstructor) {
|
|
5263
|
+
throw new WaveformLoadError('DECODE_FAILED', 'AudioContext is not available');
|
|
5264
|
+
}
|
|
5265
|
+
const context = new AudioContextConstructor();
|
|
5266
|
+
try {
|
|
5267
|
+
const audioBuffer = await decodeWithContext(context, arrayBuffer.slice(0), signal);
|
|
5268
|
+
if (signal?.aborted) {
|
|
5269
|
+
throw createAbortError();
|
|
5270
|
+
}
|
|
5271
|
+
const extractStarted = getCurrentTimeMs();
|
|
5272
|
+
const peaks = extractPeaks(audioBuffer, peakCount);
|
|
5273
|
+
return {
|
|
5274
|
+
durationSec: audioBuffer.duration,
|
|
5275
|
+
peaks,
|
|
5276
|
+
extractMs: getCurrentTimeMs() - extractStarted
|
|
5277
|
+
};
|
|
5278
|
+
} catch (error) {
|
|
5279
|
+
if (signal?.aborted || isAbortError(error)) {
|
|
5280
|
+
throw createAbortError();
|
|
5281
|
+
}
|
|
5282
|
+
throw error;
|
|
5283
|
+
} finally {
|
|
5284
|
+
try {
|
|
5285
|
+
await context.close();
|
|
5286
|
+
} catch {
|
|
5287
|
+
// Context may already be closed.
|
|
5288
|
+
}
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
|
|
5292
|
+
/**
|
|
5293
|
+
* Run a client-decode attempt against the V1 waveform contract.
|
|
5294
|
+
* Decode is not invoked when size or duration is over the cap (or unknown).
|
|
5295
|
+
*/
|
|
5296
|
+
async function runClientDecode(options) {
|
|
5297
|
+
const decision = getDecodeDecision({
|
|
5298
|
+
compressedBytes: options.compressedBytes,
|
|
5299
|
+
durationSec: options.durationSec
|
|
5300
|
+
}, options.caps);
|
|
5301
|
+
if (!decision.isAllowed) {
|
|
5302
|
+
return createSkippedDecodeResult(decision.reason);
|
|
5303
|
+
}
|
|
5304
|
+
if (options.signal?.aborted) {
|
|
5305
|
+
return {
|
|
5306
|
+
status: 'cancelled',
|
|
5307
|
+
isDecodeSkipped: true,
|
|
5308
|
+
timings: EMPTY_TIMINGS
|
|
5309
|
+
};
|
|
5310
|
+
}
|
|
5311
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
5312
|
+
const decodeStarted = getCurrentTimeMs();
|
|
5313
|
+
let decodeOutput;
|
|
5314
|
+
try {
|
|
5315
|
+
decodeOutput = await options.decode(signal);
|
|
5316
|
+
} catch (error) {
|
|
5317
|
+
if (signal.aborted || isAbortError(error)) {
|
|
5318
|
+
return {
|
|
5319
|
+
status: 'cancelled',
|
|
5320
|
+
isDecodeSkipped: false,
|
|
5321
|
+
timings: EMPTY_TIMINGS
|
|
5322
|
+
};
|
|
5323
|
+
}
|
|
5324
|
+
const waveformError = errorFromUnknown(error, 'DECODE_FAILED');
|
|
5325
|
+
return {
|
|
5326
|
+
status: 'failed',
|
|
5327
|
+
error: waveformError,
|
|
5328
|
+
retryable: validateWaveformPayload_isRetryableWaveformError(waveformError.code),
|
|
5329
|
+
isDecodeSkipped: false,
|
|
5330
|
+
timings: {
|
|
5331
|
+
attemptMs: getCurrentTimeMs() - decodeStarted,
|
|
5332
|
+
extractMs: null
|
|
5333
|
+
}
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
5336
|
+
const attemptMs = getCurrentTimeMs() - decodeStarted;
|
|
5337
|
+
if (signal.aborted) {
|
|
5338
|
+
return {
|
|
5339
|
+
status: 'cancelled',
|
|
5340
|
+
isDecodeSkipped: false,
|
|
5341
|
+
timings: {
|
|
5342
|
+
attemptMs,
|
|
5343
|
+
extractMs: null
|
|
5344
|
+
}
|
|
5345
|
+
};
|
|
5346
|
+
}
|
|
5347
|
+
const validation = validateWaveformPayload_validateWaveformPayload({
|
|
5348
|
+
version: WAVEFORM_PAYLOAD_VERSION,
|
|
5349
|
+
durationSec: options.durationSec ?? decodeOutput.durationSec,
|
|
5350
|
+
peaks: decodeOutput.peaks
|
|
5351
|
+
}, {
|
|
5352
|
+
isPayloadByteCheckSkipped: true
|
|
5353
|
+
});
|
|
5354
|
+
if (!validation.ok) {
|
|
5355
|
+
return {
|
|
5356
|
+
status: 'failed',
|
|
5357
|
+
error: validation.error,
|
|
5358
|
+
retryable: validateWaveformPayload_isRetryableWaveformError(validation.error.code),
|
|
5359
|
+
isDecodeSkipped: false,
|
|
5360
|
+
timings: {
|
|
5361
|
+
attemptMs,
|
|
5362
|
+
extractMs: decodeOutput.extractMs
|
|
5363
|
+
}
|
|
5364
|
+
};
|
|
5365
|
+
}
|
|
5366
|
+
return {
|
|
5367
|
+
status: 'ready',
|
|
5368
|
+
payload: validation.payload,
|
|
5369
|
+
isDecodeSkipped: false,
|
|
5370
|
+
timings: {
|
|
5371
|
+
attemptMs,
|
|
5372
|
+
extractMs: decodeOutput.extractMs
|
|
5373
|
+
}
|
|
5374
|
+
};
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
/**
|
|
5378
|
+
* Gate, fetch, decode, and extract in-viewer peaks. Skips decode when over cap.
|
|
5379
|
+
* Playback must not await this.
|
|
5380
|
+
*/
|
|
5381
|
+
function loadPeaks(request) {
|
|
5382
|
+
return runClientDecode({
|
|
5383
|
+
compressedBytes: request.compressedBytes,
|
|
5384
|
+
durationSec: request.durationSec,
|
|
5385
|
+
signal: request.signal,
|
|
5386
|
+
decode: async signal => {
|
|
5387
|
+
const arrayBuffer = await request.fetchArrayBuffer(signal);
|
|
5388
|
+
return decodeToPeaks(arrayBuffer, signal);
|
|
5389
|
+
}
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
/***/ },
|
|
5394
|
+
|
|
4670
5395
|
/***/ 2485
|
|
4671
5396
|
(module, exports) {
|
|
4672
5397
|
|
|
@@ -20330,7 +21055,7 @@ class Browser {
|
|
|
20330
21055
|
;// ./src/lib/Logger.js
|
|
20331
21056
|
/* eslint-disable no-undef */
|
|
20332
21057
|
const CLIENT_NAME = "box-content-preview";
|
|
20333
|
-
const CLIENT_VERSION = "3.
|
|
21058
|
+
const CLIENT_VERSION = "3.83.0";
|
|
20334
21059
|
/* eslint-enable no-undef */
|
|
20335
21060
|
|
|
20336
21061
|
class Logger {
|
|
@@ -33172,6 +33897,11 @@ function MP3Viewer_toPrimitive(t, r) { if ("object" != typeof t || !t) return t;
|
|
|
33172
33897
|
|
|
33173
33898
|
|
|
33174
33899
|
const CSS_CLASS_MP3 = 'bp-media-mp3';
|
|
33900
|
+
function createLoadFailedError(message) {
|
|
33901
|
+
const error = new Error(message);
|
|
33902
|
+
error.name = 'LOAD_FAILED';
|
|
33903
|
+
return error;
|
|
33904
|
+
}
|
|
33175
33905
|
class MP3Viewer extends media_MediaBaseViewer {
|
|
33176
33906
|
constructor(...args) {
|
|
33177
33907
|
super(...args);
|
|
@@ -33181,6 +33911,11 @@ class MP3Viewer extends media_MediaBaseViewer {
|
|
|
33181
33911
|
MP3Viewer_defineProperty(this, "handlePlayRequest", () => {
|
|
33182
33912
|
this.userRequestedPlay = true;
|
|
33183
33913
|
this.togglePlay();
|
|
33914
|
+
if (this.isWaveformDecodeRetryPending && !this.hasUsedWaveformDecodePlayRetry) {
|
|
33915
|
+
this.hasUsedWaveformDecodePlayRetry = true;
|
|
33916
|
+
this.isWaveformDecodeRetryPending = false;
|
|
33917
|
+
this.startClientWaveformDecode();
|
|
33918
|
+
}
|
|
33184
33919
|
});
|
|
33185
33920
|
/**
|
|
33186
33921
|
* Auto-play was prevented, pause the audio
|
|
@@ -33210,6 +33945,7 @@ class MP3Viewer extends media_MediaBaseViewer {
|
|
|
33210
33945
|
this.wrapperEl.classList.add('bp-media--v2');
|
|
33211
33946
|
this.mediaContainerEl.classList.add('bp-media-container--v2');
|
|
33212
33947
|
this.ensureV2Controls();
|
|
33948
|
+
this.importWaveformDecode();
|
|
33213
33949
|
}
|
|
33214
33950
|
|
|
33215
33951
|
// Audio element
|
|
@@ -33271,6 +34007,29 @@ class MP3Viewer extends media_MediaBaseViewer {
|
|
|
33271
34007
|
return Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 7893));
|
|
33272
34008
|
}
|
|
33273
34009
|
|
|
34010
|
+
/**
|
|
34011
|
+
* @return {Promise<{ loadPeaks: Function }>} client-decode helpers
|
|
34012
|
+
*/
|
|
34013
|
+
async importWaveformDecode() {
|
|
34014
|
+
if (!this.waveformDecodeImport) {
|
|
34015
|
+
this.waveformDecodeImport = Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 9514));
|
|
34016
|
+
}
|
|
34017
|
+
try {
|
|
34018
|
+
return await this.waveformDecodeImport;
|
|
34019
|
+
} catch (error) {
|
|
34020
|
+
this.waveformDecodeImport = null;
|
|
34021
|
+
throw error;
|
|
34022
|
+
}
|
|
34023
|
+
}
|
|
34024
|
+
|
|
34025
|
+
/**
|
|
34026
|
+
* @inheritdoc
|
|
34027
|
+
*/
|
|
34028
|
+
destroy() {
|
|
34029
|
+
this.abortClientWaveformDecode();
|
|
34030
|
+
super.destroy();
|
|
34031
|
+
}
|
|
34032
|
+
|
|
33274
34033
|
/**
|
|
33275
34034
|
* @inheritdoc
|
|
33276
34035
|
*/
|
|
@@ -33315,10 +34074,126 @@ class MP3Viewer extends media_MediaBaseViewer {
|
|
|
33315
34074
|
*/
|
|
33316
34075
|
loadeddataHandler() {
|
|
33317
34076
|
super.loadeddataHandler();
|
|
33318
|
-
if (this.isAudioPlayerV2
|
|
34077
|
+
if (!this.isAudioPlayerV2) {
|
|
34078
|
+
return;
|
|
34079
|
+
}
|
|
34080
|
+
|
|
34081
|
+
// Play first so Safari has a user gesture before AudioContext is created.
|
|
34082
|
+
if (this.userRequestedPlay) {
|
|
33319
34083
|
this.play();
|
|
33320
34084
|
}
|
|
34085
|
+
this.startClientWaveformDecode();
|
|
33321
34086
|
}
|
|
34087
|
+
/**
|
|
34088
|
+
* Fetch compressed audio bytes for client decode. Prefers an already-fetched blob URL.
|
|
34089
|
+
*
|
|
34090
|
+
* @param {AbortSignal} signal
|
|
34091
|
+
* @return {Promise<ArrayBuffer>}
|
|
34092
|
+
*/
|
|
34093
|
+
async fetchAudioArrayBuffer(signal) {
|
|
34094
|
+
if (this.mediaBlobUrl) {
|
|
34095
|
+
const response = await fetch(this.mediaBlobUrl, {
|
|
34096
|
+
signal
|
|
34097
|
+
});
|
|
34098
|
+
if (!response.ok) {
|
|
34099
|
+
throw createLoadFailedError(`Waveform fetch failed (${response.status})`);
|
|
34100
|
+
}
|
|
34101
|
+
return response.arrayBuffer();
|
|
34102
|
+
}
|
|
34103
|
+
const template = this.options.representation && this.options.representation.content && this.options.representation.content.url_template;
|
|
34104
|
+
if (!template) {
|
|
34105
|
+
throw createLoadFailedError('Waveform fetch URL is missing');
|
|
34106
|
+
}
|
|
34107
|
+
const data = await this.api.get(this.createContentUrlV2(template), {
|
|
34108
|
+
headers: this.appendAuthHeader(),
|
|
34109
|
+
signal,
|
|
34110
|
+
type: 'arraybuffer'
|
|
34111
|
+
});
|
|
34112
|
+
if (data instanceof ArrayBuffer) {
|
|
34113
|
+
return data;
|
|
34114
|
+
}
|
|
34115
|
+
throw createLoadFailedError('Waveform fetch did not return binary data');
|
|
34116
|
+
}
|
|
34117
|
+
abortClientWaveformDecode() {
|
|
34118
|
+
if (!this.waveformDecodeController) {
|
|
34119
|
+
return;
|
|
34120
|
+
}
|
|
34121
|
+
this.waveformDecodeController.abort();
|
|
34122
|
+
this.waveformDecodeController = null;
|
|
34123
|
+
}
|
|
34124
|
+
|
|
34125
|
+
/**
|
|
34126
|
+
* Decode peaks in the background when the file is under size and duration caps.
|
|
34127
|
+
* Does not block playback. Capped or failed decode keeps the placeholder waveform.
|
|
34128
|
+
*
|
|
34129
|
+
* @return {Promise<void>}
|
|
34130
|
+
*/
|
|
34131
|
+
async startClientWaveformDecode() {
|
|
34132
|
+
if (!this.isAudioPlayerV2 || this.destroyed) {
|
|
34133
|
+
return;
|
|
34134
|
+
}
|
|
34135
|
+
this.abortClientWaveformDecode();
|
|
34136
|
+
const controller = new AbortController();
|
|
34137
|
+
this.waveformDecodeController = controller;
|
|
34138
|
+
const {
|
|
34139
|
+
signal
|
|
34140
|
+
} = controller;
|
|
34141
|
+
try {
|
|
34142
|
+
const decodeModule = await this.importWaveformDecode();
|
|
34143
|
+
if (this.destroyed || signal.aborted || this.waveformDecodeController !== controller) {
|
|
34144
|
+
return;
|
|
34145
|
+
}
|
|
34146
|
+
const result = await decodeModule.loadPeaks({
|
|
34147
|
+
compressedBytes: this.options.file && this.options.file.size,
|
|
34148
|
+
durationSec: this.mediaEl && this.mediaEl.duration,
|
|
34149
|
+
fetchArrayBuffer: fetchSignal => this.fetchAudioArrayBuffer(fetchSignal),
|
|
34150
|
+
signal
|
|
34151
|
+
});
|
|
34152
|
+
this.handleClientWaveformDecodeResult(result, controller, signal);
|
|
34153
|
+
} catch (error) {
|
|
34154
|
+
if (error && error.name === 'AbortError') {
|
|
34155
|
+
this.handleClientWaveformDecodeResult({
|
|
34156
|
+
status: 'cancelled'
|
|
34157
|
+
}, controller, signal);
|
|
34158
|
+
return;
|
|
34159
|
+
}
|
|
34160
|
+
this.handleClientWaveformDecodeResult({
|
|
34161
|
+
error: {
|
|
34162
|
+
code: 'LOAD_FAILED',
|
|
34163
|
+
message: error instanceof Error ? error.message : 'Waveform decode failed'
|
|
34164
|
+
},
|
|
34165
|
+
retryable: true,
|
|
34166
|
+
status: 'failed'
|
|
34167
|
+
}, controller, signal);
|
|
34168
|
+
}
|
|
34169
|
+
}
|
|
34170
|
+
|
|
34171
|
+
/**
|
|
34172
|
+
* Apply peaks, or record a retryable failure for overlay play.
|
|
34173
|
+
*
|
|
34174
|
+
* @param {import('./waveform/types').ClientDecodeResult} result
|
|
34175
|
+
* @param {AbortController} controller in-flight controller for this attempt
|
|
34176
|
+
* @param {AbortSignal} signal
|
|
34177
|
+
* @return {void}
|
|
34178
|
+
*/
|
|
34179
|
+
handleClientWaveformDecodeResult(result, controller, signal) {
|
|
34180
|
+
if (!result || this.destroyed || signal.aborted || this.waveformDecodeController !== controller) {
|
|
34181
|
+
return;
|
|
34182
|
+
}
|
|
34183
|
+
if (result.status === 'ready') {
|
|
34184
|
+
this.waveformPeaks = result.payload.peaks;
|
|
34185
|
+
this.isWaveformDecodeRetryPending = false;
|
|
34186
|
+
this.renderUI();
|
|
34187
|
+
return;
|
|
34188
|
+
}
|
|
34189
|
+
if (result.status === 'cancelled') {
|
|
34190
|
+
return;
|
|
34191
|
+
}
|
|
34192
|
+
if (result.status === 'failed' && result.retryable && !this.hasUsedWaveformDecodePlayRetry) {
|
|
34193
|
+
this.isWaveformDecodeRetryPending = true;
|
|
34194
|
+
}
|
|
34195
|
+
}
|
|
34196
|
+
|
|
33322
34197
|
/**
|
|
33323
34198
|
* @inheritdoc
|
|
33324
34199
|
*/
|