Skip to content

Commit 6854b08

Browse files
committed
Add CameraFeed support for Web
1 parent 64b0990 commit 6854b08

File tree

11 files changed

+1009
-3
lines changed

11 files changed

+1009
-3
lines changed

doc/classes/CameraFeed.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<description>
77
A camera feed gives you access to a single physical camera attached to your device. When enabled, Godot will start capturing frames from the camera which can then be used. See also [CameraServer].
88
[b]Note:[/b] Many cameras will return YCbCr images which are split into two textures and need to be combined in a shader. Godot does this automatically for you if you set the environment to show the camera image in the background.
9-
[b]Note:[/b] This class is currently only implemented on Linux, macOS, and iOS. On other platforms no [CameraFeed]s will be available. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required.
9+
[b]Note:[/b] This class is currently only implemented on Linux, Web, macOS, and iOS. On other platforms no [CameraFeed]s will be available. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required.
1010
</description>
1111
<tutorials>
1212
</tutorials>

modules/camera/SCsub

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Import("env_modules")
66

77
env_camera = env_modules.Clone()
88

9-
if env["platform"] in ["windows", "macos", "linuxbsd", "android"]:
9+
if env["platform"] in ["windows", "macos", "linuxbsd", "android", "web"]:
1010
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
1111

1212
if env["platform"] == "windows":
@@ -23,3 +23,6 @@ elif env["platform"] == "linuxbsd":
2323
env_camera.add_source_files(env.modules_sources, "camera_linux.cpp")
2424
env_camera.add_source_files(env.modules_sources, "camera_feed_linux.cpp")
2525
env_camera.add_source_files(env.modules_sources, "buffer_decoder.cpp")
26+
27+
elif env["platform"] == "web":
28+
env_camera.add_source_files(env.modules_sources, "camera_web.cpp")

