1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use dioxus::prelude::*;
use crate::state::app_state::AppState;
/// Voice broadcast state.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BroadcastState {
Idle,
Recording,
Paused,
}
/// Voice broadcast component for live audio streaming in a room.
#[component]
pub fn VoiceBroadcast(room_id: String, on_close: EventHandler<()>) -> Element {
let _state = use_context::<Signal<AppState>>();
let mut broadcast_state = use_signal(|| BroadcastState::Idle);
let elapsed_secs = use_signal(|| 0u32);
let _listener_count = use_signal(|| 0u32);
let on_start = {
let rid = room_id.clone();
move |_| {
broadcast_state.set(BroadcastState::Recording);
let rid = rid.clone();
spawn(async move {
tracing::info!("Started voice broadcast in room {rid}");
// In full impl:
// 1. Send io.element.voice_broadcast_info state event (started)
// 2. Start audio recording
// 3. Chunk audio into segments and send as voice messages
});
}
};
let elapsed = *elapsed_secs.read();
let mins = elapsed / 60;
let secs = elapsed % 60;
rsx! {
div {
class: "voice-broadcast",
h4 { "Voice Broadcast" }
if *broadcast_state.read() == BroadcastState::Idle {
div {
class: "voice-broadcast__start",
p { "Start a voice broadcast to share audio with everyone in this room in real-time." }
div {
class: "voice-broadcast__actions",
button { class: "btn btn--secondary", onclick: move |_| on_close.call(()), "Cancel" }
button { class: "btn btn--primary", onclick: on_start, "Start Broadcast" }
}
}
} else {
div {
class: "voice-broadcast__live",
div {
class: "voice-broadcast__indicator",
if *broadcast_state.read() == BroadcastState::Recording {
span { class: "voice-broadcast__live-dot" }
span { "LIVE" }
} else {
span { "PAUSED" }
}
}
div {
class: "voice-broadcast__timer",
"{mins}:{secs:02}"
}
div {
class: "voice-broadcast__controls",
if *broadcast_state.read() == BroadcastState::Recording {
button {
class: "voice-broadcast__control-btn",
title: "Pause",
onclick: move |_| broadcast_state.set(BroadcastState::Paused),
"\u{23F8}"
}
} else {
button {
class: "voice-broadcast__control-btn",
title: "Resume",
onclick: move |_| broadcast_state.set(BroadcastState::Recording),
"\u{25B6}"
}
}
button {
class: "voice-broadcast__control-btn voice-broadcast__control-btn--stop",
title: "Stop broadcast",
onclick: move |_| {
broadcast_state.set(BroadcastState::Idle);
on_close.call(());
tracing::info!("Voice broadcast stopped");
},
"\u{23F9}"
}
}
}
}
}
}
}