Table of Contents
- JavaScript SDK Guide
- Installation
- Node.js
- With TypeScript support
- With async/await support
- Browser (CDN)
- ES Modules
- Quick Start
- Basic Configuration
- Your First Conversion
- Core Classes
- MediaConvert.ConversionJob
- MediaConvert.Account
- Node.js Integration
- Express.js Application
- Background Job Processing (Bull Queue)
- TypeScript Integration
- Browser Integration
- Modern Frontend Framework (React)
- Vue.js Integration
- Error Handling Patterns
- Comprehensive Error Handling
- Batch Processing
- Concurrent Processing with Promise Control
- AWS S3 Integration
- S3 Helper Class
- Testing
- Jest Testing Framework
- Performance Optimization
- Connection Pooling and Caching
- Next Steps
- Support
JavaScript SDK Guide
The MediaConvert.io JavaScript SDK works in both Node.js and browser environments. This guide covers installation, configuration, and common usage patterns for modern JavaScript applications.
Installation
Node.js
bash
npm install @mediaconvert/sdk
# With TypeScript support
npm install @mediaconvert/sdk @types/mediaconvert
# With async/await support
npm install @mediaconvert/sdk axios
Browser (CDN)
html
<!-- Latest version -->
<script src="https://cdn.jsdelivr.net/npm/@mediaconvert/sdk@latest/dist/mediaconvert.min.js"></script>
<!-- Specific version -->
<script src="https://cdn.jsdelivr.net/npm/@mediaconvert/sdk@1.0.0/dist/mediaconvert.min.js"></script>
ES Modules
javascript
// ES6 imports
import MediaConvert from '@mediaconvert/sdk';
// CommonJS
const MediaConvert = require('@mediaconvert/sdk');
// Browser ES modules
import MediaConvert from 'https://cdn.skypack.dev/@mediaconvert/sdk';
Quick Start
Basic Configuration
javascript
import MediaConvert, { ConversionJob, Account } from '@mediaconvert/sdk';
// Configure the client
MediaConvert.configure({
apiToken: process.env.MEDIACONVERT_API_TOKEN,
apiBaseUrl: 'https://mediaconvert.io/api/v1',
timeout: 30000,
retries: 3
});
// Alternative: Create client instance
const client = new MediaConvert({
apiToken: process.env.MEDIACONVERT_API_TOKEN
});
Your First Conversion
javascript
async function convertVideo() {
try {
// Create a conversion job
const job = await ConversionJob.create({
jobType: 'video',
inputUrl: 'https://bucket.s3.amazonaws.com/input.mp4?...',
outputUrl: 'https://bucket.s3.amazonaws.com/output.webm?...',
inputFormat: 'mp4',
outputFormat: 'webm',
inputSizeBytes: 52428800,
webhookUrl: 'https://your-app.com/webhooks/mediaconvert'
});
console.log(`Job created: ${job.id}`);
console.log(`Status: ${job.status}`);
// New micro-precision API (recommended)
console.log(`Estimated cost: €${job.estimatedCostMicros / 1_000_000}`);
// Or using backward-compatible cents field
console.log(`Estimated cost: €${job.estimatedCostCents / 100}`);
return job;
} catch (error) {
console.error('Conversion failed:', error.message);
throw error;
}
}
Core Classes
MediaConvert.ConversionJob
The main class for managing conversion jobs:
javascript
// Create a job
const job = await ConversionJob.create({
jobType: 'video',
inputUrl: inputPresignedUrl,
outputUrl: outputPresignedUrl,
inputFormat: 'mp4',
outputFormat: 'webm',
inputSizeBytes: fileSizeBytes
});
// Check job status
await job.reload();
console.log(`Status: ${job.status}`);
console.log(`Progress: ${job.progressPercentage}%`);
// Wait for completion with progress callback
await job.waitUntilComplete((currentJob) => {
console.log(`Progress: ${currentJob.progressPercentage}%`);
});
// Access results
console.log(`Processing time: ${job.processingTimeSeconds}s`);
console.log(`Output size: ${job.outputSizeBytes} bytes`);
// New micro-precision API (recommended)
console.log(`Total cost: €${job.costMicros / 1_000_000}`);
// Or using backward-compatible cents field
console.log(`Total cost: €${job.costCents / 100}`);
MediaConvert.Account
Manage your account information:
javascript
// Get account details
const account = await Account.current();
console.log(`Email: ${account.email}`);
console.log(`Tier: ${account.tier}`);
console.log(`Credits: €${account.creditsBalanceCents / 100}`);
// Check usage
const usage = await account.usageSummary();
console.log(`Jobs this month: ${usage.jobsCount}`);
console.log(`Data processed: ${usage.totalMbProcessed} MB`);
console.log(`Total spent: €${usage.totalCostMicros / 1_000_000}`);
Node.js Integration
Express.js Application
javascript
const express = require('express');
const MediaConvert = require('@mediaconvert/sdk');
const app = express();
// Configure MediaConvert
MediaConvert.configure({
apiToken: process.env.MEDIACONVERT_API_TOKEN,
webhookSecret: process.env.MEDIACONVERT_WEBHOOK_SECRET
});
app.use(express.json());
// Start conversion endpoint
app.post('/api/convert', async (req, res) => {
try {
const { inputUrl, outputUrl, jobType, inputFormat, outputFormat } = req.body;
const job = await MediaConvert.ConversionJob.create({
jobType,
inputUrl,
outputUrl,
inputFormat,
outputFormat,
webhookUrl: `${req.protocol}://${req.get('host')}/webhooks/mediaconvert`
});
res.status(201).json({
jobId: job.id,
status: job.status,
estimatedCostMicros: job.estimatedCostMicros
});
} catch (error) {
console.error('Conversion error:', error);
res.status(400).json({ error: error.message });
}
});
// Webhook endpoint
app.post('/webhooks/mediaconvert', MediaConvert.webhookHandler({
onCompleted: async (jobData) => {
console.log(`Job ${jobData.jobId} completed successfully`);
// Process completed job
await handleCompletedJob(jobData);
},
onFailed: async (jobData) => {
console.log(`Job ${jobData.jobId} failed: ${jobData.errorMessage}`);
// Handle failed job
await handleFailedJob(jobData);
},
onProgress: async (jobData) => {
console.log(`Job ${jobData.jobId} progress: ${jobData.progressPercentage}%`);
// Update progress in database
await updateJobProgress(jobData.jobId, jobData.progressPercentage);
}
}));
async function handleCompletedJob(jobData) {
// Update database, notify user, etc.
await updateJobStatus(jobData.jobId, 'completed');
await notifyUser(jobData.userId, 'Conversion completed!');
}
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Background Job Processing (Bull Queue)
javascript
const Bull = require('bull');
const MediaConvert = require('@mediaconvert/sdk');
// Create conversion queue
const conversionQueue = new Bull('media conversion', {
redis: {
port: 6379,
host: '127.0.0.1'
}
});
// Process conversion jobs
conversionQueue.process('convert', async (job) => {
const { inputUrl, outputUrl, options, userId } = job.data;
try {
const conversionJob = await MediaConvert.ConversionJob.create({
inputUrl,
outputUrl,
webhookUrl: `https://yourapp.com/webhooks/conversion/${userId}`,
...options
});
// Store job reference
await storeJobReference(userId, conversionJob.id, {
status: conversionJob.status,
inputUrl,
outputUrl
});
return {
jobId: conversionJob.id,
status: conversionJob.status
};
} catch (error) {
if (error instanceof MediaConvert.InsufficientCreditsError) {
// Don't retry - notify user to add credits
await notifyInsufficientCredits(userId, error.requiredCredits, error.availableCredits);
throw new Error(`Insufficient credits: need €${error.requiredCredits / 1_000_000} but only have €${error.availableCredits / 1_000_000}`);
}
if (error instanceof MediaConvert.RateLimitError) {
// Retry after delay
throw new Error(`Rate limited. Retry after ${error.retryAfter}s`);
}
throw error;
}
});
// Add retry logic for rate limits
conversionQueue.on('failed', (job, err) => {
if (err.message.includes('Rate limited')) {
const retryAfter = extractRetryAfter(err.message);
setTimeout(() => {
job.retry();
}, retryAfter * 1000);
}
});
// Usage: Add job to queue
async function enqueueConversion(userId, inputUrl, outputUrl, options = {}) {
await conversionQueue.add('convert', {
userId,
inputUrl,
outputUrl,
options
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
});
}
TypeScript Integration
typescript
import MediaConvert, {
ConversionJob,
Account,
MediaConvertError,
InsufficientCreditsError,
ValidationError,
ConversionError,
RateLimitError
} from '@mediaconvert/sdk';
// Type definitions
interface ConversionOptions {
jobType: 'video' | 'image' | 'audio' | 'document';
inputUrl: string;
outputUrl: string;
inputFormat: string;
outputFormat: string;
inputSizeBytes?: number;
webhookUrl?: string;
}
interface ConversionResult {
success: boolean;
jobId?: string;
costMicros?: number;
processingTime?: number;
error?: string;
}
class MediaConversionService {
constructor(private readonly apiToken: string) {
MediaConvert.configure({
apiToken: this.apiToken
});
}
async convertWithRetry(
options: ConversionOptions,
maxRetries: number = 3
): Promise<ConversionResult> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const job = await ConversionJob.create(options);
await job.waitUntilComplete((currentJob) => {
console.log(`Attempt ${attempt + 1}: Progress ${currentJob.progressPercentage}%`);
});
return {
success: true,
jobId: job.id,
costMicros: job.costMicros,
processingTime: job.processingTimeSeconds
};
} catch (error) {
if (error instanceof InsufficientCreditsError) {
return {
success: false,
error: `Need €${error.requiredCredits / 1_000_000} but only have €${error.availableCredits / 1_000_000}`
};
}
if (error instanceof ValidationError) {
return {
success: false,
error: `Validation failed: ${error.errors.join(', ')}`
};
}
if (error instanceof RateLimitError && attempt < maxRetries) {
console.log(`Rate limited. Waiting ${error.retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, error.retryAfter * 1000));
continue;
}
if (attempt === maxRetries) {
return {
success: false,
error: error.message
};
}
// Exponential backoff for other errors
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return {
success: false,
error: 'Max retries exceeded'
};
}
}
// Usage
const conversionService = new MediaConversionService(process.env.MEDIACONVERT_API_TOKEN!);
const result = await conversionService.convertWithRetry({
jobType: 'video',
inputUrl: 'https://example.com/input.mp4',
outputUrl: 'https://example.com/output.webm',
inputFormat: 'mp4',
outputFormat: 'webm'
});
if (result.success) {
console.log(`Conversion completed! Cost: €${result.costMicros! / 1_000_000}`);
} else {
console.error(`Conversion failed: ${result.error}`);
}
Browser Integration
Modern Frontend Framework (React)
javascript
import React, { useState, useEffect } from 'react';
import MediaConvert from '@mediaconvert/sdk';
// Configure for browser environment
MediaConvert.configure({
apiToken: process.env.REACT_APP_MEDIACONVERT_API_TOKEN,
// Note: In browser, consider using a backend proxy for API calls
// to avoid exposing API tokens
});
function VideoConverter() {
const [file, setFile] = useState(null);
const [job, setJob] = useState(null);
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState('idle');
const handleFileSelect = (event) => {
setFile(event.target.files[0]);
};
const generatePresignedUrls = async (filename) => {
// Call your backend to generate presigned URLs
const response = await fetch('/api/presigned-urls', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename })
});
return response.json();
};
const uploadToS3 = async (file, presignedUrl) => {
const formData = new FormData();
formData.append('file', file);
await fetch(presignedUrl, {
method: 'PUT',
body: file
});
};
const startConversion = async () => {
if (!file) return;
try {
setStatus('uploading');
// Generate presigned URLs via backend
const { inputUrl, outputUrl } = await generatePresignedUrls(file.name);
// Upload file to S3
await uploadToS3(file, inputUrl);
setStatus('converting');
// Start conversion
const conversionJob = await MediaConvert.ConversionJob.create({
jobType: 'video',
inputUrl,
outputUrl,
inputFormat: file.name.split('.').pop(),
outputFormat: 'webm',
inputSizeBytes: file.size
});
setJob(conversionJob);
// Monitor progress
const progressInterval = setInterval(async () => {
await conversionJob.reload();
setProgress(conversionJob.progressPercentage || 0);
if (conversionJob.status === 'completed') {
setStatus('completed');
clearInterval(progressInterval);
} else if (conversionJob.status === 'failed') {
setStatus('failed');
clearInterval(progressInterval);
}
}, 2000);
} catch (error) {
console.error('Conversion error:', error);
setStatus('error');
}
};
return (
<div className="video-converter">
<h2>Video Converter</h2>
<div className="upload-section">
<input
type="file"
accept="video/*"
onChange={handleFileSelect}
disabled={status !== 'idle'}
/>
<button
onClick={startConversion}
disabled={!file || status !== 'idle'}
>
Convert to WebM
</button>
</div>
{status !== 'idle' && (
<div className="status-section">
<div className="status">Status: {status}</div>
{status === 'converting' && (
<div className="progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
<span>{progress}%</span>
</div>
)}
{status === 'completed' && job && (
<div className="results">
<p>Conversion completed!</p>
<p>Cost: €{job.costMicros / 1_000_000}</p>
<p>Processing time: {job.processingTimeSeconds}s</p>
</div>
)}
{status === 'failed' && (
<div className="error">
Conversion failed. Please try again.
</div>
)}
</div>
)}
</div>
);
}
export default VideoConverter;
Vue.js Integration
javascript
<template>
<div class="media-converter">
<h2>Media Converter</h2>
<div class="file-input">
<input
type="file"
@change="handleFileSelect"
:disabled="isProcessing"
/>
<button
@click="startConversion"
:disabled="!selectedFile || isProcessing"
>
{{ isProcessing ? 'Converting...' : 'Convert' }}
</button>
</div>
<div v-if="job" class="job-status">
<p>Status: {{ job.status }}</p>
<div v-if="job.progressPercentage" class="progress">
Progress: {{ job.progressPercentage }}%
</div>
<div v-if="job.status === 'completed'" class="results">
<p>✅ Conversion completed!</p>
<p>Cost: €{{ (job.costMicros / 1_000_000).toFixed(6) }}</p>
</div>
</div>
<div v-if="error" class="error">
{{ error }}
</div>
</div>
</template>
<script>
import MediaConvert from '@mediaconvert/sdk';
export default {
name: 'MediaConverter',
data() {
return {
selectedFile: null,
job: null,
error: null,
isProcessing: false,
progressInterval: null
};
},
methods: {
handleFileSelect(event) {
this.selectedFile = event.target.files[0];
this.error = null;
},
async startConversion() {
if (!this.selectedFile) return;
this.isProcessing = true;
this.error = null;
try {
// Generate presigned URLs via your backend API
const urls = await this.$http.post('/api/presigned-urls', {
filename: this.selectedFile.name
});
// Upload file to S3
await fetch(urls.data.inputUrl, {
method: 'PUT',
body: this.selectedFile
});
// Create conversion job
this.job = await MediaConvert.ConversionJob.create({
jobType: 'video',
inputUrl: urls.data.inputUrl,
outputUrl: urls.data.outputUrl,
inputFormat: this.selectedFile.name.split('.').pop(),
outputFormat: 'webm',
inputSizeBytes: this.selectedFile.size
});
// Monitor progress
this.monitorProgress();
} catch (error) {
this.error = error.message;
this.isProcessing = false;
}
},
async monitorProgress() {
this.progressInterval = setInterval(async () => {
try {
await this.job.reload();
if (this.job.status === 'completed' || this.job.status === 'failed') {
clearInterval(this.progressInterval);
this.isProcessing = false;
}
} catch (error) {
console.error('Progress monitoring error:', error);
}
}, 2000);
}
},
beforeDestroy() {
if (this.progressInterval) {
clearInterval(this.progressInterval);
}
}
};
</script>
<style scoped>
.progress {
margin: 10px 0;
}
.error {
color: red;
margin-top: 10px;
}
.results {
color: green;
margin-top: 10px;
}
</style>
Error Handling Patterns
Comprehensive Error Handling
javascript
import {
AuthenticationError,
InsufficientCreditsError,
ValidationError,
ConversionError,
RateLimitError,
APIError,
NetworkError,
TimeoutError
} from '@mediaconvert/sdk';
async function robustConversion(inputUrl, outputUrl, options = {}) {
const maxRetries = 3;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const job = await MediaConvert.ConversionJob.create({
inputUrl,
outputUrl,
...options
});
await job.waitUntilComplete({
timeout: 3600000, // 1 hour
progressCallback: (job) => {
console.log(`Progress: ${job.progressPercentage}%`);
}
});
return {
success: true,
jobId: job.id,
costMicros: job.costMicros,
processingTime: job.processingTimeSeconds
};
} catch (error) {
console.error(`Attempt ${attempt + 1} failed:`, error.message);
// Handle different error types
if (error instanceof AuthenticationError) {
return {
success: false,
error: 'Authentication failed - check your API token',
retryable: false
};
}
if (error instanceof InsufficientCreditsError) {
return {
success: false,
error: `Need €${error.requiredCredits / 1_000_000} but only have €${error.availableCredits / 1_000_000}`,
retryable: false,
requiredCreditsMicros: error.requiredCredits,
availableCreditsMicros: error.availableCredits
};
}
if (error instanceof ValidationError) {
return {
success: false,
error: `Validation failed: ${error.errors.join(', ')}`,
retryable: false
};
}
if (error instanceof ConversionError) {
return {
success: false,
error: `Conversion failed: ${error.message}`,
retryable: false
};
}
if (error instanceof RateLimitError) {
if (attempt < maxRetries) {
console.log(`Rate limited. Waiting ${error.retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, error.retryAfter * 1000));
continue;
} else {
return {
success: false,
error: `Rate limited after ${maxRetries} attempts`,
retryAfter: error.retryAfter
};
}
}
if (error instanceof NetworkError || error instanceof TimeoutError) {
if (attempt < maxRetries) {
const waitTime = Math.pow(2, attempt) * 1000;
console.log(`Network error. Retrying in ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
} else {
return {
success: false,
error: `Network error after ${maxRetries} attempts: ${error.message}`,
retryable: true
};
}
}
if (error instanceof APIError) {
if (attempt < maxRetries && error.statusCode >= 500) {
const waitTime = Math.pow(2, attempt) * 1000;
console.log(`Server error. Retrying in ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
} else {
return {
success: false,
error: `API error: ${error.message} (Status: ${error.statusCode})`,
retryable: error.statusCode >= 500
};
}
}
// Unknown error
if (attempt === maxRetries) {
return {
success: false,
error: `Unknown error: ${error.message}`,
retryable: true
};
}
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
// Usage with comprehensive error handling
const result = await robustConversion(inputUrl, outputUrl, {
jobType: 'video',
inputFormat: 'mp4',
outputFormat: 'webm'
});
if (result.success) {
console.log(`✅ Conversion completed! Cost: €${result.costMicros / 1_000_000}`);
} else {
console.error(`❌ Conversion failed: ${result.error}`);
if (result.retryable) {
console.log('💡 This error might be temporary. You can try again.');
}
if (result.requiredCreditsMicros) {
console.log(`💳 Add €${(result.requiredCreditsMicros - result.availableCreditsMicros) / 1_000_000} to your account`);
}
}
Batch Processing
Concurrent Processing with Promise Control
javascript
class BatchProcessor {
constructor(maxConcurrency = 5) {
this.maxConcurrency = maxConcurrency;
this.jobs = [];
}
addJob(inputUrl, outputUrl, options = {}) {
const jobPromise = MediaConvert.ConversionJob.create({
inputUrl,
outputUrl,
...options
});
this.jobs.push(jobPromise);
return jobPromise;
}
async processAll() {
const results = {
completed: [],
failed: []
};
// Process in batches to control concurrency
for (let i = 0; i < this.jobs.length; i += this.maxConcurrency) {
const batch = this.jobs.slice(i, i + this.maxConcurrency);
const batchResults = await Promise.allSettled(
batch.map(async (jobPromise) => {
const job = await jobPromise;
await job.waitUntilComplete();
return job;
})
);
batchResults.forEach((result) => {
if (result.status === 'fulfilled') {
results.completed.push(result.value);
} else {
results.failed.push({
error: result.reason,
job: result.reason.job || null
});
}
});
console.log(`Processed batch ${Math.ceil((i + this.maxConcurrency) / this.maxConcurrency)}`);
}
return results;
}
async processAllWithProgress(progressCallback) {
const totalJobs = this.jobs.length;
let completedJobs = 0;
const jobsWithProgress = await Promise.all(this.jobs);
const results = await Promise.allSettled(
jobsWithProgress.map(async (job) => {
try {
await job.waitUntilComplete((currentJob) => {
// Individual job progress
progressCallback({
jobId: currentJob.id,
progress: currentJob.progressPercentage,
overallProgress: (completedJobs / totalJobs) * 100
});
});
completedJobs++;
return job;
} catch (error) {
completedJobs++;
throw error;
}
})
);
return {
completed: results.filter(r => r.status === 'fulfilled').map(r => r.value),
failed: results.filter(r => r.status === 'rejected').map(r => ({
error: r.reason,
job: r.reason.job
}))
};
}
}
// Usage
const processor = new BatchProcessor(3); // Max 3 concurrent jobs
// Add multiple jobs
processor.addJob('s3://bucket/input1.mp4', 's3://bucket/output1.webm', { outputFormat: 'webm' });
processor.addJob('s3://bucket/input2.mp4', 's3://bucket/output2.mp4', { outputFormat: 'mp4' });
processor.addJob('s3://bucket/input3.jpg', 's3://bucket/output3.webp', { outputFormat: 'webp' });
// Process with progress tracking
const results = await processor.processAllWithProgress((progress) => {
console.log(`Job ${progress.jobId}: ${progress.progress}% (Overall: ${progress.overallProgress}%)`);
});
console.log(`${results.completed.length} completed, ${results.failed.length} failed`);
// Calculate total cost
const totalCostMicros = results.completed.reduce((sum, job) => sum + (job.costMicros || 0), 0);
console.log(`Total cost: €${totalCostMicros / 1_000_000}`);
AWS S3 Integration
S3 Helper Class
javascript
import AWS from 'aws-sdk';
class S3Helper {
constructor(bucketName, region = 'us-east-1') {
this.bucketName = bucketName;
this.s3 = new AWS.S3({ region });
}
async generatePresignedUrl(key, method = 'getObject', expiresIn = 3600) {
const params = {
Bucket: this.bucketName,
Key: key,
Expires: expiresIn
};
try {
if (method === 'putObject') {
return await this.s3.getSignedUrlPromise('putObject', params);
} else {
return await this.s3.getSignedUrlPromise('getObject', params);
}
} catch (error) {
throw new Error(`Failed to generate presigned URL: ${error.message}`);
}
}
async getObjectSize(key) {
try {
const response = await this.s3.headObject({
Bucket: this.bucketName,
Key: key
}).promise();
return response.ContentLength;
} catch (error) {
throw new Error(`Failed to get object size: ${error.message}`);
}
}
async uploadFile(file, key) {
try {
await this.s3.upload({
Bucket: this.bucketName,
Key: key,
Body: file
}).promise();
return await this.generatePresignedUrl(key);
} catch (error) {
throw new Error(`Failed to upload file: ${error.message}`);
}
}
}
// Complete S3-to-S3 conversion workflow
async function convertFromS3(bucketName, inputKey, outputKey, options = {}) {
const s3Helper = new S3Helper(bucketName);
// Get input file size
const inputSizeBytes = await s3Helper.getObjectSize(inputKey);
// Generate presigned URLs
const inputUrl = await s3Helper.generatePresignedUrl(inputKey, 'getObject');
const outputUrl = await s3Helper.generatePresignedUrl(outputKey, 'putObject');
// Create conversion job
const job = await MediaConvert.ConversionJob.create({
inputUrl,
outputUrl,
inputSizeBytes,
...options
});
return job;
}
// Usage
const job = await convertFromS3(
'my-media-bucket',
'videos/input.mp4',
'videos/output.webm',
{
jobType: 'video',
inputFormat: 'mp4',
outputFormat: 'webm'
}
);
console.log(`Conversion started: ${job.id}`);
await job.waitUntilComplete();
console.log(`Conversion completed! Cost: €${job.costMicros / 1_000_000}`);
Testing
Jest Testing Framework
javascript
// __tests__/mediaconvert.test.js
import MediaConvert, { ConversionJob } from '@mediaconvert/sdk';
// Mock the HTTP client
jest.mock('@mediaconvert/sdk');
describe('MediaConvert Integration', () => {
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Configure MediaConvert
MediaConvert.configure({
apiToken: 'test_token'
});
});
test('should create conversion job successfully', async () => {
const mockJob = {
id: 'job_test123',
status: 'pending',
estimatedCostMicros: 150000 // €0.15
};
ConversionJob.create.mockResolvedValue(mockJob);
const job = await ConversionJob.create({
jobType: 'video',
inputUrl: 'https://example.com/input.mp4',
outputUrl: 'https://example.com/output.webm'
});
expect(job.id).toBe('job_test123');
expect(job.status).toBe('pending');
expect(job.estimatedCostMicros).toBe(150000);
expect(ConversionJob.create).toHaveBeenCalledWith({
jobType: 'video',
inputUrl: 'https://example.com/input.mp4',
outputUrl: 'https://example.com/output.webm'
});
});
test('should handle insufficient credits error', async () => {
const error = new MediaConvert.InsufficientCreditsError(
'Insufficient credits',
200000, // required
50000 // available
);
ConversionJob.create.mockRejectedValue(error);
await expect(ConversionJob.create({
jobType: 'video',
inputUrl: 'https://example.com/input.mp4',
outputUrl: 'https://example.com/output.webv'
})).rejects.toThrow(MediaConvert.InsufficientCreditsError);
});
test('should retry on rate limit error', async () => {
const rateLimitError = new MediaConvert.RateLimitError('Rate limited', 30);
const successJob = { id: 'job_success', status: 'pending' };
ConversionJob.create
.mockRejectedValueOnce(rateLimitError)
.mockResolvedValue(successJob);
// Test retry logic would go here
// This depends on your specific retry implementation
});
});
// Integration test helpers
export class MediaConvertTestHelper {
static mockSuccessfulJob() {
return {
id: 'job_test123',
status: 'completed',
progressPercentage: 100,
costMicros: 150000,
processingTimeSeconds: 45,
reload: jest.fn().mockResolvedValue(undefined),
waitUntilComplete: jest.fn().mockResolvedValue(undefined)
};
}
static mockFailedJob() {
return {
id: 'job_test456',
status: 'failed',
errorMessage: 'Unsupported input format',
reload: jest.fn().mockResolvedValue(undefined)
};
}
static setupSuccessfulConversion() {
ConversionJob.create.mockResolvedValue(this.mockSuccessfulJob());
}
static setupFailedConversion() {
ConversionJob.create.mockResolvedValue(this.mockFailedJob());
}
}
Performance Optimization
Connection Pooling and Caching
javascript
import MediaConvert from '@mediaconvert/sdk';
import NodeCache from 'node-cache';
// Create cache for job status with 30 second TTL
const jobCache = new NodeCache({ stdTTL: 30 });
// Configure with connection pooling
MediaConvert.configure({
apiToken: process.env.MEDIACONVERT_API_TOKEN,
// Enable connection pooling
keepAlive: true,
maxSockets: 10,
// Custom HTTP agent for connection reuse
httpAgent: new require('http').Agent({
keepAlive: true,
maxSockets: 10
}),
httpsAgent: new require('https').Agent({
keepAlive: true,
maxSockets: 10
})
});
// Cached job status checking
async function getCachedJobStatus(jobId) {
const cacheKey = `job_status_${jobId}`;
let status = jobCache.get(cacheKey);
if (!status) {
const job = await ConversionJob.get(jobId);
status = {
id: job.id,
status: job.status,
progressPercentage: job.progressPercentage
};
jobCache.set(cacheKey, status);
}
return status;
}
// Efficient batch status checking
async function getBatchJobStatuses(jobIds) {
const uncachedJobIds = [];
const results = {};
// Check cache first
jobIds.forEach(jobId => {
const cached = jobCache.get(`job_status_${jobId}`);
if (cached) {
results[jobId] = cached;
} else {
uncachedJobIds.push(jobId);
}
});
// Fetch uncached jobs in parallel
if (uncachedJobIds.length > 0) {
const jobs = await Promise.all(
uncachedJobIds.map(jobId => ConversionJob.get(jobId))
);
jobs.forEach(job => {
const status = {
id: job.id,
status: job.status,
progressPercentage: job.progressPercentage
};
results[job.id] = status;
jobCache.set(`job_status_${job.id}`, status);
});
}
return results;
}
Next Steps
- Python SDK Guide - Server-side Python integration
- cURL Examples - Direct API usage
- Ruby SDK Guide - Ruby/Rails integration
- API Reference - Complete API documentation
Support
- NPM Package: npmjs.com/package/@mediaconvert/sdk
- GitHub Repository: github.com/mediaconvert-io/javascript-sdk
- Documentation: docs.mediaconvert.io/javascript
- Email Support: support@mediaconvert.io