Compare commits
4
Commits
ca7b8b7e6f
..
1.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fee31c87b8 | ||
|
|
5369532ab4 | ||
|
|
42b915bd25 | ||
|
|
d60a8bd7fb |
@@ -2,7 +2,7 @@ name: build-main
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
# Manual only. Dispatch against main: build, run the offline test suite,
|
# Manual only. Dispatch against main: build, run the offline test suite,
|
||||||
# publish to the release Attic cache.
|
# publish to the release Attic cache and live-update manifest.
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -74,3 +74,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
attic login radiantwave https://attic.radiantwavetech.com "$ATTIC_TOKEN"
|
attic login radiantwave https://attic.radiantwavetech.com "$ATTIC_TOKEN"
|
||||||
attic push radiantwave:radiantwave ./result-pkg
|
attic push radiantwave:radiantwave ./result-pkg
|
||||||
|
|
||||||
|
# Publish the manifest production kiosks poll for live app updates.
|
||||||
|
# `storePath` must match exactly the closure pushed to the release Attic
|
||||||
|
# cache above (readlink resolves the result-pkg out-link symlink to its
|
||||||
|
# real /nix/store path), so a kiosk can `nix copy --from` it without ever
|
||||||
|
# building from source. Upload mirrors the ISO scp pattern to the
|
||||||
|
# fileserver via the build-server's `radiantwave-infra` SSH alias.
|
||||||
|
- name: Publish manifest
|
||||||
|
run: |
|
||||||
|
STOREPATH=$(readlink -f ./result-pkg)
|
||||||
|
printf '{"version":"%s","storePath":"%s"}\n' "${{ env.APP_VERSION }}" "$STOREPATH" > manifest.json
|
||||||
|
cat manifest.json
|
||||||
|
scp manifest.json \
|
||||||
|
radiantwave-infra:/mnt/infrastructure_data/files/radiantwave/manifest.json
|
||||||
|
|||||||
+47
-20
@@ -512,9 +512,19 @@ namespace radiantwave::audio
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be called with mutex_ held. Destroys the current mixer and creates a
|
// Must be called with mutex_ held. Creates a fresh mixer on the given
|
||||||
// fresh one on the given device, recreating the two track slots and
|
// device, recreating the two track slots and resuming the active track if
|
||||||
// resuming the active track if one was playing.
|
// one was playing.
|
||||||
|
//
|
||||||
|
// The new mixer is created BEFORE the current one is destroyed. Reversing
|
||||||
|
// the order (destroy-then-reopen) quits the whole SDL audio subsystem the
|
||||||
|
// moment the last device closes: MIX_DestroyMixer() follows up with
|
||||||
|
// SDL_QuitSubSystem(SDL_INIT_AUDIO), which tears down SDL's device registry
|
||||||
|
// and renumbers every enumerated device id on the way back up. The id this
|
||||||
|
// switch was asked to open is then stale and the reopen fails with
|
||||||
|
// "Invalid audio device instance ID". Keeping the current device open while
|
||||||
|
// the replacement opens also means a failed switch leaves the current
|
||||||
|
// output playing instead of going permanently mute.
|
||||||
bool AudioManager::recreateMixerOnDevice(SDL_AudioDeviceID targetId)
|
bool AudioManager::recreateMixerOnDevice(SDL_AudioDeviceID targetId)
|
||||||
{
|
{
|
||||||
// Capture the playback state we want to preserve across the switch.
|
// Capture the playback state we want to preserve across the switch.
|
||||||
@@ -523,7 +533,37 @@ namespace radiantwave::audio
|
|||||||
? slots_[activeSlot_].fadeInDuration
|
? slots_[activeSlot_].fadeInDuration
|
||||||
: 0.0f;
|
: 0.0f;
|
||||||
|
|
||||||
// Tear down the existing mixer (its tracks die with it).
|
// Open the replacement mixer on the target device first, while the
|
||||||
|
// current device (and SDL's device registry) is still alive.
|
||||||
|
MIX_Mixer *newMixer = MIX_CreateMixerDevice(targetId, nullptr);
|
||||||
|
if (!newMixer)
|
||||||
|
{
|
||||||
|
std::string err = SDL_GetError();
|
||||||
|
logger::Logger::instance().error(
|
||||||
|
"Audio",
|
||||||
|
"Could not open audio output device "
|
||||||
|
+ mapToFriendlyDeviceName(resolvePlaybackDeviceName(targetId))
|
||||||
|
+ "; keeping the current device (MIX_CreateMixerDevice error: "
|
||||||
|
+ err + ")");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MIX_Track *newTracks[2];
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
newTracks[i] = MIX_CreateTrack(newMixer);
|
||||||
|
}
|
||||||
|
if (!newTracks[0] || !newTracks[1])
|
||||||
|
{
|
||||||
|
MIX_DestroyMixer(newMixer);
|
||||||
|
logger::Logger::instance().error(
|
||||||
|
"Audio", "Could not create mixer tracks; keeping the current device (SDL error: "
|
||||||
|
+ std::string(SDL_GetError()) + ")");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tear down the existing mixer (its tracks die with it) and adopt the
|
||||||
|
// new one.
|
||||||
for (int i = 0; i < 2; ++i)
|
for (int i = 0; i < 2; ++i)
|
||||||
{
|
{
|
||||||
slots_[i].track = nullptr;
|
slots_[i].track = nullptr;
|
||||||
@@ -539,22 +579,9 @@ namespace radiantwave::audio
|
|||||||
mixer_ = nullptr;
|
mixer_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
mixer_ = MIX_CreateMixerDevice(targetId, nullptr);
|
mixer_ = newMixer;
|
||||||
if (!mixer_)
|
slots_[0].track = newTracks[0];
|
||||||
{
|
slots_[1].track = newTracks[1];
|
||||||
initialized_ = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
slots_[0].track = MIX_CreateTrack(mixer_);
|
|
||||||
slots_[1].track = MIX_CreateTrack(mixer_);
|
|
||||||
if (!slots_[0].track || !slots_[1].track)
|
|
||||||
{
|
|
||||||
MIX_DestroyMixer(mixer_);
|
|
||||||
mixer_ = nullptr;
|
|
||||||
initialized_ = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float gain = enabled_ ? (volume_ / 100.0f) : 0.0f;
|
float gain = enabled_ ? (volume_ / 100.0f) : 0.0f;
|
||||||
MIX_SetMixerGain(mixer_, gain);
|
MIX_SetMixerGain(mixer_, gain);
|
||||||
|
|||||||
@@ -111,8 +111,11 @@ private:
|
|||||||
// Recreates the mixer on the given playback device id (0 = system default).
|
// Recreates the mixer on the given playback device id (0 = system default).
|
||||||
// Called with mutex_ held. Preserves playback state: whichever track was
|
// Called with mutex_ held. Preserves playback state: whichever track was
|
||||||
// playing on the active slot is reloaded and resumed at full gain, an
|
// playing on the active slot is reloaded and resumed at full gain, an
|
||||||
// in-progress fade is restarted. Returns false on failure (previous mixer is
|
// in-progress fade is restarted. The replacement mixer is opened before the
|
||||||
// left destroyed but the app remains safe to keep running muted).
|
// current one is destroyed so SDL's device ids stay valid (destroy-first
|
||||||
|
// quits the whole audio subsystem and orphans the id being opened).
|
||||||
|
// Returns false on failure without touching the current mixer, so the
|
||||||
|
// existing output keeps playing.
|
||||||
bool recreateMixerOnDevice(SDL_AudioDeviceID targetId);
|
bool recreateMixerOnDevice(SDL_AudioDeviceID targetId);
|
||||||
|
|
||||||
config::ConfigManager* configManager_ = nullptr;
|
config::ConfigManager* configManager_ = nullptr;
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ void AudioSettingsPage::applyDeviceSelection() {
|
|||||||
if (ok) {
|
if (ok) {
|
||||||
statusMessage_ = "Switched to " + devices_[currentDeviceIndex_].friendly_name;
|
statusMessage_ = "Switched to " + devices_[currentDeviceIndex_].friendly_name;
|
||||||
} else {
|
} else {
|
||||||
statusMessage_ = "Device unavailable - using system default";
|
statusMessage_ = "Device unavailable - kept the current output";
|
||||||
}
|
}
|
||||||
refreshDeviceAndTrackLists();
|
refreshDeviceAndTrackLists();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,16 @@ void Home::render(SDL_Renderer *renderer)
|
|||||||
SDL_RenderFillRect(renderer, &line);
|
SDL_RenderFillRect(renderer, &line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Large bold reminder to connect WiFi, drawn below the menu while no
|
||||||
|
// network is joined (refreshed every 5s in update()).
|
||||||
|
if (wifiNote_.texture && ssid_.empty())
|
||||||
|
{
|
||||||
|
float noteX = (fw - wifiNote_.width) / 2.0f;
|
||||||
|
float noteY = settingsY_ + settings_.height + 40.0f;
|
||||||
|
SDL_FRect noteDst{noteX, noteY, wifiNote_.width, wifiNote_.height};
|
||||||
|
SDL_RenderTexture(renderer, wifiNote_.texture, nullptr, ¬eDst);
|
||||||
|
}
|
||||||
|
|
||||||
// Bottom-right status footer (20pt, aligned to a virtual rectangle:
|
// Bottom-right status footer (20pt, aligned to a virtual rectangle:
|
||||||
// names left-aligned at the rect's left edge, values right-aligned at
|
// names left-aligned at the rect's left edge, values right-aligned at
|
||||||
// the rect's right edge; the longest name's colon sits 4 spaces from its
|
// the rect's right edge; the longest name's colon sits 4 spaces from its
|
||||||
@@ -352,6 +362,15 @@ void Home::createTextures(AppState &state)
|
|||||||
|
|
||||||
TTF_CloseFont(font);
|
TTF_CloseFont(font);
|
||||||
|
|
||||||
|
font = TTF_OpenFont("assets/fonts/Roboto-Regular.ttf", 48);
|
||||||
|
if (font)
|
||||||
|
{
|
||||||
|
TTF_SetFontStyle(font, TTF_STYLE_BOLD);
|
||||||
|
wifiNote_ = renderText(state.renderer, font, "Please connect to WiFi", white);
|
||||||
|
TTF_SetFontStyle(font, TTF_STYLE_NORMAL);
|
||||||
|
TTF_CloseFont(font);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Surface *bgSurface = IMG_Load("assets/images/RWT-Circular-Color-ForOnDark-NoText.png");
|
SDL_Surface *bgSurface = IMG_Load("assets/images/RWT-Circular-Color-ForOnDark-NoText.png");
|
||||||
if (bgSurface)
|
if (bgSurface)
|
||||||
{
|
{
|
||||||
@@ -410,6 +429,11 @@ void Home::destroyTextures()
|
|||||||
SDL_DestroyTexture(settings_.texture);
|
SDL_DestroyTexture(settings_.texture);
|
||||||
settings_.texture = nullptr;
|
settings_.texture = nullptr;
|
||||||
}
|
}
|
||||||
|
if (wifiNote_.texture)
|
||||||
|
{
|
||||||
|
SDL_DestroyTexture(wifiNote_.texture);
|
||||||
|
wifiNote_.texture = nullptr;
|
||||||
|
}
|
||||||
for (auto &t : footerNames_)
|
for (auto &t : footerNames_)
|
||||||
t.destroy();
|
t.destroy();
|
||||||
for (auto &t : footerValues_)
|
for (auto &t : footerValues_)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ private:
|
|||||||
TextLine play_;
|
TextLine play_;
|
||||||
TextLine settings_;
|
TextLine settings_;
|
||||||
TextLine bg_;
|
TextLine bg_;
|
||||||
|
TextLine wifiNote_;
|
||||||
|
|
||||||
AppState* state_ = nullptr;
|
AppState* state_ = nullptr;
|
||||||
float playY_ = 0;
|
float playY_ = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user