export class StreamList { constructor() { this.listContainer = document.getElementById('streams-list'); this.streams = []; this.onUseCallback = null; this.expandedIndex = null; } render(streams, onUseCallback) { this.streams = streams; this.onUseCallback = onUseCallback; // Render stream items this.listContainer.innerHTML = streams.map((stream, index) => this.renderItem(stream, index)).join(''); // Attach event listeners this.attachEventListeners(); } renderItem(stream, index) { const icon = this.getStreamIcon(stream.type); const isExpanded = this.expandedIndex === index; const truncatedUrl = this.truncateURL(stream.url, 60); return `
${icon} ${stream.type} ${this.getStreamTypeTooltip(stream.type)}
${truncatedUrl}
${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
' : ''}
`; } truncateURL(url, maxLength = 60) { if (url.length <= maxLength) { return url; } return url.substring(0, maxLength) + '...'; } getStreamIcon(type) { const icons = { 'FFMPEG': '', 'ONVIF': '', 'JPEG': '', 'MJPEG': '', 'HLS': '', 'HTTP_VIDEO': '', 'BUBBLE': '' }; return icons[type] || icons['FFMPEG']; } getStreamTypeTooltip(type) { const tooltips = { 'FFMPEG': `
FFMPEG Stream

Standard video stream decoded by FFmpeg. Most compatible and widely supported format for RTSP, HTTP, and other protocols.

Features:
✓ Universal compatibility ✓ Supports H.264, H.265, MJPEG ✓ Works with most cameras ✓ Best for recording

Best for: Main streams, recording, high-quality playback. Default choice for most use cases.

`, 'ONVIF': `
ONVIF Stream

Industry standard protocol for IP cameras. Discovered via ONVIF specification, ensuring maximum compatibility with camera features.

Features:
✓ Standardized protocol ✓ Auto-discovery support ✓ PTZ control capable ✓ Vendor-independent

Best for: Enterprise cameras, systems requiring standardization, cameras with PTZ controls.

`, 'JPEG': `
JPEG Snapshot

Single static image endpoint. Can be converted to video stream by repeatedly fetching images.

Features:
✓ Low bandwidth ✓ Simple HTTP request ✓ No streaming protocol needed ⚠ Limited framerate (1-10 fps)

Best for: Thumbnails, snapshots, very low bandwidth scenarios. Not recommended for recording.

`, 'MJPEG': `
MJPEG Stream

Motion JPEG - sequence of JPEG images transmitted continuously. Simple but bandwidth-intensive.

Features:
✓ Simple HTTP streaming ✓ No complex codecs ✓ Frame-by-frame ⚠ High bandwidth usage

Best for: Sub streams, low-latency monitoring, simple camera integration. Higher bandwidth than H.264.

`, 'HLS': `
HLS Stream

HTTP Live Streaming - Apple's adaptive bitrate streaming protocol. Delivers video in small chunks over HTTP.

Features:
✓ Adaptive bitrate ✓ Wide browser support ✓ Firewall-friendly (HTTP) ⚠ Higher latency (5-30s)

Best for: Web playback, public streaming, CDN delivery. Not ideal for real-time monitoring.

`, 'HTTP_VIDEO': `
HTTP Video Stream

Generic HTTP-based video stream. Simple protocol that works over standard web connections.

Features:
✓ Simple HTTP protocol ✓ No special ports ✓ Firewall-friendly ✓ Direct browser playback

Best for: Quick viewing, simple setups, scenarios where RTSP ports are blocked.

`, 'BUBBLE': `
BUBBLE / DVRIP Protocol

Proprietary protocol for Chinese DVR/NVR cameras. Also known as: ESeeCloud, dvr163, DVR-IP, NetSurveillance, Sofia protocol, XMeye SDK.

Compatible brands:
XMEye, Floureon, ZOSI Sannce, Annke, DVR163 ESeeCloud, NetSurveillance
Features:
⚠ Proprietary protocol ✓ Go2RTC converts to standard ✓ Two-way audio support ⚠ TCP only (port 34567)

Note: Automatically converted to standard RTSP format by Go2RTC. Works seamlessly with Frigate without additional configuration.

` }; return tooltips[type] || ''; } attachEventListeners() { // Click on header to toggle this.listContainer.querySelectorAll('.stream-item-header').forEach(header => { header.addEventListener('click', (e) => { // Don't toggle if clicking "Use Stream" button if (e.target.closest('.btn-use-stream')) { return; } const index = parseInt(header.dataset.index); this.toggleExpand(index); }); }); // Use Stream buttons this.listContainer.querySelectorAll('.btn-use-stream').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); // Prevent toggle const index = parseInt(e.target.dataset.index); if (this.onUseCallback) { this.onUseCallback(this.streams[index], index); } }); }); } toggleExpand(index) { if (this.expandedIndex === index) { // Collapse if already expanded this.expandedIndex = null; } else { // Expand new item this.expandedIndex = index; } // Re-render to update state this.render(this.streams, this.onUseCallback); } }