Files
v10/packages/react/react

@vjs-10/react

Complete React library for building media players

npm

Status: Early Development

⚠️ PROTOTYPE - SUBJECT TO CHANGE

This package is in early prototype phase. Expect significant changes including:

  • Package restructuring and naming
  • Breaking API changes
  • Major architectural updates
  • Incomplete or experimental features

Not recommended for production use.

Overview

@vjs-10/react is a comprehensive library for building media players in React applications. It provides a complete set of components, hooks, and utilities for creating feature-rich, accessible video and audio players with React.

Key Features

  • Complete Component Library - Full suite of player components
  • React Hooks - Easy state management and control
  • Default Skins - Production-ready player themes
  • HLS/DASH Support - Built-in streaming protocol support
  • Type Safe - Full TypeScript support
  • SSR Ready - Works with Next.js, Remix, Gatsby, etc.
  • Accessible - WCAG compliant with keyboard navigation
  • Customizable - Style with CSS, extend with custom components

Installation

npm install @vjs-10/react

Peer Dependencies:

  • react >=16.8.0

Quick Start

Minimal Player

import { VideoPlayer } from '@vjs-10/react';

function App() {
  return (
    <VideoPlayer
      src="video.mp4"
      poster="poster.jpg"
      controls
    />
  );
}

With HLS Streaming

import { VideoPlayer } from '@vjs-10/react';

function StreamingPlayer() {
  return (
    <VideoPlayer
      src="https://example.com/stream.m3u8"
      controls
      autoPlay
      muted
    />
  );
}

Custom Controls

import {
  CurrentTimeDisplay,
  DurationDisplay,
  FullscreenButton,
  MediaContainer,
  PlayButton,
  TimeSlider,
  Video,
  VolumeSlider,
} from '@vjs-10/react';

function CustomPlayer() {
  return (
    <MediaContainer>
      <Video src="video.mp4" />

      <div className="controls">
        <PlayButton />
        <TimeSlider />
        <CurrentTimeDisplay />
        <DurationDisplay />
        <VolumeSlider />
        <FullscreenButton />
      </div>
    </MediaContainer>
  );
}

Components

VideoPlayer

Complete video player with built-in controls:

import { VideoPlayer } from '@vjs-10/react';

<VideoPlayer
  // Source
  src="video.mp4"
  poster="poster.jpg"

  // Playback
  controls={true}
  autoPlay={false}
  loop={false}
  muted={false}
  playsInline

  // Loading
  preload="metadata"

  // Styling
  className="my-player"
  theme="default" // or "custom-theme"

  // Events
  onPlay={() => console.log('Playing')}
  onPause={() => console.log('Paused')}
  onEnded={() => console.log('Ended')}

  // Configuration
  hlsConfig={{ debug: false }}
/>;

MediaContainer

Container that provides shared state to child components:

import { MediaContainer } from '@vjs-10/react';

<MediaContainer>
  <Video src="video.mp4" />
  {/* All children share the same media store */}
  <CustomControls />
  <CustomOverlay />
</MediaContainer>;

Video

Enhanced video element:

import { Video } from '@vjs-10/react';

<Video
  src="video.mp4"
  poster="poster.jpg"
  controls
  className="video-element"
/>;

Control Components

Individual control components:

import {
  PlayButton,
  MuteButton,
  FullscreenButton,
  TimeSlider,
  VolumeSlider,
  CurrentTimeDisplay,
  DurationDisplay,
  PreviewTimeDisplay,
} from '@vjs-10/react';

function ControlComponents() {
  return (
    <>
      {/* Playback controls */}
      <PlayButton />        {/* Play/pause toggle */}

      {/* Time controls */}
      <TimeSlider />        {/* Seek bar */}
      <CurrentTimeDisplay /> {/* Current time */}
      <DurationDisplay />   {/* Total duration */}
      <PreviewTimeDisplay /> {/* Time on hover */}

      {/* Volume controls */}
      <MuteButton />        {/* Mute toggle */}
      <VolumeSlider />      {/* Volume control */}

      {/* Screen controls */}
      <FullscreenButton />  {/* Fullscreen toggle */}
    </>
  );
}

UI Components

Utility components for enhanced UX:

