1use std::{sync::Arc, time::Duration};
2
3use futures::StreamExt;
4use gpui::{actions, KeyBinding, Menu, MenuItem};
5use live_kit_client::{
6 LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
7};
8use live_kit_server::token::{self, VideoGrant};
9use log::LevelFilter;
10use simplelog::SimpleLogger;
11
12actions!(live_kit_client, [Quit]);
13
14fn main() {
15 SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
16
17 gpui::App::production(Arc::new(())).run(|cx| {
18 #[cfg(any(test, feature = "test-support"))]
19 println!("USING TEST LIVEKIT");
20
21 #[cfg(not(any(test, feature = "test-support")))]
22 println!("USING REAL LIVEKIT");
23
24 cx.activate(true);
25
26 cx.on_action(quit);
27 cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
28
29 cx.set_menus(vec![Menu {
30 name: "Zed",
31 items: vec![MenuItem::Action {
32 name: "Quit",
33 action: Box::new(Quit),
34 os_action: None,
35 }],
36 }]);
37
38 let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into());
39 let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into());
40 let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into());
41
42 cx.spawn(|cx| async move {
43 let user_a_token = token::create(
44 &live_kit_key,
45 &live_kit_secret,
46 Some("test-participant-1"),
47 VideoGrant::to_join("test-room"),
48 )
49 .unwrap();
50 let room_a = Room::new();
51 room_a.connect(&live_kit_url, &user_a_token).await.unwrap();
52
53 let user2_token = token::create(
54 &live_kit_key,
55 &live_kit_secret,
56 Some("test-participant-2"),
57 VideoGrant::to_join("test-room"),
58 )
59 .unwrap();
60 let room_b = Room::new();
61 room_b.connect(&live_kit_url, &user2_token).await.unwrap();
62
63 let mut audio_track_updates = room_b.remote_audio_track_updates();
64 let audio_track = LocalAudioTrack::create();
65 let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap();
66
67 if let RemoteAudioTrackUpdate::Subscribed(track, _) =
68 audio_track_updates.next().await.unwrap()
69 {
70 let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
71 assert_eq!(remote_tracks.len(), 1);
72 assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1");
73 assert_eq!(track.publisher_id(), "test-participant-1");
74 } else {
75 panic!("unexpected message");
76 }
77
78 audio_track_publication.set_mute(true).await.unwrap();
79
80 println!("waiting for mute changed!");
81 if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } =
82 audio_track_updates.next().await.unwrap()
83 {
84 let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
85 assert_eq!(remote_tracks[0].sid(), track_id);
86 assert_eq!(muted, true);
87 } else {
88 panic!("unexpected message");
89 }
90
91 audio_track_publication.set_mute(false).await.unwrap();
92
93 if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } =
94 audio_track_updates.next().await.unwrap()
95 {
96 let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
97 assert_eq!(remote_tracks[0].sid(), track_id);
98 assert_eq!(muted, false);
99 } else {
100 panic!("unexpected message");
101 }
102
103 println!("Pausing for 5 seconds to test audio, make some noise!");
104 let timer = cx.background_executor().timer(Duration::from_secs(5));
105 timer.await;
106 let remote_audio_track = room_b
107 .remote_audio_tracks("test-participant-1")
108 .pop()
109 .unwrap();
110 room_a.unpublish_track(audio_track_publication);
111
112 // Clear out any active speakers changed messages
113 let mut next = audio_track_updates.next().await.unwrap();
114 while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next {
115 println!("Speakers changed: {:?}", speakers);
116 next = audio_track_updates.next().await.unwrap();
117 }
118
119 if let RemoteAudioTrackUpdate::Unsubscribed {
120 publisher_id,
121 track_id,
122 } = next
123 {
124 assert_eq!(publisher_id, "test-participant-1");
125 assert_eq!(remote_audio_track.sid(), track_id);
126 assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0);
127 } else {
128 panic!("unexpected message");
129 }
130
131 let mut video_track_updates = room_b.remote_video_track_updates();
132 let displays = room_a.display_sources().await.unwrap();
133 let display = displays.into_iter().next().unwrap();
134
135 let local_video_track = LocalVideoTrack::screen_share_for_display(&display);
136 let local_video_track_publication =
137 room_a.publish_video_track(local_video_track).await.unwrap();
138
139 if let RemoteVideoTrackUpdate::Subscribed(track) =
140 video_track_updates.next().await.unwrap()
141 {
142 let remote_video_tracks = room_b.remote_video_tracks("test-participant-1");
143 assert_eq!(remote_video_tracks.len(), 1);
144 assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1");
145 assert_eq!(track.publisher_id(), "test-participant-1");
146 } else {
147 panic!("unexpected message");
148 }
149
150 let remote_video_track = room_b
151 .remote_video_tracks("test-participant-1")
152 .pop()
153 .unwrap();
154 room_a.unpublish_track(local_video_track_publication);
155 if let RemoteVideoTrackUpdate::Unsubscribed {
156 publisher_id,
157 track_id,
158 } = video_track_updates.next().await.unwrap()
159 {
160 assert_eq!(publisher_id, "test-participant-1");
161 assert_eq!(remote_video_track.sid(), track_id);
162 assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0);
163 } else {
164 panic!("unexpected message");
165 }
166
167 cx.update(|cx| cx.shutdown()).ok();
168 })
169 .detach();
170 });
171}
172
173fn quit(_: &Quit, cx: &mut gpui::AppContext) {
174 cx.quit();
175}