Skip to content

Commit

Permalink
dash parser work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
wseymour15 committed Oct 18, 2023
1 parent 47fe2f5 commit b72da6a
Show file tree
Hide file tree
Showing 13 changed files with 713 additions and 44 deletions.
4 changes: 3 additions & 1 deletion happydom.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
const oldConsole = console;
GlobalRegistrator.register();
window.console = oldConsole;
55 changes: 27 additions & 28 deletions src/dash-parser/examples/mpd.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
export const testString = `<?xml version="1.0" encoding="UTF-8"?><MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" maxSubsegmentDuration="PT5.0S" mediaPresentationDuration="PT9M57S" minBufferTime="PT5.0S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011,http://xmlns.sony.net/metadata/mpeg/dash/profile/senvu/2012" type="static" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd">
<Period duration="PT9M57S" id="P1">
<!-- Adaptation Set for main audio -->
<AdaptationSet audioSamplingRate="48000" codecs="mp4a.40.5" contentType="audio" group="2" id="2" lang="en" mimeType="audio/mp4" subsegmentAlignment="true" subsegmentStartsWithSAP="1">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<Representation bandwidth="64000" id="2_1">
<BaseURL>DASH_vodaudio_Track5.m4a</BaseURL>
</Representation>
</AdaptationSet>
<!-- Adaptation Set for video -->
<AdaptationSet codecs="avc1.4D401E" contentType="video" frameRate="24000/1001" group="1" id="1" maxBandwidth="1609728" maxHeight="480" maxWidth="854" maximumSAPPeriod="5.0" mimeType="video/mp4" minBandwidth="452608" minHeight="480" minWidth="854" par="16:9" sar="1:1" subsegmentAlignment="true" subsegmentStartsWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<Representation bandwidth="1005568" height="480" id="1_1" mediaStreamStructureId="1" width="854">
<BaseURL>DASH_vodvideo_Track2.m4v</BaseURL>
</Representation>
<Representation bandwidth="1609728" height="480" id="1_2" mediaStreamStructureId="1" width="854">
<BaseURL>DASH_vodvideo_Track1.m4v</BaseURL>
</Representation>
<Representation bandwidth="704512" height="480" id="1_3" mediaStreamStructureId="1" width="854">
<BaseURL>DASH_vodvideo_Track3.m4v</BaseURL>
</Representation>
<Representation bandwidth="452608" height="480" id="1_4" mediaStreamStructureId="1" width="854">
<BaseURL>DASH_vodvideo_Track4.m4v</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>`;
export const testString = `<?xml version="1.0" encoding="utf-8"?>
<MPD mediaPresentationDuration="PT634.566S" minBufferTime="PT2.00S" profiles="urn:hbbtv:dash:profile:isoff-live:2012,urn:mpeg:dash:profile:isoff-live:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd">
<BaseURL>./</BaseURL>
<Period>
<AdaptationSet mimeType="video/mp4" contentType="video" subsegmentAlignment="true" subsegmentStartsWithSAP="1" par="16:9">
<SegmentTemplate duration="120" timescale="30" media="$RepresentationID$/$RepresentationID$_$Number$.m4v" startNumber="1" initialization="$RepresentationID$/$RepresentationID$_0.m4v"/>
<Representation id="bbb_30fps_1024x576_2500k" codecs="avc1.64001f" bandwidth="3134488" width="1024" height="576" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_1280x720_4000k" codecs="avc1.64001f" bandwidth="4952892" width="1280" height="720" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_1920x1080_8000k" codecs="avc1.640028" bandwidth="9914554" width="1920" height="1080" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_320x180_200k" codecs="avc1.64000d" bandwidth="254320" width="320" height="180" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_320x180_400k" codecs="avc1.64000d" bandwidth="507246" width="320" height="180" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_480x270_600k" codecs="avc1.640015" bandwidth="759798" width="480" height="270" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_640x360_1000k" codecs="avc1.64001e" bandwidth="1254758" width="640" height="360" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_640x360_800k" codecs="avc1.64001e" bandwidth="1013310" width="640" height="360" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_768x432_1500k" codecs="avc1.64001e" bandwidth="1883700" width="768" height="432" frameRate="30" sar="1:1" scanType="progressive"/>
<Representation id="bbb_30fps_3840x2160_12000k" codecs="avc1.640033" bandwidth="14931538" width="3840" height="2160" frameRate="30" sar="1:1" scanType="progressive"/>
</AdaptationSet>
<AdaptationSet mimeType="audio/mp4" contentType="audio" subsegmentAlignment="true" subsegmentStartsWithSAP="1">
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="6"/>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<SegmentTemplate duration="192512" timescale="48000" media="$RepresentationID$/$RepresentationID$_$Number$.m4a" startNumber="1" initialization="$RepresentationID$/$RepresentationID$_0.m4a"/>
<Representation id="bbb_a64k" codecs="mp4a.40.5" bandwidth="67071" audioSamplingRate="48000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>`;
178 changes: 178 additions & 0 deletions src/dash-parser/handleNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { ParsedManifest, Attributes } from "./types/parsedManifest";
import { parseUTCTimingScheme } from "./utils/parseUTCTimingScheme";
import { Representation } from './types/parsedManifest';
import { parseAttributes } from './parseAttributes';
import { constructTemplateUrl } from "./segments/segmentTemplate";

