ui: show active device type counts in streams header
Parses each session's client string (e.g. "Emby for Roku / Bedroom TV") into a device category using priority-ordered regex matching: Android TV, Fire TV, Apple TV, Android, iOS, Roku, LG, Samsung, Google TV, Kodi, Browser, Desktop. Counts are grouped and sorted by frequency, displayed as compact chips to the right of the server badges: Emby 7 Jellyfin 2 · 3 Android 2 Roku 1 Fire TV 1 iOS Unknown client strings are silently skipped (not shown as "Other"). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
586c7c36c1
commit
bea7b6b045
@@ -1401,6 +1401,26 @@ requestAnimationFrame(vvSyncCardWidths);
|
||||
|
||||
// ── Media streams (slower poll — media server API calls) ──────────────────────
|
||||
|
||||
// Map a session's client string to a human-readable device category
|
||||
function vvDeviceType(client) {
|
||||
const c = (client || '').toLowerCase();
|
||||
// Order matters: more specific patterns first
|
||||
if (/android\s*tv|androidtv|shield|nvidia\s*shield|android.*tv|tv.*android/.test(c)) return 'Android TV';
|
||||
if (/amazon|fire\s*tv|firetv|fire\s*stick|firestick/.test(c)) return 'Fire TV';
|
||||
if (/tvos|apple\s*tv/.test(c)) return 'Apple TV';
|
||||
if (/infuse/.test(c)) return 'Apple TV';
|
||||
if (/android|mobile/.test(c)) return 'Android';
|
||||
if (/ios|iphone|ipad/.test(c)) return 'iOS';
|
||||
if (/roku/.test(c)) return 'Roku';
|
||||
if (/webos|lg\s*tv|lgtv|lg /.test(c)) return 'LG';
|
||||
if (/samsung/.test(c)) return 'Samsung';
|
||||
if (/google\s*tv|googletv|chromecast/.test(c)) return 'Google TV';
|
||||
if (/kodi/.test(c)) return 'Kodi';
|
||||
if (/web|chrome|firefox|safari|browser|edge/.test(c)) return 'Browser';
|
||||
if (/desktop|theater|media\s*player|windows|linux|mac\s*os|macos/.test(c)) return 'Desktop';
|
||||
return null; // unknown — don't show
|
||||
}
|
||||
|
||||
function vvFmtSec(sec) {
|
||||
const h = Math.floor(sec / 3600);
|
||||
const m = Math.floor((sec % 3600) / 60);
|
||||
@@ -1427,6 +1447,20 @@ function vvRenderStreams() {
|
||||
: `<span class="vv-server-badge" style="color:#444;">${n}</span>`;
|
||||
}).join('');
|
||||
|
||||
// Device type summary — e.g. "3 Android 1 iOS 2 Roku"
|
||||
const devCounts = {};
|
||||
sessions.forEach(s => {
|
||||
const t = vvDeviceType(s.client);
|
||||
if (t) devCounts[t] = (devCounts[t] || 0) + 1;
|
||||
});
|
||||
const deviceBar = Object.entries(devCounts)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([t, n]) => `<span class="vv-device-chip">${n} ${t}</span>`)
|
||||
.join('');
|
||||
const deviceSection = deviceBar
|
||||
? `<span class="vv-device-sep">·</span>${deviceBar}`
|
||||
: '';
|
||||
|
||||
if (sessions.length === 0) {
|
||||
el.innerHTML = `<div class="vv-stream-servers">${badges}</div>`
|
||||
+ '<p class="vv-stream-empty">Nothing playing</p>';
|
||||
@@ -1486,7 +1520,7 @@ function vvRenderStreams() {
|
||||
? `<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}</div>
|
||||
el.innerHTML = `<div class="vv-stream-servers">${badges}${deviceSection}</div>
|
||||
<div style="display:flex;gap:10px;">${sCols.map(vvStreamCol).join('')}</div>${overflow}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user