export class StreamCarousel { constructor() { this.track = document.getElementById('carousel-track'); this.prevBtn = document.getElementById('carousel-prev'); this.nextBtn = document.getElementById('carousel-next'); this.counter = document.getElementById('carousel-counter'); this.dotsContainer = document.getElementById('carousel-dots'); this.streams = []; this.currentIndex = 0; this.onUseCallback = null; } render(streams, onUseCallback) { this.streams = streams; this.onUseCallback = onUseCallback; this.currentIndex = Math.min(this.currentIndex, streams.length - 1); // Render stream cards this.track.innerHTML = streams.map((stream, index) => this.renderCard(stream, index)).join(''); // Render dots this.dotsContainer.innerHTML = streams.map((_, index) => `` ).join(''); // Attach event listeners this.attachEventListeners(); // Update view this.updateView(); } renderCard(stream, index) { const icon = this.getStreamIcon(stream.type); return `
${icon} ${stream.type}
${this.truncateURL(stream.url)}
${stream.resolution ? `
Resolution: ${stream.resolution}
` : ''} ${stream.codec ? `
Codec: ${stream.codec}${stream.fps ? ` • ${stream.fps} fps` : ''}${stream.bitrate ? ` • ${Math.round(stream.bitrate / 1000)} Kbps` : ''}
` : ''} ${stream.has_audio ? '
Audio: Yes
' : ''}
`; } getStreamIcon(type) { const icons = { 'FFMPEG': '', 'ONVIF': '', 'JPEG': '', 'MJPEG': '', 'HLS': '', 'HTTP_VIDEO': '' }; return icons[type] || icons['FFMPEG']; } truncateURL(url) { if (url.length > 50) { return url.substring(0, 47) + '...'; } return url; } attachEventListeners() { // Use buttons this.track.querySelectorAll('.btn-use').forEach(btn => { btn.addEventListener('click', (e) => { const index = parseInt(e.target.dataset.index); if (this.onUseCallback) { this.onUseCallback(this.streams[index], index); } }); }); // Dots this.dotsContainer.querySelectorAll('.carousel-dot').forEach(dot => { dot.addEventListener('click', (e) => { const index = parseInt(e.target.dataset.index); this.goTo(index); }); }); // Touch gestures let touchStartX = 0; let touchEndX = 0; this.track.addEventListener('touchstart', (e) => { touchStartX = e.changedTouches[0].screenX; }); this.track.addEventListener('touchend', (e) => { touchEndX = e.changedTouches[0].screenX; this.handleSwipe(touchStartX, touchEndX); }); } handleSwipe(startX, endX) { const swipeThreshold = 50; const diff = startX - endX; if (Math.abs(diff) > swipeThreshold) { if (diff > 0) { this.next(); } else { this.prev(); } } } prev() { if (this.currentIndex > 0) { this.goTo(this.currentIndex - 1); } } next() { if (this.currentIndex < this.streams.length - 1) { this.goTo(this.currentIndex + 1); } } goTo(index) { if (index < 0 || index >= this.streams.length) return; this.currentIndex = index; this.updateView(); } updateView() { // Update track position const offset = -100 * this.currentIndex; this.track.style.transform = `translateX(${offset}%)`; // Update counter this.counter.textContent = `Stream ${this.currentIndex + 1} of ${this.streams.length}`; // Update dots this.dotsContainer.querySelectorAll('.carousel-dot').forEach((dot, i) => { dot.classList.toggle('active', i === this.currentIndex); }); // Update arrow buttons this.prevBtn.disabled = this.currentIndex === 0; this.nextBtn.disabled = this.currentIndex === this.streams.length - 1; } }