interface HandlerParams {
node: Element,
parentNode: Element | null,
manifest: ParsedManifest,
oldAttributes: Attributes,
newAttributes: Attributes
}

export interface HandlerReponse {
stopIteration?: boolean,
attributes?: Attributes,
baseUrl?: string,
}

interface Handlers {
[key: string]: (params: HandlerParams) => HandlerReponse
}

const handlers: Handlers = {
MPD(params: HandlerParams): HandlerReponse {
let { node, manifest, oldAttributes, newAttributes } = params;

// Add attributes at the manifest level.
manifest.attributes = newAttributes;

const response: HandlerReponse = {
// do not send these attributes further
attributes: {}
}

return response;
},

PERIOD(params: HandlerParams): HandlerReponse {
// TODO: handle period
let { node, manifest, oldAttributes, newAttributes } = params;

// somehow clear oldAttributes
// manifest.attributes = newAttributes;
// newAttributes.tagName = node.tagName;
// newAttributes = {};

// const response: HandlerReponse = {
// // do not send these attributes further
// attributes: {}
// }

// return response;
},

ADAPTIONSET(params: HandlerParams): HandlerReponse {
let { node, manifest, oldAttributes, newAttributes } = params;

// somehow clear oldAttributes
// manifest.attributes = newAttributes;
// newAttributes.tagName = node.tagName;
// newAttributes = {};

// const response: HandlerReponse = {
// // do not send these attributes further
// attributes: {}
// }

// return response;
},

SEGMENTTEMPLATE(params: HandlerParams): HandlerReponse {
let { node, parentNode, manifest, oldAttributes, newAttributes } = params;

if (parentNode?.tagName === 'REPRESENTATION') {
const rep = manifest.representations[manifest.representations.length - 1];
if (rep.attributes?.media) {
const templateValues = {
RepresentationID: rep.attributes.id,
Bandwidth: rep.attributes.bandwidth || 0
};

// TODO: generate segments
const x = constructTemplateUrl(rep.attributes.media as string, templateValues);
rep.attributes.URL = x;
}
}
else {
// media and initialization will be set. Ensure we check this in Representation
}

return {};
},

BASEURL(params: HandlerParams): HandlerReponse {
// TODO: handle base URL

// if child, get last representation and update it
let { node, manifest, oldAttributes, newAttributes } = params;

// somehow clear oldAttributes
// manifest.attributes = newAttributes;
// newAttributes.tagName = node.tagName;
// newAttributes = {};

// const response: HandlerReponse = {
// // do not send these attributes further
// attributes: {}
// }

// return response;
},

REPRESENTATION(params: HandlerParams): HandlerReponse {
const { node, manifest, oldAttributes, newAttributes } = params;
newAttributes.tagName = node.tagName;
console.log(node.tagName);
// This means there is a SegmentTemplate.
if (oldAttributes.media) {
const templateValues = {
RepresentationID: oldAttributes.id,
Bandwidth: oldAttributes.bandwidth || 0
};

// TODO: generate segments

const x = constructTemplateUrl(oldAttributes.media as string, templateValues);
newAttributes.URL = x;
}
const rep: Representation = {
attributes: {...oldAttributes, ...newAttributes}
}

manifest.representations.push(rep);

return {};
},

UTCTIMING(params: HandlerParams): HandlerReponse {
const { node, manifest } = params;
manifest.utcTimingScheme = parseUTCTimingScheme(node);

const response: HandlerReponse = {
// UTCTiming will have no children
stopIteration: true,
}

return response;
},

/**
* Default handler for an unknown node. Acts as a no-op.
*/
DEFAULT(): HandlerReponse {
return {};
}
};

