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
- Overview
- Architecture
- Installation and setup
- Quick usage
- Streaming and protocol handling
- Subtitles and subtitle style editor
- Customization and modular composition
- Keyboard and touch interaction
- Internationalization
- API reference
- Public exports
- Error handling and reliability
- Browser behavior and known limitations
- 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
VideoPlayerAudioPlayerPlayerErrorBoundaryPlayerProvider(context + state + actions + i18n)VideoElement(media element, protocol setup, media events, subtitle rendering)mediaSourceutils (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
PlayerErrorBoundaryandPlayerProvider -
Exposes imperative API via ref (
VideoPlayerHandle) -
AudioPlayer -
Uses native
HTMLAudioElementwith 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, andsettings -
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
.srtsubtitles to VTT Blob URLs -
Renders subtitles using a custom overlay (native rendering disabled)
-
ControlsLayer -
Handles auto-hide controls behavior
-
Loads
SettingsMenulazily -
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.
- 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}
- Install package:
npm install @source/player
# or
pnpm add @source/player
# or
yarn add @source/player
-
Ensure app already includes React and ReactDOM (
>=18). -
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:
- Dynamic import from npm (
hls.js) - 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.jsruntime 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.jsruntime 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,.avifdata: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
.srttracks 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
TextTrackand rendered inside.sp-subtitle-overlay. - Cue markup is stripped before rendering.
6.4 Subtitle styling
subtitleStyle supports:
fontFamilyfontSizefontWeightcolorbackgroundColorbackgroundOpacity(0..1)
Applied style is merged as:
- base:
subtitleStyleprop - 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
Savepersists to localStorageCancelreverts to last committed styleResetsets 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-primaryaccentColor->--player-primary-hoverbackgroundColor->--player-bgtextColor->--player-textfontFamily->--player-font-familyborderRadius->--player-radiusoverlayOpacity->--player-overlay-softcontrolsBackground->--player-surfacetextSecondaryColor->--player-text-secondarytextMutedColor->--player-text-muted
7.2 Layout and slots
aspectRatio:'16:9' | '4:3' | '21:9' | '1:1' | '9:16' | numberchildren: overlay content slotcontrolsLeftExtra: inject extra controls on left sidecontrolsRightExtra: 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-pauseArrowLeft/ArrowRight: seek5sJ/L: seek10sArrowUp/ArrowDown: volume +/-0.1M: mute toggleF: fullscreen toggleP: picture-in-picture toggle0/Home: seek to startEnd: seek to end1..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 = 30maxVolumeChange = 0.5doubleTapSeekSeconds = 10
9. Internationalization
Built-in locales:
entr
Selection flow:
- If
languageprop 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.