From becc9efc7f930be9e96c5bfb25409788f6d4d82a Mon Sep 17 00:00:00 2001 From: hibna Date: Tue, 4 Nov 2025 05:24:21 +0300 Subject: [PATCH] Add IPTV (.ts) stream support and tests Introduced detection and support for MPEG-TS (.ts) IPTV streams in videoProtocol, updated documentation and examples to reflect IPTV support, and added comprehensive tests for protocol detection. Mock implementations for flv.js and hls.js were added for testing, and vitest config now aliases these libraries to their mocks. --- .claude/settings.local.json | 6 +- DOCUMENTATION.md | 9 ++- README.md | 12 +++- examples/App.tsx | 2 +- src/test/mocks/flv.mock.ts | 9 +++ src/test/mocks/hls.mock.ts | 36 ++++++++++ src/utils/videoProtocol.test.ts | 118 ++++++++++++++++++++++++++++++++ src/utils/videoProtocol.ts | 10 +++ vitest.config.ts | 6 ++ 9 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 src/test/mocks/flv.mock.ts create mode 100644 src/test/mocks/hls.mock.ts create mode 100644 src/utils/videoProtocol.test.ts 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, + }, + }, });