|
|
@ -83,19 +83,23 @@ async function render_stat(mount_point) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parse_stat(xml) { |
|
|
function parse_stat(xml) { |
|
|
return { |
|
|
const stat = { |
|
|
nginx_version: xml.querySelector("rtmp>nginx_version").firstChild.data, |
|
|
// applications: Object.fromEntries(Array.prototype.map.call(get_xml_items(xml, "rtmp>server>application"), parse_stat_application)),
|
|
|
nginx_rtmp_version: xml.querySelector("rtmp>nginx_rtmp_version").firstChild.data, |
|
|
applications: Object.fromEntries(get_xml_items(xml, "rtmp>server>application", parse_stat_application)), |
|
|
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)), |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
function parse_stat_application(xml) { |
|
|
@ -103,65 +107,107 @@ function parse_stat_application(xml) { |
|
|
xml.querySelector("application>name").firstChild.data, |
|
|
xml.querySelector("application>name").firstChild.data, |
|
|
{ |
|
|
{ |
|
|
// nclients: +xml.querySelector("application>live>nclients").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) { |
|
|
function parse_stat_stream(xml) { |
|
|
const name = xml.querySelector("stream>name").firstChild.data |
|
|
// const clients = Array.prototype.map.call(get_xml_items(xml, "stream>client"), parse_stat_client)
|
|
|
const clients = Array.prototype.map.call(xml.querySelectorAll("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 publishers = clients.filter(client => client.is_publishing) |
|
|
const audio = xml.querySelector("stream>meta>audio")? |
|
|
const stream = { |
|
|
{ |
|
|
name: get_xml_item(xml, "stream>name", get_xml_string), |
|
|
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, |
|
|
publisher: publishers.length? publishers[0]: null, |
|
|
viewers: clients.filter(client => !client.is_publishing), |
|
|
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) { |
|
|
function parse_stat_client(xml) { |
|
|
return { |
|
|
const client = { |
|
|
name: xml.querySelector("client>id").firstChild.data, |
|
|
name: get_xml_item(xml, "client>id", get_xml_string), |
|
|
address: xml.querySelector("client>address").firstChild.data, |
|
|
is_publishing: get_xml_item(xml, "client>publishing", v=>v, null), |
|
|
time: xml.querySelector("client>time").firstChild.data, |
|
|
} |
|
|
flashver: xml.querySelector("client>flashver").firstChild.data, |
|
|
|
|
|
// swfurl: xml.querySelector("client>swfurl").firstChild.data,
|
|
|
set_item_if_present(client, "address", xml, "client>address", get_xml_string) |
|
|
dropped: xml.querySelector("client>dropped").firstChild.data, |
|
|
set_item_if_present(client, "time", xml, "client>time", get_xml_number) |
|
|
avsync: xml.querySelector("client>avsync").firstChild.data, |
|
|
set_item_if_present(client, "flashver", xml, "client>flashver", get_xml_string) |
|
|
timestamp: xml.querySelector("client>timestamp").firstChild.data, |
|
|
// set_item_if_present(client, "swfurl", xml, "client>swfurl", get_xml_string)
|
|
|
is_publishing: !!xml.querySelector("client>publishing"), |
|
|
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 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|