348 lines
7.8 KiB
JavaScript
348 lines
7.8 KiB
JavaScript
// Wild Dragon MAM - API Helper Module
|
|
|
|
const API_BASE = '/api/v1';
|
|
const CAPTURE_BASE = '/capture';
|
|
|
|
/**
|
|
* Wrapper around fetch with JSON parsing and error handling
|
|
*/
|
|
async function api(path, options = {}) {
|
|
const url = `${API_BASE}${path}`;
|
|
const config = {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers,
|
|
},
|
|
...options,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
throw new Error(error.message || `API Error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { success: true, data };
|
|
} catch (error) {
|
|
console.error(`API Error on ${path}:`, error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper around fetch for capture endpoints
|
|
*/
|
|
async function captureApi(path, options = {}) {
|
|
const url = `${CAPTURE_BASE}${path}`;
|
|
const config = {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers,
|
|
},
|
|
...options,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
throw new Error(error.message || `Capture API Error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { success: true, data };
|
|
} catch (error) {
|
|
console.error(`Capture API Error on ${path}:`, error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// ASSET API CALLS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get all assets with optional filtering
|
|
*/
|
|
async function getAssets(filters = {}) {
|
|
const params = new URLSearchParams(filters);
|
|
const path = `/assets${params.toString() ? '?' + params.toString() : ''}`;
|
|
return api(path);
|
|
}
|
|
|
|
/**
|
|
* Get a single asset by ID
|
|
*/
|
|
async function getAsset(assetId) {
|
|
return api(`/assets/${assetId}`);
|
|
}
|
|
|
|
/**
|
|
* Get signed streaming URL for an asset
|
|
*/
|
|
async function getAssetStreamUrl(assetId) {
|
|
return api(`/assets/${assetId}/stream`);
|
|
}
|
|
|
|
/**
|
|
* Update asset metadata (tags, notes, etc)
|
|
*/
|
|
async function updateAsset(assetId, data) {
|
|
return api(`/assets/${assetId}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Search assets by query
|
|
*/
|
|
async function searchAssets(query) {
|
|
return api(`/assets/search?q=${encodeURIComponent(query)}`);
|
|
}
|
|
|
|
/**
|
|
* Get asset status and metadata
|
|
*/
|
|
async function getAssetMetadata(assetId) {
|
|
return api(`/assets/${assetId}/metadata`);
|
|
}
|
|
|
|
// ============================================================
|
|
// PROJECT API CALLS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get all projects
|
|
*/
|
|
async function getProjects() {
|
|
return api('/projects');
|
|
}
|
|
|
|
/**
|
|
* Get a single project by ID
|
|
*/
|
|
async function getProject(projectId) {
|
|
return api(`/projects/${projectId}`);
|
|
}
|
|
|
|
/**
|
|
* Create a new project
|
|
*/
|
|
async function createProject(name, description = '') {
|
|
return api('/projects', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ name, description }),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update project metadata
|
|
*/
|
|
async function updateProject(projectId, data) {
|
|
return api(`/projects/${projectId}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a project
|
|
*/
|
|
async function deleteProject(projectId) {
|
|
return api(`/projects/${projectId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// BIN API CALLS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get all bins for a project
|
|
*/
|
|
async function getBins(projectId) {
|
|
return api(`/projects/${projectId}/bins`);
|
|
}
|
|
|
|
/**
|
|
* Get a single bin by ID
|
|
*/
|
|
async function getBin(projectId, binId) {
|
|
return api(`/projects/${projectId}/bins/${binId}`);
|
|
}
|
|
|
|
/**
|
|
* Create a new bin in a project
|
|
*/
|
|
async function createBin(projectId, name, description = '') {
|
|
return api(`/projects/${projectId}/bins`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ name, description }),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update bin metadata
|
|
*/
|
|
async function updateBin(projectId, binId, data) {
|
|
return api(`/projects/${projectId}/bins/${binId}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a bin
|
|
*/
|
|
async function deleteBin(projectId, binId) {
|
|
return api(`/projects/${projectId}/bins/${binId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// CAPTURE API CALLS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get list of available capture devices
|
|
*/
|
|
async function getCaptureDevices() {
|
|
return captureApi('/devices');
|
|
}
|
|
|
|
/**
|
|
* Get capture status
|
|
*/
|
|
async function getCaptureStatus() {
|
|
return captureApi('/status');
|
|
}
|
|
|
|
/**
|
|
* Get current recording state
|
|
*/
|
|
async function getRecordingStatus() {
|
|
return captureApi('/recording/status');
|
|
}
|
|
|
|
/**
|
|
* Start recording
|
|
*/
|
|
async function startRecording(deviceId, projectId, binId, clipName) {
|
|
return captureApi('/recording/start', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
device_id: deviceId,
|
|
project_id: projectId,
|
|
bin_id: binId,
|
|
clip_name: clipName,
|
|
}),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stop recording
|
|
*/
|
|
async function stopRecording() {
|
|
return captureApi('/recording/stop', {
|
|
method: 'POST',
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get recent capture sessions
|
|
*/
|
|
async function getRecentCaptures(limit = 10) {
|
|
return captureApi(`/sessions?limit=${limit}`);
|
|
}
|
|
|
|
/**
|
|
* Get timecode for current recording
|
|
*/
|
|
async function getRecordingTimecode() {
|
|
return captureApi('/recording/timecode');
|
|
}
|
|
|
|
// ============================================================
|
|
// UTILITY FUNCTIONS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Format bytes to human-readable size
|
|
*/
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
/**
|
|
* Format duration in seconds to HH:MM:SS
|
|
*/
|
|
function formatDuration(seconds) {
|
|
if (!seconds || seconds < 0) return '00:00:00';
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
return [hours, minutes, secs]
|
|
.map(v => v.toString().padStart(2, '0'))
|
|
.join(':');
|
|
}
|
|
|
|
/**
|
|
* Get status badge text from status code
|
|
*/
|
|
function getStatusLabel(status) {
|
|
const labels = {
|
|
ingesting: 'Ingesting',
|
|
processing: 'Processing',
|
|
ready: 'Ready',
|
|
error: 'Error',
|
|
archived: 'Archived',
|
|
};
|
|
return labels[status] || status;
|
|
}
|
|
|
|
/**
|
|
* Get status badge CSS class from status code
|
|
*/
|
|
function getStatusBadgeClass(status) {
|
|
return `badge-${status}`;
|
|
}
|
|
|
|
/**
|
|
* Debounce function for search
|
|
*/
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Throttle function for scroll/resize events
|
|
*/
|
|
function throttle(func, limit) {
|
|
let inThrottle;
|
|
return function(...args) {
|
|
if (!inThrottle) {
|
|
func.apply(this, args);
|
|
inThrottle = true;
|
|
setTimeout(() => inThrottle = false, limit);
|
|
}
|
|
};
|
|
}
|