Press "Enter" to skip to content

WFMU Fast-Foward and Rewind Buttons for the Archive Player

Vibed this UserScript when someone was asking for a fast-forward and rewind feature in WFMU’s archive player. I have often wanted something like for myself, too.

<code>// ==UserScript==
// @name         WFMU AccuPlayer ±10s
// @namespace    local
// @version      1.1
// @description  Add 10-second rewind and fast-forward buttons to WFMU AccuPlayer
// @match        *://*.wfmu.org/archiveplayer/*
// @grant        none
// @lastUpdated  2026-03-14
// ==/UserScript==

(() => {
  'use strict';

  const STEP = 10;
  const BTN_CLASS = 'wfmu-skip-button';

  const jump = (player, delta) => {
    if (!player || !Number.isFinite(player.duration)) return;
    player.currentTime = Math.max(0, Math.min(player.currentTime + delta, player.duration));
  };

  const makeButton = (label, title, onClick) => {
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = BTN_CLASS;
    btn.textContent = label;
    btn.title = title;
    btn.addEventListener('click', onClick);
    return btn;
  };

  const isEditable = (el) =>
    el instanceof HTMLInputElement ||
    el instanceof HTMLTextAreaElement ||
    el?.isContentEditable;

  const addStyles = () => {
    if (document.getElementById('wfmu-skip-styles')) return;

    const style = document.createElement('style');
    style.id = 'wfmu-skip-styles';
    style.textContent = `
      .${BTN_CLASS} {
        min-width: 44px;
        height: 44px;
        padding: 0 10px;
        border: 1px solid #000;
        border-radius: 999px;
        background: #000;
        color: #fff;
        cursor: pointer;
        font: 600 14px/1 sans-serif;
      }
    `;
    document.head.append(style);
  };

  const init = () => {
    const player = document.querySelector('#audio-player');
    const transport = document.querySelector('.transport');
    const playButton = document.querySelector('#play-button');

    if (!player || !transport || !playButton) return false;
    if (document.querySelector(`.${BTN_CLASS}`)) return true;

    addStyles();

    playButton.before(
      makeButton('« 10', 'Back 10 seconds', () => jump(player, -STEP))
    );
    playButton.after(
      makeButton('10 »', 'Forward 10 seconds', () => jump(player, STEP))
    );

    Object.assign(transport.style, {
      display: 'flex',
      alignItems: 'center',
      gap: '10px',
    });

    document.addEventListener('keydown', (e) => {
      if (isEditable(e.target)) return;
      if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
        jump(player, e.key === 'ArrowLeft' ? -STEP : STEP);
      }
    });

    return true;
  };

  if (init()) return;

  const observer = new MutationObserver(() => {
    if (init()) observer.disconnect();
  });

  observer.observe(document.documentElement, { childList: true, subtree: true });
})();</code>