The Butterfly release notes for the jump from 5.6.1 to 5.7.0 state one thing plainly: built-in APlayer/Meting support has been removed. After upgrading, the aplayerInject config stopped working, and the related entries in layout/includes/third-party/aplayer.pug and plugins.yml vanished along with it.

/music/ holds 188 <meting-js>, and inject also contains one fixed player. The tags themselves are still there, but the script injection is gone, so the page just shows a row of empty custom elements.

At this point the story should be “the upgrade broke the players, so roll back or hand-write the inject.” But I measured the baseline first, and the result was a little surprising.

Before the upgrade, it was already zero

meting-js defaults to MetingJS’s built-in backend api.i-meto.com. That service is already dead: its responses carry no CORS headers, so every browser request fails. In other words, not a single player on the live site had been rendering for a while — nobody had just bothered to count.

The theme upgrade isn’t the culprit; it merely pried up a floorboard that had rotted long ago. That distinction matters: roll back the version and the page returns to a state where “the player code looks like it’s there, but the count is still zero,” and you keep assuming it’s fine.

Hand-writing the inject: order is a hard constraint

The fix is to include them yourself, in order, inside inject:

<link rel="stylesheet" href="https://registry.npmmirror.com/aplayer/latest/files/dist/APlayer.min.css" media="print" onload="this.media='all'">
...
<script src="https://registry.npmmirror.com/aplayer/latest/files/dist/APlayer.min.js"></script>
<script>window.meting_api="https://meting-api.saop.cc/api?server=:server&type=:type&id=:id&r=:r"</script>
<script src="https://registry.npmmirror.com/meting/latest/files/dist/Meting.min.js"></script>

window.meting_api must be set before Meting.min.js; one line later and it has no effect. For CSS, use media="print" + onload to switch to all, which doesn’t block first paint.

The pjax glue script was actually dead code

The theme also used to have a pjax hook that destroyed and rebuilt destroyAplayer on page switches. Looking into APlayer 1.10.1, window.aplayers is no longer maintained, so that destroyAplayer had nothing to destroy in the first place.

MetingJS v2, meanwhile, is a standard custom element, so on page switches it rebuilds and destroys itself via connectedCallback / disconnectedCallback. So the conclusion this time is no glue: let the element own its own lifecycle.

After fixing it, count

Measured after the change:

  • players rendered: 0 → 167/188;
  • after a pjax round trip, every in-page player is rebuilt;
  • the fixed player stays the same DOM node throughout, with playback uninterrupted.

Of the remaining 21, twenty are type="album" — the self-hosted Meting API doesn’t support that type yet and returns 400. The other one or two are intermittent failures caused by rate limiting under 188 concurrent requests. Both are on the API server side and unrelated to this repo; the commit message says as much, and I didn’t count them as “fixed.”

167/188 isn’t a perfect score, but it’s a number you can re-check: render count, pjax round trip, and fixed-node identity each have their own observation point.

When upgrading, separate the two accounts first

The most expensive part of a dependency upgrade isn’t editing config; it’s being able to say clearly, before the upgrade, what things actually look like right now. This time the baseline was a single sentence — zero — and it decided every judgment that followed:

  • if the zero had been caused by the upgrade, I should suspect the 5.7.0 changes, or even roll back;
  • if the zero was an old debt and the upgrade merely made it visible, I should fix the backend and the injection order, not roll back.

Along the way I also cleared out a piece of dead code. Had I been lazy and copied destroyAplayer verbatim into the new inject, it would have “worked” too — it would just do something useless against an empty array on every page switch, and the next person would have even less chance of seeing why it exists.

Measure what things looked like before, then decide what the upgrade broke.