Podcasts hosted on ATProto

Fix WAV MIME type and add upload format warnings

Changes:
- Normalize audio/vnd.wave to audio/wav for better browser compatibility
- Use totalSize for chunked episodes in RSS feed
- Add warning in upload modal about WAV files
- Recommend MP3/M4A formats for best compatibility

Note: WAV files are uncompressed and very large. MP3 is recommended for podcasts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+16 -5
client
src
components
src
routes
+3 -2
client/src/components/UploadModal.jsx
··· 66 66 required 67 67 /> 68 68 {file && ( 69 - <small style={{ display: 'block', marginTop: '0.5rem', color: file.size > 500 * 1024 * 1024 ? '#ef4444' : 'inherit' }}> 69 + <small style={{ display: 'block', marginTop: '0.5rem', color: file.size > 500 * 1024 * 1024 ? '#ef4444' : (file.name.toLowerCase().endsWith('.wav') ? '#f59e0b' : 'inherit') }}> 70 70 Selected: {file.name} ({formatFileSize(file.size)}) 71 71 {file.size > 500 * 1024 * 1024 && ' - File exceeds 500 MB limit'} 72 72 {file.size > 5 * 1024 * 1024 && file.size <= 500 * 1024 * 1024 && ' - Will be uploaded in chunks'} 73 + {file.name.toLowerCase().endsWith('.wav') && ' - WAV files are very large and may not play in all podcast players. Consider converting to MP3.'} 73 74 </small> 74 75 )} 75 - <small>Supported formats: MP3, M4A, WAV, etc. Large files will be automatically chunked (max 500 MB)</small> 76 + <small>Recommended: MP3 or M4A format. WAV files work but are very large. Max size: 500 MB</small> 76 77 </div> 77 78 78 79 <div className="form-group">
+13 -3
src/routes/feed.js
··· 56 56 const mediaUrl = `${baseUrl}/api/media/stream/${userDid}/${blobCid}`; 57 57 const atUri = episode.uri; // Full AT URI: at://did/app.podcast.episode/rkey 58 58 59 + // Normalize MIME type for better compatibility 60 + // WAV files should use audio/wav or audio/x-wav for better browser support 61 + let mimeType = episode.audio.mimeType; 62 + if (mimeType === 'audio/vnd.wave') { 63 + mimeType = 'audio/wav'; 64 + } 65 + 66 + // For chunked episodes, use the total size instead of single chunk size 67 + const totalSize = episode.audio.totalSize || episode.audio.size; 68 + 59 69 feed.item({ 60 70 title: episode.title, 61 71 description: episode.description, ··· 64 74 date: episode.pubDate, 65 75 enclosure: { 66 76 url: mediaUrl, // HTTP streaming URL for podcast players 67 - type: episode.audio.mimeType, 68 - size: episode.audio.size 77 + type: mimeType, 78 + size: totalSize 69 79 }, 70 80 custom_elements: [ 71 - { 'itunes:duration': Math.floor(episode.audio.size / 16000) }, // Rough estimate 81 + { 'itunes:duration': Math.floor(totalSize / 16000) }, // Rough estimate 72 82 { 'atproto:cid': blobCid }, 73 83 { 'atproto:uri': atUri }, 74 84 { 'atproto:did': userDid },