1use futures::StreamExt;
2use gpui::{
3 actions,
4 elements::{Canvas, *},
5 keymap::Binding,
6 platform::current::Surface,
7 Menu, MenuItem, ViewContext,
8};
9use live_kit_client::{LocalVideoTrack, RemoteVideoTrackUpdate, Room};
10use live_kit_server::token::{self, VideoGrant};
11use log::LevelFilter;
12use media::core_video::CVImageBuffer;
13use postage::watch;
14use simplelog::SimpleLogger;
15use std::sync::Arc;
16
17actions!(capture, [Quit]);
18
19fn main() {
20 SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
21
22 gpui::App::new(()).unwrap().run(|cx| {
23 cx.platform().activate(true);
24 cx.add_global_action(quit);
25
26 cx.add_bindings([Binding::new("cmd-q", Quit, None)]);
27 cx.set_menus(vec![Menu {
28 name: "Zed",
29 items: vec![MenuItem::Action {
30 name: "Quit",
31 action: Box::new(Quit),
32 }],
33 }]);
34
35 let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into());
36 let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into());
37 let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into());
38
39 cx.spawn(|cx| async move {
40 let user_a_token = token::create(
41 &live_kit_key,
42 &live_kit_secret,
43 Some("test-participant-1"),
44 VideoGrant::to_join("test-room"),
45 )
46 .unwrap();
47 let room_a = Room::new();
48 room_a.connect(&live_kit_url, &user_a_token).await.unwrap();
49
50 let user2_token = token::create(
51 &live_kit_key,
52 &live_kit_secret,
53 Some("test-participant-2"),
54 VideoGrant::to_join("test-room"),
55 )
56 .unwrap();
57 let room_b = Room::new();
58 room_b.connect(&live_kit_url, &user2_token).await.unwrap();
59
60 let mut track_changes = room_b.remote_video_track_updates();
61
62 let displays = live_kit_client::display_sources().await.unwrap();
63 let display = displays.into_iter().next().unwrap();
64
65 let track_a = LocalVideoTrack::screen_share_for_display(&display);
66 let track_a_publication = room_a.publish_video_track(&track_a).await.unwrap();
67
68 if let RemoteVideoTrackUpdate::Subscribed(track) = track_changes.next().await.unwrap() {
69 let remote_tracks = room_b.remote_video_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 let remote_track = room_b
78 .remote_video_tracks("test-participant-1")
79 .pop()
80 .unwrap();
81 room_a.unpublish_track(track_a_publication);
82 if let RemoteVideoTrackUpdate::Unsubscribed {
83 publisher_id,
84 track_id,
85 } = track_changes.next().await.unwrap()
86 {
87 assert_eq!(publisher_id, "test-participant-1");
88 assert_eq!(remote_track.sid(), track_id);
89 assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0);
90 } else {
91 panic!("unexpected message");
92 }
93
94 cx.platform().quit();
95 })
96 .detach();
97 });
98}
99
100struct ScreenCaptureView {
101 image_buffer: Option<CVImageBuffer>,
102 _room: Arc<Room>,
103}
104
105impl gpui::Entity for ScreenCaptureView {
106 type Event = ();
107}
108
109impl ScreenCaptureView {
110 pub fn new(room: Arc<Room>, cx: &mut ViewContext<Self>) -> Self {
111 let mut remote_video_tracks = room.remote_video_track_updates();
112 cx.spawn_weak(|this, mut cx| async move {
113 if let Some(video_track) = remote_video_tracks.next().await {
114 let (mut frames_tx, mut frames_rx) = watch::channel_with(None);
115 // video_track.add_renderer(move |frame| *frames_tx.borrow_mut() = Some(frame));
116
117 while let Some(frame) = frames_rx.next().await {
118 if let Some(this) = this.upgrade(&cx) {
119 this.update(&mut cx, |this, cx| {
120 this.image_buffer = frame;
121 cx.notify();
122 });
123 } else {
124 break;
125 }
126 }
127 }
128 })
129 .detach();
130
131 Self {
132 image_buffer: None,
133 _room: room,
134 }
135 }
136}
137
138impl gpui::View for ScreenCaptureView {
139 fn ui_name() -> &'static str {
140 "View"
141 }
142
143 fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
144 let image_buffer = self.image_buffer.clone();
145 let canvas = Canvas::new(move |bounds, _, cx| {
146 if let Some(image_buffer) = image_buffer.clone() {
147 cx.scene.push_surface(Surface {
148 bounds,
149 image_buffer,
150 });
151 }
152 });
153
154 if let Some(image_buffer) = self.image_buffer.as_ref() {
155 canvas
156 .constrained()
157 .with_width(image_buffer.width() as f32)
158 .with_height(image_buffer.height() as f32)
159 .aligned()
160 .boxed()
161 } else {
162 canvas.boxed()
163 }
164 }
165}
166
167fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
168 cx.platform().quit();
169}