npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

centplayer

v0.2.0

Published

A streaming-style React video player with content-aware controls, intro/outro skipping, episode navigation, and picture-in-picture.

Readme

centplayer

A streaming-style video player for React. No runtime dependencies beyond React itself.

  • Content-aware controls — anime, series, and movie each get a different set
  • Intro/outro segments with timeline markers and a skip button
  • Click-to-focus surface with double-click seeking, like YouTube
  • Fullscreen, expanded view, and picture-in-picture
  • Display fit modes (fit, expand, stretch, original) and playback speed
  • Auto-hiding controls with a minimal progress indicator
  • Timeline scrubbing, hover preview, buffered range, keyboard seeking
  • Ships ESM, CJS, and TypeScript declarations
  • ~8.6 kB gzipped, stylesheet included

Install

npm install centplayer

React 18 or 19 is required, along with react-dom, both as peer dependencies.

Usage

import { Player } from "centplayer";
import "centplayer/style.css";

export function Watch({ onNextEpisode }: { onNextEpisode: () => void }) {
    return (
        <Player
            src="/episode-1.mp4"
            type="anime"
            title="One Piece"
            description="Pirates sail the Grand Line in search of the One Piece."
            intro={{ start: 82, end: 172 }}
            outro={{ start: 1382, end: 1440 }}
            onNext={onNextEpisode}
        />
    );
}

The stylesheet ships separately so it can be cached and overridden. Import it once in your app entry.

Content types

type selects a feature preset. Pass features to override any part of it.

| type | intro/outro skip | episode navigation | | -------- | ---------------- | ------------------ | | anime | yes | yes | | series | no | yes | | movie | no | no |

It defaults to movie, which is the plain player with no intro or episode controls.

<Player src="/movie.mkv" features={{ episodeNavigation: true }} />

Title, description, and the back button are independent of type and render whenever you pass them.

Props

| prop | type | default | notes | | ------------- | ---------------------------------- | ----------- | ------------------------------------------------ | | src | string | — | Required. Changing it resets the player. | | poster | string | — | Shown before playback starts. | | type | "anime" \| "series" \| "movie" | "movie" | Selects the feature preset. | | title | string | — | Uppercase heading; above the controls, or beside the back button on phones. | | description | string | — | Clamped to two lines; hidden on phones. | | intro | Segment | — | { start, end }. Enables the marker and skip. | | outro | Segment | — | { start, end }. Enables the marker and skip. | | features | Partial<PlayerFeatures> | — | Merged over the preset for type. | | view | "default" \| "expanded" \| "fullscreen" | "default" | See View. | | layout | "both" \| "fullscreen" \| "expand" | "both" | Which layout control shows in the toolbar. | | fit | "contain" \| "cover" \| "fill" \| "none" | "cover" | How the video is fitted. See Display and settings. | | autoPlay | boolean | false | Subject to browser autoplay policy. | | autoAdvance | boolean | true | Calls onNext when playback ends. | | muted | boolean | false | Changing it does not reset playback. | | startAt | number | 0 | Applied once, when the source loads. | | className | string | "" | Appended to the root element. |

Callbacks

| callback | signature | | ------------------------------- | ----------------------------- | | onPlay | () => void | | onPause | () => void | | onEnded | () => void | | onTimeUpdate | (currentTime, duration) => void | | onSeek | (time) => void | | onVolumeChange | (volume) => void | | onSkipIntro / onSkipOutro | () => void | | onPrevious / onNext | () => void | | onBack | () => void |

  • onTimeUpdate is throttled to roughly every 250 ms during playback, and also fires on pause and seek.
  • onVolumeChange fires on mute and unmute too, reporting 0 while muted.
  • onBack falls back to history.back() when you omit it.
  • autoAdvance needs onNext to do anything.

View

view="expanded" switches to the taller expanded layout. It applies immediately, with no gesture required.

