Files
leonwww/flumi/Scripts/Utils/Lua/Audio.gd
2025-09-11 17:46:32 +03:00

266 lines
7.5 KiB
GDScript

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
"paused":
vm.lua_pushboolean(not audio_node.is_playing)
return 1
"src":
if audio_node.current_element:
vm.lua_pushstring(audio_node.current_element.get_attribute("src"))
else:
vm.lua_pushstring("")
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
"src":
var src_url = str(value)
if audio_node.current_element:
audio_node.current_element.set_attribute("src", src_url)
audio_node.call_deferred("load_audio_async", src_url)
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
"paused":
vm.lua_pushboolean(not audio_node.is_playing)
return 1
"src":
if audio_node.current_element:
vm.lua_pushstring(audio_node.current_element.get_attribute("src"))
else:
vm.lua_pushstring("")
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))
"src":
var src_url = str(value)
if audio_node.current_element:
audio_node.current_element.set_attribute("src", src_url)
audio_node.call_deferred("load_audio_async", src_url)
return 0