需求描述
我在树莓派4b上开发一个工具,需要提供http接口。
1. 添加播放列表并播放。
接口接受参数如下:
{"mode":"local","path":["/home/nicolas/Music/demo1.mp3","/home/nicolas/Music/demo2.mp3"],"action":"tolist"}
表示播放本地的音频文件,并将本地的2个文件添加到到播放列表,顺序播放,播放完成后停止。或者接受以下参数:
{"mode":"remote","path":["http://minio.file.thewebpath.com/share/music/demo.mp3"],"action":"play"}
表示播放远程音频文件(可以使用缓存的形式存储到本地)然后加入到播放列表,并直接开始播放。
2. 查询当前播放列表,返回的内容类似以下:
[{"mode":"local","path":"filepath.mp3","sortting":1},{"mode":"remote","path":"filepath2.mp3","sortting":2}]
3. 默认情况下"action":"play",也就是说如果我并没有传递action键时,默认的操作就是将我指定的文件添加到到播放列表,顺序播放,播放完成后停止。
4. 当我传递"action":"tolist"时,并不影响当前的播放情况,直接将我传递的文件加入到播放列表的末尾。
5. 当我传递"action":"pause"时,忽略其他参数,只将播放暂停。
6。 当我传递"action":"stop"时,播放停止,并清空播放队列,清空临时缓存的文件。
用python编写实现,播放音频可以使用playsound,rest接口用FastAPI实现。
这样子,我就可以通过远程发起rest请求,播放指定音乐文件了。
完整的代码如下:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import os
import requests
from playsound import playsound
from threading import Thread, Event
import time
from urllib.parse import urlparse
app = FastAPI()
# 全局播放列表
playlist = []
# 控制播放的事件
play_event = Event()
# 当前播放的线程
play_thread = None
class AudioRequest(BaseModel):
mode: str
path: list
action: str = "play" # 默认值为"play"
@app.post("/play")
async def play_audio(request: AudioRequest):
global playlist, play_thread, play_event
# 如果有播放线程,则停止播放并清理
if play_thread and play_thread.is_alive():
play_event.set()
play_thread.join()
if request.action == "tolist":
for idx, file_path in enumerate(request.path):
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail=f"File {file_path} not found")
playlist.append({"mode": request.mode, "path": file_path, "sortting": idx + 1})
return {"status": "success", "message": "Files added to playlist"}
elif request.action == "play" or not request.action:
for file_path in request.path:
if request.mode == "remote":
file_path = cache_remote_file(file_path)
playlist.append({"mode": request.mode, "path": file_path, "sortting": len(playlist) + 1})
# 启动播放线程
play_event.clear()
play_thread = Thread(target=play_playlist)
play_thread.start()
return {"status": "success", "message": "Playing audio"}
elif request.action == "pause":
play_event.set()
return {"status": "success", "message": "Playback paused"}
elif request.action == "stop":
play_event.set()
if play_thread and play_thread.is_alive():
play_thread.join()
clear_playlist()
return {"status": "success", "message": "Playback stopped and playlist cleared"}
else:
raise HTTPException(status_code=400, detail="Invalid action")
@app.get("/playlist")
async def get_playlist():
return playlist
def cache_remote_file(remote_path: str) -> str:
local_dir = "/tmp/"
parsed_url = urlparse(remote_path)
file_extension = os.path.splitext(parsed_url.path)[1]
timestamp = int(time.time())
local_path = os.path.join(local_dir, f"{timestamp}{file_extension}")
if not os.path.exists(local_path):
response = requests.get(remote_path)
with open(local_path, "wb") as file:
file.write(response.content)
return local_path
def play_playlist():
global playlist
while playlist and not play_event.is_set():
item = playlist.pop(0)
file_path = item["path"]
playsound(file_path)
# 清理播放完成的文件
if os.path.exists(file_path):
os.remove(file_path)
# 清理缓存文件
if item["mode"] == "remote" and file_path.startswith("/tmp/"):
os.remove(file_path)
# 清理播放完成后的播放列表
if play_event.is_set():
clear_playlist()
def clear_playlist():
global playlist
playlist = []
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)