/** 主播工作台:当前登录用户对应的主播档案与开/关播 */ export type StreamerSelfData = { streamerId: string name: string title: string game: string isLive: boolean room: { id: string title: string status: string pullPlaybackUrl: string } | null } export async function fetchStreamerSelf(): Promise<{ ok: true; data: StreamerSelfData } | { ok: false; status: number; message: string }> { const res = await fetch("/api/streamer/me", { credentials: "include", cache: "no-store" }) const json = (await res.json().catch(() => ({}))) as { success?: boolean data?: StreamerSelfData message?: string } if (!res.ok || !json.success || !json.data) { return { ok: false, status: res.status, message: json.message ?? "无法加载主播档案" } } return { ok: true, data: json.data } } export type LiveCloudVendorId = "tencent" | "aliyun" | "qiniu" | "volcengine" | "generic" export type LiveCloudUrlsPayload = { vendor: LiveCloudVendorId configured: boolean streamName: string expiresAt: string setupHint?: string docs?: { label: string; href: string }[] push?: { rtmp?: string; srt?: string; note?: string } play?: { hls?: string; flv?: string; rtmp?: string; note?: string } } export async function fetchStreamerCloudUrls( vendor: LiveCloudVendorId, ttlSec = 7200, ): Promise<{ ok: true; data: LiveCloudUrlsPayload } | { ok: false; message: string }> { const res = await fetch( `/api/streamer/live/cloud?vendor=${encodeURIComponent(vendor)}&ttl=${encodeURIComponent(String(ttlSec))}`, { credentials: "include", cache: "no-store" }, ) const json = (await res.json().catch(() => ({}))) as { success?: boolean data?: LiveCloudUrlsPayload message?: string } if (!res.ok || !json.success || !json.data) { return { ok: false, message: json.message ?? "无法获取云厂商地址" } } return { ok: true, data: json.data } } export async function postStreamerLiveAction( action: "start" | "stop", payload?: { title?: string; pullPlaybackUrl?: string | null }, ): Promise<{ ok: boolean; message?: string; data?: Record }> { const res = await fetch("/api/streamer/live", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ action, ...payload }), }) const json = (await res.json().catch(() => ({}))) as { success?: boolean message?: string data?: Record } if (!res.ok || !json.success) { return { ok: false, message: json.message ?? "操作失败" } } return { ok: true, data: json.data } } export type AudienceViewerRow = { viewerKey: string displayLabel: string viewerType: "user" | "anonymous" lastTouchAt: string maxWatchMarkSec: number likeCount: number commentCount: number shareCount: number giftCount: number engagePercent: number recentTouches: { eventName: string; at: string; detail?: string }[] } export async function fetchAudienceInsights(): Promise< | { ok: true; data: { streamerId: string; windowDays: number; viewers: AudienceViewerRow[]; totalViewers: number } } | { ok: false; status: number; message: string } > { const res = await fetch("/api/streamer/audience-insights", { credentials: "include", cache: "no-store" }) const json = (await res.json().catch(() => ({}))) as { success?: boolean data?: { streamerId: string; windowDays: number; viewers: AudienceViewerRow[]; totalViewers: number } message?: string } if (!res.ok || !json.success || !json.data) { return { ok: false, status: res.status, message: json.message ?? "无法加载观众触达" } } return { ok: true, data: json.data } }