decorateCookedElement para cada mudança de página?

Seu código deve funcionar bem. Você acabou de tropeçar em um bug no Discourse.

loadScript() não define o atributo async para os scripts que carrega. Portanto, ele é definido como async="true" por padrão e isso bagunça sua ordem de carregamento. É uma peculiaridade do navegador. Você tem que forçar async="false" para scripts carregados com JS.

Os plugins são menores, então carregam mais rápido que o pacote principal, mas como eles são async, eles não respeitam mais a ordem de carregamento - espere o pacote principal carregar e executar antes de executar.

Provavelmente passou despercebido porque loadScript não está aninhado em nenhum lugar no core, até onde eu sei. Normalmente, você empacotaria arquivos que precisam funcionar juntos. Então, para responder à sua outra pergunta. Não, não há funções do Discourse que lidem com esse tipo de coisa.

Seu outro trecho também deve funcionar. Para torná-lo um pouco mais fácil de ler, talvez tente encadeá-los sem aninhá-los.

// Isso vai fora do decorador
const PLUGINS = {
  speed: "https://example.com/foo.js",
  skipBack: "https://example.com/bar.js",
  jumpForward: "https://example.com/baz.js"
};

// Então faça seu trabalho dentro do decorador
loadScript(MEDIA_ELEMENT_SRC)
  .then(() => loadScript(PLUGINS.speed))
  .then(() => loadScript(PLUGINS.skipBack))
  .then(() => loadScript(PLUGINS.jumpForward))
  .then(() => {
    audioplayers.forEach(function (el) {
      new MediaElementPlayer(el, MEDIA_ELEMENT_CONFIG);
    });
  });