This commit is contained in:
Face
2025-08-11 17:08:39 +03:00
parent 0ca930ed93
commit 513b0bf83a
13 changed files with 849 additions and 39 deletions

View File

@@ -0,0 +1,236 @@
class_name LuaAudioUtils
extends RefCounted
static var last_user_event_time: int = 0
static var user_event_window_ms: int = 100
static func setup_audio_api(vm: LuauVM):
vm.lua_newtable()
vm.lua_pushcallable(_lua_audio_new_handler, "Audio.new")
vm.lua_setfield(-2, "new")
vm.lua_setglobal("Audio")
static func mark_user_event():
last_user_event_time = Time.get_ticks_msec()
static func _check_if_likely_user_event(current_time: int) -> bool:
var time_since_user_event = current_time - last_user_event_time
return time_since_user_event < user_event_window_ms
static func _defer_audio_setup(audio_node: HTMLAudio):
var main_scene = Engine.get_main_loop().current_scene
main_scene.add_child(audio_node)
audio_node.visible = false
var element = audio_node.current_element
var src = element.get_attribute("src")
if not src.is_empty():
audio_node.loop = element.has_attribute("loop")
if element.has_attribute("muted"):
audio_node.muted = true
audio_node.load_audio_async(src)
static func _lua_audio_new_handler(vm: LuauVM) -> int:
var url: String = vm.luaL_checkstring(1)
var audio_scene = preload("res://Scenes/Tags/audio.tscn")
var audio_node = audio_scene.instantiate() as HTMLAudio
var dummy_element = HTMLParser.HTMLElement.new("audio")
dummy_element.set_attribute("src", url)
dummy_element.set_attribute("controls", "false")
audio_node.current_element = dummy_element
audio_node.current_parser = null
audio_node.visible = false
audio_node.set_meta("deferred_url", url)
_defer_audio_setup.call_deferred(audio_node)
vm.lua_newtable()
vm.lua_pushobject(audio_node)
vm.lua_setfield(-2, "_audio_node")
vm.lua_pushcallable(_lua_audio_play_handler, "Audio.play")
vm.lua_setfield(-2, "play")
vm.lua_pushcallable(_lua_audio_pause_handler, "Audio.pause")
vm.lua_setfield(-2, "pause")
vm.lua_pushcallable(_lua_audio_stop_handler, "Audio.stop")
vm.lua_setfield(-2, "stop")
# Set up metatable for property access
vm.lua_newtable()
vm.lua_pushcallable(_lua_audio_index_handler, "Audio.__index")
vm.lua_setfield(-2, "__index")
vm.lua_pushcallable(_lua_audio_newindex_handler, "Audio.__newindex")
vm.lua_setfield(-2, "__newindex")
vm.lua_setmetatable(-2)
return 1
static func _get_audio_node_from_table(vm: LuauVM) -> HTMLAudio:
vm.lua_getfield(1, "_audio_node")
var audio_node = vm.lua_toobject(-1) as HTMLAudio
vm.lua_pop(1)
return audio_node
static func _lua_audio_play_handler(vm: LuauVM) -> int:
var audio_node = _get_audio_node_from_table(vm)
if audio_node:
var current_time = Time.get_ticks_msec()
var is_likely_user_event = _check_if_likely_user_event(current_time)
audio_node.call_deferred("_deferred_play_with_user_context", is_likely_user_event)
vm.lua_pushboolean(true)
else:
vm.lua_pushboolean(false)
return 1
static func _lua_audio_pause_handler(vm: LuauVM) -> int:
var audio_node = _get_audio_node_from_table(vm)
if audio_node:
audio_node.call_deferred("pause")
return 0
static func _lua_audio_stop_handler(vm: LuauVM) -> int:
var audio_node = _get_audio_node_from_table(vm)
if audio_node:
audio_node.call_deferred("stop")
return 0
# Property access handlers for programmatic audio
static func _lua_audio_index_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var key: String = vm.luaL_checkstring(2)
var audio_node = _get_audio_node_from_table(vm)
if not audio_node:
vm.lua_pushnil()
return 1
match key:
"volume":
vm.lua_pushnumber(audio_node.volume)
return 1
"loop":
vm.lua_pushboolean(audio_node.loop)
return 1
"currentTime":
vm.lua_pushnumber(audio_node.get_current_time())
return 1
"duration":
vm.lua_pushnumber(audio_node.get_duration())
return 1
_:
# Look up other methods/properties in the table itself
vm.lua_rawget(1)
return 1
static func _lua_audio_newindex_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var key: String = vm.luaL_checkstring(2)
var value = vm.lua_tovariant(3)
var audio_node = _get_audio_node_from_table(vm)
if not audio_node:
return 0
match key:
"volume":
audio_node.call_deferred("set", "volume", float(value))
return 0
"loop":
audio_node.call_deferred("set", "loop", bool(value))
return 0
"currentTime":
audio_node.call_deferred("set_current_time", float(value))
return 0
_:
vm.lua_rawset(1)
return 0
static func _dom_audio_play_handler(vm: LuauVM) -> int:
var element_id: String = vm.luaL_checkstring(1)
var lua_api = vm.get_meta("lua_api") as LuaAPI
if not lua_api:
return 0
mark_user_event()
var audio_node = _get_dom_audio_node(element_id, lua_api)
if audio_node:
audio_node.call_deferred("_deferred_play_with_user_context", true)
return 0
static func _dom_audio_pause_handler(vm: LuauVM) -> int:
var element_id: String = vm.luaL_checkstring(1)
var lua_api = vm.get_meta("lua_api") as LuaAPI
if not lua_api:
return 0
var audio_node = _get_dom_audio_node(element_id, lua_api)
if audio_node:
audio_node.call_deferred("pause")
return 0
static func _get_dom_audio_node(element_id: String, lua_api) -> HTMLAudio:
return lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null) as HTMLAudio
static func handle_dom_audio_index(vm: LuauVM, element_id: String, key: String) -> int:
var lua_api = vm.get_meta("lua_api") as LuaAPI
var audio_node = _get_dom_audio_node(element_id, lua_api)
if not audio_node:
vm.lua_pushnil()
return 1
match key:
"play":
var play_code = "return function(self) _dom_audio_play('" + element_id + "') end"
vm.load_string(play_code, "audio.play_closure")
if vm.lua_pcall(0, 1, 0) == vm.LUA_OK:
return 1
else:
vm.lua_pop(1)
vm.lua_pushnil()
return 1
"pause":
var pause_code = "return function(self) _dom_audio_pause('" + element_id + "') end"
vm.load_string(pause_code, "audio.pause_closure")
if vm.lua_pcall(0, 1, 0) == vm.LUA_OK:
return 1
else:
vm.lua_pop(1)
vm.lua_pushnil()
return 1
"volume":
vm.lua_pushnumber(audio_node.volume)
return 1
"loop":
vm.lua_pushboolean(audio_node.loop)
return 1
"currentTime":
vm.lua_pushnumber(audio_node.get_current_time())
return 1
"duration":
vm.lua_pushnumber(audio_node.get_duration())
return 1
return 0
static func handle_dom_audio_newindex(vm: LuauVM, element_id: String, key: String, value: Variant) -> int:
var lua_api = vm.get_meta("lua_api") as LuaAPI
var audio_node = _get_dom_audio_node(element_id, lua_api)
if not audio_node:
return 0
match key:
"volume":
audio_node.call_deferred("set", "volume", float(value))
"loop":
audio_node.call_deferred("set", "loop", bool(value))
"currentTime":
audio_node.call_deferred("set_current_time", float(value))
return 0

