<audio>
This commit is contained in:
1
flumi/Assets/Icons/pause.svg
Normal file
1
flumi/Assets/Icons/pause.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pause-icon lucide-pause"><rect x="14" y="3" width="5" height="18" rx="1"/><rect x="5" y="3" width="5" height="18" rx="1"/></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
37
flumi/Assets/Icons/pause.svg.import
Normal file
37
flumi/Assets/Icons/pause.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cuucwb1qq2vog"
|
||||
path="res://.godot/imported/pause.svg-b569a82cdc28c1abec0d4398e766838d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/pause.svg"
|
||||
dest_files=["res://.godot/imported/pause.svg-b569a82cdc28c1abec0d4398e766838d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -7,10 +7,10 @@
|
||||
[ext_resource type="Texture2D" uid="uid://dmktpcmm6klre" path="res://Assets/Icons/volume-2.svg" id="4_5j8mp"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xqesv"]
|
||||
content_margin_left = 15.0
|
||||
content_margin_top = 15.0
|
||||
content_margin_right = 15.0
|
||||
content_margin_bottom = 15.0
|
||||
content_margin_left = 10.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 10.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
corner_radius_top_left = 15
|
||||
corner_radius_top_right = 15
|
||||
@@ -81,7 +81,7 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_xqesv")
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Play" type="Button" parent="PanelContainer/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(45, 0)
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_1hbfy")
|
||||
@@ -108,12 +108,9 @@ theme_override_icons/grabber_disabled = ExtResource("3_gfofq")
|
||||
theme_override_styles/slider = SubResource("StyleBoxFlat_1hbfy")
|
||||
theme_override_styles/grabber_area = SubResource("StyleBoxFlat_naep2")
|
||||
theme_override_styles/grabber_area_highlight = SubResource("StyleBoxFlat_ccpdr")
|
||||
value = 50.0
|
||||
editable = false
|
||||
scrollable = false
|
||||
|
||||
[node name="Volume" type="Button" parent="PanelContainer/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(45, 0)
|
||||
custom_minimum_size = Vector2(45, 45)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_1hbfy")
|
||||
@@ -138,7 +135,12 @@ theme_override_icons/grabber = ExtResource("3_gfofq")
|
||||
theme_override_styles/slider = SubResource("StyleBoxFlat_1hbfy")
|
||||
theme_override_styles/grabber_area = SubResource("StyleBoxFlat_naep2")
|
||||
theme_override_styles/grabber_area_highlight = SubResource("StyleBoxFlat_ccpdr")
|
||||
value = 50.0
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
|
||||
[connection signal="pressed" from="PanelContainer/HBoxContainer/Play" to="." method="_on_play_pressed"]
|
||||
[connection signal="value_changed" from="PanelContainer/HBoxContainer/HSlider" to="." method="_on_progress_slider_value_changed"]
|
||||
[connection signal="mouse_entered" from="PanelContainer/HBoxContainer/Volume" to="." method="_on_volume_mouse_entered"]
|
||||
[connection signal="mouse_exited" from="PanelContainer/HBoxContainer/Volume" to="." method="_on_volume_mouse_exited"]
|
||||
[connection signal="pressed" from="PanelContainer/HBoxContainer/Volume" to="." method="_on_volume_pressed"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=28 format=3 uid="uid://bytm7bt2s4ak8"]
|
||||
[gd_scene load_steps=27 format=3 uid="uid://bytm7bt2s4ak8"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bg5iqnwic1rio" path="res://Scripts/main.gd" id="1_8q3xr"]
|
||||
[ext_resource type="Texture2D" uid="uid://df1m4j7uxi63v" path="res://Assets/Icons/chevron-down.svg" id="2_6bp64"]
|
||||
@@ -11,7 +11,6 @@
|
||||
[ext_resource type="Texture2D" uid="uid://cehbtwq6gq0cn" path="res://Assets/Icons/plus.svg" id="5_ynf5e"]
|
||||
[ext_resource type="Script" uid="uid://bgqglerkcylxx" path="res://addons/SmoothScroll/SmoothScrollContainer.gd" id="10_d1ilt"]
|
||||
[ext_resource type="Script" uid="uid://b7h0k2h2qwlqv" path="res://addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd" id="11_6iyac"]
|
||||
[ext_resource type="PackedScene" uid="uid://b7w3dqcvof88f" path="res://Scenes/Tags/audio.tscn" id="12_6iyac"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_344ge"]
|
||||
|
||||
@@ -223,9 +222,6 @@ size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 22
|
||||
|
||||
[node name="Audio" parent="VBoxContainer/ScrollContainer/WebsiteContainer" instance=ExtResource("12_6iyac")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="WebsiteBackground" type="Panel" parent="."]
|
||||
unique_name_in_owner = true
|
||||
z_index = -1
|
||||
|
||||
@@ -14,6 +14,7 @@ class EventSubscription:
|
||||
var connected_signal: String = ""
|
||||
var connected_node: Node = null
|
||||
var callback_func: Callable
|
||||
var wrapper_func: Callable
|
||||
|
||||
var dom_parser: HTMLParser
|
||||
var event_subscriptions: Dictionary = {}
|
||||
|
||||
@@ -2657,6 +2657,141 @@ var HTML_CONTENT_TRANSFORM_TEST = """<head>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
|
||||
# Set the active HTML content to use the transform demo
|
||||
var HTML_CONTENT_AUDIO_TEST = """<head>
|
||||
<title>Audio Tag Demo - HTML5 Audio Testing</title>
|
||||
<icon src="https://picsum.photos/32/32?random=audio">
|
||||
<meta name="theme-color" content="#10b981">
|
||||
<meta name="description" content="Testing HTML5 audio functionality">
|
||||
<style>
|
||||
body { bg-[#f0f9ff] p-8 font-family-system }
|
||||
h1 { text-[#10b981] text-4xl font-bold text-center mb-8 }
|
||||
h2 { text-[#059669] text-2xl font-semibold mb-4 }
|
||||
h3 { text-[#047857] text-xl font-semibold mb-3 }
|
||||
.section { bg-white p-6 rounded-xl shadow-lg mb-6 }
|
||||
.audio-demo { mb-4 p-4 bg-[#f0fdf4] border border-[#bbf7d0] rounded-lg }
|
||||
.code-block { bg-[#1e293b] text-[#e2e8f0] p-3 rounded font-mono text-sm mb-3 }
|
||||
.description { text-[#475569] mb-3 }
|
||||
.btn { bg-[#10b981] text-white px-4 py-2 rounded hover:bg-[#059669] active:bg-[#047857] cursor-pointer }
|
||||
.control-group { flex gap-4 items-center mb-3 }
|
||||
</style>
|
||||
<script>
|
||||
print('hi')
|
||||
local programmaticAudio = nil
|
||||
local controlledAudio = gurt.select("#controlled-audio")
|
||||
local status = gurt.select("#programmatic-status")
|
||||
|
||||
gurt.select("#play-btn"):on("click", function()
|
||||
controlledAudio:play()
|
||||
end)
|
||||
|
||||
gurt.select("#pause-btn"):on("click", function()
|
||||
controlledAudio:pause()
|
||||
end)
|
||||
|
||||
gurt.select("#vol-low-btn"):on("click", function()
|
||||
controlledAudio.volume = 0.3
|
||||
end)
|
||||
|
||||
local test = gurt.setTimeout(function()
|
||||
print('removed')
|
||||
programmaticAudio:play()
|
||||
end, 3000)
|
||||
|
||||
gurt.select("#vol-high-btn"):on("click", function()
|
||||
controlledAudio.volume = 0.7
|
||||
end)
|
||||
|
||||
gurt.select("#toggle-loop-btn"):on("click", function()
|
||||
controlledAudio.loop = false
|
||||
end)
|
||||
|
||||
gurt.select("#create-audio-btn"):on("click", function()
|
||||
programmaticAudio = Audio.new("http://commondatastorage.googleapis.com/codeskulptor-assets/Evillaugh.ogg")
|
||||
|
||||
programmaticAudio.volume = 0.3
|
||||
end)
|
||||
|
||||
gurt.select("#play-prog-btn"):on("click", function()
|
||||
programmaticAudio:play()
|
||||
end)
|
||||
|
||||
gurt.select("#pause-prog-btn"):on("click", function()
|
||||
programmaticAudio:pause()
|
||||
end)
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>🎵 HTML5 Audio Tag Demo</h1>
|
||||
|
||||
<!-- Basic Audio Elements -->
|
||||
<div style="section">
|
||||
<h2>📀 Basic Audio Elements</h2>
|
||||
|
||||
<div style="audio-demo">
|
||||
<h3>Audio with Controls</h3>
|
||||
<div style="description">Standard audio element with visible controls</div>
|
||||
<div style="code-block"><audio src="http://commondatastorage.googleapis.com/codeskulptor-assets/Evillaugh.ogg" controls></audio></div>
|
||||
<audio src="http://commondatastorage.googleapis.com/codeskulptor-assets/Evillaugh.ogg" controls="true"></audio>
|
||||
</div>
|
||||
|
||||
<div style="audio-demo">
|
||||
<h3>Audio with Loop</h3>
|
||||
<div style="description">Audio element that loops automatically</div>
|
||||
<div style="code-block"><audio src="http://commondatastorage.googleapis.com/codeskulptor-demos/DDR_assets/Kangaroo_MusiQue_-_The_Neverwritten_Role_Playing_Game.mp3" controls loop></audio></div>
|
||||
<audio src="http://commondatastorage.googleapis.com/codeskulptor-demos/DDR_assets/Kangaroo_MusiQue_-_The_Neverwritten_Role_Playing_Game.mp3" controls="true" loop="true"></audio>
|
||||
</div>
|
||||
|
||||
<div style="audio-demo">
|
||||
<h3>Audio with Mute</h3>
|
||||
<div style="description">Audio element that starts muted</div>
|
||||
<div style="code-block"><audio src="https://www.kozco.com/tech/LRMonoPhase4.wav" controls muted></audio></div>
|
||||
<audio src="https://www.kozco.com/tech/LRMonoPhase4.wav" controls="true" muted="true"></audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lua Audio Control -->
|
||||
<div style="section">
|
||||
<h2>🎮 Lua Audio Control</h2>
|
||||
|
||||
<div style="audio-demo">
|
||||
<h3>DOM Audio Control</h3>
|
||||
<div style="description">Control audio elements via Lua scripting</div>
|
||||
<audio id="controlled-audio" src="http://commondatastorage.googleapis.com/codeskulptor-assets/Evillaugh.ogg" controls="true"></audio>
|
||||
<div style="control-group">
|
||||
<button id="play-btn" style="btn">▶️ Play</button>
|
||||
<button id="pause-btn" style="btn">⏸️ Pause</button>
|
||||
<button id="vol-low-btn" style="btn">🔉 Vol 30%</button>
|
||||
<button id="vol-high-btn" style="btn">🔊 Vol 70%</button>
|
||||
<button id="toggle-loop-btn" style="btn">🔁 Toggle Loop</button>
|
||||
</div>
|
||||
<div style="code-block">
|
||||
local audio = gurt.select("#controlled-audio")
|
||||
audio.play()
|
||||
audio.volume = 0.5
|
||||
audio.loop = true
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="audio-demo">
|
||||
<h3>Programmatic Audio</h3>
|
||||
<div style="description">Create audio instances with Audio.new()</div>
|
||||
<div style="control-group">
|
||||
<button id="create-audio-btn" style="btn">🎵 Create Audio</button>
|
||||
<button id="play-prog-btn" style="btn">▶️ Play</button>
|
||||
<button id="pause-prog-btn" style="btn">⏸️ Pause</button>
|
||||
</div>
|
||||
<div id="programmatic-status" style="text-[#059669] font-semibold"></div>
|
||||
<div style="code-block">
|
||||
local audio = Audio.new("http://commondatastorage.googleapis.com/codeskulptor-assets/Evillaugh.ogg")
|
||||
audio.volume = 0.3
|
||||
audio.play()
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
|
||||
# Set the active HTML content to use the audio demo
|
||||
func _ready():
|
||||
HTML_CONTENT = HTML_CONTENT_TRANSFORM_TEST
|
||||
HTML_CONTENT = HTML_CONTENT_AUDIO_TEST
|
||||
|
||||
@@ -1,31 +1,377 @@
|
||||
class_name HTMLAudio
|
||||
extends VBoxContainer
|
||||
|
||||
@onready var popup_panel: PopupPanel = $PopupPanel
|
||||
@onready var play_button: Button = $PanelContainer/HBoxContainer/Play
|
||||
@onready var time_label: RichTextLabel = $PanelContainer/HBoxContainer/RichTextLabel
|
||||
@onready var progress_slider: HSlider = $PanelContainer/HBoxContainer/HSlider
|
||||
@onready var volume_button: Button = $PanelContainer/HBoxContainer/Volume
|
||||
@onready var volume_slider: VSlider = $PopupPanel/VSlider
|
||||
@onready var audio_player: AudioStreamPlayer = $AudioStreamPlayer
|
||||
|
||||
const PLAY_ICON = preload("res://Assets/Icons/play.svg")
|
||||
const PAUSE_ICON = preload("res://Assets/Icons/pause.svg")
|
||||
const VOLUME_OFF = preload("res://Assets/Icons/volume-off.svg")
|
||||
const VOLUME_2 = preload("res://Assets/Icons/volume-2.svg")
|
||||
|
||||
var current_element: HTMLParser.HTMLElement
|
||||
var current_parser: HTMLParser
|
||||
var is_muted = false
|
||||
var initial_volume_value = 0.0
|
||||
var initial_volume_value = 50.0
|
||||
var is_playing = false
|
||||
var user_initiated_play = false
|
||||
var progress_timer: Timer
|
||||
var updating_slider = false
|
||||
var in_user_click_context = false
|
||||
|
||||
var volume: float = 0.5:
|
||||
set(value):
|
||||
volume = clamp(value, 0.0, 1.0)
|
||||
if audio_player:
|
||||
audio_player.volume_db = linear_to_db(volume)
|
||||
if volume_slider:
|
||||
volume_slider.value = volume * 100
|
||||
get:
|
||||
return volume
|
||||
|
||||
var loop: bool = false:
|
||||
set(value):
|
||||
loop = value
|
||||
get:
|
||||
return loop
|
||||
|
||||
var muted: bool = false:
|
||||
set(value):
|
||||
muted = value
|
||||
is_muted = value
|
||||
update_volume_display()
|
||||
get:
|
||||
return is_muted
|
||||
|
||||
func _ready():
|
||||
popup_panel.hide()
|
||||
initial_volume_value = volume_slider.value
|
||||
if popup_panel:
|
||||
popup_panel.hide()
|
||||
|
||||
if volume_slider:
|
||||
initial_volume_value = volume_slider.value
|
||||
volume = initial_volume_value / 100.0
|
||||
|
||||
if play_button:
|
||||
play_button.icon = PLAY_ICON
|
||||
|
||||
if volume_button:
|
||||
volume_button.icon = VOLUME_2
|
||||
|
||||
# Set up audio player
|
||||
if audio_player:
|
||||
audio_player.finished.connect(_on_audio_finished)
|
||||
|
||||
progress_timer = Timer.new()
|
||||
progress_timer.wait_time = 0.5
|
||||
progress_timer.timeout.connect(_on_progress_timer_timeout)
|
||||
add_child(progress_timer)
|
||||
|
||||
func init(element: HTMLParser.HTMLElement, parser: HTMLParser) -> void:
|
||||
current_element = element
|
||||
current_parser = parser
|
||||
|
||||
# Parse attributes
|
||||
var src = element.get_attribute("src")
|
||||
var controls = element.has_attribute("controls")
|
||||
var loop_attr = element.has_attribute("loop")
|
||||
var muted_attr = element.has_attribute("muted")
|
||||
|
||||
if not controls:
|
||||
visible = false
|
||||
|
||||
if src.is_empty():
|
||||
return
|
||||
|
||||
loop = loop_attr
|
||||
if muted_attr:
|
||||
muted = true
|
||||
is_muted = true
|
||||
update_volume_display()
|
||||
|
||||
load_audio_async(src)
|
||||
parser.register_dom_node(element, self)
|
||||
|
||||
func load_audio_async(src: String) -> void:
|
||||
if not is_inside_tree():
|
||||
await tree_entered
|
||||
|
||||
reset_stream_state()
|
||||
|
||||
if not src.begins_with("http"):
|
||||
return
|
||||
|
||||
var http_request = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
|
||||
http_request.download_chunk_size = 65536
|
||||
http_request.timeout = 30
|
||||
|
||||
http_request.request_completed.connect(_on_audio_download_completed)
|
||||
var error = http_request.request(src)
|
||||
|
||||
if error != OK:
|
||||
http_request.queue_free()
|
||||
return
|
||||
|
||||
func _on_audio_download_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
var http_request = get_children().filter(func(child): return child is HTTPRequest)[0]
|
||||
http_request.queue_free()
|
||||
|
||||
if response_code != 200:
|
||||
return
|
||||
|
||||
if body.size() == 0:
|
||||
return
|
||||
|
||||
var content_type = ""
|
||||
for header in headers:
|
||||
if header.to_lower().begins_with("content-type:"):
|
||||
content_type = header.split(":")[1].strip_edges().to_lower()
|
||||
break
|
||||
|
||||
var audio_stream: AudioStream
|
||||
|
||||
if "ogg" in content_type or "vorbis" in content_type:
|
||||
audio_stream = AudioStreamOggVorbis.load_from_buffer(body)
|
||||
if not audio_stream:
|
||||
return
|
||||
elif "wav" in content_type or "wave" in content_type:
|
||||
audio_stream = AudioStreamWAV.new()
|
||||
audio_stream.data = body
|
||||
audio_stream.format = AudioStreamWAV.FORMAT_16_BITS
|
||||
audio_stream.mix_rate = 44100
|
||||
audio_stream.stereo = true
|
||||
audio_stream.loop_mode = AudioStreamWAV.LOOP_DISABLED
|
||||
elif "mp3" in content_type or "mpeg" in content_type:
|
||||
audio_stream = AudioStreamMP3.load_from_buffer(body)
|
||||
if not audio_stream:
|
||||
audio_stream = AudioStreamMP3.new()
|
||||
audio_stream.data = body
|
||||
else:
|
||||
return
|
||||
|
||||
if audio_stream:
|
||||
if audio_player:
|
||||
audio_player.stream = audio_stream
|
||||
|
||||
on_stream_loaded()
|
||||
|
||||
func reset_stream_state():
|
||||
cached_duration = -1.0
|
||||
stream_load_failed = false
|
||||
|
||||
func on_stream_loaded():
|
||||
reset_stream_state()
|
||||
|
||||
progress_slider.editable = true
|
||||
progress_slider.scrollable = true
|
||||
|
||||
update_duration_display()
|
||||
|
||||
if volume_slider:
|
||||
volume_slider.value = volume * 100
|
||||
|
||||
if is_muted:
|
||||
update_volume_display()
|
||||
|
||||
func update_duration_display():
|
||||
if time_label:
|
||||
var duration = get_duration()
|
||||
time_label.text = "00:00/" + format_time(duration)
|
||||
|
||||
var last_progress_update: float = 0.0
|
||||
|
||||
func _on_progress_timer_timeout():
|
||||
if not is_playing or not audio_player.stream or updating_slider:
|
||||
return
|
||||
|
||||
var current_pos = audio_player.get_playback_position()
|
||||
var total_length = audio_player.stream.get_length()
|
||||
|
||||
var time_diff = abs(current_pos - last_progress_update)
|
||||
if time_diff < 0.1:
|
||||
return
|
||||
|
||||
last_progress_update = current_pos
|
||||
|
||||
if total_length > 0 and progress_slider:
|
||||
updating_slider = true
|
||||
progress_slider.value = (current_pos / total_length) * 100
|
||||
updating_slider = false
|
||||
|
||||
if time_label:
|
||||
time_label.text = format_time(current_pos) + "/" + format_time(total_length)
|
||||
|
||||
|
||||
var cached_duration: float = -1.0
|
||||
var stream_load_failed: bool = false
|
||||
|
||||
func get_stream_length_safe() -> float:
|
||||
if not audio_player or not audio_player.stream:
|
||||
return 0.0
|
||||
|
||||
if cached_duration > 0:
|
||||
return cached_duration
|
||||
|
||||
if stream_load_failed:
|
||||
return 0.0
|
||||
|
||||
var length = 0.0
|
||||
if audio_player.stream.has_method("get_length"):
|
||||
if audio_player.stream is AudioStreamOggVorbis:
|
||||
var ogg_stream = audio_player.stream as AudioStreamOggVorbis
|
||||
if not ogg_stream.packet_sequence:
|
||||
stream_load_failed = true
|
||||
return 0.0
|
||||
|
||||
length = audio_player.stream.get_length()
|
||||
|
||||
if length <= 0 or is_nan(length) or is_inf(length):
|
||||
if audio_player.stream is AudioStreamOggVorbis:
|
||||
stream_load_failed = true
|
||||
length = 0.0
|
||||
else:
|
||||
cached_duration = length
|
||||
|
||||
return length
|
||||
|
||||
func format_time(seconds: float) -> String:
|
||||
var mins = int(seconds / 60)
|
||||
var secs = int(seconds) % 60
|
||||
return "%02d:%02d" % [mins, secs]
|
||||
|
||||
func update_volume_display():
|
||||
if volume_button:
|
||||
if is_muted:
|
||||
volume_button.icon = VOLUME_OFF
|
||||
else:
|
||||
volume_button.icon = VOLUME_2
|
||||
|
||||
if audio_player:
|
||||
if is_muted:
|
||||
audio_player.volume_db = -80
|
||||
else:
|
||||
audio_player.volume_db = linear_to_db(volume)
|
||||
|
||||
func play() -> bool:
|
||||
if not audio_player or not audio_player.stream:
|
||||
return false
|
||||
|
||||
if stream_load_failed:
|
||||
return false
|
||||
|
||||
if not user_initiated_play:
|
||||
return false
|
||||
|
||||
if audio_player.stream is AudioStreamOggVorbis:
|
||||
var ogg_stream = audio_player.stream as AudioStreamOggVorbis
|
||||
if not ogg_stream.packet_sequence or ogg_stream.packet_sequence.granule_positions.size() == 0:
|
||||
stream_load_failed = true
|
||||
return false
|
||||
|
||||
if audio_player.stream_paused:
|
||||
audio_player.stream_paused = false
|
||||
else:
|
||||
audio_player.play()
|
||||
|
||||
is_playing = true
|
||||
if visible and play_button:
|
||||
play_button.icon = PAUSE_ICON
|
||||
|
||||
if progress_timer:
|
||||
progress_timer.start()
|
||||
|
||||
return true
|
||||
|
||||
func pause() -> void:
|
||||
if audio_player:
|
||||
audio_player.stream_paused = true
|
||||
is_playing = false
|
||||
if visible and play_button:
|
||||
play_button.icon = PLAY_ICON
|
||||
|
||||
progress_timer.stop()
|
||||
|
||||
func stop() -> void:
|
||||
if audio_player:
|
||||
audio_player.stop()
|
||||
is_playing = false
|
||||
if visible and play_button:
|
||||
play_button.icon = PLAY_ICON
|
||||
|
||||
progress_timer.stop()
|
||||
|
||||
func get_current_time() -> float:
|
||||
if audio_player:
|
||||
return audio_player.get_playback_position()
|
||||
return 0.0
|
||||
|
||||
func get_duration() -> float:
|
||||
if audio_player and audio_player.stream:
|
||||
return audio_player.stream.get_length()
|
||||
return 0.0
|
||||
|
||||
func set_current_time(time: float) -> void:
|
||||
if audio_player and audio_player.stream:
|
||||
if audio_player.stream_paused:
|
||||
audio_player.stream_paused = false
|
||||
audio_player.play(time)
|
||||
audio_player.stream_paused = true
|
||||
else:
|
||||
audio_player.seek(time)
|
||||
|
||||
func _on_play_pressed():
|
||||
user_initiated_play = true
|
||||
if is_playing:
|
||||
pause()
|
||||
else:
|
||||
play()
|
||||
user_initiated_play = false
|
||||
|
||||
func _on_progress_slider_value_changed(value: float):
|
||||
if updating_slider:
|
||||
return
|
||||
|
||||
if audio_player and audio_player.stream and progress_slider.editable:
|
||||
var total_length = audio_player.stream.get_length()
|
||||
if total_length > 0:
|
||||
var target_time = (value / 100.0) * total_length
|
||||
set_current_time(target_time)
|
||||
|
||||
func _on_audio_finished():
|
||||
if loop and audio_player:
|
||||
audio_player.play()
|
||||
else:
|
||||
is_playing = false
|
||||
|
||||
if visible and progress_slider:
|
||||
progress_slider.value = 100
|
||||
if visible and time_label:
|
||||
var total_length = get_duration()
|
||||
time_label.text = format_time(total_length) + "/" + format_time(total_length)
|
||||
|
||||
if visible and play_button:
|
||||
play_button.icon = PLAY_ICON
|
||||
|
||||
if progress_timer:
|
||||
progress_timer.stop()
|
||||
|
||||
func _on_volume_pressed():
|
||||
is_muted = !is_muted
|
||||
update_volume_display()
|
||||
|
||||
if is_muted:
|
||||
volume_button.icon = VOLUME_OFF
|
||||
else:
|
||||
volume_button.icon = VOLUME_2
|
||||
|
||||
if popup_panel.is_visible():
|
||||
if popup_panel and popup_panel.is_visible():
|
||||
popup_panel.hide()
|
||||
|
||||
func _on_volume_mouse_entered():
|
||||
if not popup_panel or not volume_button:
|
||||
return
|
||||
|
||||
if popup_panel.is_visible():
|
||||
return
|
||||
|
||||
@@ -36,6 +382,9 @@ func _on_volume_mouse_entered():
|
||||
popup_panel.show()
|
||||
|
||||
func _on_volume_mouse_exited():
|
||||
if not volume_slider or not popup_panel:
|
||||
return
|
||||
|
||||
if volume_slider.value == initial_volume_value:
|
||||
await get_tree().create_timer(0.3).timeout
|
||||
|
||||
@@ -48,8 +397,18 @@ func _on_volume_mouse_exited():
|
||||
popup_panel.hide()
|
||||
|
||||
func _on_popup_panel_focus_exited() -> void:
|
||||
popup_panel.hide()
|
||||
initial_volume_value = volume_slider.value
|
||||
if popup_panel:
|
||||
popup_panel.hide()
|
||||
if volume_slider:
|
||||
initial_volume_value = volume_slider.value
|
||||
|
||||
func _on_volume_slider_value_changed(value: float) -> void:
|
||||
initial_volume_value = value
|
||||
volume = value / 100.0
|
||||
if not is_muted:
|
||||
update_volume_display()
|
||||
|
||||
func _deferred_play_with_user_context(is_user_initiated: bool) -> void:
|
||||
user_initiated_play = is_user_initiated
|
||||
play()
|
||||
user_initiated_play = false
|
||||
|
||||
236
flumi/Scripts/Utils/Lua/Audio.gd
Normal file
236
flumi/Scripts/Utils/Lua/Audio.gd
Normal 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
|
||||
1
flumi/Scripts/Utils/Lua/Audio.gd.uid
Normal file
1
flumi/Scripts/Utils/Lua/Audio.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://defnka61xoi8d
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -28,6 +28,7 @@ const SELECT = preload("res://Scenes/Tags/select.tscn")
|
||||
const OPTION = preload("res://Scenes/Tags/option.tscn")
|
||||
const TEXTAREA = preload("res://Scenes/Tags/textarea.tscn")
|
||||
const DIV = preload("res://Scenes/Tags/div.tscn")
|
||||
const AUDIO = preload("res://Scenes/Tags/audio.tscn")
|
||||
|
||||
const MIN_SIZE = Vector2i(750, 200)
|
||||
|
||||
@@ -126,7 +127,7 @@ func render() -> void:
|
||||
var inline_node = await create_element_node(inline_element, parser)
|
||||
if inline_node:
|
||||
# Input elements register their own DOM nodes in their init() function
|
||||
if inline_element.tag_name not in ["input", "textarea", "select", "button"]:
|
||||
if inline_element.tag_name not in ["input", "textarea", "select", "button", "audio"]:
|
||||
parser.register_dom_node(inline_element, inline_node)
|
||||
|
||||
safe_add_child(hbox, inline_node)
|
||||
@@ -142,7 +143,7 @@ func render() -> void:
|
||||
var element_node = await create_element_node(element, parser)
|
||||
if element_node:
|
||||
# Input elements register their own DOM nodes in their init() function
|
||||
if element.tag_name not in ["input", "textarea", "select", "button"]:
|
||||
if element.tag_name not in ["input", "textarea", "select", "button", "audio"]:
|
||||
parser.register_dom_node(element, element_node)
|
||||
|
||||
# ul/ol handle their own adding
|
||||
@@ -279,7 +280,7 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
|
||||
var child_node = await create_element_node(child_element, parser)
|
||||
if child_node and is_instance_valid(container_for_children):
|
||||
# Input elements register their own DOM nodes in their init() function
|
||||
if child_element.tag_name not in ["input", "textarea", "select", "button"]:
|
||||
if child_element.tag_name not in ["input", "textarea", "select", "button", "audio"]:
|
||||
parser.register_dom_node(child_element, child_node)
|
||||
safe_add_child(container_for_children, child_node)
|
||||
|
||||
@@ -360,6 +361,9 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP
|
||||
"textarea":
|
||||
node = TEXTAREA.instantiate()
|
||||
node.init(element, parser)
|
||||
"audio":
|
||||
node = AUDIO.instantiate()
|
||||
node.init(element, parser)
|
||||
"div":
|
||||
var styles = parser.get_element_styles_with_inheritance(element, "", [])
|
||||
var hover_styles = parser.get_element_styles_with_inheritance(element, "hover", [])
|
||||
|
||||
Reference in New Issue
Block a user