Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 44x 43x 40x 40x 40x 183x 40x 34x 34x 40x 42x 42x 27x 42x 40x 11x 11x 23x 8x 8x 40x 16x 15x 15x 15x 40x 6x 4x 4x 4x 40x 111x 19x 2x 4x | export interface PlayerState {
readonly index: number;
readonly playing: boolean;
/** Playback speed multiplier. 1 plays one step per interval, 2 twice as fast. */
readonly speed: number;
}
export interface Player {
readonly state: PlayerState;
seek(index: number): void;
next(): void;
prev(): void;
play(): void;
pause(): void;
toggle(): void;
/** Sets the playback speed multiplier. Takes effect at once, even mid-playback. */
setSpeed(speed: number): void;
}
/** Moves through `length` steps and reports every change. Has no DOM, so it works anywhere. */
export function createPlayer(
length: number,
onChange: (state: PlayerState) => void,
intervalMs = 800,
): Player {
if (length < 1) throw new RangeError("player needs at least one step");
if (!(intervalMs > 0)) throw new RangeError(`intervalMs ${intervalMs} must be above 0`);
const last = length - 1;
let index = 0;
let speed = 1;
let timer: ReturnType<typeof setInterval> | undefined;
const state = (): PlayerState => ({ index, playing: timer !== undefined, speed });
const stop = () => {
clearInterval(timer);
timer = undefined;
};
const seek = (i: number) => {
index = Math.min(Math.max(i, 0), last);
if (index === last) stop();
// Restart the clock, so the step the reader lands on gets a full interval.
else if (timer !== undefined) restart();
onChange(state());
};
const pause = () => {
stop();
onChange(state());
};
function start() {
timer = setInterval(() => seek(index + 1), intervalMs / speed);
}
function restart() {
stop();
start();
}
const play = () => {
if (timer !== undefined) return;
if (index === last) index = 0;
start();
onChange(state());
};
const setSpeed = (next: number) => {
if (!(next > 0) || !Number.isFinite(next)) throw new RangeError(`speed ${next} must be above 0`);
speed = next;
if (timer !== undefined) restart();
onChange(state());
};
return {
get state() {
return state();
},
seek,
next: () => seek(index + 1),
prev: () => seek(index - 1),
play,
pause,
toggle: () => (timer === undefined ? play() : pause()),
setSpeed,
};
}
|