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
Generated
+1 -1
View File
@@ -1566,7 +1566,7 @@ dependencies = [
[[package]] [[package]]
name = "furumi_tui" name = "furumi_tui"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
+15 -4
View File
@@ -2784,6 +2784,12 @@ fn handle_device_playback_snapshot(
runtime: &mut Runtime, runtime: &mut Runtime,
snapshot: crate::devices::PlaybackSnapshot, 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 { if snapshot.device_id == state.device_playback.self_device_id {
return; return;
} }
@@ -2813,7 +2819,7 @@ fn handle_device_playback_snapshot(
return; return;
} }
let lease_expired = active_idle_lease_expired(&snapshot, now); 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()); && state.device_playback.active_device_id.as_deref() == Some(snapshot.device_id.as_str());
if !lease_expired || already_controls_this_device { if !lease_expired || already_controls_this_device {
let was_active = state.device_playback.is_audio_owner(); let was_active = state.device_playback.is_audio_owner();
@@ -2828,7 +2834,7 @@ fn handle_device_playback_snapshot(
return; return;
} }
if state.device_playback.is_control() { if state.device_playback.is_personal_control() {
return; return;
} }
become_active_device(state, runtime, false); 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) .unwrap_or(1)
.max(1); .max(1);
let active_revoked = state.device_playback.is_control() let active_revoked = state.device_playback.is_personal_control()
&& state && state
.device_playback .device_playback
.active_device_id .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) .is_some_and(|device| device.revoked)
}); });
let active_missing = state.device_playback.is_control() let active_missing = state.device_playback.is_personal_control()
&& state && state
.device_playback .device_playback
.active_device_id .active_device_id
@@ -3022,6 +3028,11 @@ fn handle_app_event(state: &mut AppState, runtime: &mut Runtime, event: AppEvent
AppEvent::DevicePlayback(snapshot) => { AppEvent::DevicePlayback(snapshot) => {
handle_device_playback_snapshot(state, runtime, 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) => { AppEvent::PlaybackCommand(command) => {
handle_playback_command(state, runtime, command); handle_playback_command(state, runtime, command);
} }
+6
View File
@@ -423,6 +423,12 @@ fn handle_connected_devices(
}); });
} }
KeyCode::Enter => { 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 { if cursor == 0 {
super::transfer_active_to_this_device(state, runtime); super::transfer_active_to_this_device(state, runtime);
state.status_message = Some("active playback moved to this device".into()); 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) || (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 { pub fn is_audio_owner(&self) -> bool {
self.role == DevicePlaybackRole::Active self.role == DevicePlaybackRole::Active
|| (self.role == DevicePlaybackRole::Jam && self.jam_host) || (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 { let list_area = Rect {
x: other_area.x, x: other_area.x,
y: other_area.y + 1, y: other_area.y + 1,
+12 -19
View File
@@ -38,15 +38,10 @@ pub fn selection() -> Style {
} }
pub fn selection_for(state: &AppState) -> Style { pub fn selection_for(state: &AppState) -> Style {
if state.device_playback.is_control() { match state.device_playback.role {
let background = if state.device_playback.role == DevicePlaybackRole::Jam { DevicePlaybackRole::Jam => Style::new().fg(Color::White).bg(Color::Rgb(80, 24, 96)),
Color::Rgb(80, 24, 96) DevicePlaybackRole::Control => Style::new().fg(Color::White).bg(Color::Rgb(92, 72, 0)),
} else { DevicePlaybackRole::Active => selection(),
Color::Rgb(92, 72, 0)
};
Style::new().fg(Color::White).bg(background)
} else {
selection()
} }
} }
@@ -55,10 +50,10 @@ pub fn header_for(state: &AppState) -> Style {
} }
pub fn border_for(state: &AppState) -> Style { pub fn border_for(state: &AppState) -> Style {
if state.device_playback.is_control() { match state.device_playback.role {
Style::new().fg(CONTROL_ACCENT) DevicePlaybackRole::Jam => Style::new().fg(JAM_ACCENT),
} else { DevicePlaybackRole::Control => Style::new().fg(CONTROL_ACCENT),
dim() DevicePlaybackRole::Active => dim(),
} }
} }
@@ -79,11 +74,9 @@ pub fn role_pill(role: DevicePlaybackRole) -> Style {
} }
fn accent_color_for(state: &AppState) -> Color { fn accent_color_for(state: &AppState) -> Color {
if state.device_playback.role == DevicePlaybackRole::Jam { match state.device_playback.role {
JAM_ACCENT DevicePlaybackRole::Jam => JAM_ACCENT,
} else if state.device_playback.is_control() { DevicePlaybackRole::Control => CONTROL_ACCENT,
CONTROL_ACCENT DevicePlaybackRole::Active => ACCENT,
} else {
ACCENT
} }
} }