16 KiB
16 KiB
🛠️ ИНСТРУКЦИЯ ПО ПЕРЕПИСЫВАНИЮ СИСТЕМЫ VIDEOREADER
Версия: 1.0
Дата: 9 октября 2025 г.
Цель: Создание собственной безопасной системы видеонаблюдения
📋 ПЛАН МИГРАЦИИ
Этап 1: Анализ и планирование (1-2 недели)
Этап 2: Инфраструктура (2-3 недели)
Етап 3: Backend разработка (3-4 недели)
Этап 4: Клиентские приложения (4-6 недель)
Этап 5: Тестирование и деплой (2 недели)
🏗️ СОВРЕМЕННАЯ АРХИТЕКТУРА
Рекомендуемый стек технологий:
🌐 Backend (Сигналинг сервер)
├── Node.js + TypeScript
├── Socket.IO / WebSocket
├── Redis (для сессий)
├── PostgreSQL (метаданные)
├── Docker + Docker Compose
└── Nginx (Reverse Proxy)
📱 Android приложение
├── Kotlin / Java
├── WebRTC Android API
├── Camera2 API
├── Retrofit (HTTP клиент)
├── Room (локальная БД)
└── Dagger/Hilt (DI)
💻 Desktop приложение
Вариант 1: Electron + React/Vue
├── TypeScript
├── WebRTC Web API
├── FFmpeg.js
└── Material-UI / Ant Design
Вариант 2: .NET MAUI
├── C# .NET 8
├── WebRTC.NET
├── FFMpegCore
└── Avalonia UI
Вариант 3: Flutter Desktop
├── Dart
├── WebRTC Flutter plugin
├── FFmpeg Flutter
└── Material Design
🚀 ЭТАП 1: НАСТРОЙКА ИНФРАСТРУКТУРЫ
1.1 Создание сигналинг сервера
# Создание проекта
mkdir videoreader-signaling
cd videoreader-signaling
npm init -y
# Установка зависимостей
npm install express socket.io redis ioredis
npm install @types/node typescript ts-node nodemon --save-dev
1.2 Базовая структура сервера
// src/server.ts
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
import Redis from 'ioredis';
const app = express();
const server = createServer(app);
const io = new Server(server, {
cors: { origin: "*" }
});
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
interface Device {
id: string;
type: 'sender' | 'receiver';
channel: string;
socketId: string;
}
// Управление каналами и устройствами
class ChannelManager {
async addDevice(device: Device) {
await redis.hset(`channel:${device.channel}`, device.type, device.socketId);
await redis.expire(`channel:${device.channel}`, 3600); // TTL 1 час
}
async getPartner(channel: string, deviceType: string) {
const partnerType = deviceType === 'sender' ? 'receiver' : 'sender';
return await redis.hget(`channel:${channel}`, partnerType);
}
}
const channelManager = new ChannelManager();
io.on('connection', (socket) => {
socket.on('join-channel', async (data: {channel: string, type: 'sender'|'receiver'}) => {
await channelManager.addDevice({
id: socket.id,
type: data.type,
channel: data.channel,
socketId: socket.id
});
// Поиск партнера
const partner = await channelManager.getPartner(data.channel, data.type);
if (partner) {
socket.emit('partner-found', { partnerId: partner });
io.to(partner).emit('partner-found', { partnerId: socket.id });
}
});
// WebRTC signaling
socket.on('webrtc-offer', (data) => {
io.to(data.to).emit('webrtc-offer', { from: socket.id, offer: data.offer });
});
socket.on('webrtc-answer', (data) => {
io.to(data.to).emit('webrtc-answer', { from: socket.id, answer: data.answer });
});
socket.on('webrtc-ice-candidate', (data) => {
io.to(data.to).emit('webrtc-ice-candidate', { from: socket.id, candidate: data.candidate });
});
});
server.listen(3000, () => console.log('Signaling server running on port 3000'));
1.3 Docker конфигурация
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
signaling:
build: .
ports:
- "3000:3000"
environment:
- REDIS_URL=redis://redis:6379
depends_on:
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
📱 ЭТАП 2: ANDROID ПРИЛОЖЕНИЕ (KOTLIN)
2.1 Создание проекта
// build.gradle (Module: app)
dependencies {
implementation 'org.webrtc:google-webrtc:1.0.32006'
implementation 'io.socket:socket.io-client:2.0.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'androidx.camera:camera-camera2:1.3.0'
implementation 'androidx.camera:camera-lifecycle:1.3.0'
implementation 'androidx.camera:camera-view:1.3.0'
}
2.2 WebRTC интеграция
// WebRTCManager.kt
class WebRTCManager(private val context: Context) {
private var peerConnection: PeerConnection? = null
private var localVideoTrack: VideoTrack? = null
private var socket: Socket? = null
fun initialize() {
// Инициализация PeerConnectionFactory
PeerConnectionFactory.initializeAndroidGlobals(
context,
true,
true,
true
)
val factory = PeerConnectionFactory.builder()
.createPeerConnectionFactory()
// Настройка ICE серверов
val iceServers = listOf(
PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()
)
peerConnection = factory.createPeerConnection(
PeerConnection.RTCConfiguration(iceServers),
object : PeerConnection.Observer {
override fun onIceCandidate(candidate: IceCandidate) {
sendIceCandidate(candidate)
}
// ... другие callbacks
}
)
}
fun startCapture() {
val videoCapturer = Camera2Enumerator(context).run {
deviceNames.firstOrNull()?.let { createCapturer(it, null) }
}
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", null)
val videoSource = factory.createVideoSource(false)
videoCapturer?.initialize(surfaceTextureHelper, context, videoSource.capturerObserver)
localVideoTrack = factory.createVideoTrack("local_video", videoSource)
peerConnection?.addTrack(localVideoTrack, listOf("stream_id"))
}
private fun sendIceCandidate(candidate: IceCandidate) {
socket?.emit("webrtc-ice-candidate", JSONObject().apply {
put("to", partnerId)
put("candidate", candidate.toJson())
})
}
}
2.3 Сигналинг клиент
// SignalingClient.kt
class SignalingClient(private val serverUrl: String) {
private var socket: Socket? = null
private var webRTCManager: WebRTCManager? = null
fun connect(channel: String) {
socket = IO.socket(serverUrl).apply {
on(Socket.EVENT_CONNECT) {
emit("join-channel", JSONObject().apply {
put("channel", channel)
put("type", "sender")
})
}
on("partner-found") { args ->
val data = args[0] as JSONObject
val partnerId = data.getString("partnerId")
createOffer(partnerId)
}
on("webrtc-offer") { args ->
val data = args[0] as JSONObject
handleOffer(data)
}
connect()
}
}
private fun createOffer(partnerId: String) {
webRTCManager?.createOffer { offer ->
socket?.emit("webrtc-offer", JSONObject().apply {
put("to", partnerId)
put("offer", offer.toJson())
})
}
}
}
💻 ЭТАП 3: DESKTOP ПРИЛОЖЕНИЕ (ELECTRON)
3.1 Инициализация проекта
mkdir videoreader-desktop
cd videoreader-desktop
npm init -y
npm install electron react react-dom typescript
npm install simple-peer socket.io-client --save
3.2 WebRTC компонент
// src/components/VideoReceiver.tsx
import React, { useEffect, useRef, useState } from 'react';
import io from 'socket.io-client';
import SimplePeer from 'simple-peer';
interface VideoReceiverProps {
channel: string;
serverUrl: string;
}
export const VideoReceiver: React.FC<VideoReceiverProps> = ({ channel, serverUrl }) => {
const videoRef = useRef<HTMLVideoElement>(null);
const [connected, setConnected] = useState(false);
const [peer, setPeer] = useState<SimplePeer.Instance | null>(null);
const socket = useRef(io(serverUrl));
useEffect(() => {
socket.current.emit('join-channel', {
channel,
type: 'receiver'
});
socket.current.on('partner-found', (data: { partnerId: string }) => {
const newPeer = new SimplePeer({
initiator: false,
trickle: false
});
newPeer.on('signal', (signal) => {
socket.current.emit('webrtc-answer', {
to: data.partnerId,
answer: signal
});
});
newPeer.on('stream', (stream) => {
if (videoRef.current) {
videoRef.current.srcObject = stream;
setConnected(true);
}
});
setPeer(newPeer);
});
socket.current.on('webrtc-offer', (data: { from: string; offer: any }) => {
if (peer) {
peer.signal(data.offer);
}
});
return () => {
socket.current.disconnect();
peer?.destroy();
};
}, [channel, serverUrl, peer]);
return (
<div className="video-receiver">
<h2>Channel: {channel}</h2>
<div className={`status ${connected ? 'connected' : 'disconnected'}`}>
{connected ? 'Connected' : 'Waiting for connection...'}
</div>
<video
ref={videoRef}
autoPlay
muted
style={{ width: '100%', maxWidth: '800px' }}
/>
</div>
);
};
3.3 Главное окно Electron
// src/main.ts
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
webSecurity: false // Для разработки, в продакшене нужно настроить правильно
}
});
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3001');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../build/index.html'));
}
}
app.whenReady().then(createWindow);
🔐 ЭТАП 4: БЕЗОПАСНОСТЬ
4.1 Аутентификация пользователей
// auth/AuthService.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
export class AuthService {
private static readonly JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
static async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12);
}
static async verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
static generateToken(userId: string): string {
return jwt.sign({ userId }, this.JWT_SECRET, { expiresIn: '24h' });
}
static verifyToken(token: string): any {
return jwt.verify(token, this.JWT_SECRET);
}
}
4.2 HTTPS и WSS
# nginx.conf
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location / {
proxy_pass http://signaling:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
📊 ЭТАП 5: МОНИТОРИНГ И МАСШТАБИРОВАНИЕ
5.1 Логирование
// logger/Logger.ts
import winston from 'winston';
export const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'videoreader-signaling' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
5.2 Метрики
// metrics/Metrics.ts
import prometheus from 'prom-client';
export const metrics = {
connectionsTotal: new prometheus.Counter({
name: 'connections_total',
help: 'Total number of connections'
}),
activeConnections: new prometheus.Gauge({
name: 'active_connections',
help: 'Number of active connections'
}),
channelsActive: new prometheus.Gauge({
name: 'channels_active',
help: 'Number of active channels'
})
};
prometheus.register.registerMetric(metrics.connectionsTotal);
prometheus.register.registerMetric(metrics.activeConnections);
prometheus.register.registerMetric(metrics.channelsActive);
🚀 ДЕПЛОЙMENT
Kubernetes манифесты
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: videoreader-signaling
spec:
replicas: 3
selector:
matchLabels:
app: videoreader-signaling
template:
metadata:
labels:
app: videoreader-signaling
spec:
containers:
- name: signaling
image: your-registry/videoreader-signaling:latest
ports:
- containerPort: 3000
env:
- name: REDIS_URL
value: "redis://redis-service:6379"
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: app-secrets
key: jwt-secret
💡 РЕКОМЕНДАЦИИ ПО УЛУЧШЕНИЮ
1. Производительность
- Использование WebRTC для P2P соединений
- CDN для статических файлов
- Load balancing для сигналинг серверов
- Redis Cluster для масштабирования
2. Безопасность
- End-to-end шифрование
- Rate limiting
- DDoS защита
- Регулярные security аудиты
3. Пользовательский опыт
- Progressive Web App (PWA) версия
- Адаптивный дизайн
- Офлайн режим
- Уведомления
4. Мониторинг
- Grafana дашборды
- Alertmanager для уведомлений
- Jaeger для трейсинга
- ELK стек для логов
📈 ПЛАН РАЗВИТИЯ
Версия 2.0
- Групповые видеозвонки
- Запись и хранение видео
- AI анализ контента
- Мобильные push уведомления
Версия 3.0
- Облачное хранилище
- API для интеграций
- Белый лейбл решение
- Международная локализация
🎯 РЕЗУЛЬТАТ: Современная, безопасная и масштабируемая система видеонаблюдения с собственной инфраструктурой, готовая к продакшн использованию и дальнейшему развитию.