diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31bfb0e5..58a69865 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -149,6 +149,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Each strategy button has a short tooltip; **Advanced** mode adds an optional **Fine adjustment** toggle (0.01× / 0.01 st steps) in **Settings → Audio**.
+
+### Now Playing — live status dot in "Who is listening?"
+
+**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1086](https://github.com/Psychotoxical/psysonic/pull/1086)**
+
+* Each listener in the **Who is listening?** popover now shows a small status dot — playing, paused, or idle — derived from the live playback report, replacing the previous "minutes ago" line. The status is also read out for screen readers and on hover, so it is never conveyed by colour alone.
+
+
## Changed
### Dependencies — npm and Rust refresh
@@ -219,6 +227,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Fixed
+### Internet Radio — station management limited to server admins
+
+**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1086](https://github.com/Psychotoxical/psysonic/pull/1086)**
+
+* Navidrome 0.62 made creating, editing and deleting radio stations admin-only, so those actions failed for standard accounts. Add, edit and delete controls are now hidden for non-admin Navidrome users; playback and favourites stay available to everyone. Other server types are unaffected.
+
+
### Music Network — self-hosted scrobbling reaches the server
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1085](https://github.com/Psychotoxical/psysonic/pull/1085)**
diff --git a/src/api/nowPlayingPresence.test.ts b/src/api/nowPlayingPresence.test.ts
new file mode 100644
index 00000000..50efeb96
--- /dev/null
+++ b/src/api/nowPlayingPresence.test.ts
@@ -0,0 +1,29 @@
+import { describe, expect, it } from 'vitest';
+import { nowPlayingPresence, NOW_PLAYING_IDLE_MINUTES } from './nowPlayingPresence';
+import type { SubsonicNowPlaying, PlaybackReportState } from './subsonicTypes';
+
+// The function only reads `state` and `minutesAgo`; cast a minimal fixture.
+function entry(partial: { state?: PlaybackReportState; minutesAgo?: number }): SubsonicNowPlaying {
+ return { minutesAgo: 0, ...partial } as SubsonicNowPlaying;
+}
+
+describe('nowPlayingPresence', () => {
+ it('maps live playbackReport state authoritatively', () => {
+ expect(nowPlayingPresence(entry({ state: 'playing' }))).toBe('playing');
+ expect(nowPlayingPresence(entry({ state: 'starting' }))).toBe('playing');
+ expect(nowPlayingPresence(entry({ state: 'paused' }))).toBe('paused');
+ expect(nowPlayingPresence(entry({ state: 'stopped' }))).toBe('idle');
+ });
+
+ it('live state wins over a stale minutesAgo', () => {
+ // A paused session last reported minutes ago is still "paused", not idle.
+ expect(nowPlayingPresence(entry({ state: 'paused', minutesAgo: 99 }))).toBe('paused');
+ expect(nowPlayingPresence(entry({ state: 'playing', minutesAgo: 99 }))).toBe('playing');
+ });
+
+ it('falls back to recency for legacy entries without a state', () => {
+ expect(nowPlayingPresence(entry({ minutesAgo: 0 }))).toBe('playing');
+ expect(nowPlayingPresence(entry({ minutesAgo: NOW_PLAYING_IDLE_MINUTES }))).toBe('playing');
+ expect(nowPlayingPresence(entry({ minutesAgo: NOW_PLAYING_IDLE_MINUTES + 1 }))).toBe('idle');
+ });
+});
diff --git a/src/api/nowPlayingPresence.ts b/src/api/nowPlayingPresence.ts
new file mode 100644
index 00000000..b2ca65fa
--- /dev/null
+++ b/src/api/nowPlayingPresence.ts
@@ -0,0 +1,34 @@
+import type { SubsonicNowPlaying } from './subsonicTypes';
+
+/**
+ * Derived liveness of a now-playing entry, surfaced as the indicator dot in the
+ * "Who is listening?" popover. Unifies the two sources the server can provide:
+ * the OpenSubsonic `playbackReport` transport state (Navidrome ≥ 0.62, when
+ * present) and the classic `getNowPlaying` `minutesAgo` recency (legacy
+ * fallback). This replaces rendering the raw "Nm ago" line.
+ */
+export type NowPlayingPresence = 'playing' | 'paused' | 'idle';
+
+/**
+ * Minutes since last activity beyond which a legacy entry (one without
+ * playbackReport transport state) is treated as idle rather than actively
+ * playing. Entries that carry a live `state` ignore this entirely.
+ */
+export const NOW_PLAYING_IDLE_MINUTES = 5;
+
+/** Resolve the liveness an entry should display. Live `state` is authoritative;
+ * otherwise recency (`minutesAgo`) decides playing vs idle. */
+export function nowPlayingPresence(entry: SubsonicNowPlaying): NowPlayingPresence {
+ // playbackReport extension: trust the reported transport state.
+ switch (entry.state) {
+ case 'playing':
+ case 'starting':
+ return 'playing';
+ case 'paused':
+ return 'paused';
+ case 'stopped':
+ return 'idle';
+ }
+ // Legacy getNowPlaying: only recency is known. Recent = playing, stale = idle.
+ return entry.minutesAgo > NOW_PLAYING_IDLE_MINUTES ? 'idle' : 'playing';
+}
diff --git a/src/components/NowPlayingDropdown.tsx b/src/components/NowPlayingDropdown.tsx
index 366a0189..8a94a37d 100644
--- a/src/components/NowPlayingDropdown.tsx
+++ b/src/components/NowPlayingDropdown.tsx
@@ -4,7 +4,8 @@ import { getNowPlaying } from '../api/subsonicScrobble';
import type { SubsonicNowPlaying } from '../api/subsonicTypes';
import React, { useState, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
-import { PlayCircle, Pause, User, Clock, Radio, RefreshCw } from 'lucide-react';
+import { PlayCircle, Pause, User, Radio, RefreshCw } from 'lucide-react';
+import { nowPlayingPresence } from '../api/nowPlayingPresence';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { useTranslation } from 'react-i18next';
@@ -192,7 +193,10 @@ export default function NowPlayingDropdown() {
) : (