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

vidflowx

v1.0.6

Published

A fully customizable React video player supporting direct sources, YouTube, Vimeo, Dailymotion, Facebook, TikTok, HLS, and DASH streaming

Readme

VidFlowX

A fully customizable React video player that supports all modern video player features. Play videos from direct sources (MP4, WebM, OGG), streaming formats (HLS, DASH), and social platforms (YouTube, Vimeo, Dailymotion, Facebook, TikTok).

npm version npm downloads bundle size TypeScript React License: MIT

View on NPM | Live Demo | Documentation

Features

  • Multiple Source Support: MP4, WebM, OGG, HLS, DASH, YouTube, Vimeo, Dailymotion, Facebook, TikTok
  • Full Playback Control: Play, pause, seek, volume, playback speed
  • Advanced Features: Picture-in-Picture, fullscreen, captions/subtitles
  • Keyboard Shortcuts: Fully customizable keyboard controls
  • Theming: Light/dark themes with CSS variable customization
  • Chapters & Hotspots: Define timestamp markers with labels
  • Playlist Support: Video queue with auto-next
  • Hover Preview: Thumbnail preview on progress bar hover
  • Plugin System: Extensible architecture for custom functionality
  • Component Overrides: Replace any UI component with your own
  • SSR Compatible: Works with Next.js and Remix
  • Fully Typed: Complete TypeScript support

Installation

npm install vidflowx
# or
yarn add vidflowx
# or
pnpm add vidflowx

Quick Start

import { VideoPlayer } from 'vidflowx';
import 'vidflowx/styles.css';

function App() {
  return (
    <VideoPlayer
      src="https://example.com/video.mp4"
      poster="https://example.com/poster.jpg"
      autoPlay={false}
      controls
    />
  );
}

Usage Examples

Basic Video

<VideoPlayer src="video.mp4" />

YouTube Video

<VideoPlayer src="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />

HLS Stream

<VideoPlayer src="https://example.com/stream.m3u8" />

With Captions

<VideoPlayer
  src="video.mp4"
  captions={[
    { src: '/subtitles/en.vtt', lang: 'en', label: 'English', default: true },
    { src: '/subtitles/es.vtt', lang: 'es', label: 'Spanish' },
  ]}
/>

With Chapters

<VideoPlayer
  src="video.mp4"
  chapters={[
    { startTime: 0, title: 'Introduction' },
    { startTime: 120, title: 'Main Content' },
    { startTime: 360, title: 'Conclusion' },
  ]}
/>

Playlist

VidFlowX has built-in playlist support with a visual playlist panel, auto-advance, and programmatic control. When a playlist is provided, a badge appears on the player showing the current track position (e.g. "1/4"). Clicking the badge opens a slide-out panel where users can browse and select any video.

Basic Playlist

<VideoPlayer
  controls
  playlist={{
    items: [
      { id: '1', src: 'video1.mp4', title: 'First Video', poster: '/posters/1.jpg' },
      { id: '2', src: 'video2.mp4', title: 'Second Video', poster: '/posters/2.jpg' },
      { id: '3', src: 'video3.mp4', title: 'Third Video', poster: '/posters/3.jpg' },
    ],
    autoPlayNext: true,
    loop: true,
  }}
/>

Mixed-Source Playlist (MP4, YouTube, Vimeo, etc.)

Playlist items can mix any supported source type:

