marksverdhei commited on
Commit
604fcb2
·
verified ·
1 Parent(s): 38f54be

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. Dockerfile +1 -0
  2. go2rtc.yaml +1 -0
  3. web/index.html +193 -0
Dockerfile CHANGED
@@ -12,6 +12,7 @@ RUN useradd -m appuser
12
  USER appuser
13
 
14
  COPY --chown=appuser:appuser go2rtc.yaml /home/appuser/go2rtc.yaml
 
15
 
16
  EXPOSE 7860
17
 
 
12
  USER appuser
13
 
14
  COPY --chown=appuser:appuser go2rtc.yaml /home/appuser/go2rtc.yaml
15
+ COPY --chown=appuser:appuser web/ /home/appuser/web/
16
 
17
  EXPOSE 7860
18
 
go2rtc.yaml CHANGED
@@ -1,6 +1,7 @@
1
  api:
2
  listen: ":7860"
3
  origin: "*"
 
4
 
5
  streams:
6
  live: []
 
1
  api:
2
  listen: ":7860"
3
  origin: "*"
4
+ static_dir: /home/appuser/web
5
 
6
  streams:
7
  live: []
web/index.html ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Live Stream</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ background: #0a0a0a;
11
+ color: #fff;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ min-height: 100vh;
17
+ }
18
+ .container {
19
+ width: 100%;
20
+ max-width: 1280px;
21
+ padding: 20px;
22
+ }
23
+ .video-wrapper {
24
+ position: relative;
25
+ width: 100%;
26
+ background: #111;
27
+ border-radius: 8px;
28
+ overflow: hidden;
29
+ aspect-ratio: 16/9;
30
+ }
31
+ video {
32
+ width: 100%;
33
+ height: 100%;
34
+ object-fit: contain;
35
+ background: #000;
36
+ }
37
+ .overlay {
38
+ position: absolute;
39
+ top: 0; left: 0; right: 0; bottom: 0;
40
+ display: flex;
41
+ flex-direction: column;
42
+ justify-content: center;
43
+ align-items: center;
44
+ background: rgba(0,0,0,0.7);
45
+ cursor: pointer;
46
+ transition: opacity 0.3s;
47
+ }
48
+ .overlay.hidden { opacity: 0; pointer-events: none; }
49
+ .overlay .status {
50
+ font-size: 1.2rem;
51
+ color: #aaa;
52
+ margin-top: 12px;
53
+ }
54
+ .play-btn {
55
+ width: 72px; height: 72px;
56
+ border-radius: 50%;
57
+ background: rgba(255,255,255,0.15);
58
+ display: flex;
59
+ justify-content: center;
60
+ align-items: center;
61
+ border: 2px solid rgba(255,255,255,0.3);
62
+ }
63
+ .play-btn svg { fill: #fff; width: 32px; height: 32px; margin-left: 4px; }
64
+ .live-badge {
65
+ position: absolute;
66
+ top: 12px;
67
+ left: 12px;
68
+ background: #e53e3e;
69
+ color: #fff;
70
+ padding: 4px 10px;
71
+ border-radius: 4px;
72
+ font-size: 0.75rem;
73
+ font-weight: 700;
74
+ letter-spacing: 0.05em;
75
+ display: none;
76
+ }
77
+ .live-badge.active { display: block; }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <div class="video-wrapper">
83
+ <video id="video" autoplay muted playsinline></video>
84
+ <div class="live-badge" id="liveBadge">LIVE</div>
85
+ <div class="overlay" id="overlay">
86
+ <div class="play-btn">
87
+ <svg viewBox="0 0 24 24"><polygon points="5,3 19,12 5,21"/></svg>
88
+ </div>
89
+ <div class="status" id="status">Click to watch</div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <script>
95
+ const video = document.getElementById('video');
96
+ const overlay = document.getElementById('overlay');
97
+ const status = document.getElementById('status');
98
+ const liveBadge = document.getElementById('liveBadge');
99
+
100
+ let ws;
101
+ let mediaSource;
102
+ let sourceBuffer;
103
+ let bufferQueue = [];
104
+
105
+ function connect() {
106
+ status.textContent = 'Connecting...';
107
+
108
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
109
+ ws = new WebSocket(`${proto}//${location.host}/api/ws?src=live`);
110
+ ws.binaryType = 'arraybuffer';
111
+
112
+ ws.onopen = () => {
113
+ mediaSource = new MediaSource();
114
+ video.src = URL.createObjectURL(mediaSource);
115
+ mediaSource.addEventListener('sourceopen', () => {
116
+ // go2rtc sends codec info as first text message
117
+ });
118
+ };
119
+
120
+ ws.onmessage = (event) => {
121
+ if (typeof event.data === 'string') {
122
+ // Codec info from go2rtc
123
+ const msg = JSON.parse(event.data);
124
+ if (msg.type === 'mse') {
125
+ try {
126
+ sourceBuffer = mediaSource.addSourceBuffer(msg.value);
127
+ sourceBuffer.mode = 'segments';
128
+ sourceBuffer.addEventListener('updateend', () => {
129
+ if (bufferQueue.length > 0 && !sourceBuffer.updating) {
130
+ sourceBuffer.appendBuffer(bufferQueue.shift());
131
+ }
132
+ // Keep buffer trimmed to avoid memory issues
133
+ if (video.buffered.length > 0 && video.buffered.end(0) - video.buffered.start(0) > 30) {
134
+ sourceBuffer.remove(0, video.buffered.end(0) - 10);
135
+ }
136
+ });
137
+ overlay.classList.add('hidden');
138
+ liveBadge.classList.add('active');
139
+ video.muted = false;
140
+ video.play().catch(() => {});
141
+ } catch (e) {
142
+ console.error('Failed to create source buffer:', e);
143
+ status.textContent = 'Codec not supported';
144
+ }
145
+ }
146
+ } else {
147
+ // Binary media data
148
+ if (sourceBuffer && !sourceBuffer.updating) {
149
+ try {
150
+ sourceBuffer.appendBuffer(event.data);
151
+ } catch (e) {
152
+ bufferQueue.push(event.data);
153
+ }
154
+ } else {
155
+ bufferQueue.push(event.data);
156
+ }
157
+ }
158
+ };
159
+
160
+ ws.onclose = () => {
161
+ liveBadge.classList.remove('active');
162
+ overlay.classList.remove('hidden');
163
+ status.textContent = 'Stream ended. Click to reconnect.';
164
+ cleanup();
165
+ };
166
+
167
+ ws.onerror = () => {
168
+ liveBadge.classList.remove('active');
169
+ overlay.classList.remove('hidden');
170
+ status.textContent = 'Stream offline. Click to retry.';
171
+ cleanup();
172
+ };
173
+ }
174
+
175
+ function cleanup() {
176
+ bufferQueue = [];
177
+ if (mediaSource && mediaSource.readyState === 'open') {
178
+ try { mediaSource.endOfStream(); } catch(e) {}
179
+ }
180
+ sourceBuffer = null;
181
+ mediaSource = null;
182
+ }
183
+
184
+ overlay.addEventListener('click', () => {
185
+ if (ws && ws.readyState === WebSocket.OPEN) return;
186
+ connect();
187
+ });
188
+
189
+ // Auto-connect on load
190
+ connect();
191
+ </script>
192
+ </body>
193
+ </html>