Selective autoplay of audio for certain notes

I have a card type that autoplays several media fields in sequence – some fields have a single audio file ([sound:__.mp3] tag), some multiple.

My goal:
Every now and then, I have a note where I want some of the audio files to autoplay, but for others to be skipped. I want the skipped ones to still show up as play buttons, so I can manually play them if I want to – but it’s fine (or even preferrable) if they look a little different than the standard play button. Needs to work on Desktop and AnkiDroid.

My question:
Is there an easier/“lighter-weight” way to do this than the add-on? If I use the add-on, is there anyway I can get its controls/button closer to the standar play button? I appreciate any advice! :pray:t4:


Tried:
I was hoping to find some sort of native-Anki or HTML-based “mute” or “skip” I could put around the sound tag in the field, and that would “hide” it from autoplay, but had no luck at that.

Potential add-on solution:
The Inline media add-on could work. I can add the to-be-skipped audio as inline media instead of a sound tag, and set it to not autoplay. But there are drawbacks –

  1. It’s more complicated than what I wanted.
  2. I don’t like having primary functionality on my cards that I don’t understand.
  3. I’m beholden to an add-on to continue working if I want to edit this or do it again on another note in the future. [The add-on is already out-dated and having some issues, which I’ll take up in the support thread if I can’t find another solution.]
  4. [The most disappointing thing] It displays a full bar of audio-player controls for each file – which stands out like a sore thumb in my nice orderly row of play buttons. :sob:
    image
The <audio> element added by Inline media add-on --

<audio id="im-media-f194a080-8432-45e8-b296-efa0c0402da0" class="inline-media" src="_im-media-f194a080-8432-45e8-b296-efa0c0402da0.mp3" controls="" oncanplay="if(this.getRootNode().querySelector('anki-editable') === null &amp;&amp; this.offsetParent !== null &amp;&amp; ((this.hasAttribute('auto_front') &amp;&amp; !document.body.classList.contains('back')) || (this.hasAttribute('auto_back') &amp;&amp; document.body.classList.contains('back')))) {this.play();}" oncontextmenu="pycmd(this.id); return true;"></audio>

Potential fix to the add-on solution:
I found a way to tweak the full-bar controls on Stack Overflow – by deleting “controls” from the audio element and adding a button. This part at least I sort of understand! :sweat_smile: But I don’t know how to put anything on the button except text and emoji. So the result is lackluster. On top of that, without “controls” I lose my ability to interact with the fuctionality of the add-on.

The <button> element idea --

<button onclick="document.getElementById('im-media-f194a080-8432-45e8-b296-efa0c0402da0').play()">Play ▶️</button>

html - HTML5 Audio display only play pause and mute buttons - Stack Overflow

image

2 Likes

Perhaps some of the code will be useful.

Hmmm… I’ve seen that post, but I don’t know enough JS to customize something like that. Besides – it sounds like it was that was primarily written by ChatGPT. I wouldn’t trust it in my collection.

You can copy the code for the default Anki audio button and replace the onclick code in it with the one you need to make an audio button of your own:

<a class="replay-button soundLink" href="#" onclick="YOUR ONCLICK CODE HERE">
    <svg class="playImage" viewBox="0 0 64 64" version="1.1">
        <circle cx="32" cy="32" r="29"></circle>
        <path d="M56.502,32.301l-37.502,20.101l0.329,-40.804l37.173,20.703Z"></path>
    </svg>
</a>

I’m not sure, how it will look on AnkiDroid though. Might be a bit tricky to make the same code match the default styles on both platforms at the same.

1 Like

I’ve been looking for something like this myself, but unfortunately, there doesn’t seem to be a good way to interrupt audio from a card code. Unlike images, for example, audio files do not get embedded into card’s HTML, and the playback is handled by Anki in the background.


There are two more workarounds, each with its own limitations, that I know of.

One is to disable all autoplay in deck settings, and use JS to automatically “click” the needed buttons. (you can surround the respective audio references with <autoplay> tag, for example, and modify all selectors adding this tag, e.g. “autoplay > .soundLink”, to target the selected audios).

Actually, disabling autoplay is not even necessary if every card will have at least one <autoplay> button, because activating that button with JS will prevent the rest of the buttons from being played back by Anki. This behavior is exploited in the other method, which uses playing an empty audio file to disrupt the default playback. This will work even when no autoplay files are present on a card, but has a downside of having to keep an extra field referencing the empty file on each note (maybe it can be replaced with some static HTML, I’m not sure).

