266 lines
7.5 KiB
GDScript
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
|