becc9efc7f
Introduced detection and support for MPEG-TS (.ts) IPTV streams in videoProtocol, updated documentation and examples to reflect IPTV support, and added comprehensive tests for protocol detection. Mock implementations for flv.js and hls.js were added for testing, and vitest config now aliases these libraries to their mocks.
2617 lines
60 KiB
Markdown
2617 lines
60 KiB
Markdown
# 🎬 @alper/video-player - Tam Dökümantasyon
|
||
|
||
**Modern, zengin özellikli ve hafif React video oynatıcı kütüphanesi**
|
||
|
||
[](https://www.npmjs.com/package/@alper/video-player)
|
||
[](https://bundlephobia.com/package/@alper/video-player)
|
||
[](https://www.typescriptlang.org/)
|
||
[](https://opensource.org/licenses/MIT)
|
||
|
||
---
|
||
|
||
## 📑 İçindekiler
|
||
|
||
- [Genel Bakış](#-genel-bakış)
|
||
- [Kurulum](#-kurulum)
|
||
- [NPM/PNPM ile Kurulum](#1-npmpnpm-ile-kurulum)
|
||
- [.npmrc Yapılandırması](#2-npmrc-yapılandırması)
|
||
- [Kütüphaneyi Yükleme](#3-kütüphaneyi-yükleme)
|
||
- [Hızlı Başlangıç](#-hızlı-başlangıç)
|
||
- [Mimari ve Yapı](#-mimari-ve-yapı)
|
||
- [Özellikler](#-özellikler)
|
||
- [Video Format Desteği](#video-format-desteği)
|
||
- [Altyazı Sistemi](#altyazı-sistemi)
|
||
- [Ses Parça Yönetimi](#ses-parça-yönetimi)
|
||
- [Kalite Seviyesi Kontrolü](#kalite-seviyesi-kontrolü)
|
||
- [Klavye Kısayolları](#klavye-kısayolları)
|
||
- [Dokunmatik Jestler](#dokunmatik-jestler)
|
||
- [API Referansı](#-api-referansı)
|
||
- [VideoPlayer Props](#videoplayer-props)
|
||
- [Exported Components](#exported-components)
|
||
- [Exported Hooks](#exported-hooks)
|
||
- [Exported Utilities](#exported-utilities)
|
||
- [Type Definitions](#type-definitions)
|
||
- [Kullanım Örnekleri](#-kullanım-örnekleri)
|
||
- [Gelişmiş Kullanım](#-gelişmiş-kullanım)
|
||
- [State Yönetimi](#-state-yönetimi)
|
||
- [Tema ve Özelleştirme](#-tema-ve-özelleştirme)
|
||
- [Uluslararasılaştırma (i18n)](#-uluslararasılaştırma-i18n)
|
||
- [Performans](#-performans)
|
||
- [Tarayıcı Uyumluluğu](#-tarayıcı-uyumluluğu)
|
||
- [Hata Yönetimi](#-hata-yönetimi)
|
||
- [Geliştirme](#-geliştirme)
|
||
- [Katkıda Bulunma](#-katkıda-bulunma)
|
||
- [Lisans](#-lisans)
|
||
|
||
---
|
||
|
||
## 🌟 Genel Bakış
|
||
|
||
`@alper/video-player`, React uygulamaları için özel olarak tasarlanmış, modern bir video oynatıcı kütüphanesidir. HLS streaming, RTMP/FLV desteği, çoklu altyazı ve ses parçaları, kalite değiştirme ve daha fazlası gibi gelişmiş özellikleri içerir.
|
||
|
||
### Neden @alper/video-player?
|
||
|
||
| Özellik | @alper/video-player | video.js | react-player | plyr |
|
||
|---------|---------------------|----------|--------------|------|
|
||
| **Paket Boyutu (gzipped)** | **~15KB** ✅ | ~500KB ❌ | ~50KB ⚠️ | ~30KB ⚠️ |
|
||
| **Runtime Bağımlılıkları** | **0** ✅ | Çok ❌ | Az ⚠️ | Az ⚠️ |
|
||
| **React Native** | **Evet** ✅ | Wrapper ⚠️ | **Evet** ✅ | Wrapper ⚠️ |
|
||
| **TypeScript Native** | **Evet** ✅ | Types ⚠️ | Kısmi ⚠️ | Types ⚠️ |
|
||
| **HLS Desteği** | **Evet** ✅ | Evet ✅ | Evet ✅ | Hayır ❌ |
|
||
| **RTMP/FLV Desteği** | **Evet** ✅ | Hayır ❌ | Hayır ❌ | Hayır ❌ |
|
||
| **Kalite Değiştirme** | **Evet** ✅ | Evet ✅ | Sınırlı ⚠️ | Hayır ❌ |
|
||
| **Dokunmatik Jestler** | **15+** ✅ | Sınırlı ⚠️ | Hayır ❌ | Sınırlı ⚠️ |
|
||
| **Klavye Kısayolları** | **15+** ✅ | ~8 ⚠️ | Temel ⚠️ | ~10 ⚠️ |
|
||
| **i18n Desteği** | **Evet** ✅ | Evet ✅ | Hayır ❌ | Evet ✅ |
|
||
|
||
### Ana Avantajlar
|
||
|
||
- 📦 **%97 daha küçük** - video.js'e göre sadece 15KB (500KB yerine)
|
||
- ⚡ **Çok hızlı** - Sıfır runtime bağımlılık, daha hızlı yükleme
|
||
- 🎯 **React-first** - React için özel yapılmış, wrapper değil
|
||
- 🔧 **Tam TypeScript** - Box'tan çıktığı gibi tam tip güvenliği
|
||
- 🎨 **Kolay özelleştirme** - CSS değişkenleri ile tema desteği
|
||
- 📱 **Mobil hazır** - Kapsamlı dokunmatik jest desteği
|
||
- 🌍 **Uluslararasılaştırılmış** - İngilizce ve Türkçe yerleşik i18n
|
||
- ♿ **Erişilebilir** - ARIA etiketleri ve klavye navigasyonu
|
||
- 🎬 **Çoklu format** - MP4, WebM, HLS (.m3u8), RTMP/FLV desteği
|
||
- 🔒 **Güvenli** - CORS hatası tespiti ve yardımcı hata mesajları
|
||
|
||
---
|
||
|
||
## 📦 Kurulum
|
||
|
||
Bu kütüphane, Gitea üzerinde barındırılan özel bir npm registry'de yayınlanmaktadır. Kurulum için aşağıdaki adımları takip edin.
|
||
|
||
### 1. NPM/PNPM ile Kurulum
|
||
|
||
Öncelikle projenizde npm veya pnpm kurulu olduğundan emin olun:
|
||
|
||
```bash
|
||
# npm kurulu mu kontrol et
|
||
npm --version
|
||
|
||
# pnpm kurulu mu kontrol et (opsiyonel, hızlı alternatif)
|
||
pnpm --version
|
||
|
||
# pnpm kurulu değilse
|
||
npm install -g pnpm
|
||
```
|
||
|
||
### 2. .npmrc Yapılandırması
|
||
|
||
`@alper` scope'lu paketlerin Gitea registry'den çekilmesi için projenizin kök dizininde bir `.npmrc` dosyası oluşturmanız gerekir.
|
||
|
||
#### Adım 2.1: .npmrc Dosyası Oluşturma
|
||
|
||
Projenizin **kök dizininde** (package.json dosyasının bulunduğu yerde) `.npmrc` adlı bir dosya oluşturun:
|
||
|
||
```bash
|
||
# Linux/Mac
|
||
touch .npmrc
|
||
|
||
# Windows (PowerShell)
|
||
New-Item -Path .npmrc -ItemType File
|
||
```
|
||
|
||
#### Adım 2.2: .npmrc İçeriği
|
||
|
||
`.npmrc` dosyasını aşağıdaki içerikle doldurun:
|
||
|
||
```ini
|
||
# @alper scope'u için Gitea registry'yi kullan
|
||
@alper:registry=https://gitea.hibna.com.tr/api/packages/hibna/npm/
|
||
|
||
# Authentication token
|
||
# Token'ınızı almak için: https://gitea.hibna.com.tr/user/settings/applications
|
||
# "Generate New Token" butonuna tıklayın ve "read:package" yetkisini seçin
|
||
//gitea.hibna.com.tr/api/packages/hibna/npm/:_authToken=YOUR_TOKEN_HERE
|
||
```
|
||
|
||
**ÖNEMLİ:** `YOUR_TOKEN_HERE` kısmını kendi Gitea access token'ınız ile değiştirin!
|
||
|
||
#### Adım 2.3: Gitea Access Token Alma
|
||
|
||
1. Gitea hesabınıza giriş yapın: https://gitea.hibna.com.tr
|
||
2. Sağ üst köşeden profil ikonuna tıklayın → **Settings**
|
||
3. Sol menüden **Applications** seçeneğine tıklayın
|
||
4. **Manage Access Tokens** bölümünde **Generate New Token** butonuna tıklayın
|
||
5. Token için bir isim verin (örn: "video-player-npm")
|
||
6. **Select permissions** bölümünde şu yetkileri seçin:
|
||
- `read:package` - Paketleri okuma yetkisi
|
||
- (Yayınlayacaksanız) `write:package` - Paket yayınlama yetkisi
|
||
7. **Generate Token** butonuna tıklayın
|
||
8. Oluşturulan token'ı kopyalayın (bu token sadece bir kez gösterilir!)
|
||
9. Token'ı `.npmrc` dosyasındaki `YOUR_TOKEN_HERE` yerine yapıştırın
|
||
|
||
#### Adım 2.4: .npmrc'yi .gitignore'a Ekleyin
|
||
|
||
**ÇOK ÖNEMLİ:** Token'ınızı Git'e commit etmeyin! `.npmrc` dosyasını `.gitignore`'a ekleyin:
|
||
|
||
```bash
|
||
# .gitignore dosyasına ekleyin
|
||
echo ".npmrc" >> .gitignore
|
||
```
|
||
|
||
`.gitignore` dosyanız şöyle görünmeli:
|
||
|
||
```
|
||
node_modules/
|
||
dist/
|
||
.npmrc # ← Bu satırı ekleyin
|
||
.env
|
||
```
|
||
|
||
#### Alternatif: Environment Variable Kullanımı (Önerilen - Daha Güvenli)
|
||
|
||
Daha güvenli bir yöntem için token'ı environment variable olarak saklayabilirsiniz:
|
||
|
||
**.npmrc dosyası:**
|
||
```ini
|
||
@alper:registry=https://gitea.hibna.com.tr/api/packages/hibna/npm/
|
||
//gitea.hibna.com.tr/api/packages/hibna/npm/:_authToken=${GITEA_TOKEN}
|
||
```
|
||
|
||
**Environment variable ayarlama:**
|
||
|
||
```bash
|
||
# Linux/Mac (.bashrc veya .zshrc dosyasına ekleyin)
|
||
export GITEA_TOKEN=your-actual-token-here
|
||
|
||
# Windows (PowerShell)
|
||
$env:GITEA_TOKEN="your-actual-token-here"
|
||
|
||
# Windows (Kalıcı - Sistem Environment Variables)
|
||
# Sistem Özellikler → Gelişmiş → Ortam Değişkenleri → Yeni
|
||
# Değişken adı: GITEA_TOKEN
|
||
# Değişken değeri: your-actual-token-here
|
||
```
|
||
|
||
**CI/CD için (GitHub Actions, GitLab CI, vb.):**
|
||
|
||
Repository settings → Secrets → `GITEA_TOKEN` adında bir secret oluşturun.
|
||
|
||
### 3. Kütüphaneyi Yükleme
|
||
|
||
`.npmrc` dosyası yapılandırıldıktan sonra, kütüphaneyi yükleyebilirsiniz:
|
||
|
||
```bash
|
||
# npm ile
|
||
npm install @alper/video-player
|
||
|
||
# veya pnpm ile (önerilen - daha hızlı)
|
||
pnpm add @alper/video-player
|
||
|
||
# veya yarn ile
|
||
yarn add @alper/video-player
|
||
```
|
||
|
||
### 4. Peer Dependencies
|
||
|
||
Kütüphane, React 18+ gerektirir. Eğer projenizde yoksa:
|
||
|
||
```bash
|
||
npm install react@^18.0.0 react-dom@^18.0.0
|
||
# veya
|
||
pnpm add react@^18.0.0 react-dom@^18.0.0
|
||
```
|
||
|
||
### 5. Optional Dependencies (Otomatik)
|
||
|
||
HLS ve RTMP/FLV desteği için gerekli kütüphaneler, ihtiyaç duyulduğunda **otomatik olarak lazy-load** edilir:
|
||
|
||
- `hls.js` - HLS streaming için (.m3u8 dosyaları)
|
||
- `flv.js` - RTMP/FLV streaming için
|
||
|
||
Bu kütüphaneler manuel kuruluma gerek yoktur. Ancak bundle size'ı azaltmak için isterseniz kurabilirsiniz:
|
||
|
||
```bash
|
||
npm install --save-optional hls.js flv.js
|
||
```
|
||
|
||
### Kurulum Doğrulama
|
||
|
||
Kurulumun başarılı olduğunu doğrulamak için:
|
||
|
||
```bash
|
||
npm list @alper/video-player
|
||
# veya
|
||
pnpm list @alper/video-player
|
||
```
|
||
|
||
Çıktı şöyle olmalı:
|
||
```
|
||
your-project@1.0.0
|
||
└─┬ @alper/video-player@0.1.5
|
||
```
|
||
|
||
### Sorun Giderme
|
||
|
||
**Problem: "401 Unauthorized" hatası**
|
||
- `.npmrc` dosyasındaki token'ın doğru olduğundan emin olun
|
||
- Token'ın `read:package` yetkisine sahip olduğundan emin olun
|
||
- Token'ın süresi dolmamış olduğundan emin olun
|
||
|
||
**Problem: "404 Not Found" hatası**
|
||
- Registry URL'inin doğru olduğundan emin olun
|
||
- `@alper:registry=https://gitea.hibna.com.tr/api/packages/hibna/npm/`
|
||
- İnternet bağlantınızı kontrol edin
|
||
- Gitea sunucusunun erişilebilir olduğundan emin olun
|
||
|
||
**Problem: "Cannot find module '@alper/video-player'"**
|
||
- `node_modules` klasörünü silin ve yeniden yükleyin:
|
||
```bash
|
||
rm -rf node_modules package-lock.json
|
||
npm install
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Hızlı Başlangıç
|
||
|
||
### Temel Kullanım
|
||
|
||
```tsx
|
||
import { VideoPlayer } from '@alper/video-player'
|
||
import '@alper/video-player/styles.css'
|
||
|
||
function App() {
|
||
return (
|
||
<VideoPlayer
|
||
src="https://example.com/video.mp4"
|
||
poster="https://example.com/poster.jpg"
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### HLS Streaming ile Kullanım
|
||
|
||
```tsx
|
||
<VideoPlayer
|
||
src="https://example.com/stream/master.m3u8"
|
||
autoplay={false}
|
||
subtitles={[
|
||
{ src: '/subtitles/en.vtt', lang: 'en', label: 'English', default: true },
|
||
{ src: '/subtitles/tr.srt', lang: 'tr', label: 'Türkçe' }
|
||
]}
|
||
/>
|
||
```
|
||
|
||
### Event Handler'lar ile Kullanım
|
||
|
||
```tsx
|
||
<VideoPlayer
|
||
src="https://example.com/video.mp4"
|
||
onPlay={() => console.log('Video oynatılmaya başladı')}
|
||
onPause={() => console.log('Video durakladı')}
|
||
onEnded={() => console.log('Video bitti')}
|
||
onTimeUpdate={(time) => console.log('Geçerli zaman:', time)}
|
||
onError={(error) => console.error('Video hatası:', error)}
|
||
/>
|
||
```
|
||
|
||
### Tema Özelleştirmesi
|
||
|
||
```tsx
|
||
<VideoPlayer
|
||
src="https://example.com/video.mp4"
|
||
theme={{
|
||
primaryColor: '#ef4444',
|
||
accentColor: '#dc2626',
|
||
backgroundColor: '#000000',
|
||
textColor: '#ffffff',
|
||
}}
|
||
/>
|
||
```
|
||
|
||
---
|
||
|
||
## 🏗️ Mimari ve Yapı
|
||
|
||
### Proje Yapısı
|
||
|
||
```
|
||
video-player/
|
||
├── src/
|
||
│ ├── components/ # React UI bileşenleri
|
||
│ │ ├── controls/ # Kontrol butonları (Play, Pause, vb.)
|
||
│ │ │ ├── PlayPauseButton.tsx
|
||
│ │ │ ├── ProgressBar.tsx
|
||
│ │ │ ├── VolumeControl.tsx
|
||
│ │ │ ├── TimeDisplay.tsx
|
||
│ │ │ ├── FullscreenButton.tsx
|
||
│ │ │ ├── PIPButton.tsx
|
||
│ │ │ ├── SettingsButton.tsx
|
||
│ │ │ └── CenterPlayButton.tsx
|
||
│ │ ├── menus/ # Ayar menüleri
|
||
│ │ │ └── SettingsMenu.tsx
|
||
│ │ ├── overlays/ # Overlay bileşenleri
|
||
│ │ │ └── LoadingSpinner.tsx
|
||
│ │ ├── VideoPlayer.tsx # Ana wrapper bileşen
|
||
│ │ ├── VideoElement.tsx # Video element handler
|
||
│ │ └── ControlsLayer.tsx # Kontrol katmanı
|
||
│ ├── contexts/ # React Context
|
||
│ │ └── PlayerContext.tsx
|
||
│ ├── hooks/ # Custom React hooks
|
||
│ │ ├── useKeyboardShortcuts.ts
|
||
│ │ └── useTouchGestures.ts
|
||
│ ├── utils/ # Utility fonksiyonlar
|
||
│ │ ├── hlsSetup.ts # HLS.js setup
|
||
│ │ ├── hlsLoader.ts # HLS.js lazy loading
|
||
│ │ ├── hlsControl.ts # HLS kalite/ses kontrolü
|
||
│ │ ├── rtmpSetup.ts # FLV.js setup
|
||
│ │ ├── rtmpLoader.ts # FLV.js lazy loading
|
||
│ │ ├── videoProtocol.ts # Protokol tespiti
|
||
│ │ ├── m3u8Parser.ts # M3U8 manifest parsing
|
||
│ │ ├── subtitles.ts # SRT/VTT altyazı işleme
|
||
│ │ ├── corsHelper.ts # CORS doğrulama
|
||
│ │ ├── time.ts # Zaman formatlama
|
||
│ │ └── polyfills.ts # Tarayıcı uyumluluk
|
||
│ ├── icons/ # SVG icon bileşenleri
|
||
│ │ └── index.tsx
|
||
│ ├── styles/ # CSS değişkenleri
|
||
│ │ └── variables.css
|
||
│ ├── i18n/ # Uluslararasılaştırma
|
||
│ │ └── index.ts # İngilizce & Türkçe
|
||
│ ├── types/ # TypeScript tanımları
|
||
│ │ └── index.ts
|
||
│ └── index.ts # Ana export dosyası
|
||
├── examples/ # Demo uygulaması
|
||
│ ├── App.tsx
|
||
│ └── main.tsx
|
||
├── dist/ # Build çıktısı
|
||
├── package.json
|
||
├── vite.config.ts # Geliştirme config
|
||
├── vite.config.lib.ts # Kütüphane build config
|
||
└── tsconfig.json
|
||
```
|
||
|
||
### Bileşen Hiyerarşisi
|
||
|
||
```
|
||
VideoPlayer (Main)
|
||
├── PlayerProvider (Context)
|
||
│ ├── VideoElement (Video handler)
|
||
│ │ └── <video> element
|
||
│ ├── ControlsLayer (Controls container)
|
||
│ │ ├── CenterPlayButton (Overlay)
|
||
│ │ ├── PlayPauseButton
|
||
│ │ ├── ProgressBar
|
||
│ │ ├── VolumeControl
|
||
│ │ ├── TimeDisplay
|
||
│ │ ├── SettingsButton
|
||
│ │ ├── FullscreenButton
|
||
│ │ ├── PIPButton
|
||
│ │ └── SettingsMenu (Lazy-loaded)
|
||
│ │ ├── Main Menu
|
||
│ │ ├── Speed Menu
|
||
│ │ ├── Quality Menu
|
||
│ │ ├── Subtitle Menu
|
||
│ │ └── Audio Track Menu
|
||
│ └── LoadingSpinner (Overlay)
|
||
```
|
||
|
||
### State Management
|
||
|
||
Kütüphane, **React Context API** kullanarak merkezi state yönetimi sağlar:
|
||
|
||
- **VideoState**: Video durumu (playing, currentTime, duration, vb.)
|
||
- **UIState**: UI durumu (controls visibility, menu states, vb.)
|
||
- **PlayerSettings**: Player ayarları (quality, subtitle, audioTrack, vb.)
|
||
|
||
Context yapısı, tüm child bileşenlerin state'e erişmesini ve güncellemesini sağlar.
|
||
|
||
---
|
||
|
||
## ✨ Özellikler
|
||
|
||
### Video Format Desteği
|
||
|
||
#### 1. Native HTML5 Video (MP4, WebM, OGG)
|
||
|
||
**Desteklenen Formatlar:**
|
||
- MP4 (H.264/H.265 codec)
|
||
- WebM (VP8/VP9 codec)
|
||
- OGG (Theora codec)
|
||
|
||
**Nasıl Çalışır:**
|
||
- Doğrudan `<video>` elementi ile `src` attribute kullanılır
|
||
- HTTP Range Request desteği ile progressive download
|
||
- Harici kütüphane gerektirmez
|
||
- Tüm modern tarayıcılarda native destek
|
||
|
||
**Kullanım:**
|
||
```tsx
|
||
<VideoPlayer src="https://example.com/video.mp4" />
|
||
```
|
||
|
||
#### 2. HLS Streaming (.m3u8)
|
||
|
||
**Özellikler:**
|
||
- Adaptive bitrate streaming (ABR)
|
||
- Çoklu kalite seviyeleri
|
||
- Çoklu ses parçaları
|
||
- Altyazı parça çıkarma
|
||
- Otomatik kalite seçimi
|
||
- Live stream desteği
|
||
|
||
**Kütüphane:** hls.js (opsiyonel, lazy-loaded)
|
||
|
||
**Safari Desteği:** Safari'de native HLS desteği var, kütüphane yüklenmez
|
||
|
||
**Loading Stratejisi:**
|
||
1. HLS source tespit edilir (.m3u8 uzantısı)
|
||
2. Safari ise → Native HLS kullanılır
|
||
3. Diğer tarayıcılarda:
|
||
- Önce NPM package'dan hls.js yüklenir
|
||
- Başarısız olursa CDN'den fallback (jsdelivr)
|
||
- MSE desteği kontrol edilir
|
||
|
||
**CDN Fallback:**
|
||
```
|
||
https://cdn.jsdelivr.net/npm/hls.js@1.5.13
|
||
```
|
||
|
||
**Kullanım:**
|
||
```tsx
|
||
<VideoPlayer src="https://example.com/stream/master.m3u8" />
|
||
```
|
||
|
||
**Manifest Örneği:**
|
||
```m3u8
|
||
#EXTM3U
|
||
#EXT-X-VERSION:3
|
||
|
||
# Kalite seviyeleri
|
||
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1920x1080
|
||
1080p.m3u8
|
||
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=1280x720
|
||
720p.m3u8
|
||
#EXT-X-STREAM-INF:BANDWIDTH=500000,RESOLUTION=854x480
|
||
480p.m3u8
|
||
|
||
# Ses parçaları
|
||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en",URI="audio_en.m3u8"
|
||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Turkish",LANGUAGE="tr",URI="audio_tr.m3u8"
|
||
```
|
||
|
||
**Hata Yönetimi:**
|
||
- Network hataları → Otomatik yeniden bağlanma
|
||
- Media hataları → HLS instance yeniden başlatma
|
||
- Fatal hataları → Error state'e düşme
|
||
|
||
#### 3. RTMP/FLV/IPTV Streaming
|
||
|
||
**Desteklenen Protokoller:**
|
||
- RTMP (rtmp://)
|
||
- RTMPS (rtmps://)
|
||
- RTMPT (rtmpt://)
|
||
- HTTP-FLV (.flv veya flv? query)
|
||
- MPEG-TS (.ts) - IPTV streams
|
||
|
||
**Kütüphane:** flv.js (opsiyonel, lazy-loaded)
|
||
|
||
**Özellikler:**
|
||
- Live stream desteği
|
||
- Hata recovery
|
||
- İstatistik takibi
|
||
- Low latency
|
||
|
||
**Loading Stratejisi:**
|
||
1. RTMP/FLV source tespit edilir
|
||
2. Önce NPM package'dan flv.js yüklenir
|
||
3. Başarısız olursa CDN'den fallback
|
||
4. MSE desteği kontrol edilir
|
||
|
||
**CDN Fallback:**
|
||
```
|
||
https://cdn.jsdelivr.net/npm/flv.js@1.6.2
|
||
```
|
||
|
||
**ÖNEMLİ NOT:** Doğrudan RTMP URL'leri tarayıcıda çalışmaz. HTTP-FLV proxy gerektirir.
|
||
|
||
**Kullanım:**
|
||
```tsx
|
||
// HTTP-FLV
|
||
<VideoPlayer src="https://example.com/live.flv" />
|
||
|
||
// RTMP (HTTP-FLV proxy gerektirir)
|
||
<VideoPlayer src="rtmp://example.com/live/stream" />
|
||
|
||
// IPTV (MPEG-TS)
|
||
<VideoPlayer
|
||
src="http://favoritv65.xyz:8080/live/username/password/98925.ts"
|
||
poster="http://example.com/channel-logo.png"
|
||
/>
|
||
```
|
||
|
||
**HTTP-FLV Proxy Örneği (Node.js):**
|
||
```javascript
|
||
// RTMP stream'i HTTP-FLV'ye dönüştüren proxy
|
||
const ffmpeg = require('fluent-ffmpeg');
|
||
|
||
ffmpeg('rtmp://source.com/live/stream')
|
||
.outputOptions([
|
||
'-c copy',
|
||
'-f flv'
|
||
])
|
||
.pipe(res);
|
||
```
|
||
|
||
#### 4. DASH Support (Planlanıyor)
|
||
|
||
**Durum:** Protokol tespit ediliyor ancak henüz implement edilmemiş
|
||
|
||
**.mpd URL'leri tespit edilir ve hata mesajı döner:**
|
||
```
|
||
DASH streaming is detected but not yet supported
|
||
```
|
||
|
||
**Gelecek implementasyon** dash.js kütüphanesi ile planlanıyor.
|
||
|
||
### Protocol Detection System
|
||
|
||
**Dosya:** `src/utils/videoProtocol.ts`
|
||
|
||
Video URL'inden otomatik protokol tespiti:
|
||
|
||
```typescript
|
||
function detectVideoProtocol(url: string): {
|
||
protocol: VideoProtocol
|
||
isLive: boolean
|
||
needsSpecialPlayer: boolean
|
||
}
|
||
```
|
||
|
||
**Tespit Mantığı:**
|
||
|
||
1. **HLS:** `.m3u8` uzantısı
|
||
2. **RTMP:** `rtmp://`, `rtmps://`, `rtmpt://`, `rtmpe://` protokolleri
|
||
3. **FLV:** `.flv` uzantısı veya URL'de `flv?` var
|
||
4. **DASH:** `.mpd` uzantısı
|
||
5. **Native:** Diğer her şey (MP4, WebM, vb.)
|
||
|
||
**Live Stream Tespiti:**
|
||
- RTMP/FLV → Her zaman live
|
||
- HLS → Manifest'te `#EXT-X-PLAYLIST-TYPE:VOD` yoksa live
|
||
- Diğerleri → VOD
|
||
|
||
**Kullanım:**
|
||
```typescript
|
||
import { detectVideoProtocol } from '@alper/video-player'
|
||
|
||
const info = detectVideoProtocol('https://example.com/stream.m3u8')
|
||
console.log(info.protocol) // 'hls'
|
||
console.log(info.isLive) // true/false
|
||
console.log(info.needsSpecialPlayer) // true
|
||
```
|
||
|
||
### Altyazı Sistemi
|
||
|
||
**Dosyalar:**
|
||
- `src/utils/subtitles.ts` - Altyazı işleme
|
||
- `src/components/VideoElement.tsx` - Altyazı entegrasyonu
|
||
|
||
#### Desteklenen Formatlar
|
||
|
||
**1. WebVTT (.vtt):**
|
||
- Native tarayıcı desteği
|
||
- Doğrudan `<track>` elementi ile kullanılır
|
||
|
||
**2. SRT (.srt):**
|
||
- WebVTT'ye otomatik dönüştürme
|
||
- Blob URL ile tarayıcıya verilir
|
||
|
||
#### Özellikler
|
||
|
||
- ✅ Çoklu altyazı parçaları
|
||
- ✅ Dil seçimi
|
||
- ✅ Varsayılan altyazı desteği
|
||
- ✅ Yüklendiğinde otomatik etkinleştirme
|
||
- ✅ SRT → VTT dönüşümü
|
||
- ✅ HLS manifest'ten altyazı çıkarma
|
||
- ✅ Manuel ve HLS altyazı birleştirme
|
||
|
||
#### SRT → VTT Dönüşümü
|
||
|
||
**SRT Format:**
|
||
```srt
|
||
1
|
||
00:00:01,000 --> 00:00:04,000
|
||
Merhaba dünya
|
||
|
||
2
|
||
00:00:05,000 --> 00:00:08,000
|
||
İkinci altyazı
|
||
```
|
||
|
||
**WebVTT Format (dönüştürülmüş):**
|
||
```vtt
|
||
WEBVTT
|
||
|
||
00:00:01.000 --> 00:00:04.000
|
||
Merhaba dünya
|
||
|
||
00:00:05.000 --> 00:00:08.000
|
||
İkinci altyazı
|
||
```
|
||
|
||
**Dönüşüm Fonksiyonu:**
|
||
```typescript
|
||
import { parseSRT, createSubtitleBlobURL } from '@alper/video-player'
|
||
|
||
const srtContent = '...' // SRT içeriği
|
||
const vttContent = parseSRT(srtContent)
|
||
const blobUrl = createSubtitleBlobURL(vttContent)
|
||
// blobUrl → blob:http://localhost:5173/abc123...
|
||
```
|
||
|
||
#### Implementation Akışı
|
||
|
||
1. Altyazı dosyaları fetch edilir
|
||
2. SRT ise WebVTT'ye dönüştürülür
|
||
3. Blob URL'leri oluşturulur
|
||
4. `<track>` elementleri dinamik olarak eklenir
|
||
5. Track mode Settings context üzerinden kontrol edilir
|
||
6. Component unmount'ta blob URL'leri temizlenir
|
||
|
||
#### Kullanım
|
||
|
||
**Temel Kullanım:**
|
||
```tsx
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
subtitles={[
|
||
{
|
||
src: '/subtitles/english.vtt',
|
||
lang: 'en',
|
||
label: 'English',
|
||
default: true
|
||
},
|
||
{
|
||
src: '/subtitles/turkish.srt',
|
||
lang: 'tr',
|
||
label: 'Türkçe'
|
||
}
|
||
]}
|
||
/>
|
||
```
|
||
|
||
**HLS ile Otomatik Altyazı (manifest'ten):**
|
||
```tsx
|
||
<VideoPlayer src="stream.m3u8" />
|
||
// HLS manifest'te altyazılar varsa otomatik ekler
|
||
```
|
||
|
||
**Programmatic Control:**
|
||
```tsx
|
||
import { usePlayerContext } from '@alper/video-player'
|
||
|
||
function CustomSubtitleToggle() {
|
||
const { settings, setSubtitle } = usePlayerContext()
|
||
|
||
return (
|
||
<button onClick={() => setSubtitle(settings.subtitle ? null : mySubtitle)}>
|
||
{settings.subtitle ? 'Altyazıyı Kapat' : 'Altyazıyı Aç'}
|
||
</button>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Ses Parça Yönetimi
|
||
|
||
**Dosyalar:**
|
||
- `src/utils/hlsLoader.ts` - HLS ses parçası çıkarma
|
||
- `src/utils/hlsControl.ts` - Ses parçası kontrolü
|
||
|
||
#### Özellikler
|
||
|
||
- ✅ HLS manifest'ten ses parçası çıkarma
|
||
- ✅ Oynatma sırasında değiştirme
|
||
- ✅ Dil tespiti
|
||
- ✅ Varsayılan parça seçimi
|
||
- ✅ Otomatik seçim desteği
|
||
|
||
#### HLS Manifest'ten Çıkarma
|
||
|
||
**Manifest Örneği:**
|
||
```m3u8
|
||
#EXTM3U
|
||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en",URI="audio_en.m3u8"
|
||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Turkish",LANGUAGE="tr",URI="audio_tr.m3u8"
|
||
```
|
||
|
||
**Çıkarılan AudioTrack:**
|
||
```typescript
|
||
{
|
||
name: "English",
|
||
language: "en",
|
||
url: "audio_en.m3u8",
|
||
groupId: "audio",
|
||
default: true,
|
||
autoselect: true
|
||
}
|
||
```
|
||
|
||
#### Kullanım
|
||
|
||
**Programmatic Control:**
|
||
```tsx
|
||
import { usePlayerContext } from '@alper/video-player'
|
||
|
||
function AudioTrackSelector() {
|
||
const { settings, setAudioTrack } = usePlayerContext()
|
||
const [tracks] = useState<AudioTrack[]>([...]) // HLS'den alınan parçalar
|
||
|
||
return (
|
||
<select
|
||
value={settings.audioTrack?.name}
|
||
onChange={(e) => {
|
||
const track = tracks.find(t => t.name === e.target.value)
|
||
setAudioTrack(track || null)
|
||
}}
|
||
>
|
||
{tracks.map(track => (
|
||
<option key={track.name} value={track.name}>
|
||
{track.label} ({track.language})
|
||
</option>
|
||
))}
|
||
</select>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Kalite Seviyesi Kontrolü
|
||
|
||
**Dosyalar:**
|
||
- `src/utils/hlsLoader.ts` - Kalite çıkarma
|
||
- `src/utils/hlsControl.ts` - Kalite kontrolü
|
||
|
||
#### Özellikler
|
||
|
||
- ✅ HLS'den çoklu kalite seviyeleri
|
||
- ✅ Manuel kalite seçimi
|
||
- ✅ Otomatik kalite (adaptive bitrate)
|
||
- ✅ Çözünürlük bazlı etiketleme (1080p, 720p, vb.)
|
||
- ✅ Bitrate bilgisi gösterimi
|
||
- ✅ Seçilen kalitenin kalıcılığı
|
||
|
||
#### Kalite Tespiti
|
||
|
||
**HLS Manifest:**
|
||
```m3u8
|
||
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
|
||
1080p.m3u8
|
||
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720
|
||
720p.m3u8
|
||
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=854x480
|
||
480p.m3u8
|
||
```
|
||
|
||
**Çıkarılan VideoQuality:**
|
||
```typescript
|
||
[
|
||
{
|
||
label: "1080p",
|
||
height: 1080,
|
||
width: 1920,
|
||
bitrate: 5000000,
|
||
levelIndex: 0,
|
||
url: "1080p.m3u8"
|
||
},
|
||
{
|
||
label: "720p",
|
||
height: 720,
|
||
width: 1280,
|
||
bitrate: 2500000,
|
||
levelIndex: 1,
|
||
url: "720p.m3u8"
|
||
},
|
||
// ...
|
||
]
|
||
```
|
||
|
||
#### Kalite Değiştirme
|
||
|
||
**Otomatik Kalite (Adaptive Bitrate):**
|
||
```typescript
|
||
import { setHlsQualityLevel } from '@alper/video-player'
|
||
|
||
setHlsQualityLevel(hlsInstance, null) // Auto
|
||
```
|
||
|
||
**Manuel Kalite:**
|
||
```typescript
|
||
setHlsQualityLevel(hlsInstance, {
|
||
label: "1080p",
|
||
levelIndex: 0,
|
||
// ...
|
||
})
|
||
```
|
||
|
||
#### Kullanım
|
||
|
||
**Settings Menu'den:**
|
||
Kullanıcı Settings → Quality menüsünden kalite seçer.
|
||
|
||
**Programmatic Control:**
|
||
```tsx
|
||
import { usePlayerContext } from '@alper/video-player'
|
||
|
||
function QualitySelector() {
|
||
const { settings, setQuality } = usePlayerContext()
|
||
const [qualities] = useState<VideoQuality[]>([...]) // HLS'den alınan
|
||
|
||
return (
|
||
<select
|
||
value={settings.quality?.label || 'auto'}
|
||
onChange={(e) => {
|
||
if (e.target.value === 'auto') {
|
||
setQuality(null) // Auto
|
||
} else {
|
||
const quality = qualities.find(q => q.label === e.target.value)
|
||
setQuality(quality || null)
|
||
}
|
||
}}
|
||
>
|
||
<option value="auto">Auto</option>
|
||
{qualities.map(q => (
|
||
<option key={q.label} value={q.label}>
|
||
{q.label} ({(q.bitrate! / 1000000).toFixed(1)} Mbps)
|
||
</option>
|
||
))}
|
||
</select>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Klavye Kısayolları
|
||
|
||
**Dosya:** `src/hooks/useKeyboardShortcuts.ts`
|
||
|
||
#### Tüm Kısayollar
|
||
|
||
| Tuş | Aksiyon | Açıklama |
|
||
|-----|---------|----------|
|
||
| `Space` | Oynat/Duraklat | Videoyu oynatır veya duraklatır |
|
||
| `K` | Oynat/Duraklat | Space ile aynı |
|
||
| `←` (Sol Ok) | -5 saniye | 5 saniye geri sar |
|
||
| `→` (Sağ Ok) | +5 saniye | 5 saniye ileri sar |
|
||
| `J` | -10 saniye | 10 saniye geri sar |
|
||
| `L` | +10 saniye | 10 saniye ileri sar |
|
||
| `↑` (Yukarı Ok) | Ses +10% | Sesi %10 artır |
|
||
| `↓` (Aşağı Ok) | Ses -10% | Sesi %10 azalt |
|
||
| `M` | Sessiz/Sesli | Sesi aç/kapat |
|
||
| `F` | Tam ekran | Tam ekran moduna geç/çık |
|
||
| `P` | PIP | Picture-in-Picture aç/kapat |
|
||
| `0` | 0% | Videonun başına git |
|
||
| `1` | 10% | Videonun %10'una git |
|
||
| `2` | 20% | Videonun %20'sine git |
|
||
| `3` | 30% | Videonun %30'una git |
|
||
| `4` | 40% | Videonun %40'ına git |
|
||
| `5` | 50% | Videonun ortasına git |
|
||
| `6` | 60% | Videonun %60'ına git |
|
||
| `7` | 70% | Videonun %70'ine git |
|
||
| `8` | 80% | Videonun %80'ine git |
|
||
| `9` | 90% | Videonun %90'ına git |
|
||
| `Home` | Başa git | Videonun en başına git |
|
||
| `End` | Sona git | Videonun en sonuna git |
|
||
|
||
#### Özellikler
|
||
|
||
- ✅ Input/textarea alanlarında devre dışı
|
||
- ✅ Default tarayıcı davranışını önler
|
||
- ✅ Enable/disable ile açılıp kapatılabilir
|
||
- ✅ Fullscreen'de de çalışır
|
||
|
||
#### Kullanım
|
||
|
||
**Otomatik (default):**
|
||
```tsx
|
||
<VideoPlayer src="video.mp4" />
|
||
// Klavye kısayolları otomatik aktif
|
||
```
|
||
|
||
**Manuel kontrol:**
|
||
```tsx
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
keyboardShortcuts={false} // Devre dışı
|
||
/>
|
||
```
|
||
|
||
**Custom hook ile:**
|
||
```tsx
|
||
import { useKeyboardShortcuts } from '@alper/video-player'
|
||
|
||
function MyComponent() {
|
||
const { videoRef, containerRef } = usePlayerContext()
|
||
|
||
useKeyboardShortcuts({
|
||
videoRef,
|
||
containerRef,
|
||
enabled: true
|
||
})
|
||
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### Dokunmatik Jestler
|
||
|
||
**Dosya:** `src/hooks/useTouchGestures.ts`
|
||
|
||
#### Tüm Jestler
|
||
|
||
| Jest | Aksiyon | Açıklama |
|
||
|------|---------|----------|
|
||
| Tek dokunuş (tap) | Oynat/Duraklat | Videoyu oynatır/duraklatır |
|
||
| Çift dokunuş sol | -10 saniye | 10 saniye geri sar |
|
||
| Çift dokunuş sağ | +10 saniye | 10 saniye ileri sar |
|
||
| Yatay kaydırma (swipe) | Seek | Videoyu ileri/geri sar (max 30s) |
|
||
| Dikey kaydırma (swipe) | Ses | Sesi artır/azalt |
|
||
|
||
#### Özellikler
|
||
|
||
- ✅ Tap tespiti (timeout ile)
|
||
- ✅ Double-tap tespit (pozisyon bazlı - sol/sağ)
|
||
- ✅ Görsel geri bildirim animasyonları
|
||
- ✅ Swipe mesafesi hesaplama
|
||
- ✅ Threshold değerleri (minimum hareket mesafesi)
|
||
- ✅ Mobil ve tablet uyumlu
|
||
|
||
#### Parametreler
|
||
|
||
**Tap Timeout:** 300ms (çift dokunuş için bekleme süresi)
|
||
**Swipe Threshold:** 50px (minimum swipe mesafesi)
|
||
**Double Tap Zone:** Ekran genişliğinin 1/3'ü (sol/sağ bölge)
|
||
|
||
#### Görsel Geri Bildirim
|
||
|
||
**Double Tap Animasyonu:**
|
||
- Sol tarafa çift dokunuş → "« 10s" animasyonu
|
||
- Sağ tarafa çift dokunuş → "10s »" animasyonu
|
||
- 500ms sonra kaybolur
|
||
|
||
**Seek Feedback:**
|
||
- Swipe sırasında progress bar güncellemesi
|
||
- Seek edilen zamanın gösterimi
|
||
|
||
#### Kullanım
|
||
|
||
**Otomatik (mobil cihazlarda):**
|
||
```tsx
|
||
<VideoPlayer src="video.mp4" />
|
||
// Touch jestleri otomatik aktif (touch destekli cihazlarda)
|
||
```
|
||
|
||
**Custom hook ile:**
|
||
```tsx
|
||
import { useTouchGestures } from '@alper/video-player'
|
||
|
||
function MyComponent() {
|
||
const { videoRef, containerRef } = usePlayerContext()
|
||
|
||
useTouchGestures({
|
||
videoRef,
|
||
containerRef,
|
||
onGesture: (event) => {
|
||
console.log('Jest:', event.type, event.direction)
|
||
}
|
||
})
|
||
|
||
// ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📚 API Referansı
|
||
|
||
### VideoPlayer Props
|
||
|
||
```typescript
|
||
interface VideoPlayerProps {
|
||
// Video kaynağı (zorunlu)
|
||
src: string
|
||
|
||
// Poster/thumbnail resmi
|
||
poster?: string
|
||
|
||
// Otomatik oynat
|
||
autoplay?: boolean
|
||
|
||
// Döngü
|
||
loop?: boolean
|
||
|
||
// Başlangıçta sessiz
|
||
muted?: boolean
|
||
|
||
// Kontrolleri göster (default: true)
|
||
controls?: boolean
|
||
|
||
// Altyazı parçaları
|
||
subtitles?: SubtitleTrack[]
|
||
|
||
// Tema özelleştirme
|
||
theme?: PlayerTheme
|
||
|
||
// Dil (i18n için)
|
||
language?: string
|
||
|
||
// Klavye kısayolları (default: true)
|
||
keyboardShortcuts?: boolean
|
||
|
||
// Picture-in-Picture butonu (default: true)
|
||
pictureInPicture?: boolean
|
||
|
||
// CSS class
|
||
className?: string
|
||
|
||
// Inline styles
|
||
style?: CSSProperties
|
||
|
||
// Event handlers
|
||
onPlay?: () => void
|
||
onPause?: () => void
|
||
onEnded?: () => void
|
||
onTimeUpdate?: (currentTime: number) => void
|
||
onVolumeChange?: (volume: number) => void
|
||
onError?: (error: Error) => void
|
||
onLoadedMetadata?: () => void
|
||
onSeeking?: () => void
|
||
onSeeked?: () => void
|
||
}
|
||
```
|
||
|
||
#### Prop Detayları
|
||
|
||
**src** (zorunlu)
|
||
- Tip: `string`
|
||
- Video dosyasının URL'i
|
||
- Desteklenen formatlar:
|
||
- MP4: `https://example.com/video.mp4`
|
||
- WebM: `https://example.com/video.webm`
|
||
- HLS: `https://example.com/stream.m3u8`
|
||
- RTMP: `rtmp://example.com/live/stream`
|
||
- FLV: `https://example.com/stream.flv`
|
||
|
||
**poster**
|
||
- Tip: `string | undefined`
|
||
- Video yüklenmeden önce gösterilecek resim
|
||
- Önerilen format: JPG, PNG, WebP
|
||
- Önerilen boyut: Video ile aynı aspect ratio
|
||
|
||
**autoplay**
|
||
- Tip: `boolean | undefined`
|
||
- Default: `false`
|
||
- NOT: Çoğu tarayıcı, `muted: true` olmadan autoplay'e izin vermez
|
||
|
||
**loop**
|
||
- Tip: `boolean | undefined`
|
||
- Default: `false`
|
||
- Video bittiğinde başa döner
|
||
|
||
**muted**
|
||
- Tip: `boolean | undefined`
|
||
- Default: `false`
|
||
- Başlangıçta sessiz moda alır
|
||
|
||
**controls**
|
||
- Tip: `boolean | undefined`
|
||
- Default: `true`
|
||
- `false` yaparsanız tüm kontroller gizlenir
|
||
|
||
**subtitles**
|
||
- Tip: `SubtitleTrack[] | undefined`
|
||
- Altyazı parçaları dizisi
|
||
- WebVTT (.vtt) ve SRT (.srt) desteklenir
|
||
|
||
**theme**
|
||
- Tip: `PlayerTheme | undefined`
|
||
- CSS variable'ları override eder
|
||
- Tüm alanlar opsiyonel
|
||
|
||
**language**
|
||
- Tip: `string | undefined`
|
||
- Default: Tarayıcı dili
|
||
- Desteklenen: `'en'`, `'tr'`
|
||
|
||
**keyboardShortcuts**
|
||
- Tip: `boolean | undefined`
|
||
- Default: `true`
|
||
- Klavye kısayollarını aktif/deaktif eder
|
||
|
||
**pictureInPicture**
|
||
- Tip: `boolean | undefined`
|
||
- Default: `true`
|
||
- PIP butonunu gösterir/gizler
|
||
- Tarayıcı desteği yoksa otomatik gizlenir
|
||
|
||
**onTimeUpdate**
|
||
- Tip: `(currentTime: number) => void | undefined`
|
||
- Video oynarken sürekli çağrılır (~250ms aralıklarla)
|
||
- Analytics ve progress tracking için kullanışlı
|
||
|
||
**onError**
|
||
- Tip: `(error: Error) => void | undefined`
|
||
- Video yükleme veya oynatma hatalarında çağrılır
|
||
- CORS hataları için `isCORSError(error)` kullanabilirsiniz
|
||
|
||
### Type Definitions
|
||
|
||
#### SubtitleTrack
|
||
|
||
```typescript
|
||
interface SubtitleTrack {
|
||
// Altyazı dosyasının URL'i (.vtt veya .srt)
|
||
src: string
|
||
|
||
// Dil kodu (ISO 639-1)
|
||
lang: string
|
||
|
||
// Kullanıcıya gösterilecek etiket
|
||
label: string
|
||
|
||
// Varsayılan altyazı olarak işaretle
|
||
default?: boolean
|
||
}
|
||
```
|
||
|
||
**Örnek:**
|
||
```typescript
|
||
const subtitles: SubtitleTrack[] = [
|
||
{
|
||
src: '/subtitles/english.vtt',
|
||
lang: 'en',
|
||
label: 'English',
|
||
default: true
|
||
},
|
||
{
|
||
src: '/subtitles/turkish.srt',
|
||
lang: 'tr',
|
||
label: 'Türkçe'
|
||
}
|
||
]
|
||
```
|
||
|
||
#### AudioTrack
|
||
|
||
```typescript
|
||
interface AudioTrack {
|
||
// Ses parçasının adı
|
||
name: string
|
||
|
||
// Dil kodu
|
||
language: string
|
||
|
||
// HLS manifest'teki URL
|
||
url: string
|
||
|
||
// Group ID (HLS)
|
||
groupId: string
|
||
|
||
// Varsayılan parça mı
|
||
default?: boolean
|
||
|
||
// Otomatik seçilsin mi
|
||
autoselect?: boolean
|
||
}
|
||
```
|
||
|
||
**Örnek:**
|
||
```typescript
|
||
const audioTrack: AudioTrack = {
|
||
name: "English Stereo",
|
||
language: "en",
|
||
url: "audio_en.m3u8",
|
||
groupId: "audio",
|
||
default: true,
|
||
autoselect: true
|
||
}
|
||
```
|
||
|
||
#### VideoQuality
|
||
|
||
```typescript
|
||
interface VideoQuality {
|
||
// Yükseklik (piksel)
|
||
height?: number
|
||
|
||
// Kullanıcıya gösterilecek etiket (örn: "1080p")
|
||
label: string
|
||
|
||
// Kalite stream URL'i (HLS)
|
||
url?: string
|
||
|
||
// Genişlik (piksel)
|
||
width?: number
|
||
|
||
// Bitrate (bits/second)
|
||
bitrate?: number
|
||
|
||
// HLS.js level index
|
||
levelIndex?: number
|
||
}
|
||
```
|
||
|
||
**Örnek:**
|
||
```typescript
|
||
const quality: VideoQuality = {
|
||
label: "1080p",
|
||
height: 1080,
|
||
width: 1920,
|
||
bitrate: 5000000,
|
||
levelIndex: 0,
|
||
url: "1080p.m3u8"
|
||
}
|
||
```
|
||
|
||
#### PlayerTheme
|
||
|
||
```typescript
|
||
interface PlayerTheme {
|
||
// Ana renk (progress bar, butonlar)
|
||
primaryColor?: string
|
||
|
||
// Vurgu rengi (hover states)
|
||
accentColor?: string
|
||
|
||
// Arka plan rengi
|
||
backgroundColor?: string
|
||
|
||
// Metin rengi
|
||
textColor?: string
|
||
}
|
||
```
|
||
|
||
**Örnek:**
|
||
```typescript
|
||
const theme: PlayerTheme = {
|
||
primaryColor: '#ef4444', // Kırmızı
|
||
accentColor: '#dc2626', // Koyu kırmızı
|
||
backgroundColor: '#000000', // Siyah
|
||
textColor: '#ffffff' // Beyaz
|
||
}
|
||
```
|
||
|
||
#### VideoState
|
||
|
||
```typescript
|
||
interface VideoState {
|
||
playing: boolean // Oynatılıyor mu
|
||
currentTime: number // Geçerli zaman (saniye)
|
||
duration: number // Toplam süre (saniye)
|
||
buffered: number // Buffered zaman (saniye)
|
||
volume: number // Ses seviyesi (0-1)
|
||
muted: boolean // Sessiz mi
|
||
playbackRate: number // Oynatma hızı (0.25-2)
|
||
fullscreen: boolean // Tam ekran mı
|
||
pictureInPicture: boolean // PIP modunda mı
|
||
loading: boolean // Yükleniyor mu
|
||
error: Error | null // Hata varsa
|
||
seeking: boolean // Seek ediliyor mu
|
||
}
|
||
```
|
||
|
||
#### UIState
|
||
|
||
```typescript
|
||
interface UIState {
|
||
controlsVisible: boolean // Kontroller görünür mü
|
||
settingsOpen: boolean // Ayarlar menüsü açık mı
|
||
volumeControlOpen: boolean // Ses kontrolü açık mı
|
||
qualityMenuOpen: boolean // Kalite menüsü açık mı
|
||
subtitleMenuOpen: boolean // Altyazı menüsü açık mı
|
||
}
|
||
```
|
||
|
||
#### PlayerSettings
|
||
|
||
```typescript
|
||
interface PlayerSettings {
|
||
quality: VideoQuality | null // Seçili kalite (null = auto)
|
||
subtitle: SubtitleTrack | null // Seçili altyazı (null = off)
|
||
audioTrack: AudioTrack | null // Seçili ses parçası
|
||
playbackRate: number // Oynatma hızı
|
||
}
|
||
```
|
||
|
||
#### PlayerContextValue
|
||
|
||
```typescript
|
||
interface PlayerContextValue {
|
||
// State
|
||
videoState: VideoState
|
||
uiState: UIState
|
||
settings: PlayerSettings
|
||
|
||
// Refs
|
||
videoRef: MutableRefObject<HTMLVideoElement | null>
|
||
containerRef: MutableRefObject<HTMLDivElement | null>
|
||
|
||
// Video kontrol fonksiyonları
|
||
play: () => void
|
||
pause: () => void
|
||
togglePlay: () => void
|
||
seek: (time: number) => void
|
||
setVolume: (volume: number) => void
|
||
toggleMute: () => void
|
||
setPlaybackRate: (rate: number) => void
|
||
|
||
// Fullscreen & PIP
|
||
toggleFullscreen: () => void
|
||
togglePictureInPicture: () => void
|
||
|
||
// UI kontrolleri
|
||
showControls: () => void
|
||
hideControls: () => void
|
||
toggleSettings: () => void
|
||
|
||
// Ayarlar
|
||
setQuality: (quality: VideoQuality | null) => void
|
||
setSubtitle: (subtitle: SubtitleTrack | null) => void
|
||
setAudioTrack: (audioTrack: AudioTrack | null) => void
|
||
}
|
||
```
|
||
|
||
### Exported Components
|
||
|
||
```typescript
|
||
// Ana bileşen
|
||
import { VideoPlayer } from '@alper/video-player'
|
||
```
|
||
|
||
### Exported Hooks
|
||
|
||
```typescript
|
||
// Player context hook
|
||
import { usePlayerContext } from '@alper/video-player'
|
||
|
||
// Klavye kısayolları hook'u
|
||
import { useKeyboardShortcuts } from '@alper/video-player'
|
||
|
||
// Dokunmatik jest hook'u
|
||
import { useTouchGestures } from '@alper/video-player'
|
||
```
|
||
|
||
**usePlayerContext Kullanımı:**
|
||
```tsx
|
||
function CustomControl() {
|
||
const {
|
||
videoState,
|
||
play,
|
||
pause,
|
||
seek,
|
||
setVolume
|
||
} = usePlayerContext()
|
||
|
||
return (
|
||
<div>
|
||
<button onClick={play}>Oynat</button>
|
||
<button onClick={pause}>Duraklat</button>
|
||
<button onClick={() => seek(30)}>30s İleri</button>
|
||
<span>{videoState.currentTime}s / {videoState.duration}s</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// PlayerProvider içinde kullanılmalı
|
||
<VideoPlayer src="video.mp4">
|
||
<CustomControl />
|
||
</VideoPlayer>
|
||
```
|
||
|
||
### Exported Utilities
|
||
|
||
```typescript
|
||
// Zaman formatlama
|
||
import { formatTime, parseTime } from '@alper/video-player'
|
||
|
||
formatTime(125) // "2:05"
|
||
formatTime(3665) // "1:01:05"
|
||
parseTime("2:05") // 125
|
||
parseTime("1:01:05") // 3665
|
||
|
||
// Altyazı utilities
|
||
import {
|
||
parseSRT,
|
||
createSubtitleBlobURL,
|
||
fetchSubtitle
|
||
} from '@alper/video-player'
|
||
|
||
const srtContent = "1\n00:00:01,000 --> 00:00:04,000\nMerhaba"
|
||
const vttContent = parseSRT(srtContent)
|
||
const blobUrl = createSubtitleBlobURL(vttContent)
|
||
const subtitle = await fetchSubtitle('/subtitle.srt')
|
||
|
||
// CORS helpers
|
||
import {
|
||
validateVideoURL,
|
||
getCORSErrorMessage,
|
||
isCORSError,
|
||
checkVideoCORS
|
||
} from '@alper/video-player'
|
||
|
||
const validation = validateVideoURL(url)
|
||
if (!validation.valid) {
|
||
console.error(validation.error)
|
||
}
|
||
|
||
const corsCheck = await checkVideoCORS(url)
|
||
if (!corsCheck.supported) {
|
||
console.error(corsCheck.error)
|
||
}
|
||
|
||
if (isCORSError(error)) {
|
||
const message = getCORSErrorMessage(error, videoUrl)
|
||
console.log(message)
|
||
}
|
||
|
||
// i18n
|
||
import {
|
||
getTranslations,
|
||
detectBrowserLanguage,
|
||
translations
|
||
} from '@alper/video-player'
|
||
|
||
const lang = detectBrowserLanguage() // "tr", "en", vb.
|
||
const t = getTranslations('tr')
|
||
console.log(t.subtitles) // "Altyazı"
|
||
console.log(translations.tr.quality) // "Kalite"
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 Kullanım Örnekleri
|
||
|
||
### Temel MP4 Oynatma
|
||
|
||
```tsx
|
||
import { VideoPlayer } from '@alper/video-player'
|
||
import '@alper/video-player/styles.css'
|
||
|
||
function App() {
|
||
return (
|
||
<VideoPlayer
|
||
src="https://example.com/video.mp4"
|
||
poster="https://example.com/poster.jpg"
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### HLS Streaming ile Altyazı
|
||
|
||
```tsx
|
||
<VideoPlayer
|
||
src="https://example.com/stream/master.m3u8"
|
||
subtitles={[
|
||
{
|
||
src: '/subtitles/english.vtt',
|
||
lang: 'en',
|
||
label: 'English',
|
||
default: true
|
||
},
|
||
{
|
||
src: '/subtitles/turkish.srt',
|
||
lang: 'tr',
|
||
label: 'Türkçe'
|
||
}
|
||
]}
|
||
theme={{
|
||
primaryColor: '#3b82f6',
|
||
accentColor: '#2563eb'
|
||
}}
|
||
/>
|
||
```
|
||
|
||
### Event Tracking
|
||
|
||
```tsx
|
||
function VideoWithAnalytics() {
|
||
const handlePlay = () => {
|
||
// Analytics gönder
|
||
analytics.track('video_play')
|
||
}
|
||
|
||
const handleTimeUpdate = (time: number) => {
|
||
// Her 30 saniyede milestone kaydet
|
||
if (time % 30 === 0) {
|
||
analytics.track('video_progress', { seconds: time })
|
||
}
|
||
}
|
||
|
||
const handleEnded = () => {
|
||
analytics.track('video_completed')
|
||
}
|
||
|
||
return (
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
onPlay={handlePlay}
|
||
onTimeUpdate={handleTimeUpdate}
|
||
onEnded={handleEnded}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### RTMP Live Stream
|
||
|
||
```tsx
|
||
// HTTP-FLV proxy üzerinden RTMP stream
|
||
<VideoPlayer
|
||
src="https://your-server.com/live/stream.flv"
|
||
autoplay
|
||
muted
|
||
/>
|
||
```
|
||
|
||
### Custom Kontroller
|
||
|
||
```tsx
|
||
import { VideoPlayer, usePlayerContext } from '@alper/video-player'
|
||
|
||
function CustomControls() {
|
||
const {
|
||
videoState,
|
||
play,
|
||
pause,
|
||
seek,
|
||
setPlaybackRate
|
||
} = usePlayerContext()
|
||
|
||
return (
|
||
<div className="custom-controls">
|
||
{videoState.playing ? (
|
||
<button onClick={pause}>⏸️ Duraklat</button>
|
||
) : (
|
||
<button onClick={play}>▶️ Oynat</button>
|
||
)}
|
||
|
||
<button onClick={() => seek(videoState.currentTime - 10)}>
|
||
⏪ 10s Geri
|
||
</button>
|
||
|
||
<button onClick={() => seek(videoState.currentTime + 10)}>
|
||
⏩ 10s İleri
|
||
</button>
|
||
|
||
<select
|
||
value={videoState.playbackRate}
|
||
onChange={(e) => setPlaybackRate(Number(e.target.value))}
|
||
>
|
||
<option value="0.5">0.5x</option>
|
||
<option value="1">Normal</option>
|
||
<option value="1.5">1.5x</option>
|
||
<option value="2">2x</option>
|
||
</select>
|
||
|
||
<span>
|
||
{formatTime(videoState.currentTime)} / {formatTime(videoState.duration)}
|
||
</span>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function App() {
|
||
return (
|
||
<VideoPlayer src="video.mp4">
|
||
<CustomControls />
|
||
</VideoPlayer>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Çoklu Video Playlist
|
||
|
||
```tsx
|
||
import { useState } from 'react'
|
||
import { VideoPlayer } from '@alper/video-player'
|
||
|
||
const videos = [
|
||
{ id: 1, src: 'video1.mp4', title: 'Video 1' },
|
||
{ id: 2, src: 'video2.mp4', title: 'Video 2' },
|
||
{ id: 3, src: 'video3.mp4', title: 'Video 3' },
|
||
]
|
||
|
||
function Playlist() {
|
||
const [currentIndex, setCurrentIndex] = useState(0)
|
||
const currentVideo = videos[currentIndex]
|
||
|
||
const handleEnded = () => {
|
||
// Sonraki videoya geç
|
||
if (currentIndex < videos.length - 1) {
|
||
setCurrentIndex(currentIndex + 1)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<VideoPlayer
|
||
key={currentVideo.id}
|
||
src={currentVideo.src}
|
||
onEnded={handleEnded}
|
||
/>
|
||
|
||
<div className="playlist">
|
||
{videos.map((video, index) => (
|
||
<button
|
||
key={video.id}
|
||
onClick={() => setCurrentIndex(index)}
|
||
className={index === currentIndex ? 'active' : ''}
|
||
>
|
||
{video.title}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### CORS Hata Yönetimi
|
||
|
||
```tsx
|
||
import { VideoPlayer, isCORSError, getCORSErrorMessage } from '@alper/video-player'
|
||
import { useState } from 'react'
|
||
|
||
function VideoWithCORSHandling() {
|
||
const [error, setError] = useState<string | null>(null)
|
||
const videoUrl = 'https://example.com/video.mp4'
|
||
|
||
const handleError = (err: Error) => {
|
||
if (isCORSError(err)) {
|
||
const message = getCORSErrorMessage(err, videoUrl)
|
||
setError(message)
|
||
console.error('CORS Hatası:', message)
|
||
} else {
|
||
setError(err.message)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<VideoPlayer
|
||
src={videoUrl}
|
||
onError={handleError}
|
||
/>
|
||
|
||
{error && (
|
||
<div className="error-message">
|
||
<h3>Video Yükleme Hatası</h3>
|
||
<p>{error}</p>
|
||
<p>Çözüm: Video sunucunuzda CORS headers ekleyin:</p>
|
||
<pre>
|
||
Access-Control-Allow-Origin: *{'\n'}
|
||
Access-Control-Allow-Methods: GET, HEAD{'\n'}
|
||
Access-Control-Allow-Headers: Range
|
||
</pre>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Gelişmiş Kullanım
|
||
|
||
### Feature Detection
|
||
|
||
```typescript
|
||
import {
|
||
hasNativeHLS,
|
||
hasMSE,
|
||
hasPIP,
|
||
hasFullscreen,
|
||
hasTouch,
|
||
isIOSSafari,
|
||
hasVolumeControl
|
||
} from '@alper/video-player'
|
||
|
||
// Safari'de native HLS var mı kontrol et
|
||
if (hasNativeHLS()) {
|
||
console.log('Native HLS destekleniyor')
|
||
}
|
||
|
||
// MSE desteği kontrol et (HLS.js için gerekli)
|
||
if (hasMSE()) {
|
||
console.log('HLS.js kullanılabilir')
|
||
}
|
||
|
||
// PIP desteği kontrol et
|
||
if (hasPIP()) {
|
||
console.log('Picture-in-Picture kullanılabilir')
|
||
}
|
||
|
||
// iOS Safari kontrolü
|
||
if (isIOSSafari()) {
|
||
console.log('iOS Safari - volume control yok')
|
||
}
|
||
|
||
// Ses kontrolü desteği
|
||
if (hasVolumeControl()) {
|
||
// Volume slider göster
|
||
} else {
|
||
// Volume slider gizle (iOS)
|
||
}
|
||
```
|
||
|
||
### Manual HLS.js Setup
|
||
|
||
```typescript
|
||
import { loadHls, setupHls } from '@alper/video-player'
|
||
|
||
async function customHlsSetup() {
|
||
const videoElement = document.querySelector('video')
|
||
const Hls = await loadHls()
|
||
|
||
if (Hls) {
|
||
const { instance, cleanup } = setupHls(
|
||
Hls,
|
||
videoElement,
|
||
'https://example.com/stream.m3u8',
|
||
{
|
||
onManifestParsed: (qualities, audioTracks, subtitles) => {
|
||
console.log('Qualities:', qualities)
|
||
console.log('Audio tracks:', audioTracks)
|
||
console.log('Subtitles:', subtitles)
|
||
},
|
||
onError: (error) => {
|
||
console.error('HLS error:', error)
|
||
}
|
||
}
|
||
)
|
||
|
||
// instance → hls.js instance
|
||
// cleanup → cleanup function (unmount'ta çağır)
|
||
|
||
return { instance, cleanup }
|
||
}
|
||
}
|
||
```
|
||
|
||
### Custom Subtitle Processing
|
||
|
||
```typescript
|
||
import { parseSRT, createSubtitleBlobURL, fetchSubtitle } from '@alper/video-player'
|
||
|
||
async function loadCustomSubtitle(url: string) {
|
||
// SRT dosyasını fetch et
|
||
const srtContent = await fetchSubtitle(url)
|
||
|
||
// VTT'ye dönüştür
|
||
const vttContent = parseSRT(srtContent)
|
||
|
||
// Blob URL oluştur
|
||
const blobUrl = createSubtitleBlobURL(vttContent)
|
||
|
||
// Video elementine ekle
|
||
const track = document.createElement('track')
|
||
track.kind = 'subtitles'
|
||
track.label = 'Custom'
|
||
track.srclang = 'en'
|
||
track.src = blobUrl
|
||
|
||
videoElement.appendChild(track)
|
||
|
||
// Cleanup
|
||
return () => URL.revokeObjectURL(blobUrl)
|
||
}
|
||
```
|
||
|
||
### Progressive Download Monitoring
|
||
|
||
```tsx
|
||
function VideoWithProgress() {
|
||
const [buffered, setBuffered] = useState(0)
|
||
const { videoRef } = usePlayerContext()
|
||
|
||
useEffect(() => {
|
||
const video = videoRef.current
|
||
if (!video) return
|
||
|
||
const updateBuffer = () => {
|
||
if (video.buffered.length > 0) {
|
||
const bufferedEnd = video.buffered.end(video.buffered.length - 1)
|
||
setBuffered((bufferedEnd / video.duration) * 100)
|
||
}
|
||
}
|
||
|
||
video.addEventListener('progress', updateBuffer)
|
||
return () => video.removeEventListener('progress', updateBuffer)
|
||
}, [videoRef])
|
||
|
||
return (
|
||
<div>
|
||
<VideoPlayer src="large-video.mp4" />
|
||
<div className="buffer-indicator">
|
||
Buffer: {buffered.toFixed(0)}%
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 Tema ve Özelleştirme
|
||
|
||
### CSS Variables
|
||
|
||
Tüm tema CSS değişkenleri ile özelleştirilebilir:
|
||
|
||
```css
|
||
:root {
|
||
/* Ana renkler */
|
||
--player-primary: #ef4444;
|
||
--player-primary-hover: #dc2626;
|
||
--player-bg: #000000;
|
||
--player-text: #ffffff;
|
||
|
||
/* Spacing */
|
||
--player-spacing-xs: 4px;
|
||
--player-spacing-sm: 8px;
|
||
--player-spacing-md: 12px;
|
||
--player-spacing-lg: 16px;
|
||
|
||
/* Border radius */
|
||
--player-radius: 14px;
|
||
--player-radius-sm: 8px;
|
||
|
||
/* Transitions */
|
||
--player-transition-fast: 120ms;
|
||
--player-transition-normal: 200ms;
|
||
--player-transition-slow: 300ms;
|
||
|
||
/* Z-index layers */
|
||
--player-z-video: 1;
|
||
--player-z-overlay: 10;
|
||
--player-z-controls: 20;
|
||
--player-z-menu: 30;
|
||
--player-z-loading: 40;
|
||
}
|
||
```
|
||
|
||
### Custom Theme Örneği
|
||
|
||
**Mavi Tema:**
|
||
```tsx
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
theme={{
|
||
primaryColor: '#3b82f6',
|
||
accentColor: '#2563eb',
|
||
backgroundColor: '#1e293b',
|
||
textColor: '#f1f5f9'
|
||
}}
|
||
/>
|
||
```
|
||
|
||
**Yeşil Tema:**
|
||
```tsx
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
theme={{
|
||
primaryColor: '#10b981',
|
||
accentColor: '#059669',
|
||
backgroundColor: '#064e3b',
|
||
textColor: '#ecfdf5'
|
||
}}
|
||
/>
|
||
```
|
||
|
||
**Dark Theme:**
|
||
```tsx
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
theme={{
|
||
primaryColor: '#8b5cf6',
|
||
accentColor: '#7c3aed',
|
||
backgroundColor: '#18181b',
|
||
textColor: '#fafafa'
|
||
}}
|
||
/>
|
||
```
|
||
|
||
### CSS Override ile Özelleştirme
|
||
|
||
```css
|
||
/* Progress bar yüksekliğini artır */
|
||
.video-player .progress-bar {
|
||
height: 8px !important;
|
||
}
|
||
|
||
/* Kontrol butonlarını büyüt */
|
||
.video-player .control-button {
|
||
width: 48px !important;
|
||
height: 48px !important;
|
||
}
|
||
|
||
/* Loading spinner rengini değiştir */
|
||
.video-player .loading-spinner {
|
||
border-color: #10b981 transparent transparent transparent !important;
|
||
}
|
||
|
||
/* Settings menü arka planı */
|
||
.video-player .settings-menu {
|
||
background: rgba(30, 41, 59, 0.95) !important;
|
||
backdrop-filter: blur(10px) !important;
|
||
}
|
||
```
|
||
|
||
### Responsive Design
|
||
|
||
```css
|
||
/* Mobil cihazlar için özelleştirme */
|
||
@media (max-width: 768px) {
|
||
.video-player {
|
||
--player-spacing-md: 8px;
|
||
--player-radius: 8px;
|
||
}
|
||
|
||
.video-player .control-button {
|
||
width: 40px;
|
||
height: 40px;
|
||
}
|
||
|
||
.video-player .time-display {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
/* Tablet için */
|
||
@media (min-width: 769px) and (max-width: 1024px) {
|
||
.video-player {
|
||
--player-spacing-md: 10px;
|
||
}
|
||
}
|
||
|
||
/* Desktop için */
|
||
@media (min-width: 1025px) {
|
||
.video-player {
|
||
--player-spacing-md: 12px;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🌍 Uluslararasılaştırma (i18n)
|
||
|
||
### Desteklenen Diller
|
||
|
||
- **English (en)** - Varsayılan
|
||
- **Turkish (tr)** - Türkçe
|
||
|
||
### Otomatik Dil Tespiti
|
||
|
||
```tsx
|
||
// Tarayıcı dilini otomatik tespit eder
|
||
<VideoPlayer src="video.mp4" />
|
||
```
|
||
|
||
### Manuel Dil Seçimi
|
||
|
||
```tsx
|
||
// Türkçe zorla
|
||
<VideoPlayer src="video.mp4" language="tr" />
|
||
|
||
// İngilizce zorla
|
||
<VideoPlayer src="video.mp4" language="en" />
|
||
```
|
||
|
||
### Translation Keys
|
||
|
||
```typescript
|
||
interface Translations {
|
||
noSubtitlesAvailable: string // "No subtitles available" / "Altyazı mevcut değil"
|
||
subtitles: string // "Subtitles" / "Altyazı"
|
||
off: string // "Off" / "Kapalı"
|
||
auto: string // "Auto" / "Otomatik"
|
||
quality: string // "Quality" / "Kalite"
|
||
speed: string // "Speed" / "Hız"
|
||
normal: string // "Normal" / "Normal"
|
||
default: string // "Default" / "Varsayılan"
|
||
audioTrack: string // "Audio Track" / "Ses"
|
||
settings: string // "Settings" / "Ayarlar"
|
||
level: string // "Level" / "Seviye"
|
||
}
|
||
```
|
||
|
||
### Programmatic Access
|
||
|
||
```typescript
|
||
import { getTranslations, detectBrowserLanguage } from '@alper/video-player'
|
||
|
||
// Tarayıcı dilini tespit et
|
||
const browserLang = detectBrowserLanguage() // "tr", "en-US", vb.
|
||
|
||
// Translation'ları al
|
||
const t = getTranslations('tr')
|
||
console.log(t.subtitles) // "Altyazı"
|
||
console.log(t.quality) // "Kalite"
|
||
|
||
// Fallback ile
|
||
const t2 = getTranslations('fr') // Fransızca yok
|
||
console.log(t2.subtitles) // "Subtitles" (İngilizce fallback)
|
||
```
|
||
|
||
### Yeni Dil Ekleme
|
||
|
||
`src/i18n/index.ts` dosyasını düzenleyin:
|
||
|
||
```typescript
|
||
export const translations: Record<string, Translations> = {
|
||
en: { /* ... */ },
|
||
tr: { /* ... */ },
|
||
// Yeni dil ekle
|
||
es: {
|
||
noSubtitlesAvailable: 'No hay subtítulos disponibles',
|
||
subtitles: 'Subtítulos',
|
||
off: 'Desactivado',
|
||
auto: 'Automático',
|
||
quality: 'Calidad',
|
||
speed: 'Velocidad',
|
||
normal: 'Normal',
|
||
default: 'Predeterminado',
|
||
audioTrack: 'Pista de audio',
|
||
settings: 'Configuración',
|
||
level: 'Nivel',
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ⚡ Performans
|
||
|
||
### Bundle Size
|
||
|
||
| Component | Size (gzipped) |
|
||
|-----------|----------------|
|
||
| Core library | ~8KB |
|
||
| CSS | ~7KB |
|
||
| **Total** | **~15KB** |
|
||
| HLS.js (lazy) | ~200KB |
|
||
| FLV.js (lazy) | ~150KB |
|
||
|
||
### Optimizasyon Teknikleri
|
||
|
||
1. **Lazy Loading**
|
||
- HLS.js ve FLV.js sadece ihtiyaç duyulduğunda yüklenir
|
||
- Settings menü lazy-loaded
|
||
- CDN fallback ile yükleme garantisi
|
||
|
||
2. **Tree Shaking**
|
||
- ES Module format
|
||
- Kullanılmayan kod elimine edilir
|
||
- Named exports ile selective import
|
||
|
||
3. **Code Splitting**
|
||
- Async component loading
|
||
- Dynamic imports
|
||
- Route-based splitting için hazır
|
||
|
||
4. **CSS Optimization**
|
||
- CSS minification
|
||
- Tek dosyada bundle
|
||
- CSS-only animasyonlar (JS yok)
|
||
- Critical CSS inline (opsiyonel)
|
||
|
||
5. **React Optimization**
|
||
- React.memo kullanımı
|
||
- useCallback/useMemo
|
||
- Gereksiz re-render önleme
|
||
- Event delegation
|
||
|
||
6. **Memory Management**
|
||
- Proper cleanup (useEffect return)
|
||
- Blob URL revocation
|
||
- HLS/FLV instance destroy
|
||
- Event listener removal
|
||
|
||
### Performance Monitoring
|
||
|
||
```tsx
|
||
import { useEffect } from 'react'
|
||
import { VideoPlayer } from '@alper/video-player'
|
||
|
||
function MonitoredVideo() {
|
||
useEffect(() => {
|
||
// Bundle size monitoring
|
||
console.log('Initial bundle loaded')
|
||
|
||
// Measure loading time
|
||
const start = performance.now()
|
||
|
||
return () => {
|
||
const end = performance.now()
|
||
console.log(`Player active for ${end - start}ms`)
|
||
}
|
||
}, [])
|
||
|
||
return (
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
onLoadedMetadata={() => {
|
||
console.log('Video metadata loaded')
|
||
}}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Loading Strategy
|
||
|
||
```tsx
|
||
// Lazy load player component
|
||
import { lazy, Suspense } from 'react'
|
||
|
||
const VideoPlayer = lazy(() =>
|
||
import('@alper/video-player').then(module => ({
|
||
default: module.VideoPlayer
|
||
}))
|
||
)
|
||
|
||
function App() {
|
||
return (
|
||
<Suspense fallback={<div>Loading player...</div>}>
|
||
<VideoPlayer src="video.mp4" />
|
||
</Suspense>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🌐 Tarayıcı Uyumluluğu
|
||
|
||
### Desteklenen Tarayıcılar
|
||
|
||
| Tarayıcı | Versiyon | MP4 | HLS | RTMP/FLV | PIP | Fullscreen |
|
||
|----------|----------|-----|-----|----------|-----|------------|
|
||
| Chrome | 90+ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||
| Edge | 90+ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||
| Firefox | 88+ | ✅ | ✅ | ✅ | ❌ | ✅ |
|
||
| Safari | 14+ | ✅ | ✅ (native) | ❌ | ✅ | ✅ |
|
||
| iOS Safari | 14+ | ✅ | ✅ (native) | ❌ | ✅ | ✅ |
|
||
| Chrome Mobile | 90+ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||
|
||
### Feature Support Matrix
|
||
|
||
| Özellik | Desktop | Mobile | Safari | iOS |
|
||
|---------|---------|--------|--------|-----|
|
||
| MP4/WebM | ✅ | ✅ | ✅ | ✅ |
|
||
| HLS (native) | ❌ | ❌ | ✅ | ✅ |
|
||
| HLS (hls.js) | ✅ | ✅ | ❌ | ❌ |
|
||
| RTMP/FLV | ✅ | ✅ | ❌ | ❌ |
|
||
| Subtitles | ✅ | ✅ | ✅ | ✅ |
|
||
| Quality switching | ✅ | ✅ | ✅ | ✅ |
|
||
| Audio tracks | ✅ | ✅ | ✅ | ✅ |
|
||
| Keyboard shortcuts | ✅ | ❌ | ✅ | ❌ |
|
||
| Touch gestures | ❌ | ✅ | ❌ | ✅ |
|
||
| Picture-in-Picture | ✅ | ✅ | ✅ | ✅ |
|
||
| Fullscreen | ✅ | ✅ | ✅ | ✅ |
|
||
| Volume control | ✅ | ✅ | ✅ | ❌* |
|
||
|
||
*iOS Safari'de programmatic volume control yok (donanım butonları)
|
||
|
||
### Polyfills
|
||
|
||
Kütüphane, eski tarayıcılar için otomatik polyfill içerir:
|
||
|
||
```typescript
|
||
// src/utils/polyfills.ts
|
||
|
||
// Fullscreen API polyfill (vendor prefixes)
|
||
- requestFullscreen
|
||
- exitFullscreen
|
||
- fullscreenElement
|
||
- fullscreenchange event
|
||
|
||
// Picture-in-Picture polyfill
|
||
- requestPictureInPicture
|
||
- exitPictureInPicture
|
||
- pictureInPictureElement
|
||
```
|
||
|
||
### Known Issues
|
||
|
||
**Safari:**
|
||
- HLS.js kullanılmaz (native HLS var)
|
||
- FLV.js MSE desteği sınırlı
|
||
|
||
**iOS Safari:**
|
||
- Volume control programmatic olarak değiştirilemez
|
||
- Autoplay sadece `muted: true` ile çalışır
|
||
- Fullscreen API sınırlı (video element fullscreen olur, container değil)
|
||
|
||
**Firefox:**
|
||
- Picture-in-Picture API henüz desteklenmiyor (planned)
|
||
|
||
**Eski Tarayıcılar (IE11, Edge <90):**
|
||
- Desteklenmez
|
||
- MSE/EME yok → HLS/FLV çalışmaz
|
||
- ES6 features gerektirir
|
||
|
||
---
|
||
|
||
## 🚨 Hata Yönetimi
|
||
|
||
### CORS Errors
|
||
|
||
**Problem:** Video farklı origin'den geliyorsa CORS hatası
|
||
|
||
**Tespit:**
|
||
```typescript
|
||
import { isCORSError, getCORSErrorMessage } from '@alper/video-player'
|
||
|
||
const handleError = (error: Error) => {
|
||
if (isCORSError(error)) {
|
||
const message = getCORSErrorMessage(error, videoUrl)
|
||
console.error(message)
|
||
}
|
||
}
|
||
```
|
||
|
||
**Çözüm:**
|
||
Video sunucunuzda CORS headers ekleyin:
|
||
|
||
```
|
||
Access-Control-Allow-Origin: *
|
||
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
|
||
Access-Control-Allow-Headers: Range, Content-Type
|
||
Access-Control-Expose-Headers: Content-Length, Content-Range
|
||
```
|
||
|
||
**Nginx Örneği:**
|
||
```nginx
|
||
location /videos/ {
|
||
add_header Access-Control-Allow-Origin *;
|
||
add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
|
||
add_header Access-Control-Allow-Headers 'Range';
|
||
add_header Access-Control-Expose-Headers 'Content-Length, Content-Range';
|
||
|
||
if ($request_method = OPTIONS) {
|
||
return 204;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Apache Örneği:**
|
||
```apache
|
||
<IfModule mod_headers.c>
|
||
Header set Access-Control-Allow-Origin "*"
|
||
Header set Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
|
||
Header set Access-Control-Allow-Headers "Range"
|
||
Header set Access-Control-Expose-Headers "Content-Length, Content-Range"
|
||
</IfModule>
|
||
```
|
||
|
||
### HLS Errors
|
||
|
||
**Manifest Parse Error:**
|
||
```typescript
|
||
// HLS manifest yüklenemedi veya parse edilemedi
|
||
// Çözüm: .m3u8 URL'inin doğru olduğundan emin olun
|
||
```
|
||
|
||
**Network Error:**
|
||
```typescript
|
||
// HLS segment indirilemiyor
|
||
// Otomatik recovery: 3 kez retry
|
||
```
|
||
|
||
**Media Error:**
|
||
```typescript
|
||
// Video decode hatası
|
||
// Otomatik recovery: HLS instance restart
|
||
```
|
||
|
||
**Fatal Error:**
|
||
```typescript
|
||
// Kurtarılamaz hata
|
||
// Error state'e düşer, onError callback çağrılır
|
||
```
|
||
|
||
### RTMP/FLV Errors
|
||
|
||
**Network Exception:**
|
||
```typescript
|
||
// FLV stream bağlantı hatası
|
||
// Otomatik retry
|
||
```
|
||
|
||
**Media Error:**
|
||
```typescript
|
||
// FLV decode hatası
|
||
// Error callback çağrılır
|
||
```
|
||
|
||
### Error Handling Best Practices
|
||
|
||
```tsx
|
||
function RobustVideoPlayer() {
|
||
const [error, setError] = useState<string | null>(null)
|
||
const [retryCount, setRetryCount] = useState(0)
|
||
const maxRetries = 3
|
||
|
||
const handleError = (err: Error) => {
|
||
console.error('Video error:', err)
|
||
|
||
if (isCORSError(err)) {
|
||
setError('CORS hatası: Video sunucusu CORS headers eklemeli')
|
||
} else if (retryCount < maxRetries) {
|
||
// Retry
|
||
setRetryCount(retryCount + 1)
|
||
setTimeout(() => {
|
||
window.location.reload()
|
||
}, 2000)
|
||
} else {
|
||
setError(`Video yüklenemedi: ${err.message}`)
|
||
}
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="error-container">
|
||
<h3>Video Hatası</h3>
|
||
<p>{error}</p>
|
||
<button onClick={() => window.location.reload()}>
|
||
Yeniden Dene
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<VideoPlayer
|
||
src="video.mp4"
|
||
onError={handleError}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ Geliştirme
|
||
|
||
### Kurulum
|
||
|
||
```bash
|
||
# Repository'yi klonlayın
|
||
git clone https://gitea.hibna.com.tr/hibna/video-player.git
|
||
cd video-player
|
||
|
||
# Bağımlılıkları yükleyin
|
||
npm install
|
||
# veya
|
||
pnpm install
|
||
```
|
||
|
||
### Development Server
|
||
|
||
```bash
|
||
# Dev server'ı başlat (examples/ klasörü)
|
||
npm run dev
|
||
|
||
# Tarayıcıda açın: http://localhost:5173
|
||
```
|
||
|
||
### Build
|
||
|
||
```bash
|
||
# Kütüphane build
|
||
npm run build:lib
|
||
|
||
# Development build (examples)
|
||
npm run build
|
||
|
||
# Çıktı: dist/ klasörü
|
||
```
|
||
|
||
### Testing
|
||
|
||
```bash
|
||
# Unit testleri çalıştır
|
||
npm run test
|
||
|
||
# Test UI
|
||
npm run test:ui
|
||
|
||
# Coverage raporu
|
||
npm run test:coverage
|
||
```
|
||
|
||
### Linting
|
||
|
||
```bash
|
||
# ESLint kontrolü
|
||
npm run lint
|
||
```
|
||
|
||
### Type Checking
|
||
|
||
```bash
|
||
# TypeScript kontrolü
|
||
npx tsc --noEmit
|
||
```
|
||
|
||
### Package Yayınlama
|
||
|
||
```bash
|
||
# 1. Version bump
|
||
npm version patch # 0.1.5 -> 0.1.6
|
||
npm version minor # 0.1.5 -> 0.2.0
|
||
npm version major # 0.1.5 -> 1.0.0
|
||
|
||
# 2. Build
|
||
npm run build:lib
|
||
|
||
# 3. Gitea'ya push
|
||
git push origin main --tags
|
||
|
||
# 4. NPM'e yayınla (Gitea registry)
|
||
npm publish
|
||
|
||
# NOT: .npmrc dosyasında authentication token olmalı
|
||
```
|
||
|
||
### Project Scripts
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"dev": "vite", // Dev server
|
||
"build": "tsc && vite build", // Build examples
|
||
"build:lib": "vite build --config vite.config.lib.ts", // Build library
|
||
"preview": "vite preview", // Preview build
|
||
"lint": "eslint . --max-warnings 0", // ESLint
|
||
"test": "vitest", // Run tests
|
||
"test:ui": "vitest --ui", // Test UI
|
||
"test:coverage": "vitest --coverage" // Coverage
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🤝 Katkıda Bulunma
|
||
|
||
Katkılarınızı bekliyoruz! Lütfen şu adımları takip edin:
|
||
|
||
### 1. Fork & Clone
|
||
|
||
```bash
|
||
# Repository'yi fork edin (Gitea UI'dan)
|
||
# Fork'unuzu klonlayın
|
||
git clone https://gitea.hibna.com.tr/YOUR_USERNAME/video-player.git
|
||
cd video-player
|
||
```
|
||
|
||
### 2. Branch Oluşturun
|
||
|
||
```bash
|
||
git checkout -b feature/amazing-feature
|
||
```
|
||
|
||
### 3. Değişiklik Yapın
|
||
|
||
```bash
|
||
# Kodunuzu yazın
|
||
# Testlerinizi ekleyin
|
||
# Linting'i kontrol edin
|
||
npm run lint
|
||
npm run test
|
||
```
|
||
|
||
### 4. Commit & Push
|
||
|
||
```bash
|
||
git add .
|
||
git commit -m "feat: Add amazing feature"
|
||
git push origin feature/amazing-feature
|
||
```
|
||
|
||
### 5. Pull Request
|
||
|
||
Gitea'da Pull Request oluşturun.
|
||
|
||
### Commit Conventions
|
||
|
||
```
|
||
feat: Yeni özellik
|
||
fix: Bug fix
|
||
docs: Dökümantasyon
|
||
style: Kod formatı
|
||
refactor: Refactoring
|
||
test: Test ekleme
|
||
chore: Build/config değişikliği
|
||
```
|
||
|
||
### Code Style
|
||
|
||
- TypeScript strict mode
|
||
- ESLint rules
|
||
- Prettier formatting (opsiyonel)
|
||
- Component/function comment'leri
|
||
|
||
---
|
||
|
||
## 📝 Lisans
|
||
|
||
MIT License
|
||
|
||
Copyright (c) 2024 Alper
|
||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
of this software and associated documentation files (the "Software"), to deal
|
||
in the Software without restriction, including without limitation the rights
|
||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
copies of the Software, and to permit persons to whom the Software is
|
||
furnished to do so, subject to the following conditions:
|
||
|
||
The above copyright notice and this permission notice shall be included in all
|
||
copies or substantial portions of the Software.
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
SOFTWARE.
|
||
|
||
---
|
||
|
||
## 📞 İletişim
|
||
|
||
- **Repository:** https://gitea.hibna.com.tr/hibna/video-player
|
||
- **NPM Registry:** https://gitea.hibna.com.tr/api/packages/hibna/npm/
|
||
- **Issues:** https://gitea.hibna.com.tr/hibna/video-player/issues
|
||
- **Author:** Alper
|
||
|
||
---
|
||
|
||
## 🎉 Teşekkürler
|
||
|
||
Bu proje, aşağıdaki açık kaynak kütüphanelerden ilham almıştır:
|
||
|
||
- [hls.js](https://github.com/video-dev/hls.js) - HLS streaming
|
||
- [flv.js](https://github.com/bilibili/flv.js) - FLV streaming
|
||
- [React](https://react.dev/) - UI framework
|
||
- [Vite](https://vitejs.dev/) - Build tool
|
||
- [TypeScript](https://www.typescriptlang.org/) - Type safety
|
||
|
||
---
|
||
|
||
**Built with ❤️ using React, TypeScript, and Vite**
|
||
|
||
*Son güncelleme: 2024*
|