But the main issue with both methods arises when there are multiple files on a card that need to be auto-played. “Clicking” on all of them at once with JS will only playback the last one because each consecutive click will interrupt the previously started audio, so some delay has to be added between clicks. Since JS has no access to audio files and does not receive any feedback from Anki to know when an initiated playback has finished, the only apparent solution is to add a static delay, which, if the audiofiles have significantly different lengths, will inevitably cut the longer files off, and add redundant pauses after each short audio.

Thank you! This is very promising! Well, actually, in the browser, it’s comically large –


– but on the cards, it looks great! I added a color to differentiate it from the other buttons – with style="fill: gray;" in the <circle> element. I don’t know if that’s the right place, but seems to be working!
image

I’ll see how AnkiDroid feels about it later. :crossed_fingers:t4:

Did you take a look at the <audio> element code I posted above? I’m wondering if I can just keep that handy and paste it (and this new play button) into a note whenever I want to do this. As long as I have the audio as an underscore-file in collection media, it seems like I can use anything for my “onclick” code, as long as I use the same thing in both elements. That could allow me to avoid being dependent on the add-on. Or am I crazy to think that would work?

Do you see anything about that <audio> code that could be simplified?

1 Like

a native svg way of specifying this would be to add it as an attribute to the circle itself:

<circle cx="32" cy="32" r="29" fill="gray">

but the general style attribute works fine as well. Alternatively, you might want to move the style to the card’s styling tab, so that it could be easily modified for all cards if needed. Or move the whole svg part there – this will simplify the html significantly.

Anything moved to the styling tab will stop having an effect on how the element is displayed in the editor, however. But I don’t know if you really want to be able to see the button images inside cards’ fields, or if that was just the most straightforward way to go about it. The route that I would take in this instance is to keep the field contents in plain text, similar to the default Anki sound references (wich would also simplify converting between autoplayed and not autoplayed sounds):

[sound-muted:_muted.mp3]

and then use JS to generate the audio buttons in place of such text patterns.


Most of that code, as far as I can tell, simply lists different conditions on when the audio should be played. Depending on your goals some or all of them can be removed. If all you need from this element is to be a container for audio file to be played back when a custom button is clicked, then the only necessary part is this one:

<audio id="im-media-f194a080-8432-45e8-b296-efa0c0402da0" src="_im-media-f194a080-8432-45e8-b296-efa0c0402da0.mp3"></audio>

(which can also be generated with JS from the text element [sound-muted:_muted.mp3] if you choose to go in that direction).

here is a little demo (click “Run pen” and then “HTML” in the top left corner for the source code):

I’ve taken a look at Anki’s source code and it seems like a simple modification of the “play_clicked_audio” method in the “sound.py” script to accept lists as the last argument alongside plain integers should achieve several sought for pieces of functionality for card scripts:

  1. stopping audio playback (by calling the ‘play’ pycmd with empty list)
  2. filtering out some of the audio files from the autoplay (including with an HTML tag as in your suggestion)
  3. repeating each audio a certain number of times (or looping of them together)

I’m not sure if such changes to the core codebase are welcome, however. Maybe you know more about that?

pycmd() is not public API, so I’m afraid this is not a change I’d like to accept at this time.

2 Likes
This part doesn't really need a fix, but it seems odd.

I tried this (with “gray” and with the hex color I switched to), and the color still shows in the Editor, but in Preview and when studying, there’s no color. That makes me think something in my Styling must be interfering, but I can’t find anything svg, circle, or fill-related anywhere in my templates. Could Anki be overriding the color, because it’s the same code used to create the default buttons?

<circle cx="32" cy="32" r="29" fill="gray"></circle>
<circle cx="32" cy="32" r="29" fill="#557fc9"></circle>

This sounds pretty much ideal. The oversized play buttons in the Editor are a bit of a bother, and being able to simply convert my sound tags to sound-muted, and underscore the audio files will make it a lot simpler to convert a few dozen notes that need this treatment.

I think I’m missing a step though. I copied all of <script>...</script> onto my back template. I added the CSS to Styling. And I changed the field so it now contains 2 muted and 2 regular sound tags. [sound-muted:_im-media-pronunciation_tr_öge.mp3][sound-muted:_im-media-öge-oh0089.mp3][sound:pronunciation_tr_öğe.mp3][sound:709737.mp3]
But the card is either showing the muted tags to me as text (that extra play button is coming from another field), or sometimes is completely blank.
image

Does it matter where I put the JS on my template? I have other JS (that does some hover-button stuff), could that be interfering? Should I put the JS inside the <script> tags that already exist on my template?

