Examples
This section provides practical examples of using the M3U8 Online Player for various scenarios and use cases.
Basic Usage Examples
Example 1: Simple Stream Loading
// Load a basic M3U8 stream
const loadBasicStream = async () => {
const streamUrl =
'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8';
try {
await loadStream(streamUrl);
console.log('Stream loaded successfully');
} catch (error) {
console.error('Failed to load stream:', error);
}
};
Example 2: Stream with Custom Controls
// Load stream with custom playback controls
const loadStreamWithControls = async (url: string) => {
await loadStream(url);
// Set custom volume
updateVolume(75);
// Auto-play if desired
if (videoPlayer.value) {
videoPlayer.value.play();
}
};
Example 3: Error Handling
// Comprehensive error handling example
const loadStreamWithErrorHandling = async (url: string) => {
try {
// Validate URL first
if (!isValidUrl(url)) {
throw new Error('Invalid URL format');
}
await loadStream(url);
// Set up error listeners
if (videoPlayer.value) {
videoPlayer.value.addEventListener('error', (event) => {
const error = event.target?.error;
console.error('Video error:', error);
// Handle specific error codes
switch (error?.code) {
case 1: // MEDIA_ERR_ABORTED
console.log('Stream loading was aborted');
break;
case 2: // MEDIA_ERR_NETWORK
console.log('Network error occurred');
break;
case 3: // MEDIA_ERR_DECODE
console.log('Stream decoding error');
break;
case 4: // MEDIA_ERR_SRC_NOT_SUPPORTED
console.log('Stream format not supported');
break;
}
});
}
} catch (error) {
console.error('Stream loading failed:', error);
// Show user-friendly error message
toast.error('Failed to load stream. Please check the URL and try again.');
}
};
Advanced Examples
Example 4: Stream Quality Detection
// Detect and display stream quality information
const analyzeStreamQuality = async (url: string) => {
await loadStream(url);
if (videoPlayer.value) {
videoPlayer.value.addEventListener('loadedmetadata', () => {
const video = videoPlayer.value!;
const quality = {
width: video.videoWidth,
height: video.videoHeight,
duration: video.duration,
bitrate: estimateBitrate(video),
};
console.log('Stream quality:', quality);
// Update UI with quality information
streamInfo.value = {
resolution: `${quality.width}x${quality.height}`,
duration: formatTime(quality.duration),
bitrate: `${quality.bitrate} kbps`,
};
});
}
};
const estimateBitrate = (video: HTMLVideoElement): number => {
// Simple bitrate estimation based on file size and duration
// This is a basic example - real implementation would be more complex
return Math.round((video.duration * 1000) / 8); // Rough estimate
};
Example 5: History Management
// Advanced history management with filtering
const manageStreamHistory = () => {
// Get history records
const history = historyRecords.value;
// Filter by date range
const recentHistory = history.filter((record) => {
const recordDate = new Date(record.timestamp);
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
return recordDate > weekAgo;
});
// Group by domain
const historyByDomain = history.reduce(
(groups, record) => {
try {
const domain = new URL(record.url).hostname;
if (!groups[domain]) {
groups[domain] = [];
}
groups[domain].push(record);
} catch (error) {
// Handle invalid URLs
console.warn('Invalid URL in history:', record.url);
}
return groups;
},
{} as Record<string, HistoryRecord[]>,
);
console.log('Recent history:', recentHistory);
console.log('History by domain:', historyByDomain);
};
Example 6: Custom Event Handling
// Custom event system for stream management
class StreamEventManager {
private listeners: Map<string, Function[]> = new Map();
on(event: string, callback: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(callback);
}
emit(event: string, data?: any) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach((callback) => callback(data));
}
off(event: string, callback: Function) {
const callbacks = this.listeners.get(event) || [];
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
// Usage
const eventManager = new StreamEventManager();
eventManager.on('streamLoaded', (url) => {
console.log('Stream loaded:', url);
// Update UI, analytics, etc.
});
eventManager.on('streamError', (error) => {
console.error('Stream error:', error);
// Handle error, show notification, etc.
});
// Emit events when appropriate
const loadStreamWithEvents = async (url: string) => {
try {
await loadStream(url);
eventManager.emit('streamLoaded', url);
} catch (error) {
eventManager.emit('streamError', error);
}
};
Integration Examples
Example 7: React Component Integration
import React, { useEffect, useRef, useState } from 'react';
interface M3U8PlayerProps {
url?: string;
autoplay?: boolean;
onLoad?: (url: string) => void;
onError?: (error: string) => void;
}
const M3U8Player: React.FC<M3U8PlayerProps> = ({ url, autoplay = false, onLoad, onError }) => {
const videoRef = useRef<HTMLVideoElement>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [isPlaying, setIsPlaying] = useState(false);
useEffect(() => {
if (url && videoRef.current) {
loadStream(url);
}
}, [url]);
const loadStream = async (streamUrl: string) => {
setIsLoading(true);
setError('');
try {
if (videoRef.current) {
videoRef.current.src = streamUrl;
onLoad?.(streamUrl);
if (autoplay) {
await videoRef.current.play();
}
}
} catch (err: any) {
const errorMessage = err.message || 'Failed to load stream';
setError(errorMessage);
onError?.(errorMessage);
} finally {
setIsLoading(false);
}
};
const togglePlayPause = () => {
if (videoRef.current) {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
}
};
return (
<div className="m3u8-player">
<div className="player-container">
<video
ref={videoRef}
controls
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onError={(e) => {
const error = e.currentTarget.error;
const errorMessage = error?.message || 'Playback error';
setError(errorMessage);
onError?.(errorMessage);
}}
/>
{isLoading && (
<div className="loading-overlay">
<div className="spinner">Loading...</div>
</div>
)}
{error && (
<div className="error-overlay">
<div className="error-message">{error}</div>
</div>
)}
</div>
<div className="controls">
<button onClick={togglePlayPause}>{isPlaying ? 'Pause' : 'Play'}</button>
</div>
</div>
);
};
export default M3U8Player;
Example 8: Vue Composable
// composables/useM3U8Player.ts
import { ref, computed, onMounted, onUnmounted } from 'vue';
export const useM3U8Player = () => {
const videoPlayer = ref<HTMLVideoElement>();
const currentUrl = ref('');
const isLoading = ref(false);
const error = ref('');
const isPlaying = ref(false);
const volume = ref(100);
const currentTime = ref(0);
const duration = ref(0);
const loadStream = async (url: string) => {
if (!isValidUrl(url)) {
throw new Error('Invalid URL format');
}
isLoading.value = true;
error.value = '';
try {
currentUrl.value = url;
if (videoPlayer.value) {
videoPlayer.value.src = url;
}
} catch (err: any) {
error.value = err.message || 'Failed to load stream';
throw err;
} finally {
isLoading.value = false;
}
};
const togglePlayPause = () => {
if (!videoPlayer.value) return;
if (isPlaying.value) {
videoPlayer.value.pause();
} else {
videoPlayer.value.play();
}
};
const updateVolume = (newVolume: number) => {
if (!videoPlayer.value) return;
videoPlayer.value.volume = newVolume / 100;
volume.value = newVolume;
};
const setupEventListeners = () => {
if (!videoPlayer.value) return;
const video = videoPlayer.value;
video.addEventListener('play', () => {
isPlaying.value = true;
});
video.addEventListener('pause', () => {
isPlaying.value = false;
});
video.addEventListener('loadedmetadata', () => {
duration.value = video.duration;
volume.value = Math.round(video.volume * 100);
});
video.addEventListener('timeupdate', () => {
currentTime.value = video.currentTime;
});
video.addEventListener('error', (event) => {
const target = event.target as HTMLVideoElement;
error.value = target.error?.message || 'Playback error';
});
};
onMounted(() => {
setupEventListeners();
});
return {
videoPlayer,
currentUrl,
isLoading,
error,
isPlaying,
volume,
currentTime,
duration,
loadStream,
togglePlayPause,
updateVolume,
};
};
Example 9: URL Parameter Integration
// Load stream from URL parameters
const loadFromUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
// Get stream URL
const streamUrl = urlParams.get('stream');
if (streamUrl && isValidUrl(streamUrl)) {
loadStream(streamUrl);
}
// Get configuration
const config = {
autoplay: urlParams.get('autoplay') === 'true',
volume: parseInt(urlParams.get('volume') || '100'),
muted: urlParams.get('muted') === 'true',
};
// Apply configuration
if (videoPlayer.value) {
videoPlayer.value.volume = config.volume / 100;
videoPlayer.value.muted = config.muted;
if (config.autoplay) {
videoPlayer.value.autoplay = true;
}
}
return config;
};
// Usage: /m3u8-player?stream=https://example.com/stream.m3u8&autoplay=true&volume=75
Testing Examples
Example 10: Unit Testing
// tests/m3u8Player.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { loadStream, isValidUrl, formatTime } from '../m3u8Player';
describe('M3U8 Player', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('URL Validation', () => {
it('should validate correct M3U8 URLs', () => {
const validUrls = [
'https://example.com/stream.m3u8',
'https://cdn.example.com/playlist.m3u8',
'http://localhost:8080/stream.m3u8',
];
validUrls.forEach((url) => {
expect(isValidUrl(url)).toBe(true);
});
});
it('should reject invalid URLs', () => {
const invalidUrls = [
'not-a-url',
'ftp://example.com/stream.m3u8',
'https://example.com/stream.mp4',
'',
];
invalidUrls.forEach((url) => {
expect(isValidUrl(url)).toBe(false);
});
});
});
describe('Time Formatting', () => {
it('should format time correctly', () => {
expect(formatTime(0)).toBe('00:00');
expect(formatTime(65)).toBe('01:05');
expect(formatTime(3661)).toBe('01:01:01');
expect(formatTime(NaN)).toBe('00:00');
});
});
describe('Stream Loading', () => {
it('should load valid streams', async () => {
const mockUrl = 'https://example.com/stream.m3u8';
// Mock fetch for testing
global.fetch = vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve('#EXTM3U\n#EXTINF:10,segment1.ts'),
});
await expect(loadStream(mockUrl)).resolves.not.toThrow();
});
it('should handle loading errors', async () => {
const invalidUrl = 'invalid-url';
await expect(loadStream(invalidUrl)).rejects.toThrow('Invalid URL format');
});
});
});
Example 11: Integration Testing
// tests/integration/m3u8Player.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createApp } from 'vue';
import M3U8Player from '../components/M3U8Player.vue';
describe('M3U8 Player Integration', () => {
let app: any;
let container: HTMLElement;
beforeAll(() => {
container = document.createElement('div');
document.body.appendChild(container);
app = createApp(M3U8Player);
app.mount(container);
});
afterAll(() => {
app.unmount();
document.body.removeChild(container);
});
it('should render player component', () => {
const videoElement = container.querySelector('video');
expect(videoElement).toBeTruthy();
});
it('should handle stream loading', async () => {
const testUrl =
'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8';
// Simulate loading a stream
const loadButton = container.querySelector('[data-testid="load-button"]');
const urlInput = container.querySelector('[data-testid="url-input"]') as HTMLInputElement;
urlInput.value = testUrl;
loadButton?.click();
// Wait for loading to complete
await new Promise((resolve) => setTimeout(resolve, 1000));
const videoElement = container.querySelector('video') as HTMLVideoElement;
expect(videoElement.src).toBe(testUrl);
});
});
These examples demonstrate various ways to use and integrate the M3U8 Online Player, from basic usage to advanced integration scenarios.