Live Stream Gateway
Live Stream Gateway Dökümantasyonu
streamStarted event'inin uçtan uca entegrasyonu için: docs/live-stream-started-integration.md
Giriş ve Genel Bakış
Live Stream Gateway, canlı yayın uygulaması için WebSocket tabanlı gerçek zamanlı iletişim sağlayan bir servistir. Bu gateway, yayın izleyicilerinin sayısını takip etme, yayın durumu güncellemeleri, ban işlemleri ve süre uzatma gibi olayları yönetir.
Teknik Detaylar
- Namespace:
/live-stream - Protokol: WebSocket (Socket.IO)
- CORS: Tüm origin'lere açık (
origin: "*") - Metodlar: GET, POST
WebSocket Endpoint'leri
- Local:
ws://localhost:3000/live-stream - Development:
wss://dev.allmine.win/live-stream - Staging:
wss://staging.allmine.win/live-stream - Production:
wss://api.allmine.win/live-stream
Bağlantı Bilgileri
React Native'de Bağlantı Kurma
React Native'de Socket.IO kullanmak için socket.io-client paketini kurmanız gerekir:
npm install socket.io-client
# veya
yarn add socket.io-clientTemel Bağlantı Örneği
import { io, Socket } from 'socket.io-client';
const BASE_URL = 'https://api.allmine.win'; // veya ortamınıza göre
const socket: Socket = io(`${BASE_URL}/live-stream`, {
transports: ['websocket'],
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
reconnectionDelayMax: 5000,
});
// Bağlantı durumu dinleme
socket.on('connect', () => {
console.log('WebSocket bağlantısı kuruldu:', socket.id);
});
socket.on('disconnect', (reason) => {
console.log('WebSocket bağlantısı kesildi:', reason);
});
socket.on('connect_error', (error) => {
console.error('Bağlantı hatası:', error);
});Authentication
Şu anda gateway'de authentication zorunluluğu yoktur. Ancak gelecekte JWT token ile kimlik doğrulama eklenebilir. Bu durumda bağlantı şu şekilde yapılabilir:
const socket = io(`${BASE_URL}/live-stream`, {
auth: {
token: 'your-jwt-token-here'
},
// ... diğer ayarlar
});Room Yapısı
Gateway iki tür room yapısı kullanır:
1. Stream Room (stream:{streamId})
Belirli bir yayına katılan tüm kullanıcılar bu room'a eklenir. Bu room'a katıldığınızda:
- O yayının viewer count güncellemelerini alırsınız
- Yayına özel eventleri (süre uzatma, ban işlemleri) alırsınız
- Yayın bittiğinde otomatik olarak bağlantınız kesilir
Örnek Room Adı: stream:507f1f77bcf86cd799439011
2. Overview Room (streams:overview)
Homepage veya yayın listesi sayfaları için kullanılır. Bu room'a katıldığınızda:
- Tüm aktif yayınların listesini alırsınız
- Yayın bittiğinde bildirim alırsınız
- Tüm yayınların viewer count güncellemelerini alırsınız
Room Adı: streams:overview
Client'tan Server'a Gönderilen Eventler
1. joinStream
Belirli bir yayına katılmak için kullanılır.
Payload:
{
streamId: string; // MongoDB ObjectId formatında
}Örnek:
socket.emit('joinStream', {
streamId: '507f1f77bcf86cd799439011'
});Başarılı Yanıt: joinedStream eventi gelir
Hata Durumu: joinStreamError eventi gelir
2. leaveStream
Belirli bir yayından ayrılmak için kullanılır.
Payload:
{
streamId: string; // MongoDB ObjectId formatında
}Örnek:
socket.emit('leaveStream', {
streamId: '507f1f77bcf86cd799439011'
});Başarılı Yanıt: leftStream eventi gelir
Hata Durumu: leaveStreamError eventi gelir
3. joinOverview
Global overview room'una katılmak için kullanılır. Homepage veya yayın listesi sayfalarında kullanılmalıdır.
Payload: Yok (boş)
Örnek:
socket.emit('joinOverview');Başarılı Yanıt: joinedOverview eventi gelir
Hata Durumu: joinOverviewError eventi gelir
4. leaveOverview
Global overview room'undan ayrılmak için kullanılır.
Payload: Yok (boş)
Örnek:
socket.emit('leaveOverview');Başarılı Yanıt: leftOverview eventi gelir
Hata Durumu: leaveOverviewError eventi gelir
Server'dan Client'a Gönderilen Eventler
Başarı Yanıt Eventleri
1. joinedStream
Yayına başarıyla katıldığınızda gelir.
Payload:
{
streamId: string;
viewerCount: number;
success: boolean;
}Örnek:
socket.on('joinedStream', (data) => {
console.log(`Yayına katıldınız: ${data.streamId}`);
console.log(`İzleyici sayısı: ${data.viewerCount}`);
});2. leftStream
Yayından başarıyla ayrıldığınızda gelir.
Payload:
{
streamId: string;
viewerCount: number;
success: boolean;
}Örnek:
socket.on('leftStream', (data) => {
console.log(`Yayından ayrıldınız: ${data.streamId}`);
console.log(`Güncel izleyici sayısı: ${data.viewerCount}`);
});3. joinedOverview
Overview room'una başarıyla katıldığınızda gelir.
Payload:
{
activeStreams: Array<{
streamId: string;
viewerCount: number;
}>;
success: boolean;
}Örnek:
socket.on('joinedOverview', (data) => {
console.log('Aktif yayınlar:', data.activeStreams);
// data.activeStreams = [
// { streamId: '507f1f77bcf86cd799439011', viewerCount: 15 },
// { streamId: '507f1f77bcf86cd799439012', viewerCount: 8 }
// ]
});4. leftOverview
Overview room'undan başarıyla ayrıldığınızda gelir.
Payload:
{
success: boolean;
}Örnek:
socket.on('leftOverview', (data) => {
console.log('Overview room\'undan ayrıldınız');
});Viewer Count Güncelleme Eventleri
5. viewerCountUpdated
Belirli bir yayının viewer count'u güncellendiğinde, o yayının room'undaki tüm kullanıcılara gönderilir.
Payload:
{
streamId: string;
viewerCount: number;
timestamp: string; // ISO 8601 formatında
}Örnek:
socket.on('viewerCountUpdated', (data) => {
console.log(`Yayın ${data.streamId} izleyici sayısı: ${data.viewerCount}`);
// UI'da viewer count'u güncelle
});6. streamViewerUpdated
Overview room'undaki kullanıcılara, herhangi bir yayının viewer count güncellemesi geldiğinde gönderilir.
Payload:
{
streamId: string;
viewerCount: number;
timestamp: string; // ISO 8601 formatında
}Örnek:
socket.on('streamViewerUpdated', (data) => {
// Homepage'deki yayın listesinde viewer count'u güncelle
updateStreamViewerCount(data.streamId, data.viewerCount);
});giftCountUpdated
Belirli bir yayının toplam hediye adedi güncellendiğinde stream room'undaki kullanıcılara gönderilir.
Bu event iki durumda tetiklenir:
joinStreamsonrasında ilgili kullanıcıya başlangıç snapshot'u- Yayına yeni gift geldiğinde room'daki herkese güncel toplam
Payload:
{
streamId: string;
totalGiftCount: number;
timestamp: string; // ISO 8601 formatında
}Örnek:
socket.on('giftCountUpdated', (data) => {
updateGiftCount(data.streamId, data.totalGiftCount);
});Yayın Durumu Eventleri
7. streamStarted
Yayın gerçekten başladığında (startedAt set edildiğinde), ilgili stream room'undaki tüm kullanıcılara gönderilir.
Payload:
{
streamId: string; // Yayın ID
startedAt: string; // ISO 8601 format
plannedEndDate: string | null; // ISO 8601 format veya null
timestamp: string; // ISO 8601 format
}Örnek:
socket.on('streamStarted', (data) => {
console.log('Yayın başladı:', data.streamId, data.startedAt, data.plannedEndDate);
});8. streamEnded
Bir yayın bittiğinde, overview room'undaki tüm kullanıcılara gönderilir. Ayrıca yayın room'undaki tüm kullanıcıların bağlantısı otomatik olarak kesilir.
Payload:
{
streamId: string;
}Örnek:
socket.on('streamEnded', (data) => {
console.log('Yayın bitti:', data.streamId);
// Homepage'den yayını kaldır
removeStreamFromList(data.streamId);
});Süre Uzatma Eventi
9. streamTimeExtensionAdded
Bir kullanıcı yayına süre eklediğinde, o yayının room'undaki tüm kullanıcılara gönderilir.
Payload:
{
id: string; // Time extension kaydının ID'si
streamId: string;
senderId: string;
durationSeconds: number; // Eklenen süre (saniye cinsinden)
senderSnapshot: {
_id: string;
username: string | null;
name: string | null;
surname: string | null;
profilePhoto: {
_id: string;
url: string;
variants: Array<{
size: number;
url: string;
}>;
} | null;
};
createdAt: string; // ISO 8601 formatında
}Örnek:
socket.on('streamTimeExtensionAdded', (data) => {
console.log(`${data.senderSnapshot.username} ${data.durationSeconds} saniye ekledi`);
// UI'da bildirim göster
showTimeExtensionNotification(data);
});Ban İşlemi Eventleri
10. userBanned
Bir kullanıcı yayından banlandığında, o yayının room'undaki tüm kullanıcılara gönderilir.
Payload:
{
targetUserId: string; // Banlanan kullanıcının ID'si
actionType: string; // 'BLOCK' veya diğer action tipleri
reason?: string; // Ban nedeni (opsiyonel)
scope: string; // 'STREAM' veya 'BROADCASTER'
timestamp: string; // ISO 8601 formatında
}Örnek:
socket.on('userBanned', (data) => {
console.log(`Kullanıcı banlandı: ${data.targetUserId}`);
// Eğer banlanan kullanıcı sizseniz, yayından çıkın
if (data.targetUserId === currentUserId) {
// Yayından ayrıl ve hata mesajı göster
handleUserBanned(data);
}
});11. userBanRevoked
Bir kullanıcının banı kaldırıldığında, o yayının room'undaki tüm kullanıcılara gönderilir.
Payload:
{
targetUserId: string; // Ban kaldırılan kullanıcının ID'si
timestamp: string; // ISO 8601 formatında
}Örnek:
socket.on('userBanRevoked', (data) => {
console.log(`Kullanıcının banı kaldırıldı: ${data.targetUserId}`);
// UI'da güncelleme yap
});12. userKickedFromStream
Kullanıcı yetersiz bakiye veya başka bir nedenle yayından atıldığında, o yayının room'undaki tüm kullanıcılara gönderilir.
Payload:
{
streamId: string;
userId: string; // Atılan kullanıcının ID'si
reason: string; // Atılma nedeni (örn: 'Insufficient credit balance')
timestamp: string; // ISO 8601 formatında
}Örnek:
socket.on('userKickedFromStream', (data) => {
if (data.userId === currentUserId) {
console.log(`Yayından atıldınız: ${data.reason}`);
// Yayından çık ve hata mesajı göster
handleUserKicked(data);
} else {
console.log(`Kullanıcı yayından atıldı: ${data.userId}`);
}
});Hata Eventleri
13. joinStreamError
Yayına katılma işlemi başarısız olduğunda gelir.
Payload:
{
streamId: string;
error: string; // Hata mesajı
}Örnek:
socket.on('joinStreamError', (data) => {
console.error('Yayına katılma hatası:', data.error);
// Kullanıcıya hata mesajı göster
});14. leaveStreamError
Yayından ayrılma işlemi başarısız olduğunda gelir.
Payload:
{
streamId: string;
error: string; // Hata mesajı
}15. joinOverviewError
Overview room'una katılma işlemi başarısız olduğunda gelir.
Payload:
{
error: string; // Hata mesajı
}16. leaveOverviewError
Overview room'undan ayrılma işlemi başarısız olduğunda gelir.
Payload:
{
error: string; // Hata mesajı
}Viewer Count Yönetimi
Otomatik Artırma/Azaltma Mekanizması
Viewer count yönetimi tamamen otomatiktir:
- Katılma:
joinStreameventi gönderildiğinde viewer count otomatik olarak artırılır - Ayrılma:
leaveStreameventi gönderildiğinde viewer count otomatik olarak azaltılır - Disconnect: Kullanıcı bağlantısı kesildiğinde, katıldığı tüm stream room'larından otomatik olarak çıkarılır ve viewer count'ları azaltılır
- Yayın Bitişi: Yayın bittiğinde viewer count sıfırlanır ve aktif stream listesinden çıkarılır
Cache Yönetimi
Viewer count'lar Redis veya in-memory cache'de tutulur:
- Key Formatı:
liveStream:{streamId}:viewerCount - Aktif Stream Listesi:
activeStreams:list
Viewer Count'un 0 Olması
Bir yayının viewer count'u 0'a düştüğünde:
- Yayın aktif stream listesinden otomatik olarak çıkarılır
- Overview room'undaki kullanıcılara
streamViewerUpdatedeventi gönderilir (viewerCount: 0)
React Native Kullanım Örnekleri
TypeScript Tip Tanımlamaları
Önce tip tanımlamalarını oluşturalım:
// types/live-stream-socket.types.ts
export interface JoinStreamPayload {
streamId: string;
}
export interface LeaveStreamPayload {
streamId: string;
}
export interface JoinedStreamResponse {
streamId: string;
viewerCount: number;
success: boolean;
}
export interface LeftStreamResponse {
streamId: string;
viewerCount: number;
success: boolean;
}
export interface ActiveStream {
streamId: string;
viewerCount: number;
}
export interface JoinedOverviewResponse {
activeStreams: ActiveStream[];
success: boolean;
}
export interface ViewerCountUpdate {
streamId: string;
viewerCount: number;
timestamp: string;
}
export interface GiftCountUpdated {
streamId: string;
totalGiftCount: number;
timestamp: string;
}
export interface StreamStartedPayload {
streamId: string;
startedAt: string; // ISO 8601
plannedEndDate: string | null; // ISO 8601 veya null
timestamp: string; // ISO 8601
}
export interface StreamEndedPayload {
streamId: string;
}
export interface StreamTimeExtensionPayload {
id: string;
streamId: string;
senderId: string;
durationSeconds: number;
senderSnapshot: {
_id: string;
username: string | null;
name: string | null;
surname: string | null;
profilePhoto: {
_id: string;
url: string;
variants: Array<{
size: number;
url: string;
}>;
} | null;
};
createdAt: string;
}
export interface UserBannedPayload {
targetUserId: string;
actionType: string;
reason?: string;
scope: string;
timestamp: string;
}
export interface UserBanRevokedPayload {
targetUserId: string;
timestamp: string;
}
export interface UserKickedFromStreamPayload {
streamId: string;
userId: string;
reason: string;
timestamp: string;
}
export interface SocketErrorPayload {
streamId?: string;
error: string;
}Socket Hook (Custom Hook)
// hooks/useLiveStreamSocket.ts
import { useEffect, useRef, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import {
JoinedStreamResponse,
LeftStreamResponse,
JoinedOverviewResponse,
ViewerCountUpdate,
StreamStartedPayload,
StreamEndedPayload,
StreamTimeExtensionPayload,
UserBannedPayload,
UserBanRevokedPayload,
UserKickedFromStreamPayload,
SocketErrorPayload,
} from '../types/live-stream-socket.types';
const BASE_URL = 'https://api.allmine.win'; // Ortamınıza göre değiştirin
export const useLiveStreamSocket = () => {
const socketRef = useRef<Socket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [socketId, setSocketId] = useState<string | null>(null);
useEffect(() => {
// Socket bağlantısını oluştur
const socket = io(`${BASE_URL}/live-stream`, {
transports: ['websocket'],
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
reconnectionDelayMax: 5000,
});
socketRef.current = socket;
// Bağlantı eventleri
socket.on('connect', () => {
console.log('Live Stream Socket bağlandı:', socket.id);
setIsConnected(true);
setSocketId(socket.id);
});
socket.on('disconnect', (reason) => {
console.log('Live Stream Socket bağlantısı kesildi:', reason);
setIsConnected(false);
setSocketId(null);
});
socket.on('connect_error', (error) => {
console.error('Live Stream Socket bağlantı hatası:', error);
setIsConnected(false);
});
// Cleanup
return () => {
socket.disconnect();
socketRef.current = null;
};
}, []);
return {
socket: socketRef.current,
isConnected,
socketId,
};
};Stream Detay Sayfası Component'i
// components/StreamDetailScreen.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useLiveStreamSocket } from '../hooks/useLiveStreamSocket';
import {
JoinedStreamResponse,
ViewerCountUpdate,
GiftCountUpdated,
StreamTimeExtensionPayload,
UserBannedPayload,
UserKickedFromStreamPayload,
} from '../types/live-stream-socket.types';
interface StreamDetailScreenProps {
streamId: string;
currentUserId?: string;
}
export const StreamDetailScreen: React.FC<StreamDetailScreenProps> = ({
streamId,
currentUserId,
}) => {
const { socket, isConnected } = useLiveStreamSocket();
const [viewerCount, setViewerCount] = useState<number>(0);
const [giftCount, setGiftCount] = useState<number>(0);
const [timeExtensions, setTimeExtensions] = useState<StreamTimeExtensionPayload[]>([]);
useEffect(() => {
if (!socket || !isConnected) return;
// Yayına katıl
socket.emit('joinStream', { streamId });
// Event listener'ları tanımla
const handleJoinedStream = (data: JoinedStreamResponse) => {
console.log('Yayına katıldınız:', data);
setViewerCount(data.viewerCount);
};
const handleViewerCountUpdate = (data: ViewerCountUpdate) => {
console.log('Viewer count güncellendi:', data);
setViewerCount(data.viewerCount);
};
const handleGiftCountUpdate = (data: GiftCountUpdated) => {
console.log('Gift count güncellendi:', data);
setGiftCount(data.totalGiftCount);
};
const handleTimeExtension = (data: StreamTimeExtensionPayload) => {
console.log('Süre eklendi:', data);
setTimeExtensions((prev) => [data, ...prev]);
// Bildirim göster
// showNotification(`${data.senderSnapshot.username} ${data.durationSeconds} saniye ekledi`);
};
const handleUserBanned = (data: UserBannedPayload) => {
if (data.targetUserId === currentUserId) {
// Kullanıcı banlandı, yayından çık
socket.emit('leaveStream', { streamId });
// Hata mesajı göster
// showError('Bu yayından banlandınız');
}
};
const handleUserKicked = (data: UserKickedFromStreamPayload) => {
if (data.userId === currentUserId) {
// Kullanıcı yayından atıldı
socket.emit('leaveStream', { streamId });
// Hata mesajı göster
// showError(`Yayından atıldınız: ${data.reason}`);
}
};
const handleError = (data: { streamId: string; error: string }) => {
console.error('Yayın hatası:', data.error);
// Hata mesajı göster
};
// Event listener'ları ekle
socket.on('joinedStream', handleJoinedStream);
socket.on('viewerCountUpdated', handleViewerCountUpdate);
socket.on('giftCountUpdated', handleGiftCountUpdate);
socket.on('streamTimeExtensionAdded', handleTimeExtension);
socket.on('userBanned', handleUserBanned);
socket.on('userKickedFromStream', handleUserKicked);
socket.on('joinStreamError', handleError);
// Cleanup
return () => {
// Yayından ayrıl
socket.emit('leaveStream', { streamId });
// Event listener'ları kaldır
socket.off('joinedStream', handleJoinedStream);
socket.off('viewerCountUpdated', handleViewerCountUpdate);
socket.off('giftCountUpdated', handleGiftCountUpdate);
socket.off('streamTimeExtensionAdded', handleTimeExtension);
socket.off('userBanned', handleUserBanned);
socket.off('userKickedFromStream', handleUserKicked);
socket.off('joinStreamError', handleError);
};
}, [socket, isConnected, streamId, currentUserId]);
if (!isConnected) {
return (
<View style={styles.container}>
<Text>Bağlanılıyor...</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.viewerCount}>İzleyici: {viewerCount}</Text>
<Text style={styles.viewerCount}>Toplam Gift: {giftCount}</Text>
{timeExtensions.length > 0 && (
<View style={styles.extensionsContainer}>
<Text style={styles.extensionsTitle}>Süre Eklemeleri:</Text>
{timeExtensions.slice(0, 5).map((ext) => (
<Text key={ext.id} style={styles.extensionText}>
{ext.senderSnapshot.username} +{ext.durationSeconds}s
</Text>
))}
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
viewerCount: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 16,
},
extensionsContainer: {
marginTop: 16,
},
extensionsTitle: {
fontSize: 16,
fontWeight: '600',
marginBottom: 8,
},
extensionText: {
fontSize: 14,
marginBottom: 4,
},
});Homepage/Stream List Component'i
// components/StreamListScreen.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { useLiveStreamSocket } from '../hooks/useLiveStreamSocket';
import {
ActiveStream,
JoinedOverviewResponse,
ViewerCountUpdate,
StreamEndedPayload,
} from '../types/live-stream-socket.types';
export const StreamListScreen: React.FC = () => {
const { socket, isConnected } = useLiveStreamSocket();
const [activeStreams, setActiveStreams] = useState<ActiveStream[]>([]);
useEffect(() => {
if (!socket || !isConnected) return;
// Overview room'una katıl
socket.emit('joinOverview');
// Event listener'ları tanımla
const handleJoinedOverview = (data: JoinedOverviewResponse) => {
console.log('Overview\'a katıldınız:', data);
setActiveStreams(data.activeStreams);
};
const handleStreamViewerUpdate = (data: ViewerCountUpdate) => {
console.log('Stream viewer güncellendi:', data);
setActiveStreams((prev) =>
prev.map((stream) =>
stream.streamId === data.streamId
? { ...stream, viewerCount: data.viewerCount }
: stream
).filter((stream) => stream.viewerCount > 0) // 0 viewer olanları kaldır
);
};
const handleStreamEnded = (data: StreamEndedPayload) => {
console.log('Yayın bitti:', data);
setActiveStreams((prev) =>
prev.filter((stream) => stream.streamId !== data.streamId)
);
};
const handleError = (data: { error: string }) => {
console.error('Overview hatası:', data.error);
};
// Event listener'ları ekle
socket.on('joinedOverview', handleJoinedOverview);
socket.on('streamViewerUpdated', handleStreamViewerUpdate);
socket.on('streamEnded', handleStreamEnded);
socket.on('joinOverviewError', handleError);
// Cleanup
return () => {
// Overview'dan ayrıl
socket.emit('leaveOverview');
// Event listener'ları kaldır
socket.off('joinedOverview', handleJoinedOverview);
socket.off('streamViewerUpdated', handleStreamViewerUpdate);
socket.off('streamEnded', handleStreamEnded);
socket.off('joinOverviewError', handleError);
};
}, [socket, isConnected]);
if (!isConnected) {
return (
<View style={styles.container}>
<Text>Bağlanılıyor...</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>Aktif Yayınlar</Text>
<FlatList
data={activeStreams}
keyExtractor={(item) => item.streamId}
renderItem={({ item }) => (
<View style={styles.streamItem}>
<Text style={styles.streamId}>{item.streamId}</Text>
<Text style={styles.viewerCount}>
{item.viewerCount} izleyici
</Text>
</View>
)}
ListEmptyComponent={
<Text style={styles.emptyText}>Aktif yayın yok</Text>
}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
},
streamItem: {
padding: 12,
backgroundColor: '#f0f0f0',
borderRadius: 8,
marginBottom: 8,
},
streamId: {
fontSize: 16,
fontWeight: '600',
},
viewerCount: {
fontSize: 14,
color: '#666',
marginTop: 4,
},
emptyText: {
textAlign: 'center',
color: '#999',
marginTop: 32,
},
});Tam Entegrasyon Örneği
// screens/LiveStreamScreen.tsx
import React, { useEffect, useRef } from 'react';
import { View, Button, Alert } from 'react-native';
import { useLiveStreamSocket } from '../hooks/useLiveStreamSocket';
import { StreamDetailScreen } from '../components/StreamDetailScreen';
export const LiveStreamScreen: React.FC<{ streamId: string }> = ({ streamId }) => {
const { socket, isConnected } = useLiveStreamSocket();
const handleJoinStream = () => {
if (!socket || !isConnected) {
Alert.alert('Hata', 'Socket bağlantısı yok');
return;
}
socket.emit('joinStream', { streamId });
};
const handleLeaveStream = () => {
if (!socket || !isConnected) {
return;
}
socket.emit('leaveStream', { streamId });
};
return (
<View style={{ flex: 1 }}>
<View style={{ padding: 16, flexDirection: 'row', gap: 8 }}>
<Button title="Yayına Katıl" onPress={handleJoinStream} />
<Button title="Yayından Ayrıl" onPress={handleLeaveStream} />
</View>
<StreamDetailScreen streamId={streamId} />
</View>
);
};Best Practices
1. Bağlantı Yönetimi
- Tek Socket Instance: Uygulama genelinde tek bir socket instance kullanın (singleton pattern veya context API)
- Reconnection: Socket.IO otomatik reconnection sağlar, ancak manuel kontrol de ekleyebilirsiniz
- Connection State: Bağlantı durumunu takip edin ve kullanıcıya bilgi verin
// Bağlantı durumunu kontrol et
if (!socket || !socket.connected) {
// Bağlantı yok, işlem yapma
return;
}2. Memory Leak Önleme
- Event Listener Cleanup:
useEffectcleanup fonksiyonunda tüm event listener'ları kaldırın - Component Unmount: Component unmount olduğunda socket'ten ayrılın (
leaveStream,leaveOverview) - Ref Kullanımı: Socket instance'ını ref ile saklayın, state'e koymayın
useEffect(() => {
// Event listener ekle
socket.on('event', handler);
return () => {
// Cleanup: Event listener'ı kaldır
socket.off('event', handler);
// Room'lardan ayrıl
socket.emit('leaveStream', { streamId });
};
}, [socket, streamId]);3. Reconnection Stratejileri
- Otomatik Reconnection: Socket.IO varsayılan olarak otomatik reconnect yapar
- Manuel Reconnection: Gerekirse manuel reconnect ekleyebilirsiniz
socket.on('disconnect', (reason) => {
if (reason === 'io server disconnect') {
// Server tarafından kesildi, manuel reconnect gerekebilir
socket.connect();
}
// Diğer durumlarda otomatik reconnect çalışır
});4. Error Handling
- Tüm Error Eventlerini Dinleyin:
joinStreamError,leaveStreamError, vb. - Kullanıcıya Bilgi Verin: Hataları kullanıcıya anlaşılır şekilde gösterin
- Retry Mekanizması: Kritik işlemler için retry mekanizması ekleyin
const joinStreamWithRetry = async (streamId: string, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
socket.emit('joinStream', { streamId });
// Başarılı olursa döngüden çık
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
socket.once('joinedStream', () => {
clearTimeout(timeout);
resolve(true);
});
socket.once('joinStreamError', (error) => {
clearTimeout(timeout);
reject(error);
});
});
return;
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
};5. Performance Optimizasyonları
- Debounce/Throttle: Sık güncellenen eventler için debounce veya throttle kullanın
- Conditional Updates: Sadece gerekli durumlarda state güncellemesi yapın
- Memoization: Pahalı hesaplamalar için
useMemokullanın
// Viewer count güncellemelerini throttle et
const throttledUpdateViewerCount = useMemo(
() => throttle((count: number) => {
setViewerCount(count);
}, 1000),
[]
);
socket.on('viewerCountUpdated', (data) => {
throttledUpdateViewerCount(data.viewerCount);
});6. Testing
- Mock Socket: Testlerde socket'i mock'layın
- Event Simulation: Event'leri manuel olarak tetikleyin
- Connection State Testing: Farklı bağlantı durumlarını test edin
// Test örneği
import { io } from 'socket.io-client';
const mockSocket = {
emit: jest.fn(),
on: jest.fn(),
off: jest.fn(),
disconnect: jest.fn(),
connected: true,
};
jest.mock('socket.io-client', () => ({
io: jest.fn(() => mockSocket),
}));7. Güvenlik
- Input Validation: Client tarafında da input validation yapın
- Rate Limiting: Çok sık event göndermeyi engelleyin
- Authentication: Gelecekte authentication eklendiğinde token'ı güvenli şekilde saklayın
Özet
Live Stream Gateway, canlı yayın uygulamanız için güçlü bir WebSocket çözümü sunar. Bu dökümantasyonu takip ederek:
- ✅ WebSocket bağlantısı kurabilirsiniz
- ✅ Yayınlara katılıp ayrılabilirsiniz
- ✅ Viewer count güncellemelerini alabilirsiniz
- ✅ Yayın durumu değişikliklerini takip edebilirsiniz
- ✅ Süre uzatma ve ban işlemlerini yönetebilirsiniz
- ✅ Homepage için overview room'unu kullanabilirsiniz
Herhangi bir sorunuz veya sorununuz olursa, backend ekibiyle iletişime geçebilirsiniz.