Can you run anki from the source code? It should be easier to change things and try them out. For example, I run Anki directly from the source code and patched the sound button from return f"[sound:{html.escape(fname, quote=False)}]" to return f'<audio src="{html.escape(fname, quote=False)}" controlslist="nodownload" controls=""></audio>'.

I think a new button to add media as an HTML audio element should be enough to have specified media not playing out automatically. In my decks, I only use javascript and HTML media, with an addon I wrote: HTML audio/video tags support addon (support thread), it integrates HTML media with anki. Still, it would not work for Android or Anki Web unless I embed the addon javascript in my card template (which I will do if I need to study outside Anki Desktop).

Surprisingly, it’s not an Anki thing. CSS stylesheets are supposed to take precedence over inline svg attributes. I would never have guessed. In that case, using inline/styling tab CSS is indeed the better way to go.

Just to be clear, that one in itself could easily be solved by setting the appropriate max-width as a style for the button.


I tried this code in Anki myself, and it reproduced the blank page issue. Not sure why it does this, as even a trivial replacement document.body.innerHTML = document.body.innerHTML; causes whole page to disappear (despite all elements remaining where they are if inspected with dev tools).
Anyway, I was able to avoid it by wrapping the code in a timeout function:

<script>
setTimeout(()=>{
  const pattern = /\x5Bsound-muted:(.*?)\x5D/g;

  function audioHTML(filename) {
    return `<audio id="muted_audio${filename}"><source src="${filename}"></audio>`;
  }
  function buttonHTML(filename) {
    return `<a class="replay-button soundLink muted" href="#" onclick="document.getElementById('muted_audio${filename}').play()">
      <svg class="playImage" viewBox="0 0 64 64" version="1.1">
          <circle cx="32" cy="32" r="29"></circle>
          <path d="M56.502,32.301l-37.502,20.101l0.329,-40.804l37.173,20.703Z"></path>
      </svg>
  </a>`;
  }

  document.body.innerHTML = document.body.innerHTML.replace(pattern, (match, filename) => {
          return audioHTML(filename) + buttonHTML(filename);
      });
}, 0);
</script>

this will also solve error with a constant being redefined, which would otherwise appear on desktop Anki when flipped on subsequent cards during review. In addition I replaced square brackets in the regex with their unicode representations, just in case they also interfere with LaTeX preprocessor on Ankidroid, and removed the “fill” attribute, replacing it with “muted” class to be used for styling through the styling tab:

.replay-button.muted svg circle,
.soundLink.muted svg circle {
  fill: #557fc9;
}

To clarify some things:

The only important aspect is whether it is placed after the html elements it is supposed to modify, as long as it doesn’t introduce any naming conflicts for global variables with other scripts (now that it is wrapped in setTimeout, all variables in that script are local).

If there is an error in any part of a script, it stops all the following lines inside the same <script> tag from being executed. For this reason independent scripts are better kept in separate tags in order to localize the effect of potential errors and prevent the scripts from breaking each other. In terms of the scope, though, it doesn’t matter as all <script> tags share global definitions.

This one was not necessary, as that CSS was a default Anki style copied to CodePen simply to reproduce the environment. But for the updated version, the CSS presented above is supposed to be placed in the styling tab.

1 Like

I suppose theoretically, anything’s possible! :laughing: I appreciate your faith in me, but that just isn’t going to happen. I try to approach Anki first and foremost as a user, so it’s best for me to keep things as generic and vanilla as possible.

1 Like

Thank you so much for taking the time to do this! I’m very excited to try all of this out in the next few days.

Edit: a few days turned into a few weeks …

1 Like

I finally got around to finishing this – using exactly the script and CSS you gave me here. It’s pretty excellent, but there’s just a couple of last kinks to work out.


#1 Quick flash-of-text before buttons are drawn

I don’t know quite how else to describe this, but when I click “Show Answer” the sound-muted tags flash quickly as text before drawing the buttons. [On both Windows and Android.]

I was able to catch a screenshot going frame-by-frame through the video –

– but I’m attaching the video too, in case what I’m describing doesn’t make sense. The few that don’t have the flash of text are ones that were already viewed a few minutes earlier, so it’s possible they were cached.


#2 Oversized button in AnkiDroid
This one just needs a picture – those are the 2 regular sound tag buttons at the top, and their new giant sound-muted friend! :laughing:

I thought that maybe the CSS in Styling wasn’t applying to android.

.replay-button.muted svg circle,
.soundLink.muted svg circle {
  fill: gray;
}

