diff --git a/AUTHORS b/AUTHORS
index a1588b5..ebba5d7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,3 +8,4 @@ Contributors:
Raphipod (docs, fixes, Windows)
Daniel Sanche (javascript injection, fixes)
BlayTheNinth (portage to Godot 4.2)
+pixaline (Routing audio)
diff --git a/addons/gdcef/demos/2D/CEF.gd b/addons/gdcef/demos/2D/CEF.gd
index 8d27d81..40f4621 100644
--- a/addons/gdcef/demos/2D/CEF.gd
+++ b/addons/gdcef/demos/2D/CEF.gd
@@ -8,7 +8,8 @@ extends Control
# Default pages
const DEFAULT_PAGE = "user://default_page.html"
-const RADIO_PAGE = "https://www.programmes-radio.com/fr/stream-e8BxeoRhsz9jY9mXXRiFTE/ecouter-KPJK"
+const RADIO_PAGE = "http://streaming.radio.co/s9378c22ee/listen"
+# "https://www.programmes-radio.com/fr/stream-e8BxeoRhsz9jY9mXXRiFTE/ecouter-KPJK"
const HOME_PAGE = "https://github.com/Lecrapouille/gdcef"
# The current browser as Godot node
@@ -17,6 +18,8 @@ const HOME_PAGE = "https://github.com/Lecrapouille/gdcef"
# Memorize if the mouse was pressed
@onready var mouse_pressed : bool = false
+@onready var playback = null
+
# ==============================================================================
# Callback when a page has ended to load with success (200): we print a message
# ==============================================================================
@@ -33,6 +36,9 @@ func _on_page_loaded(node):
# Display a load error message using a data: URI.
# ==============================================================================
func _on_page_failed_loading(aborted, msg_err, node):
+ # FIXME: I dunno why the radio page is considered as canceled by the user
+ if node.get_url() == RADIO_PAGE:
+ return
var html = "
Failed to load URL " + node.get_url()
if aborted:
html = html + " aborted by the user!
"
@@ -183,9 +189,11 @@ func _on_radio_pressed():
# ==============================================================================
# Mute/unmute the sound
# ==============================================================================
-func _on_Mute_pressed():
- if current_browser != null:
- current_browser.set_muted(not current_browser.is_muted())
+func _on_mute_pressed():
+ if current_browser == null:
+ return
+ current_browser.set_muted($Panel/VBox/HBox2/Mute.button_pressed)
+ $AudioStreamPlayer2D.stream_paused = $Panel/VBox/HBox2/Mute.button_pressed
pass
####
@@ -285,6 +293,7 @@ func _ready():
push_error($CEF.get_error())
return
print("CEF version: " + $CEF.get_full_version())
+ print("You are listening CEF native audio")
# Wait one frame for the texture rect to get its size
current_browser = await create_browser(HOME_PAGE)
@@ -295,3 +304,26 @@ func _ready():
# ==============================================================================
func _process(_delta):
pass
+
+# ==============================================================================
+# CEF audio will be routed to this Godot stream object.
+# ==============================================================================
+func _on_routing_audio_pressed():
+ if current_browser == null:
+ return
+ if $Panel/VBox/HBox2/RoutingAudio.button_pressed:
+ print("You are listening CEF audio routed to Godot and filtered with reverberation effect")
+ $AudioStreamPlayer2D.stream = AudioStreamGenerator.new()
+ $AudioStreamPlayer2D.stream.set_buffer_length(1)
+ $AudioStreamPlayer2D.playing = true
+ current_browser.audio_stream = $AudioStreamPlayer2D.get_stream_playback()
+ else:
+ print("You are listening CEF native audio")
+ current_browser.audio_stream = null
+ current_browser.set_muted(false)
+ $Panel/VBox/HBox2/Mute.button_pressed = false
+ # Not necessary, but, I do not know what, to apply the new mode, the user
+ # shall click on the html halt button and click on the html button. To avoid
+ # this, we reload the page.
+ current_browser.reload()
+ pass
diff --git a/addons/gdcef/demos/2D/CEF.tscn b/addons/gdcef/demos/2D/CEF.tscn
index 1c798a9..37584e2 100644
--- a/addons/gdcef/demos/2D/CEF.tscn
+++ b/addons/gdcef/demos/2D/CEF.tscn
@@ -101,9 +101,13 @@ layout_mode = 2
tooltip_text = "Load a page with sound"
text = "Radio"
-[node name="Mute" type="Button" parent="Panel/VBox/HBox2"]
+[node name="RoutingAudio" type="CheckBox" parent="Panel/VBox/HBox2"]
+layout_mode = 2
+tooltip_text = "Route CEF audio to Godot"
+text = " Audio routing"
+
+[node name="Mute" type="CheckBox" parent="Panel/VBox/HBox2"]
layout_mode = 2
-tooltip_text = "Mute/unmute the sound"
text = "Mute sound"
[node name="Info2" type="Label" parent="Panel/VBox/HBox2"]
@@ -127,6 +131,8 @@ anchor_bottom = 1.0
edit_alpha = false
presets_visible = false
+[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
+
[connection signal="pressed" from="Panel/VBox/HBox/New" to="." method="_on_Add_pressed"]
[connection signal="pressed" from="Panel/VBox/HBox/Home" to="." method="_on_Home_pressed"]
[connection signal="pressed" from="Panel/VBox/HBox/Go" to="." method="_on_go_pressed"]
@@ -139,5 +145,6 @@ presets_visible = false
[connection signal="resized" from="Panel/VBox/TextureRect" to="." method="_on_texture_rect_resized"]
[connection signal="pressed" from="Panel/VBox/HBox2/BGColor" to="." method="_on_BGColor_pressed"]
[connection signal="pressed" from="Panel/VBox/HBox2/Radio" to="." method="_on_radio_pressed"]
-[connection signal="pressed" from="Panel/VBox/HBox2/Mute" to="." method="_on_Mute_pressed"]
+[connection signal="pressed" from="Panel/VBox/HBox2/RoutingAudio" to="." method="_on_routing_audio_pressed"]
+[connection signal="pressed" from="Panel/VBox/HBox2/Mute" to="." method="_on_mute_pressed"]
[connection signal="color_changed" from="ColorPopup/ColorPicker" to="." method="_on_ColorPicker_color_changed"]
diff --git a/addons/gdcef/demos/2D/default_bus_layout.tres b/addons/gdcef/demos/2D/default_bus_layout.tres
new file mode 100644
index 0000000..131f49e
--- /dev/null
+++ b/addons/gdcef/demos/2D/default_bus_layout.tres
@@ -0,0 +1,8 @@
+[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://bebfjoqlbtwr1"]
+
+[sub_resource type="AudioEffectReverb" id="AudioEffectReverb_ox0vc"]
+resource_name = "Reverb"
+
+[resource]
+bus/0/effect/0/effect = SubResource("AudioEffectReverb_ox0vc")
+bus/0/effect/0/enabled = true
diff --git a/addons/gdcef/demos/3D/CEF.tscn b/addons/gdcef/demos/3D/CEF.tscn
index 754dab7..ceec304 100644
--- a/addons/gdcef/demos/3D/CEF.tscn
+++ b/addons/gdcef/demos/3D/CEF.tscn
@@ -80,6 +80,6 @@ offset_bottom = 26.0
[connection signal="gui_input" from="Panel/TextureRect" to="." method="_on_TextureRect_gui_input"]
[connection signal="pressed" from="Panel/Home" to="." method="_on_Home_pressed"]
[connection signal="pressed" from="Panel/Prev" to="." method="_on_Prev_pressed"]
-[connection signal="pressed" from="Panel/Next" to="." method="_on_Prev_pressed"]
[connection signal="pressed" from="Panel/Next" to="." method="_on_Next_pressed"]
+[connection signal="pressed" from="Panel/Next" to="." method="_on_Prev_pressed"]
[connection signal="text_changed" from="Panel/TextEdit" to="." method="_on_TextEdit_text_changed"]
diff --git a/addons/gdcef/demos/3D/GUI.gd b/addons/gdcef/demos/3D/GUI.gd
index 7f9d564..dc61440 100644
--- a/addons/gdcef/demos/3D/GUI.gd
+++ b/addons/gdcef/demos/3D/GUI.gd
@@ -7,7 +7,12 @@
extends Control
# Name of the browser
-const browser_name = "browser1"
+const browser_name = "browser"
+
+# Page with sound
+# "https://www.programmes-radio.com/fr/stream-e8BxeoRhsz9jY9mXXRiFTE/ecouter-KPJK"
+const RADIO_URL = "http://streaming.radio.co/s9378c22ee/listen"
+const HOME_URL = "https://github.com/Lecrapouille/gdcef"
# Memorize if the mouse was pressed
@onready var mouse_pressed : bool = false
@@ -20,7 +25,7 @@ func _on_Home_pressed():
if browser == null:
$Panel/Label.set_text("Failed getting Godot node " + browser_name)
return
- browser.load_url("https://bitbucket.org/chromiumembedded/cef/wiki/Home")
+ browser.load_url(HOME_URL)
pass
# ==============================================================================
@@ -161,10 +166,19 @@ func _ready():
# {"image_loading", true}
# {"databases", true}
# {"webgl", true}
- var browser = $CEF.create_browser("https://github.com/Lecrapouille/gdcef", $Panel/TextureRect, {"javascript":true})
+ var browser = $CEF.create_browser(RADIO_URL, $Panel/TextureRect, {"javascript":true})
browser.name = browser_name
browser.connect("on_page_loaded", _on_page_loaded)
browser.connect("on_page_failed_loading", _on_page_failed_loading)
+ browser.set_zoom_level(0.05)
+
+ # 3D sound
+ get_tree().get_root().print_tree_pretty()
+ var player = get_node("/root/GUIin3D/Background/Cube2/AudioStreamPlayer3D")
+ player.stream = AudioStreamGenerator.new()
+ player.stream.set_buffer_length(1)
+ player.playing = true
+ browser.audio_stream = player.get_stream_playback()
pass
# ==============================================================================
diff --git a/addons/gdcef/demos/3D/gui_in_3d.tscn b/addons/gdcef/demos/3D/gui_in_3d.tscn
index 277c39d..f3e12ad 100644
--- a/addons/gdcef/demos/3D/gui_in_3d.tscn
+++ b/addons/gdcef/demos/3D/gui_in_3d.tscn
@@ -52,7 +52,7 @@ environment = SubResource("Environment_niyks")
[node name="GUIPanel3D" parent="." instance=ExtResource("1")]
[node name="Camera3D" type="Camera3D" parent="."]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 0.999999, 0, 0, 3)
+transform = Transform3D(0.997487, 0, 0.0708434, 0, 1, 0, -0.0708434, 0, 0.997487, 0.372951, 0, 1.79731)
fov = 74.0
near = 0.1
@@ -99,3 +99,14 @@ surface_material_override/0 = SubResource("4")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.88761, 2.01326, 0.374871)
mesh = SubResource("3")
surface_material_override/0 = SubResource("4")
+
+[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="Background/Cube2"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.09059, 0, 8.36119)
+volume_db = 20.0
+emission_angle_enabled = true
+emission_angle_degrees = 17.0
+
+[node name="Cube3" type="MeshInstance3D" parent="Background"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.62461, 1.46641, -0.69153)
+mesh = SubResource("3")
+surface_material_override/0 = SubResource("4")
diff --git a/addons/gdcef/gdcef/src/gdbrowser.cpp b/addons/gdcef/gdcef/src/gdbrowser.cpp
index 854a7db..de46ed0 100644
--- a/addons/gdcef/gdcef/src/gdbrowser.cpp
+++ b/addons/gdcef/gdcef/src/gdbrowser.cpp
@@ -78,6 +78,10 @@ void GDBrowserView::_bind_methods()
ClassDB::bind_method(D_METHOD("set_mouse_wheel_horizontal"), &GDBrowserView::mouseWheelHorizontal);
ClassDB::bind_method(D_METHOD("set_muted"), &GDBrowserView::mute);
ClassDB::bind_method(D_METHOD("is_muted"), &GDBrowserView::muted);
+ ClassDB::bind_method(D_METHOD("set_audio_stream", "audio"), &GDBrowserView::setAudioStreamer);
+ ClassDB::bind_method(D_METHOD("get_audio_stream"), &GDBrowserView::getAudioStreamer);
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "audio_stream", PROPERTY_HINT_NODE_TYPE,
+ "AudioStreamGeneratorPlayback"), "set_audio_stream", "get_audio_stream");
ADD_SIGNAL(MethodInfo("on_page_loaded", PropertyInfo(Variant::OBJECT, "node")));
ADD_SIGNAL(MethodInfo("on_page_failed_loading", PropertyInfo(Variant::BOOL, "aborted"),
@@ -138,6 +142,7 @@ GDBrowserView::GDBrowserView()
BROWSER_DEBUG_VAL("Create Godot texture");
m_impl = new GDBrowserView::Impl(*this);
+ assert((m_impl != nullptr) && "Failed allocating GDBrowserView");
m_image.instantiate();
m_texture.instantiate();
}
@@ -303,7 +308,6 @@ void GDBrowserView::stopLoading()
//------------------------------------------------------------------------------
void GDBrowserView::executeJavaScript(godot::String javascript)
{
-
if (m_browser && m_browser->GetMainFrame())
{
CefString codeStr;
@@ -462,3 +466,33 @@ bool GDBrowserView::muted()
return m_browser->GetHost()->IsAudioMuted();
}
+
+//------------------------------------------------------------------------------
+void GDBrowserView::onAudioStreamStarted(CefRefPtr browser,
+ const CefAudioParameters& params,
+ int channels)
+{
+ m_impl->m_audio.channels = int(params.channel_layout);
+}
+
+//------------------------------------------------------------------------------
+void GDBrowserView::onAudioStreamPacket(CefRefPtr browser,
+ const float** data, int frames, int64_t pts)
+{
+ if ((m_impl == nullptr) || (m_impl->m_audio.streamer == nullptr))
+ {
+ return ;
+ }
+
+ if ((data == nullptr) || (frames <= 0) || (m_impl->m_audio.channels == -1))
+ return;
+
+ auto& streamer = *(m_impl->m_audio.streamer.ptr());
+ if (streamer.can_push_buffer(frames))
+ {
+ for (int i = 0; i < frames; i++)
+ {
+ streamer.push_frame(godot::Vector2(data[0][i], data[0][i]));
+ }
+ }
+}
diff --git a/addons/gdcef/gdcef/src/gdbrowser.hpp b/addons/gdcef/gdcef/src/gdbrowser.hpp
index 35951f8..4e6e755 100644
--- a/addons/gdcef/gdcef/src/gdbrowser.hpp
+++ b/addons/gdcef/gdcef/src/gdbrowser.hpp
@@ -60,6 +60,7 @@
# include "gd_script.hpp"
# include "node.hpp"
# include "image_texture.hpp"
+# include "audio_stream_generator_playback.hpp"
# include "global_constants.hpp"
// Chromium Embedded Framework
@@ -98,17 +99,33 @@ class GDBrowserView : public godot::Node
private: // CEF interfaces
+ // *************************************************************************
+ //! \brief Routing CEF audio to Godot streamer node.
+ // *************************************************************************
+ struct RoutingAudio
+ {
+ //! \brief Godot audio streamer
+ godot::Ref streamer = nullptr;
+ //! \brief Audio received from CEF
+ godot::PackedVector2Array buffer;
+ //! \brief Number of audio channels
+ int channels = -1;
+ };
+
// *************************************************************************
//! \brief Mandatory since Godot ref counter is conflicting with CEF ref
//! counting and therefore we reach with pure virtual destructor called.
//! To avoid this we have to create this intermediate class.
// *************************************************************************
- class Impl: public CefRenderHandler,
+ class Impl: public CefClient,
+ public CefRenderHandler,
public CefLoadHandler,
- public CefClient
+ public CefAudioHandler
{
public:
+ friend GDBrowserView;
+
// ---------------------------------------------------------------------
//! \brief Pass the owner instance.
// ---------------------------------------------------------------------
@@ -146,6 +163,16 @@ class GDBrowserView : public godot::Node
return this;
}
+ // ---------------------------------------------------------------------
+ //! \brief Return the handler for audio rendering events.
+ // ---------------------------------------------------------------------
+ virtual CefRefPtr GetAudioHandler() override
+ {
+ // FIXME this is called once, so we cannot swap modes :( How to do that ?
+ std::cout << (m_audio.streamer == nullptr ? "GetAudioHandler CEF Audio" : "GetAudioHandler Godot audio") << "\n";
+ return m_audio.streamer != nullptr ? this : nullptr;
+ }
+
private: // CefRenderHandler interfaces
// ---------------------------------------------------------------------
@@ -207,9 +234,32 @@ class GDBrowserView : public godot::Node
m_owner.onLoadError(browser, frame, errorCode == ERR_ABORTED, errorText);
}
+ private: // CefAudioHandler interfaces
+
+ virtual void OnAudioStreamStarted(CefRefPtr browser,
+ const CefAudioParameters& params,
+ int channels) override
+ {
+ m_owner.onAudioStreamStarted(browser, params, channels);
+ }
+
+ virtual void OnAudioStreamPacket(CefRefPtr browser,
+ const float** data, int frames,
+ int64_t pts) override
+ {
+ m_owner.onAudioStreamPacket(browser, data, frames, pts);
+ }
+
+ virtual void OnAudioStreamStopped(CefRefPtr browser) override
+ {}
+
+ virtual void OnAudioStreamError(CefRefPtr browser, const CefString& message) override
+ {}
+
private:
GDBrowserView& m_owner;
+ RoutingAudio m_audio;
};
public:
@@ -445,6 +495,21 @@ class GDBrowserView : public godot::Node
//--------------------------------------------------------------------------
bool muted();
+ void setAudioStreamer(godot::Ref streamer)
+ {
+ if (m_impl != nullptr)
+ {
+ m_impl->m_audio.streamer = streamer;
+ }
+ }
+
+ godot::Ref getAudioStreamer()
+ {
+ if (m_impl == nullptr)
+ return nullptr;
+ return m_impl->m_audio.streamer;
+ }
+
private:
void resize_(int width, int height);
@@ -482,6 +547,29 @@ class GDBrowserView : public godot::Node
void onLoadError(CefRefPtr browser, CefRefPtr frame,
const bool aborted, const CefString& errorText);
+ // -------------------------------------------------------------------------
+ //! \brief Called on a browser audio capture thread when the browser starts
+ //! streaming audio. OnAudioStreamStopped will always be called after
+ //! OnAudioStreamStarted; both methods may be called multiple times
+ //! for the same browser. |params| contains the audio parameters like
+ //! sample rate and channel layout. |channels| is the number of channels.
+ // -------------------------------------------------------------------------
+ void onAudioStreamStarted(CefRefPtr browser,
+ const CefAudioParameters& params, int channels);
+
+ // -------------------------------------------------------------------------
+ //! \brief Called on the audio stream thread when a PCM packet is received for the
+ //! stream. |data| is an array representing the raw PCM data as a floating
+ //! point type, i.e. 4-byte value(s). |frames| is the number of frames in the
+ //! PCM packet. |pts| is the presentation timestamp (in milliseconds since the
+ //! Unix Epoch) and represents the time at which the decompressed packet
+ //! should be presented to the user. Based on |frames| and the
+ //! |channel_layout| value passed to OnAudioStreamStarted you can calculate
+ //! the size of the |data| array in bytes.
+ // -------------------------------------------------------------------------
+ void onAudioStreamPacket(CefRefPtr browser, const float** data,
+ int frames, int64_t pts);
+
private:
//! \brief CEF interface implementation