<VideoPlayer
  controls
  playlist={{
    items: [
      { id: 'local', src: '/videos/intro.mp4', title: 'Intro' },
      { id: 'yt', src: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', title: 'YouTube Video' },
      { id: 'vimeo', src: 'https://vimeo.com/123456789', title: 'Vimeo Video' },
      { id: 'hls', src: 'https://example.com/stream.m3u8', title: 'Live Stream' },
    ],
    startIndex: 0,
    autoPlayNext: true,
  }}
/>

Controlling the Playlist Programmatically

Use a ref to skip tracks, jump to a specific index, or read the current state.

The VideoPlayerRef type exposes playlist actions: nextTrack(), previousTrack(), and skipToTrack(index).

import { useRef } from 'react';
import { VideoPlayer } from 'vidflowx';
import 'vidflowx/styles.css';

function App() {
  const playerRef = useRef(null);

  const playlist = {
    items: [
      { id: '1', src: 'video1.mp4', title: 'Video 1' },
      { id: '2', src: 'video2.mp4', title: 'Video 2' },
      { id: '3', src: 'video3.mp4', title: 'Video 3' },
    ],
    autoPlayNext: true,
  };

  return (
    <div>
      <VideoPlayer ref={playerRef} controls playlist={playlist} />

      <div>
        <button onClick={() => playerRef.current?.previousTrack()}>
          Previous
        </button>
        <button onClick={() => playerRef.current?.nextTrack()}>
          Next
        </button>
        <button onClick={() => playerRef.current?.skipToTrack(2)}>
          Jump to #3
        </button>
      </div>
    </div>
  );
}

Playlist UI Behavior

  • A badge ("1/4") appears in the top-right corner of the player whenever a playlist has more than one item.
  • Clicking the badge opens a slide-out panel listing all tracks with thumbnails, titles, and a "Now Playing" indicator.
  • Selecting a track from the panel starts playback and closes the panel.
  • When autoPlayNext is true, the next video plays automatically after the current one ends.
  • When loop is true, the playlist restarts from the first video after the last one finishes.

Custom Theme

<VideoPlayer
  src="video.mp4"
  theme={{
    mode: 'dark',
    colors: {
      primary: '#e11d48',
      progressFilled: '#e11d48',
    },
    borderRadius: 12,
  }}
/>

Using the Hook for External Control

import { VideoPlayer, useVideoPlayer } from 'vidflowx';

function App() {
  const { state, actions, ref } = useVideoPlayer();

  return (
    <div>
      <VideoPlayer ref={ref} src="video.mp4" />
      
      <div className="external-controls">
        <button onClick={actions.togglePlay}>
          {state.isPlaying ? 'Pause' : 'Play'}
        </button>
        <span>
          {Math.floor(state.currentTime)}s / {Math.floor(state.duration)}s
        </span>
        <button onClick={actions.toggleMute}>
          {state.isMuted ? 'Unmute' : 'Mute'}
        </button>
      </div>
    </div>
  );
}

Custom Controls

const CustomPlayButton = ({ state, actions }) => (
  <button onClick={actions.togglePlay}>
    {state.isPlaying ? '⏸️' : '▶️'}
  </button>
);

<VideoPlayer
  src="video.mp4"
  components={{
    PlayButton: CustomPlayButton,
  }}
/>

Analytics Callbacks

<VideoPlayer
  src="video.mp4"
  onPlay={(data) => analytics.track('video_play', data)}
  onPause={(data) => analytics.track('video_pause', data)}
  onEnded={(data) => analytics.track('video_complete', data)}
  onSeek={(data) => analytics.track('video_seek', { from: data.fromTime, to: data.toTime })}
  onError={(data) => console.error('Video error:', data.error)}
/>

With Plugin

const analyticsPlugin = {
  name: 'analytics',
  onPlay: (context) => {
    console.log('Video started at', context.state.currentTime);
  },
  onPause: (context) => {
    console.log('Video paused at', context.state.currentTime);
  },
  onEnded: (context) => {
    console.log('Video completed');
  },
};

<VideoPlayer
  src="video.mp4"
  plugins={[analyticsPlugin]}
/>

API Reference

VideoPlayerProps

| Prop | Type | Default | Description | |------|------|---------|-------------| | src | string \| VideoSourceObject | required | Video source URL or source object | | autoPlay | boolean | false | Auto-play when loaded | | loop | boolean | false | Loop the video | | muted | boolean | false | Start muted | | preload | 'auto' \| 'metadata' \| 'none' | 'metadata' | Preload behavior | | poster | string | - | Poster image URL | | volume | number | 1 | Initial volume (0-1) | | playbackRate | number | 1 | Initial playback speed | | startTime | number | 0 | Start time in seconds | | controls | boolean \| ControlsConfig | true | Show controls | | controlsAutoHide | number | 3000 | Auto-hide delay (ms) | | keyboardShortcuts | boolean \| KeyboardShortcuts | true | Enable shortcuts | | seekStep | number | 10 | Seek step in seconds | | playbackSpeeds | number[] | [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] | Available speeds | | captions | CaptionTrack[] | [] | Caption/subtitle tracks | | chapters | Chapter[] | [] | Chapter markers | | playlist | PlaylistConfig | - | Playlist configuration | | theme | ThemeConfig | - | Theme configuration | | width | number \| string | - | Player width | | height | number \| string | - | Player height | | aspectRatio | string | '16:9' | Aspect ratio | | responsive | boolean | true | Enable responsive sizing | | hoverPreview | boolean | false | Enable hover preview | | plugins | Plugin[] | [] | Plugins to load | | components | ComponentOverrides | - | Custom components |

VideoSourceObject

{
  type: 'file' | 'youtube' | 'vimeo' | 'dailymotion' | 'facebook' | 'tiktok' | 'hls' | 'dash';
  url: string;
}

CaptionTrack

{
  src: string;      // URL to .vtt file
  lang: string;     // Language code
  label?: string;   // Display label
  default?: boolean;
}

Chapter

{
  startTime: number;    // Start time in seconds
  endTime?: number;     // End time in seconds
  title: string;        // Chapter title
  description?: string; // Optional description
  thumbnail?: string;   // Optional thumbnail URL
}

PlaylistItem

{
  id: string;                          // Unique identifier
  src: string | VideoSourceObject;     // Video source (URL or source object)
  title?: string;                      // Display title
  poster?: string;                     // Poster/thumbnail image URL
  duration?: number;                   // Duration in seconds (auto-detected if omitted)
  metadata?: Record<string, unknown>;  // Custom metadata
}

PlaylistConfig

{
  items: PlaylistItem[];   // Array of videos in the playlist
  startIndex?: number;     // Index of the initial video to play (default: 0)
  autoPlayNext?: boolean;  // Automatically play the next video when the current one ends
  loop?: boolean;          // Restart from the first video after the last one finishes
}

Keyboard Shortcuts

| Key | Action | |-----|--------| | Space | Toggle play/pause | | | Seek backward | | | Seek forward | | | Volume up | | | Volume down | | M | Toggle mute | | F | Toggle fullscreen | | C | Toggle captions | | P | Toggle PiP | | N | Next track | | B | Previous track | | 0-9 | Seek to percentage |

PlayerState

{
  playbackState: 'idle' | 'loading' | 'playing' | 'paused' | 'buffering' | 'ended' | 'error';
  isPlaying: boolean;
  isPaused: boolean;
  isBuffering: boolean;
  isEnded: boolean;
  isLoading: boolean;
  currentTime: number;
  duration: number;
  buffered: TimeRanges | null;
  bufferedPercent: number;
  playedPercent: number;
  volume: number;
  isMuted: boolean;
  playbackRate: number;
  isFullscreen: boolean;
  isPip: boolean;
  activeCaptionIndex: number;
  source: VideoSource | null;
  sourceType: VideoSourceType | null;
  error: Error | null;
  controlsVisible: boolean;
  quality: string | null;
  availableQualities: string[];
}

PlayerActions

{
  play: () => Promise<void>;
  pause: () => void;
  togglePlay: () => void;
  stop: () => void;
  seek: (time: number) => void;
  seekForward: (seconds?: number) => void;
  seekBackward: (seconds?: number) => void;
  setVolume: (volume: number) => void;
  mute: () => void;
  unmute: () => void;
  toggleMute: () => void;
  setPlaybackRate: (rate: number) => void;
  enterFullscreen: () => Promise<void>;
  exitFullscreen: () => Promise<void>;
  toggleFullscreen: () => Promise<void>;
  enterPip: () => Promise<void>;
  exitPip: () => Promise<void>;
  togglePip: () => Promise<void>;
  setActiveCaption: (index: number) => void;
  setSource: (source: VideoSource) => void;
  setQuality: (quality: string) => void;
  nextTrack: () => void;
  previousTrack: () => void;
  skipToTrack: (index: number) => void;
}

Styling

CSS Variables

You can customize the player appearance using CSS variables:

:root {
  --vidflowx-primary: #3b82f6;
  --vidflowx-secondary: #64748b;
  --vidflowx-controls-background: rgba(0, 0, 0, 0.75);
  --vidflowx-text: #ffffff;
  --vidflowx-text-on-primary: #ffffff;
  --vidflowx-progress-background: rgba(255, 255, 255, 0.3);
  --vidflowx-progress-filled: #3b82f6;
  --vidflowx-progress-buffer: rgba(255, 255, 255, 0.5);
  --vidflowx-tooltip-background: rgba(0, 0, 0, 0.9);
  --vidflowx-tooltip-text: #ffffff;
  --vidflowx-border-radius: 8px;
  --vidflowx-control-bar-height: 48px;
  --vidflowx-icon-size: 24px;
}

Theming via Props

<VideoPlayer
  src="video.mp4"
  theme={{
    mode: 'dark', // 'light' | 'dark' | 'auto'
    colors: {
      primary: '#e11d48',
      controlsBackground: 'rgba(0, 0, 0, 0.8)',
      text: '#ffffff',
      progressFilled: '#e11d48',
    },
    borderRadius: 12,
    controlBarHeight: 56,
    iconSize: 28,
  }}
/>

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)
  • iOS Safari (latest)
  • Android Chrome (latest)

SSR Support

VidFlowX is compatible with server-side rendering. The player handles client-side-only features gracefully:

// Next.js App Router
'use client';

import { VideoPlayer } from 'vidflowx';
import 'vidflowx/styles.css';

export default function VideoComponent() {
  return <VideoPlayer src="video.mp4" />;
}

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

License

MIT © Musawir Khan