Skip to content

Commit d4cdc98

Browse files
committed
Fix audio records in android app
1 parent e98b302 commit d4cdc98

4 files changed

Lines changed: 35 additions & 128 deletions

File tree

crates/wordbase-cli/src/query.rs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
use {
22
anyhow::{Context, Result},
3-
itertools::Itertools,
43
std::time::Instant,
54
tracing::info,
6-
wordbase::{
7-
Engine, Profile, RecordId, RecordKind,
8-
render::{HtmlRender, RenderConfig},
9-
},
5+
wordbase::{Engine, Profile, RecordKind, render::RenderConfig},
106
};
117

128
pub fn deinflect(engine: &Engine, text: &str) {
@@ -33,36 +29,22 @@ pub async fn render(engine: &Engine, profile: &Profile, text: &str) -> Result<()
3329
info!("Fetched records in {:?}", end.duration_since(start));
3430

3531
let start = Instant::now();
36-
let HtmlRender { body, audio_blobs } = engine
37-
.render_html(
32+
let body = engine
33+
.render_html_body(
3834
&records,
3935
&RenderConfig {
4036
s_add_note: "Add Card".into(),
4137
fn_add_note: Some("unimplemented".into()),
42-
fn_audio_blob: "Wordbase.audio_blob".into(),
4338
},
4439
)
4540
.context("failed to render HTML")?;
4641

47-
let audio_blobs = audio_blobs
48-
.into_iter()
49-
.map(|(RecordId(record_id), blob)| format!("{record_id}: '{blob}'"))
50-
.join(",");
51-
52-
let js = format!(
53-
"Wordbase.audio_blob = function(record_id) {{
54-
const audio_blobs = {{ {audio_blobs} }};
55-
return audio_blobs[record_id];
56-
}}"
57-
);
58-
5942
let document = format!(
6043
"
6144
<!doctype html>
6245
<html>
6346
<body>
6447
{body}
65-
<script>{js}</script>
6648
<style>{EXTRA_CSS}</style>
6749
</body>
6850
</html>

crates/wordbase/src/records.html

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -245,34 +245,11 @@
245245
}
246246
</style>
247247
{% endmacro style %}
248-
248+
</html></title></head>
249249
<!-- {# content #} -->
250250

251251
{{ self::style() }}
252252

253-
<script>
254-
class Wordbase {
255-
static audio = {};
256-
257-
static audio_for_record(record_id) {
258-
if (this.audio[record_id]) {
259-
return this.audio[record_id];
260-
} else {
261-
const blob = {{ config.fn_audio_blob }}(record_id);
262-
const audio = new Audio(blob);
263-
this.audio[record_id] = audio;
264-
return audio;
265-
}
266-
}
267-
268-
static play_audio_for_record(record_id) {
269-
const audio = this.audio_for_record(record_id);
270-
audio.currentTime = 0;
271-
audio.play();
272-
}
273-
}
274-
</script>
275-
276253
<div style="display: none">
277254
<svg
278255
id="speakers-symbolic"
@@ -339,11 +316,13 @@
339316
{%- endfor -%}
340317
</span>
341318
{% for audio in pitch.audio %}
319+
<audio src="{{ audio.blob | safe }}"></audio>
320+
342321
<button
343322
class="pill"
344323
style="width: 2em; height: 2em; padding: 4px"
345324
title="{{ self::audio_kind_name(kind=audio.kind) }}"
346-
onclick="Wordbase.play_audio_for_record({{ audio.record_id }})"
325+
onclick="this.previousElementSibling.play()"
347326
>
348327
<svg class="icon">
349328
<use href="#speakers-symbolic"></use>
@@ -357,7 +336,9 @@
357336

358337
{% for source, audio_group in group.audio_no_pitch %} {% for audio in
359338
audio_group %}
360-
<button onclick="Wordbase.play_audio_for_record({{ audio.record_id }})">
339+
<audio src="{{ audio.blob | safe }}"></audio>
340+
341+
<button onclick="this.previousElementSibling.play()">
361342
<svg class="icon">
362343
<use href="#speakers-symbolic"></use>
363344
</svg>

crates/wordbase/src/render.rs

Lines changed: 23 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
use std::collections::HashMap;
2-
31
use anyhow::{Context, Result};
42
use arc_swap::ArcSwap;
53
use data_encoding::BASE64;
64
use foldhash::HashSet;
75
use serde::Serialize;
86
use tera::Tera;
9-
use wordbase_api::{DictionaryId, Record, RecordEntry, RecordId, RecordKind, Term, dict};
7+
use wordbase_api::{DictionaryId, Record, RecordEntry, RecordKind, Term, dict};
108

119
use crate::{Engine, IndexMap, lang};
1210

@@ -27,20 +25,29 @@ impl Renderer {
2725
}
2826

2927
impl Engine {
30-
/// Renders the results of [`Engine::lookup`] to parts of an HTML document,
31-
/// so you can display it to the user in a web view or similar.
28+
/// Renders the results of [`Engine::lookup`] to the `<body>` contents of
29+
/// an HTML document, so you can display it to the user in a web view or
30+
/// similar.
31+
///
32+
/// To get a valid HTML document, you will need to add:
33+
/// - `<!doctype html>`
34+
/// - `<html></html>`
35+
/// - `<body></body>`
36+
///
37+
/// You can also include your own HTML to further customize the output,
38+
/// such as adding your own `<style>` block.
3239
///
3340
/// # Errors
3441
///
3542
/// Errors if the HTML template cannot be rendered by [`tera`]. This should
3643
/// not happen normally, but if you are modifying the template and
3744
/// hot-reloading it, then this may error. It is usually safe to just
3845
/// `expect` this to be [`Ok`].
39-
pub fn render_html(
46+
pub fn render_html_body(
4047
&self,
4148
entries: &[RecordEntry],
4249
config: &RenderConfig,
43-
) -> Result<HtmlRender> {
50+
) -> Result<String> {
4451
let terms = group_terms(entries);
4552

4653
let mut context = tera::Context::new();
@@ -49,57 +56,10 @@ impl Engine {
4956
context.insert("config", config);
5057
let body = self.renderer.tera.load().render("records.html", &context)?;
5158

52-
Ok(HtmlRender {
53-
body,
54-
audio_blobs: render_audio_blobs(entries),
55-
})
59+
Ok(body)
5660
}
5761
}
5862

59-
/// Parts of an HTML document rendered by [`Engine::render_html`].
60-
///
61-
/// This intentionally does not contain a complete HTML document, since you must
62-
/// do some extra platform-specific things to be able to render this in a web
63-
/// view.
64-
///
65-
/// [`HtmlRender::body`] holds the main HTML content of the rendering, but it
66-
/// does not contain some assets such as audio blobs. This is because the
67-
/// base 64 audio blobs may be very large, and platforms such as Android's
68-
/// default web view cannot handle that much data in an HTML document.
69-
///
70-
/// Finally, you must wrap the HTML content in:
71-
/// - `<!doctype html>`
72-
/// - `<html>`
73-
/// - `<body>`
74-
#[derive(Debug, Clone)]
75-
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
76-
pub struct HtmlRender {
77-
/// Main HTML body content of the render.
78-
pub body: String,
79-
pub audio_blobs: HashMap<RecordId, String>,
80-
}
81-
82-
fn render_audio_blobs(entries: &[RecordEntry]) -> HashMap<RecordId, String> {
83-
entries
84-
.iter()
85-
.filter_map(|record| {
86-
if let Record::YomichanAudioForvo(dict::yomichan_audio::Forvo { audio, .. })
87-
| Record::YomichanAudioJpod(dict::yomichan_audio::Jpod { audio })
88-
| Record::YomichanAudioNhk16(dict::yomichan_audio::Nhk16 { audio, .. })
89-
| Record::YomichanAudioShinmeikai8(dict::yomichan_audio::Shinmeikai8 {
90-
audio,
91-
..
92-
}) = &record.record
93-
{
94-
let blob = audio_blob(audio);
95-
Some((record.record_id, blob))
96-
} else {
97-
None
98-
}
99-
})
100-
.collect()
101-
}
102-
10363
#[derive(Debug, Clone, Serialize)]
10464
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
10565
pub struct RenderConfig {
@@ -111,15 +71,6 @@ pub struct RenderConfig {
11171
/// - `headword`: `string?`
11272
/// - `reading`: `string?`
11373
pub fn_add_note: Option<String>,
114-
/// JavaScript function name to return a [`HtmlRender::audio_blobs`] string
115-
/// for a given record ID.
116-
///
117-
/// Arguments:
118-
/// - `record_id`: `number`
119-
///
120-
/// Returns:
121-
/// - `string` - corresponding audio blob from [`HtmlRender::audio_blobs`].
122-
pub fn_audio_blob: String,
12374
}
12475

12576
pub fn group_terms(entries: &[RecordEntry]) -> Vec<RecordTerm> {
@@ -172,22 +123,19 @@ pub fn group_terms(entries: &[RecordEntry]) -> Vec<RecordTerm> {
172123
Record::YomichanAudioForvo(audio) => {
173124
info.audio_no_pitch.entry(source).or_default().push(Audio {
174125
kind: RecordKind::YomichanAudioForvo,
175-
mime_type: audio_mime_type(&audio.audio),
176-
record_id: record.record_id,
126+
blob: audio_blob(&audio.audio),
177127
});
178128
}
179129
Record::YomichanAudioJpod(audio) => {
180130
info.audio_no_pitch.entry(source).or_default().push(Audio {
181131
kind: RecordKind::YomichanAudioJpod,
182-
mime_type: audio_mime_type(&audio.audio),
183-
record_id: record.record_id,
132+
blob: audio_blob(&audio.audio),
184133
});
185134
}
186135
Record::YomichanAudioNhk16(audio) => {
187136
let conv = Audio {
188137
kind: RecordKind::YomichanAudioNhk16,
189-
mime_type: audio_mime_type(&audio.audio),
190-
record_id: record.record_id,
138+
blob: audio_blob(&audio.audio),
191139
};
192140
if audio.pitch_positions.is_empty() {
193141
info.audio_no_pitch.entry(source).or_default().push(conv);
@@ -210,8 +158,7 @@ pub fn group_terms(entries: &[RecordEntry]) -> Vec<RecordTerm> {
210158
Record::YomichanAudioShinmeikai8(audio) => {
211159
let conv = Audio {
212160
kind: RecordKind::YomichanAudioShinmeikai8,
213-
mime_type: audio_mime_type(&audio.audio),
214-
record_id: record.record_id,
161+
blob: audio_blob(&audio.audio),
215162
};
216163
if let Some(pos) = audio.pitch_number {
217164
info.pitches
@@ -285,8 +232,7 @@ pub fn base_pitch<'a>(term: &Term, downstep: dict::jpn::PitchPosition) -> Pitch<
285232
#[derive(Debug, Clone, Serialize)]
286233
pub struct Audio {
287234
pub kind: RecordKind,
288-
pub mime_type: &'static str,
289-
pub record_id: RecordId,
235+
pub blob: String,
290236
}
291237

292238
fn audio_mime_type(audio: &dict::yomichan_audio::Audio) -> &'static str {
@@ -308,12 +254,12 @@ const _: () = {
308254

309255
#[uniffi::export]
310256
impl Wordbase {
311-
pub fn render_html(
257+
pub fn render_html_body(
312258
&self,
313259
entries: &[RecordEntry],
314260
config: &RenderConfig,
315-
) -> FfiResult<HtmlRender> {
316-
Ok(self.0.render_html(entries, config)?)
261+
) -> FfiResult<String> {
262+
Ok(self.0.render_html_body(entries, config)?)
317263
}
318264
}
319265
};

wordbase-android/app/src/main/java/io/github/aecsocket/wordbase/RecordsView.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,15 +227,13 @@ fun RawRecordsView(
227227
}
228228
}
229229

230-
231230
val sAddNote = stringResource(R.string.add_note)
232231
val document by derivedStateOf {
233-
val html = wordbase.renderHtml(
232+
val body = wordbase.renderHtmlBody(
234233
entries = entries,
235234
config = RenderConfig(
236235
sAddNote = sAddNote,
237236
fnAddNote = "WordbaseAndroid.addNote",
238-
fnAudioBlob = "WordbaseAndroid.audioBlob",
239237
),
240238
)
241239

@@ -245,7 +243,7 @@ fun RawRecordsView(
245243
<!doctype html>
246244
<html>
247245
<body>
248-
${html.body}
246+
$body
249247
<style>$extraCss</style>
250248
</body>
251249
</html>

0 commit comments

Comments
 (0)