2026-10-06 02:36:54 +00:00
|
|
|
from flask import Blueprint, request, jsonify, Response
|
2026-05-12 23:50:49 +00:00
|
|
|
from datetime import datetime, timezone
|
2026-10-06 02:36:54 +00:00
|
|
|
from typing import Dict, Any, Tuple
|
2026-05-12 23:50:49 +00:00
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
from models.state import video_state
|
2026-05-12 23:50:49 +00:00
|
|
|
|
|
|
|
|
watchparty_bp = Blueprint("watchparty", __name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@watchparty_bp.route("/watchparty/<room>/video", methods=["GET", "POST"])
|
2026-10-06 02:36:54 +00:00
|
|
|
def watchparty_video_control(room: str) -> Tuple[Response, int] | Response:
|
2026-05-12 23:50:49 +00:00
|
|
|
room = room.strip()
|
|
|
|
|
video_state.setdefault(room, {})
|
|
|
|
|
|
|
|
|
|
if request.method == "POST":
|
2026-10-06 02:36:54 +00:00
|
|
|
data: Dict[str, Any] = request.get_json() or {}
|
|
|
|
|
url: str = data.get("url", "").strip()
|
2026-05-12 23:50:49 +00:00
|
|
|
if not url:
|
|
|
|
|
return jsonify({"error": "No video URL"}), 400
|
|
|
|
|
|
|
|
|
|
video_state[room] = {
|
|
|
|
|
"url": url,
|
|
|
|
|
"started_at": datetime.now(timezone.utc).isoformat(),
|
|
|
|
|
"is_paused": False,
|
2026-10-06 02:36:54 +00:00
|
|
|
"is_active": True,
|
2026-05-12 23:50:49 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
return jsonify({"status": "started", "url": url})
|
2026-05-12 23:50:49 +00:00
|
|
|
|
2026-10-06 02:36:54 +00:00
|
|
|
# GET: return URL, elapsed time, is_paused, is_active
|
|
|
|
|
video_data = video_state.get(room, {})
|
2026-05-12 23:50:49 +00:00
|
|
|
if not video_data or "url" not in video_data:
|
2026-10-06 02:36:54 +00:00
|
|
|
return jsonify({"is_active": False, "url": None, "elapsed": 0, "is_paused": False})
|
2026-05-12 23:50:49 +00:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
is_paused = video_data.get("is_paused", False)
|
|
|
|
|
if is_paused:
|
|
|
|
|
elapsed = video_data.get("elapsed_at_pause", 0)
|
|
|
|
|
else:
|
|
|
|
|
start_time = datetime.fromisoformat(video_data["started_at"])
|
|
|
|
|
elapsed = (datetime.now(timezone.utc) - start_time).total_seconds()
|
2026-10-06 02:36:54 +00:00
|
|
|
|
2026-05-12 23:50:49 +00:00
|
|
|
return jsonify(
|
2026-10-06 02:36:54 +00:00
|
|
|
{
|
|
|
|
|
"url": video_data["url"],
|
|
|
|
|
"elapsed": max(0, int(elapsed)),
|
|
|
|
|
"is_paused": is_paused,
|
|
|
|
|
"is_active": video_data.get("is_active", True),
|
|
|
|
|
}
|
2026-05-12 23:50:49 +00:00
|
|
|
)
|
2026-10-06 02:36:54 +00:00
|
|
|
except Exception:
|
|
|
|
|
return jsonify({"is_active": False, "url": None, "elapsed": 0, "is_paused": False})
|
2026-05-12 23:50:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@watchparty_bp.route("/watchparty/<room>/video/clear", methods=["POST"])
|
2026-10-06 02:36:54 +00:00
|
|
|
def clear_watchparty_video(room: str) -> Response:
|
2026-05-12 23:50:49 +00:00
|
|
|
room = room.strip()
|
|
|
|
|
video_state.pop(room, None)
|
|
|
|
|
return jsonify({"status": "cleared"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@watchparty_bp.route("/watchparty/<room>/video/pause", methods=["POST"])
|
2026-10-06 02:36:54 +00:00
|
|
|
def watchparty_pause_video(room: str) -> Response:
|
2026-05-12 23:50:49 +00:00
|
|
|
room = room.strip()
|
|
|
|
|
state = video_state.get(room)
|
|
|
|
|
if not state or state.get("is_paused"):
|
|
|
|
|
return jsonify({"status": "ignored"})
|
|
|
|
|
|
|
|
|
|
start_time = datetime.fromisoformat(state["started_at"])
|
|
|
|
|
elapsed = (datetime.now(timezone.utc) - start_time).total_seconds()
|
|
|
|
|
|
|
|
|
|
state["is_paused"] = True
|
2026-10-06 02:36:54 +00:00
|
|
|
state["elapsed_at_pause"] = max(0, int(elapsed))
|
2026-05-12 23:50:49 +00:00
|
|
|
|
|
|
|
|
return jsonify({"status": "paused"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@watchparty_bp.route("/watchparty/<room>/video/resume", methods=["POST"])
|
2026-10-06 02:36:54 +00:00
|
|
|
def watchparty_resume_video(room: str) -> Response:
|
2026-05-12 23:50:49 +00:00
|
|
|
room = room.strip()
|
|
|
|
|
state = video_state.get(room)
|
|
|
|
|
if not state or not state.get("is_paused"):
|
|
|
|
|
return jsonify({"status": "ignored"})
|
|
|
|
|
|
|
|
|
|
elapsed = state.get("elapsed_at_pause", 0)
|
|
|
|
|
new_start_timestamp = datetime.now(timezone.utc).timestamp() - elapsed
|
|
|
|
|
|
|
|
|
|
state["started_at"] = datetime.fromtimestamp(new_start_timestamp, timezone.utc).isoformat()
|
|
|
|
|
state["is_paused"] = False
|
|
|
|
|
state.pop("elapsed_at_pause", None)
|
|
|
|
|
|
|
|
|
|
return jsonify({"status": "resumed"})
|
2026-10-06 02:36:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@watchparty_bp.route("/watchparty/<room>/video/toggle", methods=["POST"])
|
|
|
|
|
def watchparty_toggle_video(room: str) -> Response:
|
|
|
|
|
room = room.strip()
|
|
|
|
|
data: Dict[str, Any] = request.get_json() or {}
|
|
|
|
|
state = video_state.setdefault(room, {})
|
|
|
|
|
|
|
|
|
|
if "is_active" in data:
|
|
|
|
|
state["is_active"] = bool(data["is_active"])
|
|
|
|
|
else:
|
|
|
|
|
state["is_active"] = not state.get("is_active", False)
|
|
|
|
|
|
|
|
|
|
return jsonify({"status": "toggled", "is_active": state["is_active"]})
|