add handling for public streams

new env vars for mediamtx
switch to redis for ipc
This commit is contained in:
CEF Server 2024-08-27 14:46:44 +00:00
parent 215ffced7c
commit 6233c96e6a
5 changed files with 120 additions and 31 deletions

View file

@ -1,5 +1,14 @@
from starlette.responses import JSONResponse
import asyncio
import subprocess
import time
from enum import Enum
import aiohttp
import ffmpeg
import select
from starlette.responses import JSONResponse, Response, FileResponse
from config import MEDIAMTX_API, MEDIAMTX_RTSP
from . import router
from fastapi import Request, Depends
@ -18,8 +27,11 @@ def pathParts(path):
return None, None, None
return channel, user, token
class TargetType(str, Enum):
user = "u"
channel = "c"
@router.get("/mediamtx/streams/{channel}", dependencies=[Depends(JWTBearer())])
@router.get("/mediamtx/streams/{channel}", dependencies=[Depends(JWTBearer(True))])
async def mediamtxChannelStreams(request: Request, channel: str):
inChannel = request.state.jwt.get("channel", "").lower() == "#" + channel.lower()
results = []
@ -32,12 +44,70 @@ async def mediamtxChannelStreams(request: Request, channel: str):
})
return JSONResponse(status_code=200, content=results)
@router.get("/mediamtx/thumbnail/{user}/{key}")
@router.get("/mediamtx/thumbnail/{channel}/{user}/{key}")
async def mediamtxThumbnail(user: str, key: str, channel: str = None):
blob = "".join([x for x in (channel, user, key) if x])
if "?" in blob:
return Response(status_code=404)
print(f"rtsp://{MEDIAMTX_RTSP}/" + "/".join([x for x in (channel, user, key) if x]))
proc: subprocess.Popen = ffmpeg.input(f"rtsp://{MEDIAMTX_RTSP}/" + "/".join([x for x in (channel, user, key) if x]),
).output('pipe:', loglevel="quiet", vframes=1, format='image2', vcodec='mjpeg').run_async(pipe_stdout=True)
timeout = time.time() + 8
data = b""
while 1:
if select.select([proc.stdout], [], [], 0)[0]:
data += proc.stdout.read(65535)
if proc.poll() is not None:
break
else:
if timeout < time.time():
proc.kill()
return Response(status_code=503)
await asyncio.sleep(0.1)
return Response(content=data, media_type="image/jpeg", headers={
"Cache-Control": "public, max-age=60, stale-while-revalidate=300"
})
@router.get("/mediamtx/public", dependencies=[])
async def mediamtxAllPublicStreams():
results = []
async for result in redis.scan_iter(f"stream *"):
_, channel, user, token = result.decode("utf8").split()
if token == "public":
results.append({
"target": channel,
"user": user,
"token": token
})
return JSONResponse(status_code=200, content=results)
@router.get("/mediamtx/public/{targetType}/{target}", dependencies=[])
async def mediamtxEntityPublicStreams(targetType: TargetType, target: str):
if targetType == TargetType.channel:
target = "#" + target
results = []
async for result in redis.scan_iter(f"stream {target} *"):
_, channel, user, token = result.decode("utf8").split()
print(result)
if token == "public":
results.append({
"user": user,
"token": token
})
return JSONResponse(status_code=200, content=results)
@router.post("/mediamtx/auth", include_in_schema=False)
async def mediamtxAuth(request: Request):
if request.client.host not in privilegedIps:
return False
body = await request.json()
# Just never expose RTSP :)
if body["protocol"] == "rtsp":
return JSONResponse(status_code=200, content={"success": True})
jwt = decodeJWT(body["query"][4:])
path = body["path"].split("/")
reading = body["action"] == "read"
@ -74,7 +144,7 @@ async def mediamtxAdd(request: Request):
body = await request.json()
path = body["env"]["MTX_PATH"].split("/")
parts = [x for x in pathParts(path) if x]
await redis.set("stream " + " ".join(parts), parts[2])
await redis.set("stream " + " ".join(parts), parts[2], ex=60)
if len(parts) == 3:
await ergo.broadcastTo(parts[0], "STREAMSTART", parts[0], parts[1], parts[2])
@ -89,3 +159,16 @@ async def mediamtxDelete(request: Request):
await redis.delete("stream " + " ".join(parts))
if len(parts) == 3:
await ergo.broadcastTo(parts[0], "STREAMEND", parts[0], parts[1], parts[2])
# Not an endpoint
async def mediamtxPoll():
while 1:
async with aiohttp.ClientSession() as sess:
req = await sess.get(MEDIAMTX_API +"/v3/paths/list?itemsPerPage=9999999")
streams = await req.json()
for stream in streams["items"]:
if stream["ready"]:
path = stream["name"].split("/")
parts = [x for x in pathParts(path) if x]
await redis.set(f"stream " + " ".join(parts), parts[-1], ex=60)
await asyncio.sleep(30)