Added JAM feature

This commit is contained in:
Ultradesu
2026-07-28 18:41:08 +01:00
parent 1c721b68f5
commit d4c219837b
6 changed files with 39 additions and 25 deletions
+15 -4
View File
@@ -2784,6 +2784,12 @@ fn handle_device_playback_snapshot(
runtime: &mut Runtime,
snapshot: crate::devices::PlaybackSnapshot,
) {
// Personal-device reconciliation must never change Jam ownership. Jam
// has its own authority and lifecycle even when the same TUI also belongs
// to a trusted-device group.
if state.device_playback.role == state::DevicePlaybackRole::Jam {
return;
}
if snapshot.device_id == state.device_playback.self_device_id {
return;
}
@@ -2813,7 +2819,7 @@ fn handle_device_playback_snapshot(
return;
}
let lease_expired = active_idle_lease_expired(&snapshot, now);
let already_controls_this_device = state.device_playback.is_control()
let already_controls_this_device = state.device_playback.is_personal_control()
&& state.device_playback.active_device_id.as_deref() == Some(snapshot.device_id.as_str());
if !lease_expired || already_controls_this_device {
let was_active = state.device_playback.is_audio_owner();
@@ -2828,7 +2834,7 @@ fn handle_device_playback_snapshot(
return;
}
if state.device_playback.is_control() {
if state.device_playback.is_personal_control() {
return;
}
become_active_device(state, runtime, false);
@@ -2942,7 +2948,7 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
})
.unwrap_or(1)
.max(1);
let active_revoked = state.device_playback.is_control()
let active_revoked = state.device_playback.is_personal_control()
&& state
.device_playback
.active_device_id
@@ -2960,7 +2966,7 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
})
.is_some_and(|device| device.revoked)
});
let active_missing = state.device_playback.is_control()
let active_missing = state.device_playback.is_personal_control()
&& state
.device_playback
.active_device_id
@@ -3022,6 +3028,11 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
AppEvent::DevicePlayback(snapshot) => {
handle_device_playback_snapshot(state, runtime, snapshot);
}
AppEvent::PlaybackCommand(_)
if state.device_playback.role == state::DevicePlaybackRole::Jam =>
{
tracing::debug!("ignored personal-device playback command while Jam is active");
}
AppEvent::PlaybackCommand(command) => {
handle_playback_command(state, runtime, command);
}
+6
View File
@@ -423,6 +423,12 @@ fn handle_connected_devices(
});
}
KeyCode::Enter => {
if state.device_playback.role == crate::app::state::DevicePlaybackRole::Jam {
state.status_message =
Some("leave the current Jam before switching personal devices".into());
state.popup = Some(Popup::ConnectedDevices { cursor });
return;
}
if cursor == 0 {
super::transfer_active_to_this_device(state, runtime);
state.status_message = Some("active playback moved to this device".into());
+4
View File
@@ -1248,6 +1248,10 @@ impl DevicePlaybackState {
|| (self.role == DevicePlaybackRole::Jam && !self.jam_host)
}
pub fn is_personal_control(&self) -> bool {
self.role == DevicePlaybackRole::Control
}
pub fn is_audio_owner(&self) -> bool {
self.role == DevicePlaybackRole::Active
|| (self.role == DevicePlaybackRole::Jam && self.jam_host)
+1 -1
View File
@@ -555,7 +555,7 @@ fn draw_connected_devices(frame: &mut Frame, state: &AppState, cursor: usize) {
);
}
render_subtitle(frame, other_area, state, "Other devices");
render_subtitle(frame, other_area, state, "My devices");
let list_area = Rect {
x: other_area.x,
y: other_area.y + 1,
+12 -19
View File
@@ -38,15 +38,10 @@ pub fn selection() -> Style {
}
pub fn selection_for(state: &AppState) -> Style {
if state.device_playback.is_control() {
let background = if state.device_playback.role == DevicePlaybackRole::Jam {
Color::Rgb(80, 24, 96)
} else {
Color::Rgb(92, 72, 0)
};
Style::new().fg(Color::White).bg(background)
} else {
selection()
match state.device_playback.role {
DevicePlaybackRole::Jam => Style::new().fg(Color::White).bg(Color::Rgb(80, 24, 96)),
DevicePlaybackRole::Control => Style::new().fg(Color::White).bg(Color::Rgb(92, 72, 0)),
DevicePlaybackRole::Active => selection(),
}
}
@@ -55,10 +50,10 @@ pub fn header_for(state: &AppState) -> Style {
}
pub fn border_for(state: &AppState) -> Style {
if state.device_playback.is_control() {
Style::new().fg(CONTROL_ACCENT)
} else {
dim()
match state.device_playback.role {
DevicePlaybackRole::Jam => Style::new().fg(JAM_ACCENT),
DevicePlaybackRole::Control => Style::new().fg(CONTROL_ACCENT),
DevicePlaybackRole::Active => dim(),
}
}
@@ -79,11 +74,9 @@ pub fn role_pill(role: DevicePlaybackRole) -> Style {
}
fn accent_color_for(state: &AppState) -> Color {
if state.device_playback.role == DevicePlaybackRole::Jam {
JAM_ACCENT
} else if state.device_playback.is_control() {
CONTROL_ACCENT
} else {
ACCENT
match state.device_playback.role {
DevicePlaybackRole::Jam => JAM_ACCENT,
DevicePlaybackRole::Control => CONTROL_ACCENT,
DevicePlaybackRole::Active => ACCENT,
}
}