diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7b90019..0ec9849 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -7,7 +7,11 @@ "Bash(python check_srt.py:*)", "Bash(python fix_srt.py:*)", "Bash(python:*)", - "Bash(pnpm run build:lib:*)" + "Bash(pnpm run build:lib:*)", + "Bash(npm run build:lib:*)", + "Bash(npm publish)", + "Bash(git push:*)", + "Bash(npm test:*)" ], "deny": [], "ask": [] diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 39cc0a2..5ae8320 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -501,13 +501,14 @@ https://cdn.jsdelivr.net/npm/hls.js@1.5.13 - Media hataları → HLS instance yeniden başlatma - Fatal hataları → Error state'e düşme -#### 3. RTMP/FLV Streaming +#### 3. RTMP/FLV/IPTV Streaming **Desteklenen Protokoller:** - RTMP (rtmp://) - RTMPS (rtmps://) - RTMPT (rtmpt://) - HTTP-FLV (.flv veya flv? query) +- MPEG-TS (.ts) - IPTV streams **Kütüphane:** flv.js (opsiyonel, lazy-loaded) @@ -537,6 +538,12 @@ https://cdn.jsdelivr.net/npm/flv.js@1.6.2 // RTMP (HTTP-FLV proxy gerektirir) + +// IPTV (MPEG-TS) + ``` **HTTP-FLV Proxy Örneği (Node.js):** diff --git a/README.md b/README.md index b43beac..25bee64 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ A feature-rich, modern video player library built with React, TypeScript, and Vi ### 🚀 Advanced Features - **HLS Streaming** - Automatic HLS.js integration for .m3u8 files +- **IPTV Support** - MPEG-TS (.ts) streams for IPTV services - **HTTP Range Request** - Progressive download for large MP4 files - **Subtitles** - WebVTT and SRT support - **Multiple Audio Tracks** - Switch between different audio streams @@ -146,6 +147,15 @@ function App() { /> ``` +### IPTV Streaming + +```tsx + +``` + ### Custom Theme ```tsx @@ -259,7 +269,7 @@ video-player/ | Prop | Type | Default | Description | |------|------|---------|-------------| -| `src` | `string` | **required** | Video source URL (MP4, WebM, HLS) | +| `src` | `string` | **required** | Video source URL (MP4, WebM, HLS, IPTV .ts) | | `poster` | `string` | - | Poster image URL | | `autoplay` | `boolean` | `false` | Auto-play video on load | | `loop` | `boolean` | `false` | Loop video playback | diff --git a/examples/App.tsx b/examples/App.tsx index c73f389..ddf542e 100644 --- a/examples/App.tsx +++ b/examples/App.tsx @@ -58,7 +58,7 @@ function App() {
setVideoUrl(e.target.value)} disabled={useDemo} diff --git a/src/test/mocks/flv.mock.ts b/src/test/mocks/flv.mock.ts new file mode 100644 index 0000000..64aa925 --- /dev/null +++ b/src/test/mocks/flv.mock.ts @@ -0,0 +1,9 @@ +// Mock for flv.js library used in tests +export default { + isSupported: () => true, + getFeatureList: () => ({ + mseSupported: true, + networkStreamIOSupported: true, + httpsSupported: true, + }), +} diff --git a/src/test/mocks/hls.mock.ts b/src/test/mocks/hls.mock.ts new file mode 100644 index 0000000..388b035 --- /dev/null +++ b/src/test/mocks/hls.mock.ts @@ -0,0 +1,36 @@ +// Mock for hls.js library used in tests +export default class Hls { + static isSupported() { + return true + } + + static get Events() { + return { + MEDIA_ATTACHED: 'hlsMediaAttached', + MANIFEST_PARSED: 'hlsManifestParsed', + ERROR: 'hlsError', + } + } + + static get ErrorTypes() { + return { + NETWORK_ERROR: 'networkError', + MEDIA_ERROR: 'mediaError', + OTHER_ERROR: 'otherError', + } + } + + static get ErrorDetails() { + return { + MANIFEST_LOAD_ERROR: 'manifestLoadError', + LEVEL_LOAD_ERROR: 'levelLoadError', + FRAG_LOAD_ERROR: 'fragLoadError', + } + } + + loadSource(_src: string) {} + attachMedia(_video: HTMLVideoElement) {} + destroy() {} + on(_event: string, _handler: Function) {} + off(_event: string, _handler: Function) {} +} diff --git a/src/utils/videoProtocol.test.ts b/src/utils/videoProtocol.test.ts new file mode 100644 index 0000000..4124761 --- /dev/null +++ b/src/utils/videoProtocol.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect } from 'vitest' +import { detectVideoProtocol, isHlsStream, isRtmpStream, isLiveStream } from './videoProtocol' + +describe('videoProtocol', () => { + describe('detectVideoProtocol', () => { + it('should detect MPEG-TS IPTV streams', () => { + const result = detectVideoProtocol('http://favoritv65.xyz:8080/live/Apollon45/HpjWrDa6gWWd/98925.ts') + expect(result.protocol).toBe('hls') + expect(result.isLive).toBe(true) + expect(result.needsSpecialPlayer).toBe(true) + }) + + it('should detect .ts files with query parameters', () => { + const result = detectVideoProtocol('http://example.com/stream/video.ts?token=abc123') + expect(result.protocol).toBe('hls') + expect(result.isLive).toBe(true) + expect(result.needsSpecialPlayer).toBe(true) + }) + + it('should detect HLS streams', () => { + const result = detectVideoProtocol('http://example.com/stream/playlist.m3u8') + expect(result.protocol).toBe('hls') + expect(result.needsSpecialPlayer).toBe(true) + }) + + it('should detect live HLS streams', () => { + const result = detectVideoProtocol('http://example.com/live/stream/playlist.m3u8') + expect(result.protocol).toBe('hls') + expect(result.isLive).toBe(true) + expect(result.needsSpecialPlayer).toBe(true) + }) + + it('should detect RTMP streams', () => { + const result = detectVideoProtocol('rtmp://example.com/live/stream') + expect(result.protocol).toBe('rtmp') + expect(result.isLive).toBe(true) + expect(result.needsSpecialPlayer).toBe(true) + }) + + it('should detect FLV streams', () => { + const result = detectVideoProtocol('http://example.com/stream.flv') + expect(result.protocol).toBe('rtmp') + expect(result.needsSpecialPlayer).toBe(true) + }) + + it('should detect DASH streams', () => { + const result = detectVideoProtocol('http://example.com/stream.mpd') + expect(result.protocol).toBe('dash') + expect(result.needsSpecialPlayer).toBe(true) + }) + + it('should detect native video formats', () => { + const result = detectVideoProtocol('http://example.com/video.mp4') + expect(result.protocol).toBe('native') + expect(result.isLive).toBe(false) + expect(result.needsSpecialPlayer).toBe(false) + }) + + it('should handle empty string', () => { + const result = detectVideoProtocol('') + expect(result.protocol).toBe('native') + expect(result.isLive).toBe(false) + expect(result.needsSpecialPlayer).toBe(false) + }) + + it('should not confuse TypeScript files with transport streams', () => { + // .ts in path but not as extension shouldn't be detected + const result = detectVideoProtocol('http://example.com/ts/video.mp4') + expect(result.protocol).toBe('native') + }) + }) + + describe('isHlsStream', () => { + it('should return true for HLS streams', () => { + expect(isHlsStream('http://example.com/stream.m3u8')).toBe(true) + }) + + it('should return true for IPTV .ts streams', () => { + expect(isHlsStream('http://favoritv65.xyz:8080/live/user/pass/98925.ts')).toBe(true) + }) + + it('should return false for non-HLS streams', () => { + expect(isHlsStream('http://example.com/video.mp4')).toBe(false) + }) + }) + + describe('isRtmpStream', () => { + it('should return true for RTMP streams', () => { + expect(isRtmpStream('rtmp://example.com/live/stream')).toBe(true) + }) + + it('should return true for FLV streams', () => { + expect(isRtmpStream('http://example.com/stream.flv')).toBe(true) + }) + + it('should return false for non-RTMP streams', () => { + expect(isRtmpStream('http://example.com/video.mp4')).toBe(false) + }) + }) + + describe('isLiveStream', () => { + it('should return true for IPTV streams', () => { + expect(isLiveStream('http://favoritv65.xyz:8080/live/user/pass/98925.ts')).toBe(true) + }) + + it('should return true for live HLS streams', () => { + expect(isLiveStream('http://example.com/live/stream.m3u8')).toBe(true) + }) + + it('should return true for RTMP streams', () => { + expect(isLiveStream('rtmp://example.com/live/stream')).toBe(true) + }) + + it('should return false for MP4 files', () => { + expect(isLiveStream('http://example.com/video.mp4')).toBe(false) + }) + }) +}) diff --git a/src/utils/videoProtocol.ts b/src/utils/videoProtocol.ts index 7977f71..4353af1 100644 --- a/src/utils/videoProtocol.ts +++ b/src/utils/videoProtocol.ts @@ -71,6 +71,16 @@ export const detectVideoProtocol = (src: string): ProtocolDetectionResult => { } } + // MPEG-TS (IPTV) detection + // Check for .ts extension (Transport Stream used in IPTV) + if (lowerSrc.includes('.ts') || lowerSrc.match(/\.ts(\?|$)/)) { + return { + protocol: 'hls', // HLS player can handle MPEG-TS streams + isLive: true, // IPTV streams are typically live + needsSpecialPlayer: true, + } + } + // Native HTML5 video formats (MP4, WebM, OGG, etc.) return { protocol: 'native', diff --git a/vitest.config.ts b/vitest.config.ts index 373e6fc..ed11c6d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -20,4 +20,10 @@ export default defineConfig({ ], }, }, + resolve: { + alias: { + 'flv.js': new URL('./src/test/mocks/flv.mock.ts', import.meta.url).pathname, + 'hls.js': new URL('./src/test/mocks/hls.mock.ts', import.meta.url).pathname, + }, + }, });