Files
player/DOCUMENTATION.md

22 KiB

@source/player Documentation

This document reflects the current codebase in this repository (version 3.2.0) and replaces older, drifted documentation.

Table of contents

  1. Overview
  2. Architecture
  3. Installation and setup
  4. Quick usage
  5. Streaming and protocol handling
  6. Subtitles and subtitle style editor
  7. Customization and modular composition
  8. Keyboard and touch interaction
  9. Internationalization
  10. API reference
  11. Public exports
  12. Error handling and reliability
  13. Browser behavior and known limitations
  14. Testing and development

1. Overview

@source/player is a React media player library with:

  • Protocol-aware playback (native, hls, rtmp/flv, mpegts)
  • Animated image playback (.gif, .webp, .apng, .avif) through a minimal render path
  • Dedicated audio playback component for music/podcast style use-cases
  • Built-in controls with settings menus (speed, quality, subtitles, subtitle styling, audio tracks)
  • Modular extension points for custom controls and overlays
  • Strong TypeScript API (props, handle types, context types, utility exports)
  • Runtime loading strategy for optional stream engines (hls.js, flv.js, mpegts.js)

2. Architecture

The player is split into focused modules.

2.1 High-level component graph

  • VideoPlayer
  • AudioPlayer
  • PlayerErrorBoundary
  • PlayerProvider (context + state + actions + i18n)
  • VideoElement (media element, protocol setup, media events, subtitle rendering)
  • mediaSource utils (media kind detection for video/audio/animated image)
  • ControlsLayer (controls visibility, settings menu, keyboard/touch integration)
  • SettingsMenu (lazy-loaded, menu subviews)

2.2 Core responsibilities

  • VideoPlayer

  • Normalizes props and default values

  • Resolves subtitle style editor config (enabled + storageKey)

  • Wraps content with PlayerErrorBoundary and PlayerProvider

  • Exposes imperative API via ref (VideoPlayerHandle)

  • AudioPlayer

  • Uses native HTMLAudioElement with a custom control surface

  • Supports keyboard shortcuts, playback rate cycling, and metadata/artwork UI

  • Exposes imperative API via ref (AudioPlayerHandle)

  • PlayerProvider

  • Owns central videoState, uiState, and settings

  • Manages subtitle style draft/commit/persist lifecycle

  • Merges built-in translations with custom translations

  • Exposes playback and UI actions through context

  • VideoElement

  • Validates URL and detects protocol

  • Initializes and tears down stream engine instances

  • Subscribes to media events and forwards callbacks

  • Converts .srt subtitles to VTT Blob URLs

  • Renders subtitles using a custom overlay (native rendering disabled)

  • ControlsLayer

  • Handles auto-hide controls behavior

  • Loads SettingsMenu lazily

  • Enables keyboard shortcuts and touch gestures hooks

  • Renders center play, spinner, progress, volume, settings, PIP, fullscreen

3. Installation and setup

This package is published to a private registry.

  1. Add .npmrc:
@source:registry=https://gits.hibna.com.tr/api/packages/hibna/npm/
//gits.hibna.com.tr/api/packages/hibna/npm/:_authToken=${GITS_NPM_TOKEN}
  1. Install package:
npm install @source/player
# or
pnpm add @source/player
# or
yarn add @source/player
  1. Ensure app already includes React and ReactDOM (>=18).

  2. Import player styles:

import '@source/player/styles.css'

Optional local dependencies (instead of CDN fallback):

npm install hls.js flv.js mpegts.js

4. Quick usage

4.1 Minimal usage

import { VideoPlayer } from '@source/player'
import '@source/player/styles.css'

export function App() {
  return <VideoPlayer src="https://example.com/video.mp4" />
}

4.2 With subtitles + editor

<VideoPlayer
  src="https://example.com/video.mp4"
  subtitles={[
    { src: '/subs/en.vtt', lang: 'en', label: 'English', default: true },
    { src: '/subs/tr.srt', lang: 'tr', label: 'Turkish' },
  ]}
  subtitleStyleEditor={{ enabled: true, storageKey: 'player-subtitle-style' }}
/>

4.3 With theme and modular controls