So I guessed that maybe it needed to have a platform-specific section, and a max-width – but it hasn’t gotten any better. [I got the “58px” based on the “r=29” you had up here, but I don’t even know if that’s right.]

.android .replay-button.muted svg circle,
.android .soundLink.muted svg circle {
  fill: gray;
  max-width: 58px;
}

Any ideas?

By the nature of Java Script, it executes after a web page is loaded. Depending on how fast everything runs you will typically see a glimpse of an unprocessed webpage as well, so, I’m afraid, flashing is not completely avoidable. It can be mediated to some extent though by hiding the whole audio section until JS is done with all the substitutions, for example.


I’ve also noticed at some point that the audio buttons on AnkiDroid and on the desktop use slightly different html, so they probably just have different associated CSS as well. If I remember correctly, AnkiDroid additionally wraps svg tag with a span tag for some obscure reason. Considering that it doesn’t seem to affect audio button appearance on desktop, maybe adding this to the buttonHTML code is the easiest way around the issue:

  function buttonHTML(filename) {
    return `<a class="replay-button soundLink muted" href="#" onclick="document.getElementById('muted_audio${filename}').play()">
      <span>
        <svg class="playImage" viewBox="0 0 64 64" version="1.1">
          <circle cx="32" cy="32" r="29"></circle>
          <path d="M56.502,32.301l-37.502,20.101l0.329,-40.804l37.173,20.703Z"></path>
        </svg>
      </span>
    </a>`;
  }

But I can’t test it right now, and also am not sure if it’s the best option in terms of being future-proof.


On a sidenote, how did you manage to reopen the thread, if you don’t mind me asking?

I’m afraid I think this is something only moderators can do.

Since I can view it as a sign that things are working and not broken – rather than introduce more script complexity, I think I’ll just live with it for now! I’m using this on less than 1% of my cards so far, so I will probably be okay.


That worked, but with the odd addition that it popped the button up a little higher on the line, which just looks weird.
image

I figured out that adding a certain display property fixed it on desktop.

<span style="display:inline-flex;">

Unfortunately, that makes it sit a little lower on the line on Android. I tried every value for display and couldn’t find one that worked for both. But the screen is smaller on Android, so I’m going to let that one be a little off.

I cannot thank you enough for your patience and creative thinking to help me get through this. You really are a rock-star!


Dae’s right, I used my extra powers for that. But if you ever have a good reason to re-open a thread – just let me know! The reasons most of the threads here auto-close are spam, zombies, hijacking, and out of respect for OP. But keeping things organized and tidy benefits everyone! :face_with_hand_over_mouth:

1 Like

Well, since the original audiobuttons are different on the two platforms any static setting (that does not alter appearance of the originals) ought to have differences manifesting themselves on either desktop or android. But if the above works, it can be easily built upon to change the display property based on a platform by using the following CSS instead of the inline styling:

html:not(.android) .replay-button.soundLink.muted span {
    display: inline-flex;
}

I think, it’s probably better to account for platform differences in JS, though. Here is the full code, which inserts the span tag conditionally:

<script>
setTimeout(()=>{
  const isAndroid = document.documentElement.classList.contains("android");
  const pattern = /\x5Bsound-muted:(.*?)\x5D/g;

  function audioHTML(filename) {
    return `<audio id="muted_audio${filename}"><source src="${filename}"></audio>`;
  }
  function buttonHTML(filename) {
    return `<a class="replay-button soundLink muted" href="#" onclick="document.getElementById('muted_audio${filename}').play()">
      ${isAndroid ? '<span>' : ''}
        <svg class="playImage" viewBox="0 0 64 64" version="1.1">
          <circle cx="32" cy="32" r="29"></circle>
          <path d="M56.502,32.301l-37.502,20.101l0.329,-40.804l37.173,20.703Z"></path>
        </svg>
      ${isAndroid ? '</span>' : ''}
    </a>`;
  }

  document.body.innerHTML = document.body.innerHTML.replace(pattern, (match, filename) => {
          return audioHTML(filename) + buttonHTML(filename);
      });
}, 0);
</script>

this ensures that the code for native and inserted audiobuttons is equivalent on either platform, making it immune to potential CSS changes in either of the apps. It also makes the code easily adaptable to HTML changes that apps might introduce in the future.


In terms of script complexity per se, this would only require a single line. The rest is supposed to be done by adding a container element around buttons’ HTML and choosing appropriate styling that would make differences less noticeable.


I wasn’t questioning the auto-closing itself (the reasons for that are apparent enough), I was just wondering how exactly things are organized in this forum. I have a feeling that I might require some help with one of my threads in the future, so thank you, I will keep this in mind!