import base64 import os import re from odoo import http from odoo.http import request class VideoPlaybackController(http.Controller): @http.route('/yuthon_school/video/', type='http', auth='user', methods=['GET']) def serve_video(self, video_id, **kwargs): """提供视频流服务,支持 Range 请求以实现视频进度拖拽""" video = request.env['yuthon.school.video'].sudo().browse(video_id) if not video.exists() or not video.video_file: return request.not_found() video_data = self._decode_video(video.video_file) file_size = len(video_data) mime_map = { 'mp4': 'video/mp4', 'webm': 'video/webm', 'ogg': 'video/ogg', 'ogv': 'video/ogg', 'mov': 'video/quicktime', 'mkv': 'video/x-matroska', 'avi': 'video/x-msvideo', } mime_type = 'video/mp4' if video.video_filename: ext = os.path.splitext(video.video_filename)[1].lower().lstrip('.') mime_type = mime_map.get(ext, 'video/mp4') # 处理 Range 请求(视频进度条拖拽需要) range_header = request.httprequest.headers.get('Range') if range_header: range_match = re.search(r'bytes=(\d+)-(\d*)', range_header) if range_match: start = int(range_match.group(1)) end = int(range_match.group(2)) if range_match.group(2) else file_size - 1 if start >= file_size: headers = [('Content-Range', f'bytes */{file_size}')] return request.make_response(b'', headers, status=416) end = min(end, file_size - 1) chunk = video_data[start:end + 1] headers = [ ('Content-Type', mime_type), ('Content-Length', str(len(chunk))), ('Content-Range', f'bytes {start}-{end}/{file_size}'), ('Accept-Ranges', 'bytes'), ] return request.make_response(chunk, headers, status=206) # 返回完整视频内容(在线播放) headers = [ ('Content-Type', mime_type), ('Content-Length', str(file_size)), ('Accept-Ranges', 'bytes'), ] return request.make_response(video_data, headers) @staticmethod def _decode_video(raw_file): """解码 Odoo 18 Binary 字段数据""" if isinstance(raw_file, bytes): try: return base64.b64decode(raw_file, validate=True) except Exception: return raw_file elif isinstance(raw_file, str): return base64.b64decode(raw_file) return b''