-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
47fe2f5
commit b72da6a
Showing
13 changed files
with
713 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.