From 8da0491ae5accb5bd7e1fbbf5aa9fc5495d106ce Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 08:28:39 +0000 Subject: [PATCH] Refactor PlayAudio --- proto/anki/frontend.proto | 8 +++----- proto/anki/scheduler.proto | 7 +++++-- qt/aqt/mediasrv.py | 22 ++++++++++++---------- rslib/src/scheduler/service/mod.rs | 4 +++- ts/routes/reviewer/reviewer.ts | 11 ++++------- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/proto/anki/frontend.proto b/proto/anki/frontend.proto index 03a9ffec6..bd4c3d34b 100644 --- a/proto/anki/frontend.proto +++ b/proto/anki/frontend.proto @@ -10,6 +10,7 @@ package anki.frontend; import "anki/scheduler.proto"; import "anki/generic.proto"; import "anki/search.proto"; +import "anki/card_rendering.proto"; service FrontendService { // Returns values from the reviewer @@ -31,8 +32,7 @@ service FrontendService { // Save colour picker's custom colour palette rpc SaveCustomColours(generic.Empty) returns (generic.Empty); - // Plays an audio tag at an index in a specific card - // If the index is blank, plays all audio on that side + // Plays the listed audio tags rpc PlayAudio(PlayAudioRequest) returns (generic.Empty); } @@ -49,7 +49,5 @@ message SetSchedulingStatesRequest { } message PlayAudioRequest { - bool answer_side = 1; - optional uint32 index = 2; - uint64 cid = 3; + repeated card_rendering.AVTag tags = 1; } \ No newline at end of file diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index e9ee3b89f..14f5b184d 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -306,13 +306,16 @@ message NextCardDataResponse { string css = 5; string body_class = 6; + repeated card_rendering.AVTag question_av_tags = 7; + repeated card_rendering.AVTag answer_av_tags = 8; + // TODO: We can probably make this a little faster by using oneof and // preventing the partial_front and back being sent to svelte where it isn't // used. Alternatively we can use a completely different message for both // Rust -> Python and the Python -> Svelte though this would be more // complicated to implement. - repeated card_rendering.RenderedTemplateNode partial_front = 7; - repeated card_rendering.RenderedTemplateNode partial_back = 8; + repeated card_rendering.RenderedTemplateNode partial_front = 9; + repeated card_rendering.RenderedTemplateNode partial_back = 10; } optional NextCardData next_card = 1; diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 2ed59ffd5..f3f5d716f 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -28,7 +28,7 @@ import aqt import aqt.main import aqt.operations from anki import hooks -from anki.cards import Card, CardId +from anki.cards import Card from anki.collection import OpChanges, OpChangesOnly, Progress, SearchNode from anki.decks import UpdateDeckConfigs from anki.frontend_pb2 import PlayAudioRequest @@ -38,6 +38,7 @@ from anki.template import ( PartiallyRenderedCard, TemplateRenderContext, apply_custom_filters, + av_tags_to_native, ) from anki.utils import dev_mode from aqt.changenotetype import ChangeNotetypeDialog @@ -670,8 +671,15 @@ def next_card_data() -> bytes: qside, ) - qside = ctx.col()._backend.extract_av_tags(text=qside, question_side=True).text - aside = ctx.col()._backend.extract_av_tags(text=aside, question_side=False).text + q_avtags = ctx.col()._backend.extract_av_tags(text=qside, question_side=True) + a_avtags = ctx.col()._backend.extract_av_tags(text=aside, question_side=False) + + # Assumes the av tags are empty in the original response + data.next_card.question_av_tags.extend(q_avtags.av_tags) + data.next_card.answer_av_tags.extend(a_avtags.av_tags) + + qside = q_avtags.text + aside = a_avtags.text qside = aqt.mw.prepare_card_text_for_display(qside) aside = aqt.mw.prepare_card_text_for_display(aside) @@ -687,13 +695,7 @@ def next_card_data() -> bytes: def play_audio(): req = PlayAudioRequest.FromString(request.data) - card = aqt.mw.col.get_card(CardId(req.cid)) - # TODO: Pass tags with next_card_data rather than rendering the card here. - tags = card.answer_av_tags() if req.answer_side else card.question_av_tags() - if req.HasField("index"): - play_tags([tags[req.index]]) - else: - play_tags(tags) + play_tags(av_tags_to_native(req.tags)) post_handler_list = [ diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index d9cd5a009..a49f31d4f 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -413,11 +413,13 @@ impl crate::services::SchedulerService for Collection { next_card: Some(NextCardData { queue: Some(queue.into()), - css: render.css, + css: render.css.clone(), // Filled by python front: "".to_string(), back: "".to_string(), body_class: "".to_string(), + question_av_tags: vec![], + answer_av_tags: vec![], partial_front: rendered_nodes_to_proto(render.qnodes), partial_back: rendered_nodes_to_proto(render.anodes), diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index e69a786a1..0e1c8ec51 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -42,14 +42,11 @@ export class ReviewerState { addEventListener("message", this.onMessage.bind(this)); } - playAudio(answerSide: boolean, index?: number) { - playAudio({ answerSide, index, cid: this.currentCard!.card!.id }); - } - onMessage(e: MessageEvent) { switch (e.data.type) { case "audio": { - this.playAudio(e.data.answerSide, e.data.index); + const tags = get(this.answerShown) ? this._cardData!.answerAvTags : this._cardData!.questionAvTags; + playAudio({ tags: [tags[e.data.index]] }); break; } } @@ -113,7 +110,7 @@ export class ReviewerState { const question = resp.nextCard?.front || ""; this.updateHtml(question, resp?.nextCard?.css, resp?.nextCard?.bodyClass); - this.playAudio(false) + playAudio({ tags: this._cardData!.questionAvTags }); this.beginAnsweringMs = Date.now(); } @@ -124,7 +121,7 @@ export class ReviewerState { public showAnswer() { this.answerShown.set(true); - this.playAudio(true) + playAudio({ tags: this._cardData!.answerAvTags }); this.updateHtml(this._cardData?.back || ""); }