import { Tooltip, Popover } from '@vjs-10/react';

function UIComponentsExample() {
  return (
    <>
      {/* Tooltip */}
      <Tooltip content="Play video">
        <PlayButton />
      </Tooltip>

      {/* Popover */}
      <Popover
        trigger={<button>Settings</button>}
        content={<SettingsMenu />}
      />
    </>
  );
}

Hooks

useMediaStore

Access the media store:

import { useMediaStore } from '@vjs-10/react';

function CustomControl() {
  const store = useMediaStore();
  const paused = store.paused.get();

  return (
    <button onClick={() => store.paused.set(!paused)}>
      {paused ? 'Play' : 'Pause'}
    </button>
  );
}

useMediaState

Subscribe to specific state values:

import { useMediaState } from '@vjs-10/react';

function TimeInfo() {
  const currentTime = useMediaState(store => store.currentTime);
  const duration = useMediaState(store => store.duration);

  return (
    <div>
      {currentTime}
      {' '}
      /
      {' '}
      {duration}
    </div>
  );
}

useMediaContainerRef

Access the media container reference:

import { useMediaContainerRef } from '@vjs-10/react';

function CustomOverlay() {
  const containerRef = useMediaContainerRef();

  // Access container element for measurements, fullscreen, etc.
  return <div>{/* ... */}</div>;
}

Skins

Default Skin

import { VideoPlayer } from '@vjs-10/react';
import '@vjs-10/react/themes/default.css';

function App() {
  return (
    <VideoPlayer
      src="video.mp4"
      theme="default"
      controls
    />
  );
}

Custom Skin

Create your own player theme:

import { MediaContainer, Video } from '@vjs-10/react';
import './custom-skin.css';

function CustomStyledPlayer() {
  return (
    <MediaContainer className="custom-player">
      <Video src="video.mp4" />
      <CustomControlBar />
    </MediaContainer>
  );
}

Customization

CSS Custom Properties

:root {
  /* Colors */
  --vjs-primary-color: #007bff;
  --vjs-text-color: #ffffff;
  --vjs-bg-color: rgba(0, 0, 0, 0.7);

  /* Sizing */
  --vjs-control-size: 44px;
  --vjs-control-spacing: 8px;

  /* Typography */
  --vjs-font-family: system-ui, sans-serif;
  --vjs-font-size: 14px;
}

Styled Components

import { PlayButton as BasePlayButton } from '@vjs-10/react';
import styled from 'styled-components';

const PlayButton = styled(BasePlayButton)`
  background: #007bff;
  border-radius: 50%;
  padding: 16px;
  transition: transform 0.2s;

  &:hover {
    transform: scale(1.1);
  }
`;

Tailwind CSS

import { PlayButton, TimeSlider } from '@vjs-10/react';

function TailwindControls() {
  return (
    <div className="flex items-center gap-4 p-4 bg-black/70">
      <PlayButton className="w-12 h-12 rounded-full bg-blue-500 hover:bg-blue-600" />
      <TimeSlider className="flex-1 h-2 bg-gray-600 rounded" />
    </div>
  );
}

Advanced Usage

Programmatic Control

import { VideoPlayer } from '@vjs-10/react';
import { useRef } from 'react';

function ControlledPlayer() {
  const playerRef = useRef<HTMLVideoElement>(null);

  const handleSeek = () => {
    if (playerRef.current) {
      playerRef.current.currentTime = 30;
    }
  };

  return (
    <div>
      <VideoPlayer ref={playerRef} src="video.mp4" />
      <button onClick={handleSeek}>Skip to 30s</button>
    </div>
  );
}

Custom Components

import { useMediaStore, useMediaValue } from '@vjs-10/react';
import { PauseIcon, PlayIcon } from '@vjs-10/react-icons';

function CustomPlayButton() {
  const store = useMediaStore();
  const paused = useMediaValue(store.paused);

  return (
    <button
      onClick={() => store.paused.set(!paused)}
      className="custom-play-button"
    >
      {paused ? <PlayIcon /> : <PauseIcon />}
      <span>{paused ? 'Play' : 'Pause'}</span>
    </button>
  );
}

Playlist

import { VideoPlayer } from '@vjs-10/react';
import { useState } from 'react';

