feat: provide on-demand H264 compatible browser previews

This commit is contained in:
Codex
2026-10-03 16:49:48 +08:00
parent a887a76095
commit c8e9901b90
8 changed files with 91 additions and 13 deletions
+13 -4
View File
@@ -14,9 +14,11 @@ import os
from pathlib import Path
import re
import secrets
import shlex
import shutil
import socket
import sqlite3
import sys
import threading
import time
import urllib.error
@@ -266,6 +268,12 @@ def write_media_config():
'record': bool(c['enabled'] and c['record'] and quality == 'main' and not STORAGE['paused']),
'recordDeleteAfter': str(policy['retentionDays'] * 24) + 'h',
}
paths[stream_name(c, 'compat')] = {
'source': 'publisher', 'record': False,
'runOnDemand': shlex.join([sys.executable, str(ROOT / 'preview.py'), str(DATA)]) if c['enabled'] else '',
'runOnDemandRestart': True,
'runOnDemandStartTimeout': '45s', 'runOnDemandCloseAfter': '15s',
}
config = {
'logLevel': 'warn', 'logDestinations': ['stdout'],
'api': True, 'apiAddress': '127.0.0.1:' + str(API_PORT),
@@ -278,7 +286,8 @@ def write_media_config():
'authMethod': 'internal',
'authInternalUsers': [{'user': 'vision', 'pass': setting('mediaSecret'),
'ips': ['127.0.0.1', '::1'],
'permissions': [{'action': a} for a in ('read', 'api', 'playback')]}],
'permissions': [{'action': a} for a in ('read', 'api', 'playback')]
+ [{'action': 'publish', 'path': '~^cam_[a-f0-9]{16}_compat$'}]}],
'pathDefaults': {'recordFormat': 'fmp4', 'recordPath': str(RECORDINGS / '%path' / '%Y-%m-%d_%H-%M-%S-%f'),
'recordSegmentDuration': '5m', 'recordPartDuration': '1s'},
'paths': paths,
@@ -585,7 +594,7 @@ class Handler(BaseHTTPRequestHandler):
self.answer({'error': '操作未完成,请检查输入或服务状态'}, 500)
def proxy(self, port, path):
conn = http.client.HTTPConnection('127.0.0.1', port, timeout=25)
conn = http.client.HTTPConnection('127.0.0.1', port, timeout=55)
headers = {'Authorization': MEDIA_AUTH}
if self.headers.get('Range'):
headers['Range'] = self.headers['Range']
@@ -601,7 +610,7 @@ class Handler(BaseHTTPRequestHandler):
# browser must keep that session query on subsequent playlists.
target = url.urlsplit(url.urljoin(path, r.getheader('Location', '')))
if (port != HLS_PORT or target.scheme or target.netloc
or not re.fullmatch(r'/cam_[a-f0-9]{16}_(main|sub)/[a-zA-Z0-9_.-]+', target.path)):
or not re.fullmatch(r'/cam_[a-f0-9]{16}_(main|sub|compat)/[a-zA-Z0-9_.-]+', target.path)):
raise Problem('视频服务返回了无效的播放跳转', 502)
location = '/media/live' + target.path + ('?' + target.query if target.query else '')
self.send_response(r.status)
@@ -657,7 +666,7 @@ class Handler(BaseHTTPRequestHandler):
self.answer({'items': items})
elif path.startswith('/media/live/'):
suffix = path[len('/media/live/'):]
if not re.fullmatch(r'cam_[a-f0-9]{16}_(main|sub)/[a-zA-Z0-9_.-]+', suffix):
if not re.fullmatch(r'cam_[a-f0-9]{16}_(main|sub|compat)/[a-zA-Z0-9_.-]+', suffix):
raise Problem('视频路径不正确')
self.proxy(HLS_PORT, '/' + suffix + ('?' + parsed.query if parsed.query else ''))
elif path == '/media/playback':