Skip to content

TalentBricksAI usa AWS S3 para almacenar videos y CloudFront para streaming optimizado.

┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Usuario │────▶│ CloudFront │────▶│ S3 Bucket │
│ (Video Player)│ │ (CDN) │ │ (Storage) │
└────────────────┘ └────────────────┘ └────────────────┘
URLs Firmadas
(Acceso Temporizado)
Terminal window
# Crear bucket
aws s3 mb s3://talentbricksai-videos --region us-east-1
# Configurar como privado (por defecto)
aws s3api put-public-access-block \
--bucket talentbricksai-videos \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
talentbricksai-videos/
├── courses/
│ ├── fundamentos-data-engineering/
│ │ ├── 01-introduccion.mp4
│ │ ├── 02-conceptos.mp4
│ │ └── thumbnail.jpg
│ └── ml-basico/
│ ├── 01-intro.mp4
│ └── thumbnail.jpg
└── assets/
└── common/

Crear un usuario IAM con permisos minimos:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::talentbricksai-videos/*"
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::talentbricksai-videos"
}
]
}
Terminal window
# Subir un video
aws s3 cp ./video.mp4 s3://talentbricksai-videos/courses/mi-curso/01-intro.mp4
# Subir carpeta completa
aws s3 sync ./videos/ s3://talentbricksai-videos/courses/mi-curso/
  1. Ir a S3 en la consola de AWS
  2. Seleccionar el bucket
  3. Click en “Upload”
  4. Arrastrar archivos

La aplicacion incluye funcionalidad de upload usando presigned URLs:

// El admin panel genera URLs de subida
const { uploadUrl, fileUrl } = await generateUploadUrl({
fileName: 'video.mp4',
contentType: 'video/mp4'
});
// Subir directamente a S3
await fetch(uploadUrl, {
method: 'PUT',
body: videoFile,
headers: { 'Content-Type': 'video/mp4' }
});

En la consola de AWS CloudFront:

  1. Click “Create Distribution”
  2. Origin Domain: talentbricksai-videos.s3.us-east-1.amazonaws.com
  3. Origin Access: “Origin access control settings (recommended)”
  4. Crear nuevo OAC (Origin Access Control)
SettingValor
Viewer Protocol PolicyRedirect HTTP to HTTPS
Allowed HTTP MethodsGET, HEAD
Cache PolicyCachingOptimized
Compress ObjectsYes

Para contenido privado, usar URLs firmadas:

server/s3Utils.ts
import { getSignedUrl } from '@aws-sdk/cloudfront-signer';
export function generateVideoUrl(videoPath: string): string {
const url = `https://d123abc.cloudfront.net/${videoPath}`;
return getSignedUrl({
url,
keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID!,
privateKey: process.env.CLOUDFRONT_PRIVATE_KEY!,
dateLessThan: new Date(Date.now() + 3600 * 1000).toISOString(), // 1 hora
});
}

Permitir acceso desde CloudFront:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::talentbricksai-videos/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
}
SettingValor Recomendado
FormatoMP4 (H.264)
Resolucion1080p (1920x1080)
Bitrate5-8 Mbps
AudioAAC, 128-192 kbps
Frame Rate30 fps
Terminal window
# Comprimir video para web
ffmpeg -i input.mov \
-c:v libx264 -preset slow -crf 22 \
-c:a aac -b:a 128k \
-movflags +faststart \
output.mp4
ServicioCosto
S3 Storage~$0.023/GB/mes
S3 Requests~$0.0004/1000 GET
CloudFront Transfer~$0.085/GB (primeros 10TB)
CloudFront Requests~$0.0075/10000

Ejemplo: 100GB de videos, 1000 visualizaciones/mes

Section titled “Ejemplo: 100GB de videos, 1000 visualizaciones/mes”
  • Storage: 100GB × $0.023 = $2.30/mes
  • Transfer: ~50GB × $0.085 = $4.25/mes
  • Total: ~$6.55/mes

Agregar a .env.server:

# AWS S3
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_S3_BUCKET=talentbricksai-videos
AWS_S3_REGION=us-east-1
# CloudFront (para URLs firmadas)
CLOUDFRONT_DOMAIN=d123abc.cloudfront.net
CLOUDFRONT_KEY_PAIR_ID=K123...
CLOUDFRONT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."

El componente VideoPlayer usa las URLs firmadas:

courses/components/VideoPlayer.tsx
export function VideoPlayer({ lesson }: { lesson: Lesson }) {
const { data: videoUrl } = useQuery(getSignedVideoUrl, {
lessonId: lesson.id
});
return (
<video
src={videoUrl}
controls
onTimeUpdate={handleProgress}
/>
);
}