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