diff --git a/.npmrc.example b/.npmrc.example index 1606ed3..d7297de 100644 --- a/.npmrc.example +++ b/.npmrc.example @@ -1,27 +1,27 @@ # .npmrc Örnek Dosyası # Bu dosyayı diğer projelerinize kopyalayın ve .npmrc olarak kaydedin -# @alper scope'u için Gitea registry'yi kullan -@alper:registry=https://gitea.yourdomain.com/api/packages/your-username/npm/ +# @source scope'u için private registry kullan +@source:registry=https://gits.yourdomain.com/api/packages/your-username/npm/ # Authentication token (environment variable kullanımı - ÖNERİLEN) -//gitea.yourdomain.com/api/packages/your-username/npm/:_authToken=${GITEA_TOKEN} +//gits.yourdomain.com/api/packages/your-username/npm/:_authToken=${GITS_NPM_TOKEN} # Alternatif: Doğrudan token (GÜVENLİ DEĞİL - sadece local geliştirme için) -# //gitea.yourdomain.com/api/packages/your-username/npm/:_authToken=your-gitea-access-token-here +# //gits.yourdomain.com/api/packages/your-username/npm/:_authToken=your-registry-token-here # Environment variable nasıl ayarlanır: # # Linux/Mac: -# export GITEA_TOKEN=your-token-here +# export GITS_NPM_TOKEN=your-token-here # # veya ~/.bashrc veya ~/.zshrc dosyasına ekleyin # # Windows (PowerShell): -# $env:GITEA_TOKEN="your-token-here" +# $env:GITS_NPM_TOKEN="your-token-here" # # veya sistem environment variables'a ekleyin # # .env dosyası (projede): -# GITEA_TOKEN=your-token-here +# GITS_NPM_TOKEN=your-token-here # Not: Bu dosyayı .gitignore'a ekleyin! # Asla token'ınızı git'e commit etmeyin. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index f317927..a965c9d 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1,9 +1,9 @@ -# 🎬 @alper/video-player - Tam Dökümantasyon +# 🎬 @source/player - Tam Dökümantasyon **Modern, zengin özellikli ve hafif React video oynatıcı kütüphanesi** -[![npm version](https://img.shields.io/npm/v/@alper/video-player.svg?style=flat-square)](https://www.npmjs.com/package/@alper/video-player) -[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@alper/video-player?style=flat-square)](https://bundlephobia.com/package/@alper/video-player) +[![npm version](https://img.shields.io/npm/v/@source/player.svg?style=flat-square)](https://www.npmjs.com/package/@source/player) +[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@source/player?style=flat-square)](https://bundlephobia.com/package/@source/player) [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?style=flat-square)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) @@ -47,15 +47,15 @@ ## 🌟 Genel Bakış -`@alper/video-player`, React uygulamaları için özel olarak tasarlanmış, modern bir video oynatıcı kütüphanesidir. HLS streaming, RTMP/FLV desteği, çoklu altyazı ve ses parçaları, kalite değiştirme ve daha fazlası gibi gelişmiş özellikleri içerir. +`@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 @alper/video-player? +### Neden @source/player? -| Özellik | @alper/video-player | video.js | react-player | plyr | +| Ö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 Native** | **Evet** ✅ | Wrapper ⚠️ | **Evet** ✅ | Wrapper ⚠️ | +| **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 ❌ | @@ -100,7 +100,7 @@ npm install -g pnpm ### 2. .npmrc Yapılandırması -`@alper` scope'lu paketlerin Gitea registry'den çekilmesi için projenizin kök dizininde bir `.npmrc` dosyası oluşturmanız gerekir. +`@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 @@ -119,20 +119,20 @@ New-Item -Path .npmrc -ItemType File `.npmrc` dosyasını aşağıdaki içerikle doldurun: ```ini -# @alper scope'u için Gitea registry'yi kullan -@alper:registry=https://gitea.hibna.com.tr/api/packages/hibna/npm/ +# @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://gitea.hibna.com.tr/user/settings/applications +# 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 -//gitea.hibna.com.tr/api/packages/hibna/npm/:_authToken=YOUR_TOKEN_HERE +//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://gitea.hibna.com.tr +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 @@ -168,28 +168,28 @@ Daha güvenli bir yöntem için token'ı environment variable olarak saklayabili **.npmrc dosyası:** ```ini -@alper:registry=https://gitea.hibna.com.tr/api/packages/hibna/npm/ -//gitea.hibna.com.tr/api/packages/hibna/npm/:_authToken=${GITEA_TOKEN} +@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 GITEA_TOKEN=your-actual-token-here +export GITS_NPM_TOKEN=your-actual-token-here # Windows (PowerShell) -$env:GITEA_TOKEN="your-actual-token-here" +$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ı: GITEA_TOKEN +# 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 → `GITEA_TOKEN` adında bir secret oluşturun. +Repository settings → Secrets → `GITS_NPM_TOKEN` adında bir secret oluşturun. ### 3. Kütüphaneyi Yükleme @@ -197,13 +197,13 @@ Repository settings → Secrets → `GITEA_TOKEN` adında bir secret oluşturun. ```bash # npm ile -npm install @alper/video-player +npm install @source/player # veya pnpm ile (önerilen - daha hızlı) -pnpm add @alper/video-player +pnpm add @source/player # veya yarn ile -yarn add @alper/video-player +yarn add @source/player ``` ### 4. Peer Dependencies @@ -234,15 +234,15 @@ npm install --save-optional hls.js flv.js Kurulumun başarılı olduğunu doğrulamak için: ```bash -npm list @alper/video-player +npm list @source/player # veya -pnpm list @alper/video-player +pnpm list @source/player ``` Çıktı şöyle olmalı: ``` your-project@1.0.0 -└─┬ @alper/video-player@0.1.5 +└─┬ @source/player@0.1.5 ``` ### Sorun Giderme @@ -254,11 +254,11 @@ your-project@1.0.0 **Problem: "404 Not Found" hatası** - Registry URL'inin doğru olduğundan emin olun -- `@alper:registry=https://gitea.hibna.com.tr/api/packages/hibna/npm/` +- `@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 '@alper/video-player'"** +**Problem: "Cannot find module '@source/player'"** - `node_modules` klasörünü silin ve yeniden yükleyin: ```bash rm -rf node_modules package-lock.json @@ -272,8 +272,8 @@ your-project@1.0.0 ### Temel Kullanım ```tsx -import { VideoPlayer } from '@alper/video-player' -import '@alper/video-player/styles.css' +import { VideoPlayer } from '@source/player' +import '@source/player/styles.css' function App() { return ( @@ -630,7 +630,7 @@ function detectVideoProtocol(url: string): { **Kullanım:** ```typescript -import { detectVideoProtocol } from '@alper/video-player' +import { detectVideoProtocol } from '@source/player' const info = detectVideoProtocol('https://example.com/stream.m3u8') console.log(info.protocol) // 'hls' @@ -690,7 +690,7 @@ Merhaba dünya **Dönüşüm Fonksiyonu:** ```typescript -import { parseSRT, createSubtitleBlobURL } from '@alper/video-player' +import { parseSRT, createSubtitleBlobURL } from '@source/player' const srtContent = '...' // SRT içeriği const vttContent = parseSRT(srtContent) @@ -737,7 +737,7 @@ const blobUrl = createSubtitleBlobURL(vttContent) **Programmatic Control:** ```tsx -import { usePlayerContext } from '@alper/video-player' +import { usePlayerContext } from '@source/player' function CustomSubtitleToggle() { const { settings, setSubtitle } = usePlayerContext() @@ -789,7 +789,7 @@ function CustomSubtitleToggle() { **Programmatic Control:** ```tsx -import { usePlayerContext } from '@alper/video-player' +import { usePlayerContext } from '@source/player' function AudioTrackSelector() { const { settings, setAudioTrack } = usePlayerContext() @@ -867,7 +867,7 @@ function AudioTrackSelector() { **Otomatik Kalite (Adaptive Bitrate):** ```typescript -import { setHlsQualityLevel } from '@alper/video-player' +import { setHlsQualityLevel } from '@source/player' setHlsQualityLevel(hlsInstance, null) // Auto ``` @@ -888,7 +888,7 @@ Kullanıcı Settings → Quality menüsünden kalite seçer. **Programmatic Control:** ```tsx -import { usePlayerContext } from '@alper/video-player' +import { usePlayerContext } from '@source/player' function QualitySelector() { const { settings, setQuality } = usePlayerContext() @@ -974,7 +974,7 @@ function QualitySelector() { **Custom hook ile:** ```tsx -import { useKeyboardShortcuts } from '@alper/video-player' +import { useKeyboardShortcuts } from '@source/player' function MyComponent() { const { videoRef, containerRef } = usePlayerContext() @@ -1039,7 +1039,7 @@ function MyComponent() { **Custom hook ile:** ```tsx -import { useTouchGestures } from '@alper/video-player' +import { useTouchGestures } from '@source/player' function MyComponent() { const { videoRef, containerRef } = usePlayerContext() @@ -1411,20 +1411,20 @@ interface PlayerContextValue { ```typescript // Ana bileşen -import { VideoPlayer } from '@alper/video-player' +import { VideoPlayer } from '@source/player' ``` ### Exported Hooks ```typescript // Player context hook -import { usePlayerContext } from '@alper/video-player' +import { usePlayerContext } from '@source/player' // Klavye kısayolları hook'u -import { useKeyboardShortcuts } from '@alper/video-player' +import { useKeyboardShortcuts } from '@source/player' // Dokunmatik jest hook'u -import { useTouchGestures } from '@alper/video-player' +import { useTouchGestures } from '@source/player' ``` **usePlayerContext Kullanımı:** @@ -1458,7 +1458,7 @@ function CustomControl() { ```typescript // Zaman formatlama -import { formatTime, parseTime } from '@alper/video-player' +import { formatTime, parseTime } from '@source/player' formatTime(125) // "2:05" formatTime(3665) // "1:01:05" @@ -1470,7 +1470,7 @@ import { parseSRT, createSubtitleBlobURL, fetchSubtitle -} from '@alper/video-player' +} from '@source/player' const srtContent = "1\n00:00:01,000 --> 00:00:04,000\nMerhaba" const vttContent = parseSRT(srtContent) @@ -1483,7 +1483,7 @@ import { getCORSErrorMessage, isCORSError, checkVideoCORS -} from '@alper/video-player' +} from '@source/player' const validation = validateVideoURL(url) if (!validation.valid) { @@ -1505,7 +1505,7 @@ import { getTranslations, detectBrowserLanguage, translations -} from '@alper/video-player' +} from '@source/player' const lang = detectBrowserLanguage() // "tr", "en", vb. const t = getTranslations('tr') @@ -1520,8 +1520,8 @@ console.log(translations.tr.quality) // "Kalite" ### Temel MP4 Oynatma ```tsx -import { VideoPlayer } from '@alper/video-player' -import '@alper/video-player/styles.css' +import { VideoPlayer } from '@source/player' +import '@source/player/styles.css' function App() { return ( @@ -1603,7 +1603,7 @@ function VideoWithAnalytics() { ### Custom Kontroller ```tsx -import { VideoPlayer, usePlayerContext } from '@alper/video-player' +import { VideoPlayer, usePlayerContext } from '@source/player' function CustomControls() { const { @@ -1660,7 +1660,7 @@ function App() { ```tsx import { useState } from 'react' -import { VideoPlayer } from '@alper/video-player' +import { VideoPlayer } from '@source/player' const videos = [ { id: 1, src: 'video1.mp4', title: 'Video 1' }, @@ -1706,7 +1706,7 @@ function Playlist() { ### CORS Hata Yönetimi ```tsx -import { VideoPlayer, isCORSError, getCORSErrorMessage } from '@alper/video-player' +import { VideoPlayer, isCORSError, getCORSErrorMessage } from '@source/player' import { useState } from 'react' function VideoWithCORSHandling() { @@ -1762,7 +1762,7 @@ import { hasTouch, isIOSSafari, hasVolumeControl -} from '@alper/video-player' +} from '@source/player' // Safari'de native HLS var mı kontrol et if (hasNativeHLS()) { @@ -1795,7 +1795,7 @@ if (hasVolumeControl()) { ### Manual HLS.js Setup ```typescript -import { loadHls, setupHls } from '@alper/video-player' +import { loadHls, setupHls } from '@source/player' async function customHlsSetup() { const videoElement = document.querySelector('video') @@ -1829,7 +1829,7 @@ async function customHlsSetup() { ### Custom Subtitle Processing ```typescript -import { parseSRT, createSubtitleBlobURL, fetchSubtitle } from '@alper/video-player' +import { parseSRT, createSubtitleBlobURL, fetchSubtitle } from '@source/player' async function loadCustomSubtitle(url: string) { // SRT dosyasını fetch et @@ -2077,7 +2077,7 @@ interface Translations { ### Programmatic Access ```typescript -import { getTranslations, detectBrowserLanguage } from '@alper/video-player' +import { getTranslations, detectBrowserLanguage } from '@source/player' // Tarayıcı dilini tespit et const browserLang = detectBrowserLanguage() // "tr", "en-US", vb. @@ -2170,7 +2170,7 @@ export const translations: Record = { ```tsx import { useEffect } from 'react' -import { VideoPlayer } from '@alper/video-player' +import { VideoPlayer } from '@source/player' function MonitoredVideo() { useEffect(() => { @@ -2204,7 +2204,7 @@ function MonitoredVideo() { import { lazy, Suspense } from 'react' const VideoPlayer = lazy(() => - import('@alper/video-player').then(module => ({ + import('@source/player').then(module => ({ default: module.VideoPlayer })) ) @@ -2300,7 +2300,7 @@ Kütüphane, eski tarayıcılar için otomatik polyfill içerir: **Tespit:** ```typescript -import { isCORSError, getCORSErrorMessage } from '@alper/video-player' +import { isCORSError, getCORSErrorMessage } from '@source/player' const handleError = (error: Error) => { if (isCORSError(error)) { @@ -2437,7 +2437,7 @@ function RobustVideoPlayer() { ```bash # Repository'yi klonlayın -git clone https://gitea.hibna.com.tr/hibna/video-player.git +git clone https://gits.hibna.com.tr/hibna/video-player.git cd video-player # Bağımlılıkları yükleyin @@ -2542,7 +2542,7 @@ Katkılarınızı bekliyoruz! Lütfen şu adımları takip edin: ```bash # Repository'yi fork edin (Gitea UI'dan) # Fork'unuzu klonlayın -git clone https://gitea.hibna.com.tr/YOUR_USERNAME/video-player.git +git clone https://gits.hibna.com.tr/YOUR_USERNAME/video-player.git cd video-player ``` @@ -2623,9 +2623,9 @@ SOFTWARE. ## 📞 İletişim -- **Repository:** https://gitea.hibna.com.tr/hibna/video-player -- **NPM Registry:** https://gitea.hibna.com.tr/api/packages/hibna/npm/ -- **Issues:** https://gitea.hibna.com.tr/hibna/video-player/issues +- **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 --- @@ -2645,3 +2645,6 @@ 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* + + + diff --git a/README.md b/README.md index 66267e4..7a6b41e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A feature-rich, modern video player library built with React, TypeScript, and Vi |---------|---------------------|----------|--------------|------| | **Bundle Size (gzipped)** | **~18KB JS + ~3.5KB CSS** ✅ | ~500KB ❌ | ~50KB ⚠️ | ~30KB ⚠️ | | **Runtime Dependencies** | **0** ✅ | Many ❌ | Few ⚠️ | Few ⚠️ | -| **React Native** | **Yes** ✅ | Wrapper ⚠️ | **Yes** ✅ | Wrapper ⚠️ | +| **React (Web)** | **Yes** ✅ | Wrapper ⚠️ | **Yes** ✅ | Wrapper ⚠️ | | **TypeScript Native** | **Yes** ✅ | Types ⚠️ | Partial ⚠️ | Types ⚠️ | | **HLS Support** | **Yes** ✅ | Yes ✅ | Yes ✅ | No ❌ | | **Quality Switching** | **Yes** ✅ | Yes ✅ | Limited ⚠️ | No ❌ | @@ -84,27 +84,45 @@ A feature-rich, modern video player library built with React, TypeScript, and Vi ## 📦 Installation -This is a local library project. To use it in your projects: +This package is distributed through a private registry. -### Option 1: Copy the library -```bash -# Copy the src folder to your project -cp -r src/components your-project/src/ -cp -r src/contexts your-project/src/ -cp -r src/hooks your-project/src/ -cp -r src/utils your-project/src/ -cp -r src/types your-project/src/ -cp -r src/icons your-project/src/ -cp -r src/styles your-project/src/ +### 1. Configure `.npmrc` + +Create `.npmrc` in your app root: + +```ini +@source:registry=https://gits.hibna.com.tr/api/packages/hibna/npm/ +//gits.hibna.com.tr/api/packages/hibna/npm/:_authToken=${GITS_NPM_TOKEN} ``` -### Option 2: Build as library and link +### 2. Set token + +Set your token in environment variables (`GITS_NPM_TOKEN`) and do not commit `.npmrc` with a hardcoded token. + +### 3. Install package + ```bash -# In this project +npm install @source/player +# or +pnpm add @source/player +# or +yarn add @source/player +``` + +### 4. Ensure peer dependencies + +```bash +npm install react react-dom +``` + +### Local development (optional) + +```bash +# In this repository npm run build:lib npm link -# In your other project +# In your consuming app npm link @source/player ``` diff --git a/src/components/ControlsLayer.tsx b/src/components/ControlsLayer.tsx index c1814df..1f357e3 100644 --- a/src/components/ControlsLayer.tsx +++ b/src/components/ControlsLayer.tsx @@ -31,7 +31,7 @@ export const ControlsLayer: React.FC = ({ audioTracks = [], qualities = [], }) => { - const { videoState, uiState, togglePlay, toggleFullscreen, showControls, hideControls } = + const { videoState, uiState, togglePlay, toggleFullscreen, showControls, hideControls, translations } = usePlayerContext() const [isPointerOver, setIsPointerOver] = useState(false) const [lastInteraction, setLastInteraction] = useState(0) @@ -228,7 +228,7 @@ export const ControlsLayer: React.FC = ({ {videoState.isLiveBroadcast && (
- LIVE + {translations.live}
)} diff --git a/src/components/VideoElement.tsx b/src/components/VideoElement.tsx index 2500528..e57fa47 100644 --- a/src/components/VideoElement.tsx +++ b/src/components/VideoElement.tsx @@ -8,6 +8,7 @@ import { setupRtmpInstance } from '../utils/rtmpSetup' import { setupMpegtsInstance } from '../utils/mpegtsSetup' import { detectVideoProtocol } from '../utils/videoProtocol' import { createSubtitleBlobURL } from '../utils/subtitles' +import { logger } from '../utils/logger' import './VideoElement.css' interface VideoElementProps { @@ -112,7 +113,7 @@ export const VideoElement: React.FC = ({ // Check if this is a live broadcast (duration is Infinity for live streams) const isLiveBroadcast = !isFinite(video.duration) || video.duration === 0 - console.log('[VideoElement] Is live broadcast?', isLiveBroadcast, 'duration:', video.duration) + logger.log('[VideoElement] Is live broadcast?', isLiveBroadcast, 'duration:', video.duration) setVideoState((prev) => ({ ...prev, @@ -127,7 +128,7 @@ export const VideoElement: React.FC = ({ if (tracks && processedSubtitles.length > 0) { const defaultSubtitle = processedSubtitles.find((sub) => sub.default) if (defaultSubtitle) { - console.log(`🎯 Found default subtitle in metadata: ${defaultSubtitle.label}`) + logger.log(`🎯 Found default subtitle in metadata: ${defaultSubtitle.label}`) // Set subtitle in context (this will trigger the useEffect that enables it) setSubtitle(defaultSubtitle) } @@ -142,7 +143,7 @@ export const VideoElement: React.FC = ({ // Re-check if this is a live broadcast when duration changes const isLiveBroadcast = !isFinite(video.duration) || video.duration === 0 - console.log('[VideoElement] Duration changed. Is live broadcast?', isLiveBroadcast, 'duration:', video.duration) + logger.log('[VideoElement] Duration changed. Is live broadcast?', isLiveBroadcast, 'duration:', video.duration) setVideoState((prev) => ({ ...prev, @@ -339,25 +340,16 @@ export const VideoElement: React.FC = ({ throw new Error(`Failed to fetch subtitle: ${response.status} ${response.statusText}`) } const srtContent = await response.text() - console.log(`SRT content length: ${srtContent.length} chars`) const blobUrl = createSubtitleBlobURL(srtContent, 'srt') subtitleBlobUrlsRef.current.push(blobUrl) - // Debug: fetch the blob URL to verify VTT content - const vttResponse = await fetch(blobUrl) - const vttContent = await vttResponse.text() - console.log(`VTT content preview (first 500 chars):`, vttContent.substring(0, 500)) - console.log(`Total VTT length: ${vttContent.length} chars`) - - console.log(`Processed SRT subtitle "${subtitle.label}": ${subtitle.src} -> ${blobUrl}`) return { ...subtitle, src: blobUrl } } // VTT files can be used directly - console.log(`Using VTT subtitle "${subtitle.label}": ${subtitle.src}`) return subtitle } catch (error) { - console.error(`Failed to process subtitle ${subtitle.label}:`, error) + logger.error(`Failed to process subtitle ${subtitle.label}:`, error) return subtitle } }) @@ -459,10 +451,10 @@ export const VideoElement: React.FC = ({ } } - console.log('[VideoElement] Source:', src) - console.log('[VideoElement] Detected protocol:', detection.protocol) - console.log('[VideoElement] Is live stream?', detection.isLive) - console.log('[VideoElement] Needs special player?', detection.needsSpecialPlayer) + logger.log('[VideoElement] Source:', src) + logger.log('[VideoElement] Detected protocol:', detection.protocol) + logger.log('[VideoElement] Is live stream?', detection.isLive) + logger.log('[VideoElement] Needs special player?', detection.needsSpecialPlayer) const setupPlayer = async () => { try { @@ -473,12 +465,12 @@ export const VideoElement: React.FC = ({ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) const shouldUseHlsJs = canPlayHLS === '' || !isSafari - console.log('[VideoElement] Native HLS support?', canPlayHLS) - console.log('[VideoElement] Is Safari?', isSafari) - console.log('[VideoElement] Will use HLS.js?', shouldUseHlsJs) + logger.log('[VideoElement] Native HLS support?', canPlayHLS) + logger.log('[VideoElement] Is Safari?', isSafari) + logger.log('[VideoElement] Will use HLS.js?', shouldUseHlsJs) if (shouldUseHlsJs) { - console.log('[VideoElement] Setting up HLS.js...') + logger.log('[VideoElement] Setting up HLS.js...') cleanupFn = await setupHlsInstance({ video, src, @@ -507,7 +499,7 @@ export const VideoElement: React.FC = ({ } } else { if (isCancelled) return - console.log('[VideoElement] Using native HLS playback') + logger.log('[VideoElement] Using native HLS playback') video.src = src if (autoplay) { void video.play().catch(() => undefined) @@ -518,7 +510,7 @@ export const VideoElement: React.FC = ({ case 'rtmp': { // RTMP/FLV streaming setup - console.log('[VideoElement] Setting up RTMP/FLV player...') + logger.log('[VideoElement] Setting up RTMP/FLV player...') cleanupFn = await setupRtmpInstance({ video, src, @@ -536,7 +528,7 @@ export const VideoElement: React.FC = ({ case 'mpegts': { // MPEG-TS/IPTV streaming setup - console.log('[VideoElement] Setting up MPEG-TS player...') + logger.log('[VideoElement] Setting up MPEG-TS player...') cleanupFn = await setupMpegtsInstance({ video, src, @@ -556,7 +548,7 @@ export const VideoElement: React.FC = ({ // DASH streaming - not yet implemented if (isCancelled) return const error = new Error('DASH streaming is not yet supported') - console.error('[VideoElement]', error.message) + logger.error('[VideoElement]', error.message) setVideoState((prev) => ({ ...prev, error, loading: false })) onError?.(error) break @@ -566,7 +558,7 @@ export const VideoElement: React.FC = ({ default: { // Native HTML5 video (MP4, WebM, etc.) if (isCancelled) return - console.log('[VideoElement] Using native video.src') + logger.log('[VideoElement] Using native video.src') video.src = src if (autoplay) { void video.play().catch(() => undefined) @@ -585,7 +577,7 @@ export const VideoElement: React.FC = ({ if (isCancelled) return - console.error('[VideoElement] Setup error:', error) + logger.error('[VideoElement] Setup error:', error) setVideoState((prev) => ({ ...prev, error, @@ -728,11 +720,11 @@ export const VideoElement: React.FC = ({ // Wait for track to have cues before showing if (track.cues && track.cues.length > 0) { track.mode = 'showing' - console.log(`🔊 Enabled subtitle track: ${track.label} (${track.language})`) - console.log(` - cues available: ${track.cues.length}`) - console.log(` - track.mode: ${track.mode}`) + logger.log(`🔊 Enabled subtitle track: ${track.label} (${track.language})`) + logger.log(` - cues available: ${track.cues.length}`) + logger.log(` - track.mode: ${track.mode}`) } else { - console.warn(`⚠️ Track ${track.label} has no cues yet, waiting...`) + logger.warn(`⚠️ Track ${track.label} has no cues yet, waiting...`) // Track not ready yet, will be handled by load event track.mode = 'showing' } @@ -747,7 +739,7 @@ export const VideoElement: React.FC = ({ // Also listen for track load events to retry const handleTrackChange = () => { - console.log(`🔄 Track changed, re-enabling subtitle`) + logger.log(`🔄 Track changed, re-enabling subtitle`) enableSubtitle() } @@ -762,80 +754,6 @@ export const VideoElement: React.FC = ({ } }, [settings.subtitle, videoRef]) - // Debug: Monitor text track loading - useEffect(() => { - const video = videoRef.current - if (!video) return - - const handleTrackLoad = (e: Event) => { - const track = e.target as HTMLTrackElement - const textTrack = track.track - console.log(`✅ Track loaded: ${track.label} (${track.srclang})`) - console.log(` - readyState: ${track.readyState}`) - console.log(` - track.mode: ${textTrack.mode}`) - console.log(` - track.cues: ${textTrack.cues?.length || 0}`) - console.log(` - src: ${track.src}`) - - // Log first few cues if available - if (textTrack.cues && textTrack.cues.length > 0) { - console.log(` - First cue: ${(textTrack.cues[0] as VTTCue).startTime}s - ${(textTrack.cues[0] as VTTCue).endTime}s: "${(textTrack.cues[0] as VTTCue).text}"`) - } else { - console.warn(` ⚠️ No cues found in track!`) - } - } - - const handleTrackError = (e: Event) => { - const track = e.target as HTMLTrackElement - console.error(`❌ Track error: ${track.label} (${track.srclang})`) - console.error(` - src: ${track.src}`) - console.error(` - readyState: ${track.readyState}`) - } - - const trackElements = video.querySelectorAll('track') - trackElements.forEach((track) => { - track.addEventListener('load', handleTrackLoad) - track.addEventListener('error', handleTrackError) - }) - - // Also monitor text tracks - const textTracks = video.textTracks - const handleCueChange = () => { - for (let i = 0; i < textTracks.length; i++) { - const track = textTracks[i] - if (track.mode === 'showing') { - console.log(`🎬 Cuechange: ${track.label}, cues: ${track.cues?.length || 0}, active cues: ${track.activeCues?.length || 0}`) - if (track.activeCues && track.activeCues.length > 0) { - const cue = track.activeCues[0] as VTTCue - console.log(` - Active cue text: "${cue.text}"`) - } - } - } - } - - for (let i = 0; i < textTracks.length; i++) { - textTracks[i].addEventListener('cuechange', handleCueChange) - } - - // Log all text tracks after a delay to see their state - setTimeout(() => { - console.log(`📊 Text tracks summary (${textTracks.length} total):`) - for (let i = 0; i < textTracks.length; i++) { - const track = textTracks[i] - console.log(` [${i}] ${track.label} (${track.language}): mode=${track.mode}, cues=${track.cues?.length || 0}`) - } - }, 1000) - - return () => { - trackElements.forEach((track) => { - track.removeEventListener('load', handleTrackLoad) - track.removeEventListener('error', handleTrackError) - }) - for (let i = 0; i < textTracks.length; i++) { - textTracks[i].removeEventListener('cuechange', handleCueChange) - } - } - }, [videoRef, processedSubtitles]) - return (
) } + diff --git a/src/components/controls/CenterPlayButton.tsx b/src/components/controls/CenterPlayButton.tsx index b8493ab..75e2fef 100644 --- a/src/components/controls/CenterPlayButton.tsx +++ b/src/components/controls/CenterPlayButton.tsx @@ -4,7 +4,7 @@ import { PlayIcon } from '../../icons' import './CenterPlayButton.css' export const CenterPlayButton: React.FC = () => { - const { play } = usePlayerContext() + const { play, translations } = usePlayerContext() return (
@@ -12,8 +12,8 @@ export const CenterPlayButton: React.FC = () => { className="center-play-button" type="button" onClick={play} - aria-label="Play" - title="Play" + aria-label={translations.play} + title={translations.play} > diff --git a/src/components/controls/FullscreenButton.tsx b/src/components/controls/FullscreenButton.tsx index 24eb4c1..4e90ecf 100644 --- a/src/components/controls/FullscreenButton.tsx +++ b/src/components/controls/FullscreenButton.tsx @@ -4,14 +4,17 @@ import { FullscreenIcon, FullscreenExitIcon } from '../../icons' import './ControlButton.css' export const FullscreenButton: React.FC = () => { - const { videoState, toggleFullscreen } = usePlayerContext() + const { videoState, toggleFullscreen, translations } = usePlayerContext() + const actionLabel = videoState.fullscreen + ? translations.exitFullscreen + : translations.enterFullscreen return ( diff --git a/src/components/controls/PlayPauseButton.tsx b/src/components/controls/PlayPauseButton.tsx index 5e75686..3602f9e 100644 --- a/src/components/controls/PlayPauseButton.tsx +++ b/src/components/controls/PlayPauseButton.tsx @@ -4,14 +4,15 @@ import { PlayIcon, PauseIcon } from '../../icons' import './ControlButton.css' export const PlayPauseButton: React.FC = () => { - const { videoState, togglePlay } = usePlayerContext() + const { videoState, togglePlay, translations } = usePlayerContext() + const actionLabel = videoState.playing ? translations.pause : translations.play return ( diff --git a/src/components/controls/VolumeControl.tsx b/src/components/controls/VolumeControl.tsx index fbe3f3b..4c3f09b 100644 --- a/src/components/controls/VolumeControl.tsx +++ b/src/components/controls/VolumeControl.tsx @@ -4,7 +4,7 @@ import { VolumeUpIcon, VolumeDownIcon, VolumeMuteIcon } from '../../icons' import './VolumeControl.css' export const VolumeControl: React.FC = () => { - const { videoState, setVolume, toggleMute } = usePlayerContext() + const { videoState, setVolume, toggleMute, translations } = usePlayerContext() const [showSlider, setShowSlider] = useState(false) const timeoutRef = useRef(undefined) @@ -30,6 +30,7 @@ export const VolumeControl: React.FC = () => { ) const VolumeIcon = videoState.muted ? VolumeMuteIcon : videoState.volume > 0.5 ? VolumeUpIcon : VolumeDownIcon + const actionLabel = videoState.muted ? translations.unmute : translations.mute return (
{ @@ -55,7 +56,7 @@ export const VolumeControl: React.FC = () => { value={videoState.muted ? 0 : videoState.volume} onChange={handleSliderChange} className="volume-slider" - aria-label="Volume" + aria-label={translations.volume} />
= ({
{translations.speed} - {videoState.playbackRate === 1 ? 'Normal' : `${videoState.playbackRate}x`} + {videoState.playbackRate === 1 ? translations.normal : `${videoState.playbackRate}x`}
@@ -148,7 +148,7 @@ export const SettingsMenu: React.FC = ({ setTimeout(() => goBack(), 150) }} > - {rate === 1 ? 'Normal' : `${rate}x`} + {rate === 1 ? translations.normal : `${rate}x`} {videoState.playbackRate === rate && } ))} diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 655cab0..4294d63 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -14,6 +14,17 @@ export interface Translations { audioTrack: string; settings: string; level: string; + play: string; + pause: string; + mute: string; + unmute: string; + enterFullscreen: string; + exitFullscreen: string; + enterPictureInPicture: string; + exitPictureInPicture: string; + videoProgress: string; + volume: string; + live: string; } export const translations: Record = { @@ -29,6 +40,17 @@ export const translations: Record = { audioTrack: 'Audio Track', settings: 'Settings', level: "Level", + play: 'Play', + pause: 'Pause', + mute: 'Mute', + unmute: 'Unmute', + enterFullscreen: 'Enter fullscreen', + exitFullscreen: 'Exit fullscreen', + enterPictureInPicture: 'Enter picture-in-picture', + exitPictureInPicture: 'Exit picture-in-picture', + videoProgress: 'Video progress', + volume: 'Volume', + live: 'LIVE', }, tr: { noSubtitlesAvailable: 'Altyazı mevcut değil', @@ -42,6 +64,17 @@ export const translations: Record = { audioTrack: 'Ses', settings: 'Ayarlar', level: "Seviye", + play: 'Oynat', + pause: 'Duraklat', + mute: 'Sesi kapat', + unmute: 'Sesi aç', + enterFullscreen: 'Tam ekrana gir', + exitFullscreen: 'Tam ekrandan çık', + enterPictureInPicture: 'Resim içinde resme gir', + exitPictureInPicture: 'Resim içinde resimden çık', + videoProgress: 'Video ilerlemesi', + volume: 'Ses', + live: 'CANLI', }, }; diff --git a/src/utils/hlsLoader.ts b/src/utils/hlsLoader.ts index dd2c8a0..c2fe6c9 100644 --- a/src/utils/hlsLoader.ts +++ b/src/utils/hlsLoader.ts @@ -5,6 +5,7 @@ import type { AudioTrack, VideoQuality, SubtitleTrack } from '../types' import { getTranslations, detectBrowserLanguage } from '../i18n' +import { logger } from './logger' // Re-export control functions for backward compatibility export { setHlsQualityLevel, setHlsAudioTrack } from './hlsControl' @@ -47,20 +48,20 @@ const loadHlsFromCDN = (): Promise => { */ export const loadHls = async (): Promise => { try { - console.log('[HLS Loader] Attempting to load from npm package...') + logger.log('[HLS Loader] Attempting to load from npm package...') // Try loading from npm package first const hlsModule = await import('hls.js') - console.log('[HLS Loader] Successfully loaded from npm package') + logger.log('[HLS Loader] Successfully loaded from npm package') return hlsModule.default } catch (npmError) { - console.warn('[HLS Loader] Failed to load from npm, trying CDN...', npmError) + logger.warn('[HLS Loader] Failed to load from npm, trying CDN...', npmError) try { // Fallback to CDN const Hls = await loadHlsFromCDN() - console.log('[HLS Loader] Successfully loaded from CDN') + logger.log('[HLS Loader] Successfully loaded from CDN') return Hls } catch (cdnError) { - console.error('[HLS Loader] Failed to load from CDN:', cdnError) + logger.error('[HLS Loader] Failed to load from CDN:', cdnError) throw new Error('Unable to load HLS.js library. HLS streaming is not available.') } } @@ -87,17 +88,17 @@ export const hasNativeHlsSupport = (): boolean => { export const getHlsAudioTracks = (hls: any): AudioTrack[] => { try { if (!hls) { - console.warn('[HLS Loader] getHlsAudioTracks: No HLS instance provided') + logger.warn('[HLS Loader] getHlsAudioTracks: No HLS instance provided') return [] } // Check if audioTracks property exists if (!hls.audioTracks || !Array.isArray(hls.audioTracks)) { - console.warn('[HLS Loader] getHlsAudioTracks: No audioTracks array found on HLS instance') + logger.warn('[HLS Loader] getHlsAudioTracks: No audioTracks array found on HLS instance') return [] } - console.log('[HLS Loader] getHlsAudioTracks: Raw audioTracks from HLS:', hls.audioTracks) + logger.log('[HLS Loader] getHlsAudioTracks: Raw audioTracks from HLS:', hls.audioTracks) const audioTracks: AudioTrack[] = hls.audioTracks.map((track: any, index: number) => { const audioTrack = { @@ -111,10 +112,10 @@ export const getHlsAudioTracks = (hls: any): AudioTrack[] => { return audioTrack }) - console.log('[HLS Loader] getHlsAudioTracks: Processed tracks:', audioTracks) + logger.log('[HLS Loader] getHlsAudioTracks: Processed tracks:', audioTracks) return audioTracks } catch (error) { - console.error('[HLS Loader] getHlsAudioTracks: Error extracting audio tracks:', error) + logger.error('[HLS Loader] getHlsAudioTracks: Error extracting audio tracks:', error) return [] } } @@ -154,16 +155,16 @@ export const getHlsSubtitleTracks = (hls: any): SubtitleTrack[] => { export const getHlsQualities = (hls: any): VideoQuality[] => { try { if (!hls) { - console.warn('[HLS Loader] getHlsQualities: No HLS instance provided') + logger.warn('[HLS Loader] getHlsQualities: No HLS instance provided') return [] } if (!Array.isArray(hls.levels)) { - console.warn('[HLS Loader] getHlsQualities: No levels array found on HLS instance') + logger.warn('[HLS Loader] getHlsQualities: No levels array found on HLS instance') return [] } - console.log('[HLS Loader] getHlsQualities: Raw levels from HLS:', hls.levels) + logger.log('[HLS Loader] getHlsQualities: Raw levels from HLS:', hls.levels) const qualities: VideoQuality[] = hls.levels.map((level: any, index: number) => { const resolution = typeof level.attrs?.RESOLUTION === 'string' ? level.attrs.RESOLUTION : undefined @@ -205,11 +206,12 @@ export const getHlsQualities = (hls: any): VideoQuality[] => { return (b.bitrate || 0) - (a.bitrate || 0) }) - console.log('[HLS Loader] getHlsQualities: Processed qualities:', sortedQualities) + logger.log('[HLS Loader] getHlsQualities: Processed qualities:', sortedQualities) return sortedQualities } catch (error) { - console.error('[HLS Loader] getHlsQualities: Error extracting qualities:', error) + logger.error('[HLS Loader] getHlsQualities: Error extracting qualities:', error) return [] } } + diff --git a/src/utils/hlsSetup.ts b/src/utils/hlsSetup.ts index 4890ca2..4101122 100644 --- a/src/utils/hlsSetup.ts +++ b/src/utils/hlsSetup.ts @@ -3,6 +3,7 @@ */ import type { AudioTrack, VideoQuality, SubtitleTrack } from '../types' +import { logger } from './logger' interface HlsSetupOptions { video: HTMLVideoElement @@ -30,7 +31,7 @@ export const setupHlsInstance = async ({ throw new Error('HLS.js is not supported in this browser') } - console.log('[HLS Setup] Creating HLS instance for:', src) + logger.log('[HLS Setup] Creating HLS instance for:', src) const hls = new Hls({ enableWorker: true, @@ -44,10 +45,10 @@ export const setupHlsInstance = async ({ let manifestParsedHandled = false hls.on(Hls.Events.MANIFEST_PARSED, () => { - console.log('[HLS Setup] MANIFEST_PARSED event fired') + logger.log('[HLS Setup] MANIFEST_PARSED event fired') if (manifestParsedHandled) { - console.warn('[HLS Setup] MANIFEST_PARSED already handled, skipping') + logger.warn('[HLS Setup] MANIFEST_PARSED already handled, skipping') return } manifestParsedHandled = true @@ -58,23 +59,23 @@ export const setupHlsInstance = async ({ const qualities = getHlsQualities(hls) const subtitles = getHlsSubtitleTracks(hls) - console.log('[HLS Setup] Detected tracks:', { + logger.log('[HLS Setup] Detected tracks:', { audioTracks: tracks.length, qualities: qualities.length, subtitles: subtitles.length }) if (tracks.length > 0) { - console.log('[HLS Setup] Loading audio tracks:', tracks) + logger.log('[HLS Setup] Loading audio tracks:', tracks) onAudioTracksLoaded?.(tracks) } if (subtitles.length > 0) { - console.log('[HLS Setup] Loading subtitle tracks:', subtitles) + logger.log('[HLS Setup] Loading subtitle tracks:', subtitles) onSubtitleTracksLoaded?.(subtitles) } - console.log('[HLS Setup] Loading quality levels:', qualities) + logger.log('[HLS Setup] Loading quality levels:', qualities) onQualityLevelsLoaded?.(qualities) } @@ -93,14 +94,14 @@ export const setupHlsInstance = async ({ hls.on(Hls.Events.LEVEL_LOADED, () => { const qualities = getHlsQualities(hls) if (qualities.length > 0) { - console.log('[HLS Setup] LEVEL_LOADED - Qualities available:', qualities.length) + logger.log('[HLS Setup] LEVEL_LOADED - Qualities available:', qualities.length) onQualityLevelsLoaded?.(qualities) } }) hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => { const tracks = getHlsAudioTracks(hls) - console.log('[HLS Setup] AUDIO_TRACKS_UPDATED event:', tracks.length, 'tracks') + logger.log('[HLS Setup] AUDIO_TRACKS_UPDATED event:', tracks.length, 'tracks') if (tracks.length > 0) { onAudioTracksLoaded?.(tracks) } @@ -108,7 +109,7 @@ export const setupHlsInstance = async ({ hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, () => { const subtitles = getHlsSubtitleTracks(hls) - console.log('[HLS Setup] SUBTITLE_TRACKS_UPDATED event:', subtitles.length, 'tracks') + logger.log('[HLS Setup] SUBTITLE_TRACKS_UPDATED event:', subtitles.length, 'tracks') if (subtitles.length > 0) { onSubtitleTracksLoaded?.(subtitles) } @@ -139,3 +140,4 @@ export const setupHlsInstance = async ({ delete (video as any).__hlsInstance } } + diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..aeb0bf2 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,9 @@ +type LoggerMethod = (..._args: unknown[]) => void + +const noop: LoggerMethod = () => undefined + +export const logger = { + log: noop, + warn: noop, + error: noop, +} diff --git a/src/utils/mpegtsLoader.ts b/src/utils/mpegtsLoader.ts index 14fb8e9..a5813bd 100644 --- a/src/utils/mpegtsLoader.ts +++ b/src/utils/mpegtsLoader.ts @@ -2,6 +2,7 @@ * MPEG-TS loader utility * Dynamically loads mpegts.js library */ +import { logger } from './logger' export interface MpegtsConfig { enableWorker?: boolean @@ -29,26 +30,26 @@ let loadingPromise: Promise | null = null export const loadMpegts = async (): Promise => { // Return cached instance if available if (mpegtsInstance) { - console.log('[MPEG-TS Loader] Using cached mpegts.js instance') + logger.log('[MPEG-TS Loader] Using cached mpegts.js instance') return mpegtsInstance } // Return existing loading promise if already loading if (loadingPromise) { - console.log('[MPEG-TS Loader] Already loading, waiting for existing promise...') + logger.log('[MPEG-TS Loader] Already loading, waiting for existing promise...') return loadingPromise } // Start loading loadingPromise = (async () => { try { - console.log('[MPEG-TS Loader] Attempting to load from npm package...') + logger.log('[MPEG-TS Loader] Attempting to load from npm package...') const module = await import('mpegts.js') mpegtsInstance = module.default || module - console.log('[MPEG-TS Loader] Successfully loaded from npm package') + logger.log('[MPEG-TS Loader] Successfully loaded from npm package') return mpegtsInstance } catch (error) { - console.error('[MPEG-TS Loader] Failed to load mpegts.js:', error) + logger.error('[MPEG-TS Loader] Failed to load mpegts.js:', error) throw new Error('Failed to load mpegts.js. Make sure it is installed: npm install mpegts.js') } finally { loadingPromise = null @@ -103,5 +104,6 @@ export const getMpegtsInstance = (): any | null => { export const clearMpegtsCache = (): void => { mpegtsInstance = null loadingPromise = null - console.log('[MPEG-TS Loader] Cache cleared') + logger.log('[MPEG-TS Loader] Cache cleared') } + diff --git a/src/utils/mpegtsSetup.ts b/src/utils/mpegtsSetup.ts index 97ce01c..ba09e5b 100644 --- a/src/utils/mpegtsSetup.ts +++ b/src/utils/mpegtsSetup.ts @@ -5,6 +5,7 @@ import { loadMpegts, isMpegtsSupported, createDefaultMpegtsConfig } from './mpegtsLoader' import { isLiveStream } from './videoProtocol' +import { logger } from './logger' export interface MpegtsSetupOptions { video: HTMLVideoElement @@ -41,7 +42,7 @@ export const setupMpegtsInstance = async ({ throw error } - console.log('[MPEG-TS Setup] Creating player instance for:', src) + logger.log('[MPEG-TS Setup] Creating player instance for:', src) // Detect if stream is live const isLive = isLiveStream(src) @@ -72,40 +73,40 @@ export const setupMpegtsInstance = async ({ // Event handlers player.on(mpegts.Events.ERROR, (errorType: string, errorDetail: string, errorInfo: any) => { - console.error('mpegts.js error:', { errorType, errorDetail, errorInfo }) + logger.error('mpegts.js error:', { errorType, errorDetail, errorInfo }) const error = new Error(`MPEG-TS Player Error: ${errorType} - ${errorDetail}`) // Handle specific error types if (errorType === mpegts.ErrorTypes.NETWORK_ERROR) { - console.error('Network error occurred:', errorDetail) + logger.error('Network error occurred:', errorDetail) // Attempt recovery for recoverable network errors if ( errorDetail === mpegts.ErrorDetails.NETWORK_EXCEPTION || errorDetail === mpegts.ErrorDetails.NETWORK_STATUS_CODE_INVALID ) { - console.log('Attempting to recover from network error...') + logger.log('Attempting to recover from network error...') try { player.unload() player.load() return } catch (recoveryError) { - console.error('Failed to recover from network error:', recoveryError) + logger.error('Failed to recover from network error:', recoveryError) } } } else if (errorType === mpegts.ErrorTypes.MEDIA_ERROR) { - console.error('Media error occurred:', errorDetail) + logger.error('Media error occurred:', errorDetail) // Some media errors are recoverable if (errorDetail === mpegts.ErrorDetails.MEDIA_MSE_ERROR) { - console.log('Attempting to recover from media error...') + logger.log('Attempting to recover from media error...') try { player.unload() player.load() return } catch (recoveryError) { - console.error('Failed to recover from media error:', recoveryError) + logger.error('Failed to recover from media error:', recoveryError) } } } @@ -117,15 +118,15 @@ export const setupMpegtsInstance = async ({ }) player.on(mpegts.Events.LOADING_COMPLETE, () => { - console.log('mpegts.js: Loading complete') + logger.log('mpegts.js: Loading complete') }) player.on(mpegts.Events.RECOVERED_EARLY_EOF, () => { - console.log('mpegts.js: Recovered from early EOF') + logger.log('mpegts.js: Recovered from early EOF') }) player.on(mpegts.Events.METADATA_ARRIVED, (metadata: any) => { - console.log('mpegts.js: Metadata arrived', metadata) + logger.log('mpegts.js: Metadata arrived', metadata) // Trigger onLoadedMetadata callback if (onLoadedMetadata) { @@ -145,7 +146,7 @@ export const setupMpegtsInstance = async ({ try { await video.play() } catch (playError) { - console.warn('Autoplay failed:', playError) + logger.warn('Autoplay failed:', playError) // Autoplay might be blocked by browser, ignore error } } @@ -153,7 +154,7 @@ export const setupMpegtsInstance = async ({ // Return cleanup function return () => { try { - console.log('Cleaning up mpegts.js player...') + logger.log('Cleaning up mpegts.js player...') // Remove event listeners player.off(mpegts.Events.ERROR) @@ -176,11 +177,11 @@ export const setupMpegtsInstance = async ({ delete (video as any).__mpegtsInstance delete (video as any).__mpegtsStats } catch (cleanupError) { - console.error('Error during mpegts.js cleanup:', cleanupError) + logger.error('Error during mpegts.js cleanup:', cleanupError) } } } catch (error) { - console.error('Failed to setup mpegts.js player:', error) + logger.error('Failed to setup mpegts.js player:', error) const setupError = error instanceof Error ? error : new Error('Failed to setup MPEG-TS player') @@ -225,3 +226,4 @@ export const getMpegtsStats = (video: HTMLVideoElement | null): any | null => { export const hasMpegtsInstance = (video: HTMLVideoElement | null): boolean => { return getMpegtsInstance(video) !== null } + diff --git a/src/utils/rtmpLoader.ts b/src/utils/rtmpLoader.ts index af5fb3d..e35c0fc 100644 --- a/src/utils/rtmpLoader.ts +++ b/src/utils/rtmpLoader.ts @@ -3,6 +3,7 @@ * Loads flv.js library with NPM fallback to CDN strategy * Mirrors the HLS loader pattern for consistency */ +import { logger } from './logger' const FLVJS_CDN_URL = 'https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js' @@ -48,14 +49,14 @@ export const loadFlvjs = async (): Promise => { const flvModule = await import('flv.js') return flvModule.default || flvModule } catch (npmError) { - console.warn('flv.js NPM package not available, loading from CDN...', npmError) + logger.warn('flv.js NPM package not available, loading from CDN...', npmError) try { // Fallback to CDN const flvjs = await loadFlvjsFromCDN() return flvjs } catch (cdnError) { - console.error('Failed to load flv.js from both NPM and CDN', cdnError) + logger.error('Failed to load flv.js from both NPM and CDN', cdnError) throw new Error( 'Failed to load flv.js library. Please ensure flv.js is available or check your network connection.' ) @@ -162,7 +163,8 @@ export const extractFlvQualityInfo = (player: any): { audioBitrate: stats.audioBitrate, } } catch (error) { - console.warn('Failed to extract flv.js quality info:', error) + logger.warn('Failed to extract flv.js quality info:', error) return null } } + diff --git a/src/utils/rtmpSetup.ts b/src/utils/rtmpSetup.ts index 816d528..6b053bb 100644 --- a/src/utils/rtmpSetup.ts +++ b/src/utils/rtmpSetup.ts @@ -6,6 +6,7 @@ import { loadFlvjs, isFlvjsSupported, createDefaultFlvConfig } from './rtmpLoader' import { isLiveStream } from './videoProtocol' +import { logger } from './logger' export interface RtmpSetupOptions { video: HTMLVideoElement @@ -50,7 +51,7 @@ export const setupRtmpInstance = async ({ if (src.startsWith('rtmp://') || src.startsWith('rtmps://')) { // For RTMP URLs, flv.js expects HTTP-FLV endpoint // This is a limitation - direct RTMP playback requires server-side conversion - console.warn( + logger.warn( 'Direct RTMP playback requires an HTTP-FLV proxy. Please ensure your RTMP stream is available via HTTP-FLV.' ) type = 'flv' @@ -84,40 +85,40 @@ export const setupRtmpInstance = async ({ // Event handlers player.on(flvjs.Events.ERROR, (errorType: string, errorDetail: string, errorInfo: any) => { - console.error('flv.js error:', { errorType, errorDetail, errorInfo }) + logger.error('flv.js error:', { errorType, errorDetail, errorInfo }) const error = new Error(`FLV Player Error: ${errorType} - ${errorDetail}`) // Handle specific error types if (errorType === flvjs.ErrorTypes.NETWORK_ERROR) { - console.error('Network error occurred:', errorDetail) + logger.error('Network error occurred:', errorDetail) // Attempt recovery for recoverable network errors if ( errorDetail === flvjs.ErrorDetails.NETWORK_EXCEPTION || errorDetail === flvjs.ErrorDetails.NETWORK_STATUS_CODE_INVALID ) { - console.log('Attempting to recover from network error...') + logger.log('Attempting to recover from network error...') try { player.unload() player.load() return } catch (recoveryError) { - console.error('Failed to recover from network error:', recoveryError) + logger.error('Failed to recover from network error:', recoveryError) } } } else if (errorType === flvjs.ErrorTypes.MEDIA_ERROR) { - console.error('Media error occurred:', errorDetail) + logger.error('Media error occurred:', errorDetail) // Some media errors are recoverable if (errorDetail === flvjs.ErrorDetails.MEDIA_MSE_ERROR) { - console.log('Attempting to recover from media error...') + logger.log('Attempting to recover from media error...') try { player.unload() player.load() return } catch (recoveryError) { - console.error('Failed to recover from media error:', recoveryError) + logger.error('Failed to recover from media error:', recoveryError) } } } @@ -129,15 +130,15 @@ export const setupRtmpInstance = async ({ }) player.on(flvjs.Events.LOADING_COMPLETE, () => { - console.log('flv.js: Loading complete') + logger.log('flv.js: Loading complete') }) player.on(flvjs.Events.RECOVERED_EARLY_EOF, () => { - console.log('flv.js: Recovered from early EOF') + logger.log('flv.js: Recovered from early EOF') }) player.on(flvjs.Events.METADATA_ARRIVED, (metadata: any) => { - console.log('flv.js: Metadata arrived', metadata) + logger.log('flv.js: Metadata arrived', metadata) // Trigger onLoadedMetadata callback if (onLoadedMetadata) { @@ -158,7 +159,7 @@ export const setupRtmpInstance = async ({ try { await video.play() } catch (playError) { - console.warn('Autoplay failed:', playError) + logger.warn('Autoplay failed:', playError) // Autoplay might be blocked by browser, ignore error } } @@ -166,7 +167,7 @@ export const setupRtmpInstance = async ({ // Return cleanup function return () => { try { - console.log('Cleaning up flv.js player...') + logger.log('Cleaning up flv.js player...') // Remove event listeners player.off(flvjs.Events.ERROR) @@ -189,11 +190,11 @@ export const setupRtmpInstance = async ({ delete (video as any).__rtmpInstance delete (video as any).__rtmpStats } catch (cleanupError) { - console.error('Error during flv.js cleanup:', cleanupError) + logger.error('Error during flv.js cleanup:', cleanupError) } } } catch (error) { - console.error('Failed to setup flv.js player:', error) + logger.error('Failed to setup flv.js player:', error) const setupError = error instanceof Error ? error : new Error('Failed to setup RTMP/FLV player') @@ -238,3 +239,4 @@ export const getRtmpStats = (video: HTMLVideoElement | null): any | null => { export const hasRtmpInstance = (video: HTMLVideoElement | null): boolean => { return getRtmpInstance(video) !== null } +