function PlaylistPlayer() {
  const playlist = [
    { src: 'video1.mp4', title: 'Video 1' },
    { src: 'video2.mp4', title: 'Video 2' },
    { src: 'video3.mp4', title: 'Video 3' },
  ];

  const [currentIndex, setCurrentIndex] = useState(0);

  const handleEnded = () => {
    setCurrentIndex(i => (i + 1) % playlist.length);
  };

  return (
    <div>
      <VideoPlayer
        src={playlist[currentIndex].src}
        onEnded={handleEnded}
        controls
      />

      <ul className="playlist">
        {playlist.map((item, index) => (
          <li
            key={index}
            onClick={() => setCurrentIndex(index)}
            className={index === currentIndex ? 'active' : ''}
          >
            {item.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

Picture-in-Picture

import { VideoPlayer } from '@vjs-10/react';
import { useRef, useState } from 'react';

function PiPPlayer() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [isPiP, setIsPiP] = useState(false);

  const togglePiP = async () => {
    if (!videoRef.current) return;

    if (isPiP) {
      await document.exitPictureInPicture();
    } else {
      await videoRef.current.requestPictureInPicture();
    }
    setIsPiP(!isPiP);
  };

  return (
    <div>
      <VideoPlayer ref={videoRef} src="video.mp4" controls />
      <button onClick={togglePiP}>
        {isPiP ? 'Exit' : 'Enter'}
        {' '}
        Picture-in-Picture
      </button>
    </div>
  );
}

Analytics Integration

import { VideoPlayer } from '@vjs-10/react';

function AnalyticsPlayer() {
  const handlePlay = () => {
    analytics.track('Video Play', { videoId: 'abc123' });
  };

  const handleProgress = (e: React.SyntheticEvent<HTMLVideoElement>) => {
    const progress = (e.currentTarget.currentTime / e.currentTarget.duration) * 100;

    if (progress >= 25 && !tracked25) {
      analytics.track('Video 25%', { videoId: 'abc123' });
    }
  };

  return (
    <VideoPlayer
      src="video.mp4"
      onPlay={handlePlay}
      onTimeUpdate={handleProgress}
      onEnded={() => analytics.track('Video Complete', { videoId: 'abc123' })}
      controls
    />
  );
}

Server-Side Rendering

Works with Next.js, Remix, and other SSR frameworks:

Next.js App Router

// app/page.tsx
import { VideoPlayer } from '@vjs-10/react';

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

Next.js Pages Router

// pages/index.tsx
import { VideoPlayer } from '@vjs-10/react';

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

Dynamic Import (if needed)

import dynamic from 'next/dynamic';

const VideoPlayer = dynamic(
  () => import('@vjs-10/react').then(mod => mod.VideoPlayer),
  { ssr: false }
);

TypeScript Support

Full TypeScript definitions:

import type {
  MediaContainerProps,
  PlayButtonProps,
  TimeSliderProps,
  VideoPlayerProps,
} from '@vjs-10/react';

const props: VideoPlayerProps = {
  src: 'video.mp4',
  controls: true,
  autoPlay: false,
};

Accessibility

All components include:

  • ARIA attributes - Screen reader support
  • Keyboard navigation - Complete keyboard control
  • Focus management - Visible focus indicators
  • Semantic markup - Proper HTML structure

Keyboard Shortcuts

  • Space - Play/pause
  • ←/→ - Seek backward/forward
  • ↑/↓ - Volume up/down
  • M - Mute
  • F - Fullscreen

Package Dependencies

  • Dependencies:
    • @vjs-10/core - Core components
    • @vjs-10/media - Media utilities
    • @vjs-10/media-store - State management
    • @vjs-10/react-icons - Icon components
    • @vjs-10/react-media-elements - Media elements
    • @vjs-10/react-media-store - State hooks
    • @floating-ui/react - Tooltip/popover positioning
  • Peer Dependencies:
    • react >=16.8.0

Development

# Build the package
pnpm build

# Watch mode for development
pnpm dev

# Run tests
pnpm test

# Clean build artifacts
pnpm clean

Examples

See the React demo for complete examples:

# Run the demo
pnpm dev:react

License

Apache-2.0