diff --git a/tiny_streamlit_webrtc/frontend/src/TinyWebrtc.tsx b/tiny_streamlit_webrtc/frontend/src/TinyWebrtc.tsx index 3d6afd7..83b3456 100644 --- a/tiny_streamlit_webrtc/frontend/src/TinyWebrtc.tsx +++ b/tiny_streamlit_webrtc/frontend/src/TinyWebrtc.tsx @@ -2,6 +2,7 @@ import { Streamlit, StreamlitComponentBase, withStreamlitConnection, + ComponentProps, } from "streamlit-component-lib" import React, { ReactNode } from "react" @@ -10,8 +11,100 @@ interface State {} class TinyWebrtc extends StreamlitComponentBase { public state = {} + private pc: RTCPeerConnection | undefined + private videoRef: React.RefObject + + constructor(props: ComponentProps) { + super(props) + + this.pc = undefined + this.videoRef = React.createRef() + } + public render = (): ReactNode => { - return + return ( +
+ +
+ ) + } + + private createPeerConnection = () => { + const config: RTCConfiguration = {} + const pc = new RTCPeerConnection(config) + + // connect audio / video + pc.addEventListener("track", (evt) => { + if (evt.track.kind === "video") { + const videoElement = this.videoRef.current + if (videoElement != null) { + videoElement.srcObject = evt.streams[0] + } else { + console.warn("Video element is not mounted.") + } + } + }) + + return pc + } + + private negotiate = (pc: RTCPeerConnection) => { + return pc + .createOffer() + .then(function (offer) { + return pc.setLocalDescription(offer) + }) + .then(function () { + // wait for ICE gathering to complete + return new Promise(function (resolve) { + if (pc.iceGatheringState === "complete") { + resolve() + } else { + const checkState = () => { + if (pc.iceGatheringState === "complete") { + pc.removeEventListener("icegatheringstatechange", checkState) + resolve() + } + } + pc.addEventListener("icegatheringstatechange", checkState) + } + }) + }) + .then(function () { + const offer = pc.localDescription + + if (offer == null) { + console.error("Offer is null.") + return + } + + // Offer is created! + console.log(offer) + }) + } + + private start = () => { + const pc = this.createPeerConnection() + + const constraints: MediaStreamConstraints = { + audio: false, + video: true, + } + + navigator.mediaDevices.getUserMedia(constraints).then( + (stream) => { + stream.getTracks().forEach(function (track) { + pc.addTrack(track, stream) + }) + return this.negotiate(pc) + }, + function (err) { + alert("Could not acquire media: " + err) + } + ) + + this.pc = pc } }