]> git.sesse.net Git - nageru/blob - cef_capture.cpp
Expose the absolute path to the theme in Nageru.THEME_PATH.
[nageru] / cef_capture.cpp
1 #include <assert.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <chrono>
5 #include <memory>
6 #include <string>
7
8 #include "cef_capture.h"
9 #include "nageru_cef_app.h"
10
11 #undef CHECK
12 #include <cef_app.h>
13 #include <cef_browser.h>
14 #include <cef_client.h>
15
16 #include "bmusb/bmusb.h"
17
18 using namespace std;
19 using namespace std::chrono;
20 using namespace bmusb;
21
22 extern CefRefPtr<NageruCefApp> cef_app;
23
24 CEFCapture::CEFCapture(const string &url, unsigned width, unsigned height)
25         : cef_client(new NageruCEFClient(width, height, this)),
26           width(width),
27           height(height),
28           start_url(url)
29 {
30         char buf[256];
31         snprintf(buf, sizeof(buf), "CEF card %d", card_index + 1);
32         description = buf;
33 }
34
35 CEFCapture::~CEFCapture()
36 {
37         if (has_dequeue_callbacks) {
38                 dequeue_cleanup_callback();
39         }
40 }
41
42 void CEFCapture::post_to_cef_ui_thread(std::function<void()> &&func)
43 {
44         lock_guard<mutex> lock(browser_mutex);
45         if (browser != nullptr) {
46                 CefPostTask(TID_UI, new CEFTaskAdapter(std::move(func)));
47         } else {
48                 deferred_tasks.push_back(std::move(func));
49         }
50 }
51
52 void CEFCapture::set_url(const string &url)
53 {
54         post_to_cef_ui_thread([this, url] {
55                 loaded = false;
56                 browser->GetMainFrame()->LoadURL(url);
57         });
58 }
59
60 void CEFCapture::reload()
61 {
62         post_to_cef_ui_thread([this] {
63                 loaded = false;
64                 browser->Reload();
65         });
66 }
67
68 void CEFCapture::set_max_fps(int max_fps)
69 {
70         post_to_cef_ui_thread([this, max_fps] {
71                 browser->GetHost()->SetWindowlessFrameRate(max_fps);
72                 this->max_fps = max_fps;
73         });
74 }
75
76 void CEFCapture::execute_javascript_async(const string &js)
77 {
78         post_to_cef_ui_thread([this, js] {
79                 if (loaded) {
80                         CefString script_url("<theme eval>");
81                         int start_line = 1;
82                         browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
83                 } else {
84                         deferred_javascript.push_back(js);
85                 }
86         });
87 }
88
89 void CEFCapture::resize(unsigned width, unsigned height)
90 {
91         lock_guard<mutex> lock(resolution_mutex);
92         this->width = width;
93         this->height = height;
94 }
95
96 void CEFCapture::OnPaint(const void *buffer, int width, int height)
97 {
98         steady_clock::time_point timestamp = steady_clock::now();
99
100         VideoFormat video_format;
101         video_format.width = width;
102         video_format.height = height;
103         video_format.stride = width * 4;
104         video_format.frame_rate_nom = max_fps;
105         video_format.frame_rate_den = 1;
106         video_format.has_signal = true;
107         video_format.is_connected = true;
108
109         FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame();
110         if (video_frame.data == nullptr) {
111                 // We lost a frame, so we need to invalidate the entire thing.
112                 // (CEF only sends OnPaint when there are actual changes,
113                 // so we need to do this explicitly, or we could be stuck on an
114                 // old frame forever if the image doesn't change.)
115                 post_to_cef_ui_thread([this] {
116                         browser->GetHost()->Invalidate(PET_VIEW);
117                 });
118                 ++timecode;
119         } else {
120                 assert(video_frame.size >= unsigned(width * height * 4));
121                 assert(!video_frame.interleaved);
122                 memcpy(video_frame.data, buffer, width * height * 4);
123                 video_frame.len = video_format.stride * height;
124                 video_frame.received_timestamp = timestamp;
125                 frame_callback(timecode++,
126                         video_frame, 0, video_format,
127                         FrameAllocator::Frame(), 0, AudioFormat());
128         }
129 }
130
131 void CEFCapture::OnLoadEnd()
132 {
133         post_to_cef_ui_thread([this] {
134                 loaded = true;
135                 for (const string &js : deferred_javascript) {
136                         CefString script_url("<theme eval>");
137                         int start_line = 1;
138                         browser->GetMainFrame()->ExecuteJavaScript(js, script_url, start_line);
139                 }
140                 deferred_javascript.clear();
141         });
142 }
143
144 #define FRAME_SIZE (8 << 20)  // 8 MB.
145
146 void CEFCapture::configure_card()
147 {
148         if (video_frame_allocator == nullptr) {
149                 owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));
150                 set_video_frame_allocator(owned_video_frame_allocator.get());
151         }
152 }
153
154 void CEFCapture::start_bm_capture()
155 {
156         cef_app->initialize_cef();
157
158         CefPostTask(TID_UI, new CEFTaskAdapter([this]{
159                 lock_guard<mutex> lock(browser_mutex);
160
161                 CefBrowserSettings browser_settings;
162                 browser_settings.web_security = cef_state_t::STATE_DISABLED;
163                 browser_settings.webgl = cef_state_t::STATE_ENABLED;
164                 browser_settings.windowless_frame_rate = max_fps;
165
166                 CefWindowInfo window_info;
167                 window_info.SetAsWindowless(0);
168                 browser = CefBrowserHost::CreateBrowserSync(window_info, cef_client, start_url, browser_settings, nullptr);
169                 for (function<void()> &task : deferred_tasks) {
170                         task();
171                 }
172                 deferred_tasks.clear();
173         }));
174 }
175
176 void CEFCapture::stop_dequeue_thread()
177 {
178         lock_guard<mutex> lock(browser_mutex);
179         cef_app->close_browser(browser);
180         browser = nullptr;  // Or unref_cef() will be sad.
181         cef_app->unref_cef();
182 }
183
184 std::map<uint32_t, VideoMode> CEFCapture::get_available_video_modes() const
185 {
186         VideoMode mode;
187
188         char buf[256];
189         snprintf(buf, sizeof(buf), "%ux%u", width, height);
190         mode.name = buf;
191
192         mode.autodetect = false;
193         mode.width = width;
194         mode.height = height;
195         mode.frame_rate_num = max_fps;
196         mode.frame_rate_den = 1;
197         mode.interlaced = false;
198
199         return {{ 0, mode }};
200 }
201
202 std::map<uint32_t, std::string> CEFCapture::get_available_video_inputs() const
203 {
204         return {{ 0, "HTML video input" }};
205 }
206
207 std::map<uint32_t, std::string> CEFCapture::get_available_audio_inputs() const
208 {
209         return {{ 0, "Fake HTML audio input (silence)" }};
210 }
211
212 void CEFCapture::set_video_mode(uint32_t video_mode_id)
213 {
214         assert(video_mode_id == 0);
215 }
216
217 void CEFCapture::set_video_input(uint32_t video_input_id)
218 {
219         assert(video_input_id == 0);
220 }
221
222 void CEFCapture::set_audio_input(uint32_t audio_input_id)
223 {
224         assert(audio_input_id == 0);
225 }
226
227 void NageruCEFClient::OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirtyRects, const void *buffer, int width, int height)
228 {
229         parent->OnPaint(buffer, width, height);
230 }
231
232 bool NageruCEFClient::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
233 {
234         return parent->GetViewRect(rect);
235 }
236
237 bool CEFCapture::GetViewRect(CefRect &rect)
238 {
239         lock_guard<mutex> lock(resolution_mutex);
240         rect = CefRect(0, 0, width, height);
241         return true;
242 }
243
244 void NageruCEFClient::OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode)
245 {
246         parent->OnLoadEnd();
247 }