        hasNext: player.qi + 1 < player.queue.length || player.repeat > 0,
        hasPrev: true,
    };
    /* the window, clamped at both ends. `at` is recomputed rather than
       assumed to be QUEUE_WINDOW: at the top of the queue the slice starts
       at 0 and the current item is the first entry, not the eleventh. */
    const lo = Math.max(0, player.qi - QUEUE_WINDOW);
    const win = player.qi < 0 ? [] : player.queue.slice(lo, player.qi + QUEUE_WINDOW + 1);
    const at = player.qi - lo;
    state.queue = win.map(windowItem);
    state.queueIndex = at >= 0 && at < win.length ? at : null;