/**
* Handles a node while iterating through the tree of XML Nodes from the MPD file.
* A function is selected by the name of the node.
* Based on the type of node, we store and parse different data.
*
* @param node The node to parse attributes from
*/
export const handleNode = (
node: Element,
parentNode: Element | null,
manifest: ParsedManifest,
oldAttributes: Attributes,
newAttributes: Attributes
): HandlerReponse => {
const nodeName = node.tagName;

const handlerFn: (params: HandlerParams) => HandlerReponse = handlers[nodeName] || handlers.DEFAULT;
return handlerFn({ node, parentNode, manifest, oldAttributes, newAttributes });
};
65 changes: 56 additions & 9 deletions src/dash-parser/parse.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,66 @@
import { ParsedManifest } from "./types/parsedManifest";
import { Attributes, ParsedManifest } from "./types/parsedManifest";
import { testString } from "./examples/mpd";
import { handleNode } from "./handleNode";
import { parseAttributes } from "./parseAttributes";

/**
* Parses a MPD manifest file.
*
* @param playlist The URL of the mpd manifest to be parsed.
* @param manifest A string containing the XML of a DASH manifest.
*/
export default function parse(playlist: string): ParsedManifest {
const parsedManifest: ParsedManifest = {
segments: [],
custom: {}
export default function parse(manifest: string, url: string): ParsedManifest {
const initialParsedManifest: ParsedManifest = {
uri: url || '',
representations: [],
};

// TODO: implement parsing.
var doc = new DOMParser().parseFromString(testString, 'text/xml');
const doc = new DOMParser().parseFromString(manifest, 'text/xml');

return parsedManifest;
return iterateXMLNodes(doc, initialParsedManifest);
}

// also going to need to keep track of BaseURL
function iterateXMLNodes(xmlDocument: XMLDocument, manifest: ParsedManifest): ParsedManifest {
// TODO: Maybe use some sort of object to check state of the parser
// depth, bredth, current period, adaptionset, base URL, ect.
// const parserState: ParserState = {
// node: xmlDocument,
// parentNode: null,
// baseURL: '',
// attributes: {}
// }

function iterateNode(node: Node | Element, parentNode: Element | null, attributes?: any, baseUrl?: string): void {
// Display node name and attributes
// 1 is equivalent to Node.ELEMENT_NODE
let newAttributes = {} as Attributes
let response;

if (node.nodeType == 1) {
const elementNode = node as Element;

newAttributes = parseAttributes(elementNode) as Attributes;

response = handleNode(elementNode, parentNode, manifest, attributes, newAttributes);

if (response?.attributes) {
// Overwrite what attributes should continue to be passed on.
newAttributes = response.attributes;
}
} else if (node.nodeType === Node.TEXT_NODE) {
// Do nothing on text nodes
}

// Recursively iterate child nodes
if (!response?.stopIteration) {
for (let i = 0; i < node.childNodes.length; i++) {
iterateNode(node.childNodes[i], node as Element, {...attributes, ...newAttributes});
}
}
}

// Start iteration from the root node
iterateNode(xmlDocument, null);

return manifest;
}
Loading

0 comments on commit b72da6a

Please sign in to comment.