soundwave/docs/AUDIO_SEEKING_FIX.md

223 lines
7.3 KiB
Markdown
Raw Normal View History

# Audio Seeking Fix - HTTP Range Request Support
## Issue
When users attempted to seek through playing audio files (especially YouTube downloads), the progress bar would reset to the start. This issue only affected downloaded files; local files uploaded by users worked correctly.
## Root Cause
The backend was using Django's default `serve` view to deliver media files, which does not support HTTP Range requests. When a browser seeks in an audio/video file, it sends a Range header requesting specific byte ranges. Without proper Range support:
1. Browser requests bytes at a specific position (e.g., "Range: bytes=1000000-")
2. Server returns entire file with 200 OK instead of partial content with 206 Partial Content
3. Browser receives data from the beginning, causing the player to restart
## Solution
Implemented a custom media streaming view (`serve_media_with_range`) with full HTTP Range request support:
### Key Features
#### 1. HTTP Range Request Support
- **206 Partial Content**: Returns only requested byte ranges
- **Accept-Ranges header**: Advertises range support to browsers
- **Content-Range header**: Specifies byte range being returned
- **416 Range Not Satisfiable**: Properly handles invalid range requests
#### 2. Security Enhancements
- **Path Traversal Prevention**: Blocks `..`, absolute paths, and backslashes
- **Symlink Attack Prevention**: Verifies resolved paths stay within document root
- **Directory Listing Prevention**: Only serves files, not directories
- **Authentication Integration**: Works with Django's authentication middleware
- **Security Logging**: Logs suspicious access attempts
#### 3. Performance Optimizations
- **Streaming Iterator**: Processes files in 8KB chunks to avoid memory issues
- **Cache Headers**: Sets appropriate caching (1 hour) for better performance
- **Last-Modified Headers**: Enables conditional requests
#### 4. Content Type Detection
Automatically detects and sets proper MIME types for audio formats:
- `.mp3``audio/mpeg`
- `.m4a``audio/mp4`
- `.webm``video/webm`
- `.ogg``audio/ogg`
- `.wav``audio/wav`
- `.flac``audio/flac`
- `.aac``audio/aac`
- `.opus``audio/opus`
## Files Modified
### Backend Changes
#### 1. `/backend/common/streaming.py` (NEW)
Custom streaming view with Range request support. This is the core fix that enables seeking.
**Key Functions:**
- `range_file_iterator()`: Efficiently streams file chunks with offset support
- `serve_media_with_range()`: Main view handling Range requests and security
#### 2. `/backend/config/urls.py`
Updated media URL pattern to use the new streaming view:
```python
# Before
re_path(r'^media/(?P<path>.*)$', serve, {...})
# After
re_path(r'^media/(?P<path>.*)$', serve_media_with_range, {...})
```
### Security Analysis
#### Path Security
**Directory Traversal**: Blocked by checking for `..`, `/`, and `\\`
**Symlink Attacks**: Prevented by verifying resolved path stays in document_root
**Directory Listing**: Only files are served, directories return 404
#### Authentication & Authorization
**User Authentication**: Handled by Django middleware before view
**User Isolation**: Audio models have `owner` field with proper filtering
**Admin Access**: Admins can access all files through middleware
#### Content Security
**Content-Type**: Proper MIME types prevent content sniffing attacks
**Inline Disposition**: Files play inline rather than forcing download
**File Validation**: Verifies file exists and is readable
#### Audit Trail
**Security Logging**: Suspicious access attempts are logged
**Debug Logging**: File not found errors are logged for troubleshooting
## Testing Checklist
### Functional Testing
- [x] ✅ Seeking works in YouTube downloaded files
- [x] ✅ Seeking works in user-uploaded local files
- [x] ✅ Full file playback works (non-Range requests)
- [x] ✅ PWA mobile playback with seeking
- [x] ✅ Desktop browser playback with seeking
### Security Testing
- [x] ✅ Directory traversal attempts blocked (`../../../etc/passwd`)
- [x] ✅ Absolute path attempts blocked (`/etc/passwd`)
- [x] ✅ Symlink attacks prevented (resolved path verification)
- [x] ✅ Unauthenticated access blocked (middleware)
- [x] ✅ User isolation maintained (can't access other users' files)
### Performance Testing
- [x] ✅ Large file streaming (no memory issues)
- [x] ✅ Multiple simultaneous streams
- [x] ✅ Cache headers work correctly
- [x] ✅ Chunk-based delivery efficient
### Browser Compatibility
- [x] ✅ Chrome/Edge (Chromium)
- [x] ✅ Firefox
- [x] ✅ Safari (iOS/macOS)
- [x] ✅ Mobile browsers (PWA)
## HTTP Range Request Examples
### Full File Request (No Range)
```
GET /media/audio/example.mp3
→ 200 OK
Content-Length: 5000000
Content-Type: audio/mpeg
Accept-Ranges: bytes
```
### Seek to Middle (Range Request)
```
GET /media/audio/example.mp3
Range: bytes=2500000-
→ 206 Partial Content
Content-Length: 2500000
Content-Range: bytes 2500000-4999999/5000000
Content-Type: audio/mpeg
Accept-Ranges: bytes
```
### Specific Range Request
```
GET /media/audio/example.mp3
Range: bytes=1000000-2000000
→ 206 Partial Content
Content-Length: 1000001
Content-Range: bytes 1000000-2000000/5000000
Content-Type: audio/mpeg
```
### Invalid Range Request
```
GET /media/audio/example.mp3
Range: bytes=9999999-
→ 416 Range Not Satisfiable
Content-Range: bytes */5000000
```
## User Impact
### Before Fix
❌ Seeking would restart playback from beginning
❌ Poor user experience with downloaded files
❌ PWA mobile seeking broken
❌ Users had to reload entire file to seek
### After Fix
✅ Smooth seeking to any position
✅ Instant response to seek operations
✅ Works consistently for all file types
✅ Better mobile/PWA experience
✅ Reduced bandwidth usage (only requested ranges transferred)
## Deployment Notes
### Container Restart Required
The fix requires restarting the Django application to load the new module:
```bash
docker compose restart soundwave
```
### No Database Migrations
No database changes are required - this is a pure code update.
### No Configuration Changes
Default settings work for all users. No environment variables or settings updates needed.
### Backwards Compatible
- Existing files continue to work
- Non-Range requests still supported
- No breaking changes to API
## Future Enhancements
### Potential Improvements
1. **Rate Limiting**: Add per-user bandwidth throttling
2. **Analytics**: Track seeking patterns for insights
3. **CDN Integration**: Add support for CDN/proxy caching
4. **Compression**: Consider gzip/brotli for text-based formats
5. **Adaptive Streaming**: HLS/DASH support for better quality adaptation
### Monitoring
Consider adding metrics for:
- Range request success rate
- Average seek time
- Bandwidth usage by file type
- Failed seek attempts
## References
- [HTTP Range Requests (RFC 7233)](https://tools.ietf.org/html/rfc7233)
- [Django File Serving Best Practices](https://docs.djangoproject.com/en/stable/howto/static-files/deployment/)
- [HTML5 Audio/Video Seeking](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeking)
## Date
December 16, 2025
## Status
**IMPLEMENTED AND DEPLOYED**
---
**Note**: This fix ensures all users (admin and managed users) can seek through audio files without issues. The implementation maintains security, performance, and compatibility while providing a significantly improved user experience.