modules/camera/camera_web.cpp

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**************************************************************************/
2+
/* camera_web.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "camera_web.h"
32+
33+
#include "core/io/json.h"
34+
35+
void CameraFeedWeb::_on_get_pixeldata(void *context, const uint8_t *rawdata, const int length, const int p_width, const int p_height, const char *error) {
36+
CameraFeedWeb *feed = reinterpret_cast<CameraFeedWeb *>(context);
37+
if (error) {
38+
if (feed->is_active()) {
39+
feed->deactivate_feed();
40+
};
41+
String error_str = String::utf8(error);
42+
ERR_PRINT(vformat("Camera feed error from JS: %s", error_str));
43+
return;
44+
}
45+
46+
if (context == nullptr || rawdata == nullptr || length < 0 || p_width <= 0 || p_height <= 0) {
47+
if (feed->is_active()) {
48+
feed->deactivate_feed();
49+
};
50+
ERR_PRINT("Camera feed error: Invalid pixel data received.");
51+
return;
52+
}
53+
54+
Vector<uint8_t> data = feed->data;
55+
Ref<Image> image = feed->image;
56+
57+
if (length != data.size()) {
58+
int64_t size = Image::get_image_data_size(p_width, p_height, Image::FORMAT_RGBA8, false);
59+
data.resize(length > size ? length : size);
60+
}
61+
memcpy(data.ptrw(), rawdata, length);
62+
63+
image->initialize_data(p_width, p_height, false, Image::FORMAT_RGBA8, data);
64+
feed->set_rgb_image(image);
65+
feed->emit_signal(SNAME("frame_changed"));
66+
}
67+
68+
void CameraFeedWeb::_on_denied_callback(void *context) {
69+
CameraFeedWeb *feed = reinterpret_cast<CameraFeedWeb *>(context);
70+
feed->deactivate_feed();
71+
}
72+
73+
bool CameraFeedWeb::activate_feed() {
74+
ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");
75+
76+
CameraFeed::FeedFormat f = formats[selected_format];
77+
int width = parameters.get(KEY_WIDTH, 0);
78+
int height = parameters.get(KEY_HEIGHT, 0);
79+
width = width > 0 ? width : f.width;
80+
height = height > 0 ? height : f.height;
81+
CameraDriverWeb::get_singleton()->get_pixel_data(this, device_id, width, height, &_on_get_pixeldata, &_on_denied_callback);
82+
return true;
83+
}
84+
85+
void CameraFeedWeb::deactivate_feed() {
86+
CameraDriverWeb::get_singleton()->stop_stream(device_id);
87+
}
88+
89+
bool CameraFeedWeb::set_format(int p_index, const Dictionary &p_parameters) {
90+
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
91+
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
92+
93+
selected_format = p_index;
94+
parameters = p_parameters;
95+
return true;
96+
}
97+
98+
Array CameraFeedWeb::get_formats() const {
99+
Array result;
100+
for (const FeedFormat &feed_format : formats) {
101+
Dictionary dictionary;
102+
dictionary["width"] = feed_format.width;
103+
dictionary["height"] = feed_format.height;
104+
dictionary["format"] = feed_format.format;
105+
result.push_back(dictionary);
106+
}
107+
return result;
108+
}
109+
110+
CameraFeed::FeedFormat CameraFeedWeb::get_format() const {
111+
CameraFeed::FeedFormat feed_format = {};
112+
return selected_format == -1 ? feed_format : formats[selected_format];
113+
}
114+
115+
CameraFeedWeb::CameraFeedWeb(const CameraInfo &info) {
116+
name = info.label;
117+
device_id = info.device_id;
118+
119+
Vector<CapabilityInfo> capabilities;
120+
CameraDriverWeb::get_singleton()->get_capabilities(&capabilities, device_id);
121+
for (int i = 0; i < capabilities.size(); i++) {
122+
CapabilityInfo capability = capabilities[i];
123+
FeedFormat feed_format;
124+
feed_format.width = capability.width;
125+
feed_format.height = capability.height;
126+
feed_format.format = String("RGBA");
127+
formats.append(feed_format);
128+
}
129+
130+
image.instantiate();
131+
}
132+
133+
CameraFeedWeb::~CameraFeedWeb() {
134+
if (is_active()) {
135+
deactivate_feed();
136+
}
137+
}
138+
139+
void CameraWeb::_update_feeds() {
140+
for (int i = feeds.size() - 1; i >= 0; i--) {
141+
remove_feed(feeds[i]);
142+
}
143+
144+
Vector<CameraInfo> camera_info;
145+
camera_driver_web->get_cameras(&camera_info);
146+
for (int i = 0; i < camera_info.size(); i++) {
147+
CameraInfo info = camera_info[i];
148+
Ref<CameraFeedWeb> feed = memnew(CameraFeedWeb(info));
149+
add_feed(feed);
150+
}
151+
}
152+
153+
void CameraWeb::_cleanup() {
154+
if (camera_driver_web != nullptr) {
155+
camera_driver_web->stop_stream();
156+
memdelete(camera_driver_web);
157+
camera_driver_web = nullptr;
158+
}
159+
}
160+
161+
void CameraWeb::set_monitoring_feeds(bool p_monitoring_feeds) {
162+
if (p_monitoring_feeds == monitoring_feeds) {
163+
return;
164+
}
165+
166+
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
167+
if (p_monitoring_feeds) {
168+
if (camera_driver_web == nullptr) {
169+
camera_driver_web = new CameraDriverWeb();
170+
}
171+
_update_feeds();
172+
} else {
173+
_cleanup();
174+
}
175+
}
176+
177+
CameraWeb::~CameraWeb() {
178+
_cleanup();
179+
}

modules/camera/camera_web.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**************************************************************************/
2+
/* camera_web.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#pragma once
32+
33+
#include "platform/web/camera_driver_web.h"
34+
#include "servers/camera/camera_feed.h"
35+
#include "servers/camera_server.h"
36+
37+
class CameraFeedWeb : public CameraFeed {
38+
GDCLASS(CameraFeedWeb, CameraFeed);
39+
40+
private:
41+
String device_id;
42+
Ref<Image> image;
43+
Vector<uint8_t> data;
44+
static void _on_get_pixeldata(void *, const uint8_t *, const int, const int, const int, const char *error);
45+
static void _on_denied_callback(void *context);
46+
47+
protected:
48+
public:
49+
bool activate_feed() override;
50+
void deactivate_feed() override;
51+
bool set_format(int p_index, const Dictionary &p_parameters) override;
52+
Array get_formats() const override;
53+
FeedFormat get_format() const override;
54+
55+
CameraFeedWeb(const CameraInfo &info);
56+
~CameraFeedWeb();
57+
};
58+
59+
class CameraWeb : public CameraServer {
60+
GDCLASS(CameraWeb, CameraServer);
61+
62+
private:
63+
CameraDriverWeb *camera_driver_web = nullptr;
64+
void _cleanup();
65+
void _update_feeds();
66+
67+
protected:
68+
public:
69+
void set_monitoring_feeds(bool p_monitoring_feeds) override;
70+
71+
~CameraWeb();
72+
};

modules/camera/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ def can_build(env, platform):
33

44
if sys.platform.startswith("freebsd"):
55
return False
6-
return platform == "macos" or platform == "windows" or platform == "linuxbsd" or platform == "android"
6+
return platform in ["macos", "windows", "linuxbsd", "android", "web"]
77

88

99
def configure(env):

modules/camera/register_types.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
#if defined(ANDROID_ENABLED)
4343
#include "camera_android.h"
4444
#endif
45+
#if defined(WEB_ENABLED)
46+
#include "camera_web.h"
47+
#endif
4548

4649
void initialize_camera_module(ModuleInitializationLevel p_level) {
4750
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
@@ -60,6 +63,9 @@ void initialize_camera_module(ModuleInitializationLevel p_level) {
6063
#if defined(ANDROID_ENABLED)
6164
CameraServer::make_default<CameraAndroid>();
6265
#endif
66+
#if defined(WEB_ENABLED)
67+
CameraServer::make_default<CameraWeb>();
68+
#endif
6369
}
6470

6571
void uninitialize_camera_module(ModuleInitializationLevel p_level) {

platform/web/SCsub

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS:
2222

2323
web_files = [
2424
"audio_driver_web.cpp",
25+
"camera_driver_web.cpp",
2526
"webmidi_driver.cpp",
2627
"display_server_web.cpp",
2728
"http_client_web.cpp",
@@ -39,6 +40,7 @@ sys_env = env.Clone()
3940
sys_env.AddJSLibraries(
4041
[
4142
"js/libs/library_godot_audio.js",
43+
"js/libs/library_godot_camera.js",
4244
"js/libs/library_godot_display.js",
4345
"js/libs/library_godot_fetch.js",
4446
"js/libs/library_godot_webmidi.js",
@@ -104,6 +106,9 @@ else:
104106
sys_env.Append(LIBS=["idbfs.js"])
105107
build = sys_env.add_program(build_targets, web_files + ["web_runtime.cpp"])
106108

109+
sys_env.Append(LINKFLAGS=["-s", "ASYNCIFY=2"])
110+
sys_env.Append(LINKFLAGS=["-s", "ASYNCIFY_IMPORTS=['godot_js_camera_get_cameras', 'godot_js_camera_get_capabilities']"])
111+
107112
sys_env.Depends(build[0], sys_env["JS_LIBS"])
108113
sys_env.Depends(build[0], sys_env["JS_PRE"])
109114
sys_env.Depends(build[0], sys_env["JS_POST"])

0 commit comments

Comments
 (0)