Skip to content

Commit

Permalink
Add support gopro source
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexxIT committed Nov 15, 2023
1 parent 54b1999 commit 9bda2f7
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
30 changes: 30 additions & 0 deletions internal/gopro/gopro.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package gopro

import (
"net/http"

"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/gopro"
)

func Init() {
streams.HandleFunc("gopro", handleGoPro)

api.HandleFunc("api/gopro", apiGoPro)
}

func handleGoPro(rawURL string) (core.Producer, error) {
return gopro.Dial(rawURL)
}

func apiGoPro(w http.ResponseWriter, r *http.Request) {
var items []*api.Source

for _, host := range gopro.Discovery() {
items = append(items, &api.Source{Name: host, URL: "gopro://" + host})
}

api.ResponseSources(w, items)
}
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/AlexxIT/go2rtc/internal/exec"
"github.com/AlexxIT/go2rtc/internal/expr"
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
"github.com/AlexxIT/go2rtc/internal/gopro"
"github.com/AlexxIT/go2rtc/internal/hass"
"github.com/AlexxIT/go2rtc/internal/hls"
"github.com/AlexxIT/go2rtc/internal/homekit"
Expand Down Expand Up @@ -78,6 +79,7 @@ func main() {
nest.Init() // nest source
bubble.Init() // bubble source
expr.Init() // expr source
gopro.Init() // gopro source

// 6. Helper modules

Expand Down
43 changes: 43 additions & 0 deletions pkg/gopro/discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gopro

import (
"net"
"net/http"
"regexp"
)

func Discovery() (urls []string) {
ints, err := net.Interfaces()
if err != nil {
return nil
}

// The socket address for USB connections is 172.2X.1YZ.51:8080
// https://gopro.github.io/OpenGoPro/http_2_0#socket-address
re := regexp.MustCompile(`^172\.2\d\.1\d\d\.`)

for _, itf := range ints {
addrs, err := itf.Addrs()
if err != nil {
continue
}

for _, addr := range addrs {
host := addr.String()
if !re.MatchString(host) {
continue
}

host = host[:11] + "51" // 172.2x.1xx.xxx
res, err := http.Get("http://" + host + ":8080/gopro/webcam/status")
if err != nil {
continue
}
_ = res.Body.Close()

urls = append(urls, host)
}
}

return
}
117 changes: 117 additions & 0 deletions pkg/gopro/gopro.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package gopro

import (
"errors"
"io"
"net"
"net/http"
"net/url"
"time"

"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/mpegts"
)

func Dial(rawURL string) (core.Producer, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}

r := &listener{host: u.Host}

if err = r.command("/gopro/webcam/stop"); err != nil {
return nil, err
}

if err = r.listen(); err != nil {
return nil, err
}

if err = r.command("/gopro/webcam/start"); err != nil {
return nil, err
}

return mpegts.Open(r)
}

type listener struct {
conn net.PacketConn
host string
packet []byte
packets chan []byte
}

func (r *listener) Read(p []byte) (n int, err error) {
if r.packet == nil {
var ok bool
if r.packet, ok = <-r.packets; !ok {
return 0, io.EOF // channel closed
}
}

n = copy(p, r.packet)

if n < len(r.packet) {
r.packet = r.packet[n:]
} else {
r.packet = nil
}

return
}

func (r *listener) Close() error {
return r.conn.Close()
}

func (r *listener) command(api string) error {
client := &http.Client{Timeout: 5 * time.Second}

res, err := client.Get("http://" + r.host + ":8080" + api)
if err != nil {
return err
}

_ = res.Body.Close()

if res.StatusCode != http.StatusOK {
return errors.New("gopro: wrong response: " + res.Status)
}

return nil
}

func (r *listener) listen() (err error) {
if r.conn, err = net.ListenPacket("udp4", ":8554"); err != nil {
return
}

r.packets = make(chan []byte, 1024)
go r.worker()

return
}

func (r *listener) worker() {
b := make([]byte, 1500)
for {
if err := r.conn.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
break
}

n, _, err := r.conn.ReadFrom(b)
if err != nil {
break
}

packet := make([]byte, n)
copy(packet, b)

r.packets <- packet
}

close(r.packets)

_ = r.command("/gopro/webcam/stop")
}
12 changes: 12 additions & 0 deletions www/add.html
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@
</script>


<button id="gopro">GoPro</button>
<div class="module">
<table id="gopro-table"></table>
</div>
<script>
document.getElementById('gopro').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block';
await getSources('gopro-table', 'api/gopro');
});
</script>


<button id="hass">Home Assistant</button>
<div class="module">
<table id="hass-table"></table>
Expand Down

0 comments on commit 9bda2f7

Please sign in to comment.