Skip to content

Commit a86d286

Browse files
committed
feat: add support for HTTP range requests for audio files
1 parent 95be524 commit a86d286

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

apps/oss/serializers/file.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# coding=utf-8
2+
import re
23
import urllib
34

45
import uuid_utils.compat as uuid
@@ -54,6 +55,9 @@
5455
"csv": "text/csv", "tsv": "text/tab-separated-values", "ics": "text/calendar",
5556
}
5657

58+
# 如果是音频文件并且有range请求,处理部分内容
59+
audio_types = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'opus', 'm4a']
60+
5761

5862
class FileSerializer(serializers.Serializer):
5963
file = UploadedFileField(required=True, label=_('file'))
@@ -85,6 +89,10 @@ def upload(self, with_valid=True):
8589

8690
class Operate(serializers.Serializer):
8791
id = serializers.UUIDField(required=True)
92+
http_range = serializers.CharField(
93+
required=False, allow_blank=True, allow_null=True, label=_('HTTP Range'),
94+
help_text=_('HTTP Range header for partial content requests, e.g., "bytes=0-1023"')
95+
)
8896

8997
def get(self, with_valid=True):
9098
if with_valid:
@@ -96,16 +104,53 @@ def get(self, with_valid=True):
96104
file_type = file.file_name.split(".")[-1].lower()
97105
content_type = mime_types.get(file_type, 'application/octet-stream')
98106
encoded_filename = urllib.parse.quote(file.file_name)
107+
# 获取文件内容
108+
file_bytes = file.get_bytes()
109+
file_size = len(file_bytes)
110+
111+
response = None
112+
if file_type in audio_types and self.data.get('http_range'):
113+
response = self.handle_audio(file_size, file_bytes, content_type, encoded_filename)
114+
if response:
115+
return response
116+
117+
# 对于非范围请求或其他类型文件,返回完整内容
99118
headers = {
100119
'Content-Type': content_type,
101120
'Content-Disposition': f'{"inline" if file_type == "pdf" else "attachment"}; filename={encoded_filename}'
102121
}
103122
return HttpResponse(
104-
file.get_bytes(),
123+
file_bytes,
105124
status=200,
106125
headers=headers
107126
)
108127

128+
def handle_audio(self, file_size, file_bytes, content_type, encoded_filename):
129+
130+
# 解析range请求 (格式如 "bytes=0-1023")
131+
range_match = re.match(r'bytes=(\d+)-(\d*)', self.data.get('http_range', ''))
132+
if range_match:
133+
start = int(range_match.group(1))
134+
end = int(range_match.group(2)) if range_match.group(2) else file_size - 1
135+
136+
# 确保范围合法
137+
end = min(end, file_size - 1)
138+
length = end - start + 1
139+
140+
# 创建部分响应
141+
response = HttpResponse(
142+
file_bytes[start:start + length],
143+
status=206,
144+
content_type=content_type
145+
)
146+
147+
# 设置部分内容响应头
148+
response['Content-Range'] = f'bytes {start}-{end}/{file_size}'
149+
response['Accept-Ranges'] = 'bytes'
150+
response['Content-Length'] = str(length)
151+
response['Content-Disposition'] = f'inline; filename={encoded_filename}'
152+
return response
153+
109154
def delete(self):
110155
self.is_valid(raise_exception=True)
111156
file_id = self.data.get('id')

apps/oss/views/file.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ class FileRetrievalView(APIView):
2323
tags=[_('File')] # type: ignore
2424
)
2525
def get(self, request: Request, file_id: str):
26-
return FileSerializer.Operate(data={'id': file_id}).get()
26+
return FileSerializer.Operate(data={
27+
'id': file_id,
28+
'http_range': request.headers.get('Range', ''),
29+
}).get()
2730

2831

2932
class FileView(APIView):

0 commit comments

Comments
 (0)