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