View File

@@ -0,0 +1 @@
uid://defnka61xoi8d

View File

@@ -477,6 +477,12 @@ static func create_element_wrapper(vm: LuauVM, element: HTMLParser.HTMLElement,
static func add_element_methods(vm: LuauVM, lua_api: LuaAPI) -> void:
vm.set_meta("lua_api", lua_api)
vm.lua_pushcallable(LuaAudioUtils._dom_audio_play_handler, "_dom_audio_play")
vm.lua_setglobal("_dom_audio_play")
vm.lua_pushcallable(LuaAudioUtils._dom_audio_pause_handler, "_dom_audio_pause")
vm.lua_setglobal("_dom_audio_pause")
vm.lua_pushcallable(LuaDOMUtils._element_on_wrapper, "element.on")
vm.lua_setfield(-2, "on")
@@ -827,10 +833,20 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var key: String = vm.luaL_checkstring(2)
var lua_api = vm.get_meta("lua_api") as LuaAPI
vm.lua_getfield(1, "_tag_name")
var tag_name: String = vm.lua_tostring(-1)
vm.lua_pop(1)
if tag_name == "audio":
vm.lua_getfield(1, "_element_id")
var element_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
return LuaAudioUtils.handle_dom_audio_index(vm, element_id, key)
match key:
"text":
# Get lua_api from VM metadata
var lua_api = vm.get_meta("lua_api") as LuaAPI
if lua_api:
# Get element ID and find the element
vm.lua_getfield(1, "_element_id")
@@ -846,8 +862,6 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
vm.lua_pushstring("")
return 1
"children":
# Get lua_api from VM metadata
var lua_api = vm.get_meta("lua_api") as LuaAPI
if lua_api:
# Get element ID and find the element
vm.lua_getfield(1, "_element_id")
@@ -870,7 +884,6 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
return 1
_:
# Check for DOM traversal properties first
var lua_api = vm.get_meta("lua_api") as LuaAPI
if lua_api:
match key:
"parent":
@@ -991,6 +1004,16 @@ static func _element_newindex_wrapper(vm: LuauVM) -> int:
var key: String = vm.luaL_checkstring(2)
var value = vm.lua_tovariant(3)
vm.lua_getfield(1, "_tag_name")
var tag_name: String = vm.lua_tostring(-1)
vm.lua_pop(1)
if tag_name == "audio":
vm.lua_getfield(1, "_element_id")
var element_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
return LuaAudioUtils.handle_dom_audio_newindex(vm, element_id, key, value)
match key:
"text":
var text: String = str(value) # Convert value to string

View File

@@ -14,14 +14,22 @@ static func connect_element_event(signal_node: Node, event_name: String, subscri
match event_name:
"click":
if signal_node.has_signal("pressed"):
signal_node.pressed.connect(subscription.lua_api._on_event_triggered.bind(subscription))
var wrapper = func():
LuaAudioUtils.mark_user_event()
subscription.lua_api._on_event_triggered(subscription)
signal_node.pressed.connect(wrapper)
subscription.connected_signal = "pressed"
subscription.connected_node = signal_node if signal_node != subscription.lua_api.get_dom_node(signal_node.get_parent(), "signal") else null
subscription.wrapper_func = wrapper
return true
elif signal_node is Control:
signal_node.gui_input.connect(subscription.lua_api._on_gui_input_click.bind(subscription))
var wrapper = func(event: InputEvent):
LuaAudioUtils.mark_user_event()
subscription.lua_api._on_gui_input_click(subscription, event)
signal_node.gui_input.connect(wrapper)
subscription.connected_signal = "gui_input"
subscription.connected_node = signal_node
subscription.wrapper_func = wrapper
return true
"mousedown", "mouseup":
if signal_node is Control:
@@ -212,10 +220,16 @@ static func disconnect_subscription(subscription, lua_api) -> void:
match subscription.connected_signal:
"pressed":
if target_node.has_signal("pressed"):
target_node.pressed.disconnect(lua_api._on_event_triggered.bind(subscription))
if subscription.has("wrapper_func") and subscription.wrapper_func:
target_node.pressed.disconnect(subscription.wrapper_func)
else:
target_node.pressed.disconnect(lua_api._on_event_triggered.bind(subscription))
"gui_input":
if target_node.has_signal("gui_input"):
target_node.gui_input.disconnect(lua_api._on_gui_input_click.bind(subscription))
if subscription.has("wrapper_func") and subscription.wrapper_func:
target_node.gui_input.disconnect(subscription.wrapper_func)
else:
target_node.gui_input.disconnect(lua_api._on_gui_input_click.bind(subscription))
"gui_input_mouse":
if target_node.has_signal("gui_input"):
target_node.gui_input.disconnect(lua_api._on_gui_input_mouse_universal.bind(target_node))

View File

@@ -333,6 +333,7 @@ func _setup_additional_lua_apis():
LuaNetworkUtils.setup_network_api(lua_vm)
LuaJSONUtils.setup_json_api(lua_vm)
LuaWebSocketUtils.setup_websocket_api(lua_vm)
LuaAudioUtils.setup_audio_api(lua_vm)
func _threaded_table_tostring_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)