Narration for Posts, for You!

This article has narration:
(Press the 'p' key to toggle playback)

If you’ve clicked on this post itself, you’ll notice there’s a sound file at the top. That’s because if you click it, or hit the P key, you’ll hear… me, reading this! I’m working on adding this to old posts one by one, so you might see that popping up eventually. But, partially for accessibility, and partially just to make these sort of ramblings easier to take in while only half paying attention or just doing something else entirely, I’m putting audio narration on every post here that I can.

Really, there’s a third reason: having to read what I wrote, and then hearing it back over, and over, as I have to clean up the audio, means I’ll hopefully make less boneheaded mistakes, Actually, even the previous article had me catching quite a few weird constructs that needed a rewrite.

Implementation HTML

Somehow, the HTML for this was just a slight bit more complicated than it should have been, just to keep features playing well with each other. All I have to add to the templates is this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="narration-container">
    <table>
        <tr>
            <td>
                This article has narration:<br />
                (Press the 'p' key to toggle playback)
            </td>
            <td>
                <audio controls preload="metadata" id="narration-player">
                    <source src="{{ .ctx.Params.narration }}" />
                    <p>Your browser does not support the HTML5 <code>&lt;audio&gt;</code> element, it seems, so this isn't available.</p>
                </audio>
            </td>
        </tr>
    </table>
</div>

This goes in the header space, technically in the area for the article content, but before the actual inclusion for it. However, this is also where labels appear, I had to make them play nice, and look nice, in any of the four combinations they can produce. That means this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{{ if or (.ctx.Params.chips) (and .ctx.Params.narration (or (eq .template_type "single") (.ctx.Params.noSummary))) }}
<div class="article-extras">
    {{ if .ctx.Params.chips }}
        <div class="chips">
            ...
        </div>
        {{ if and .ctx.Params.narration (or (eq .template_type "single") (.ctx.Params.noSummary)) }}
            <hr />
        {{ end }}
    {{ end }}
    {{ if and .ctx.Params.narration (or (eq .template_type "single") (.ctx.Params.noSummary)) }}
        <div class="narration-container">
            ...
        </div>
    {{ end }}
</div>
{{ end }}

Basically, I’ve shoved both into a <div> named article-extras, and this div is generated if:

  • The article has any labels
  • OR the article has a narration file, AND is either the full page or has no summary.

The “no summary” part is because if I set the noSummary key in the post front-matter, the entire thing is rendered in the timeline, and it’s not split out into a page of its own. I have no reason to do this, but it’s the convention in this theme so I’m following it. The reason for this logical structure is that I want the labels to appear no matter if it’s the full article or just the preview, but I want the narration box to only appear if it’s the full article, otherwise on a list of 10 posts, there’s 10 separate play buttons.

After this, if labels exist, the label section is created, and if narration also exists it generates a separator. After that, the narration box is generated, from the code in the previous listing. There’s a fallback for if your browser cannot render <audio> elements, but as far as I know, just about everything supports it at this point. It’ll also display that message if all sources are unsupported, but I would love to see a browser that can’t play an MP3.1

Implementation CSS

There’s actually not that much.

div.article-extras {
    border-bottom: 3px double $meta-border-color;
    padding-bottom: 2.5%;
    margin-bottom: 4.5%;

    div.narration-container table {
        margin-right: 0px;
        margin-left: 0px;
        margin-bottom: 0px;

        th, td {
            border: 0px;
        }
    }
    
    div.chips {
        ...
    }
}

I have to do some messing about with the margins and borders, because this particular table is really only structural, it’s not meant to be an actual table. The only remaining part of this with any interest is the few lines of JavaScript:

Implementation JS

if (e.keyCode === 80 && !doingSearch) {
    // Toggle narration with P.
    // Note: jQuery doesn't play nice with this at all,
    // just returning a jQuery.fn.init,
    // So this can't be done with it for some reason.
    if (document.getElementById('narration-player')) {
        player = document.getElementById('narration-player')
        if (player.paused) {
            player.play()
        } else {
            player.pause()
        }
    }
}

‘80’ is the key code for the lowercase ‘p’ key, so by pressing ‘p’ you can toggle play / pause, much like you can press ’s’ to pull up the search bar at the top. The doingSearch variable is just to make sure that typing into the search bar prevents toggling playback.


Really, that’s all there is. I just need to add the URL to an audio file in the front-matter, most of which are probably going to be CDN links. As much as 10 minutes of MP3 isn’t the largest of files, it would still add up over time. I’m only paying about 15ยข a month for AWS, so it’s just fine for me to keep putting them there, for now. Intelligent tiering means that most of the content I put in there falls into infrequent access anyways.


  1. Genuinely. If you can’t play an MP3 file for some reason on your browser, send me a message or leave a comment with what you’re using. ↩︎