Aller au contenu principal

Module Streaming

Module de streaming securise par tokens pour la MyTelevision API.


Vue d'ensemble

Le module Streaming fournit :

  • Acces aux flux par tokens securises
  • Liaison IP (IP binding) pour la securite
  • Limites d'utilisation par token
  • Gestion des cles DRM (AES-128)
  • Selection de la qualite video

Architecture


Flux de Streaming


Structure des Fichiers

src/
├── application/
│ ├── dtos/
│ │ └── streaming/
│ │ ├── generate-token.dto.ts
│ │ ├── stream-token-response.dto.ts
│ │ └── validate-token.dto.ts
│ └── services/
│ └── streaming/
│ ├── streaming.service.ts
│ ├── token.service.ts
│ └── drm.service.ts
├── infrastructure/
│ └── guards/
│ └── stream-token.guard.ts
└── presentation/
└── controllers/
└── api/v2/
└── streaming.controller.ts

Modele de Donnees

model StreamingToken {
id String @id @default(uuid())
token String @unique
userId String
profileId String?
contentType MediaType
contentId String
quality VideoQuality
ipAddress String
userAgent String?
maxUses Int @default(5)
usageCount Int @default(0)
expiresAt DateTime
createdAt DateTime @default(now())
lastUsedAt DateTime?

@@index([token])
@@index([userId, contentId])
}

model DrmKey {
id String @id @default(uuid())
contentId String
contentType MediaType
keyId String @unique
key String // Encrypted AES-128 key
iv String // Initialization vector
createdAt DateTime @default(now())

@@index([contentId, contentType])
}

enum VideoQuality {
SD_480
HD_720
FHD_1080
UHD_4K
}

Composants Principaux

StreamingService

@Injectable()
export class StreamingService {
async generateToken(
userId: string,
profileId: string,
dto: GenerateTokenDto,
): Promise<StreamTokenResponseDto>;

async validateToken(token: string, ip: string): Promise<boolean>;

async getStreamUrl(
contentType: MediaType,
contentId: string,
quality: VideoQuality,
): Promise<string>;

async revokeToken(tokenId: string): Promise<void>;

async revokeAllUserTokens(userId: string): Promise<void>;
}

TokenService

@Injectable()
export class TokenService {
generateSecureToken(): string;

async storeToken(data: StreamingTokenData): Promise<StreamingToken>;

async validateAndIncrement(token: string, ip: string): Promise<boolean>;

async isExpired(token: StreamingToken): boolean;

async isExhausted(token: StreamingToken): boolean;

async matchesIp(token: StreamingToken, ip: string): boolean;
}

StreamTokenGuard

@Injectable()
export class StreamTokenGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractToken(request);
const ip = request.ip;

return this.streamingService.validateToken(token, ip);
}
}

DTOs

GenerateTokenDto

export class GenerateTokenDto {
@IsEnum(MediaType)
contentType: MediaType;

@IsUUID()
contentId: string;

@IsOptional()
@IsEnum(VideoQuality)
quality?: VideoQuality = VideoQuality.HD_720;
}

StreamTokenResponseDto

export class StreamTokenResponseDto {
token: string;
streamUrl: string;
hlsUrl: string;
quality: VideoQuality;
expiresAt: Date;
maxUses: number;
}

Securite des Tokens

Proprietes du Token

ProprieteValeurDescription
Longueur64 caracteresCryptographiquement securise
TTL4 heuresConfigurable par type de contenu
Utilisations max5Empeche le partage de token
Liaison IPOuiToken lie a l'IP du client

Regles de Validation

  1. Le token doit exister et ne pas etre revoque
  2. Le token ne doit pas etre expire
  3. Le compteur d'utilisation doit etre inferieur au maximum
  4. L'IP de la requete doit correspondre a l'IP du token
  5. L'utilisateur doit avoir un abonnement valide (si contenu premium)

Mesures Anti-Abus

// Rate limiting pour la generation de tokens
@Throttle({ short: { ttl: 1000, limit: 3 } })
@Post('token')
async generateToken() { ... }

// Job de nettoyage des tokens
@Cron('0 */15 * * * *') // Toutes les 15 minutes
async cleanupExpiredTokens() {
await this.streamingService.deleteExpiredTokens();
}

Adaptation de la Qualite

Qualites disponibles selon l'abonnement :

AbonnementQualite MaxFlux Simultanes
FreeSD_4801
BasicHD_7202
PremiumFHD_10804
UltimateUHD_4K6

Integration DRM

Chiffrement AES-128

@Injectable()
export class DrmService {
async generateKey(contentId: string): Promise<DrmKey>;

async getKeyForContent(
contentId: string,
): Promise<{ keyId: string; key: string }>;

async rotateKey(contentId: string): Promise<DrmKey>;
}

Livraison des Cles

GET /streaming/key/:keyId
Authorization: Bearer <stream_token>

Response: Binary AES-128 key

Manifeste HLS

Exemple de manifeste HLS multi-qualite :

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
stream_360p.m3u8?token=xxx
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=854x480
stream_480p.m3u8?token=xxx
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
stream_720p.m3u8?token=xxx
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
stream_1080p.m3u8?token=xxx

Monitoring

Metriques Prometheus

mytelevision_stream_tokens_generated_total{content_type, quality}
mytelevision_stream_tokens_validated_total{status}
mytelevision_stream_tokens_rejected_total{reason}
mytelevision_active_streams_total{content_type}
mytelevision_stream_duration_seconds{content_type}

Alertes

AlerteConditionAction
HighTokenRejection>10% rejets en 5 minInvestiguer les abus
TokenGenerationSpike>100 tokens/minVerifier activite de bots
DrmKeyRotationFailedErreur rotation cleIntervention manuelle

Tests

describe('StreamingService', () => {
it('should generate valid token', async () => {
const result = await streamingService.generateToken(
'user-id',
'profile-id',
{ contentType: MediaType.MOVIE, contentId: 'movie-id' },
);

expect(result.token).toHaveLength(64);
expect(result.streamUrl).toContain('movie-id');
});

it('should reject expired token', async () => {
const token = await createExpiredToken();

const isValid = await streamingService.validateToken(token, '127.0.0.1');

expect(isValid).toBe(false);
});

it('should reject token from different IP', async () => {
const token = await createTokenForIp('192.168.1.1');

const isValid = await streamingService.validateToken(token, '10.0.0.1');

expect(isValid).toBe(false);
});
});

Documentation associee