Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] scte214 - use supplemental codec instead of base codec if it's supported #1307

Merged
merged 20 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7d1ecef
parse supplemental codec
Florent-Bouisset Oct 27, 2023
85b5941
add a property isSupplementalCoded supported and update getMimeType
Florent-Bouisset Oct 27, 2023
09e1d03
add documentation for isSupplementalCodecSupported property
Florent-Bouisset Nov 7, 2023
6c67e53
parse supplementalCodec in the adaptation set
Florent-Bouisset Nov 7, 2023
e7d22a6
return the codec that will be played if both supplemental codec is su…
Florent-Bouisset Nov 7, 2023
c5cb8c7
parse multiple supplemental codec separated by white space
Florent-Bouisset Nov 8, 2023
c57acb9
avoid unecessary call to iseCodecSupported if supplementalCodec is true
Florent-Bouisset Nov 8, 2023
d14148b
remove test file that shouldn't have been add
Florent-Bouisset Nov 8, 2023
2d56d1a
remplace == with isNullOrUndefined function
Florent-Bouisset Nov 9, 2023
3d7d1da
update regex to match more unexpected whitespace
Florent-Bouisset Nov 9, 2023
49d1a7d
code review
Florent-Bouisset Nov 9, 2023
385b533
update rust WASM parser to parse supplementalCodecs
Florent-Bouisset Nov 9, 2023
7bdef9d
doc: update rfc number that obsoletes rfc4281
Florent-Bouisset Nov 9, 2023
9010407
move codec converting logic in "common" to convert from any parser
Florent-Bouisset Nov 9, 2023
a02d3df
code review
Florent-Bouisset Nov 10, 2023
0a694d7
set the codec to supplementalCodec if supported and remove supplement…
Florent-Bouisset Nov 10, 2023
be795bc
handle case for subtitle
Florent-Bouisset Nov 10, 2023
b16c914
move code to another folder
Florent-Bouisset Nov 13, 2023
435033d
reset unchanged file
Florent-Bouisset Nov 13, 2023
99d3a3f
remove staged file
Florent-Bouisset Nov 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 63 additions & 6 deletions src/manifest/representation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ class Representation {
*/
public codec : string | undefined;

/**
* Supplemental codecs are defined as backwards-compatible codecs enhancing
Florent-Bouisset marked this conversation as resolved.
Show resolved Hide resolved
* the experience of a base layer codec
* Optional attribute, a representation may not have supplemental codecs
*/
public supplementalCodec?: string | undefined;
peaBerberian marked this conversation as resolved.
Show resolved Hide resolved

/**
* `true` if the Supplemental codec is in a supported codec
* `false` if there is no supplemental codec or
* if the supplemental codec is not supported
*/
public isSupplementalCodecSupported: boolean;
peaBerberian marked this conversation as resolved.
Show resolved Hide resolved

/**
* A string describing the mime-type for this Representation.
* Examples: audio/mp4, video/webm, application/mp4, text/plain
Expand Down Expand Up @@ -191,8 +205,26 @@ class Representation {

this.cdnMetadata = args.cdnMetadata;

if (args.supplementalCodecs !== undefined) {
this.supplementalCodec = args.supplementalCodecs;
}

if (this.supplementalCodec !== undefined) {
const supplementalCodecMimeTypeStr =
`${this.mimeType ?? ""};codecs="${this.supplementalCodec ?? ""}"`;
const isSupplementalCodecSupported = isCodecSupported(supplementalCodecMimeTypeStr);
peaBerberian marked this conversation as resolved.
Show resolved Hide resolved
this.isSupplementalCodecSupported = isSupplementalCodecSupported;
} else {
this.isSupplementalCodecSupported = false;
}

this.index = args.index;
if (opts.type === "audio" || opts.type === "video") {

if (this.isSupplementalCodecSupported) {
// The supplemental codec being supported indicate that the base codec will also
// be supported as the supplemental codec is backwards compatible with base codec
this.isSupported = true;
} else if (opts.type === "audio" || opts.type === "video") {
const mimeTypeStr = this.getMimeTypeString();
const isSupported = isCodecSupported(mimeTypeStr);
if (!isSupported) {
Expand All @@ -210,7 +242,11 @@ class Representation {
* @returns {string}
*/
public getMimeTypeString() : string {
return `${this.mimeType ?? ""};codecs="${this.codec ?? ""}"`;
if (this.isSupplementalCodecSupported) {
return `${this.mimeType ?? ""};codecs="${this.supplementalCodec ?? ""}"`;
} else {
return `${this.mimeType ?? ""};codecs="${this.codec ?? ""}"`;
}
}

/**
Expand Down Expand Up @@ -387,17 +423,38 @@ class Representation {
* @returns {Object}
*/
public toAudioRepresentation(): IAudioRepresentation {
const { id, isSpatialAudio, bitrate, codec } = this;
return { id, isSpatialAudio, bitrate, codec };
const { id,
isSpatialAudio,
bitrate,
codec,
supplementalCodec,
isSupplementalCodecSupported,
} = this;
// Depending if the device can play the supplemental codec or not
// We return as codec the codec that WILL be played if this representation is chosen
const codecInUse = isSupplementalCodecSupported ? supplementalCodec : codec;
return { id, isSpatialAudio, bitrate, codec: codecInUse };
}

/**
* Format Representation as an `IVideoRepresentation`.
* @returns {Object}
*/
public toVideoRepresentation(): IVideoRepresentation {
const { id, bitrate, frameRate, width, height, codec, hdrInfo } = this;
return { id, bitrate, frameRate, width, height, codec, hdrInfo };
const { id,
bitrate,
frameRate,
width,
height,
codec,
hdrInfo,
supplementalCodec,
isSupplementalCodecSupported,
} = this;
// Depending if the device can play the supplemental codec or not
// We return as codec the codec that WILL be played if this representation is chosen
const codecInUse = isSupplementalCodecSupported ? supplementalCodec : codec;
return { id, bitrate, frameRate, width, height, codec: codecInUse, hdrInfo };
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/parsers/manifest/dash/common/parse_representations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,17 @@ export default function parseRepresentations(
codecs = codecs === "mp4a.40.02" ? "mp4a.40.2" : codecs;
parsedRepresentation.codecs = codecs;
}

let supplementalCodecs: string | undefined;
if (representation.attributes.supplementalCodecs != null) {
Copy link

@myoann myoann Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you use a !==, in order to verify also the type or the existing isNullOrUndefined method in the utils?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used to check if the value is either null or undefined,
@peaBerberian is there any recommandations about using != or the function !isNullOrUndefined ?
Both syntax exists in the project

Copy link
Collaborator

@peaBerberian peaBerberian Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we now try to avoid relying on double equals in the RxPlayer as:

  • double equals' behavior might not be understood by every dev
  • it's more explicit when reading to have the exact conditions written
  • it avoid some classes of bug where we wrote double equals just by lazyness.
    Here the intent is to have to check explicitely for what can happen. In some situations where only null xor undefined are possible at first, we may create a bug in future code if ever the other value become possible.

There are some areas of the code still relying on the == null trick, but that's old logic we ultimately want to replace them by more explicit code.

Copy link
Collaborator Author

@Florent-Bouisset Florent-Bouisset Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, should I change all the != to !isNullOrUndefined in the parse_representation.ts file, or should I do another MR for this change ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you want, both are good to me

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here isn't it only undefined though? I'm unsure

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think too it's only undefined but as a precaution I didn't want to change the behavior of the API.

supplementalCodecs = representation.attributes.supplementalCodecs;
} else if (adaptation.attributes.supplementalCodecs != null) {
supplementalCodecs = adaptation.attributes.supplementalCodecs;
}
if (supplementalCodecs != null) {
parsedRepresentation.supplementalCodecs = supplementalCodecs;
Florent-Bouisset marked this conversation as resolved.
Show resolved Hide resolved
}

if (representation.attributes.frameRate != null) {
parsedRepresentation.frameRate =
representation.attributes.frameRate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
parseMPDFloat,
parseMPDInteger,
parseScheme,
parseSupplementalCodec,
ValueParser,
} from "./utils";

Expand Down Expand Up @@ -296,6 +297,12 @@ function parseAdaptationSetAttributes(
parsedAdaptation.codecs = attribute.value;
break;

case "scte214:supplementalCodecs":
parseValue(attribute.value, { asKey: "supplementalCodecs",
parser: parseSupplementalCodec,
dashName: "scte214:supplementalCodecs" });
break;

case "codingDependency":
parseValue(attribute.value, { asKey: "codingDependency",
parser: parseBoolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
parseMPDFloat,
parseMPDInteger,
parseScheme,
parseSupplementalCodec,
ValueParser,
} from "./utils";

Expand Down Expand Up @@ -184,6 +185,12 @@ function parseRepresentationAttributes(
dashName: "qualityRanking" });
break;

case "scte214:supplementalCodecs":
parseValue(attr.value, { asKey: "supplementalCodecs",
parser: parseSupplementalCodec,
dashName: "scte214:supplementalCodecs" });
break;

case "segmentProfiles":
attributes.segmentProfiles = attr.value;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import {
MPDError,
parseBoolean,
parseSupplementalCodec,
parseByteRange,
parseDateTime,
parseDuration,
Expand Down Expand Up @@ -79,6 +80,35 @@ describe("dash parser helpers", function() {
});
});

describe("parseSupplementalCodec", () => {
it("should return the codec unchanged if there is only one codec", () => {
expect(parseSupplementalCodec("avc1.4d400d")).toEqual(["avc1.4d400d", null]);
});
it("should trim starting and ending whitespace", () => {
expect(
parseSupplementalCodec(" avc1.4d400d "))
.toEqual(["avc1.4d400d", null]);
});
it("should return comma-separated list if input is whitespace-separated" , () => {
expect(
parseSupplementalCodec("avc1.4d400d avc1.4d4015"))
.toEqual(["avc1.4d400d, avc1.4d4015", null]);
});
it("should return comma-separated value if input is alreadu comma-separated" , () => {
Florent-Bouisset marked this conversation as resolved.
Show resolved Hide resolved
expect(
parseSupplementalCodec("avc1.4d400d, avc1.4d4015"))
.toEqual(["avc1.4d400d, avc1.4d4015", null]);
});

it(`should return comma-separated value if input is mix of comma and
whitespace separated list`
, () => {
expect(
parseSupplementalCodec("avc1.4d400d avc1.4d4015, avc1.4d401f"))
.toEqual(["avc1.4d400d, avc1.4d4015, avc1.4d401f", null]);
});
});

describe("parseDateTime", () => {
it("should correctly parse a given date into a timestamp", () => {
expect(parseDateTime("1970-01-01T00:00:00Z", "a")).toEqual([0, null]);
Expand Down
36 changes: 36 additions & 0 deletions src/parsers/manifest/dash/js-parser/node_parsers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,41 @@ const iso8601Duration =
/^P(([\d.]*)Y)?(([\d.]*)M)?(([\d.]*)D)?T?(([\d.]*)H)?(([\d.]*)M)?(([\d.]*)S)?/;
const rangeRe = /([0-9]+)-([0-9]+)/;


Florent-Bouisset marked this conversation as resolved.
Show resolved Hide resolved
const supplementalCodecSeparator = /([,]? +)/g;
peaBerberian marked this conversation as resolved.
Show resolved Hide resolved
/**
* Parse SCTE 214 supplemental codec string into RFC4281 codec string
*
* The returned value is a tuple of two elements where:
* 1. the first value is the parsed value - or `undefined` if the value is empty
* 2. the second value is a possible error encountered while parsing this
* value - set to `null` if no error was encountered.
*
* SCTE 214 defines supplemental codecs as a whitespace-separated multiple list of
* codec strings
*
* RFC4281 defines codecs as a comma-separated list of codec strings.
*
* This two syntax differs and this parser is used to convert SCTE214
* to be compliant with what MSE APIs expect
*
* @param {string} val - The codec string to parse
* @returns { Array.<string | undefined | null>}
*/
function parseSupplementalCodec(
val: string
) : [string ,
MPDError | null] {

if (isNonEmptyString(val)) {
const parsedValue = val
.trim()
.replace(supplementalCodecSeparator, ", ");
return [parsedValue, null];
}
return ["", null];
}

/**
* Parse MPD boolean attributes.
*
Expand Down Expand Up @@ -373,6 +408,7 @@ export {
MPDError,
ValueParser,
parseBase64,
parseSupplementalCodec,
parseBoolean,
parseByteRange,
parseDateTime,
Expand Down
2 changes: 2 additions & 0 deletions src/parsers/manifest/dash/node_parser_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export interface IAdaptationSetAttributes {
segmentAlignment? : number|boolean;
segmentProfiles? : string;
subsegmentAlignment? : number|boolean;
supplementalCodecs?: string;
width? : number;
availabilityTimeComplete?: boolean;
availabilityTimeOffset?: number;
Expand Down Expand Up @@ -271,6 +272,7 @@ export interface IRepresentationAttributes {
profiles? : string;
qualityRanking? : number;
segmentProfiles? : string;
supplementalCodecs?: string;
width? : number;
availabilityTimeComplete?: boolean;
availabilityTimeOffset?: number;
Expand Down
2 changes: 2 additions & 0 deletions src/parsers/manifest/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export interface IParsedRepresentation {
hdrInfo?: IHDRInformation | undefined;
/** `true` if audio has Dolby Atmos. */
isSpatialAudio?: boolean | undefined;

supplementalCodecs? : string | undefined;
}

/** Every possible types an Adaptation can have. */
Expand Down
Loading