# Client Integration Guide

Frontend and native player implementation patterns for integrating Vyla Stream API with HLS.js, React, Vue, Android ExoPlayer, and iOS AVPlayer.

## Overview Stream API exposes standard HTTP and Server-Sent Events (SSE) interfaces, allowing it to integrate with any modern video player library. This guide provides copyable, production-ready integration patterns across multiple platforms and frameworks. --- ## Pattern 1: React Player Hook Below is a complete custom React hook (`useVylaStream`) managing SSE event consumption, fallback candidate queuing, and playback state: ```tsx import { useState, useEffect, useRef } from 'react'; interface StreamSource { source: string; label: string; url: string; quality: string; type: 'hls' | 'mp4'; } interface StreamMeta { title: string; year: number; poster: string; } export function useVylaStream(apiBase: string, tmdbId: number, season?: number, episode?: number) { const [meta, setMeta] = useState(null); const [activeSource, setActiveSource] = useState(null); const [subtitles, setSubtitles] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const fallbackQueue = useRef([]); const eventSourceRef = useRef(null); useEffect(() => { setIsLoading(true); setError(null); fallbackQueue.current = []; const endpoint = season != null && episode != null ? `${apiBase}/tv?id=${tmdbId}&season=${season}&episode=${episode}` : `${apiBase}/movie?id=${tmdbId}`; const es = new EventSource(endpoint); eventSourceRef.current = es; es.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'meta') { setMeta(data.meta); if (data.subtitles) setSubtitles(data.subtitles); } else if (data.type === 'source') { if (!activeSource) { setActiveSource(data); setIsLoading(false); } else { fallbackQueue.current.push(data); } } else if (data.type === 'done') { es.close(); if (!activeSource && fallbackQueue.current.length === 0) { setError('No available streaming sources found.'); setIsLoading(false); } } }; es.onerror = () => { es.close(); if (!activeSource) { setError('Failed to connect to Stream API.'); setIsLoading(false); } }; return () => { es.close(); }; }, [apiBase, tmdbId, season, episode]); const triggerNextFallback = () => { if (fallbackQueue.current.length > 0) { const next = fallbackQueue.current.shift()!; console.warn(`Switching to fallback candidate: ${next.label}`); setActiveSource(next); } else { setError('All provider stream candidates exhausted.'); } }; return { meta, activeSource, subtitles, isLoading, error, triggerNextFallback }; } ``` --- ## Pattern 2: Vue 3 Composable Component ```vue ``` --- ## Pattern 3: Android Native (Kotlin & ExoPlayer) In Android applications, connect to the Stream API proxy using Google's `MediaItem`: ```kotlin import androidx.media3.common.MediaItem import androidx.media3.common.MimeTypes import androidx.media3.exoplayer.ExoPlayer fun playStream(player: ExoPlayer, proxiedHlsUrl: String) { val mediaItem = MediaItem.Builder() .setUri(proxiedHlsUrl) .setMimeType(MimeTypes.APPLICATION_M3U8) .build() player.setMediaItem(mediaItem) player.prepare() player.playWhenReady = true } ``` --- ## Pattern 4: iOS / macOS Native (Swift & AVPlayer) Because iOS AVPlayer natively handles HLS streams and respects cookies/redirects, assigning the proxied URL is straightforward: ```swift import AVKit func playVylaStream(proxiedUrlString: String) { guard let url = URL(string: proxiedUrlString) else { return } let player = AVPlayer(url: url) let playerViewController = AVPlayerViewController() playerViewController.player = player present(playerViewController, animated: true) { player.play() } } ```