# 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
Discovering streams...
{{ error }}
```
---
## 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()
}
}
```