Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,10 @@ Options:
--config <path> Read defaults from a JSON config file. Any CLI
flag passed alongside it overrides the file's
value. See "Configuration file" below.
--minimal-ui Slim HTML status page: keep stream links, listener
counts and previews; hide input/port/rate/version
rows, external-player URLs, recorder cards and
Now Playing. API endpoints are unchanged.
--keep-alive Keep HTTP outputs available after stdin reaches EOF.
If stdin is a FIFO (mkfifo), the reader also
re-opens it on EOF so a new producer can attach;
Expand Down
4 changes: 4 additions & 0 deletions Sources/LiveAudioServer/LiveAudioServerApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ func printUsage() {
--config <path> Read defaults from a JSON config file. Any CLI
flag passed alongside it overrides the file's
value. See the README for the full schema.
--minimal-ui Slim HTML status page: keep stream links, listener
counts and previews; hide input/port/rate/version
rows, external-player URLs, recorder cards and
Now Playing. API endpoints are unchanged.
--keep-alive Keep HTTP outputs available after stdin reaches EOF.
If stdin is a FIFO (created with mkfifo), the
reader also re-opens it on EOF so a new
Expand Down
2 changes: 2 additions & 0 deletions Sources/LiveAudioServerCore/CLIParse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ public func parseCLI(_ args: [String]) -> CLIParseResult {
config.httpAuthRealm = r
case "--keep-alive":
config.keepAliveOnInputEnd = true
case "--minimal-ui":
config.minimalUI = true
case "--exit-with-parent":
config.exitWithParent = true
case "--no-fifo-reopen":
Expand Down
6 changes: 6 additions & 0 deletions Sources/LiveAudioServerCore/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ public struct ServerConfig {
/// `LiveAudioServerApp`.
public var exitWithParent: Bool = false
public var keepAliveOnInputEnd: Bool = false
/// Slim down the HTML status page for host apps that embed it: keeps the
/// stream links, listener counts and audio previews, and drops the
/// input/port/rate/version rows, the external-player URL card, the
/// recorder cards and the Now Playing card. The JSON/API endpoints are
/// unaffected. CLI: `--minimal-ui`.
public var minimalUI: Bool = false
/// When the input stream is a FIFO/named pipe and `keepAliveOnInputEnd` is
/// on, re-`open()` the same path on EOF so a new producer can attach.
/// Plain pipes (e.g. shell `|`) cannot be reopened — this only takes
Expand Down
75 changes: 43 additions & 32 deletions Sources/LiveAudioServerCore/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ func statusPage(config: ServerConfig,
}

var recorderCards = ""
if config.enableMP3 { recorderCards += recorderCard("mp3", label: "MP3") }
if config.enableAAC { recorderCards += recorderCard("aac", label: "AAC") }
if !config.minimalUI && config.enableMP3 { recorderCards += recorderCard("mp3", label: "MP3") }
if !config.minimalUI && config.enableAAC { recorderCards += recorderCard("aac", label: "AAC") }

var urlRows = ""
if config.enableMP3 {
Expand Down Expand Up @@ -257,6 +257,44 @@ func statusPage(config: ServerConfig,
df.locale = Locale(identifier: "en_US_POSIX")
let startedStr = df.string(from: startedAt)

let urlCard: String = config.minimalUI ? "" : """
<div class="card">
<p class="label" style="margin-bottom:8px">Stream URLs for external players</p>
\(urlRows)
</div>
"""
let nowPlayingCard: String = config.minimalUI ? "" : """
<div class="card" id="now-playing-card" style="display:none">
<p class="label" style="margin-bottom:8px">Now Playing</p>
<div class="row"><span class="label">Title</span><span class="value" id="np-title">—</span></div>
<div class="row"><span class="label">Artist</span><span class="value" id="np-artist">—</span></div>
<div class="row"><span class="label">Station</span><span class="value" id="np-station">—</span></div>
<div class="row"><span class="label">Note</span><span class="value" id="np-note">—</span></div>
<div class="row"><span class="label">Updated</span><span class="value" id="np-updated">—</span></div>
</div>
"""
let detailRows: String = config.minimalUI ? "" : """
<div class="row">
<span class="label">Input Source</span>
<span class="value">\(config.inputSource.displayName)</span>
</div>
<div class="row">
<span class="label">Port</span>
<span class="value">\(config.port)</span>
</div>
<div class="row">
<span class="label">Sample Rate</span>
<span class="value">\(config.sampleRate) Hz</span>
</div>
<div class="row">
<span class="label">Channels</span>
<span class="value">\(config.channels == 1 ? "Mono" : "Stereo")</span>
</div>
\(row("Version", "\(liveAudioServerVersion) (\(liveAudioServerGitSHA))"))
\(row("Started", "\(startedStr) · up \(uptimeStr)"))
"""


let html = """
<!DOCTYPE html>
<html>
Expand Down Expand Up @@ -336,46 +374,19 @@ func statusPage(config: ServerConfig,
<h1>🎙 LiveAudioServer</h1>
<p class="sub">Live audio streaming — listener counts update every 5s</p>

<div class="row">
<span class="label">Input Source</span>
<span class="value">\(config.inputSource.displayName)</span>
</div>
<div class="row">
<span class="label">Port</span>
<span class="value">\(config.port)</span>
</div>
<div class="row">
<span class="label">Sample Rate</span>
<span class="value">\(config.sampleRate) Hz</span>
</div>
<div class="row">
<span class="label">Channels</span>
<span class="value">\(config.channels == 1 ? "Mono" : "Stereo")</span>
</div>
\(row("Version", "\(liveAudioServerVersion) (\(liveAudioServerGitSHA))"))
\(row("Started", "\(startedStr) · up \(uptimeStr)"))
\(detailRows)
\(streamRows)
</div>

<div class="card">
\(players)
</div>

<div class="card">
<p class="label" style="margin-bottom:8px">Stream URLs for external players</p>
\(urlRows)
</div>
\(urlCard)

\(recorderCards)

<div class="card" id="now-playing-card" style="display:none">
<p class="label" style="margin-bottom:8px">Now Playing</p>
<div class="row"><span class="label">Title</span><span class="value" id="np-title">—</span></div>
<div class="row"><span class="label">Artist</span><span class="value" id="np-artist">—</span></div>
<div class="row"><span class="label">Station</span><span class="value" id="np-station">—</span></div>
<div class="row"><span class="label">Note</span><span class="value" id="np-note">—</span></div>
<div class="row"><span class="label">Updated</span><span class="value" id="np-updated">—</span></div>
</div>
\(nowPlayingCard)

<script>
// Poll listener counts (and now-playing) in place so the <audio>
Expand Down
20 changes: 20 additions & 0 deletions Tests/LiveAudioServerTests/CLIParseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ struct CLIParseTests {
return nil
}

@Test("--minimal-ui sets the flag and is off by default")
func minimalUI() {
#expect(runConfig([])?.minimalUI == false)
#expect(runConfig(["--minimal-ui"])?.minimalUI == true)
}

@Test("Minimal status page drops detail, URL, recorder and Now Playing cards")
func minimalStatusPage() {
var cfg = ServerConfig()
let full = String(decoding: statusPage(config: cfg, mp3Clients: 0, m4aClients: 0, startedAt: Date()), as: UTF8.self)
cfg.minimalUI = true
let slim = String(decoding: statusPage(config: cfg, mp3Clients: 0, m4aClients: 0, startedAt: Date()), as: UTF8.self)
for marker in ["Input Source", "Stream URLs for external players", "rec-mp3-card", "rec-aac-card", "now-playing-card\" style"] {
#expect(full.contains(marker), "full page should contain \(marker)")
#expect(!slim.contains(marker), "minimal page should not contain \(marker)")
}
#expect(slim.contains("mp3-listeners"))
#expect(slim.contains("<audio"))
}

@Test("No arguments yields defaults")
func emptyArgs() {
let cfg = runConfig([])
Expand Down
Loading