|
1 | 1 | <!DOCTYPE html> |
2 | 2 | <html> |
3 | 3 | <head> |
4 | | - <meta charset="utf-8" /> |
| 4 | + <title>WebRTC Samples > Data Channel Only (TS v2)</title> |
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
6 | | - <title>webrtc-sdk data-only sample</title> |
7 | | - <style> body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 16px; } </style> |
| 6 | + <meta charset="UTF-8" /> |
| 7 | + <link |
| 8 | + rel="stylesheet" |
| 9 | + href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" |
| 10 | + /> |
| 11 | + <style> |
| 12 | + .jumbotron { |
| 13 | + text-align: center; |
| 14 | + padding: 2em; |
| 15 | + padding-top: 3em; |
| 16 | + } |
| 17 | + .header, |
| 18 | + .footer { |
| 19 | + padding: 15px; |
| 20 | + } |
| 21 | + .header { |
| 22 | + padding-bottom: 20px; |
| 23 | + } |
| 24 | + @media (min-width: 768px) { |
| 25 | + .container { |
| 26 | + max-width: 730px; |
| 27 | + } |
| 28 | + } |
| 29 | + #dataMessagesTextarea { |
| 30 | + font-size: 12px; |
| 31 | + background-color: #f8f9fa; |
| 32 | + } |
| 33 | + pre#log { |
| 34 | + text-align: left; |
| 35 | + max-height: 200px; |
| 36 | + overflow: auto; |
| 37 | + background: #f8f9fa; |
| 38 | + padding: 10px; |
| 39 | + font-size: 12px; |
| 40 | + margin-top: 20px; |
| 41 | + } |
| 42 | + </style> |
8 | 43 | </head> |
| 44 | + |
9 | 45 | <body> |
10 | | - <h2>Data-Only Publish (TS v2)</h2> |
11 | | - <div> |
12 | | - <input id="streamId" placeholder="stream id" /> |
13 | | - <button id="start">Start</button> |
14 | | - <button id="stop" disabled>Stop</button> |
| 46 | + <div class="container"> |
| 47 | + <div class="header clearfix"> |
| 48 | + <div class="row"> |
| 49 | + <h3 class="col text-muted"> |
| 50 | + <a href="samples.html">WebRTC Samples</a> > Data Channel Only (TS v2) |
| 51 | + </h3> |
| 52 | + </div> |
| 53 | + </div> |
| 54 | + |
| 55 | + <div class="jumbotron"> |
| 56 | + <div class="alert alert-primary text-center" role="alert" style="margin-top: -2em"> |
| 57 | + WebRTC Data Channel is an enterprise edition feature. <br /> |
| 58 | + <a href="https://antmedia.io">Try Enterprise Edition for free at antmedia.io</a> |
| 59 | + </div> |
| 60 | + |
| 61 | + <div class="form-group col-sm-12 text-left"> |
| 62 | + <input |
| 63 | + type="text" |
| 64 | + class="form-control" |
| 65 | + id="streamName" |
| 66 | + placeholder="Type stream name" |
| 67 | + /> |
| 68 | + </div> |
| 69 | + |
| 70 | + <div class="form-group col-sm-12 text-left"> |
| 71 | + <label>Data Channel Messages</label> |
| 72 | + <textarea |
| 73 | + class="form-control" |
| 74 | + id="dataMessagesTextarea" |
| 75 | + rows="10" |
| 76 | + readonly |
| 77 | + ></textarea> |
| 78 | + <div class="form-row mt-2"> |
| 79 | + <div class="form-group col-sm-10"> |
| 80 | + <input |
| 81 | + type="text" |
| 82 | + class="form-control" |
| 83 | + id="dataTextbox" |
| 84 | + placeholder="Write your message to send" |
| 85 | + /> |
| 86 | + </div> |
| 87 | + <div class="form-group col-sm-2"> |
| 88 | + <button |
| 89 | + type="button" |
| 90 | + id="send" |
| 91 | + class="btn btn-outline-primary btn-block" |
| 92 | + disabled |
| 93 | + > |
| 94 | + Send |
| 95 | + </button> |
| 96 | + </div> |
| 97 | + </div> |
| 98 | + </div> |
| 99 | + |
| 100 | + <div class="form-group col-sm-12 text-center" id="offlineInfo"> |
| 101 | + <div class="form-group text-center" style="font-size: 1.2em"> |
| 102 | + <span class="badge badge-secondary">Status: Offline</span> |
| 103 | + </div> |
| 104 | + </div> |
| 105 | + <div |
| 106 | + class="form-group col-sm-12 text-center" |
| 107 | + id="broadcastingInfo" |
| 108 | + style="display: none" |
| 109 | + > |
| 110 | + <div class="form-group text-center" style="margin-bottom: 8px; font-size: 1em;"> |
| 111 | + <a href="" id="playlink" target="_blank">Join channel in a New Tab</a> |
| 112 | + </div> |
| 113 | + <div class="form-group text-center" style="font-size: 1.2em"> |
| 114 | + <span class="badge badge-success" id="statusBadge">Status: Connected</span> |
| 115 | + </div> |
| 116 | + </div> |
| 117 | + |
| 118 | + <div class="form-group"> |
| 119 | + <button class="btn btn-primary" id="connect_channel_button"> |
| 120 | + Join Channel |
| 121 | + </button> |
| 122 | + <button class="btn btn-primary" disabled id="disconnect_channel_button"> |
| 123 | + Disconnect |
| 124 | + </button> |
| 125 | + </div> |
| 126 | + </div> |
| 127 | + |
| 128 | + <pre id="log"></pre> |
| 129 | + |
| 130 | + <footer class="footer text-left"> |
| 131 | + <div class="row"> |
| 132 | + <div class="col-sm-6 text-left"> |
| 133 | + <a href="http://antmedia.io" target="_blank">antmedia.io</a> |
| 134 | + </div> |
| 135 | + <div class="col-sm-6 text-right"> |
| 136 | + <span class="text-muted">Ant Media Server</span> |
| 137 | + </div> |
| 138 | + </div> |
| 139 | + </footer> |
15 | 140 | </div> |
16 | | - <fieldset style="margin-top:12px;"> |
17 | | - <legend>Data Channel</legend> |
18 | | - <input id="dcText" placeholder="message" /> |
19 | | - <button id="sendText" disabled>Send Text</button> |
20 | | - <button id="sendJSON" disabled>Send JSON</button> |
21 | | - </fieldset> |
22 | | - <pre id="log"></pre> |
| 141 | + |
| 142 | + <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> |
| 143 | + <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script> |
23 | 144 |
|
24 | 145 | <script type="module"> |
25 | | - import { StreamingClient, getWebSocketURL, generateRandomString } from './js/index.js'; |
26 | | - const websocketURL = getWebSocketURL(window.location); |
27 | | - const streamIdEl = document.getElementById('streamId'); |
28 | | - const startBtn = document.getElementById('start'); |
29 | | - const stopBtn = document.getElementById('stop'); |
30 | | - const dcText = document.getElementById('dcText'); |
31 | | - const sendTextBtn = document.getElementById('sendText'); |
32 | | - const sendJSONBtn = document.getElementById('sendJSON'); |
33 | | - const logEl = document.getElementById('log'); |
| 146 | + import { |
| 147 | + StreamingClient, |
| 148 | + getWebSocketURL, |
| 149 | + generateRandomString, |
| 150 | + } from "./js/index.js"; |
| 151 | + |
| 152 | + const streamNameInput = document.getElementById("streamName"); |
| 153 | + const connectBtn = document.getElementById("connect_channel_button"); |
| 154 | + const disconnectBtn = document.getElementById("disconnect_channel_button"); |
| 155 | + const sendBtn = document.getElementById("send"); |
| 156 | + const dataTextbox = document.getElementById("dataTextbox"); |
| 157 | + const messagesTextarea = document.getElementById("dataMessagesTextarea"); |
| 158 | + const logEl = document.getElementById("log"); |
| 159 | + const offlineInfo = document.getElementById("offlineInfo"); |
| 160 | + const broadcastingInfo = document.getElementById("broadcastingInfo"); |
| 161 | + const statusBadge = document.getElementById("statusBadge"); |
| 162 | + const playlink = document.getElementById("playlink"); |
| 163 | + |
34 | 164 | let client = null; |
35 | | - let currentId = ''; |
36 | | - function log(m) { logEl.textContent += m + '\n'; } |
| 165 | + let currentId = ""; |
| 166 | + let isPublisher = false; |
| 167 | + |
| 168 | + function log(msg) { |
| 169 | + logEl.textContent += msg + "\n"; |
| 170 | + logEl.scrollTop = logEl.scrollHeight; |
| 171 | + } |
| 172 | + |
| 173 | + function appendMessage(msg) { |
| 174 | + messagesTextarea.value += msg + "\r\n"; |
| 175 | + messagesTextarea.scrollTop = messagesTextarea.scrollHeight; |
| 176 | + } |
37 | 177 |
|
38 | | - startBtn.onclick = async () => { |
39 | | - const id = streamIdEl.value.trim() || 'data_' + generateRandomString(6); |
| 178 | + const websocketURL = getWebSocketURL(window.location); |
| 179 | + |
| 180 | + async function initClient() { |
| 181 | + if (!client) { |
| 182 | + client = new StreamingClient({ |
| 183 | + websocketURL, |
| 184 | + onlyDataChannel: true, |
| 185 | + }); |
| 186 | + |
| 187 | + client.on("initialized", () => log("initialized")); |
| 188 | + client.on("data_channel_opened", () => { |
| 189 | + log("data channel opened"); |
| 190 | + sendBtn.disabled = false; |
| 191 | + }); |
| 192 | + client.on("data_channel_closed", () => { |
| 193 | + log("data channel closed"); |
| 194 | + sendBtn.disabled = true; |
| 195 | + }); |
| 196 | + client.on("data_received", ({ data }) => { |
| 197 | + const msg = typeof data === "string" ? data : data.byteLength + " bytes"; |
| 198 | + log("dc <- " + msg); |
| 199 | + appendMessage("Received: " + msg); |
| 200 | + }); |
| 201 | + client.on("publish_started", (o) => { |
| 202 | + log("publish_started " + o.streamId); |
| 203 | + isPublisher = true; |
| 204 | + updateStatus(true, "Connected (Publisher)"); |
| 205 | + }); |
| 206 | + client.on("play_started", (o) => { |
| 207 | + log("play_started " + o.streamId); |
| 208 | + isPublisher = false; |
| 209 | + updateStatus(true, "Connected (Player)"); |
| 210 | + }); |
| 211 | + client.on("publish_finished", () => updateStatus(false)); |
| 212 | + client.on("play_finished", () => updateStatus(false)); |
| 213 | + client.on("error", (e) => log("error: " + (e.error || e.message || e))); |
| 214 | + |
| 215 | + await client.ready(); |
| 216 | + } |
| 217 | + return client; |
| 218 | + } |
| 219 | + |
| 220 | + function updateStatus(connected, text = "Connected") { |
| 221 | + if (connected) { |
| 222 | + offlineInfo.style.display = "none"; |
| 223 | + broadcastingInfo.style.display = "block"; |
| 224 | + statusBadge.textContent = "Status: " + text; |
| 225 | + connectBtn.disabled = true; |
| 226 | + disconnectBtn.disabled = false; |
| 227 | + playlink.href = window.location.pathname + "?id=" + currentId; |
| 228 | + } else { |
| 229 | + offlineInfo.style.display = "block"; |
| 230 | + broadcastingInfo.style.display = "none"; |
| 231 | + connectBtn.disabled = false; |
| 232 | + disconnectBtn.disabled = true; |
| 233 | + sendBtn.disabled = true; |
| 234 | + } |
| 235 | + } |
| 236 | + |
| 237 | + connectBtn.onclick = async () => { |
| 238 | + const id = streamNameInput.value.trim() || "stream_" + generateRandomString(6); |
| 239 | + currentId = id; |
| 240 | + |
40 | 241 | try { |
41 | | - const r = await StreamingClient.createSession({ websocketURL, role: 'publisher', streamId: id, onlyDataChannel: true }); |
42 | | - client = r.client; currentId = id; stopBtn.disabled = false; |
43 | | - client.on('data_channel_opened', () => { sendTextBtn.disabled = false; sendJSONBtn.disabled = false; log('data channel opened'); }); |
44 | | - client.on('data_channel_closed', () => { sendTextBtn.disabled = true; sendJSONBtn.disabled = true; log('data channel closed'); }); |
45 | | - client.on('data_received', ({ data }) => log('dc <- ' + (typeof data === 'string' ? data : (data.byteLength + ' bytes')))); |
46 | | - log('started data-only for ' + id); |
47 | | - } catch(e) { log('start failed: ' + (e && e.message ? e.message : e)); } |
| 242 | + const sdk = await initClient(); |
| 243 | + log("Attempting to join as player..."); |
| 244 | + try { |
| 245 | + await sdk.join({ role: "player", streamId: id, timeoutMs: 5000 }); |
| 246 | + } catch (e) { |
| 247 | + log("Join as player failed or timed out, attempting as publisher..."); |
| 248 | + await sdk.join({ role: "publisher", streamId: id, timeoutMs: 5000 }); |
| 249 | + } |
| 250 | + } catch (e) { |
| 251 | + log("Connection failed: " + (e.message || e)); |
| 252 | + updateStatus(false); |
| 253 | + } |
| 254 | + }; |
| 255 | + |
| 256 | + disconnectBtn.onclick = () => { |
| 257 | + if (client && currentId) { |
| 258 | + client.stop(currentId); |
| 259 | + } |
48 | 260 | }; |
49 | | - stopBtn.onclick = () => { if (client && currentId) { client.stop(currentId); stopBtn.disabled = true; } }; |
50 | | - sendTextBtn.onclick = async () => { if (client && currentId) { await client.sendData(currentId, dcText.value || 'hello'); log('dc -> ' + (dcText.value || 'hello')); } }; |
51 | | - sendJSONBtn.onclick = async () => { if (client && currentId) { await client.sendJSON(currentId, { hello: 'world' }); log('dc -> {"hello":"world"}'); } }; |
| 261 | + |
| 262 | + sendBtn.onclick = async () => { |
| 263 | + if (client && currentId) { |
| 264 | + const val = dataTextbox.value || "hello"; |
| 265 | + try { |
| 266 | + await client.sendData(currentId, val); |
| 267 | + log("dc -> " + val); |
| 268 | + appendMessage("Sent: " + val); |
| 269 | + dataTextbox.value = ""; |
| 270 | + } catch (e) { |
| 271 | + log("send failed: " + e.message); |
| 272 | + } |
| 273 | + } |
| 274 | + }; |
| 275 | + |
| 276 | + dataTextbox.onkeypress = (e) => { |
| 277 | + if (e.key === "Enter") sendBtn.click(); |
| 278 | + }; |
| 279 | + |
| 280 | + // Set initial stream ID if present in URL |
| 281 | + const urlParams = new URLSearchParams(window.location.search); |
| 282 | + const idParam = urlParams.get("id") || urlParams.get("name"); |
| 283 | + if (idParam) { |
| 284 | + streamNameInput.value = idParam; |
| 285 | + } else { |
| 286 | + streamNameInput.value = "stream1"; |
| 287 | + } |
52 | 288 | </script> |
53 | 289 | </body> |
54 | | - </html> |
55 | | - |
| 290 | +</html> |
0 commit comments