Skip to content

Commit 16bdd0d

Browse files
committed
feat(player): thumbnails now support json
1 parent b986bf8 commit 16bdd0d

2 files changed

Lines changed: 59 additions & 31 deletions

File tree

packages/vidstack/src/components/ui/thumbnails/thumbnail-loader.ts

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { effect, onDispose, peek, signal, type ReadSignal } from 'maverick.js';
2-
import { noop } from 'maverick.js/std';
32
import type { VTTCue } from 'media-captions';
43

54
import { useMediaContext, type MediaContext } from '../../../core/api/media-context';
5+
import { parseJSONCaptionsFile } from '../../../core/tracks/text/text-track';
66
import { getRequestCredentials } from '../../../utils/network';
77

88
const cache = new Map<string, VTTCue[]>(),
@@ -12,9 +12,9 @@ const cache = new Map<string, VTTCue[]>(),
1212
export class ThumbnailsLoader {
1313
readonly $cues = signal<VTTCue[]>([]);
1414

15-
static create(src: ReadSignal<string>) {
15+
static create($src: ReadSignal<string>) {
1616
const media = useMediaContext();
17-
return new ThumbnailsLoader(src, media);
17+
return new ThumbnailsLoader($src, media);
1818
}
1919

2020
constructor(
@@ -52,24 +52,30 @@ export class ThumbnailsLoader {
5252
this.$cues.set(cache.get(src)!);
5353
} else if (!pending.has(src)) {
5454
pending.add(src);
55-
import('media-captions').then(({ parseResponse }) => {
56-
parseResponse(
57-
fetch(src, {
58-
signal: controller.signal,
59-
credentials: getRequestCredentials(crossorigin()),
60-
}),
61-
)
62-
.then(({ cues }) => {
63-
this.$cues.set(cues);
64-
65-
for (const t of registry) {
66-
if (peek(t.$src) === src) t.$cues.set(cues);
55+
import('media-captions').then(async ({ parseResponse }) => {
56+
try {
57+
const response = await fetch(src, {
58+
signal: controller.signal,
59+
credentials: getRequestCredentials(crossorigin()),
60+
}),
61+
isJSON = response.headers.get('content-type') === 'application/json';
62+
63+
if (isJSON) {
64+
try {
65+
const { cues } = parseJSONCaptionsFile(await response.text(), window.VTTCue);
66+
this._updateCues(src, cues);
67+
} catch (e) {
68+
// no-op
6769
}
6870

69-
cache.set(src, cues);
70-
pending.delete(src);
71-
})
72-
.catch(noop);
71+
return;
72+
}
73+
74+
const { cues } = await parseResponse(response);
75+
this._updateCues(src, cues);
76+
} catch (e) {
77+
// no-op
78+
}
7379
});
7480
}
7581

@@ -78,4 +84,15 @@ export class ThumbnailsLoader {
7884
this.$cues.set([]);
7985
};
8086
}
87+
88+
private _updateCues(currentSrc: string, cues: VTTCue[]) {
89+
this.$cues.set(cues);
90+
91+
for (const t of registry) {
92+
if (peek(t.$src) === currentSrc) t.$cues.set(cues);
93+
}
94+
95+
cache.set(currentSrc, cues);
96+
pending.delete(currentSrc);
97+
}
8198
}

packages/vidstack/src/core/tracks/text/text-track.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DOMEvent, EventsTarget, isNumber } from 'maverick.js/std';
1+
import { DOMEvent, EventsTarget, isArray, isNumber } from 'maverick.js/std';
22
import type {
33
CaptionsFileFormat,
44
CaptionsParserFactory,
@@ -264,17 +264,9 @@ export class TextTrack extends EventsTarget<TextTrackEvents> {
264264

265265
private _parseJSON(json, VTTCue, VTTRegion) {
266266
try {
267-
json = JSON.parse(json);
268-
269-
if (json.regions) {
270-
this._regions = json.regions.map((json) => Object.assign(new VTTRegion(), json));
271-
}
272-
273-
if (json.cues) {
274-
this._cues = json.cues
275-
.filter((json) => isNumber(json.startTime) && isNumber(json.endTime))
276-
.map((json) => Object.assign(new VTTCue(0, 0, ''), json));
277-
}
267+
const { regions, cues } = parseJSONCaptionsFile(json, VTTCue, VTTRegion);
268+
this._regions = regions;
269+
this._cues = cues;
278270
} catch (error) {
279271
if (__DEV__) {
280272
console.error(`[vidstack] failed to parse JSON captions at: \`${this.src}\`\n\n`, error);
@@ -405,3 +397,22 @@ const captionRE = /captions|subtitles/;
405397
export function isTrackCaptionKind(track: TextTrack): boolean {
406398
return captionRE.test(track.kind);
407399
}
400+
401+
export function parseJSONCaptionsFile(json: string, Cue: typeof VTTCue, Region?: typeof VTTRegion) {
402+
const content = JSON.parse(json);
403+
404+
let regions: VTTRegion[] = [],
405+
cues: VTTCue[] = [];
406+
407+
if (content.regions && Region) {
408+
regions = content.regions.map((region) => Object.assign(new Region(), region));
409+
}
410+
411+
if (content.cues || isArray(content)) {
412+
cues = (isArray(content) ? content : content.cues)
413+
.filter((content) => isNumber(content.startTime) && isNumber(content.endTime))
414+
.map((cue) => Object.assign(new Cue(0, 0, ''), cue));
415+
}
416+
417+
return { regions, cues };
418+
}

0 commit comments

Comments
 (0)