mirror of
https://github.com/pion/mediadevices.git
synced 2026-04-22 15:57:27 +08:00
@@ -1,3 +1,8 @@
|
||||
# mediadevices
|
||||
|
||||
Pure Go implementation of the [https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices](MediaDevices) API.
|
||||
Go implementation of the [https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices](MediaDevices) API.
|
||||
|
||||
## References
|
||||
|
||||
- https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs
|
||||
- https://tools.ietf.org/html/rfc7742
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blackjack/webcam"
|
||||
"github.com/pion/codec/h264"
|
||||
codecEngine "github.com/pion/codec"
|
||||
"github.com/pion/webrtc/v2"
|
||||
"github.com/pion/webrtc/v2/pkg/media"
|
||||
)
|
||||
|
||||
// Camera implementation using v4l2
|
||||
// Reference: https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/videodev.html#videodev
|
||||
type Camera struct {
|
||||
cam *webcam.Webcam
|
||||
track *webrtc.Track
|
||||
encoder codecEngine.Encoder
|
||||
opts Options
|
||||
}
|
||||
|
||||
func New(opts Options) (*Camera, error) {
|
||||
cam, err := webcam.Open("/dev/video0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
width := opts.Width
|
||||
height := opts.Height
|
||||
|
||||
var selectedFormat webcam.PixelFormat
|
||||
for v, k := range cam.GetSupportedFormats() {
|
||||
if strings.HasPrefix(k, "Motion-JPEG") {
|
||||
selectedFormat = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if selectedFormat == 0 {
|
||||
return nil, fmt.Errorf("Only Motion-JPEG supported")
|
||||
}
|
||||
|
||||
if _, _, _, err = cam.SetImageFormat(selectedFormat, uint32(width), uint32(height)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c *Camera
|
||||
switch opts.Codec {
|
||||
case webrtc.H264:
|
||||
// TODO: Replace "pion1" with device id instead
|
||||
track, err := opts.PC.NewTrack(webrtc.DefaultPayloadTypeH264, rand.Uint32(), "video", "pion1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Remove hardcoded values
|
||||
encoder, err := h264.NewEncoder(h264.Options{
|
||||
Width: width,
|
||||
Height: height,
|
||||
MaxFrameRate: 30,
|
||||
Bitrate: 1000000,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c = &Camera{
|
||||
cam: cam,
|
||||
track: track,
|
||||
encoder: encoder,
|
||||
opts: opts,
|
||||
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%s is not currently supported", opts.Codec)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Camera) Start() error {
|
||||
if err := c.cam.StartStreaming(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastTimestamp := time.Now()
|
||||
for {
|
||||
err := c.cam.WaitForFrame(5)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
case *webcam.Timeout:
|
||||
continue
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
frame, err := c.cam.ReadFrame()
|
||||
if err != nil {
|
||||
// TODO: Add a better error handling
|
||||
return err
|
||||
}
|
||||
|
||||
if len(frame) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
img, err := jpeg.Decode(bytes.NewReader(frame))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
encoded, err := c.encoder.Encode(img)
|
||||
if err != nil {
|
||||
// TODO: Add a better error handling
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
duration := now.Sub(lastTimestamp).Seconds()
|
||||
samples := uint32(clockRate * duration)
|
||||
lastTimestamp = now
|
||||
|
||||
if err := c.track.WriteSample(media.Sample{Data: encoded, Samples: samples}); err != nil {
|
||||
// TODO: Add a better error handling
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Camera) Track() *webrtc.Track {
|
||||
return c.track
|
||||
}
|
||||
|
||||
func (c *Camera) Stop() {
|
||||
if c.cam == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.cam.StopStreaming()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package camera
|
||||
|
||||
const (
|
||||
clockRate = 90000
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"github.com/pion/webrtc/v2"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
PC *webrtc.PeerConnection
|
||||
Codec string
|
||||
Width, Height int
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package signal
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// HTTPSDPServer starts a HTTP Server that consumes SDPs
|
||||
func HTTPSDPServer() chan string {
|
||||
port := flag.Int("port", 8080, "http server port")
|
||||
flag.Parse()
|
||||
|
||||
sdpChan := make(chan string)
|
||||
http.HandleFunc("/sdp", func(w http.ResponseWriter, r *http.Request) {
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
fmt.Fprintf(w, "done")
|
||||
sdpChan <- string(body)
|
||||
})
|
||||
|
||||
go func() {
|
||||
err := http.ListenAndServe(":"+strconv.Itoa(*port), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return sdpChan
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package signal
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RandSeq generates a random string to serve as dummy data
|
||||
func RandSeq(n int) string {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[r.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Package signal contains helpers to exchange the SDP session
|
||||
// description between examples.
|
||||
package signal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Allows compressing offer/answer to bypass terminal input limits.
|
||||
const compress = false
|
||||
|
||||
// MustReadStdin blocks until input is received from stdin
|
||||
func MustReadStdin() string {
|
||||
r := bufio.NewReader(os.Stdin)
|
||||
|
||||
var in string
|
||||
for {
|
||||
var err error
|
||||
in, err = r.ReadString('\n')
|
||||
if err != io.EOF {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
in = strings.TrimSpace(in)
|
||||
if len(in) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
|
||||
return in
|
||||
}
|
||||
|
||||
// Encode encodes the input in base64
|
||||
// It can optionally zip the input before encoding
|
||||
func Encode(obj interface{}) string {
|
||||
b, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if compress {
|
||||
b = zip(b)
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
// Decode decodes the input from base64
|
||||
// It can optionally unzip the input after decoding
|
||||
func Decode(in string, obj interface{}) {
|
||||
b, err := base64.StdEncoding.DecodeString(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if compress {
|
||||
b = unzip(b)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func zip(in []byte) []byte {
|
||||
var b bytes.Buffer
|
||||
gz := gzip.NewWriter(&b)
|
||||
_, err := gz.Write(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = gz.Flush()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = gz.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func unzip(in []byte) []byte {
|
||||
var b bytes.Buffer
|
||||
_, err := b.Write(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r, err := gzip.NewReader(&b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
## Instructions
|
||||
|
||||
### Download gstreamer-send
|
||||
|
||||
```
|
||||
go get github.com/pion/mediadevices/examples/simple
|
||||
```
|
||||
|
||||
### Open example page
|
||||
|
||||
[jsfiddle.net](https://jsfiddle.net/z7ms3u5r/) you should see two text-areas and a 'Start Session' button
|
||||
|
||||
### Run simple with your browsers SessionDescription as stdin
|
||||
|
||||
In the jsfiddle the top textarea is your browser, copy that and:
|
||||
|
||||
#### Linux
|
||||
|
||||
Run `echo $BROWSER_SDP | simple`
|
||||
|
||||
### Input simple's SessionDescription into your browser
|
||||
|
||||
Copy the text that `simple` just emitted and copy into second text area
|
||||
|
||||
### Hit 'Start Session' in jsfiddle, enjoy your video!
|
||||
|
||||
A video should start playing in your browser above the input boxes, and will continue playing until you close the application.
|
||||
|
||||
Congrats, you have used pion-WebRTC! Now start building something cool
|
||||
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/mediadevices"
|
||||
"github.com/pion/mediadevices/examples/internal/signal"
|
||||
"github.com/pion/webrtc/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a new RTCPeerConnection
|
||||
peerConnection, err := webrtc.NewPeerConnection(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Set the handler for ICE connection state
|
||||
// This will notify you when the peer has connected/disconnected
|
||||
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
|
||||
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
||||
})
|
||||
|
||||
mediaDevices := mediadevices.NewMediaDevices(peerConnection)
|
||||
|
||||
s, err := mediaDevices.GetUserMedia(mediadevices.MediaStreamConstraints{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, track := range s.GetTracks() {
|
||||
_, err = peerConnection.AddTrack(track)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the offer to be pasted
|
||||
offer := webrtc.SessionDescription{}
|
||||
signal.Decode(signal.MustReadStdin(), &offer)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
err = peerConnection.SetRemoteDescription(offer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create an answer
|
||||
answer, err := peerConnection.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
err = peerConnection.SetLocalDescription(answer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Output the answer in base64 so we can paste it in browser
|
||||
fmt.Println(signal.Encode(answer))
|
||||
select {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module github.com/pion/mediadevices
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/blackjack/webcam v0.0.0-20191123110216-08fa32efcb67
|
||||
github.com/pion/codec v0.0.0-20191205052333-635f762e4bd6
|
||||
github.com/pion/webrtc/v2 v2.1.12
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
github.com/blackjack/webcam v0.0.0-20190407142958-6cd3de4f4861/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||
github.com/blackjack/webcam v0.0.0-20191123110216-08fa32efcb67 h1:m6dKY5E0TM5pRV5MJgTlZKXeEtTHWo2uwdGKApswA3w=
|
||||
github.com/blackjack/webcam v0.0.0-20191123110216-08fa32efcb67/go.mod h1:G0X+rEqYPWSq0dG8OMf8M446MtKytzpPjgS3HbdOJZ4=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pion/codec v0.0.0-20191205052333-635f762e4bd6 h1:gU0pfjZ36yk2vvU5uYGAFq+GvOc+bsgA3Cks8uoNmnc=
|
||||
github.com/pion/codec v0.0.0-20191205052333-635f762e4bd6/go.mod h1:1B/zwUX1+FV9jTHAagdRFlOLXofTw2y2uzOsuY4+Ujs=
|
||||
github.com/pion/datachannel v1.4.12 h1:fFdt26Ppkk9US5mpa/G+9QvYDoYXRIWipj0YUhxSBJI=
|
||||
github.com/pion/datachannel v1.4.12/go.mod h1:Ulrx2j4r8c0Za5ltWFv/hZvSpc3ZpvOvcz46tvnt+PY=
|
||||
github.com/pion/dtls v1.5.3 h1:zLEjR0hUiXGermhTFq4DmhALcRz9u+lSa//AH7H1sW0=
|
||||
github.com/pion/dtls v1.5.3/go.mod h1:v4ULmyyV65geAZQBBckCjgMhmngTqz7HQVsQVYnfkGo=
|
||||
github.com/pion/ice v0.7.1 h1:Leyv/AO/mSPeevlvKuUEgNkfYNZgETONNXTVTYCzMbk=
|
||||
github.com/pion/ice v0.7.1/go.mod h1:fPnWLWO3B83fJmO6Sci5Mv3ypN4Vd956Py4JlbJfVwU=
|
||||
github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.3 h1:DxdOYd0pgwLKiDlIIxfU0qdG5iWh1Xn6CsS9vc6cMAY=
|
||||
github.com/pion/mdns v0.0.3/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE=
|
||||
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
|
||||
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
|
||||
github.com/pion/rtcp v1.2.1 h1:S3yG4KpYAiSmBVqKAfgRa5JdwBNj4zK3RLUa8JYdhak=
|
||||
github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM=
|
||||
github.com/pion/rtp v1.1.3/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE=
|
||||
github.com/pion/rtp v1.1.4 h1:P6xh8Y8JfzR7+JAbI79X2M8kfYETaqbuM5Otm+Z+k6U=
|
||||
github.com/pion/rtp v1.1.4/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE=
|
||||
github.com/pion/sctp v1.7.2 h1:eSFgSHeHkzG8VUbYFxLlN6RqUM/GaNo0v0Uf+LKn6og=
|
||||
github.com/pion/sctp v1.7.2/go.mod h1:HlTD+15FeLYYQTTDO35uKEeRLVq5L2AY/ef6ZSvpIXc=
|
||||
github.com/pion/sdp/v2 v2.3.1 h1:45dub4NRdwyDmQCD3GIY7DZuqC49GBUwBdjuetvdOr0=
|
||||
github.com/pion/sdp/v2 v2.3.1/go.mod h1:jccXVYW0fuK6ds2pwKr89SVBDYlCjhgMI6nucl5R5rA=
|
||||
github.com/pion/srtp v1.2.6 h1:mHQuAMh0P67R7/j1F260u3O+fbRWLyjKLRPZYYvODFM=
|
||||
github.com/pion/srtp v1.2.6/go.mod h1:rd8imc5htjfs99XiEoOjLMEOcVjME63UHx9Ek9IGst0=
|
||||
github.com/pion/stun v0.3.3 h1:brYuPl9bN9w/VM7OdNzRSLoqsnwlyNvD9MVeJrHjDQw=
|
||||
github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
|
||||
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
|
||||
github.com/pion/transport v0.8.9/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8=
|
||||
github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk=
|
||||
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
|
||||
github.com/pion/turn v1.4.0 h1:7NUMRehQz4fIo53Qv9ui1kJ0Kr1CA82I81RHKHCeM80=
|
||||
github.com/pion/turn v1.4.0/go.mod h1:aDSi6hWX/hd1+gKia9cExZOR0MU95O7zX9p3Gw/P2aU=
|
||||
github.com/pion/webrtc/v2 v2.1.12 h1:x/K+Hwj1Iud+62rA3KaCCAugFWIofzxgBvDEyc1KQmM=
|
||||
github.com/pion/webrtc/v2 v2.1.12/go.mod h1:CYstxYIn64VLKMmqTHgLvJRnYIF9Ofr4APJXsCVpeso=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc h1:KyTYo8xkh/2WdbFLUyQwBS0Jfn3qfZ9QmuPbok2oENE=
|
||||
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -0,0 +1,40 @@
|
||||
package mediadevices
|
||||
|
||||
import (
|
||||
"github.com/pion/mediadevices/camera"
|
||||
"github.com/pion/webrtc/v2"
|
||||
)
|
||||
|
||||
// MediaDevices is an interface that's defined on https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices
|
||||
type MediaDevices interface {
|
||||
GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error)
|
||||
}
|
||||
|
||||
func NewMediaDevices(pc *webrtc.PeerConnection) MediaDevices {
|
||||
return &mediaDevices{pc}
|
||||
}
|
||||
|
||||
type mediaDevices struct {
|
||||
pc *webrtc.PeerConnection
|
||||
}
|
||||
|
||||
func (m *mediaDevices) GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error) {
|
||||
// TODO: It should return media stream based on constraints
|
||||
c, err := camera.New(camera.Options{
|
||||
PC: m.pc,
|
||||
Codec: webrtc.H264,
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := NewMediaStream(c.Track())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go c.Start()
|
||||
return s, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package mediadevices
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pion/webrtc/v2"
|
||||
)
|
||||
|
||||
type MediaStream interface {
|
||||
GetAudioTracks() []*webrtc.Track
|
||||
GetVideoTracks() []*webrtc.Track
|
||||
GetTracks() []*webrtc.Track
|
||||
AddTrack(t *webrtc.Track)
|
||||
RemoveTrack(t *webrtc.Track)
|
||||
}
|
||||
|
||||
type mediaStream struct {
|
||||
tracks map[string]*webrtc.Track
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
const rtpCodecTypeDefault webrtc.RTPCodecType = 0
|
||||
|
||||
// NewMediaStream creates a MediaStream interface that's defined in
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream
|
||||
func NewMediaStream(tracks ...*webrtc.Track) (MediaStream, error) {
|
||||
m := mediaStream{tracks: make(map[string]*webrtc.Track)}
|
||||
|
||||
for _, track := range tracks {
|
||||
id := track.ID()
|
||||
if _, ok := m.tracks[id]; !ok {
|
||||
m.tracks[id] = track
|
||||
}
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// GetAudioTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks
|
||||
func (m *mediaStream) GetAudioTracks() []*webrtc.Track {
|
||||
return m.queryTracks(webrtc.RTPCodecTypeAudio)
|
||||
}
|
||||
|
||||
// GetVideoTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks
|
||||
func (m *mediaStream) GetVideoTracks() []*webrtc.Track {
|
||||
return m.queryTracks(webrtc.RTPCodecTypeVideo)
|
||||
}
|
||||
|
||||
// GetTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks
|
||||
func (m *mediaStream) GetTracks() []*webrtc.Track {
|
||||
return m.queryTracks(rtpCodecTypeDefault)
|
||||
}
|
||||
|
||||
// queryTracks returns all tracks that are the same kind as t.
|
||||
// If t is 0, which is the default, queryTracks will return all the tracks.
|
||||
func (m *mediaStream) queryTracks(t webrtc.RTPCodecType) []*webrtc.Track {
|
||||
m.l.RLock()
|
||||
defer m.l.RUnlock()
|
||||
|
||||
result := make([]*webrtc.Track, 0)
|
||||
for _, track := range m.tracks {
|
||||
if track.Kind() == t || t == rtpCodecTypeDefault {
|
||||
result = append(result, track)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AddTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack
|
||||
func (m *mediaStream) AddTrack(t *webrtc.Track) {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
|
||||
id := t.ID()
|
||||
if _, ok := m.tracks[id]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
m.tracks[id] = t
|
||||
}
|
||||
|
||||
// RemoveTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack
|
||||
func (m *mediaStream) RemoveTrack(t *webrtc.Track) {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
|
||||
delete(m.tracks, t.ID())
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package mediadevices
|
||||
|
||||
type MediaStreamConstraints struct {
|
||||
Audio MediaTrackConstraints
|
||||
Video MediaTrackConstraints
|
||||
}
|
||||
|
||||
type MediaTrackConstraints bool
|
||||
Reference in New Issue
Block a user