Skip to main content
AnimeThemes Web features a custom-built video player with extensive controls, keyboard shortcuts, and support for both video and audio-only playback modes.

Player Features

The VideoPlayer component provides:
  • Auto-play: Automatic playback on load
  • Progress tracking: Visual progress bar with buffering indicator
  • Volume control: Adjustable volume with mute toggle
  • Playlist integration: Play next/previous tracks
  • Audio mode: Switch between video and audio-only
  • Picture-in-Picture: Detach player to floating window
  • Fullscreen: Immersive viewing experience
  • Keyboard shortcuts: Extensive hotkey support

Player Controls

The player bar displays at the bottom with:
1

Track Information

  • Song title with link to video page
  • Theme type and sequence (OP1, ED2, etc.)
  • Anime name with navigation link
  • Artist performances
2

Playback Controls

  • Previous track button
  • Play/Pause toggle
  • Next track button
3

Action Buttons

  • Volume slider
  • Add to Playlist
  • Share menu
  • Close player (when in background mode)

Progress Bar

The progress bar shows:
  • Current playback position
  • Buffered content (lighter background)
  • Click to seek to position

Keyboard Shortcuts

The player supports extensive keyboard controls:
Keyboard shortcuts are disabled when typing in input fields

Playback Control

KeyAction
Space or KPlay/Pause
Seek forward 5 seconds
Seek backward 5 seconds
LSeek forward 10 seconds
JSeek backward 10 seconds
.Next frame (when paused)
,Previous frame (when paused)

Volume Control

KeyAction
MToggle mute
Volume up 10%
Volume down 10%
KeyAction
NNext track
BPrevious track

Display Options

KeyAction
FToggle fullscreen
PToggle Picture-in-Picture
AToggle audio mode
DDownload current video/audio

Implementation Details

The VideoPlayer component is built with React and manages state for:
// From VideoPlayer.tsx
export function VideoPlayer({ watchListItem, background }) {
  const { video, entry } = watchListItem;
  const theme = entry.theme;
  const anime = theme.anime;
  
  const videoUrl = `${VIDEO_URL}/${video.basename}`;
  const audioUrl = `${AUDIO_URL}/${video.audio.basename}`;
  
  const [isPlaying, setPlaying] = useState(false);
  const playerRef = useRef<HTMLVideoElement | HTMLAudioElement>(null);
  
  // Handle play/pause
  const togglePlay = useCallback(() => {
    if (isPlaying) {
      playerRef.current?.pause();
    } else {
      playerRef.current?.play();
    }
  }, [isPlaying]);
  
  return (
    <VideoPlayerContext.Provider value={...}>
      {/* Player UI */}
    </VideoPlayerContext.Provider>
  );
}

Auto-play Next Track

The player automatically advances when:
  • Current video ends
  • Auto-play is enabled (global or local setting)
  • Next track exists in watch list
const autoPlayNextTrack = useCallback(() => {
  if (
    (isWatchListUsingLocalAutoPlay && isLocalAutoPlay) ||
    (!isWatchListUsingLocalAutoPlay && isGlobalAutoPlay)
  ) {
    playNextTrack(!background);
  }
}, [background, isGlobalAutoPlay, isLocalAutoPlay, playNextTrack]);

Audio Mode

Switch between video and audio playback:

Video Mode

Full video playback with visual content

Audio Mode

Audio-only with album art cover display
In audio mode:
  • Displays anime cover art
  • Plays audio track only
  • Reduces bandwidth usage
  • Current time is preserved when switching modes
const updateAudioMode = useCallback((audioMode: string) => {
  // Preserve playback position
  currentTimeBeforeModeSwitch.current = playerRef.current?.currentTime ?? null;
  setAudioMode(audioMode);
}, [setAudioMode]);

Progress Tracking

The player updates progress without re-rendering:
function updateProgress(event: SyntheticEvent<HTMLVideoElement | HTMLAudioElement>) {
  const duration = event.currentTarget.duration;
  
  if (progressRef.current) {
    // Update progress bar using ref to prevent re-rendering
    const progress = (event.currentTarget.currentTime / duration) * 100;
    progressRef.current.style.width = `${progress}%`;
  }
  
  if (bufferedRef.current) {
    const buffered = event.currentTarget.buffered;
    if (buffered.length > 0) {
      const bufferedEnd = buffered.end(buffered.length - 1);
      bufferedRef.current.style.width = `${(bufferedEnd / duration) * 100}%`;
    }
  }
}
Progress bar updates use refs instead of state to avoid expensive re-renders

Media Session API

Integration with browser media controls:
if (theme && smallCover && navigator.mediaSession) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: `${theme.type + (theme.sequence || "")}${theme.song?.title || "T.B.A."}`,
    artist: theme.song?.performances
      ? theme.song.performances.map(p => p.as || p.artist.name).join(", ")
      : undefined,
    album: anime.name,
    artwork: [{ src: smallCover, sizes: "512x512", type: "image/jpeg" }],
  });
  
  navigator.mediaSession.setActionHandler("previoustrack", () => {
    playPreviousTrack(true);
  });
  navigator.mediaSession.setActionHandler("nexttrack", () => {
    playNextTrack(true);
  });
}

Background Player

When navigating away from the video page:
  • Player minimizes to corner
  • Becomes draggable
  • Double-click returns to video page
  • Can be dismissed with close button
The mini player uses Framer Motion for smooth drag animations

Volume Persistence

Volume settings are persisted:
  • Global volume level saved to settings
  • Mute state remembered
  • Applied on player mount
const [globalVolume, setGlobalVolume] = useSetting(GlobalVolume);
const [muted, setMuted] = useSetting(Muted);

useEffect(() => {
  if (playerRef.current) {
    playerRef.current.muted = muted;
    playerRef.current.volume = globalVolume;
  }
}, [globalVolume, muted]);

Frame-by-Frame Navigation

For precise control:
  • Calculate video frame rate automatically
  • Step forward/backward by exact frames
  • Works when video is paused
Use , and . keys to navigate frame-by-frame when paused

Build docs developers (and LLMs) love