├── .gitignore ├── webrtc ├── rtpcodeckind.go ├── pkg │ └── media │ │ ├── sample.go │ │ ├── ivfreader │ │ └── ivfreader.go │ │ └── track.go ├── rtpparameters.go ├── go.mod ├── rtpcodeccapability.go ├── rtptransceiverdirection.go ├── settingengine.go ├── rtptransceiver.go ├── peerconnection.go └── track.go ├── rtpengine ├── go.mod ├── interceptor.go ├── io.go ├── jitterbuffer │ └── jitterbuffer.go └── nackhandler │ └── nackhandler.go ├── mediadevices ├── go.mod └── mediadevices.go ├── README.md ├── examples ├── go.mod ├── save-to-disk │ └── main.go ├── play-from-disk │ └── main.go ├── fanout │ └── main.go ├── play-from-disk-dynamic │ └── main.go ├── portable-getusermedia │ └── main.go └── README.md ├── LICENSE └── .github └── workflows └── ci.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | go.sum 2 | -------------------------------------------------------------------------------- /webrtc/rtpcodeckind.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | type RTPCodecKind int 4 | 5 | const ( 6 | RTPCodecKindVideo RTPCodecKind = iota + 1 7 | RTPCodecKindAudio 8 | ) 9 | -------------------------------------------------------------------------------- /rtpengine/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pion/webrtc-v3-design/rtpengine 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/pion/rtcp v1.2.3 7 | github.com/pion/rtp v1.6.0 8 | ) 9 | -------------------------------------------------------------------------------- /mediadevices/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pion/webrtc-v3-design/mediadevices 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/pion/mediadevices v0.0.0-20200929170321-f3e3dc9589ca 7 | github.com/pion/webrtc-v3-design/webrtc v0.0.0-20200927053335-2e0f9748bf9e 8 | ) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pion/webrtc v3 API design documents 2 | 3 | **!!EXPERIMENTAL!!** 4 | 5 | This repository contains design of the pion/webrtc v3 API. 6 | New API is represented as Go interfaces with GoDoc comments. 7 | 8 | Feel free to open PRs to propose and discuss adding new APIs and breaking existing APIs! 9 | -------------------------------------------------------------------------------- /webrtc/pkg/media/sample.go: -------------------------------------------------------------------------------- 1 | // Package media provides media writer and filters 2 | package media 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | // A Sample contains encoded media and the number of samples in that media 9 | type Sample struct { 10 | Data []byte 11 | Timestamp time.Time 12 | Duration time.Duration 13 | } 14 | -------------------------------------------------------------------------------- /webrtc/rtpparameters.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | // RTPParameters represents RTCRtpParameters which contains information about 4 | // how the RTC data is to be encoded/decoded. 5 | // 6 | // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSendParameters 7 | type RTPParameters struct { 8 | SSRC SSRC 9 | SelectedCodec *RTPCodecCapability 10 | Codecs []RTPCodecCapability 11 | } 12 | -------------------------------------------------------------------------------- /webrtc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pion/webrtc-v3-design/webrtc 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/pion/rtcp v1.2.4 7 | github.com/pion/rtp v1.6.1 8 | github.com/pion/srtp v1.5.2 9 | github.com/pion/webrtc-v3-design/rtpengine v0.0.0-20200905201212-4337232b67dc 10 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 11 | ) 12 | 13 | replace github.com/pion/webrtc-v3-design/rtpengine => ../rtpengine 14 | -------------------------------------------------------------------------------- /webrtc/rtpcodeccapability.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | // PayloadType is used to indicate the Payload format 4 | // PayloadType type is used to identify an individual codec 5 | // they are dynamic and determined by the offerer 6 | type PayloadType uint8 7 | 8 | // RTPCodecCapability is the configuration for one RTPCodec 9 | type RTPCodecCapability struct { 10 | PreferredPayloadType PayloadType 11 | MimeType string 12 | ClockRate int 13 | Channels int 14 | SdpFmtpLine string 15 | } 16 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pion/webrtc-v3-design/examples 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/pion/mediadevices v0.0.0-20200929170321-f3e3dc9589ca 7 | github.com/pion/webrtc-v3-design/mediadevices v0.0.0-20200905201212-4337232b67dc 8 | github.com/pion/webrtc-v3-design/webrtc v0.0.0-20200927053335-2e0f9748bf9e 9 | ) 10 | 11 | replace ( 12 | github.com/pion/webrtc-v3-design/mediadevices => ../mediadevices 13 | github.com/pion/webrtc-v3-design/rtpengine => ../rtpengine 14 | github.com/pion/webrtc-v3-design/webrtc => ../webrtc 15 | ) 16 | -------------------------------------------------------------------------------- /rtpengine/interceptor.go: -------------------------------------------------------------------------------- 1 | package rtpengine 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // WriteInterceptor processes outbound RTP stream. 8 | // For example, WriteInterceptor may implements congestion control and packet retransmission. 9 | type WriteInterceptor interface { 10 | Intercept(context.Context, Writer) Writer 11 | } 12 | 13 | // ReadInterceptor processes received RTP stream. 14 | // For example, ReadInterceptor may implements RTP jitter buffer, RTCP report sender, 15 | // and packet retransmission request. 16 | type ReadInterceptor interface { 17 | Intercept(context.Context, Reader) Reader 18 | } 19 | -------------------------------------------------------------------------------- /examples/save-to-disk/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pion/webrtc-v3-design/webrtc" 5 | ) 6 | 7 | func main() { 8 | var s webrtc.SettingEngine 9 | 10 | // During Offer/Answer exchange the only codec we support will be VP8 11 | // If the remote doesn't support VP8 signaling will fail 12 | _ = s.SetEncodings([]*webrtc.RTPCodecCapability{ 13 | { 14 | MimeType: "video/vp8", // Should we make this a enum? 15 | ClockRate: 90000, // Sholud we drop from API and just assume? 16 | }, 17 | }) 18 | 19 | peerConnection, _ := s.NewPeerConnection(webrtc.Configuration{}) 20 | 21 | peerConnection.OnTrack(func(track webrtc.TrackRemote, receiver webrtc.RTPReceiver) { 22 | // Read RTP Packets (Keep the same API as before) 23 | }) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /webrtc/pkg/media/ivfreader/ivfreader.go: -------------------------------------------------------------------------------- 1 | // Package ivfreader implements IVF media container reader 2 | package ivfreader 3 | 4 | import ( 5 | "io" 6 | ) 7 | 8 | // IVFFileHeader 32-byte header for IVF files 9 | type IVFFileHeader struct{} 10 | 11 | // IVFFrameHeader 12-byte header for IVF frames 12 | type IVFFrameHeader struct{} 13 | 14 | // IVFReader is used to read IVF files and return frame payloads 15 | type IVFReader struct{} 16 | 17 | // NewWith returns a new IVF reader and IVF file header 18 | func NewWith(in io.Reader) (*IVFReader, *IVFFileHeader, error) { return nil, nil, nil } 19 | 20 | // ParseNextFrame reads from stream and returns IVF frame payload, header, 21 | func (i *IVFReader) ParseNextFrame() ([]byte, *IVFFrameHeader, error) { return nil, nil, nil } 22 | -------------------------------------------------------------------------------- /webrtc/rtptransceiverdirection.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | // RTPTransceiverDirection indicates the direction of the RTPTransceiver. 4 | type RTPTransceiverDirection int 5 | 6 | const ( 7 | // RTPTransceiverDirectionSendrecv indicates the RTPSender will offer 8 | // to send RTP and RTPReceiver the will offer to receive RTP. 9 | RTPTransceiverDirectionSendrecv RTPTransceiverDirection = iota + 1 10 | 11 | // RTPTransceiverDirectionSendonly indicates the RTPSender will offer 12 | // to send RTP. 13 | RTPTransceiverDirectionSendonly 14 | 15 | // RTPTransceiverDirectionRecvonly indicates the RTPReceiver the will 16 | // offer to receive RTP. 17 | RTPTransceiverDirectionRecvonly 18 | 19 | // RTPTransceiverDirectionInactive indicates the RTPSender won't offer 20 | // to send RTP and RTPReceiver the won't offer to receive RTP. 21 | RTPTransceiverDirectionInactive 22 | ) 23 | -------------------------------------------------------------------------------- /webrtc/settingengine.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | type SettingEngine struct{} 4 | 5 | // SetEncodings configures the codecs the newly created PeerConnection is willing to 6 | // send and receive. If nothing is configured it will support everything that Pion WebRTC 7 | // implements packetization for. 8 | func (s *SettingEngine) SetEncodings([]*RTPCodecCapability) error { return nil } 9 | 10 | // SetTrackLocalAdapters registers TrackLocalAdapters. 11 | // TrackLocalRTP-RTPSenderPassthrough is registered by default. 12 | // func (s *SettingEngine) SetTrackLocalAdapters([]TrackLocalAdapter) error { return nil } 13 | 14 | // SetTrackRemoteAdapters registers TrackRemoteAdapters. 15 | // TrackRemoteRTP-RTPReceiverPassthrough is registered by default. 16 | // func (s *SettingEngine) SetTrackRemoteAdapters([]TrackRemoteAdapter) error { return nil } 17 | 18 | // NewPeerConnection creates a NewPeerConnection 19 | func (s *SettingEngine) NewPeerConnection(Configuration) (PeerConnection, error) { 20 | p := &peerConnection{} 21 | return p, nil 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rtpengine/io.go: -------------------------------------------------------------------------------- 1 | package rtpengine 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pion/rtcp" 7 | "github.com/pion/rtp" 8 | ) 9 | 10 | // Reader is an interface to handle incoming RTP stream. 11 | type Reader interface { 12 | FeedbackWriter 13 | ReadRTP(context.Context) (*rtp.Packet, error) 14 | } 15 | 16 | // Writer is an interface to handle outgoing RTP stream. 17 | type Writer interface { 18 | FeedbackReader 19 | WriteRTP(context.Context, *rtp.Packet) error 20 | } 21 | 22 | // Copy rtp.Packet from Reader to Writer. 23 | func Copy(context.Context, Writer, Reader) error { 24 | panic("unimplemented") 25 | } 26 | 27 | // FeedbackReader is an interface to read feedback RTCP packets. 28 | type FeedbackReader interface { 29 | ReadRTCP(context.Context) (rtcp.Packet, error) 30 | } 31 | 32 | // FeedbackWriter is an interface to write feedback RTCP packets. 33 | type FeedbackWriter interface { 34 | WriteRTCP(context.Context, rtcp.Packet) error 35 | } 36 | 37 | // Copy rtcp.Packet from FeedbackReader to FeedbackWriter. 38 | func CopyFeedback(context.Context, FeedbackWriter, FeedbackReader) error { 39 | panic("unimplemented") 40 | } 41 | -------------------------------------------------------------------------------- /rtpengine/jitterbuffer/jitterbuffer.go: -------------------------------------------------------------------------------- 1 | package jitterbuffer 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pion/rtcp" 7 | "github.com/pion/rtp" 8 | "github.com/pion/webrtc-v3-design/rtpengine" 9 | ) 10 | 11 | // JitterBuffer is a stub implementation of Interceptor for jitter buffering. 12 | type JitterBuffer struct { 13 | maxLate int 14 | } 15 | 16 | // Intercept implements ReadInterceptor. 17 | func (j *JitterBuffer) Intercept(ctx context.Context, r rtpengine.Reader) rtpengine.Reader { 18 | return &jitterBufferReader{r} 19 | } 20 | 21 | type jitterBufferReader struct { 22 | in rtpengine.Reader 23 | } 24 | 25 | func (j *jitterBufferReader) WriteRTCP(ctx context.Context, p rtcp.Packet) error { 26 | // This interceptor doesn't use RTCP packet. 27 | return j.in.WriteRTCP(ctx, p) 28 | } 29 | 30 | func (j *jitterBufferReader) ReadRTP(ctx context.Context) (*rtp.Packet, error) { 31 | p, err := j.in.ReadRTP(ctx) 32 | if err != nil { 33 | return nil, err 34 | } 35 | // Do jitter buffering here. 36 | 37 | return p, nil 38 | } 39 | 40 | // Assert JitterBuffer implements rtpengine.ReadInterceptor. 41 | var _jitterBuffer rtpengine.ReadInterceptor = &JitterBuffer{} 42 | -------------------------------------------------------------------------------- /webrtc/rtptransceiver.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | type SSRC uint32 4 | 5 | // RTPSender represents RTCRtpSender. 6 | type RTPSender interface { 7 | // ReplaceTrackLocal registers given TrackLocal as a source of RTP packets. 8 | ReplaceTrack(TrackLocal) error 9 | 10 | // Track returns currently bound TrackLocal. 11 | Track() TrackLocal 12 | 13 | // Parameters returns information about how the data is to be encoded. 14 | Parameters() RTPParameters 15 | 16 | // SetParameters sets information about how the data is to be encoded. 17 | // This will be called by PeerConnection according to the result of 18 | // SDP based negotiation. 19 | SetParameters(RTPParameters) error 20 | } 21 | 22 | // RTPReceiver represents RTCRtpReceiver. 23 | type RTPReceiver interface { 24 | // Track returns associated TrackRemote. 25 | Track() TrackRemote 26 | 27 | // Parameters returns information about how the data is to be decoded. 28 | Parameters() RTPParameters 29 | } 30 | 31 | // RTPTransceiver represents RTCRtpTransceiver. 32 | // It represents a combination of an RTCRtpSender and an RTCRtpReceiver that share a common mid. 33 | // 34 | // ref: https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface 35 | type RTPTransceiver interface { 36 | RTPSender() RTPSender 37 | RTPReceiver() RTPReceiver 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build-linux: 12 | runs-on: ubuntu-latest 13 | name: build 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Setup Go 18 | uses: actions/setup-go@v1 19 | with: 20 | go-version: 1.14 21 | - name: Install deps 22 | run: | 23 | sudo apt-get update -qq 24 | sudo apt-get install libvpx-dev 25 | - name: Setup env 26 | run: | 27 | echo "::set-env name=GOPATH::$(go env GOPATH)" 28 | echo "::add-path::$(go env GOPATH)/bin" 29 | - name: Setup golint 30 | run: GO111MODULE=off go get -u golang.org/x/lint/golint 31 | - name: Go build 32 | run: | 33 | echo \ 34 | examples \ 35 | mediadevices \ 36 | rtpengine \ 37 | webrtc \ 38 | | xargs -n1 echo | while read pkgdir 39 | do 40 | ( 41 | echo "Building ${pkgdir}" 42 | cd ${pkgdir} 43 | go build ./... 44 | ) 45 | done 46 | - name: Go lint 47 | run: golint ./... 48 | -------------------------------------------------------------------------------- /examples/play-from-disk/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/pion/webrtc-v3-design/webrtc" 8 | "github.com/pion/webrtc-v3-design/webrtc/pkg/media" 9 | "github.com/pion/webrtc-v3-design/webrtc/pkg/media/ivfreader" 10 | ) 11 | 12 | func main() { 13 | var s webrtc.SettingEngine 14 | 15 | // During Offer/Answer exchange the only codec we support will be VP8 16 | // If the remote doesn't support VP8 signaling will fail 17 | _ = s.SetEncodings([]*webrtc.RTPCodecCapability{ 18 | { 19 | MimeType: "video/vp8", // Should we make this a enum? 20 | ClockRate: 90000, // Sholud we drop from API and just assume? 21 | }, 22 | }) 23 | 24 | peerConnection, _ := s.NewPeerConnection(webrtc.Configuration{}) 25 | 26 | track, _ := media.NewLocalTrackStaticSample(webrtc.RTPCodecCapability{ 27 | MimeType: "video/vp8", // Should we make this a enum? 28 | ClockRate: 90000, // Sholud we drop from API and just assume? 29 | }, "video", "desktop-capture") 30 | 31 | _, _ = peerConnection.AddTransceiverFromTrack(track, nil) 32 | 33 | file, _ := os.Open("video.ivf") 34 | ivf, _, _ := ivfreader.NewWith(file) 35 | 36 | for i := 0; i <= 10; i++ { 37 | frame, _, _ := ivf.ParseNextFrame() 38 | _ = track.WriteSample(media.Sample{Data: frame, Duration: time.Second}) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/fanout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pion/webrtc-v3-design/webrtc" 5 | "github.com/pion/webrtc-v3-design/webrtc/pkg/media" 6 | ) 7 | 8 | func main() { 9 | var s webrtc.SettingEngine 10 | 11 | // During Offer/Answer exchange the only codec we support will be VP8 12 | // If the remote doesn't support VP8 signaling will fail 13 | _ = s.SetEncodings([]*webrtc.RTPCodecCapability{ 14 | { 15 | MimeType: "video/vp8", // Should we make this a enum? 16 | ClockRate: 90000, // Sholud we drop from API and just assume? 17 | }, 18 | }) 19 | 20 | uploadPeerConnection, _ := s.NewPeerConnection(webrtc.Configuration{}) 21 | 22 | outboundTrack, _ := media.NewLocalTrackStaticRTP(webrtc.RTPCodecCapability{ 23 | MimeType: "video/vp8", // Should we make this a enum? 24 | ClockRate: 90000, // Sholud we drop from API and just assume? 25 | }, "video", "desktop-capture") 26 | 27 | uploadPeerConnection.OnTrack(func(track webrtc.TrackRemote, receiver webrtc.RTPReceiver) { 28 | for { 29 | pkt, _ := track.ReadRTP() 30 | 31 | // WriteRTP sets the proper SSRC/PayloadType when pushing out to each PeerConnection 32 | _ = outboundTrack.WriteRTP(pkt) 33 | } 34 | 35 | }) 36 | 37 | for i := 0; i <= 10; i++ { 38 | viewerPeerConnection, _ := s.NewPeerConnection(webrtc.Configuration{}) 39 | _, _ = viewerPeerConnection.AddTransceiverFromTrack(outboundTrack, nil) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mediadevices/mediadevices.go: -------------------------------------------------------------------------------- 1 | package mediadevices 2 | 3 | import ( 4 | mediadevices2 "github.com/pion/mediadevices" 5 | "github.com/pion/webrtc-v3-design/webrtc" 6 | ) 7 | 8 | // MediaDevices is an interface that's defined on https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices 9 | type MediaDevices interface { 10 | GetDisplayMedia(constraints mediadevices2.MediaStreamConstraints) (MediaStream, error) 11 | GetUserMedia(constraints mediadevices2.MediaStreamConstraints) (MediaStream, error) 12 | EnumerateDevices() []mediadevices2.MediaDeviceInfo 13 | } 14 | 15 | // NewMediaDevices creates mediadevices interface. 16 | func NewMediaDevices(opts ...mediadevices2.MediaDevicesOption) MediaDevices { 17 | panic("not implementd") 18 | } 19 | 20 | // MediaStream is an interface that represents a collection of existing tracks. 21 | type MediaStream interface { 22 | // GetAudioTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks 23 | GetAudioTracks() []webrtc.TrackLocal 24 | // GetVideoTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks 25 | GetVideoTracks() []webrtc.TrackLocal 26 | // GetTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks 27 | GetTracks() []webrtc.TrackLocal 28 | // AddTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack 29 | AddTrack(t webrtc.TrackLocal) 30 | // RemoveTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack 31 | RemoveTrack(t webrtc.TrackLocal) 32 | } 33 | -------------------------------------------------------------------------------- /rtpengine/nackhandler/nackhandler.go: -------------------------------------------------------------------------------- 1 | package nackhandler 2 | 3 | import ( 4 | "context" 5 | "io" 6 | 7 | "github.com/pion/rtcp" 8 | "github.com/pion/rtp" 9 | "github.com/pion/webrtc-v3-design/rtpengine" 10 | ) 11 | 12 | // NACKHandler is a stub implementation of Interceptor for NACK handler. 13 | type NACKHandler struct { 14 | } 15 | 16 | // Intercept implements ReadInterceptor. 17 | func (n *NACKHandler) Intercept(ctx context.Context, w rtpengine.Writer) rtpengine.Writer { 18 | chRTCP := make(chan rtcp.Packet) 19 | go func() { 20 | for { 21 | p, err := w.ReadRTCP(ctx) 22 | if err != nil { 23 | return 24 | } 25 | // If received RTCP packet is NACK and requested buffered packet, 26 | // resend the packet by w.WriteRTP(). 27 | 28 | // Passthrough the RTCP packet to the next writer. 29 | select { 30 | case chRTCP <- p: 31 | default: 32 | } 33 | } 34 | }() 35 | return &nackHandlerWriter{out: w, chRTCP: chRTCP} 36 | } 37 | 38 | type nackHandlerWriter struct { 39 | out rtpengine.Writer 40 | chRTCP chan rtcp.Packet 41 | } 42 | 43 | func (n *nackHandlerWriter) WriteRTP(ctx context.Context, p *rtp.Packet) error { 44 | // Passthrough the packet and buffer some duration of the packets. 45 | return n.out.WriteRTP(ctx, p) 46 | } 47 | 48 | func (n *nackHandlerWriter) ReadRTCP(ctx context.Context) (rtcp.Packet, error) { 49 | select { 50 | case p, ok := <-n.chRTCP: 51 | if !ok { 52 | return nil, io.EOF 53 | } 54 | return p, nil 55 | case <-ctx.Done(): 56 | return nil, ctx.Err() 57 | } 58 | } 59 | 60 | // Assert NACKHandler implements rtpengine.WriteInterceptor. 61 | var _nackHandler rtpengine.WriteInterceptor = &NACKHandler{} 62 | -------------------------------------------------------------------------------- /examples/play-from-disk-dynamic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/pion/webrtc-v3-design/webrtc" 7 | ) 8 | 9 | // dynamicTrackLocal is a track in which the codec is determined by what 10 | // codecs the remote supports 11 | type dynamicTrackLocal struct { 12 | ssrc webrtc.SSRC 13 | payloadType webrtc.PayloadType 14 | writeStream webrtc.TrackLocalWriter 15 | } 16 | 17 | // Users can define their own Track types and decide codecs as they wish 18 | func (d *dynamicTrackLocal) Bind(t webrtc.TrackLocalContext) error { 19 | writeFileToStream := func(fileName string) { 20 | // loop file and write to the writeStream 21 | } 22 | 23 | for _, codec := range t.Parameters().Codecs { 24 | if codec.MimeType == "h264" || codec.MimeType == "vp8" { 25 | d.ssrc = t.Parameters().SSRC 26 | d.payloadType = codec.PreferredPayloadType 27 | d.writeStream = t.WriteStream() 28 | go writeFileToStream("out." + codec.MimeType) 29 | return nil 30 | } 31 | } 32 | 33 | return errors.New("remote Peer supports neither H264 or VP8") 34 | } 35 | func (d *dynamicTrackLocal) Unbind(c webrtc.TrackLocalContext) error { return nil } 36 | func (d *dynamicTrackLocal) ID() string { return "video" } 37 | func (d *dynamicTrackLocal) StreamID() string { return "desktop-capture" } 38 | 39 | func main() { 40 | var s webrtc.SettingEngine 41 | 42 | // We support both VP8 and H264 43 | _ = s.SetEncodings([]*webrtc.RTPCodecCapability{ 44 | { 45 | MimeType: "video/vp8", 46 | ClockRate: 90000, 47 | }, 48 | { 49 | MimeType: "video/h264", 50 | ClockRate: 90000, 51 | }, 52 | }) 53 | 54 | pc, _ := s.NewPeerConnection(webrtc.Configuration{}) 55 | track := &dynamicTrackLocal{} 56 | 57 | _, _ = pc.AddTransceiverFromTrack(track, 58 | &webrtc.RTPTransceiverInit{ 59 | Direction: webrtc.RTPTransceiverDirectionSendonly, 60 | }, 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /examples/portable-getusermedia/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | mediadevices2 "github.com/pion/mediadevices" 5 | "github.com/pion/mediadevices/pkg/codec" 6 | "github.com/pion/mediadevices/pkg/codec/openh264" 7 | "github.com/pion/mediadevices/pkg/codec/vpx" 8 | "github.com/pion/mediadevices/pkg/prop" 9 | "github.com/pion/webrtc-v3-design/mediadevices" 10 | "github.com/pion/webrtc-v3-design/webrtc" 11 | 12 | // Note: If you don't have a camera or microphone or your adapters are not supported, 13 | // you can always swap your adapters with our dummy adapters below. 14 | // _ "github.com/pion/mediadevices/pkg/driver/videotest" 15 | _ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter 16 | ) 17 | 18 | func main() { 19 | var setting webrtc.SettingEngine 20 | 21 | openh264Params, _ := openh264.NewParams() 22 | vp8Params, _ := vpx.NewVP8Params() 23 | 24 | // Assume that we have configured the api and have proper config 25 | pc, _ := setting.NewPeerConnection(webrtc.Configuration{}) 26 | 27 | md := mediadevices.NewMediaDevices() 28 | mediaStream, _ := md.GetUserMedia(mediadevices2.MediaStreamConstraints{ 29 | Video: func(constraint *mediadevices2.MediaTrackConstraints) { 30 | constraint.Width = prop.Int(600) 31 | constraint.Height = prop.Int(400) 32 | constraint.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&openh264Params, &vp8Params} 33 | }, 34 | }) 35 | 36 | for _, mediaTrack := range mediaStream.GetTracks() { 37 | // rtpTracker.Track will create TrackLocalRTP, which later can be used for pulling video/audio frames. 38 | pc.AddTransceiverFromTrack(mediaTrack, 39 | &webrtc.RTPTransceiverInit{ 40 | Direction: webrtc.RTPTransceiverDirectionSendonly, 41 | }, 42 | ) 43 | } 44 | 45 | // peerconnection should negotiate with the other peer 46 | // peerconnection should set the negotiated codec with LocalRTPTrack.setParameters() 47 | 48 | // mediadevices then will set the codec and attach the encoder to the pipeline. PeerConnection now can start 49 | // pulling encoded frames from mediadevices. 50 | // 51 | // Question: How are we going to handle errors from the encoder, e.g. invalid resolutions, invalid sample rate, etc.? 52 | // Should we return the error when the user calls SetRemoteDescription? 53 | } 54 | -------------------------------------------------------------------------------- /webrtc/peerconnection.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | // A Configuration defines how peer-to-peer communication via PeerConnection 4 | // is established or re-established. 5 | // Configurations may be set up once and reused across multiple connections. 6 | // Configurations are treated as readonly. As long as they are unmodified, 7 | // they are safe for concurrent use. 8 | type Configuration struct{} 9 | 10 | // NewPeerConnection creates a new WebRTC PeerConnection. This uses a uninitialized 11 | // SettingEngine. If you wish to access any Pion specific behaviors you should create a 12 | // PeerConnection using `NewPeerConnection` method of the SettingEngine 13 | func NewPeerConnection(c Configuration) (PeerConnection, error) { 14 | var s SettingEngine 15 | return s.NewPeerConnection(c) 16 | } 17 | 18 | // PeerConnection represents RTCPeerConnection. 19 | type PeerConnection interface { 20 | // AddTransceiverFromTrack creates a new RTPTransceiver from TrackLocal 21 | // and register it to the PeerConnection. 22 | // Pass nil as a second argument to use default setting. 23 | // Returned RTPTransceiver will be sendrecv by default. 24 | // 25 | // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTransceiver 26 | AddTransceiverFromTrack(TrackLocal, *RTPTransceiverInit) (RTPTransceiver, error) 27 | 28 | // AddTransceiverFromKind creates a new RTPTransceiver from RTPCodecType 29 | // and register it to the PeerConnection. 30 | // Pass nil as a second argument to use default setting. 31 | // Returned RTPTransceiver will be sendrecv by default. 32 | // 33 | // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addTransceiver 34 | AddTransceiverFromKind(RTPCodecKind, *RTPTransceiverInit) (RTPTransceiver, error) 35 | 36 | // OnTrack handles an incoming media feed. 37 | // 38 | // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack 39 | OnTrack(func(TrackRemote, RTPReceiver)) 40 | } 41 | 42 | type peerConnection struct{} 43 | 44 | func (p *peerConnection) AddTransceiverFromTrack(TrackLocal, *RTPTransceiverInit) (RTPTransceiver, error) { 45 | return nil, nil 46 | } 47 | func (p *peerConnection) AddTransceiverFromKind(RTPCodecKind, *RTPTransceiverInit) (RTPTransceiver, error) { 48 | return nil, nil 49 | } 50 | func (p *peerConnection) OnTrack(func(TrackRemote, RTPReceiver)) {} 51 | 52 | // RTPTransceiverInit represents RTCRtpTransceiverInit dictionary. 53 | type RTPTransceiverInit struct { 54 | Direction RTPTransceiverDirection 55 | SendEncodings []RTPParameters 56 | } 57 | -------------------------------------------------------------------------------- /webrtc/track.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/pion/rtp" 7 | ) 8 | 9 | var ( 10 | // ErrIncompatible is an internal error returned from TrackLocal/RemoteAdapter. 11 | ErrIncompatible = errors.New("incompatible pair of Track and RTPSender/Receiver") 12 | ) 13 | 14 | // TrackLocalContext is the Context passed when a TrackLocal has been Binded/Unbinded from a PeerConnection 15 | type TrackLocalContext struct{} 16 | 17 | // Parameters returns the negotiated Parameters. These are the codecs supported by both 18 | // PeerConnections and the SSRC/PayloadTypes 19 | func (t *TrackLocalContext) Parameters() RTPParameters { 20 | return RTPParameters{} 21 | } 22 | 23 | // TrackLocalWriter is the Writer for outbound RTP Packets 24 | type TrackLocalWriter interface { 25 | // WriteRTP encrypts a RTP packet and writes to the connection 26 | WriteRTP(header *rtp.Header, payload []byte) (int, error) 27 | 28 | // Write encrypts and writes a full RTP packet 29 | Write(b []byte) (int, error) 30 | } 31 | 32 | // WriteStream returns the WriteStream for this TrackLocal. The implementer writes the outbound 33 | // media packets to it 34 | func (t *TrackLocalContext) WriteStream() TrackLocalWriter { 35 | return nil 36 | } 37 | 38 | // TrackLocal is an interface that controls how the user can send media 39 | // The user can provide their own TrackLocal implementatiosn, or use 40 | // the implementations in pkg/media 41 | type TrackLocal interface { 42 | // Bind should implement the way how the media data flows from the Track to the PeerConnection 43 | // This will be called internally after signaling is complete and the list of available 44 | // codecs has been determined 45 | Bind(TrackLocalContext) error 46 | 47 | // Unbind should implement the teardown logic when the track is no longer needed. This happens 48 | // because a track has been stopped. 49 | Unbind(TrackLocalContext) error 50 | 51 | // ID is the unique identifier for this Track. This should be unique for the 52 | // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' 53 | // and StreamID would be 'desktop' or 'webcam' 54 | ID() string 55 | 56 | // StreamID is the group this track belongs too. This must be unique 57 | StreamID() string 58 | } 59 | 60 | // Track represents a single media track from a remote peer 61 | type TrackRemote struct{} 62 | 63 | // Read reads data from the track. 64 | func (t *TrackRemote) Read(b []byte) (n int, err error) { return } 65 | 66 | // ReadRTP is a convenience method that wraps Read and unmarshals for you 67 | func (t *TrackRemote) ReadRTP() (*rtp.Packet, error) { return nil, nil } 68 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | #### [Play Files from Disk](examples/play-from-disk) 2 | Example of playing file from disk. 3 | 4 | * User declares codec they are reading from disk 5 | * On each negotiated PeerConnection they are notified if the receiver can accept what they are offering 6 | * Read packets from disk and send until completed 7 | 8 | #### [Play Files from Disk Dynamic](examples/play-from-disk-dynamic) 9 | Example of playing a file from a disk and condtionally selecting codec. 10 | 11 | * User declares codecs that are available 12 | * On each negotiated PeerConnection a callback is fired with the codecs that are supported 13 | * Read packets from disk and send until completed 14 | 15 | #### [Save files to Disk](examples/save-to-disk) 16 | Example of saving user input to disk, 17 | 18 | * User declares codecs they are saving to disk 19 | * On each negotiated PeerConnection they are notified if the receiver can send what they are offering 20 | * In OnTrack user Read packets from webrtc.RTPReceiver and save to disk 21 | 22 | #### [Fan out WebRTC Input](examples/fanout) 23 | Example of simple SFU 24 | 25 | * User declares codec they wish to fan out 26 | * Uploader connects and we assert that our chosen codec is supported, if so we start reading 27 | * Downloader connects and we assert our chosen codec is supported 28 | * We send our video packets and the developer doesn't have to worry about managing PayloadTypes/SSRCes etc.. 29 | 30 | We can also implement some basic receiver feedback. These need to be supported, but are not mandatory 31 | * Determine the lowest support bitrate across all receivers, and forward it back to the sender 32 | * Forward NACK/PLI messages from each receiver back to the sender 33 | * Simulcast (see below) 34 | 35 | #### [Simulcast Receive](examples/simulcast-send) 36 | A user should be able to receive multiple feeds for a single Track 37 | 38 | **TODO** (Works in master) 39 | 40 | #### [Error Resilience Send](examples/error-resilience-send) 41 | A user sending video should be able to receive NACKs and respond to them 42 | 43 | **TODO** 44 | 45 | #### [Error Resilience Receive](examples/error-resilience-receive) 46 | A user receiving video should be able to send NACKs and receive retransmissions 47 | 48 | **TODO** 49 | 50 | #### [Congestion Control Send](examples/congestion-control-send) 51 | A user sending video should be able to receive REMB/Receiver Reports/TWCC and adjust bitrate 52 | 53 | **TODO** 54 | 55 | #### [Congestion Control Receive](examples/congestion-control-receive) 56 | A user receiving video should be able to send REMB/Receiver Reports/TWCC 57 | 58 | **TODO** 59 | 60 | #### [Simulcast Send](examples/simulcast-send) 61 | A user should be able to send simulcast to another Pion instance. This isn't supported to the browser. 62 | 63 | **TODO** 64 | 65 | #### [Portable getUserMedia](examples/portable-getusermedia) 66 | Users should be able to call getUserMedia and have it work in both their Go and WASM code. 67 | 68 | Everything should be behind platform flags in the `mediadevices` repo so user doesn't need to write platform specific 69 | code. 70 | -------------------------------------------------------------------------------- /webrtc/pkg/media/track.go: -------------------------------------------------------------------------------- 1 | package media 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/pion/rtp" 7 | "github.com/pion/webrtc-v3-design/webrtc" 8 | ) 9 | 10 | var ( 11 | ErrUnsupportedCodec = errors.New("unsupported codec") 12 | ErrUnbindFailed = errors.New("failed to unbind Track from PeerConnection") 13 | ) 14 | 15 | // trackBinding is a single bind for a Track 16 | // Bind can be called multiple times, this stores the 17 | // result for a single bind call so that it can be used when writing 18 | type trackBinding struct { 19 | ssrc webrtc.SSRC 20 | payloadType webrtc.PayloadType 21 | writeStream webrtc.TrackLocalWriter 22 | } 23 | 24 | // staticLocalRTPTrack is a track that has a pre-set codec 25 | type localTrackStaticRTP struct { 26 | bindings []trackBinding 27 | codec webrtc.RTPCodecCapability 28 | id, streamId string 29 | } 30 | 31 | // NewStaticLocalTrack returns a TrackLocalRTP with a pre-set codec. 32 | func NewLocalTrackStaticRTP(c webrtc.RTPCodecCapability, id, streamId string) (*localTrackStaticRTP, error) { 33 | return &localTrackStaticRTP{ 34 | codec: c, 35 | bindings: []trackBinding{}, 36 | id: id, 37 | streamId: streamId, 38 | }, nil 39 | } 40 | 41 | // Bind is called by the PeerConnection after negotation is complete 42 | // This asserts that the code requested is supported by the remote peer. 43 | // If so it setups all the state (SSRC and PayloadType) to have a call 44 | func (s *localTrackStaticRTP) Bind(t webrtc.TrackLocalContext) error { 45 | for _, codec := range t.Parameters().Codecs { 46 | if codec.MimeType == s.codec.MimeType { 47 | s.bindings = append(s.bindings, trackBinding{ 48 | ssrc: t.Parameters().SSRC, 49 | payloadType: codec.PreferredPayloadType, 50 | writeStream: t.WriteStream(), 51 | }) 52 | return nil 53 | } 54 | } 55 | return ErrUnsupportedCodec 56 | } 57 | 58 | func (s *localTrackStaticRTP) Unbind(t webrtc.TrackLocalContext) error { 59 | for i := range s.bindings { 60 | if s.bindings[i].writeStream == t.WriteStream() { 61 | s.bindings[i] = s.bindings[len(s.bindings)-1] 62 | s.bindings = s.bindings[:len(s.bindings)-1] 63 | return nil 64 | } 65 | } 66 | 67 | return ErrUnbindFailed 68 | } 69 | 70 | func (s *localTrackStaticRTP) ID() string { return s.id } 71 | func (s *localTrackStaticRTP) StreamID() string { return s.streamId } 72 | 73 | // Loop each binding and set the proper SSRC/PayloadType before writing 74 | func (s *localTrackStaticRTP) WriteRTP(p *rtp.Packet) error { return nil } 75 | func (s *localTrackStaticRTP) Write(b []byte) (n int, err error) { return } 76 | 77 | type localTrackStaticSample struct { 78 | packetizer interface{} 79 | rtpTrack *localTrackStaticRTP 80 | } 81 | 82 | func NewLocalTrackStaticSample(c webrtc.RTPCodecCapability, id, streamId string) (*localTrackStaticSample, error) { 83 | rtpTrack, err := NewLocalTrackStaticRTP(c, id, streamId) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return &localTrackStaticSample{ 89 | packetizer: nil, 90 | rtpTrack: rtpTrack, 91 | }, nil 92 | } 93 | 94 | func (s *localTrackStaticSample) ID() string { return s.rtpTrack.ID() } 95 | func (s *localTrackStaticSample) StreamID() string { return s.rtpTrack.StreamID() } 96 | 97 | // Call rtpTrack.Bind + setup packetizer 98 | func (s *localTrackStaticSample) Bind(t webrtc.TrackLocalContext) error { return nil } 99 | func (s *localTrackStaticSample) Unbind(t webrtc.TrackLocalContext) error { return nil } 100 | 101 | func (s *localTrackStaticSample) WriteSample(samp Sample) error { return nil } 102 | --------------------------------------------------------------------------------