fix: 继续优化延迟问题
This commit is contained in:
parent
6e2d674758
commit
2a1fa9f8cf
@ -143,39 +143,48 @@ def main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Execption {e}")
|
print(f"Execption {e}")
|
||||||
|
|
||||||
def run_server():
|
def run_server(port):
|
||||||
xiaomusic = XiaoMusic(config)
|
xiaomusic = XiaoMusic(config)
|
||||||
HttpInit(xiaomusic)
|
HttpInit(xiaomusic)
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
HttpApp,
|
HttpApp,
|
||||||
host="127.0.0.1",
|
host=["0.0.0.0", "::"],
|
||||||
port=config.port + 1,
|
port=port,
|
||||||
log_config=LOGGING_CONFIG,
|
log_config=LOGGING_CONFIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
command = [
|
process = None
|
||||||
"uvicorn",
|
|
||||||
"xiaomusic.gate:app",
|
def run_gate():
|
||||||
"--workers",
|
command = [
|
||||||
"4",
|
"uvicorn",
|
||||||
"--host",
|
"xiaomusic.gate:app",
|
||||||
"0.0.0.0",
|
"--workers",
|
||||||
"--port",
|
"4",
|
||||||
str(config.port),
|
"--host",
|
||||||
]
|
"0.0.0.0",
|
||||||
|
"--port",
|
||||||
|
str(config.port),
|
||||||
|
]
|
||||||
|
global process
|
||||||
|
process = subprocess.Popen(command)
|
||||||
|
|
||||||
process = subprocess.Popen(command)
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
print("主进程收到退出信号,准备退出...")
|
print("主进程收到退出信号,准备退出...")
|
||||||
process.terminate() # 终止子进程
|
if process is not None:
|
||||||
process.wait() # 等待子进程退出
|
process.terminate() # 终止子进程
|
||||||
print("子进程已退出")
|
process.wait() # 等待子进程退出
|
||||||
|
print("子进程已退出")
|
||||||
os._exit(0) # 退出主进程
|
os._exit(0) # 退出主进程
|
||||||
|
|
||||||
# 捕获主进程的退出信号
|
# 捕获主进程的退出信号
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
run_server()
|
if config.enable_gate:
|
||||||
|
run_gate()
|
||||||
|
run_server(config.port + 1)
|
||||||
|
else:
|
||||||
|
run_server(config.port)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -137,6 +137,7 @@ class Config:
|
|||||||
os.getenv("XIAOMUSIC_REMOVE_ID3TAG", "false").lower() == "true"
|
os.getenv("XIAOMUSIC_REMOVE_ID3TAG", "false").lower() == "true"
|
||||||
)
|
)
|
||||||
delay_sec: int = int(os.getenv("XIAOMUSIC_DELAY_SEC", 3)) # 下一首歌延迟播放秒数
|
delay_sec: int = int(os.getenv("XIAOMUSIC_DELAY_SEC", 3)) # 下一首歌延迟播放秒数
|
||||||
|
enable_gate: bool = os.getenv("XIAOMUSIC_ENABLE_GATE", "true").lower() == "true"
|
||||||
|
|
||||||
def append_keyword(self, keys, action):
|
def append_keyword(self, keys, action):
|
||||||
for key in keys.split(","):
|
for key in keys.split(","):
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response, StreamingResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from starlette.background import BackgroundTask
|
|
||||||
|
|
||||||
from xiaomusic import __version__
|
from xiaomusic import __version__
|
||||||
from xiaomusic.config import Config
|
from xiaomusic.config import Config
|
||||||
@ -36,19 +38,69 @@ app = FastAPI(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def reset_gate():
|
folder = os.path.dirname(__file__)
|
||||||
# 更新 music 链接
|
app.mount("/static", StaticFiles(directory=f"{folder}/static"), name="static")
|
||||||
app.router.routes = [route for route in app.router.routes if route.path != "/music"]
|
|
||||||
app.mount(
|
|
||||||
"/music",
|
async def file_iterator(file_path, start, end):
|
||||||
StaticFiles(directory=config.music_path, follow_symlink=True),
|
async with aiofiles.open(file_path, mode="rb") as file:
|
||||||
name="music",
|
await file.seek(start)
|
||||||
|
chunk_size = 1024
|
||||||
|
while start <= end:
|
||||||
|
read_size = min(chunk_size, end - start + 1)
|
||||||
|
data = await file.read(read_size)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
start += len(data)
|
||||||
|
yield data
|
||||||
|
|
||||||
|
|
||||||
|
range_pattern = re.compile(r"bytes=(\d+)-(\d*)")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/music/{file_path:path}")
|
||||||
|
async def music_file(request: Request, file_path: str):
|
||||||
|
absolute_path = os.path.abspath(config.music_path)
|
||||||
|
absolute_file_path = os.path.join(absolute_path, file_path)
|
||||||
|
if not os.path.exists(absolute_file_path):
|
||||||
|
raise HTTPException(status_code=404, detail="File not found")
|
||||||
|
|
||||||
|
file_size = os.path.getsize(absolute_file_path)
|
||||||
|
range_start, range_end = 0, file_size - 1
|
||||||
|
|
||||||
|
range_header = request.headers.get("Range")
|
||||||
|
log.info(f"music_file range_header {range_header}")
|
||||||
|
if range_header:
|
||||||
|
range_match = range_pattern.match(range_header)
|
||||||
|
if range_match:
|
||||||
|
range_start = int(range_match.group(1))
|
||||||
|
if range_match.group(2):
|
||||||
|
range_end = int(range_match.group(2))
|
||||||
|
|
||||||
|
log.info(f"music_file in range {absolute_file_path}")
|
||||||
|
|
||||||
|
log.info(f"music_file {range_start} {range_end} {absolute_file_path}")
|
||||||
|
headers = {
|
||||||
|
"Content-Range": f"bytes {range_start}-{range_end}/{file_size}",
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
}
|
||||||
|
mime_type, _ = mimetypes.guess_type(file_path)
|
||||||
|
if mime_type is None:
|
||||||
|
mime_type = "application/octet-stream"
|
||||||
|
return StreamingResponse(
|
||||||
|
file_iterator(absolute_file_path, range_start, range_end),
|
||||||
|
headers=headers,
|
||||||
|
status_code=206 if range_header else 200,
|
||||||
|
media_type=mime_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
folder = os.path.dirname(__file__)
|
@app.options("/music/{file_path:path}")
|
||||||
app.mount("/static", StaticFiles(directory=f"{folder}/static"), name="static")
|
async def music_options():
|
||||||
reset_gate()
|
headers = {
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
}
|
||||||
|
return Response(headers=headers)
|
||||||
|
|
||||||
|
|
||||||
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
|
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
|
||||||
@ -63,15 +115,6 @@ async def proxy(path: str, request: Request):
|
|||||||
params=request.query_params,
|
params=request.query_params,
|
||||||
content=await request.body() if request.method in ["POST", "PUT"] else None,
|
content=await request.body() if request.method in ["POST", "PUT"] else None,
|
||||||
)
|
)
|
||||||
if path == "savesetting":
|
|
||||||
# 使用BackgroundTask在响应发送完毕后执行逻辑
|
|
||||||
background_task = BackgroundTask(reset_gate)
|
|
||||||
return Response(
|
|
||||||
content=response.content,
|
|
||||||
status_code=response.status_code,
|
|
||||||
headers=dict(response.headers),
|
|
||||||
background=background_task,
|
|
||||||
)
|
|
||||||
return Response(
|
return Response(
|
||||||
content=response.content,
|
content=response.content,
|
||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -8,12 +10,14 @@ from contextlib import asynccontextmanager
|
|||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Request, status
|
from fastapi import Depends, FastAPI, HTTPException, Request, status
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from starlette.background import BackgroundTask
|
from starlette.background import BackgroundTask
|
||||||
from starlette.responses import FileResponse
|
from starlette.responses import FileResponse, Response
|
||||||
|
|
||||||
from xiaomusic import __version__
|
from xiaomusic import __version__
|
||||||
from xiaomusic.utils import (
|
from xiaomusic.utils import (
|
||||||
@ -79,14 +83,6 @@ def reset_http_server():
|
|||||||
else:
|
else:
|
||||||
app.dependency_overrides = {}
|
app.dependency_overrides = {}
|
||||||
|
|
||||||
# 更新 music 链接
|
|
||||||
app.router.routes = [route for route in app.router.routes if route.path != "/music"]
|
|
||||||
app.mount(
|
|
||||||
"/music",
|
|
||||||
StaticFiles(directory=config.music_path, follow_symlink=True),
|
|
||||||
name="music",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def HttpInit(_xiaomusic):
|
def HttpInit(_xiaomusic):
|
||||||
global xiaomusic, config, log
|
global xiaomusic, config, log
|
||||||
@ -170,9 +166,12 @@ async def do_cmd(data: DidCmd):
|
|||||||
return {"ret": "Did not exist"}
|
return {"ret": "Did not exist"}
|
||||||
|
|
||||||
if len(cmd) > 0:
|
if len(cmd) > 0:
|
||||||
await xiaomusic.cancel_all_tasks()
|
try:
|
||||||
task = asyncio.create_task(xiaomusic.do_check_cmd(did=did, query=cmd))
|
await xiaomusic.cancel_all_tasks()
|
||||||
xiaomusic.append_running_task(task)
|
task = asyncio.create_task(xiaomusic.do_check_cmd(did=did, query=cmd))
|
||||||
|
xiaomusic.append_running_task(task)
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"Execption {e}")
|
||||||
return {"ret": "OK"}
|
return {"ret": "OK"}
|
||||||
return {"ret": "Unknow cmd"}
|
return {"ret": "Unknow cmd"}
|
||||||
|
|
||||||
@ -295,3 +294,64 @@ async def debug_play_by_music_url(request: Request):
|
|||||||
return await xiaomusic.debug_play_by_music_url(arg1=data_dict)
|
return await xiaomusic.debug_play_by_music_url(arg1=data_dict)
|
||||||
except json.JSONDecodeError as err:
|
except json.JSONDecodeError as err:
|
||||||
raise HTTPException(status_code=400, detail="Invalid JSON") from err
|
raise HTTPException(status_code=400, detail="Invalid JSON") from err
|
||||||
|
|
||||||
|
|
||||||
|
async def file_iterator(file_path, start, end):
|
||||||
|
async with aiofiles.open(file_path, mode="rb") as file:
|
||||||
|
await file.seek(start)
|
||||||
|
chunk_size = 1024
|
||||||
|
while start <= end:
|
||||||
|
read_size = min(chunk_size, end - start + 1)
|
||||||
|
data = await file.read(read_size)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
start += len(data)
|
||||||
|
yield data
|
||||||
|
|
||||||
|
|
||||||
|
range_pattern = re.compile(r"bytes=(\d+)-(\d*)")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/music/{file_path:path}")
|
||||||
|
async def music_file(request: Request, file_path: str):
|
||||||
|
absolute_path = os.path.abspath(config.music_path)
|
||||||
|
absolute_file_path = os.path.join(absolute_path, file_path)
|
||||||
|
if not os.path.exists(absolute_file_path):
|
||||||
|
raise HTTPException(status_code=404, detail="File not found")
|
||||||
|
|
||||||
|
file_size = os.path.getsize(absolute_file_path)
|
||||||
|
range_start, range_end = 0, file_size - 1
|
||||||
|
|
||||||
|
range_header = request.headers.get("Range")
|
||||||
|
log.info(f"music_file range_header {range_header}")
|
||||||
|
if range_header:
|
||||||
|
range_match = range_pattern.match(range_header)
|
||||||
|
if range_match:
|
||||||
|
range_start = int(range_match.group(1))
|
||||||
|
if range_match.group(2):
|
||||||
|
range_end = int(range_match.group(2))
|
||||||
|
|
||||||
|
log.info(f"music_file in range {absolute_file_path}")
|
||||||
|
|
||||||
|
log.info(f"music_file {range_start} {range_end} {absolute_file_path}")
|
||||||
|
headers = {
|
||||||
|
"Content-Range": f"bytes {range_start}-{range_end}/{file_size}",
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
}
|
||||||
|
mime_type, _ = mimetypes.guess_type(file_path)
|
||||||
|
if mime_type is None:
|
||||||
|
mime_type = "application/octet-stream"
|
||||||
|
return StreamingResponse(
|
||||||
|
file_iterator(absolute_file_path, range_start, range_end),
|
||||||
|
headers=headers,
|
||||||
|
status_code=206 if range_header else 200,
|
||||||
|
media_type=mime_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.options("/music/{file_path:path}")
|
||||||
|
async def music_options():
|
||||||
|
headers = {
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
}
|
||||||
|
return Response(headers=headers)
|
||||||
|
@ -932,7 +932,7 @@ class XiaoMusicDevice:
|
|||||||
self.log.info(f"播放 {name} 失败")
|
self.log.info(f"播放 {name} 失败")
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
if self.isplaying() and self._last_cmd != "stop":
|
if self.isplaying() and self._last_cmd != "stop":
|
||||||
await self._play_next()
|
await self._play_next()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log.info(f"【{name}】已经开始播放了")
|
self.log.info(f"【{name}】已经开始播放了")
|
||||||
|
Loading…
Reference in New Issue
Block a user