<VideoPlayer
  src="https://example.com/video.mp4"
  theme={{
    primaryColor: '#22c55e',
    accentColor: '#16a34a',
    backgroundColor: '#000000',
    textColor: '#f8fafc',
    borderRadius: 16,
    fontFamily: 'Manrope, sans-serif',
  }}
  controlsLeftExtra={<button type="button">Bookmark</button>}
  controlsRightExtra={<button type="button">Share</button>}
>
  <div style={{ position: 'absolute', top: 12, right: 12 }}>Custom overlay</div>
</VideoPlayer>

4.4 Animated image support

<VideoPlayer src="https://example.com/animations/loader.gif" />

VideoPlayer auto-detects animated image sources and renders them with a lightweight <img> path.
In this mode, control layer is hidden automatically for minimal runtime cost.

4.5 Dedicated audio player

import { AudioPlayer } from '@source/player'

<AudioPlayer
  src="https://example.com/audio/episode.mp3"
  title="Episode 12"
  subtitle="Engineering Notes"
  artwork="https://example.com/audio/cover.jpg"
  playbackRates={[0.75, 1, 1.25, 1.5, 2]}
/>

5. Streaming and protocol handling

5.1 Auto protocol detection

When protocol="auto" (default), detectVideoProtocol(src) chooses by URL pattern.

Pattern Detected protocol Live hint Needs special engine
rtmp://, rtmps://, rtmpt://, rtmpe:// rtmp true true
contains .m3u8 hls true if contains /live/ or live.m3u8 true
contains .mpd dash true if contains /live/ or live.mpd true
contains .flv or flv? rtmp true if contains /live/ or live.flv true
.ts stream URL mpegts true true
other URLs native false false

You can override with protocol="native" | "hls" | "rtmp" | "dash" | "mpegts".

5.2 HLS flow

  • For HLS URLs:
  • Safari/native HLS path is preferred when appropriate.
  • Other browsers use hls.js.

Loading strategy:

  1. Dynamic import from npm (hls.js)
  2. If unavailable, CDN fallback: https://cdn.jsdelivr.net/npm/hls.js@1.6.13/dist/hls.min.js

HLS setup behavior:

  • Tracks audio tracks, quality levels, subtitle tracks
  • Handles fatal errors:
  • NETWORK_ERROR -> startLoad()
  • MEDIA_ERROR -> recoverMediaError()
  • others -> emit onError

5.3 RTMP / FLV flow

  • Uses flv.js runtime loader.
  • npm dynamic import first, then CDN fallback:
  • https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js

Important note:

  • Browser playback is effectively HTTP-FLV based.
  • Direct RTMP endpoints usually require server-side proxy/conversion to HTTP-FLV.

5.4 MPEG-TS flow

  • Uses mpegts.js runtime loader.
  • npm dynamic import first, then CDN fallback:
  • https://cdn.jsdelivr.net/npm/mpegts.js@1.7.3/dist/mpegts.js

5.5 DASH status

  • DASH (.mpd) is detected but not implemented.
  • Player emits Error('DASH streaming is not yet supported').

5.6 Live broadcast UI behavior

videoState.isLiveBroadcast is set from media duration (Infinity or 0):

  • Progress bar and time display are hidden
  • Live badge is shown in controls

5.7 Animated image detection behavior

VideoPlayer can detect animated image sources by extension or data MIME:

  • .gif, .webp, .apng, .avif
  • data:image/gif, data:image/webp, data:image/apng, data:image/avif

When detected, player uses image render path instead of protocol setup and stream engines.

6. Subtitles and subtitle style editor

6.1 Subtitle sources

subtitles prop accepts manual tracks:

type SubtitleTrack = {
  src: string
  lang: string
  label: string
  default?: boolean
}

HLS subtitle tracks are merged with manual tracks internally.

6.2 SRT support

  • .srt tracks are fetched and converted to VTT Blob URLs.
  • Blob URLs are revoked on cleanup.

6.3 Custom subtitle rendering

  • Native text track rendering is disabled.
  • Active cues are read from selected TextTrack and rendered inside .sp-subtitle-overlay.
  • Cue markup is stripped before rendering.

6.4 Subtitle styling

subtitleStyle supports:

  • fontFamily
  • fontSize
  • fontWeight
  • color
  • backgroundColor
  • backgroundOpacity (0..1)

Applied style is merged as:

  • base: subtitleStyle prop
  • override: context settings.subtitleStyle (including editor changes)

