marksverdhei commited on
Commit
6e20182
·
verified ·
1 Parent(s): 604fcb2

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. web/index.html +13 -114
web/index.html CHANGED
@@ -6,105 +6,25 @@
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';
@@ -112,14 +32,10 @@
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 {
@@ -129,22 +45,17 @@
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);
@@ -157,19 +68,8 @@
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() {
@@ -181,12 +81,11 @@
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>
 
6
  <title>Live Stream</title>
7
  <style>
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  video {
11
+ width: 100vw;
12
+ height: 100vh;
13
  object-fit: contain;
14
  background: #000;
15
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  </style>
17
  </head>
18
  <body>
19
+ <video id="video" autoplay muted playsinline></video>
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  <script>
22
  const video = document.getElementById('video');
23
+ let ws, mediaSource, sourceBuffer;
 
 
 
 
 
 
24
  let bufferQueue = [];
25
+ let reconnectTimer;
26
 
27
  function connect() {
 
 
28
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
29
  ws = new WebSocket(`${proto}//${location.host}/api/ws?src=live`);
30
  ws.binaryType = 'arraybuffer';
 
32
  ws.onopen = () => {
33
  mediaSource = new MediaSource();
34
  video.src = URL.createObjectURL(mediaSource);
 
 
 
35
  };
36
 
37
  ws.onmessage = (event) => {
38
  if (typeof event.data === 'string') {
 
39
  const msg = JSON.parse(event.data);
40
  if (msg.type === 'mse') {
41
  try {
 
45
  if (bufferQueue.length > 0 && !sourceBuffer.updating) {
46
  sourceBuffer.appendBuffer(bufferQueue.shift());
47
  }
 
48
  if (video.buffered.length > 0 && video.buffered.end(0) - video.buffered.start(0) > 30) {
49
  sourceBuffer.remove(0, video.buffered.end(0) - 10);
50
  }
51
  });
 
 
52
  video.muted = false;
53
  video.play().catch(() => {});
54
  } catch (e) {
55
+ console.error('Source buffer error:', e);
 
56
  }
57
  }
58
  } else {
 
59
  if (sourceBuffer && !sourceBuffer.updating) {
60
  try {
61
  sourceBuffer.appendBuffer(event.data);
 
68
  }
69
  };
70
 
71
+ ws.onclose = () => { cleanup(); scheduleReconnect(); };
72
+ ws.onerror = () => { cleanup(); scheduleReconnect(); };
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
 
75
  function cleanup() {
 
81
  mediaSource = null;
82
  }
83
 
84
+ function scheduleReconnect() {
85
+ clearTimeout(reconnectTimer);
86
+ reconnectTimer = setTimeout(connect, 3000);
87
+ }
88
 
 
89
  connect();
90
  </script>
91
  </body>