[Nom de la Configuration Cloudflare Stream]
Type de ressource : Configuration Cloudflare Stream (Direct Upload / Lecture / Métadonnées)
Fichier source :services/media-upload/src/index.tsou configuration associée
Statut : [Actif / Déprécié / En développement]
1. Vue dâoverview
Section titled â1. Vue dâoverviewâ1.1. Description
Section titled â1.1. Descriptionâ[Description concise du rĂŽle de cette configuration Stream dans lâarchitecture du site]
1.2. Objectif
Section titled â1.2. Objectifâ- [Objectif principal : ex. hĂ©berger les vidĂ©os du portfolio avec transcoding automatique]
- [BĂ©nĂ©fice pour lâarchitecture : ex. adaptive bitrate sans gestion serveur]
1.3. PérimÚtre concerné
Section titled â1.3. PĂ©rimĂštre concernĂ©â- Type de contenu : [Portfolio / Blog / Pages vitrine / Les deux]
- Format source : [MP4 / MOV / WebM / Les deux]
- AccÚs : [Public / Privé / Signé / Les deux]
- Durée max : [3600s (1h) / 7200s (2h) / etc.]
2. Architecture et flux
Section titled â2. Architecture et fluxâ2.1. Diagramme de flux
Section titled â2.1. Diagramme de fluxâ[Sanity Studio] â (demande d'upload vidĂ©o) âŒ[Worker media-upload (L3)] â âą GĂ©nĂ©ration Direct Upload Token â âą Retourne uploadURL + uid âŒ[Sanity Studio] â âą Upload direct vers Cloudflare Stream â âą Stocke l'uid dans le document âŒ[Cloudflare Stream] â âą Transcoding automatique (multi-bitrate) â âą GĂ©nĂ©ration des thumbnails â âą CrĂ©ation du player âŒ[Astro / Site] â âą Iframe ou SDK Stream âŒ[Utilisateur final]2.2. Composants impliquĂ©s
Section titled â2.2. Composants impliquĂ©sâ| Composant | RĂŽle | Couche |
|---|---|---|
| BibliothĂšque Stream | Stockage + transcoding + distribution | Infrastructure |
Worker media-upload |
Génération de tokens Direct Upload | L3 |
| Sanity | Stockage de lâUID Stream | L1 |
| Astro | Intégration du player (iframe/SDK) | L2 |
| Cloudflare CDN | Distribution Edge | Infrastructure |
3. Configuration
Section titled â3. Configurationâ3.1. Variables dâenvironnement requises
Section titled â3.1. Variables dâenvironnement requisesâ| Variable | Description | Obligatoire | Valeur par dĂ©faut |
|---|---|---|---|
CLOUDFLARE_ACCOUNT_ID |
ID du compte Cloudflare | â | - |
CLOUDFLARE_STREAM_API_TOKEN |
Token API Stream (avec permissions stream:write) |
â | - |
STREAM_PUBLIC_DOMAIN |
Domaine public pour les vidéos (ex: videos.votredomaine.com) |
â | iframe.videodelivery.net |
3.2. Configuration du token API
Section titled â3.2. Configuration du token APIâDans le Dashboard Cloudflare :
- My Profile â API Tokens â Create Token
- Template : âCreate Custom Tokenâ
- Permissions :
- Account â Cloudflare Stream â Edit
- Account Resources : Votre compte
- Copier le token et lâinjecter via Wrangler :
wrangler secret put CLOUDFLARE_STREAM_API_TOKEN --env productionwrangler secret put CLOUDFLARE_STREAM_API_TOKEN --env staging3.3. Configuration du domaine personnalisé (optionnel)
Section titled â3.3. Configuration du domaine personnalisĂ© (optionnel)âDans le Dashboard Cloudflare :
- Stream â Settings â Custom Domains
- Add Domain :
videos.votredomaine.com - Cloudflare proxy activé (orange cloud)
4. Upload de vidéos
Section titled â4. Upload de vidĂ©osâ4.1. Flux dâupload via Direct Upload Token
Section titled â4.1. Flux dâupload via Direct Upload Tokenâ// services/media-upload/src/index.ts (extrait)export default { async fetch(request: Request, env: Env): Promise<Response> { const { type, filename, maxDuration } = await request.json();
if (type !== 'video') { return new Response('Type non supporté', { status: 400 }); }
// Génération du Direct Upload Token const streamRes = await fetch( `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/stream/direct_upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${env.CLOUDFLARE_STREAM_API_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ maxDurationSeconds: maxDuration || 3600, allowedOrigins: ['https://votresite.com', 'https://studio.votresite.com'], requireSignedURLs: false, watermarks: [], }), } );
const streamData = await streamRes.json();
return new Response(JSON.stringify({ uploadUrl: streamData.result.uploadURL, uid: streamData.result.uid, stagingVideo: streamData.result.stagingVideo, }), { status: 200 }); },};4.2. Upload depuis Sanity Studio
Section titled â4.2. Upload depuis Sanity Studioâconst handleVideoUpload = async (file: File) => { // 1. Demander un token d'upload au Worker const res = await fetch('https://api.votredomaine.com/media-upload', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'video', filename: file.name, maxDuration: 3600, }), });
const { uploadUrl, uid } = await res.json();
// 2. Upload direct vers Cloudflare Stream (sans transiter par le Worker) const formData = new FormData(); formData.append('file', file);
await fetch(uploadUrl, { method: 'POST', body: formData, });
// 3. Retourner l'UID à Sanity pour stockage return { _type: 'video', streamUid: uid, title: file.name.replace(/\.[^/.]+$/, ''), originalFilename: file.name, };};4.3. Vérification du statut de traitement
Section titled â4.3. VĂ©rification du statut de traitementâ// VĂ©rifier si la vidĂ©o est prĂȘte (transcoding terminĂ©)const checkVideoStatus = async (uid: string, env: Env) => { const res = await fetch( `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/stream/${uid}`, { headers: { 'Authorization': `Bearer ${env.CLOUDFLARE_STREAM_API_TOKEN}`, }, } );
const data = await res.json(); return { ready: data.result.ready, status: data.result.status.state, // 'queued', 'inprogress', 'ready', 'error' thumbnail: data.result.thumbnail, duration: data.result.duration, playback: data.result.playback.hls, };};5. Intégration dans Sanity
Section titled â5. IntĂ©gration dans Sanityâ5.1. SchĂ©ma de champ vidĂ©o
Section titled â5.1. SchĂ©ma de champ vidĂ©oâimport { defineField } from 'sanity';
export const videoField = defineField({ name: 'video', title: 'VidĂ©o', type: 'object', fields: [ { name: 'streamUid', title: 'UID Stream', type: 'string', description: 'UID de la vidĂ©o dans Cloudflare Stream (gĂ©nĂ©rĂ© automatiquement)', readOnly: true, }, { name: 'title', title: 'Titre', type: 'string', validation: Rule => Rule.required(), }, { name: 'description', title: 'Description', type: 'text', }, { name: 'poster', title: 'Image de couverture', type: 'image', options: { hotspot: true }, description: 'GĂ©nĂ©rĂ©e automatiquement par Stream, mais peut ĂȘtre remplacĂ©e', }, ], preview: { select: { title: 'title', uid: 'streamUid', }, prepare({ title, uid }) { return { title, subtitle: uid ? `UID: ${uid}` : 'Non uploadĂ©e', }; }, },});5.2. Stockage dans les documents
Section titled â5.2. Stockage dans les documentsâ{ "_type": "project", "title": "Projet Alpha", "video": { "_type": "video", "streamUid": "abc123def456", "title": "PrĂ©sentation du projet", "description": "VidĂ©o de dĂ©monstration du projet Alpha", "poster": { "_type": "image", "asset": { "_type": "reference", "_ref": "image-xyz789" } } }}6. IntĂ©gration dans Astro
Section titled â6. IntĂ©gration dans Astroâ6.1. Composant VideoPlayer
Section titled â6.1. Composant VideoPlayerâ---interface Props { uid: string; title: string; poster?: string; autoplay?: boolean; muted?: boolean; controls?: boolean;}
const { uid, title, poster, autoplay = false, muted = false, controls = true} = Astro.props;
const iframeSrc = new URL(`https://iframe.videodelivery.net/${uid}`);iframeSrc.searchParams.set('poster', poster || '');iframeSrc.searchParams.set('autoplay', autoplay.toString());iframeSrc.searchParams.set('muted', muted.toString());iframeSrc.searchParams.set('controls', controls.toString());iframeSrc.searchParams.set('loop', 'false');iframeSrc.searchParams.set('preload', 'metadata');---
<div class="video-container"> <iframe src={iframeSrc.toString()} title={title} allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture" allowfullscreen loading="lazy" ></iframe></div>
<style> .video-container { position: relative; width: 100%; padding-bottom: 56.25%; /* 16:9 */ height: 0; overflow: hidden; }
.video-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0; }</style>6.2. Utilisation dans une page
Section titled â6.2. Utilisation dans une pageâ---import { getSanityClient } from '../../lib/sanity';import VideoPlayer from '../../components/ui/VideoPlayer.astro';
const project = await getSanityClient(Astro).fetch(`*[_type == "project" && slug.current == $slug][0]{ title, video { streamUid, title, poster { url } }}`, { slug: Astro.params.slug });---
<VideoPlayer uid={project.video.streamUid} title={project.video.title} poster={project.video.poster?.url} controls={true}/>6.3. Utilisation du SDK Stream (optionnel)
Section titled â6.3. Utilisation du SDK Stream (optionnel)â---// Pour un contrĂŽle plus fin, utiliser le SDK Streamimport { StreamPlayer } from '@cloudflare/stream-react';---
<StreamPlayer src={project.video.streamUid} controls={true} autoplay={false} muted={false} poster={project.video.poster?.url} responsive={true}/>7. Sécurité et accÚs
Section titled â7. SĂ©curitĂ© et accĂšsâ7.1. Types dâaccĂšs
Section titled â7.1. Types dâaccĂšsâ| Type | Usage | Configuration |
|---|---|---|
| Public | Vidéos du portfolio, démos | requireSignedURLs: false |
| Signé | Vidéos privées, previews | requireSignedURLs: true + génération de tokens |
| Restreint par domaine | Vidéos accessibles uniquement depuis votre site | allowedOrigins: ['https://votresite.com'] |
7.2. GĂ©nĂ©ration dâURLs signĂ©es (si nĂ©cessaire)
Section titled â7.2. GĂ©nĂ©ration dâURLs signĂ©es (si nĂ©cessaire)â// GĂ©nĂ©rer une URL signĂ©e pour une vidĂ©o privĂ©econst generateSignedUrl = async (uid: string, env: Env, expiration: number = 3600) => { const res = await fetch( `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/stream/${uid}/token`, { method: 'POST', headers: { 'Authorization': `Bearer ${env.CLOUDFLARE_STREAM_API_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ exp: Math.floor(Date.now() / 1000) + expiration, accessRules: [ { type: 'ip.src', country: ['FR', 'US'], // Optionnel : restreindre par pays }, ], }), } );
const data = await res.json(); return `https://iframe.videodelivery.net/${uid}?token=${data.result.token}`;};7.3. RÚgles de sécurité
Section titled â7.3. RĂšgles de sĂ©curitĂ©â- â Le token API Stream ne doit jamais ĂȘtre exposĂ© cĂŽtĂ© client
- â
Utiliser
allowedOriginspour restreindre lâembed aux domaines autorisĂ©s - â
Activer
requireSignedURLspour les vidĂ©os sensibles - â DĂ©finir des durĂ©es dâexpiration courtes pour les tokens signĂ©s (1h max)
- â Ne pas stocker les vidĂ©os sensibles dans Stream (utiliser R2 avec URLs signĂ©es)
8. Performance et optimisation
Section titled â8. Performance et optimisationâ8.1. Optimisation automatique par Stream
Section titled â8.1. Optimisation automatique par Streamâ| FonctionnalitĂ© | Description | Gain |
|---|---|---|
| Transcoding automatique | Génération de multiples bitrates (240p à 1080p) | Adaptive bitrate |
| Compression | H.264 + AAC optimisés | Réduction de taille |
| Thumbnails | GĂ©nĂ©ration automatique dâimages de couverture | UX amĂ©liorĂ©e |
| HLS + DASH | Protocoles adaptatifs | Compatibilité maximale |
| CDN Edge | Distribution mondiale | Latence réduite |
8.2. Bonnes pratiques
Section titled â8.2. Bonnes pratiquesâ- â Uploader des vidĂ©os de qualitĂ© raisonnable (1080p max, bitrate 5-10 Mbps)
- â Limiter la durĂ©e Ă 1 heure (3600s) pour les vidĂ©os courtes
- â Utiliser le format MP4 (H.264 + AAC) pour une compatibilitĂ© maximale
- â Fournir un poster personnalisĂ© si lâautomatique nâest pas pertinent
- â
Utiliser
preload="metadata"pour Ă©viter le chargement complet au chargement de la page - â
Activer
loading="lazy"sur les iframes
8.3. Limites et quotas
Section titled â8.3. Limites et quotasâ| Limite | Valeur | Rationale |
|---|---|---|
| Durée max | 3600s (1h) par vidéo | Plan Free/Pro |
| Taille max | 12 GB par vidéo | Limite upload |
| Stockage | 100h (Free) / Illimité (Pro) | Quota Stream |
| Lecture | 1000 min/mois (Free) / Illimité (Pro) | Quota Stream |
| Uploads simultanés | 10 par compte | Limite API |
9. Débogage et troubleshooting
Section titled â9. DĂ©bogage et troubleshootingâ9.1. ProblĂšmes courants
Section titled â9.1. ProblĂšmes courantsâ| SymptĂŽme | Cause probable | Solution |
|---|---|---|
| VidĂ©o ne se charge pas | UID incorrect ou vidĂ©o non prĂȘte | VĂ©rifier lâUID et le statut via lâAPI |
| âVideo not foundâ | UID invalide ou vidĂ©o supprimĂ©e | VĂ©rifier lâUID dans Sanity et Stream |
| Lecture bloquée | CORS ou allowedOrigins incorrect | Ajouter le domaine dans allowedOrigins |
| QualitĂ© faible | Upload dâune vidĂ©o basse qualitĂ© | Uploader en 1080p minimum |
| Transcoding long | Vidéo trÚs longue ou lourde | Attendre ou optimiser la source |
| Player ne sâaffiche pas | Iframe bloquĂ©e par CSP | Ajouter iframe.videodelivery.net dans CSP |
9.2. Logs et monitoring
Section titled â9.2. Logs et monitoringâ# VĂ©rifier le statut d'une vidĂ©o via l'APIcurl -H "Authorization: Bearer [token]" \ "https://api.cloudflare.com/client/v4/accounts/[account-id]/stream/[uid]"
# VĂ©rifier les logs d'upload dans le Dashboard Stream# Cloudflare Dashboard â Stream â Videos â [Video] â Analytics
# Vérifier les logs du Worker media-uploadwrangler tail media-upload9.3. Tests manuels
Section titled â9.3. Tests manuelsâ# Test de gĂ©nĂ©ration de token d'uploadcurl -X POST "https://api.votredomaine.com/media-upload" \ -H "Content-Type: application/json" \ -d '{"type": "video", "filename": "test.mp4", "maxDuration": 3600}'
# Test d'upload direct vers Streamcurl -X POST "[upload-url]" \ -F "file=@test.mp4"
# Test de vérification du statutcurl -H "Authorization: Bearer [token]" \ "https://api.cloudflare.com/client/v4/accounts/[account-id]/stream/[uid]"10. Bonnes pratiques
Section titled â10. Bonnes pratiquesâ10.1. Pour les dĂ©veloppeurs
Section titled â10.1. Pour les dĂ©veloppeursâ- â
Toujours vérifier le statut
readyavant dâafficher une vidĂ©o - â
Gérer les erreurs de transcoding (statut
error) - â Utiliser des URLs signĂ©es pour les vidĂ©os sensibles
- â Logger les uploads et les erreurs pour le debugging
- â Tester les vidĂ©os sur diffĂ©rents appareils (mobile, desktop, tablet)
10.2. Pour les contributeurs
Section titled â10.2. Pour les contributeursâ- â Optimiser les vidĂ©os avant upload (1080p, H.264, bitrate raisonnable)
- â Utiliser des titres descriptifs pour les vidĂ©os
- â VĂ©rifier le rendu sur diffĂ©rents appareils avant publication
- â Ne pas uploader de vidĂ©os de plus dâ1 heure
- â Fournir une description pour lâaccessibilitĂ©
10.3. Considérations de coût
Section titled â10.3. ConsidĂ©rations de coĂ»tâ- â ïž Stream facture le stockage + le transcoding + la lecture
- â Supprimer les vidĂ©os obsolĂštes rĂ©guliĂšrement
- â Utiliser des durĂ©es courtes pour les dĂ©mos (< 5 min)
- â Optimiser les vidĂ©os source pour rĂ©duire le transcoding
- â Surveiller les quotas dans le Dashboard Cloudflare
11. Ăvolutions et roadmap
Section titled â11. Ăvolutions et roadmapâ11.1. AmĂ©liorations planifiĂ©es
Section titled â11.1. AmĂ©liorations planifiĂ©esâ- [Ăvolution 1 : ex. Support des sous-titres (VTT)]
- [Ăvolution 2 : ex. Analytics avancĂ©s (views, engagement)]
- [Ăvolution 3 : ex. Watermarking dynamique]
11.2. Limitations connues
Section titled â11.2. Limitations connuesâ- â Stream ne supporte pas nativement les vidĂ©os 4K
- â Les vidĂ©os ne peuvent pas ĂȘtre tĂ©lĂ©chargĂ©es (streaming uniquement)
- â Le transcoding peut prendre plusieurs minutes pour les vidĂ©os longues
- â Les URLs signĂ©es ont une durĂ©e de vie limitĂ©e (pas de partage long terme)
12. Ressources liées
Section titled â12. Ressources liĂ©esâ- Documentation Cloudflare Stream : Getting Started
- Documentation Cloudflare Stream : Direct Upload
- Documentation Cloudflare Stream : Signed URLs
- DAT L1 : §4.8 - Gestion des Médias (Cloudflare R2 + Stream)
- DAT L2 : §5.4 - Consommation et optimisation des médias
- DAT L3 : §5.3 - Worker
media-upload - Fichiers concernés :
services/media-upload/src/index.tssanity-studio/schemas/fields/video.tssrc/components/ui/VideoPlayer.astro- Configuration Dashboard Cloudflare â Stream
13. Historique des modifications
Section titled â13. Historique des modificationsâ| Date | Version | Description | Auteur |
|---|---|---|---|
| YYYY-MM-DD | 1.0 | Création initiale | [Nom] |
| YYYY-MM-DD | 1.1 | [Modification] | [Nom] |
---
**Points clĂ©s de ce template :**- Documente le flux complet d'upload (Sanity â Worker â Stream)- DĂ©taille l'intĂ©gration avec Sanity (schĂ©ma, stockage UID)- Fournit l'intĂ©gration avec Astro (composant VideoPlayer, iframe, SDK)- Couvre la sĂ©curitĂ© (accĂšs public/signĂ©, allowedOrigins)- Inclut les bonnes pratiques de performance et d'optimisation- SĂ©pare clairement les responsabilitĂ©s (dĂ©veloppeurs vs contributeurs)