nuxt-media-library 0.0.61 → 0.0.63
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 +77 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -1
- package/dist/runtime/components/MLPreviewProvider.vue +2 -0
- package/dist/runtime/composables/useMLAssetPreview.d.ts +1 -0
- package/dist/runtime/composables/useMLAssetPreview.js +2 -0
- package/dist/runtime/composables/useMLMediaRenewal.d.ts +28 -0
- package/dist/runtime/composables/useMLMediaRenewal.js +38 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/plugins/asset-direcetive-client.js +2 -0
- package/dist/runtime/types/embeddedMedia.d.ts +25 -0
- package/dist/runtime/types/embeddedMedia.js +0 -0
- package/dist/runtime/utils/deprecation.d.ts +2 -0
- package/dist/runtime/utils/deprecation.js +7 -0
- package/dist/runtime/utils/embeddedMedia.d.ts +20 -0
- package/dist/runtime/utils/embeddedMedia.js +15 -0
- package/dist/runtime/utils/streamUrls.d.ts +7 -0
- package/dist/runtime/utils/streamUrls.js +6 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -188,8 +188,85 @@ export default createMLCatchAllProxy({
|
|
|
188
188
|
and, when `serviceUploads.proxy` is on, the service-upload pass-through.
|
|
189
189
|
Your catch-all handles everything else under `/api/media-library`.
|
|
190
190
|
|
|
191
|
+
### Showing media on public pages (embedded media)
|
|
192
|
+
|
|
193
|
+
Public pages never resolve an asset id. Each service puts the media into its
|
|
194
|
+
own response (Python: `pkg_media`'s `MediaEmbedder`, or `StrawberryMedia` for
|
|
195
|
+
GraphQL), having decided who may see what. The page renders the fields it gets:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
interface EmbeddedMedia { // from the service's GraphQL schema (codegen)
|
|
199
|
+
assetId: string
|
|
200
|
+
kind: 'image' | 'video' | 'audio' | 'document' | 'other'
|
|
201
|
+
url: string | null // the file; null when denied or not ready
|
|
202
|
+
poster: string | null // the one image to show; set even when denied
|
|
203
|
+
sources: { url: string, width: number, height?: number | null }[] // poster sizes
|
|
204
|
+
streams: { hls?: string | null, dash?: string | null } | null
|
|
205
|
+
width?: number | null; height?: number | null; alt?: string | null
|
|
206
|
+
expiresIn: number | null // seconds until the first link expires; null = never
|
|
207
|
+
public: boolean // every link tokenless (cache-safe)
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The module auto-imports the helpers that read it (a structural type,
|
|
212
|
+
`MLEmbeddedMedia`, so any codegen type fits):
|
|
213
|
+
|
|
214
|
+
| Helper | Gives |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `mlMediaPoster(media, fallback?)` | the image to show, else `fallback` |
|
|
217
|
+
| `mlMediaSrcset(media)` | `srcset` from `media.sources`, or `undefined` |
|
|
218
|
+
| `mlPlayableSource(media, { preferFile? })` | `{ src, type: 'hls' \| 'progressive' }`: the HLS ladder, else the file; `null` when locked or not ready |
|
|
219
|
+
| `mlIsPlayableVideo(media)` | a video the viewer can play now |
|
|
220
|
+
| `useMLMediaRenewal(media, renew, { marginSeconds?, retrySeconds? })` | calls `renew` 5 minutes (default) before tokened links expire, and again every 30 s (default) while it fails; browser only |
|
|
221
|
+
|
|
222
|
+
```vue
|
|
223
|
+
<script setup lang="ts">
|
|
224
|
+
const { data: item } = await useAsyncData('article', fetchArticle)
|
|
225
|
+
// Re-fetch before the links expire and swap in only `media`.
|
|
226
|
+
useMLMediaRenewal(() => item.value?.media, async () => {
|
|
227
|
+
const fresh = await fetchArticle()
|
|
228
|
+
if (item.value && fresh?.media) item.value = { ...item.value, media: fresh.media }
|
|
229
|
+
})
|
|
230
|
+
const source = computed(() => mlPlayableSource(item.value?.media))
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<template>
|
|
234
|
+
<!-- `sizes` is the box's widest rendered width. -->
|
|
235
|
+
<img
|
|
236
|
+
:src="mlMediaPoster(item?.media, '/placeholder.jpg')"
|
|
237
|
+
:srcset="mlMediaSrcset(item?.media)"
|
|
238
|
+
sizes="(min-width: 1024px) 420px, 100vw"
|
|
239
|
+
alt=""
|
|
240
|
+
loading="lazy"
|
|
241
|
+
>
|
|
242
|
+
<!-- A video: the app's own player (hls.js for type 'hls'). -->
|
|
243
|
+
<AppVideoPlayer v-if="source" :src="source.src" :type="source.type" :poster="item?.media?.poster" />
|
|
244
|
+
</template>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- **Locked or missing:** `url` and `streams` are null; show `poster` (and a
|
|
248
|
+
paywall). No call to the media library can do better.
|
|
249
|
+
- **Renewal:** tokened links expire (`expiresIn`). `useMLMediaRenewal` fetches the
|
|
250
|
+
record again before then; swap in only the new `media` and resume the player at
|
|
251
|
+
its playhead (key the player on its source).
|
|
252
|
+
- **SEO:** pass `media.poster` (and the service's embedded share image) to your
|
|
253
|
+
SEO helper as URLs; the HTML rendered on the server then carries them.
|
|
254
|
+
- **Caching:** a response with a tokened link is per user (`public: false`);
|
|
255
|
+
don't cache it at the edge.
|
|
256
|
+
- `useMLAssetPreview`, `<MLPreviewProvider>` and `v-asset` resolve ids in the
|
|
257
|
+
browser through `/delivery`. They are **deprecated** (a warning in dev) and go
|
|
258
|
+
with `/delivery`; so do read grants (below).
|
|
259
|
+
|
|
260
|
+
Design and rulings: [`docs/design/embedded-media.md`](docs/design/embedded-media.md).
|
|
261
|
+
Moving a service and its layer over, step by step:
|
|
262
|
+
[`docs/design/embedded-media-migration-guide.md`](docs/design/embedded-media-migration-guide.md).
|
|
263
|
+
|
|
191
264
|
### Showing assets of several services: read grants
|
|
192
265
|
|
|
266
|
+
> **Deprecated** with `/delivery`: public pages show embedded media instead
|
|
267
|
+
> (see above), and no grant is needed.
|
|
268
|
+
|
|
269
|
+
|
|
193
270
|
Every link comes from the media library; each service decides only for its
|
|
194
271
|
own assets by returning a read grant (`mlg_…`, e.g. the Python client's
|
|
195
272
|
`client.grants.read(ids)`) with the data that references them. Hand the
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { defineNuxtModule, createResolver, addImportsDir, addComponentsDir, addServerHandler, useLogger, addPlugin } from '@nuxt/kit';
|
|
3
|
+
import { defineNuxtModule, createResolver, addImportsDir, addImports, addComponentsDir, addServerHandler, useLogger, addPlugin } from '@nuxt/kit';
|
|
4
4
|
import { browserHeaders } from '../dist/runtime/utils/credentialHeaders.js';
|
|
5
5
|
import { normalizeBackends } from '../dist/runtime/utils/backends.js';
|
|
6
6
|
export * from '../dist/runtime/types/resourcePolicy.js';
|
|
@@ -53,6 +53,10 @@ const module$1 = defineNuxtModule({
|
|
|
53
53
|
const resolver = createResolver(import.meta.url);
|
|
54
54
|
const runtimeDir = resolver.resolve("./runtime");
|
|
55
55
|
addImportsDir(resolver.resolve("runtime/composables"));
|
|
56
|
+
addImports(["mlMediaPoster", "mlMediaSrcset", "mlPlayableSource", "mlIsPlayableVideo"].map((name) => ({
|
|
57
|
+
name,
|
|
58
|
+
from: resolver.resolve("runtime/utils/embeddedMedia")
|
|
59
|
+
})));
|
|
56
60
|
nuxt.options.build.transpile ||= [];
|
|
57
61
|
nuxt.options.build.transpile.push(runtimeDir);
|
|
58
62
|
const existingPrivate = nuxt.options.runtimeConfig.mediaLibrary || {};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, watch } from "vue";
|
|
3
3
|
import { useMLAssetPreview } from "../composables/useMLAssetPreview";
|
|
4
|
+
import { warnDeprecated } from "../utils/deprecation";
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
assetId: { type: [String, null], required: false },
|
|
6
7
|
fallbackSrc: { type: [String, null], required: false },
|
|
7
8
|
autoRegister: { type: Boolean, required: false, default: true }
|
|
8
9
|
});
|
|
10
|
+
warnDeprecated("<MLPreviewProvider>");
|
|
9
11
|
const assetsStore = useMLAssetPreview();
|
|
10
12
|
const asset = computed(
|
|
11
13
|
() => props.assetId ? assetsStore.getAsset(props.assetId) : void 0
|
|
@@ -6,6 +6,7 @@ declare function markBroken(id?: string | null): void;
|
|
|
6
6
|
declare function clearBroken(id?: string | null): void;
|
|
7
7
|
declare function ensureAssets(ids: string[]): Promise<void>;
|
|
8
8
|
declare function register(id?: string | null): void;
|
|
9
|
+
/** @deprecated Show the service's embedded media instead (see `warnDeprecated`). */
|
|
9
10
|
export declare const useMLAssetPreview: () => {
|
|
10
11
|
assets: import("vue").Ref<Record<string, AssetPreview>, Record<string, AssetPreview>>;
|
|
11
12
|
pending: import("vue").Ref<Set<string> & Omit<Set<string>, keyof Set<any>>, Set<string> | (Set<string> & Omit<Set<string>, keyof Set<any>>)>;
|
|
@@ -3,6 +3,7 @@ import { $fetch } from "ofetch";
|
|
|
3
3
|
import { mediaGrantHeaders } from "./useMLMediaGrants.js";
|
|
4
4
|
import { onMediaGrantsAdded } from "../utils/grants/listeners.js";
|
|
5
5
|
import { streamUrlsStale } from "../utils/streamUrls.js";
|
|
6
|
+
import { warnDeprecated } from "../utils/deprecation.js";
|
|
6
7
|
const assets = ref({});
|
|
7
8
|
const pending = ref(/* @__PURE__ */ new Set());
|
|
8
9
|
const broken = ref(/* @__PURE__ */ new Set());
|
|
@@ -159,6 +160,7 @@ onMediaGrantsAdded(() => {
|
|
|
159
160
|
if (import.meta.client) scheduleFetch();
|
|
160
161
|
});
|
|
161
162
|
export const useMLAssetPreview = () => {
|
|
163
|
+
warnDeprecated("useMLAssetPreview()");
|
|
162
164
|
return {
|
|
163
165
|
assets,
|
|
164
166
|
pending,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { MLEmbeddedMedia } from '../types/embeddedMedia.js';
|
|
3
|
+
export interface UseMLMediaRenewalOptions {
|
|
4
|
+
/** Seconds before expiry to renew. Default 300. */
|
|
5
|
+
marginSeconds?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Seconds to wait before trying again when `renew` rejects (a transient
|
|
8
|
+
* network error must not let the links expire mid-playback). Default 30.
|
|
9
|
+
*/
|
|
10
|
+
retrySeconds?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The renewal loop without the client guard: watches `media` and calls
|
|
14
|
+
* `renew` shortly before its links expire. Returns a stop function. Prefer
|
|
15
|
+
* `useMLMediaRenewal`, which runs this in the browser only.
|
|
16
|
+
*/
|
|
17
|
+
export declare function watchMLMediaRenewal(media: MaybeRefOrGetter<MLEmbeddedMedia | null | undefined>, renew: () => Promise<unknown>, { marginSeconds, retrySeconds }?: UseMLMediaRenewalOptions): () => void;
|
|
18
|
+
/**
|
|
19
|
+
* Keeps embedded media links alive: tokened links expire (`expiresIn`), so
|
|
20
|
+
* shortly before they do this calls `renew`, which re-fetches the record and
|
|
21
|
+
* swaps in its fresh `media`. Tokenless media (`expiresIn: null`) is left
|
|
22
|
+
* alone. Browser only; stops with the calling scope.
|
|
23
|
+
*
|
|
24
|
+
* Swap only `media` in `renew` rather than refreshing the whole page's data,
|
|
25
|
+
* so the page does not fall back to its loading state mid-playback; a player
|
|
26
|
+
* keyed on its source resumes at its playhead.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useMLMediaRenewal(media: MaybeRefOrGetter<MLEmbeddedMedia | null | undefined>, renew: () => Promise<unknown>, options?: UseMLMediaRenewalOptions): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { onScopeDispose, toValue, watch } from "vue";
|
|
2
|
+
import { refreshDelay } from "../utils/streamUrls.js";
|
|
3
|
+
const DEFAULT_MARGIN_SECONDS = 300;
|
|
4
|
+
const DEFAULT_RETRY_SECONDS = 30;
|
|
5
|
+
export function watchMLMediaRenewal(media, renew, { marginSeconds = DEFAULT_MARGIN_SECONDS, retrySeconds = DEFAULT_RETRY_SECONDS } = {}) {
|
|
6
|
+
let timer = null;
|
|
7
|
+
let generation = 0;
|
|
8
|
+
const clear = () => {
|
|
9
|
+
generation++;
|
|
10
|
+
if (timer) clearTimeout(timer);
|
|
11
|
+
timer = null;
|
|
12
|
+
};
|
|
13
|
+
const arm = (delay) => {
|
|
14
|
+
const current = generation;
|
|
15
|
+
timer = setTimeout(() => {
|
|
16
|
+
timer = null;
|
|
17
|
+
renew().catch((err) => {
|
|
18
|
+
console.warn("[nuxt-media-library] media renewal failed, retrying:", err);
|
|
19
|
+
if (current === generation) arm(retrySeconds * 1e3);
|
|
20
|
+
});
|
|
21
|
+
}, delay);
|
|
22
|
+
};
|
|
23
|
+
const stopWatch = watch(() => toValue(media), (current) => {
|
|
24
|
+
clear();
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const delay = refreshDelay(current?.expiresIn, now, now, marginSeconds);
|
|
27
|
+
if (delay == null) return;
|
|
28
|
+
arm(delay);
|
|
29
|
+
}, { immediate: true });
|
|
30
|
+
return () => {
|
|
31
|
+
stopWatch();
|
|
32
|
+
clear();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function useMLMediaRenewal(media, renew, options = {}) {
|
|
36
|
+
if (!import.meta.client) return;
|
|
37
|
+
onScopeDispose(watchMLMediaRenewal(media, renew, options));
|
|
38
|
+
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ export * from './utils/assetFormat.js';
|
|
|
25
25
|
export * from './utils/fileAccept.js';
|
|
26
26
|
export * from './utils/processingSteps.js';
|
|
27
27
|
export * from './utils/streamUrls.js';
|
|
28
|
+
export * from './utils/embeddedMedia.js';
|
|
29
|
+
export * from './types/embeddedMedia.js';
|
|
30
|
+
export * from './composables/useMLMediaRenewal.js';
|
|
28
31
|
export * from './utils/webAppDeploys.js';
|
|
29
32
|
export * from './composables/useMLWebAppDeploys.js';
|
|
30
33
|
export * from './composables/useMLMediaMeta.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -25,6 +25,9 @@ export * from "./utils/assetFormat.js";
|
|
|
25
25
|
export * from "./utils/fileAccept.js";
|
|
26
26
|
export * from "./utils/processingSteps.js";
|
|
27
27
|
export * from "./utils/streamUrls.js";
|
|
28
|
+
export * from "./utils/embeddedMedia.js";
|
|
29
|
+
export * from "./types/embeddedMedia.js";
|
|
30
|
+
export * from "./composables/useMLMediaRenewal.js";
|
|
28
31
|
export * from "./utils/webAppDeploys.js";
|
|
29
32
|
export * from "./composables/useMLWebAppDeploys.js";
|
|
30
33
|
export * from "./composables/useMLMediaMeta.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineNuxtPlugin } from "#app";
|
|
2
2
|
import { watch } from "vue";
|
|
3
3
|
import { useMLAssetPreview } from "../composables/useMLAssetPreview.js";
|
|
4
|
+
import { warnDeprecated } from "../utils/deprecation.js";
|
|
4
5
|
function applyAssetToElement(el, asset, arg) {
|
|
5
6
|
if (!asset || !asset.url) return;
|
|
6
7
|
if (arg === "bg") {
|
|
@@ -33,6 +34,7 @@ function setupWatcher(el, id, arg) {
|
|
|
33
34
|
}
|
|
34
35
|
const assetDirective = {
|
|
35
36
|
mounted(el, binding) {
|
|
37
|
+
warnDeprecated("v-asset");
|
|
36
38
|
if (import.meta.server) return;
|
|
37
39
|
const id = binding.value || "";
|
|
38
40
|
const arg = binding.arg;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface MLEmbeddedMediaSource {
|
|
2
|
+
url: string;
|
|
3
|
+
width: number;
|
|
4
|
+
height?: number | null;
|
|
5
|
+
}
|
|
6
|
+
export interface MLEmbeddedMedia {
|
|
7
|
+
kind?: string | null;
|
|
8
|
+
/** The file; null when the viewer may not open it or it is not ready. */
|
|
9
|
+
url?: string | null;
|
|
10
|
+
/** The one image to show; set even for a viewer who may not open the file. */
|
|
11
|
+
poster?: string | null;
|
|
12
|
+
/** The poster's sizes, smallest first. */
|
|
13
|
+
sources?: readonly MLEmbeddedMediaSource[] | null;
|
|
14
|
+
streams?: {
|
|
15
|
+
hls?: string | null;
|
|
16
|
+
dash?: string | null;
|
|
17
|
+
} | null;
|
|
18
|
+
/** Seconds until the first carried link expires; null: none expires. */
|
|
19
|
+
expiresIn?: number | null;
|
|
20
|
+
}
|
|
21
|
+
/** What a player plays: the HLS ladder (adaptive), else the file. */
|
|
22
|
+
export interface MLPlayableSource {
|
|
23
|
+
src: string;
|
|
24
|
+
type: 'hls' | 'progressive';
|
|
25
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const warned = /* @__PURE__ */ new Set();
|
|
2
|
+
export const EMBEDDED_MEDIA_HINT = "Show the media the owning service embeds in its response instead (`media.poster`, `media.sources`, `media.streams.hls`, `media.url`); the browser should not resolve asset ids. See nuxt-media-library docs/design/embedded-media-migration-guide.md.";
|
|
3
|
+
export function warnDeprecated(name, hint = EMBEDDED_MEDIA_HINT) {
|
|
4
|
+
if (!import.meta.dev || warned.has(name)) return;
|
|
5
|
+
warned.add(name);
|
|
6
|
+
console.warn(`[nuxt-media-library] ${name} is deprecated and goes with the media library's /delivery. ${hint}`);
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MLEmbeddedMedia, MLPlayableSource } from '../types/embeddedMedia.js';
|
|
2
|
+
/** The image to show, or `fallback` when the media has none. */
|
|
3
|
+
export declare function mlMediaPoster<F extends string | null | undefined = undefined>(media: MLEmbeddedMedia | null | undefined, fallback?: F): string | F;
|
|
4
|
+
/**
|
|
5
|
+
* `srcset` for the poster `<img>`: its sizes from `media.sources`, so the
|
|
6
|
+
* browser loads the size the box needs (pair it with a `sizes` that is an
|
|
7
|
+
* upper bound of the rendered width). Undefined when there are none: `src`
|
|
8
|
+
* is then the only size.
|
|
9
|
+
*/
|
|
10
|
+
export declare function mlMediaSrcset(media: MLEmbeddedMedia | null | undefined): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* What a player should play: the HLS ladder, else the file. `preferFile`
|
|
13
|
+
* skips the ladder (a player falling back after the stream failed). Null when
|
|
14
|
+
* the viewer may not play it or it is not ready.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mlPlayableSource(media: MLEmbeddedMedia | null | undefined, { preferFile }?: {
|
|
17
|
+
preferFile?: boolean;
|
|
18
|
+
}): MLPlayableSource | null;
|
|
19
|
+
/** A video the viewer can play now (a stream or the file). */
|
|
20
|
+
export declare function mlIsPlayableVideo(media: MLEmbeddedMedia | null | undefined): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function mlMediaPoster(media, fallback) {
|
|
2
|
+
return media?.poster || fallback;
|
|
3
|
+
}
|
|
4
|
+
export function mlMediaSrcset(media) {
|
|
5
|
+
const sources = media?.sources;
|
|
6
|
+
return sources?.length ? sources.map((s) => `${s.url} ${s.width}w`).join(", ") : void 0;
|
|
7
|
+
}
|
|
8
|
+
export function mlPlayableSource(media, { preferFile = false } = {}) {
|
|
9
|
+
if (!media) return null;
|
|
10
|
+
if (!preferFile && media.streams?.hls) return { src: media.streams.hls, type: "hls" };
|
|
11
|
+
return media.url ? { src: media.url, type: "progressive" } : null;
|
|
12
|
+
}
|
|
13
|
+
export function mlIsPlayableVideo(media) {
|
|
14
|
+
return media?.kind === "video" && !!mlPlayableSource(media);
|
|
15
|
+
}
|
|
@@ -5,5 +5,12 @@ import type { StreamUrlFields } from '../types/asset.js';
|
|
|
5
5
|
* Null when the asset has no stream URLs or no lifetime (nothing to refresh).
|
|
6
6
|
*/
|
|
7
7
|
export declare function streamUrlsRefreshDelay(asset: StreamUrlFields | null | undefined, resolvedAt: number, now?: number, marginSeconds?: number): number | null;
|
|
8
|
+
/**
|
|
9
|
+
* Milliseconds from `now` until links that lasted `ttlSeconds` from
|
|
10
|
+
* `resolvedAt` (ms epoch) should be fetched again, `marginSeconds` early
|
|
11
|
+
* (capped at half the lifetime); 0 when already due. Null when there is no
|
|
12
|
+
* lifetime: the links never expire.
|
|
13
|
+
*/
|
|
14
|
+
export declare function refreshDelay(ttlSeconds: number | null | undefined, resolvedAt: number, now?: number, marginSeconds?: number): number | null;
|
|
8
15
|
/** True when the asset's stream URLs are expired or about to. */
|
|
9
16
|
export declare function streamUrlsStale(asset: StreamUrlFields | null | undefined, resolvedAt: number, now?: number, marginSeconds?: number): boolean;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const DEFAULT_MARGIN_SECONDS = 30;
|
|
2
2
|
export function streamUrlsRefreshDelay(asset, resolvedAt, now = Date.now(), marginSeconds = DEFAULT_MARGIN_SECONDS) {
|
|
3
3
|
if (!asset || !asset.hlsUrl && !asset.dashUrl) return null;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
return refreshDelay(asset.streamUrlExpiresIn, resolvedAt, now, marginSeconds);
|
|
5
|
+
}
|
|
6
|
+
export function refreshDelay(ttlSeconds, resolvedAt, now = Date.now(), marginSeconds = DEFAULT_MARGIN_SECONDS) {
|
|
7
|
+
if (ttlSeconds == null || !Number.isFinite(ttlSeconds) || ttlSeconds <= 0) return null;
|
|
8
|
+
const margin = Math.min(marginSeconds, ttlSeconds / 2);
|
|
9
|
+
return Math.max(0, resolvedAt + (ttlSeconds - margin) * 1e3 - now);
|
|
8
10
|
}
|
|
9
11
|
export function streamUrlsStale(asset, resolvedAt, now = Date.now(), marginSeconds = DEFAULT_MARGIN_SECONDS) {
|
|
10
12
|
return streamUrlsRefreshDelay(asset, resolvedAt, now, marginSeconds) === 0;
|