view="fullscreen" requests fullscreen. Browsers only grant this from a user gesture, so it succeeds when the player mounts as a result of a click and is silently declined on a cold page load. Rejections are caught and ignored, so nothing throws. Use the fullscreen button when you need a guaranteed path.

Expanded view and fullscreen are mutually exclusive. Entering one leaves the other, so the two layouts never fight over the frame. Entering fullscreen on a phone also asks the browser to lock the screen to landscape.

layout picks which of the two controls the toolbar shows. Leave it at "both" for the default, or set "expand" or "fullscreen" to expose only one button.

<Player src="/movie.mkv" layout="expand" />

view is the source of truth when it changes. Toggling expanded mode by hand still works until view changes again.

Interaction

A single click anywhere on the video toggles the chrome. Tap once to hide every control for a clean, video-only view, then tap again to bring them back. This is the main gesture on phones, where there is no pointer to move.

Playback and seeking live on double click so a stray tap never skips. The left third seeks back 10 seconds, the right third seeks forward 10 seconds, and the middle pauses or plays. Each seek shows a brief on-screen cue on the side you double-clicked.

| key | action | | ------------- | ------------ | | Space / K | play / pause | | ← / → | seek ∓10 s | | ↑ / ↓ | volume ±5 % | | M | mute | | F | fullscreen |

Clicking the volume button slides a slider out to its right, pushing the rest of the control row left to make room. It closes on outside click or Escape.

While playing, the controls fade out after 3 seconds of inactivity and return on pointer movement or the next tap. A 3 px progress bar stays visible behind them. Pausing keeps the controls up. The back button hides and returns with the rest of the chrome. On phones the title sits beside the back button in that top bar; on desktop it stays above the controls.

Display and settings

The gear button opens a settings sheet:

  • Display — how the video is fitted: Fit to screen (letterboxed), Expand to fit (fills the frame and crops), Stretch, or Original. The default is Expand to fit; set it with the fit prop.
  • Speed — playback rate from 0.5× to 2×.

On phones the sheet opens as a bottom sheet so it never runs off a short, portrait player. The description under the controls is hidden on phones, and the controls shrink so the whole row keeps fitting.

Styling

The component ships one stylesheet with plain class names, all prefixed cp-. Colors are hardcoded, so theming means overriding rules:

.cp-played {
    background: #4f8cff;
}

Intro and outro markers are styled separately under .cp-marker, so recolouring the played bar leaves them alone.

box-sizing is scoped to the component, so the surrounding page's reset cannot affect its layout.

Notes

  • Picture-in-picture and fullscreen are browser APIs. The PiP button is hidden when unsupported, and requests are feature-detected with a webkitSetPresentationMode fallback for older Safari.
  • Intro and outro segments are derived from currentTime, so the source must be seekable with a known duration. Markers stay hidden until metadata loads.
  • A single click on the surface only toggles the chrome, so a tap never seeks. Seeking needs a double click, which is treated as one action regardless of how many clicks the browser reports.
  • Expanded view and fullscreen cannot be active at the same time.
  • Rendering on the server is fine; the component only touches the DOM inside effects and event handlers.

Exports

Alongside Player, the package exposes the pieces it is built from:

import { usePlayer } from "centplayer";
import { CONTENT_PRESETS, resolveFeatures } from "centplayer";
import { formatTime, segmentStyle } from "centplayer";
import { surfaceZone, surfaceSeekDelta } from "centplayer";
import type { PlayerController, PlayerLayout, Segment, SurfaceZone, VideoFit } from "centplayer";

usePlayer is the headless hook behind the component and is useful if you want the playback state without the UI.

Development

npm install
npm run dev            # demo app
npm run lint           # eslint, type-aware
npm run typecheck      # tsc over the app and build configs
npm run build          # library, then demo
npm run build:lib      # dist-lib: esm, cjs, css, types

prepublishOnly runs lint, typecheck, and the library build, so a publish cannot ship a broken package.

The demo lives in src/demo and is excluded from the published tarball.

License

MIT