Create working prototype
This commit is contained in:
parent
c8f076c2ab
commit
86e9024a07
@ -8,4 +8,6 @@ RUN go build -o ministream main.go
|
|||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
COPY --from=builder /app/ministream /usr/bin/ministream
|
COPY --from=builder /app/ministream /usr/bin/ministream
|
||||||
|
EXPOSE 8080
|
||||||
|
EXPOSE 50000-50050/udp
|
||||||
CMD ["/usr/bin/ministream", "serve"]
|
CMD ["/usr/bin/ministream", "serve"]
|
||||||
|
17
Taskfile.yaml
Normal file
17
Taskfile.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
default: task -l
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: "Build image using podman"
|
||||||
|
cmd: podman build -t git.t-juice.club/torjus/ministream:latest .
|
||||||
|
|
||||||
|
push:
|
||||||
|
desc: "Push image to git.t-juice.club/torjus/ministream"
|
||||||
|
deps:
|
||||||
|
- build
|
||||||
|
cmds:
|
||||||
|
- cmd: podman push git.t-juice.club/torjus/ministream:latest
|
||||||
|
|
2
main.go
2
main.go
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "v0.1.0"
|
const Version = "v0.1.1"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.App{
|
app := cli.App{
|
||||||
|
@ -35,7 +35,9 @@ func NewServer(store *UserStore) *Server {
|
|||||||
r.Get("/{name}", srv.StaticHandler)
|
r.Get("/{name}", srv.StaticHandler)
|
||||||
r.Post("/whip", http.HandlerFunc(srv.WhipHandler))
|
r.Post("/whip", http.HandlerFunc(srv.WhipHandler))
|
||||||
r.Get("/whip", srv.ListHandler)
|
r.Get("/whip", srv.ListHandler)
|
||||||
|
r.Options("/whip", srv.OptionsHandler)
|
||||||
r.Delete("/whip/{streamKey}", srv.DeleteHandler)
|
r.Delete("/whip/{streamKey}", srv.DeleteHandler)
|
||||||
|
r.Patch("/whip/{streamKey}", srv.PatchHandler)
|
||||||
r.Post("/whip/{streamKey}", srv.PostOfferHandler)
|
r.Post("/whip/{streamKey}", srv.PostOfferHandler)
|
||||||
|
|
||||||
srv.Handler = r
|
srv.Handler = r
|
||||||
@ -43,6 +45,14 @@ func NewServer(store *UserStore) *Server {
|
|||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) OptionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
slog.Info("Got OPTIONS")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) PatchHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
slog.Info("Got PATCH!")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) StaticHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) StaticHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
name := chi.URLParam(r, "name")
|
name := chi.URLParam(r, "name")
|
||||||
if name == "" {
|
if name == "" {
|
||||||
@ -93,7 +103,7 @@ func (s *Server) PostOfferHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
io.WriteString(w, answer.SDP)
|
io.WriteString(w, answer.SDP)
|
||||||
|
|
||||||
slog.Info("Got offer.", "stream", stream)
|
slog.Info("Got listener for stream.", "stream_key", streamKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) DeleteHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) DeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -140,6 +150,7 @@ func (s *Server) WhipHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Location", fmt.Sprintf("/whip/%s", streamKey))
|
w.Header().Add("Location", fmt.Sprintf("/whip/%s", streamKey))
|
||||||
|
w.Header().Add("Link", "stun:stun.l.google.com:19302; rel=\"ice-server\";")
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
if _, err := io.WriteString(w, answer.SDP); err != nil {
|
if _, err := io.WriteString(w, answer.SDP); err != nil {
|
||||||
slog.Error("Error writing response.", "error", err)
|
slog.Error("Error writing response.", "error", err)
|
||||||
|
@ -4,12 +4,13 @@ const startStream = function (streamKey) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
pc.oniceconnectionstatechange = e => console.log(e)
|
pc.oniceconnectionstatechange = e => console.log(e)
|
||||||
pc.addTransceiver('video')
|
pc.onicecandidate = e => {
|
||||||
pc.addTransceiver('audio')
|
console.log("Adding ice candidate: " + e.candidate);
|
||||||
pc.createOffer().then(offer => {
|
if (!e.candidate) {
|
||||||
|
console.log("Done adding candidates. Creating offer.");
|
||||||
fetch("/whip/tjuice", {
|
fetch("/whip/tjuice", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: offer.sdp
|
body: pc.localDescription.sdp
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
resp.text().then(text => {
|
resp.text().then(text => {
|
||||||
var answer = {
|
var answer = {
|
||||||
@ -17,15 +18,21 @@ const startStream = function (streamKey) {
|
|||||||
sdp: text
|
sdp: text
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
pc.setLocalDescription(offer).then(
|
console.log("Setting remote description.");
|
||||||
pc.setRemoteDescription(answer)
|
pc.setRemoteDescription(answer)
|
||||||
)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error setting remote description: " + e)
|
console.log("Error setting remote description: " + e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pc.createOffer().then(offer => {
|
||||||
|
console.log("Setting local description.");
|
||||||
|
pc.setLocalDescription(offer)
|
||||||
})
|
})
|
||||||
|
pc.addTransceiver('video')
|
||||||
|
pc.addTransceiver('audio')
|
||||||
|
|
||||||
pc.ontrack = function (event) {
|
pc.ontrack = function (event) {
|
||||||
console.log(event)
|
console.log(event)
|
||||||
|
@ -75,7 +75,22 @@ func (s *Stream) AddListener(sd *webrtc.SessionDescription) (*webrtc.SessionDesc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
|
|
||||||
|
gatherComplete := make(chan struct{})
|
||||||
|
peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
|
||||||
|
if i == nil {
|
||||||
|
gatherComplete <- struct{}{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("Got ICE Candidate for listener.",
|
||||||
|
"addr", i.Address,
|
||||||
|
"port", i.Port,
|
||||||
|
"related_addr", i.RelatedAddress,
|
||||||
|
"related_port", i.RelatedPort)
|
||||||
|
if err := peerConnection.AddICECandidate(i.ToJSON()); err != nil {
|
||||||
|
slog.Info("Error adding ICE Candidate.", "error", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Sets the LocalDescription, and starts our UDP listeners
|
// Sets the LocalDescription, and starts our UDP listeners
|
||||||
err = peerConnection.SetLocalDescription(answer)
|
err = peerConnection.SetLocalDescription(answer)
|
||||||
@ -126,8 +141,10 @@ func (s *StreamStore) Add(streamKey string, sd *webrtc.SessionDescription) (*web
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
se := webrtc.SettingEngine{}
|
||||||
|
_ = se.SetEphemeralUDPPortRange(50000, 50050)
|
||||||
// Create a new RTCPeerConnection
|
// Create a new RTCPeerConnection
|
||||||
peerConnection, err := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)).NewPeerConnection(peerConnectionConfig)
|
peerConnection, err := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i), webrtc.WithSettingEngine(se)).NewPeerConnection(peerConnectionConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -146,7 +163,7 @@ func (s *StreamStore) Add(streamKey string, sd *webrtc.SessionDescription) (*web
|
|||||||
// to connected peers
|
// to connected peers
|
||||||
peerConnection.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
peerConnection.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
||||||
// Create a local track, all our SFU clients will be fed via this track
|
// Create a local track, all our SFU clients will be fed via this track
|
||||||
slog.Info("Got track!", "type", remoteTrack.Codec().MimeType, "id", remoteTrack.ID())
|
slog.Info("Got track.", "stream_key", streamKey, "type", remoteTrack.Codec().MimeType, "id", remoteTrack.ID())
|
||||||
localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, "video", "pion")
|
localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, "video", "pion")
|
||||||
if newTrackErr != nil {
|
if newTrackErr != nil {
|
||||||
panic(newTrackErr)
|
panic(newTrackErr)
|
||||||
@ -181,7 +198,6 @@ func (s *StreamStore) Add(streamKey string, sd *webrtc.SessionDescription) (*web
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create answer
|
// Create answer
|
||||||
answer, err := peerConnection.CreateAnswer(nil)
|
answer, err := peerConnection.CreateAnswer(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -189,7 +205,7 @@ func (s *StreamStore) Add(streamKey string, sd *webrtc.SessionDescription) (*web
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create channel that is blocked until ICE Gathering is complete
|
// Create channel that is blocked until ICE Gathering is complete
|
||||||
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
|
// gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
|
||||||
|
|
||||||
// Sets the LocalDescription, and starts our UDP listeners
|
// Sets the LocalDescription, and starts our UDP listeners
|
||||||
err = peerConnection.SetLocalDescription(answer)
|
err = peerConnection.SetLocalDescription(answer)
|
||||||
@ -200,14 +216,37 @@ func (s *StreamStore) Add(streamKey string, sd *webrtc.SessionDescription) (*web
|
|||||||
// Block until ICE Gathering is complete, disabling trickle ICE
|
// Block until ICE Gathering is complete, disabling trickle ICE
|
||||||
// we do this because we only can exchange one signaling message
|
// we do this because we only can exchange one signaling message
|
||||||
// in a production application you should exchange ICE Candidates via OnICECandidate
|
// in a production application you should exchange ICE Candidates via OnICECandidate
|
||||||
<-gatherComplete
|
// answerChan <- &answer
|
||||||
slog.Info("ICE Gathering complete.", "answer", answer)
|
|
||||||
answerChan <- &answer
|
|
||||||
|
|
||||||
|
peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) {
|
||||||
|
slog.Info("ICE state changed.", "stream_key", streamKey, "ice_state", is)
|
||||||
|
})
|
||||||
|
|
||||||
|
gatherComplete := make(chan struct{})
|
||||||
|
peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
|
||||||
|
if i == nil {
|
||||||
|
gatherComplete <- struct{}{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("Got ICE Candidate",
|
||||||
|
"addr", i.Address,
|
||||||
|
"port", i.Port,
|
||||||
|
"related_addr", i.RelatedAddress,
|
||||||
|
"related_port", i.RelatedPort)
|
||||||
|
if err := peerConnection.AddICECandidate(i.ToJSON()); err != nil {
|
||||||
|
slog.Info("Error adding ICE Candidate.", "error", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
<-gatherComplete
|
||||||
|
slog.Info("ICE Gathering complete.", "stream_key", streamKey, "ice_state", peerConnection.ICEConnectionState())
|
||||||
|
|
||||||
|
answerChan <- peerConnection.CurrentLocalDescription()
|
||||||
s.Streams[streamKey] = stream
|
s.Streams[streamKey] = stream
|
||||||
slog.Info("Added stream.", "stream_key", streamKey)
|
slog.Info("Added stream.", "stream_key", streamKey, "answer", answer)
|
||||||
}()
|
}()
|
||||||
answer := <-answerChan
|
answer := <-answerChan
|
||||||
|
|
||||||
return answer, nil
|
return answer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user