mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-15 16:06:41 +03:00
202 lines
5.8 KiB
JavaScript
202 lines
5.8 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// cspell:ignore subk subv
|
|
|
|
goog.provide('shaka.dash.JsonUtils');
|
|
|
|
goog.require('shaka.util.StringUtils');
|
|
|
|
|
|
/**
|
|
* @summary JSON processing utility functions.
|
|
*/
|
|
shaka.dash.JsonUtils = class {
|
|
/**
|
|
* Converts a DASH-JSON manifest (dash-json-schema) into an MPD XML string.
|
|
*
|
|
* @param {!Object} json - Root JSON object following dash-json-schema.
|
|
* @return {string}
|
|
*/
|
|
static jsonToMpd(json) {
|
|
const MPD_NS = 'urn:mpeg:dash:schema:mpd:2011';
|
|
|
|
/**
|
|
* Emit a tag with attributes and content.
|
|
*
|
|
* @param {string} tagName
|
|
* @param {!Object<string, ?>} attrs
|
|
* @param {string} inner
|
|
* @return {string}
|
|
*/
|
|
function emit(tagName, attrs, inner) {
|
|
let out = '<' + tagName;
|
|
for (const [k, v] of Object.entries(attrs)) {
|
|
out += ` ${k}="${shaka.util.StringUtils.htmlUnescape(v)}"`;
|
|
}
|
|
if (inner === '') {
|
|
return out + '/>';
|
|
}
|
|
return out + '>' + inner + `</${tagName}>`;
|
|
}
|
|
|
|
/**
|
|
* Recursively build XML for a JSON node.
|
|
*
|
|
* @param {string} key Element name, WITHOUT prefix
|
|
* @param {*} node Value
|
|
* @param {!Object} nsMap Map prefix->URI
|
|
* @return {string}
|
|
*/
|
|
function buildNode(key, node, nsMap) {
|
|
if (node == null) {
|
|
return emit(key, {}, '');
|
|
}
|
|
|
|
// Primitive → <key>value</key>
|
|
if (typeof node !== 'object') {
|
|
return emit(key, {}, shaka.util.StringUtils.htmlUnescape(String(node)));
|
|
}
|
|
|
|
// Object case
|
|
const attrs = {};
|
|
let textContent = '';
|
|
const children = [];
|
|
|
|
// Handle prefix groups (namespaced stuff)
|
|
const obj = /** @type {!Object<string,?>} */ (node);
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
if (k === '$value' || k === '$ns') {
|
|
continue;
|
|
}
|
|
|
|
const isPrefixGroup = nsMap[k] !== undefined;
|
|
if (isPrefixGroup && typeof v === 'object' && v != null) {
|
|
const prefix = k;
|
|
const groupObj = v;
|
|
|
|
for (const [subk, subv] of Object.entries(groupObj)) {
|
|
if (Array.isArray(subv)) {
|
|
// Namespaced repeating child elements <prefix:subk>
|
|
for (const item of subv) {
|
|
const tag = `${prefix}:${subk}`;
|
|
children.push(buildNode(tag, item, nsMap));
|
|
}
|
|
} else if (typeof subv === 'object') {
|
|
// Single namespaced child element
|
|
const tag = `${prefix}:${subk}`;
|
|
children.push(buildNode(tag, subv, nsMap));
|
|
} else {
|
|
// Namespaced attribute prefix:attr
|
|
attrs[`${prefix}:${subk}`] = subv;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Regular attribute / child element
|
|
const val = v;
|
|
const isObj = typeof val === 'object' && val != null;
|
|
|
|
if (!isObj) {
|
|
// scalar → attribute
|
|
attrs[k] = val;
|
|
} else {
|
|
// object or array → child
|
|
if (Array.isArray(val)) {
|
|
for (const item of val) {
|
|
children.push(buildNode(k, item, nsMap));
|
|
}
|
|
} else {
|
|
children.push(buildNode(k, val, nsMap));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node['$value'] !== undefined && node['$value'] !== null) {
|
|
textContent = shaka.util.StringUtils.htmlUnescape(node['$value']);
|
|
}
|
|
|
|
return emit(key, attrs, textContent + children.join(''));
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Build namespace map prefix→URI from $ns
|
|
// ------------------------------------------------------------------
|
|
const nsMap = {};
|
|
if (json['$ns'] && typeof json['$ns'] === 'object') {
|
|
for (const [uri, info] of Object.entries(json['$ns'])) {
|
|
const prefix = info.prefix;
|
|
nsMap[prefix] = uri;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Build root attributes: xmlns + xmlns:pref + scalar attrs at root
|
|
// ------------------------------------------------------------------
|
|
const rootAttrs = {
|
|
'xmlns': MPD_NS,
|
|
};
|
|
|
|
// nsMap: prefix→uri
|
|
for (const [prefix, uri] of Object.entries(nsMap)) {
|
|
rootAttrs[`xmlns:${prefix}`] = uri;
|
|
}
|
|
|
|
// Non-object root attributes
|
|
for (const [k, v] of Object.entries(json)) {
|
|
if (k === '$ns') {
|
|
continue;
|
|
}
|
|
if (typeof v !== 'object' || v === null) {
|
|
rootAttrs[k] = v;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Build children of <MPD>
|
|
// ------------------------------------------------------------------
|
|
let mpdInner = '';
|
|
|
|
for (const [k, v] of Object.entries(json)) {
|
|
if (k === '$ns') {
|
|
continue;
|
|
}
|
|
if (k in nsMap) {
|
|
// Namespace group
|
|
const groupObj = /** @type {!Object<string,?>} */ (v);
|
|
|
|
for (const [subk, subv] of Object.entries(groupObj)) {
|
|
if (Array.isArray(subv)) {
|
|
for (const item of subv) {
|
|
const tag = `${k}:${subk}`;
|
|
mpdInner += buildNode(tag, item, nsMap);
|
|
}
|
|
} else if (typeof subv === 'object') {
|
|
const tag = `${k}:${subk}`;
|
|
mpdInner += buildNode(tag, subv, nsMap);
|
|
} else {
|
|
// namespaced attribute on root? (rare)
|
|
rootAttrs[`${k}:${subk}`] = subv;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (typeof v === 'object' && v !== null) {
|
|
if (Array.isArray(v)) {
|
|
for (const item of v) {
|
|
mpdInner += buildNode(k, item, nsMap);
|
|
}
|
|
} else {
|
|
mpdInner += buildNode(k, v, nsMap);
|
|
}
|
|
}
|
|
}
|
|
|
|
return emit('MPD', rootAttrs, mpdInner);
|
|
}
|
|
};
|