A bare minimal stream.place example showing you how to show a stream live on your website
minimal-stream-place.html
141 lines 5.4 kB view raw
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>Stream.place example</title> 7 </head> 8 <body> 9 <video id="video" autoplay playsinline muted></video> 10 11 <script> 12 // Your atmosphere handle 13 const handle = "pokemon.evil.gay"; 14 let pc = null; 15 const video = document.getElementById("video"); 16 17 async function connect() { 18 if (pc) disconnect(); 19 20 const whepUrl = `https://stream.place/api/playback/${encodeURIComponent(handle)}/webrtc?rendition=source`; 21 22 console.log("Connecting\u2026"); 23 console.log(`WHEP endpoint: ${whepUrl}`); 24 25 try { 26 pc = new RTCPeerConnection({ 27 iceServers: [{ urls: "stun:stun.l.google.com:19302" }], 28 bundlePolicy: "max-bundle", 29 }); 30 31 pc.addTransceiver("video", { direction: "recvonly" }); 32 pc.addTransceiver("audio", { direction: "recvonly" }); 33 34 pc.ontrack = (event) => { 35 console.log( 36 `Track received: ${event.track.kind}`, 37 "success", 38 ); 39 if (event.streams && event.streams[0]) { 40 video.srcObject = event.streams[0]; 41 } else { 42 if (!video.srcObject) { 43 video.srcObject = new MediaStream(); 44 } 45 video.srcObject.addTrack(event.track); 46 } 47 video.play().catch(() => {}); 48 }; 49 50 pc.oniceconnectionstatechange = () => { 51 console.log(`ICE: ${pc.iceConnectionState}`); 52 if ( 53 pc.iceConnectionState === "connected" || 54 pc.iceConnectionState === "completed" 55 ) { 56 } else if ( 57 pc.iceConnectionState === "failed" || 58 pc.iceConnectionState === "disconnected" 59 ) { 60 console.log("Connection lost", "error"); 61 } 62 }; 63 64 pc.onconnectionstatechange = () => { 65 console.log(`Connection: ${pc.connectionState}`); 66 if (pc.connectionState === "failed") { 67 console.log("PeerConnection failed", "error"); 68 } 69 }; 70 71 const offer = await pc.createOffer(); 72 await pc.setLocalDescription(offer); 73 await waitForIceGathering(pc, 2000); 74 75 console.log("Sending SDP offer\u2026"); 76 77 const resp = await fetch(whepUrl, { 78 method: "POST", 79 headers: { "Content-Type": "application/sdp" }, 80 body: pc.localDescription.sdp, 81 }); 82 83 if (!resp.ok) { 84 const errText = await resp.text(); 85 throw new Error(`WHEP ${resp.status}: ${errText}`); 86 } 87 88 const answerSdp = await resp.text(); 89 console.log("Received SDP answer", "success"); 90 91 await pc.setRemoteDescription({ 92 type: "answer", 93 sdp: answerSdp, 94 }); 95 console.log( 96 "Remote description set, waiting for media\u2026", 97 ); 98 } catch (err) { 99 console.log(`Error: ${err.message}`, "error"); 100 setStatus("Error", "error"); 101 console.error(err); 102 } 103 } 104 105 function disconnect() { 106 if (pc) { 107 pc.close(); 108 pc = null; 109 } 110 video.srcObject = null; 111 112 console.log("Disconnected"); 113 } 114 115 function waitForIceGathering(peerConnection, timeout) { 116 return new Promise((resolve) => { 117 if (peerConnection.iceGatheringState === "complete") { 118 resolve(); 119 return; 120 } 121 const timer = setTimeout(() => { 122 console.log( 123 "ICE gathering timed out, proceeding with candidates", 124 ); 125 resolve(); 126 }, timeout); 127 128 peerConnection.onicegatheringstatechange = () => { 129 if (peerConnection.iceGatheringState === "complete") { 130 clearTimeout(timer); 131 console.log("ICE gathering complete"); 132 resolve(); 133 } 134 }; 135 }); 136 } 137 138 connect(); 139 </script> 140 </body> 141</html>