ankidroid / Anki-Android

AnkiDroid: Anki flashcards on Android. Your secret trick to achieve superhuman information retention.
GNU General Public License v3.0
8.21k stars 2.17k forks source link

[BUG]: Playing audio using Javascript causes the card to not finish loading #13455

Closed Aquafina-water-bottle closed 1 year ago

Aquafina-water-bottle commented 1 year ago

Checked for duplicates?

What are the steps to reproduce this bug?

  1. Unzip the file and import the collection: money.zip
  2. Preview the card with the Front side of "Money with Mathjax".
  3. Reveal the backside.
Card Details: The card contains 4 fields: Front, Back, Audio, and AudioMoney. Front: ``` {{Front}} ``` Back: ``` {{Front}}
{{Back}} {{Audio}} {{AudioMoney}} ```

Expected behaviour

The backside should show the loaded card.

Actual behaviour

The backside is not displayed.

Screenshot: ![image](https://user-images.githubusercontent.com/17107540/226154699-bea8b10b-7ba1-4af9-ae4a-c4911dfd533c.png)

Debug info

AnkiDroid Version = 2.16alpha94-debug

Android Version = 13

Manufacturer = Google

Model = sdk_gphone64_x86_64

Hardware = ranchu

Webview User Agent = Mozilla/5.0 (Linux; Android 13; sdk_gphone64_x86_64 Build/TE1A.220922.021; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.71 Mobile Safari/537.36

ACRA UUID = 2daf5ce1-b594-44bb-b526-3ccb31f1a1e6

New schema = true

Scheduler = std2

Crash Reports Enabled = false

DatabaseV2 Enabled = true

(Optional) Anything else you want to share?

This was tested multiple ways:

Background on what this code is doing in the first place (to prevent the x-y problem):

The main problem I was trying to solve is to have a play button on the front side of the card, without having the audio played automatically. However, I want audio to be automatically played on the backside of the card. There does not seem to be a built-in approach to simply not have an audio file play on any Anki implementation without turning off audio replays on all card sides, so my current approach is to have two audio files: silence.wav and (some sentence audio).wav, and use javascript to immediately play the silence.wav file. This approach only plays silence.wav, and overrides the natural order of how the audio is played (as if the user played silence.wav manually), thus the sentence audio does not play. (A different approach that I believe would work is to have a very very long silence.wav file, i.e. 24 hours long. This would remove the need of using javascript to play the file in order to stop the other file from playing).

Unfortunately, something weird is happening with AnkiDroid. The audio plays as expected, i.e. the card with the front side "Money" has two audio files: the first one plays "Realm of the mad god", and the second one plays "Money x7". The second audio file is correctly played, and skips the first audio file. However, adding mathjax (i.e. the card with "Money with Mathjax") visually reveals the main problem: it seems like the card never "fully loads". The webview on both of these cards displays a persistent blue line at the top, suggesting that some javascript is still running and never finishes. This also suggests that Mathjax itself is not actually related to the problem, but simply a side effect that visually reveals the problem.

Screenshot of blue line on card "Money" ![image](https://user-images.githubusercontent.com/17107540/226155484-a18e5da8-ade4-462b-af22-53af84a8947e.png)

Using Chrome's webview on the "Money with Mathjax" card, the mathjax-needs-to-render CSS class is forever present on the backside of the card, and is not replaced with mathjax-rendered, which is the reason why the card doesn't show in the first place. I believe this suggests that something in the onPageFinished function of AnkiDroid/src/main/assets/scripts/card.js doesn't properly run in the first place.


Workaround

While writing this bug report, I basically rubber-ducked myself into a satisfactory workaround: we can use onShownHook and append a function to this array to play the audio. For example:

(() => {

async function playMoney() {

  let x = document.querySelector("#audiomoney .soundLink, #audiomoney .replaybutton");
  if (x) {
    console.log("going to click money...");
    x.click();
    console.log("clicked on money");
  }

}
if (onShownHook !== undefined) {
 onShownHook.push(playMoney);
}

})();

Due to a) "Javascript functionality is provided without any support or warranty" according to the Anki manual, b) this is a very niche bug that most people probably won't deal with, and c) there exists a satisfactory workaround, I'll be closing this issue despite it not being technically fixed. Hopefully, if anyone comes across the same issue, this can serve as a good reference for them!

Research

extr15 commented 11 months ago

Thank you so much!!! Really appreciate that your code saves my life! I encounter a problem described here, and it is mainly that the call of audio's click not always work. I think it's very difficult for me to solve since I am unfamiliar with js and tried several ways without success. But I see this issue today, and using your code make it works on every cards! Thanks again for your effort, and it really helps!