2651 lines
61 KiB
Markdown
2651 lines
61 KiB
Markdown
# 🎬 @source/player - Tam Dökümantasyon
|
||
|
||
**Modern, zengin özellikli ve hafif React video oynatıcı kütüphanesi**
|
||
|
||
[](https://www.npmjs.com/package/@source/player)
|
||
[](https://bundlephobia.com/package/@source/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ış
|
||
|
||
`@source/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 @source/player?
|
||
|
||
| Özellik | @source/player | video.js | react-player | plyr |
|
||
|---------|---------------------|----------|--------------|------|
|
||
| **Paket Boyutu (gzipped)** | **~15KB** ✅ | ~500KB ❌ | ~50KB ⚠️ | ~30KB ⚠️ |
|
||
| **Runtime Bağımlılıkları** | **0** ✅ | Çok ❌ | Az ⚠️ | Az ⚠️ |
|
||
| **React (Web)** | **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ı
|
||
|
||
`@source` 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
|
||
# @source scope'u için private registry'yi kullan
|
||
@source:registry=https://gits.hibna.com.tr/api/packages/hibna/npm/
|
||
|
||
# Authentication token
|
||
# Token'ınızı almak için: https://gits.hibna.com.tr/user/settings/applications
|
||
# "Generate New Token" butonuna tıklayın ve "read:package" yetkisini seçin
|
||
//gits.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://gits.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
|
||
@source:registry=https://gits.hibna.com.tr/api/packages/hibna/npm/
|
||
//gits.hibna.com.tr/api/packages/hibna/npm/:_authToken=${GITS_NPM_TOKEN}
|
||
```
|
||
|
||
**Environment variable ayarlama:**
|
||
|
||
```bash
|
||
# Linux/Mac (.bashrc veya .zshrc dosyasına ekleyin)
|
||
export GITS_NPM_TOKEN=your-actual-token-here
|
||
|
||
# Windows (PowerShell)
|
||
$env:GITS_NPM_TOKEN="your-actual-token-here"
|
||
|
||
# Windows (Kalıcı - Sistem Environment Variables)
|
||
# Sistem Özellikler → Gelişmiş → Ortam Değişkenleri → Yeni
|
||
# Değişken adı: GITS_NPM_TOKEN
|
||
# Değişken değeri: your-actual-token-here
|
||
```
|
||
|
||
**CI/CD için (GitHub Actions, GitLab CI, vb.):**
|
||
|
||
Repository settings → Secrets → `GITS_NPM_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 @source/player
|
||
|
||
# veya pnpm ile (önerilen - daha hızlı)
|
||
pnpm add @source/player
|
||
|
||
# veya yarn ile
|
||
yarn add @source/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 @source/player
|
||
# veya
|
||
pnpm list @source/player
|
||
```
|
||
|
||
Çıktı şöyle olmalı:
|
||
```
|
||
your-project@1.0.0
|
||
└─┬ @source/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
|
||
- `@source:registry=https://gits.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 '@source/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 '@source/player'
|
||
import '@source/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 Streaming
|
||
|
||
**Desteklenen Protokoller:**
|
||
- RTMP (rtmp://)
|
||
- RTMPS (rtmps://)
|
||
- RTMPT (rtmpt://)
|
||
- HTTP-FLV (.flv veya flv? query)
|
||
|
||
**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" />
|
||
```
|
||
|
||
#### 4. IPTV (MPEG-TS) Streaming
|
||
|
||
**Desteklenen Format:**
|
||
- MPEG-TS (.ts) - Direct transport stream files
|
||
|
||
**Kütüphane:** Yok (Native HTML5 video element)
|
||
|
||
**Özellikler:**
|
||
- Doğrudan HTTP üzerinden TS stream oynatma
|
||
- Canlı IPTV kanalları için ideal
|
||
- Ek kütüphane gerekmez
|
||
|
||
**Tarayıcı Desteği:**
|
||
- ⚠️ **Sınırlı destek**: Tüm tarayıcılar MPEG-TS formatını desteklemez
|
||
- ✅ **Chrome/Edge**: Genellikle destekler
|
||
- ⚠️ **Firefox**: Sınırlı destek
|
||
- ⚠️ **Safari**: Sınırlı destek
|
||
- ℹ️ **Alternatif**: M3U8 playlist üzerinden IPTV kullanımı önerilir
|
||
|
||
**Kullanım:**
|
||
```tsx
|
||
// IPTV (MPEG-TS) - Direct stream
|
||
<VideoPlayer
|
||
src="http://server.com:8080/live/username/password/12345.ts"
|
||
poster="http://example.com/channel-logo.png"
|
||
onError={(error) => {
|
||
console.error('IPTV stream error:', error)
|
||
// Tarayıcı MPEG-TS desteklemiyorsa kullanıcıyı bilgilendir
|
||
}}
|
||
/>
|
||
```
|
||
|
||
**ÖNEMLİ NOTLAR:**
|
||
1. **Tarayıcı Uyumluluğu**: Tüm tarayıcılar `.ts` dosyalarını doğrudan oynatamaz
|
||
2. **Alternatif Çözüm**: Mümkünse IPTV sağlayıcınızdan `.m3u8` (HLS) link isteyin
|
||
3. **CORS**: IPTV sunucusu CORS ayarlarını doğru yapılandırmalıdır
|
||
4. **Performans**: Doğrudan TS stream'ler bazı cihazlarda yavaş olabilir
|
||
|
||
**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);
|
||
```
|
||
|
||
#### 5. 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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/player'
|
||
```
|
||
|
||
### Exported Hooks
|
||
|
||
```typescript
|
||
// Player context hook
|
||
import { usePlayerContext } from '@source/player'
|
||
|
||
// Klavye kısayolları hook'u
|
||
import { useKeyboardShortcuts } from '@source/player'
|
||
|
||
// Dokunmatik jest hook'u
|
||
import { useTouchGestures } from '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/player'
|
||
import '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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 '@source/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('@source/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 '@source/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://gits.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://gits.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://gits.hibna.com.tr/hibna/video-player
|
||
- **NPM Registry:** https://gits.hibna.com.tr/api/packages/hibna/npm/
|
||
- **Issues:** https://gits.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*
|
||
|
||
|
||
|