dasha 4.4.3 → 4.4.5

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/index.d.mts CHANGED
@@ -183,6 +183,7 @@ type Segment = {
183
183
  };
184
184
  type DashSegmentLocation = {
185
185
  path: string;
186
+ sourcePath: string;
186
187
  offset: number;
187
188
  length: number | null;
188
189
  };
@@ -360,7 +361,10 @@ declare class DashDemuxer {
360
361
  }): void;
361
362
  findMatchingTrack(nextTracks: DashParsedTrack[], currentTrack: DashParsedTrack): DashParsedTrack | undefined;
362
363
  refreshTracks(tracks: DashParsedTrack[]): Promise<void>;
363
- fetchManifest(url: string): Promise<Response>;
364
+ fetchManifestText(url: string): Promise<{
365
+ text: string;
366
+ url: string;
367
+ }>;
364
368
  resetManifestUrls(): void;
365
369
  }
366
370
  declare abstract class DashTrackBackingBase {
package/dist/index.mjs CHANGED
@@ -316,6 +316,21 @@ const getDashTrackMatchKey = (track) => JSON.stringify({
316
316
  const getSourcePath = (source) => {
317
317
  if ("rootPath" in source && typeof source.rootPath === "string") return source.rootPath;
318
318
  };
319
+ const resolvePathedSourceRequest = async (source, request) => {
320
+ const pathedSource = source;
321
+ if (typeof pathedSource._resolveRequest !== "function") throw new Error("DASH input currently requires a pathed source such as UrlSource.");
322
+ return await pathedSource._resolveRequest(request);
323
+ };
324
+ const resolvePathedSourcePath = async (source, request) => {
325
+ const ref = await resolvePathedSourceRequest(source, request);
326
+ try {
327
+ const path = getSourcePath(ref.source);
328
+ if (!path) throw new Error("DASH segment requests must resolve to a pathed source.");
329
+ return path;
330
+ } finally {
331
+ ref.free();
332
+ }
333
+ };
319
334
  const normalizeHeaders$1 = (headers) => {
320
335
  if (!headers) return {};
321
336
  if (headers instanceof Headers) return Object.fromEntries(headers.entries());
@@ -333,30 +348,45 @@ const getSourceHeaders$1 = (source) => {
333
348
  const getSourceFetch = (source) => {
334
349
  return ("_options" in source && source._options && typeof source._options === "object" ? source._options : void 0)?.fetchFn ?? fetch;
335
350
  };
351
+ const fetchDashManifest = async (source, url) => {
352
+ const response = await getSourceFetch(source)(url, { headers: getSourceHeaders$1(source) });
353
+ if (!response.ok) throw new Error(`Failed to fetch DASH manifest: ${response.status} ${response.statusText} (${response.url})`);
354
+ return response;
355
+ };
336
356
  const parseOriginalUrlFromManifest = (text) => text.match(/<!--\s*URL:\s*([^\n]+?)\s*-->/)?.[1]?.trim();
337
357
  const loadDashManifest = async (source) => {
338
358
  const manifestPath = getSourcePath(source);
339
359
  if (!manifestPath) throw new Error("DASH input currently requires a pathed source such as UrlSource.");
340
- if (manifestPath.startsWith("http://") || manifestPath.startsWith("https://")) {
341
- const response = await getSourceFetch(source)(manifestPath, { headers: getSourceHeaders$1(source) });
342
- if (!response.ok) throw new Error(`Failed to fetch DASH manifest: ${response.status} ${response.statusText} (${response.url})`);
343
- return {
344
- text: await response.text(),
345
- url: response.url
346
- };
347
- }
348
- if (manifestPath.startsWith("file:")) {
349
- const text = await readFile(new URL(manifestPath), "utf8");
360
+ const manifestRef = await resolvePathedSourceRequest(source, {
361
+ path: manifestPath,
362
+ isRoot: true
363
+ });
364
+ const manifestSource = manifestRef.source;
365
+ const resolvedManifestPath = getSourcePath(manifestSource);
366
+ try {
367
+ if (!resolvedManifestPath) throw new Error("DASH manifest requests must resolve to a pathed source.");
368
+ if (resolvedManifestPath.startsWith("http://") || resolvedManifestPath.startsWith("https://")) {
369
+ const response = await fetchDashManifest(manifestSource, resolvedManifestPath);
370
+ return {
371
+ text: await response.text(),
372
+ url: response.url || resolvedManifestPath
373
+ };
374
+ }
375
+ if (resolvedManifestPath.startsWith("file:")) {
376
+ const text = await readFile(new URL(resolvedManifestPath), "utf8");
377
+ return {
378
+ text,
379
+ url: parseOriginalUrlFromManifest(text) ?? resolvedManifestPath
380
+ };
381
+ }
382
+ const text = await readFile(resolvedManifestPath, "utf8");
350
383
  return {
351
384
  text,
352
- url: parseOriginalUrlFromManifest(text) ?? manifestPath
385
+ url: parseOriginalUrlFromManifest(text) ?? resolvedManifestPath
353
386
  };
387
+ } finally {
388
+ manifestRef.free();
354
389
  }
355
- const text = await readFile(manifestPath, "utf8");
356
- return {
357
- text,
358
- url: parseOriginalUrlFromManifest(text) ?? manifestPath
359
- };
360
390
  };
361
391
  const isLikelyDashPath = (source) => {
362
392
  const path = getSourcePath(source);
@@ -1386,6 +1416,7 @@ const BACKING_TYPE_AUDIO = "audio";
1386
1416
  const BACKING_TYPE_VIDEO = "video";
1387
1417
  const BASE_INPUT_PATCHED = Symbol.for("dasha.base-mediabunny-input-patched");
1388
1418
  const PRESERVE_SUBTITLE_BACKINGS = Symbol.for("dasha.preserve-subtitle-backings");
1419
+ const resolvedHlsSegmentLocations = /* @__PURE__ */ new WeakSet();
1389
1420
  const getDefaultAudioTrackFormats = (source) => {
1390
1421
  return isLikelyDashPath(source instanceof SourceRef ? source.source : source) ? [DASH, ...ALL_FORMATS$1] : [...ALL_FORMATS$1, DASH];
1391
1422
  };
@@ -1549,6 +1580,22 @@ const getSegmentedInputForTrack = (track) => {
1549
1580
  const internalTrack = backing.internalTrack;
1550
1581
  return internalTrack.demuxer.getSegmentedInputForPath(internalTrack.fullPath);
1551
1582
  };
1583
+ const isHlsSegment = (segment) => !("sourcePath" in segment.location);
1584
+ const resolveHlsSegmentLocation = async (source, location) => {
1585
+ if (resolvedHlsSegmentLocations.has(location)) return;
1586
+ location.path = await resolvePathedSourcePath(source, {
1587
+ path: location.path,
1588
+ isRoot: false
1589
+ });
1590
+ resolvedHlsSegmentLocations.add(location);
1591
+ };
1592
+ const resolveHlsSegments = async (source, segments) => {
1593
+ for (const segment of segments) {
1594
+ if (!isHlsSegment(segment)) continue;
1595
+ if (segment.initSegment) await resolveHlsSegmentLocation(source, segment.initSegment.location);
1596
+ await resolveHlsSegmentLocation(source, segment.location);
1597
+ }
1598
+ };
1552
1599
  const getTrackBacking = (track) => track._backing;
1553
1600
  const getTrackSource = (track) => {
1554
1601
  return getTrackBacking(track).getSource?.() ?? track.input.source;
@@ -1572,11 +1619,13 @@ const addSegmentAccess = (track) => new Proxy(track, { get(target, prop) {
1572
1619
  if (prop === "getSegments") return async () => {
1573
1620
  const segmentedInput = getSegmentedInputForTrack(target);
1574
1621
  if (segmentedInput.segments.length === 0) await segmentedInput.runUpdateSegments();
1622
+ await resolveHlsSegments(getTrackSource(target), segmentedInput.segments);
1575
1623
  return segmentedInput.segments;
1576
1624
  };
1577
1625
  if (prop === "refreshSegments") return async () => {
1578
1626
  const segmentedInput = getSegmentedInputForTrack(target);
1579
1627
  await segmentedInput.runUpdateSegments();
1628
+ await resolveHlsSegments(getTrackSource(target), segmentedInput.segments);
1580
1629
  return segmentedInput.segments;
1581
1630
  };
1582
1631
  const value = Reflect.get(target, prop, target);
@@ -1831,23 +1880,27 @@ const getLeastRecentlyUsedIndex = (entries) => {
1831
1880
  }
1832
1881
  return bestIndex;
1833
1882
  };
1834
- const getSegmentLocation = (segment) => ({
1835
- path: segment.url,
1883
+ const getSegmentLocation = async (input, segment) => ({
1884
+ path: await resolvePathedSourcePath(input.source, {
1885
+ path: segment.url,
1886
+ isRoot: false
1887
+ }),
1888
+ sourcePath: segment.url,
1836
1889
  offset: segment.startRange ?? 0,
1837
1890
  length: segment.expectLength ?? null
1838
1891
  });
1839
- const createInitSegment = (segment) => ({
1892
+ const createInitSegment = async (input, segment) => ({
1840
1893
  timestamp: 0,
1841
1894
  duration: 0,
1842
1895
  relativeToUnixEpoch: false,
1843
1896
  firstSegment: null,
1844
1897
  sequenceNumber: segment.sequenceNumber,
1845
- location: getSegmentLocation(segment),
1898
+ location: await getSegmentLocation(input, segment),
1846
1899
  encryption: segment.encryption,
1847
1900
  initSegment: null,
1848
1901
  lastProgramDateTimeSeconds: null
1849
1902
  });
1850
- const trackToDashSegments = (internalTrack) => {
1903
+ const trackToDashSegments = async (input, internalTrack) => {
1851
1904
  const mediaSegments = internalTrack.track.mediaSegments;
1852
1905
  if (mediaSegments.length === 0) return [];
1853
1906
  let nextTimestamp = 0;
@@ -1860,7 +1913,7 @@ const trackToDashSegments = (internalTrack) => {
1860
1913
  relativeToUnixEpoch: false,
1861
1914
  firstSegment: null,
1862
1915
  sequenceNumber: mediaSegment.sequenceNumber,
1863
- location: getSegmentLocation(mediaSegment),
1916
+ location: await getSegmentLocation(input, mediaSegment),
1864
1917
  encryption: mediaSegment.encryption,
1865
1918
  initSegment: null,
1866
1919
  lastProgramDateTimeSeconds: null
@@ -1869,7 +1922,7 @@ const trackToDashSegments = (internalTrack) => {
1869
1922
  nextTimestamp = timestamp + mediaSegment.duration;
1870
1923
  }
1871
1924
  const firstSegment = segments[0] ?? null;
1872
- const initSegment = internalTrack.track.initSegment ? createInitSegment(internalTrack.track.initSegment) : null;
1925
+ const initSegment = internalTrack.track.initSegment ? await createInitSegment(input, internalTrack.track.initSegment) : null;
1873
1926
  for (const segment of segments) {
1874
1927
  segment.firstSegment = firstSegment;
1875
1928
  segment.initSegment = initSegment;
@@ -1905,8 +1958,9 @@ var DashSegmentedInput = class {
1905
1958
  })();
1906
1959
  }
1907
1960
  async updateSegments() {
1961
+ const input = this.demuxer.input;
1908
1962
  await this.demuxer.refreshTrackSegments(this.internalTrack);
1909
- this.segments = trackToDashSegments(this.internalTrack);
1963
+ this.segments = await trackToDashSegments(input, this.internalTrack);
1910
1964
  }
1911
1965
  getRemainingWaitTimeMs() {
1912
1966
  if (!this.internalTrack.track.isLive) return 0;
@@ -1970,7 +2024,7 @@ var DashSegmentedInput = class {
1970
2024
  if (segment.initSegment && segment.initSegment !== segment) initInput = this.getInputForSegment(segment.initSegment);
1971
2025
  const formatOptions = { ...input._formatOptions };
1972
2026
  const segmentInput = preserveSubtitleBackingsOnInput(new Input$1({
1973
- source: new CustomPathedSource(segment.location.path, async (request) => {
2027
+ source: new CustomPathedSource(segment.location.sourcePath, async (request) => {
1974
2028
  if (!request.isRoot) throw new Error("Nested requests are not supported for DASH segments.");
1975
2029
  const proxiedRequest = {
1976
2030
  ...request,
@@ -2764,11 +2818,10 @@ var DashDemuxer = class {
2764
2818
  }
2765
2819
  async refreshTracks(tracks) {
2766
2820
  if (!tracks.length) return;
2767
- const response = await this.fetchManifest(this.manifestUrl).catch(() => this.fetchManifest(this.originalUrl));
2768
- const rawText = await response.text();
2821
+ const response = await this.fetchManifestText(this.manifestUrl).catch(() => this.fetchManifestText(this.originalUrl));
2769
2822
  this.manifestUrl = response.url;
2770
2823
  this.resetManifestUrls();
2771
- const nextTracks = this.extractTracks(rawText);
2824
+ const nextTracks = this.extractTracks(response.text);
2772
2825
  for (const track of tracks) {
2773
2826
  const nextTrack = this.findMatchingTrack(nextTracks, track);
2774
2827
  if (!nextTrack) continue;
@@ -2781,10 +2834,23 @@ var DashDemuxer = class {
2781
2834
  track.subtitleGroupId = nextTrack.subtitleGroupId;
2782
2835
  }
2783
2836
  }
2784
- async fetchManifest(url) {
2785
- const response = await fetch(url, { headers: this.headers });
2786
- if (!response.ok) throw new Error(`Failed to fetch DASH manifest: ${response.status} ${response.statusText} (${response.url})`);
2787
- return response;
2837
+ async fetchManifestText(url) {
2838
+ const manifestRef = await resolvePathedSourceRequest(this.input.source, {
2839
+ path: url,
2840
+ isRoot: true
2841
+ });
2842
+ const manifestSource = manifestRef.source;
2843
+ try {
2844
+ const resolvedUrl = getSourcePath(manifestSource);
2845
+ if (!resolvedUrl) throw new Error("DASH manifest requests must resolve to a pathed source.");
2846
+ const response = await fetchDashManifest(manifestSource, resolvedUrl);
2847
+ return {
2848
+ text: await response.text(),
2849
+ url: response.url || resolvedUrl
2850
+ };
2851
+ } finally {
2852
+ manifestRef.free();
2853
+ }
2788
2854
  }
2789
2855
  resetManifestUrls() {
2790
2856
  this.mpdUrl = this.manifestUrl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dasha",
3
- "version": "4.4.3",
3
+ "version": "4.4.5",
4
4
  "description": "Streaming manifest parser",
5
5
  "files": [
6
6
  "dist"