From 6e0d37c3bf9df1903532f76c3461ff1918acaf3b Mon Sep 17 00:00:00 2001 From: Draklaw Date: Sun, 3 Dec 2023 15:23:44 +0100 Subject: [PATCH] Make stat xml parsing more robust. --- static/main.js | 168 +++++++++++++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 61 deletions(-) diff --git a/static/main.js b/static/main.js index 6f33805..3a2bad5 100644 --- a/static/main.js +++ b/static/main.js @@ -83,19 +83,23 @@ async function render_stat(mount_point) { function parse_stat(xml) { - return { - nginx_version: xml.querySelector("rtmp>nginx_version").firstChild.data, - nginx_rtmp_version: xml.querySelector("rtmp>nginx_rtmp_version").firstChild.data, - built: xml.querySelector("rtmp>built").firstChild.data, - pid: +xml.querySelector("rtmp>pid").firstChild.data, - uptime: +xml.querySelector("rtmp>uptime").firstChild.data, - naccepted: +xml.querySelector("rtmp>naccepted").firstChild.data, - bw_in: +xml.querySelector("rtmp>bw_in").firstChild.data, - bytes_in: +xml.querySelector("rtmp>bytes_in").firstChild.data, - bw_out: +xml.querySelector("rtmp>bw_out").firstChild.data, - bytes_out: +xml.querySelector("rtmp>bytes_out").firstChild.data, - applications: Object.fromEntries(Array.prototype.map.call(xml.querySelectorAll("rtmp>server>application") || [], parse_stat_application)), + const stat = { + // applications: Object.fromEntries(Array.prototype.map.call(get_xml_items(xml, "rtmp>server>application"), parse_stat_application)), + applications: Object.fromEntries(get_xml_items(xml, "rtmp>server>application", parse_stat_application)), } + + set_item_if_present(stat, "nginx_version", xml, "rtmp>nginx_version", get_xml_string) + set_item_if_present(stat, "nginx_rtmp_version", xml, "rtmp>nginx_rtmp_version", get_xml_string) + set_item_if_present(stat, "built", xml, "rtmp>built", get_xml_string) + set_item_if_present(stat, "pid", xml, "rtmp>pid", get_xml_number) + set_item_if_present(stat, "uptime", xml, "rtmp>uptime", get_xml_number) + set_item_if_present(stat, "naccepted", xml, "rtmp>naccepted", get_xml_number) + set_item_if_present(stat, "bw_in", xml, "rtmp>bw_in", get_xml_number) + set_item_if_present(stat, "bytes_in", xml, "rtmp>bytes_in", get_xml_number) + set_item_if_present(stat, "bw_out", xml, "rtmp>bw_out", get_xml_number) + set_item_if_present(stat, "bytes_out", xml, "rtmp>bytes_out", get_xml_number) + + return stat } function parse_stat_application(xml) { @@ -103,65 +107,107 @@ function parse_stat_application(xml) { xml.querySelector("application>name").firstChild.data, { // nclients: +xml.querySelector("application>live>nclients").firstChild.data, - streams: Object.fromEntries(Array.prototype.map.call(xml.querySelectorAll("application>live>stream") || [], parse_stat_stream)) + // streams: Object.fromEntries(Array.prototype.map.call(xml.querySelectorAll("application>live>stream") || [], parse_stat_stream)) + streams: Object.fromEntries(get_xml_items(xml, "application>live>stream", parse_stat_stream)), } ] } function parse_stat_stream(xml) { - const name = xml.querySelector("stream>name").firstChild.data - const clients = Array.prototype.map.call(xml.querySelectorAll("stream>client") || [], parse_stat_client) + // const clients = Array.prototype.map.call(get_xml_items(xml, "stream>client"), parse_stat_client) + const clients = get_xml_items(xml, "stream>client", parse_stat_client) const publishers = clients.filter(client => client.is_publishing) - const audio = xml.querySelector("stream>meta>audio")? - { - codec: xml.querySelector("stream>meta>audio>codec").firstChild.data, - profile: xml.querySelector("stream>meta>audio>profile").firstChild.data, - channels: +xml.querySelector("stream>meta>audio>channels").firstChild.data, - sample_rate: +xml.querySelector("stream>meta>audio>sample_rate").firstChild.data, - }: - null - const video = xml.querySelector("stream>meta>video")? - { - width: +xml.querySelector("stream>meta>video>width").firstChild.data, - height: +xml.querySelector("stream>meta>video>height").firstChild.data, - frame_rate: +xml.querySelector("stream>meta>video>frame_rate").firstChild.data, - codec: xml.querySelector("stream>meta>video>codec").firstChild.data, - profile: xml.querySelector("stream>meta>video>profile").firstChild.data, - compat: xml.querySelector("stream>meta>video>compat").firstChild.data, - level: xml.querySelector("stream>meta>video>level").firstChild.data, - }: - null - return [ - name, - { - name: name, - time: +xml.querySelector("stream>time").firstChild.data, - bw_in: +xml.querySelector("stream>bw_in").firstChild.data, - bytes_in: +xml.querySelector("stream>bytes_in").firstChild.data, - bw_out: +xml.querySelector("stream>bw_out").firstChild.data, - bytes_out: +xml.querySelector("stream>bytes_out").firstChild.data, - bw_audio: +xml.querySelector("stream>bw_audio").firstChild.data, - bw_video: +xml.querySelector("stream>bw_video").firstChild.data, - audio: audio, - video: video, - publisher: publishers.length? publishers[0]: null, - viewers: clients.filter(client => !client.is_publishing), - } - ] + const stream = { + name: get_xml_item(xml, "stream>name", get_xml_string), + publisher: publishers.length? publishers[0]: null, + viewers: clients.filter(client => !client.is_publishing), + } + + // const audio = xml.querySelector("stream>meta>audio")? + // { + // codec: xml.querySelector("stream>meta>audio>codec").firstChild.data, + // profile: xml.querySelector("stream>meta>audio>profile").firstChild.data, + // channels: +xml.querySelector("stream>meta>audio>channels").firstChild.data, + // sample_rate: +xml.querySelector("stream>meta>audio>sample_rate").firstChild.data, + // }: + // null + // const video = xml.querySelector("stream>meta>video")? + // { + // width: +xml.querySelector("stream>meta>video>width").firstChild.data, + // height: +xml.querySelector("stream>meta>video>height").firstChild.data, + // frame_rate: +xml.querySelector("stream>meta>video>frame_rate").firstChild.data, + // codec: xml.querySelector("stream>meta>video>codec").firstChild.data, + // profile: xml.querySelector("stream>meta>video>profile").firstChild.data, + // compat: xml.querySelector("stream>meta>video>compat").firstChild.data, + // level: xml.querySelector("stream>meta>video>level").firstChild.data, + // }: + // null + + set_item_if_present(stream, "time", xml, "stream>time", get_xml_number) + set_item_if_present(stream, "bw_in", xml, "stream>bw_in", get_xml_number) + set_item_if_present(stream, "bytes_in", xml, "stream>bytes_in", get_xml_number) + set_item_if_present(stream, "bw_out", xml, "stream>bw_out", get_xml_number) + set_item_if_present(stream, "bytes_out", xml, "stream>bytes_out", get_xml_number) + set_item_if_present(stream, "bw_audio", xml, "stream>bw_audio", get_xml_number) + set_item_if_present(stream, "bw_video", xml, "stream>bw_video", get_xml_number) + + return [stream.name, stream] } function parse_stat_client(xml) { - return { - name: xml.querySelector("client>id").firstChild.data, - address: xml.querySelector("client>address").firstChild.data, - time: xml.querySelector("client>time").firstChild.data, - flashver: xml.querySelector("client>flashver").firstChild.data, - // swfurl: xml.querySelector("client>swfurl").firstChild.data, - dropped: xml.querySelector("client>dropped").firstChild.data, - avsync: xml.querySelector("client>avsync").firstChild.data, - timestamp: xml.querySelector("client>timestamp").firstChild.data, - is_publishing: !!xml.querySelector("client>publishing"), + const client = { + name: get_xml_item(xml, "client>id", get_xml_string), + is_publishing: get_xml_item(xml, "client>publishing", v=>v, null), + } + + set_item_if_present(client, "address", xml, "client>address", get_xml_string) + set_item_if_present(client, "time", xml, "client>time", get_xml_number) + set_item_if_present(client, "flashver", xml, "client>flashver", get_xml_string) + // set_item_if_present(client, "swfurl", xml, "client>swfurl", get_xml_string) + set_item_if_present(client, "dropped", xml, "client>dropped", get_xml_number) + set_item_if_present(client, "avsync", xml, "client>avsync", get_xml_number) + set_item_if_present(client, "timestamp", xml, "client>timestamp", get_xml_number) + + return client +} + + +function get_xml_item(xml, path, factory=v=>v, default_value=undefined) { + const item = xml.querySelector(path) + if (!item && default_value === undefined) { + console.error(`${path} is not defined`, xml) + throw new Error(`${path} is not defined`) } + return item? factory(item): default_value; +} + + +function get_xml_items(xml, path, factory=v=>v) { + const items = xml.querySelectorAll(path) + if (!items) { + return [] + } + return Array.prototype.map.call(items, factory); +} + +function set_item_if_present(dst, key, xml, path, factory=v=>v) { + const value = get_xml_item(xml, path, factory, null) + if (value !== null) { + dst[key] = value + } +} + + +function get_xml_string(xml) { + return xml.firstChild.data +} + +function get_xml_number(xml) { + return +xml.firstChild.data +} + +function do_xml_item_exists(xml) { + return !!xml }