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:
Track Information
Song title with link to video page
Theme type and sequence (OP1, ED2, etc.)
Anime name with navigation link
Artist performances
Playback Controls
Previous track button
Play/Pause toggle
Next track button
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
Key Action 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
Key Action MToggle mute ↑Volume up 10% ↓Volume down 10%
Navigation
Key Action NNext track BPrevious track
Display Options
Key Action 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
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