700 lines
18 KiB
Markdown
700 lines
18 KiB
Markdown
# @source/player Documentation
|
|
|
|
This document reflects the current codebase in this repository (`version 3.1.0`) and replaces older, drifted documentation.
|
|
|
|
## Table of contents
|
|
|
|
1. [Overview](#1-overview)
|
|
2. [Architecture](#2-architecture)
|
|
3. [Installation and setup](#3-installation-and-setup)
|
|
4. [Quick usage](#4-quick-usage)
|
|
5. [Streaming and protocol handling](#5-streaming-and-protocol-handling)
|
|
6. [Subtitles and subtitle style editor](#6-subtitles-and-subtitle-style-editor)
|
|
7. [Customization and modular composition](#7-customization-and-modular-composition)
|
|
8. [Keyboard and touch interaction](#8-keyboard-and-touch-interaction)
|
|
9. [Internationalization](#9-internationalization)
|
|
10. [API reference](#10-api-reference)
|
|
11. [Public exports](#11-public-exports)
|
|
12. [Error handling and reliability](#12-error-handling-and-reliability)
|
|
13. [Browser behavior and known limitations](#13-browser-behavior-and-known-limitations)
|
|
14. [Testing and development](#14-testing-and-development)
|
|
|
|
## 1. Overview
|
|
|
|
`@source/player` is a React video player library with:
|
|
|
|
- Protocol-aware playback (`native`, `hls`, `rtmp/flv`, `mpegts`)
|
|
- 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`
|
|
- `PlayerErrorBoundary`
|
|
- `PlayerProvider` (context + state + actions + i18n)
|
|
- `VideoElement` (media element, protocol setup, media events, subtitle rendering)
|
|
- `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`)
|
|
|
|
- `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`:
|
|
|
|
```ini
|
|
@source:registry=https://gits.hibna.com.tr/api/packages/hibna/npm/
|
|
//gits.hibna.com.tr/api/packages/hibna/npm/:_authToken=${GITS_NPM_TOKEN}
|
|
```
|
|
|
|
2. Install package:
|
|
|
|
```bash
|
|
npm install @source/player
|
|
# or
|
|
pnpm add @source/player
|
|
# or
|
|
yarn add @source/player
|
|
```
|
|
|
|
3. Ensure app already includes React and ReactDOM (`>=18`).
|
|
|
|
4. Import player styles:
|
|
|
|
```tsx
|
|
import '@source/player/styles.css'
|
|
```
|
|
|
|
Optional local dependencies (instead of CDN fallback):
|
|
|
|
```bash
|
|
npm install hls.js flv.js mpegts.js
|
|
```
|
|
|
|
## 4. Quick usage
|
|
|
|
### 4.1 Minimal usage
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
<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
|
|
|
|
```tsx
|
|
<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>
|
|
```
|
|
|
|
## 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
|
|
|
|
## 6. Subtitles and subtitle style editor
|
|
|
|
### 6.1 Subtitle sources
|
|
|
|
`subtitles` prop accepts manual tracks:
|
|
|
|
```ts
|
|
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:
|
|
|
|
```tsx
|
|
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:
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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 |
|
|
| `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`
|
|
|
|
```ts
|
|
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 `PlayerErrorBoundaryProps`
|
|
|
|
```ts
|
|
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.4 Core types
|
|
|
|
```ts
|
|
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`
|
|
- `PlayerErrorBoundary`
|
|
|
|
- Context
|
|
- `PlayerProvider`
|
|
- `usePlayerContext`
|
|
|
|
- Hooks
|
|
- `useKeyboardShortcuts`
|
|
- `useTouchGestures`
|
|
|
|
- i18n
|
|
- `getTranslations`
|
|
- `detectBrowserLanguage`
|
|
- `translations`
|
|
|
|
- Utilities
|
|
- `formatTime`, `parseTime`
|
|
- `parseSRT`, `createSubtitleBlobURL`, `fetchSubtitle`
|
|
- `validateVideoURL`, `getCORSErrorMessage`, `isCORSError`, `checkVideoCORS`
|
|
- `initializePolyfills`, `features`
|
|
|
|
- Types
|
|
- `VideoPlayerProps`, `VideoPlayerHandle`, `SubtitleTrack`, `SubtitleStyle`, `SubtitleStyleEditorConfig`
|
|
- `SubtitlePosition`, `AudioTrack`, `VideoQuality`, `PlayerTheme`
|
|
- `KeyboardShortcutConfig`, `TouchConfig`
|
|
- `VideoState`, `UIState`, `PlayerSettings`, `PlayerContextValue`
|
|
- `Translations`
|
|
|
|
## 12. Error handling and reliability
|
|
|
|
### 12.1 URL and CORS helpers
|
|
|
|
Use exported helpers before rendering:
|
|
|
|
```ts
|
|
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:
|
|
|
|
```tsx
|
|
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.
|
|
|
|
Polyfill and feature utilities:
|
|
|
|
```ts
|
|
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
|
|
|
|
```bash
|
|
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`)
|
|
- Settings interactions (`SettingsMenu.test.tsx`)
|
|
- Error boundary reset and fallback behavior
|
|
- Keyboard and touch hook behavior
|
|
- Protocol detection and streaming setup utilities
|
|
- CORS helper validation
|
|
|
|
---
|
|
|
|
If you maintain this document, update it together with changes to `src/types/index.ts`, `src/index.ts`, and `src/components/VideoPlayer.tsx` to avoid API drift.
|