Fix music file serving path to match Docker mount point
This commit is contained in:
103
server/api/music/[filename].get.ts
Normal file
103
server/api/music/[filename].get.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { createReadStream } from 'fs'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
let filename = getRouterParam(event, 'filename')
|
||||
|
||||
if (!filename) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Filename is required'
|
||||
})
|
||||
}
|
||||
|
||||
// Log incoming request for debugging proxy issues
|
||||
const headers = getHeaders(event)
|
||||
const realIP = headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown'
|
||||
console.log(`[MUSIC API] Request from ${realIP} for file: ${filename}`)
|
||||
|
||||
// Decode the filename
|
||||
try {
|
||||
filename = decodeURIComponent(filename)
|
||||
console.log('Decoded filename:', filename)
|
||||
} catch (error) {
|
||||
console.error('Error decoding filename:', error)
|
||||
// If decoding fails, use original filename
|
||||
}
|
||||
|
||||
try {
|
||||
const musicDir = join(process.cwd(), 'public', 'music')
|
||||
const filePath = join(musicDir, filename)
|
||||
|
||||
// Security check: ensure the file is within the music directory
|
||||
if (!filePath.startsWith(musicDir)) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Access denied'
|
||||
})
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
} catch {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'File not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Get file stats
|
||||
const stats = await fs.stat(filePath)
|
||||
|
||||
// Set appropriate headers
|
||||
const headers = getHeaders(event)
|
||||
|
||||
// Handle range requests for audio streaming
|
||||
const range = headers.range
|
||||
|
||||
if (range) {
|
||||
const parts = range.replace(/bytes=/, "").split("-")
|
||||
const start = parseInt(parts[0], 10)
|
||||
const end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1
|
||||
const chunksize = (end - start) + 1
|
||||
|
||||
setHeader(event, 'Content-Range', `bytes ${start}-${end}/${stats.size}`)
|
||||
setHeader(event, 'Accept-Ranges', 'bytes')
|
||||
setHeader(event, 'Content-Length', chunksize.toString())
|
||||
setResponseStatus(event, 206)
|
||||
|
||||
return sendStream(event, createReadStream(filePath, { start, end }))
|
||||
} else {
|
||||
// Send entire file
|
||||
setHeader(event, 'Content-Length', stats.size.toString())
|
||||
setHeader(event, 'Accept-Ranges', 'bytes')
|
||||
|
||||
// Set content type based on file extension
|
||||
const ext = filename.toLowerCase().split('.').pop()
|
||||
const contentTypes = {
|
||||
'mp3': 'audio/mpeg',
|
||||
'wav': 'audio/wav',
|
||||
'flac': 'audio/flac',
|
||||
'm4a': 'audio/mp4',
|
||||
'ogg': 'audio/ogg',
|
||||
'aac': 'audio/aac'
|
||||
}
|
||||
|
||||
setHeader(event, 'Content-Type', contentTypes[ext] || 'audio/mpeg')
|
||||
|
||||
return sendStream(event, createReadStream(filePath))
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
console.error('Error serving music file:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to serve music file'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user