Files
player/DOCUMENTATION.md
T

61 KiB
Raw Blame History

🎬 @source/player - Tam Dökümantasyon

Modern, zengin özellikli ve hafif React video oynatıcı kütüphanesi

npm version Bundle Size TypeScript License: MIT


📑 İçindekiler


🌟 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:

# 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:

# 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:

# @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:

# .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ı:

@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:

# 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:

# 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:

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:

npm install --save-optional hls.js flv.js

Kurulum Doğrulama

Kurulumun başarılı olduğunu doğrulamak için:

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:
    rm -rf node_modules package-lock.json
    npm install
    

🚀 Hızlı Başlangıç

Temel Kullanım

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

<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

<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

<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:

<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:

<VideoPlayer src="https://example.com/stream/master.m3u8" />

Manifest Örneği:

#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:

// 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:

// 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):

// 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:

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:

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:

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üş):

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:

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:

<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):

<VideoPlayer src="stream.m3u8" />
// HLS manifest'te altyazılar varsa otomatik ekler

Programmatic Control:

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:

#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:

{
  name: "English",
  language: "en",
  url: "audio_en.m3u8",
  groupId: "audio",
  default: true,
  autoselect: true
}

Kullanım

Programmatic Control:

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:

#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:

[
  {
    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):

import { setHlsQualityLevel } from '@source/player'

setHlsQualityLevel(hlsInstance, null) // Auto

Manuel Kalite:

setHlsQualityLevel(hlsInstance, {
  label: "1080p",
  levelIndex: 0,
  // ...
})

Kullanım

Settings Menu'den: Kullanıcı Settings → Quality menüsünden kalite seçer.

Programmatic Control:

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):

<VideoPlayer src="video.mp4" />
// Klavye kısayolları otomatik aktif

Manuel kontrol:

<VideoPlayer
  src="video.mp4"
  keyboardShortcuts={false} // Devre dışı
/>

Custom hook ile:

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):

<VideoPlayer src="video.mp4" />
// Touch jestleri otomatik aktif (touch destekli cihazlarda)

Custom hook ile:

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

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

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:

const subtitles: SubtitleTrack[] = [
  {
    src: '/subtitles/english.vtt',
    lang: 'en',
    label: 'English',
    default: true
  },
  {
    src: '/subtitles/turkish.srt',
    lang: 'tr',
    label: 'Türkçe'
  }
]

AudioTrack

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:

const audioTrack: AudioTrack = {
  name: "English Stereo",
  language: "en",
  url: "audio_en.m3u8",
  groupId: "audio",
  default: true,
  autoselect: true
}

VideoQuality

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:

const quality: VideoQuality = {
  label: "1080p",
  height: 1080,
  width: 1920,
  bitrate: 5000000,
  levelIndex: 0,
  url: "1080p.m3u8"
}

PlayerTheme

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:

const theme: PlayerTheme = {
  primaryColor: '#ef4444',    // Kırmızı
  accentColor: '#dc2626',     // Koyu kırmızı
  backgroundColor: '#000000', // Siyah
  textColor: '#ffffff'        // Beyaz
}

VideoState

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

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

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

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

// Ana bileşen
import { VideoPlayer } from '@source/player'

Exported Hooks

// 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ı:

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

// 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

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ı

<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

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

// HTTP-FLV proxy üzerinden RTMP stream
<VideoPlayer
  src="https://your-server.com/live/stream.flv"
  autoplay
  muted
/>

Custom Kontroller

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

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

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

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

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

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

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:

: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:

<VideoPlayer
  src="video.mp4"
  theme={{
    primaryColor: '#3b82f6',
    accentColor: '#2563eb',
    backgroundColor: '#1e293b',
    textColor: '#f1f5f9'
  }}
/>

Yeşil Tema:

<VideoPlayer
  src="video.mp4"
  theme={{
    primaryColor: '#10b981',
    accentColor: '#059669',
    backgroundColor: '#064e3b',
    textColor: '#ecfdf5'
  }}
/>

Dark Theme:

<VideoPlayer
  src="video.mp4"
  theme={{
    primaryColor: '#8b5cf6',
    accentColor: '#7c3aed',
    backgroundColor: '#18181b',
    textColor: '#fafafa'
  }}
/>

CSS Override ile Özelleştirme

/* 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

/* 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

// Tarayıcı dilini otomatik tespit eder
<VideoPlayer src="video.mp4" />

Manuel Dil Seçimi

// Türkçe zorla
<VideoPlayer src="video.mp4" language="tr" />

// İngilizce zorla
<VideoPlayer src="video.mp4" language="en" />

Translation Keys

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

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:

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

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

// 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:

// 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:

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:

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:

<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:

// HLS manifest yüklenemedi veya parse edilemedi
// Çözüm: .m3u8 URL'inin doğru olduğundan emin olun

Network Error:

// HLS segment indirilemiyor
// Otomatik recovery: 3 kez retry

Media Error:

// Video decode hatası
// Otomatik recovery: HLS instance restart

Fatal Error:

// Kurtarılamaz hata
// Error state'e düşer, onError callback çağrılır

RTMP/FLV Errors

Network Exception:

// FLV stream bağlantı hatası
// Otomatik retry

Media Error:

// FLV decode hatası
// Error callback çağrılır

Error Handling Best Practices

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

# 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

# Dev server'ı başlat (examples/ klasörü)
npm run dev

# Tarayıcıda açın: http://localhost:5173

Build

# Kütüphane build
npm run build:lib

# Development build (examples)
npm run build

# Çıktı: dist/ klasörü

Testing

# Unit testleri çalıştır
npm run test

# Test UI
npm run test:ui

# Coverage raporu
npm run test:coverage

Linting

# ESLint kontrolü
npm run lint

Type Checking

# TypeScript kontrolü
npx tsc --noEmit

Package Yayınlama

# 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

{
  "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

# 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

git checkout -b feature/amazing-feature

3. Değişiklik Yapın

# Kodunuzu yazın
# Testlerinizi ekleyin
# Linting'i kontrol edin
npm run lint
npm run test

4. Commit & Push

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


🎉 Teşekkürler

Bu proje, aşağıdaki açık kaynak kütüphanelerden ilham almıştır:


Built with ❤️ using React, TypeScript, and Vite

Son güncelleme: 2024