moss_core/render/
audio.rs1use crate::asset_snapshot::AssetSnapshot;
26use crate::path_ext::path_extension_lower;
27use crate::resolve::embed_renderer::html_escape_attr;
28use crate::resolve::title_params::TitleParams;
29
30#[allow(unused_variables)]
44pub fn synthesize_audio_html(
45 params: &TitleParams,
46 src: &str,
47 assets: &AssetSnapshot,
48) -> String {
49 let ext = path_extension_lower(src);
50 let mime = audio_mime_for_ext(&ext);
51 let escaped = html_escape_attr(src);
52
53 format!(
54 "<audio class=\"moss-embed moss-embed-audio\" controls preload=\"metadata\"><source src=\"{}\" type=\"{}\">Your browser does not support the audio tag.</audio>",
55 escaped, mime,
56 )
57}
58
59
60fn audio_mime_for_ext(ext: &str) -> &'static str {
67 match ext {
68 "mp3" => "audio/mpeg",
69 "wav" => "audio/wav",
70 "ogg" => "audio/ogg",
71 "flac" => "audio/flac",
72 "m4a" => "audio/mp4",
73 "opus" => "audio/opus",
74 _ => "application/octet-stream",
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 fn empty_snapshot() -> AssetSnapshot {
83 AssetSnapshot::new()
84 }
85
86 fn params_with(kvs: &[(&str, &str)]) -> TitleParams {
87 let mut p = TitleParams::default();
88 for (k, v) in kvs {
89 p.insert(*k, *v);
90 }
91 p
92 }
93
94 #[test]
95 fn audio_basic_shape() {
96 let p = params_with(&[("kind", "audio")]);
97 let out = synthesize_audio_html(&p, "track.mp3", &empty_snapshot());
98 assert!(out.contains("<audio"), "got: {}", out);
99 assert!(out.contains(r#"src="track.mp3""#));
100 }
101
102 #[test]
103 fn audio_with_controls() {
104 let p = params_with(&[("kind", "audio"), ("controls", "true")]);
105 let out = synthesize_audio_html(&p, "track.mp3", &empty_snapshot());
106 assert!(out.contains("controls"));
107 }
108
109 #[test]
110 fn audio_preserves_extension() {
111 let p = params_with(&[("kind", "audio")]);
113 let mp3 = synthesize_audio_html(&p, "track.mp3", &empty_snapshot());
114 let ogg = synthesize_audio_html(&p, "track.ogg", &empty_snapshot());
115 assert!(mp3.contains(r#"src="track.mp3""#));
116 assert!(ogg.contains(r#"src="track.ogg""#));
117 }
118
119 #[test]
120 fn audio_escapes_url() {
121 let p = params_with(&[("kind", "audio")]);
122 let out = synthesize_audio_html(&p, r#"file with "quotes".mp3"#, &empty_snapshot());
123 assert!(out.contains(r#""quotes""#), "got: {}", out);
124 }
125
126 #[test]
127 fn audio_emits_pre_phase_0_class_and_attrs() {
128 let p = params_with(&[("kind", "audio")]);
130 let out = synthesize_audio_html(&p, "track.mp3", &empty_snapshot());
131 assert!(
132 out.starts_with(r#"<audio class="moss-embed moss-embed-audio" controls preload="metadata">"#),
133 "got: {}",
134 out,
135 );
136 assert!(out.contains("Your browser does not support the audio tag."));
137 assert!(out.ends_with("</audio>"));
138 }
139
140 #[test]
141 fn audio_mime_per_extension() {
142 let p = params_with(&[("kind", "audio")]);
143 let snap = empty_snapshot();
144 let cases = [
145 ("track.mp3", "audio/mpeg"),
146 ("track.wav", "audio/wav"),
147 ("track.ogg", "audio/ogg"),
148 ("track.flac", "audio/flac"),
149 ("track.m4a", "audio/mp4"),
150 ("track.opus", "audio/opus"),
151 ];
152 for (src, mime) in cases {
153 let out = synthesize_audio_html(&p, src, &snap);
154 let expected = format!(r#"type="{}""#, mime);
155 assert!(out.contains(&expected), "src={}, want {}, got: {}", src, expected, out);
156 }
157 }
158
159 #[test]
160 fn audio_unknown_extension_falls_back_to_octet_stream() {
161 let p = params_with(&[("kind", "audio")]);
162 let out = synthesize_audio_html(&p, "mystery.xyz", &empty_snapshot());
163 assert!(out.contains(r#"type="application/octet-stream""#), "got: {}", out);
164 }
165
166 #[test]
167 fn audio_extension_lookup_ignores_query_and_fragment() {
168 let p = params_with(&[("kind", "audio")]);
169 let out = synthesize_audio_html(&p, "track.mp3?v=2#t=10", &empty_snapshot());
170 assert!(out.contains(r#"type="audio/mpeg""#), "got: {}", out);
172 assert!(out.contains(r#"src="track.mp3?v=2#t=10""#), "got: {}", out);
173 }
174}