Blame view

天文台pc/tianwentai-ui/src/app/pages/SharedObservatoryPage.tsx 6.53 KB
bc518174   王天杨   提交两个项目文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
  import { useState, useEffect } from "react";
  import { Video } from "lucide-react";
  import {
    KIOSK_EVENT,
    loadVideoSourceConfig,
    type VideoSourceConfig,
  } from "../kioskStorage";
  import { PAGE_CONTENT_INSET } from "../pageContentInset";
  
  export function SharedObservatoryPage() {
    const [videoSource, setVideoSource] = useState<VideoSourceConfig>(() => loadVideoSourceConfig());
    const [hlsPlaybackError, setHlsPlaybackError] = useState(false);
  
    useEffect(() => {
      const sync = () => setVideoSource(loadVideoSourceConfig());
      window.addEventListener(KIOSK_EVENT, sync);
      window.addEventListener("storage", sync);
      return () => {
        window.removeEventListener(KIOSK_EVENT, sync);
        window.removeEventListener("storage", sync);
      };
    }, []);
  
    useEffect(() => {
      setHlsPlaybackError(false);
    }, [videoSource.protocol, videoSource.url]);
  
    const streamUrl = videoSource.url.trim();
    const hasStream = streamUrl !== "";
    const showHlsPlayer = hasStream && videoSource.protocol === "HLS" && !hlsPlaybackError;
    const canEmbedByIframe =
      hasStream &&
      (streamUrl.startsWith("http://") || streamUrl.startsWith("https://")) &&
      (!showHlsPlayer || hlsPlaybackError);
  
    return (
      <div
        className={`flex min-h-0 min-w-0 w-full flex-1 flex-col overflow-hidden ${PAGE_CONTENT_INSET}`}
      >
        <div className="flex min-h-0 w-full min-w-0 flex-1 flex-col">
          <div className="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden">
            {/* 实时视频对接区:占满主区域剩余高度,随屏伸缩 */}
            <section className="flex min-h-0 min-w-0 flex-1 flex-col" aria-labelledby="live-video-heading">
              <h2
                id="live-video-heading"
                className="mb-3 flex shrink-0 items-center gap-2 text-xl font-bold text-white sm:text-2xl"
              >
                <Video className="h-6 w-6 text-sky-400" />
                望远镜实时视频
              </h2>
              <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-white/20 bg-black/40 shadow-xl shadow-black/30">
                <div className="relative min-h-0 w-full flex-1">
                  {showHlsPlayer ? (
                    <video
                      src={streamUrl}
                      className="absolute inset-0 z-[1] h-full w-full object-cover"
                      controls
                      autoPlay
                      muted
                      playsInline
                      onError={() => setHlsPlaybackError(true)}
                    />
                  ) : null}
                  {canEmbedByIframe ? (
                    <iframe
                      src={streamUrl}
                      title="望远镜视频源"
                      className="absolute inset-0 z-[1] h-full w-full border-0 bg-black"
                      allow="autoplay; fullscreen; picture-in-picture; camera; microphone"
                      allowFullScreen
                    />
                  ) : null}
                  <div
                    className={`absolute inset-0 ${showHlsPlayer ? "z-[2]" : ""} bg-gradient-to-br from-slate-900 via-blue-950 to-slate-950`}
                    aria-hidden
                  />
                  <div
                    className={`absolute inset-0 ${showHlsPlayer ? "z-[2]" : ""} opacity-40`}
                    style={{
                      backgroundImage:
                        "radial-gradient(circle at 20% 30%, rgba(125,211,252,0.18) 0%, transparent 45%), radial-gradient(circle at 80% 70%, rgba(59,130,246,0.22) 0%, transparent 50%)",
                    }}
                    aria-hidden
                  />
                  <div className="relative flex h-full flex-col items-center justify-center gap-3 px-6 text-center">
                    <Video className="h-14 w-14 text-white/35 sm:h-16 sm:w-16" aria-hidden />
                    <p className="text-lg font-medium text-white/90 sm:text-xl">
                      {hasStream ? "已配置视频源" : "等待接入望远镜视频信号"}
                    </p>
                    {hasStream ? (
                      <div className="w-full max-w-3xl space-y-2">
                        {videoSource.protocol !== "HLS" || hlsPlaybackError ? (
                          <p className="w-full max-w-full px-2 text-sm text-blue-200/90">
                            当前协议为 {videoSource.protocol}
                            {videoSource.protocol === "RTSP"
                              ? ",浏览器通常不能直接播放 RTSP,请通过 WebRTC/HLS 网关转封装。"
                              : ",当前页面展示为已接入状态信息。"}
                          </p>
                        ) : (
                          <p className="w-full max-w-full px-2 text-sm text-blue-200/90">
                            已尝试播放 HLS 源;如画面异常,请检查源地址、跨域与鉴权配置。
                          </p>
                        )}
                        <div className="mx-auto max-w-full break-all rounded-lg border border-white/20 bg-white/10 px-3 py-2 text-left text-xs text-blue-100">
                          {streamUrl}
                        </div>
                        {videoSource.note ? (
                          <div className="mx-auto max-w-full rounded-lg border border-white/10 bg-black/20 px-3 py-2 text-left text-xs text-blue-200/90">
                            备注:{videoSource.note}
                          </div>
                        ) : null}
                        {canEmbedByIframe ? (
                          <p className="text-xs text-blue-200/85">
                            已在当前页面内嵌加载视频源页面。
                          </p>
                        ) : null}
                      </div>
                    ) : (
                      <p className="w-full max-w-full px-2 text-sm text-blue-200/90">
                        未检测到推流。配置信号源地址与鉴权后,画面将在此区域实时显示。
                      </p>
                    )}
                    <div className="mt-2 flex flex-wrap items-center justify-center gap-2">
                      <span className="rounded-full border border-sky-400/40 bg-sky-500/15 px-3 py-1 text-xs font-medium text-sky-200">
                        状态:{hasStream ? "已配置" : "未连接"}
                      </span>
                      <span className="rounded-full border border-white/15 bg-white/5 px-3 py-1 text-xs text-blue-200">
                        对接方式:{videoSource.protocol}
                      </span>
                    </div>
                  </div>
                </div>
              </div>
            </section>
          </div>
        </div>
      </div>
    );
  }