6.5 Subtitle style editor

Enable via:

subtitleStyleEditor={true}
// or
subtitleStyleEditor={{ enabled: true, storageKey: 'my-key' }}

Behavior:

  • Editor appears under Settings -> Subtitles
  • Draft updates preview in real time
  • Save persists to localStorage
  • Cancel reverts to last committed style
  • Reset sets editor defaults (not persisted until save)

Default storage key:

  • source-player-subtitle-style

7. Customization and modular composition

7.1 Theme API

theme maps to CSS variables:

  • primaryColor -> --player-primary
  • accentColor -> --player-primary-hover
  • backgroundColor -> --player-bg
  • textColor -> --player-text
  • fontFamily -> --player-font-family
  • borderRadius -> --player-radius
  • overlayOpacity -> --player-overlay-soft
  • controlsBackground -> --player-surface
  • textSecondaryColor -> --player-text-secondary
  • textMutedColor -> --player-text-muted

7.2 Layout and slots

  • aspectRatio: '16:9' | '4:3' | '21:9' | '1:1' | '9:16' | number
  • children: overlay content slot
  • controlsLeftExtra: inject extra controls on left side
  • controlsRightExtra: inject extra controls on right side

7.3 Controls auto-hide

controlsAutoHideDelay (default 3000) affects full-screen auto-hide while:

  • video is playing
  • full-screen is active
  • no settings sub-menu is open

8. Keyboard and touch interaction

8.1 Keyboard shortcuts

Enabled by default (keyboardShortcuts=true).

The player only reacts when active/focused (clicked/touched/focused instance).

Default shortcuts:

  • Space / K: play-pause
  • ArrowLeft / ArrowRight: seek 5s
  • J / L: seek 10s
  • ArrowUp / ArrowDown: volume +/- 0.1
  • M: mute toggle
  • F: fullscreen toggle
  • P: picture-in-picture toggle
  • 0 / Home: seek to start
  • End: seek to end
  • 1..9: seek to 10%..90%

Config:

type KeyboardShortcutConfig = {
  seekSmall?: number
  seekLarge?: number
  volumeStep?: number
  disabled?: string[]
}

8.2 Touch gestures

Enabled via useTouchGestures in control layer.

Default gestures:

  • single tap: play-pause
  • double tap left/right: seek -/+ 10s
  • horizontal swipe: seek proportional to swipe distance
  • vertical swipe: volume change proportional to swipe distance

Config:

type TouchConfig = {
  maxSeekSeconds?: number
  maxVolumeChange?: number
  doubleTapSeekSeconds?: number
}

Default values:

  • maxSeekSeconds = 30
  • maxVolumeChange = 0.5
  • doubleTapSeekSeconds = 10

9. Internationalization

Built-in locales:

  • en
  • tr

Selection flow:

  • If language prop is provided, it is used.
  • Else browser language is detected.
  • Region codes fallback to base language (en-US -> en).
  • Unknown language fallback: en.

You can override any key with translations?: Partial<Translations>.

10. API reference

10.1 VideoPlayerProps

Source and playback

Prop Type Default Notes
src string required media URL
mediaType 'auto' | 'video' | 'animated-image' 'auto' force image/video mode
protocol 'auto' | 'native' | 'hls' | 'rtmp' | 'dash' | 'mpegts' 'auto' force engine
poster string - poster image
autoplay boolean false autoplay attempt on load
loop boolean false loop playback
muted boolean false initial muted state
volume number - clamped to 0..1
playbackRate number - initial/current rate
currentTime number - seeks when difference is significant

Media element attributes

Prop Type Default
crossOrigin '' | 'anonymous' | 'use-credentials' -
preload 'none' | 'metadata' | 'auto' 'metadata'
playsInline boolean true
controlsList string -

UI and feature toggles

Prop Type Default Notes
controls boolean true hide entire control layer when false
keyboardShortcuts boolean true enables keyboard hook
pictureInPicture boolean true PIP button toggle
controlsAutoHideDelay number 3000 ms
playbackRates number[] [0.25,0.5,0.75,1,1.25,1.5,1.75,2] settings menu speeds
aspectRatio '16:9' | '4:3' | '21:9' | '1:1' | '9:16' | number '16:9' equivalent CSS ratio

Subtitles and settings

