mirror of
				https://gitlab.crans.org/nounous/ghostream.git
				synced 2025-10-31 20:14:29 +01:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			24478bdc7a
			...
			ff2ebd76f1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ff2ebd76f1 | ||
|  | 4cbb1d8192 | 
| @@ -3,10 +3,12 @@ | |||||||
|  */ |  */ | ||||||
| export class GsWebRTC { | export class GsWebRTC { | ||||||
|     /** |     /** | ||||||
|      * @param {list} stunServers  |      * @param {list} stunServers STUN servers | ||||||
|      * @param {HTMLElement} connectionIndicator  |      * @param {HTMLElement} viewer Video HTML element | ||||||
|  |      * @param {HTMLElement} connectionIndicator Connection indicator element | ||||||
|      */ |      */ | ||||||
|     constructor(stunServers, connectionIndicator) { |     constructor(stunServers, viewer, connectionIndicator) { | ||||||
|  |         this.viewer = viewer; | ||||||
|         this.connectionIndicator = connectionIndicator; |         this.connectionIndicator = connectionIndicator; | ||||||
|         this.pc = new RTCPeerConnection({ |         this.pc = new RTCPeerConnection({ | ||||||
|             iceServers: [{ urls: stunServers }] |             iceServers: [{ urls: stunServers }] | ||||||
| @@ -26,7 +28,7 @@ export class GsWebRTC { | |||||||
|      * If connection closed or failed, try to reconnect. |      * If connection closed or failed, try to reconnect. | ||||||
|      */ |      */ | ||||||
|     _onConnectionStateChange() { |     _onConnectionStateChange() { | ||||||
|         console.log("ICE connection state changed to " + this.pc.iceConnectionState); |         console.log("[WebRTC] ICE connection state changed to " + this.pc.iceConnectionState); | ||||||
|         switch (this.pc.iceConnectionState) { |         switch (this.pc.iceConnectionState) { | ||||||
|         case "disconnected": |         case "disconnected": | ||||||
|             this.connectionIndicator.style.fill = "#dc3545"; |             this.connectionIndicator.style.fill = "#dc3545"; | ||||||
| @@ -39,7 +41,7 @@ export class GsWebRTC { | |||||||
|             break; |             break; | ||||||
|         case "closed": |         case "closed": | ||||||
|         case "failed": |         case "failed": | ||||||
|             console.log("Connection closed, restarting..."); |             console.log("[WebRTC] Connection closed, restarting..."); | ||||||
|             /*peerConnection.close(); |             /*peerConnection.close(); | ||||||
|                     peerConnection = null; |                     peerConnection = null; | ||||||
|                     setTimeout(startPeerConnection, 1000);*/ |                     setTimeout(startPeerConnection, 1000);*/ | ||||||
| @@ -52,10 +54,9 @@ export class GsWebRTC { | |||||||
|      * @param {Event} event  |      * @param {Event} event  | ||||||
|      */ |      */ | ||||||
|     _onTrack(event) { |     _onTrack(event) { | ||||||
|         console.log(`New ${event.track.kind} track`); |         console.log(`[WebRTC] New ${event.track.kind} track`); | ||||||
|         if (event.track.kind === "video") { |         if (event.track.kind === "video") { | ||||||
|             const viewer = document.getElementById("viewer"); |             this.viewer.srcObject = event.streams[0]; | ||||||
|             viewer.srcObject = event.streams[0]; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -66,7 +67,7 @@ export class GsWebRTC { | |||||||
|     createOffer() { |     createOffer() { | ||||||
|         this.pc.createOffer().then(offer => { |         this.pc.createOffer().then(offer => { | ||||||
|             this.pc.setLocalDescription(offer); |             this.pc.setLocalDescription(offer); | ||||||
|             console.log("WebRTC offer created"); |             console.log("[WebRTC] WebRTC offer created"); | ||||||
|         }).catch(console.log); |         }).catch(console.log); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -81,7 +82,7 @@ export class GsWebRTC { | |||||||
|         this.pc.onicecandidate = event => { |         this.pc.onicecandidate = event => { | ||||||
|             if (event.candidate === null) { |             if (event.candidate === null) { | ||||||
|                 // Send offer to server |                 // Send offer to server | ||||||
|                 console.log("Sending session description to server"); |                 console.log("[WebRTC] Sending session description to server"); | ||||||
|                 sendFunction(this.pc.localDescription); |                 sendFunction(this.pc.localDescription); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|   | |||||||
| @@ -5,44 +5,42 @@ export class GsWebSocket { | |||||||
|     constructor() { |     constructor() { | ||||||
|         const protocol = (window.location.protocol === "https:") ? "wss://" : "ws://"; |         const protocol = (window.location.protocol === "https:") ? "wss://" : "ws://"; | ||||||
|         this.url = protocol + window.location.host + "/_ws/"; |         this.url = protocol + window.location.host + "/_ws/"; | ||||||
|  |  | ||||||
|  |         // Open WebSocket | ||||||
|  |         this._open(); | ||||||
|  |  | ||||||
|  |         // Configure events | ||||||
|  |         this.socket.addEventListener("open", () => { | ||||||
|  |             console.log("[WebSocket] Connection established"); | ||||||
|  |         }); | ||||||
|  |         this.socket.addEventListener("close", () => { | ||||||
|  |             console.log("[WebSocket] Connection closed, retrying connection in 1s..."); | ||||||
|  |             setTimeout(() => this._open(), 1000); | ||||||
|  |         }); | ||||||
|  |         this.socket.addEventListener("error", () => { | ||||||
|  |             console.log("[WebSocket] Connection errored, retrying connection in 1s..."); | ||||||
|  |             setTimeout(() => this._open(), 1000); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     _open() { |     _open() { | ||||||
|  |         console.log(`[WebSocket] Connecting to ${this.url}...`); | ||||||
|         this.socket = new WebSocket(this.url); |         this.socket = new WebSocket(this.url); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Open websocket. |      * Send local WebRTC session description to remote. | ||||||
|      * @param {Function} openCallback Function called when connection is established.  |  | ||||||
|      * @param {Function} closeCallback Function called when connection is lost.  |  | ||||||
|      */ |  | ||||||
|     open() { |  | ||||||
|         this._open(); |  | ||||||
|         this.socket.addEventListener("open", () => { |  | ||||||
|             console.log("WebSocket opened"); |  | ||||||
|         }); |  | ||||||
|         this.socket.addEventListener("close", () => { |  | ||||||
|             console.log("WebSocket closed, retrying connection in 1s..."); |  | ||||||
|             setTimeout(() => this._open(), 1000); |  | ||||||
|         }); |  | ||||||
|         this.socket.addEventListener("error", () => { |  | ||||||
|             console.log("WebSocket errored, retrying connection in 1s..."); |  | ||||||
|             setTimeout(() => this._open(), 1000); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Exchange WebRTC session description with server. |  | ||||||
|      * @param {SessionDescription} localDescription WebRTC local SDP |      * @param {SessionDescription} localDescription WebRTC local SDP | ||||||
|      * @param {string} stream Name of the stream |      * @param {string} stream Name of the stream | ||||||
|      * @param {string} quality Requested quality  |      * @param {string} quality Requested quality  | ||||||
|      */ |      */ | ||||||
|     sendDescription(localDescription, stream, quality) { |     sendLocalDescription(localDescription, stream, quality) { | ||||||
|         if (this.socket.readyState !== 1) { |         if (this.socket.readyState !== 1) { | ||||||
|             console.log("Waiting for WebSocket to send data..."); |             console.log("[WebSocket] Waiting for connection to send data..."); | ||||||
|             setTimeout(() => this.sendDescription(localDescription, stream, quality), 100); |             setTimeout(() => this.sendDescription(localDescription, stream, quality), 100); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         console.log(`[WebSocket] Sending WebRTC local session description for stream ${stream} quality ${quality}`); | ||||||
|         this.socket.send(JSON.stringify({ |         this.socket.send(JSON.stringify({ | ||||||
|             "webRtcSdp": localDescription, |             "webRtcSdp": localDescription, | ||||||
|             "stream": stream, |             "stream": stream, | ||||||
| @@ -51,12 +49,12 @@ export class GsWebSocket { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Set callback function on new session description. |      * Set callback function on new remote session description. | ||||||
|      * @param {Function} callback Function called when data is received |      * @param {Function} callback Function called when data is received | ||||||
|      */ |      */ | ||||||
|     onDescription(callback) { |     onRemoteDescription(callback) { | ||||||
|         this.socket.addEventListener("message", (event) => { |         this.socket.addEventListener("message", (event) => { | ||||||
|             console.log("Message from server ", event.data); |             console.log("[WebSocket] Received WebRTC remote session description"); | ||||||
|             const sdp = new RTCSessionDescription(JSON.parse(event.data)); |             const sdp = new RTCSessionDescription(JSON.parse(event.data)); | ||||||
|             callback(sdp); |             callback(sdp); | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -10,28 +10,28 @@ import { GsWebRTC } from "./modules/webrtc.js"; | |||||||
|  * @param {Number} viewersCounterRefreshPeriod  |  * @param {Number} viewersCounterRefreshPeriod  | ||||||
|  */ |  */ | ||||||
| export function initViewerPage(stream, stunServers, viewersCounterRefreshPeriod) { | export function initViewerPage(stream, stunServers, viewersCounterRefreshPeriod) { | ||||||
|  |     // Viewer element | ||||||
|  |     const viewer = document.getElementById("viewer"); | ||||||
|  |  | ||||||
|     // Default quality |     // Default quality | ||||||
|     let quality = "source"; |     let quality = "source"; | ||||||
|  |  | ||||||
|     // Create WebSocket |     // Create WebSocket and WebRTC | ||||||
|     const s = new GsWebSocket(); |     const websocket = new GsWebSocket(); | ||||||
|     s.open(); |     const webrtc = new GsWebRTC( | ||||||
|  |  | ||||||
|     // Create WebRTC |  | ||||||
|     const c = new GsWebRTC( |  | ||||||
|         stunServers, |         stunServers, | ||||||
|  |         viewer, | ||||||
|         document.getElementById("connectionIndicator"), |         document.getElementById("connectionIndicator"), | ||||||
|     ); |     ); | ||||||
|     c.createOffer(); |     webrtc.createOffer(); | ||||||
|     c.onICECandidate(localDescription => { |     webrtc.onICECandidate(localDescription => { | ||||||
|         s.sendDescription(localDescription, stream, quality); |         websocket.sendLocalDescription(localDescription, stream, quality); | ||||||
|     }); |     }); | ||||||
|     s.onDescription(sdp => { |     websocket.onRemoteDescription(sdp => { | ||||||
|         c.setRemoteDescription(sdp); |         webrtc.setRemoteDescription(sdp); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // Register keyboard events |     // Register keyboard events | ||||||
|     const viewer = document.getElementById("viewer"); |  | ||||||
|     window.addEventListener("keydown", (event) => { |     window.addEventListener("keydown", (event) => { | ||||||
|         switch (event.key) { |         switch (event.key) { | ||||||
|         case "f": |         case "f": | ||||||
| @@ -81,7 +81,7 @@ export function initViewerPage(stream, stunServers, viewersCounterRefreshPeriod) | |||||||
|         quality = event.target.value; |         quality = event.target.value; | ||||||
|         console.log(`Stream quality changed to ${quality}`); |         console.log(`Stream quality changed to ${quality}`); | ||||||
|  |  | ||||||
|         // Restart the connection with a new quality |         // Restart WebRTC negociation | ||||||
|         // FIXME |         webrtc.createOffer(); | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,10 +8,10 @@ | |||||||
|     <div class="controls"> |     <div class="controls"> | ||||||
|       <span class="control-quality"> |       <span class="control-quality"> | ||||||
|         <select id="quality"> |         <select id="quality"> | ||||||
|           <option value="">Source</option> |           <option value="source">Source</option> | ||||||
|           <option value="@720p">720p</option> |           <option value="720p">720p</option> | ||||||
|           <option value="@480p">480p</option> |           <option value="480p">480p</option> | ||||||
|           <option value="@240p">240p</option> |           <option value="240p">240p</option> | ||||||
|         </select>   |         </select>   | ||||||
|       </span> |       </span> | ||||||
|       <code class="control-srt-link">srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid={{.Path}}</code> |       <code class="control-srt-link">srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid={{.Path}}</code> | ||||||
|   | |||||||
| @@ -36,21 +36,21 @@ func websocketHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| 		err = conn.ReadJSON(c) | 		err = conn.ReadJSON(c) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Printf("Failed to receive client description: %s", err) | 			log.Printf("Failed to receive client description: %s", err) | ||||||
| 			return | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Get requested stream | 		// Get requested stream | ||||||
| 		stream, err := streams.Get(c.Stream) | 		stream, err := streams.Get(c.Stream) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Printf("Stream not found: %s", c.Stream) | 			log.Printf("Stream not found: %s", c.Stream) | ||||||
| 			return | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Get requested quality | 		// Get requested quality | ||||||
| 		q, err := stream.GetQuality(c.Quality) | 		q, err := stream.GetQuality(c.Quality) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Printf("Quality not found: %s", c.Quality) | 			log.Printf("Quality not found: %s", c.Quality) | ||||||
| 			return | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Exchange session descriptions with WebRTC stream server | 		// Exchange session descriptions with WebRTC stream server | ||||||
| @@ -61,7 +61,7 @@ func websocketHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| 		// Send new local description | 		// Send new local description | ||||||
| 		if err := conn.WriteJSON(localDescription); err != nil { | 		if err := conn.WriteJSON(localDescription); err != nil { | ||||||
| 			log.Println(err) | 			log.Println(err) | ||||||
| 			return | 			continue | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user