Overview

Mcaster1DNAS maintains an in-memory ring buffer of recently played tracks across all mount points. When the "now playing" metadata changes on any source the new entry is appended; when the limit is reached the oldest entry is discarded.

Configuration

YAML

limits:
  song-history-limit: 25   # 0 = keep all tracks (memory-backed, no disk I/O)

XML

<limits>
    <song-history-limit>25</song-history-limit>
</limits>

Default is 25. Set to 0 to keep unlimited history (memory grows with uptime).

Raw XML Endpoint

URL: GET /mcaster1songdata  •  Auth: None (public)  •  Format: application/xml

curl http://localhost:9330/mcaster1songdata

Response Structure

<?xml version="1.0" encoding="UTF-8"?>
<mcaster1songdata>
  <server-name>My Radio Station</server-name>
  <generated>2026-02-22T18:45:00Z</generated>
  <song-count>5</song-count>
  <songs>
    <song>
      <mount>/live</mount>
      <title>Artist - Track Title</title>
      <artist>Artist Name</artist>
      <song-title>Track Title</song-title>
      <started_at>2026-02-22T18:40:00Z</started_at>
      <ended_at>2026-02-22T18:43:30Z</ended_at>
      <duration>210</duration>
      <listeners>42</listeners>
      <codec>audio/mpeg</codec>
      <bitrate>128</bitrate>
      <samplerate>44100</samplerate>
      <channels>2</channels>
    </song>
    <song>
      <mount>/live</mount>
      <title>DJ Set — Live On Air</title>
      <artist></artist>
      <song-title>DJ Set — Live On Air</song-title>
      <started_at>2026-02-22T18:43:30Z</started_at>
      <ended_at></ended_at>
      <duration>-1</duration>
      <listeners>45</listeners>
      <codec>audio/mpeg</codec>
      <bitrate>128</bitrate>
      <samplerate>44100</samplerate>
      <channels>2</channels>
    </song>
  </songs>
</mcaster1songdata>

Field Reference

FieldTypeDescription
mountstringMount point path (e.g. /live, /podcast)
titlestringFull ICY StreamTitle (artist + title combined)
artiststringArtist extracted from Artist - Title format
song-titlestringTrack title extracted from Artist - Title format
started_atISO8601UTC timestamp when track started
ended_atISO8601UTC timestamp when track ended; empty = currently playing
durationintegerSeconds the track played; -1 = still playing
listenersintegerListener count at the moment the track started
codecstringAudio codec content-type string
bitrateintegerStream bitrate in kbps
samplerateintegerSample rate in Hz
channelsintegerChannel count (1=mono, 2=stereo)

HTML Pages

Public Track History

URL: GET /songdata.xsl  •  Auth: None (public)

Displays a rich table of recently played tracks with:

Admin Track History

URL: GET /admin/songdata.xsl  •  Auth: HTTP Basic (admin credentials)

Same as public view with additional admin controls and full codec details.

Filtering by Mount

# History for /live mount only
curl "http://localhost:9330/mcaster1songdata?mount=/live"

# History for /podcast mount only
curl "http://localhost:9330/mcaster1songdata?mount=/podcast"

Integration Examples

JavaScript (web widget)

async function fetchSongHistory(mount = null) {
    const url = mount
        ? `/mcaster1songdata?mount=${encodeURIComponent(mount)}`
        : '/mcaster1songdata';
    const resp = await fetch(url);
    const xml = await resp.text();
    const parser = new DOMParser();
    return parser.parseFromString(xml, 'application/xml');
}

// Get currently playing track
async function getNowPlaying(mount = '/live') {
    const doc = await fetchSongHistory(mount);
    const songs = doc.querySelectorAll('song');
    for (const song of songs) {
        const endedAt = song.querySelector('ended_at')?.textContent;
        if (!endedAt || endedAt.trim() === '') {
            return {
                title:     song.querySelector('title')?.textContent,
                artist:    song.querySelector('artist')?.textContent,
                startedAt: song.querySelector('started_at')?.textContent,
                listeners: song.querySelector('listeners')?.textContent,
            };
        }
    }
    return null;
}

Python

import requests
import xml.etree.ElementTree as ET

def get_song_history(base_url, mount=None):
    url = f"{base_url}/mcaster1songdata"
    if mount:
        url += f"?mount={mount}"
    resp = requests.get(url, timeout=5)
    root = ET.fromstring(resp.text)
    songs = []
    for song in root.findall('.//song'):
        songs.append({
            'mount':      song.findtext('mount'),
            'title':      song.findtext('title'),
            'artist':     song.findtext('artist'),
            'started_at': song.findtext('started_at'),
            'ended_at':   song.findtext('ended_at'),
            'listeners':  int(song.findtext('listeners') or 0),
        })
    return songs

# Example usage
history = get_song_history('http://localhost:9330', mount='/live')
for track in history:
    status = 'NOW PLAYING' if not track['ended_at'] else track['ended_at']
    print(f"{status}  {track['title']}  ({track['listeners']} listeners)")

Deduplication Behavior

Consecutive duplicate titles on the same mount are silently dropped:

18:00  /live  "DJ Live Mix"    ← stored
18:00  /live  "DJ Live Mix"    ← DROPPED (duplicate)
18:05  /live  "DJ Live Mix"    ← DROPPED (still same title)
18:10  /live  "Artist - Track"  ← stored (new title)

Titles on different mounts are never deduplicated against each other:

18:00  /live     "Artist - Track"  ← stored
18:00  /podcast  "Artist - Track"  ← stored (different mount)

ended_at Back-Fill

When track N+1 is added to a mount, the ended_at timestamp of track N is set to the started_at of track N+1. This gives accurate time-on-air calculations in the UI.

The most recent entry on each mount always has an empty ended_at (currently playing).

Memory Footprint

Each song entry occupies approximately 200–400 bytes. With the default limit of 25 entries the total per-server memory overhead is under 10 KB regardless of listener count or stream uptime.

With song-history-limit: 0 (unlimited), the buffer grows by one entry per track change per mount. For a busy station with frequent track changes, budget ~1 MB per 5,000 total track changes.

See Also