Prop Type Default
subtitles SubtitleTrack[] []
subtitleStyle SubtitleStyle -
subtitleStyleEditor boolean | SubtitleStyleEditorConfig false
subtitlePosition 'top' | 'center' | 'bottom' 'bottom'
subtitleOffset number | string -

Styling and composition

Prop Type Default
theme PlayerTheme -
className string ''
style CSSProperties -
children ReactNode -
controlsLeftExtra ReactNode -
controlsRightExtra ReactNode -

Localization and input config

Prop Type Default
language string browser language
translations Partial<Translations> -
keyboardShortcutConfig KeyboardShortcutConfig -
touchConfig TouchConfig -

Events

Prop Type
onPlay () => void
onPause () => void
onEnded () => void
onTimeUpdate (currentTime: number) => void
onVolumeChange (volume: number) => void
onError (error: Error) => void
onLoadedMetadata () => void
onSeeking () => void
onSeeked () => void
onProgress (buffered: number) => void
onDurationChange (duration: number) => void
onRateChange (playbackRate: number) => void
onFullscreenChange (isFullscreen: boolean) => void
onPictureInPictureChange (isPictureInPicture: boolean) => void
onWaiting () => void
onCanPlay () => void
onQualityChange (quality: VideoQuality) => void
onBufferStart () => void
onBufferEnd () => void
onFirstPlay () => void

10.2 VideoPlayerHandle

interface VideoPlayerHandle {
  video: HTMLVideoElement | null
  container: HTMLDivElement | null
  play(): void
  pause(): void
  seek(time: number): void
  setVolume(volume: number): void
  toggleMute(): void
  toggleFullscreen(): void
  togglePictureInPicture(): void
  setPlaybackRate(rate: number): void
}

10.3 AudioPlayerProps

Source and playback

Prop Type Default Notes
src string required audio URL
autoplay boolean false autoplay attempt on load
loop boolean false loop playback
muted boolean false initial muted state
volume number - clamped to 0..1
playbackRate number - initial/current rate
currentTime number - seeks when difference is significant
preload 'none' | 'metadata' | 'auto' 'metadata' media preload hint
crossOrigin '' | 'anonymous' | 'use-credentials' - CORS mode

UI, metadata and composition

Prop Type Default
controls boolean true
keyboardShortcuts boolean true
playbackRates number[] [0.5,0.75,1,1.25,1.5,2]
title string -
subtitle string -
artwork string -
theme PlayerTheme -
className string ''
style CSSProperties -
children ReactNode -
controlsLeftExtra ReactNode -
controlsRightExtra ReactNode -
language string browser language
translations Partial<Translations> -

Events

Prop Type
onPlay () => void
onPause () => void
onEnded () => void
onTimeUpdate (currentTime: number) => void
onVolumeChange (volume: number) => void
onError (error: Error) => void
onLoadedMetadata () => void
onSeeking () => void
onSeeked () => void
onProgress (buffered: number) => void
onDurationChange (duration: number) => void
onRateChange (playbackRate: number) => void
onWaiting () => void
onCanPlay () => void

10.4 AudioPlayerHandle

interface AudioPlayerHandle {
  audio: HTMLAudioElement | null
  container: HTMLDivElement | null
  play(): void
  pause(): void
  seek(time: number): void
  setVolume(volume: number): void
  toggleMute(): void
  setPlaybackRate(rate: number): void
}

10.5 PlayerErrorBoundaryProps

interface PlayerErrorBoundaryProps {
  children: React.ReactNode
  fallback?: React.ReactNode | ((error: Error, retry: () => void) => React.ReactNode)
  onError?: (error: Error, errorInfo: React.ErrorInfo) => void
  onReset?: () => void
  resetKeys?: ReadonlyArray<unknown>
}

10.6 Core types

type SubtitleTrack = {
  src: string
  lang: string
  label: string
  default?: boolean
}

type SubtitleStyle = {
  fontFamily?: string
  fontSize?: number | string
  fontWeight?: number | string
  color?: string
  backgroundColor?: string
  backgroundOpacity?: number
}

type SubtitleStyleEditorConfig = {
  enabled?: boolean
  storageKey?: string
}

type VideoQuality = {
  height?: number
  label: string
  url?: string
  width?: number
  bitrate?: number
  levelIndex?: number
}

