Carregamento multipart
Para ficheiros de vídeo grandes (superiores a 100MB), recomendamos o carregamento multipart. Este método divide o teu ficheiro em partes mais pequenas que são carregadas em paralelo, o que melhora a fiabilidade e o desempenho para ficheiros grandes.
O carregamento multipart é recomendado para ficheiros maiores que 100MB e suporta ficheiros até 60GB.
Para uma implementação mais simples, considera a biblioteca Uppy, que trata da divisão em partes, novas tentativas e seguimento do progresso automaticamente.
Visão geral
O processo de carregamento multipart tem quatro passos:
- Inicializar — Criar um objeto de vídeo e pedir um carregamento multipart
- Obter URLs assinadas — Pedir URLs pré-assinadas para cada parte (em lotes para eficiência)
- Carregar partes — Carregar cada parte para o seu URL assinado
- Concluir — Indicar que todas as partes foram carregadas
Inicializar o carregamento multipart
Cria um objeto de vídeo enviando um pedido PUT para /videos/upload com useMultipart: true.
CURL
curl -X PUT https://app.ignitevideo.cloud/api/videos/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Your video title",
"mimeType": "video/mp4",
"useMultipart": true
}'Resposta
{
"videoId": "[VIDEO_ID]",
"title": "Your video title",
"multipartUpload": {
"uploadId": "[UPLOAD_ID]",
"key": "[S3_KEY]"
}
}Guarda os valores uploadId e key — vais precisar deles em todos os pedidos seguintes.
Obter URLs assinadas para as partes
Antes de carregares as partes, tens de obter URLs pré-assinadas. Por eficiência, pede URLs em lotes em vez de uma a uma.
Pedido em lote (recomendado)
Pede URLs assinadas para várias partes de uma vez com o endpoint prepare-parts.
CURL
curl -X POST "https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/[UPLOAD_ID]/prepare-parts?key=[S3_KEY]" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"startPart": 1, "endPart": 100}'Resposta
{
"parts": [
{ "partNumber": 1, "url": "[SIGNED_URL_FOR_PART_1]" },
{ "partNumber": 2, "url": "[SIGNED_URL_FOR_PART_2]" }
]
}Pedido de uma única parte (alternativa)
Se precisares do URL de uma parte específica, podes pedi-lo individualmente.
CURL
curl "https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/[UPLOAD_ID]/[PART_NUMBER]?key=[S3_KEY]" \
-H "Authorization: Bearer YOUR_TOKEN"Carregar as partes
Divide o teu ficheiro em partes (recomendado: 5MB - 100MB por parte) e carrega cada parte para o URL assinado correspondente com um pedido PUT.
Tamanho das partes: O carregamento multipart exige um tamanho mínimo de 5MB por parte (exceto a última parte). Recomendamos partes de 10MB como equilíbrio entre paralelização e overhead.
O exemplo abaixo assume que já obtiveste todos os URLs assinados necessários. Para ficheiros com mais de 100 partes, tens de obter URLs em lotes. Vê o Exemplo completo abaixo para tratar ficheiros grandes.
JavaScript / Node.js
const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB chunks
const file = yourVideoFile; // File object
const totalParts = Math.ceil(file.size / CHUNK_SIZE);
const uploadedParts = [];
// Upload parts in parallel (limit concurrency to 5)
const uploadPart = async (partNumber, signedUrl) => {
const start = (partNumber - 1) * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const response = await fetch(signedUrl, {
method: 'PUT',
body: chunk
});
// Get the ETag from the response headers
const etag = response.headers.get('ETag');
return {
PartNumber: partNumber,
ETag: etag
};
};
// Upload all parts
for (let i = 0; i < totalParts; i += 5) {
const batch = [];
for (let j = i; j < Math.min(i + 5, totalParts); j++) {
const partNumber = j + 1;
const signedUrl = partsData.parts.find(p => p.partNumber === partNumber)?.url;
if (signedUrl) {
batch.push(uploadPart(partNumber, signedUrl));
}
}
const results = await Promise.all(batch);
uploadedParts.push(...results);
}Importante: Tens de recolher o cabeçalho ETag de cada resposta de carregamento de parte. São necessários para concluir o carregamento multipart.
Concluir o carregamento multipart
Depois de todas as partes estarem carregadas, envia um pedido de conclusão com a lista de partes e os respetivos ETags.
CURL
curl -X POST "https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/[UPLOAD_ID]/complete?key=[S3_KEY]" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"parts": [
{ "PartNumber": 1, "ETag": "\"etag1\"" },
{ "PartNumber": 2, "ETag": "\"etag2\"" }
]
}'Para carregamentos com mais de 100 partes, podes usar fetchPartsFromStorage: true em vez de passar o array de partes. O servidor obtém a informação das partes automaticamente.
// For large uploads with many parts
const completeResponse = await fetch(
`https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/complete?key=${encodeURIComponent(key)}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
fetchPartsFromStorage: true
})
}
);Processo de codificação
Depois de o carregamento multipart estar concluído, o vídeo será codificado. Este processo pode demorar consoante o tamanho do vídeo e a complexidade da codificação.
Podes verificar o estado da codificação de um vídeo chamando o endpoint /videos/[VIDEO_ID] como descrito na secção obter vídeo.
Exemplo completo
Segue-se um exemplo completo em JavaScript que implementa o carregamento multipart com seguimento do progresso:
async function uploadLargeVideo(file, title, token) {
const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
const BATCH_SIZE = 100;
const CONCURRENT_UPLOADS = 5;
// Step 1: Initialize multipart upload
const initResponse = await fetch('https://app.ignitevideo.cloud/api/videos/upload', {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title,
mimeType: file.type,
useMultipart: true
})
});
const { videoId, multipartUpload } = await initResponse.json();
const { uploadId, key } = multipartUpload;
// Calculate total parts
const totalParts = Math.ceil(file.size / CHUNK_SIZE);
const uploadedParts = [];
let uploadedBytes = 0;
// Step 2 & 3: Get signed URLs and upload parts in batches
for (let batchStart = 1; batchStart <= totalParts; batchStart += BATCH_SIZE) {
const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, totalParts);
// Get signed URLs for this batch
const urlsResponse = await fetch(
`https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/prepare-parts?key=${encodeURIComponent(key)}`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ startPart: batchStart, endPart: batchEnd })
}
);
const { parts } = await urlsResponse.json();
// Upload parts with concurrency limit
for (let i = 0; i < parts.length; i += CONCURRENT_UPLOADS) {
const chunk = parts.slice(i, i + CONCURRENT_UPLOADS);
const uploads = chunk.map(async ({ partNumber, url }) => {
const start = (partNumber - 1) * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const blob = file.slice(start, end);
const response = await fetch(url, {
method: 'PUT',
body: blob
});
uploadedBytes += blob.size;
const progress = (uploadedBytes / file.size) * 100;
console.log(`Upload progress: ${progress.toFixed(1)}%`);
return {
PartNumber: partNumber,
ETag: response.headers.get('ETag')
};
});
const results = await Promise.all(uploads);
uploadedParts.push(...results);
}
}
// Step 4: Complete multipart upload
uploadedParts.sort((a, b) => a.PartNumber - b.PartNumber);
await fetch(
`https://app.ignitevideo.cloud/api/videos/upload/s3/multipart/${uploadId}/complete?key=${encodeURIComponent(key)}`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ parts: uploadedParts })
}
);
console.log(`Upload complete! Video ID: ${videoId}`);
return videoId;
}Tratamento de erros
Se um carregamento falhar, podes repetir partes individuais sem recomeçar o carregamento inteiro. O uploadId mantém-se válido durante 24 horas.
Para partes falhadas:
- Pede um novo URL assinado para o número da parte que falhou
- Volta a carregar só essa parte
- Continua com o passo de conclusão quando todas as partes estiverem carregadas