Skip to content

Commit

Permalink
Fix segment requesting error when playing a DASH content without an u…
Browse files Browse the repository at this point in the history
…rl and without BaseURL elements

Fixes #1190

We brought in the last v3.29.0 a regression which made it impossible to play a DASH content in very specific conditions.
Thankfully, the conditions to reproduce it are relatively rare and easy to work-around (as written in the issue). Basically it only happens when all following conditions are true:
  1. You're playing a DASH content
  2. You didn't communicate an `url` for the MPD (for now only works if you at least communicated either a `initialManifest` or a `manifestLoader`)
  3. You didn't communicate about the MPD URL in the `manifestLoader` if you used one
  4. The MPD does not contain any `<BaseURL>` element, or it does but only inside `<Representation>` elements (which means basically directly for each segments, e.g. in a `<SegmentList>` element).

When those conditions are met, you will receive a `PIPELINE_LOAD_ERROR` error with the message `"No CDN to request"` when the RxPlayer is trying to request its first segment.

---

This happens because since the RxPlayer v3.29.0, we try to identify one or multiple CDN through which segments may be requested. This is done at the `<Representation>` level.

Under the specific conditions written earlier, MPD parsers will indicate an empty array as the CDN that can be used to reach that Representation's segments, because it has no way of defining even one root URL.
In the corresponding `Manifest`'s structure documentation (the protocol-agnostic structure defined by the RxPlayer for a Manifest), an empty array is then documented as `An empty array means that no CDN are left to request the resource. As such, no resource can be loaded in that situation.`.

So an empty array is for explicitly telling that the segment should be reachable through a potential CDN, but that no CDN is usable, at least right now.
That's why the  `"No CDN to request"` error was sent: the logic performing segment requests just wanted to load a segment, but saw that right now no CDN can be requested for it.

To fix this situation, we could set that value to `null`, which exists, instead of an empty array. But this is how a `null` value is defined in the same `Manifest` structure: `null if there's no CDN involved here (e.g. resources are not requested through the network).`
In that specific issue, the segment may perfectly be reachable through the network, we just don't know the root URL. We also risk breaking functional use cases if a full URL was actually declared for each segment.

I thus hesitated between redefining the empty array and not throw in that case, redefining `null`, or to invent a third special case.
For now, I chose to explicitly allow a new way of telling that an URL may exist, but should be entirely associated to each segment: by setting the empty string instead.

This basically technically indicates - by following the logic already there - that all following URL are either absolute or relative to the page doing the requests, so it may imply a new strange behavior here when only relative segment URL are present (resulting in the browser considering the current page as the root URL), but I don't know how we should realistically act anyway in that specific case.
  • Loading branch information
peaBerberian committed Dec 15, 2022
1 parent ed315a6 commit 8aace68
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 6 deletions.
10 changes: 7 additions & 3 deletions src/parsers/manifest/dash/common/parse_representations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { IHDRInformation } from "../../../../public_types";
import arrayFind from "../../../../utils/array_find";
import objectAssign from "../../../../utils/object_assign";
import {
ICdnMetadata,
IContentProtections,
IParsedRepresentation,
} from "../../types";
Expand Down Expand Up @@ -163,9 +162,14 @@ export default function parseRepresentations(

const representationBaseURLs = resolveBaseURLs(context.baseURLs,
representation.children.baseURLs);
const cdnMetadata : ICdnMetadata[] = representationBaseURLs.map(x =>
({ baseUrl: x.url, id: x.serviceLocation }));

const cdnMetadata = representationBaseURLs.length === 0 ?
// No BaseURL seems to be associated to this Representation, nor to the MPD,
// but underlying segments might have one. To indicate that segments should
// still be available through a CDN without giving any root CDN URL here,
// we just communicate about an empty `baseUrl`, as documented.
[ { baseUrl: "", id: undefined } ] :
representationBaseURLs.map(x => ({ baseUrl: x.url, id: x.serviceLocation }));

// Construct Representation Base
const parsedRepresentation : IParsedRepresentation =
Expand Down
8 changes: 6 additions & 2 deletions src/parsers/manifest/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ export interface IContentProtections {
/** Represents metadata of a CDN which can serve resources. */
export interface ICdnMetadata {
/**
* The base URL on which resources can be requested though this CDN.
* The root URL on which resources can be requested though this CDN.
*
* In most transports, you will want to add the wanted media resource's URL
* to that one to request it.
* to that one to request it as they should be relative to it.
*
* May be an empty string to indicate that all segments should contain the
* full URL.
*/
baseUrl : string;

Expand Down
53 changes: 52 additions & 1 deletion tests/integration/scenarios/loadVideo_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,57 @@ describe("loadVideo Options", () => {
.to.equal(nbVideoSegmentRequests);
expect(nbVideoSegmentRequests).to.be.above(0);
});

it("should pass through the custom segmentLoader even when no hint is given about the URL", () => {
const fakeMpdWithoutBaseURLs = `<?xml version="1.0" encoding="utf-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:mpeg:dash:schema:mpd:2011"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
profiles="urn:mpeg:dash:profile:isoff-live:2011"
type="dynamic"
minimumUpdatePeriod="PT500S"
suggestedPresentationDelay="PT1S"
availabilityStartTime="2022-12-07T08:52:13.150Z"
publishTime="2022-12-07T08:52:13.926Z"
maxSegmentDuration="PT1.0S"
minBufferTime="PT2.0S">
<Period id="0" start="PT0.0S">
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="25/1" maxWidth="768" maxHeight="576" par="4:3">
<Representation id="0" mimeType="video/mp4" codecs="avc1.640028" bandwidth="176736" width="768" height="576" sar="1:1">
<SegmentTemplate timescale="1000000" duration="1000000" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1">
</SegmentTemplate>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true">
<Representation id="1" mimeType="audio/mp4" codecs="mp4a.40.2" bandwidth="69000" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="1" />
<SegmentTemplate timescale="1000000" duration="1000000" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1">
</SegmentTemplate>
</Representation>
</AdaptationSet>
</Period>
</MPD>`;
return new Promise((res, rej) => {
player.loadVideo({
transport: manifestInfos.transport,
transportOptions: {
manifestLoader(_url, callbacks) {
callbacks.resolve({ data: fakeMpdWithoutBaseURLs });
},
segmentLoader(infos) {
expect(infos.url).to.satisfy((s) => s.includes("init-stream") ||
s.includes("chunk-stream"));
player.stop();
res();
},
},
});
player.addEventListener("error", (err) => {
rej(err);
});
});
});
});

describe("manifestLoader", () => {
Expand Down Expand Up @@ -457,7 +508,7 @@ describe("loadVideo Options", () => {
};
};

it("should pass through the custom segmentLoader for segment requests", async () => {
it("should pass through the custom manifestLoader for manifest requests", async () => {
player.loadVideo({
transport: manifestInfos.transport,
url: manifestInfos.url,
Expand Down

0 comments on commit 8aace68

Please sign in to comment.