type AudioTrack = {
  name: string
  language: string
  url: string
  groupId: string
  default?: boolean
  autoselect?: boolean
}

type PlayerTheme = {
  primaryColor?: string
  accentColor?: string
  backgroundColor?: string
  textColor?: string
  fontFamily?: string
  borderRadius?: number | string
  overlayOpacity?: number
  controlsBackground?: string
  textSecondaryColor?: string
  textMutedColor?: string
}

11. Public exports

From src/index.ts:

  • Components

  • VideoPlayer

  • AudioPlayer

  • PlayerErrorBoundary

  • Context

  • PlayerProvider

  • usePlayerContext

  • Hooks

  • useKeyboardShortcuts

  • useTouchGestures

  • i18n

  • getTranslations

  • detectBrowserLanguage

  • translations

  • Utilities

  • formatTime, parseTime

  • parseSRT, createSubtitleBlobURL, fetchSubtitle

  • validateVideoURL, getCORSErrorMessage, isCORSError, checkVideoCORS

  • initializePolyfills, features

  • detectPlayerMediaType, isAnimatedImageSource, isAudioSource

  • Types

  • VideoPlayerProps, VideoPlayerHandle, AudioPlayerProps, AudioPlayerHandle

  • SubtitleTrack, SubtitleStyle, SubtitleStyleEditorConfig

  • SubtitlePosition, AudioTrack, VideoQuality, PlayerTheme

  • VideoMediaType, VideoMediaTypeInput

  • KeyboardShortcutConfig, TouchConfig

  • VideoState, UIState, PlayerSettings, PlayerContextValue

  • Translations

12. Error handling and reliability

12.1 URL and CORS helpers

Use exported helpers before rendering:

import { validateVideoURL, checkVideoCORS } from '@source/player'

const validation = validateVideoURL(src)
if (!validation.valid) {
  throw new Error(validation.error)
}

const cors = await checkVideoCORS(src)
if (!cors.supported) {
  console.warn(cors.error)
}

12.2 Error boundary

VideoPlayer already wraps content in PlayerErrorBoundary with resetKeys={[src]}.

You can also wrap manually for custom fallback UI:

import { PlayerErrorBoundary, VideoPlayer } from '@source/player'

<PlayerErrorBoundary fallback={(error, retry) => (
  <div>
    <p>{error.message}</p>
    <button onClick={retry}>Retry</button>
  </div>
)}>
  <VideoPlayer src="https://example.com/video.mp4" />
</PlayerErrorBoundary>

12.3 Stream instance lifecycle

For HLS/FLV/MPEG-TS setups:

  • instance is attached to video element for runtime control
  • cleanup runs on source/protocol change and unmount
  • orphan instances are explicitly destroyed if still present

13. Browser behavior and known limitations

  • PIP button renders only when browser supports PIP APIs.
  • iOS Safari lacks full programmatic volume control.
  • Direct RTMP URLs usually need HTTP-FLV proxying.
  • DASH detection exists but playback is not implemented yet.
  • MPEG-TS behavior depends on MSE support and stream/server conditions.
  • Animated image mode is display-only (no timeline controls by design).
  • Some browsers may restrict autoplay for audio unless muted or user-initiated.

Polyfill and feature utilities:

import { initializePolyfills, features } from '@source/player'

initializePolyfills()

features.hasNativeHLS()
features.hasMSE()
features.hasPIP()
features.hasFullscreen()
features.hasTouch()
features.isIOSSafari()
features.hasVolumeControl()

14. Testing and development

14.1 Useful commands

npm install
npm run dev
npm run lint
npm run typecheck
npm run test:run
npm run build:lib
npm run validate:publish

14.2 Test coverage areas in repository

  • Core rendering and props (VideoPlayer.test.tsx)
  • Audio rendering and controls (AudioPlayer.test.tsx)
  • Settings interactions (SettingsMenu.test.tsx)
  • Error boundary reset and fallback behavior
  • Keyboard and touch hook behavior
  • Protocol detection and streaming setup utilities
  • Media type detection (mediaSource.test.ts)
  • CORS helper validation

If you maintain this document, update it together with changes to src/types/index.ts, src/index.ts, src/components/VideoPlayer.tsx, and src/components/AudioPlayer.tsx to avoid API drift.