feat: add audio extraction management interface and services
- Implemented ExtractionsPage component for managing audio extractions. - Added ExtractionsService for handling extraction API calls. - Created Playlist component for displaying audio tracks. - Introduced ScrollArea component for better UI scrolling experience. - Developed FilesService for file download and thumbnail management. - Added PlayerService for controlling audio playback and state. - Updated API services index to include new services.
This commit is contained in:
52
src/lib/api/services/extractions.ts
Normal file
52
src/lib/api/services/extractions.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export interface ExtractionInfo {
|
||||
id: number
|
||||
url: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||
title?: string
|
||||
service?: string
|
||||
service_id?: string
|
||||
sound_id?: number
|
||||
user_id: number
|
||||
error?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface CreateExtractionResponse {
|
||||
message: string
|
||||
extraction: ExtractionInfo
|
||||
}
|
||||
|
||||
export interface GetExtractionsResponse {
|
||||
extractions: ExtractionInfo[]
|
||||
}
|
||||
|
||||
export class ExtractionsService {
|
||||
/**
|
||||
* Create a new extraction job
|
||||
*/
|
||||
async createExtraction(url: string): Promise<CreateExtractionResponse> {
|
||||
const response = await apiClient.post<CreateExtractionResponse>(`/api/v1/extractions/?url=${encodeURIComponent(url)}`)
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extraction by ID
|
||||
*/
|
||||
async getExtraction(extractionId: number): Promise<ExtractionInfo> {
|
||||
const response = await apiClient.get<ExtractionInfo>(`/api/v1/extractions/${extractionId}`)
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's extractions
|
||||
*/
|
||||
async getUserExtractions(): Promise<ExtractionInfo[]> {
|
||||
const response = await apiClient.get<GetExtractionsResponse>('/api/v1/extractions/')
|
||||
return response.extractions
|
||||
}
|
||||
}
|
||||
|
||||
export const extractionsService = new ExtractionsService()
|
||||
86
src/lib/api/services/files.ts
Normal file
86
src/lib/api/services/files.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { apiClient } from '../client'
|
||||
import { API_CONFIG } from '../config'
|
||||
|
||||
export class FilesService {
|
||||
/**
|
||||
* Download a sound file
|
||||
*/
|
||||
async downloadSound(soundId: number): Promise<void> {
|
||||
try {
|
||||
// Use fetch directly to handle file download
|
||||
const response = await fetch(`${API_CONFIG.BASE_URL}/api/v1/files/sounds/${soundId}/download`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Download failed: ${response.statusText}`)
|
||||
}
|
||||
|
||||
// Get filename from Content-Disposition header or use default
|
||||
const contentDisposition = response.headers.get('Content-Disposition')
|
||||
let filename = `sound_${soundId}.mp3`
|
||||
|
||||
if (contentDisposition) {
|
||||
const filenameMatch = contentDisposition.match(/filename="(.+)"/)
|
||||
if (filenameMatch) {
|
||||
filename = filenameMatch[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Create blob and download
|
||||
const blob = await response.blob()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
|
||||
// Create temporary download link
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error('Failed to download sound:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thumbnail URL for a sound
|
||||
*/
|
||||
getThumbnailUrl(soundId: number): string {
|
||||
return `${API_CONFIG.BASE_URL}/api/v1/files/sounds/${soundId}/thumbnail`
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a sound has a thumbnail
|
||||
*/
|
||||
async hasThumbnail(soundId: number): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${API_CONFIG.BASE_URL}/api/v1/files/sounds/${soundId}/thumbnail`, {
|
||||
method: 'HEAD', // Only check headers, don't download
|
||||
credentials: 'include',
|
||||
})
|
||||
return response.ok
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a thumbnail image
|
||||
*/
|
||||
async preloadThumbnail(soundId: number): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
img.onload = () => resolve(true)
|
||||
img.onerror = () => resolve(false)
|
||||
img.src = this.getThumbnailUrl(soundId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const filesService = new FilesService()
|
||||
@@ -1,2 +1,5 @@
|
||||
export * from './auth'
|
||||
export * from './sounds'
|
||||
export * from './sounds'
|
||||
export * from './player'
|
||||
export * from './files'
|
||||
export * from './extractions'
|
||||
132
src/lib/api/services/player.ts
Normal file
132
src/lib/api/services/player.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export type PlayerStatus = 'playing' | 'paused' | 'stopped'
|
||||
export type PlayerMode = 'continuous' | 'loop' | 'loop_one' | 'random' | 'single'
|
||||
|
||||
export interface PlayerSound {
|
||||
id: number
|
||||
name: string
|
||||
filename: string
|
||||
duration: number
|
||||
size: number
|
||||
type: string
|
||||
thumbnail?: string
|
||||
play_count: number
|
||||
extract_url?: string
|
||||
}
|
||||
|
||||
export interface PlayerPlaylist {
|
||||
id: number
|
||||
name: string
|
||||
length: number
|
||||
duration: number
|
||||
sounds: PlayerSound[]
|
||||
}
|
||||
|
||||
export interface PlayerState {
|
||||
status: PlayerStatus
|
||||
mode: PlayerMode
|
||||
volume: number
|
||||
position: number
|
||||
duration?: number
|
||||
index?: number
|
||||
current_sound?: PlayerSound
|
||||
playlist?: PlayerPlaylist
|
||||
}
|
||||
|
||||
export interface PlayerSeekRequest {
|
||||
position: number
|
||||
}
|
||||
|
||||
export interface PlayerVolumeRequest {
|
||||
volume: number
|
||||
}
|
||||
|
||||
export interface PlayerModeRequest {
|
||||
mode: PlayerMode
|
||||
}
|
||||
|
||||
export interface MessageResponse {
|
||||
message: string
|
||||
}
|
||||
|
||||
export class PlayerService {
|
||||
/**
|
||||
* Play current sound
|
||||
*/
|
||||
async play(): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/play')
|
||||
}
|
||||
|
||||
/**
|
||||
* Play sound at specific index
|
||||
*/
|
||||
async playAtIndex(index: number): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>(`/api/v1/player/play/${index}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause playback
|
||||
*/
|
||||
async pause(): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/pause')
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playback
|
||||
*/
|
||||
async stop(): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/stop')
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip to next track
|
||||
*/
|
||||
async next(): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/next')
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous track
|
||||
*/
|
||||
async previous(): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/previous')
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to specific position
|
||||
*/
|
||||
async seek(position: number): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/seek', { position })
|
||||
}
|
||||
|
||||
/**
|
||||
* Set playback volume
|
||||
*/
|
||||
async setVolume(volume: number): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/volume', { volume })
|
||||
}
|
||||
|
||||
/**
|
||||
* Set playback mode
|
||||
*/
|
||||
async setMode(mode: PlayerMode): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/mode', { mode })
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload current playlist
|
||||
*/
|
||||
async reloadPlaylist(): Promise<MessageResponse> {
|
||||
return apiClient.post<MessageResponse>('/api/v1/player/reload-playlist')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current player state
|
||||
*/
|
||||
async getState(): Promise<PlayerState> {
|
||||
return apiClient.get<PlayerState>('/api/v1/player/state')
|
||||
}
|
||||
}
|
||||
|
||||
export const playerService = new PlayerService()
|
||||
Reference in New Issue
Block a user