ui: show resolution and codec counts in streams header
Adds height/codec fields to both Emby/Jellyfin and Plex session data, then surfaces them as chip groups (· 2 4K 3 1080p · 4 H.264 2 HEVC) in the streams card header alongside the existing device type bar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
bea7b6b045
commit
89df1a98db
@@ -109,6 +109,12 @@ function vv_fetch_jf_sessions(array $srv): array {
|
|||||||
$method = $pm === 'DirectStream' ? 'Direct Stream' : 'Direct Play';
|
$method = $pm === 'DirectStream' ? 'Direct Stream' : 'Direct Play';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Source video stream — resolution and codec of the file being played
|
||||||
|
$videoStream = null;
|
||||||
|
foreach ($item['MediaStreams'] ?? [] as $ms) {
|
||||||
|
if (($ms['Type'] ?? '') === 'Video') { $videoStream = $ms; break; }
|
||||||
|
}
|
||||||
|
|
||||||
$result[] = [
|
$result[] = [
|
||||||
'server' => $srv['name'],
|
'server' => $srv['name'],
|
||||||
'server_type' => $srv['type'],
|
'server_type' => $srv['type'],
|
||||||
@@ -122,6 +128,8 @@ function vv_fetch_jf_sessions(array $srv): array {
|
|||||||
'pos_sec' => (int)($pos / 10000000),
|
'pos_sec' => (int)($pos / 10000000),
|
||||||
'dur_sec' => (int)($dur / 10000000),
|
'dur_sec' => (int)($dur / 10000000),
|
||||||
'is_tc' => $tc !== null,
|
'is_tc' => $tc !== null,
|
||||||
|
'height' => (int)($videoStream['Height'] ?? 0),
|
||||||
|
'codec' => strtolower($videoStream['Codec'] ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
@@ -160,6 +168,12 @@ function vv_fetch_plex_sessions(array $srv): array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$player = $m['Player'] ?? [];
|
$player = $m['Player'] ?? [];
|
||||||
|
$media0 = $m['Media'][0] ?? [];
|
||||||
|
$plexH = (int)($media0['height'] ?? 0);
|
||||||
|
if (!$plexH && !empty($media0['videoResolution'])) {
|
||||||
|
$vr = strtolower($media0['videoResolution']);
|
||||||
|
$plexH = $vr === '4k' ? 2160 : (int)$vr;
|
||||||
|
}
|
||||||
$result[] = [
|
$result[] = [
|
||||||
'server' => $srv['name'],
|
'server' => $srv['name'],
|
||||||
'server_type' => $srv['type'],
|
'server_type' => $srv['type'],
|
||||||
@@ -173,6 +187,8 @@ function vv_fetch_plex_sessions(array $srv): array {
|
|||||||
'pos_sec' => (int)($offset / 1000),
|
'pos_sec' => (int)($offset / 1000),
|
||||||
'dur_sec' => (int)($dur / 1000),
|
'dur_sec' => (int)($dur / 1000),
|
||||||
'is_tc' => $isTc,
|
'is_tc' => $isTc,
|
||||||
|
'height' => $plexH,
|
||||||
|
'codec' => strtolower($media0['videoCodec'] ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
|
|||||||
@@ -1461,6 +1461,42 @@ function vvRenderStreams() {
|
|||||||
? `<span class="vv-device-sep">·</span>${deviceBar}`
|
? `<span class="vv-device-sep">·</span>${deviceBar}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
// Resolution summary — e.g. "2 4K 3 1080p 1 720p"
|
||||||
|
function vvResLabel(h) {
|
||||||
|
if (h >= 2160) return '4K';
|
||||||
|
if (h >= 1080) return '1080p';
|
||||||
|
if (h >= 720) return '720p';
|
||||||
|
if (h >= 480) return '480p';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const resCounts = {};
|
||||||
|
sessions.forEach(s => {
|
||||||
|
const r = vvResLabel(s.height || 0);
|
||||||
|
if (r) resCounts[r] = (resCounts[r] || 0) + 1;
|
||||||
|
});
|
||||||
|
const resOrder = ['4K', '1080p', '720p', '480p'];
|
||||||
|
const resBar = resOrder.filter(r => resCounts[r])
|
||||||
|
.map(r => `<span class="vv-device-chip">${resCounts[r]} ${r}</span>`)
|
||||||
|
.join('');
|
||||||
|
const resSection = resBar ? `<span class="vv-device-sep">·</span>${resBar}` : '';
|
||||||
|
|
||||||
|
// Codec summary — e.g. "4 H.264 2 HEVC"
|
||||||
|
function vvCodecLabel(c) {
|
||||||
|
if (!c) return null;
|
||||||
|
const m = { h264: 'H.264', hevc: 'HEVC', av1: 'AV1', vp9: 'VP9', mpeg4: 'MPEG-4', mpeg2video: 'MPEG-2' };
|
||||||
|
return m[c] || c.toUpperCase();
|
||||||
|
}
|
||||||
|
const codecCounts = {};
|
||||||
|
sessions.forEach(s => {
|
||||||
|
const c = vvCodecLabel(s.codec || '');
|
||||||
|
if (c) codecCounts[c] = (codecCounts[c] || 0) + 1;
|
||||||
|
});
|
||||||
|
const codecBar = Object.entries(codecCounts)
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.map(([c, n]) => `<span class="vv-device-chip">${n} ${c}</span>`)
|
||||||
|
.join('');
|
||||||
|
const codecSection = codecBar ? `<span class="vv-device-sep">·</span>${codecBar}` : '';
|
||||||
|
|
||||||
if (sessions.length === 0) {
|
if (sessions.length === 0) {
|
||||||
el.innerHTML = `<div class="vv-stream-servers">${badges}</div>`
|
el.innerHTML = `<div class="vv-stream-servers">${badges}</div>`
|
||||||
+ '<p class="vv-stream-empty">Nothing playing</p>';
|
+ '<p class="vv-stream-empty">Nothing playing</p>';
|
||||||
@@ -1520,7 +1556,7 @@ function vvRenderStreams() {
|
|||||||
? `<div style="font-size:10px;color:#555;margin-top:4px;">+${sessions.length - 12} more not shown</div>`
|
? `<div style="font-size:10px;color:#555;margin-top:4px;">+${sessions.length - 12} more not shown</div>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
el.innerHTML = `<div class="vv-stream-servers">${badges}${deviceSection}</div>
|
el.innerHTML = `<div class="vv-stream-servers">${badges}${deviceSection}${resSection}${codecSection}</div>
|
||||||
<div style="display:flex;gap:10px;">${sCols.map(vvStreamCol).join('')}</div>${overflow}`;
|
<div style="display:flex;gap:10px;">${sCols.map(vvStreamCol).join('')}</div>${overflow}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user