From 386133743e705c5f258b9b54b71c5b5861b48bd4 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 29 Oct 2025 15:29:38 +0000 Subject: [PATCH] Added: Playaudio endpoint --- proto/anki/frontend.proto | 8 ++++++++ qt/aqt/mediasrv.py | 11 ++++++++++- qt/aqt/sound.py | 12 ++++++++---- ts/routes/reviewer-inner/index.ts | 6 +++++- ts/routes/reviewer/reviewer.ts | 12 +++++++++++- 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/proto/anki/frontend.proto b/proto/anki/frontend.proto index 1d733a369..1f6b23101 100644 --- a/proto/anki/frontend.proto +++ b/proto/anki/frontend.proto @@ -30,6 +30,8 @@ service FrontendService { // Save colour picker's custom colour palette rpc SaveCustomColours(generic.Empty) returns (generic.Empty); + + rpc PlayAudio(PlayAudioRequest) returns (generic.Empty); } service BackendFrontendService {} @@ -43,3 +45,9 @@ message SetSchedulingStatesRequest { string key = 1; scheduler.SchedulingStates states = 2; } + +message PlayAudioRequest { + bool answer_side = 1; + uint32 index = 2; + uint64 cid = 3; +} \ No newline at end of file diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index ca8bea681..c8e145f1d 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -28,9 +28,10 @@ import aqt import aqt.main import aqt.operations from anki import hooks -from anki.cards import Card +from anki.cards import Card, CardId from anki.collection import OpChanges, OpChangesOnly, Progress, SearchNode from anki.decks import UpdateDeckConfigs +from anki.frontend_pb2 import PlayAudioRequest from anki.scheduler.v3 import SchedulingStatesWithContext, SetSchedulingStatesRequest from anki.scheduler_pb2 import NextCardDataResponse from anki.template import ( @@ -45,6 +46,7 @@ from aqt.operations import on_op_finished from aqt.operations.deck import update_deck_configs as update_deck_configs_op from aqt.progress import ProgressUpdate from aqt.qt import * +from aqt.sound import play_clicked_audio_with_index from aqt.theme import ThemeManager from aqt.utils import aqt_data_path, show_warning, tr @@ -683,6 +685,12 @@ def next_card_data() -> bytes: return data.SerializeToString() +def play_audio(): + req = PlayAudioRequest.FromString(request.data) + card = aqt.mw.col.get_card(CardId(req.cid)) + play_clicked_audio_with_index(req.index, req.answer_side, card) + + post_handler_list = [ congrats_info, get_deck_configs_for_update, @@ -700,6 +708,7 @@ post_handler_list = [ deck_options_ready, save_custom_colours, next_card_data, + play_audio, ] diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index f54ebd3e8..c7896b34b 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -925,11 +925,15 @@ def play_clicked_audio(pycmd: str, card: Card) -> None: """eg. if pycmd is 'play:q:0', play the first audio on the question side.""" play, context, str_idx = pycmd.split(":") idx = int(str_idx) - if context == "q": - tags = card.question_av_tags() - else: + play_clicked_audio_with_index(idx, context == "q", card) + + +def play_clicked_audio_with_index(index: int, answer_side: bool, card: Card): + if answer_side: tags = card.answer_av_tags() - av_player.play_tags([tags[idx]]) + else: + tags = card.question_av_tags() + av_player.play_tags([tags[index]]) # Init defaults diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index 091dcf696..b80ccabbc 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -54,6 +54,10 @@ base.href = "/"; document.head.appendChild(base); function pycmd(cmd: string) { - window.parent.postMessage({ type: "pycmd", value: cmd }, "*"); + const match = cmd.match(/play:(q|a):(\d+)/); + if (match) { + const [_, context, index] = match; + window.parent.postMessage({ type: "audio", answerSide: context == "a", index: parseInt(index) }, "*"); + } } globalThis.pycmd = pycmd; diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 6b466663b..a9e0c2b9d 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { CardAnswer, type NextCardDataResponse_NextCardData } from "@generated/anki/scheduler_pb"; -import { nextCardData } from "@generated/backend"; +import { nextCardData, playAudio } from "@generated/backend"; import { derived, get, writable } from "svelte/store"; import type { InnerReviewerRequest } from "../reviewer-inner/reviewerRequest"; @@ -37,6 +37,16 @@ export class ReviewerState { onReady() { this.showQuestion(null); + addEventListener("message", this.onMessage.bind(this)); + } + + onMessage(e: MessageEvent) { + switch (e.data.type) { + case "audio": { + playAudio({ answerSide: e.data.answerSide, index: e.data.index, cid: this.currentCard!.card!.id }); + break; + } + } } public registerIFrame(iframe: HTMLIFrameElement) {