feat(module): arcast integration
This commit is contained in:
49
pkg/module/cast/arcast/client.go
Normal file
49
pkg/module/cast/arcast/client.go
Normal file
@ -0,0 +1,49 @@
|
||||
package arcast
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/arcad/arcast/pkg/client"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *client.Client
|
||||
addr string
|
||||
}
|
||||
|
||||
// Close implements cast.Client.
|
||||
func (c *Client) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load implements cast.Client.
|
||||
func (c *Client) Load(ctx context.Context, url string) error {
|
||||
if _, err := c.client.Cast(ctx, c.addr, url); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status implements cast.Client.
|
||||
func (c *Client) Status(ctx context.Context) (cast.DeviceStatus, error) {
|
||||
status, err := c.client.Status(ctx, c.addr)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &DeviceStatus{status}, nil
|
||||
}
|
||||
|
||||
// Unload implements cast.Client.
|
||||
func (c *Client) Unload(ctx context.Context) error {
|
||||
if _, err := c.client.Reset(ctx, c.addr); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ cast.Client = &Client{}
|
57
pkg/module/cast/arcast/device.go
Normal file
57
pkg/module/cast/arcast/device.go
Normal file
@ -0,0 +1,57 @@
|
||||
package arcast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"forge.cadoles.com/arcad/arcast/pkg/client"
|
||||
"forge.cadoles.com/arcad/arcast/pkg/server"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const DeviceTypeArcast cast.DeviceType = "arcast"
|
||||
|
||||
type Device struct {
|
||||
player *client.Player
|
||||
}
|
||||
|
||||
// DeviceHost implements cast.Device.
|
||||
func (d *Device) DeviceHost() net.IP {
|
||||
rawPreferredIP, err := server.FindPreferredLocalAddress(d.player.IPs...)
|
||||
if err != nil {
|
||||
logger.Error(context.Background(), "could not find preferred local address", logger.CapturedE(errors.WithStack(err)))
|
||||
return d.player.IPs[0]
|
||||
}
|
||||
|
||||
preferredIP := net.ParseIP(rawPreferredIP)
|
||||
if preferredIP == nil {
|
||||
logger.Error(context.Background(), "could not parse device preferred ip", logger.F("rawPreferredIP", rawPreferredIP))
|
||||
return d.player.IPs[0]
|
||||
}
|
||||
|
||||
return preferredIP
|
||||
}
|
||||
|
||||
// DeviceID implements cast.Device.
|
||||
func (d *Device) DeviceID() string {
|
||||
return d.player.ID
|
||||
}
|
||||
|
||||
// DeviceName implements cast.Device.
|
||||
func (d *Device) DeviceName() string {
|
||||
return d.player.ID
|
||||
}
|
||||
|
||||
// DevicePort implements cast.Device.
|
||||
func (d *Device) DevicePort() int {
|
||||
return d.player.Port
|
||||
}
|
||||
|
||||
// DeviceType implements cast.Device.
|
||||
func (d *Device) DeviceType() cast.DeviceType {
|
||||
return DeviceTypeArcast
|
||||
}
|
||||
|
||||
var _ cast.Device = &Device{}
|
60
pkg/module/cast/arcast/service.go
Normal file
60
pkg/module/cast/arcast/service.go
Normal file
@ -0,0 +1,60 @@
|
||||
package arcast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/arcad/arcast/pkg/client"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cast.Register(DeviceTypeArcast, &Service{
|
||||
client: client.New(),
|
||||
})
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// Find implements cast.Service.
|
||||
func (s *Service) Find(ctx context.Context, deviceID string) (cast.Device, error) {
|
||||
players, err := s.client.Scan(ctx, client.WithPlayerIDs(deviceID))
|
||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if len(players) == 0 {
|
||||
return nil, errors.WithStack(cast.ErrDeviceNotFound)
|
||||
}
|
||||
|
||||
return &Device{players[0]}, nil
|
||||
}
|
||||
|
||||
// NewClient implements cast.Service.
|
||||
func (s *Service) NewClient(ctx context.Context, device cast.Device) (cast.Client, error) {
|
||||
return &Client{
|
||||
client: s.client,
|
||||
addr: fmt.Sprintf("%s:%d", device.DeviceHost(), device.DevicePort()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Scan implements cast.Service.
|
||||
func (s *Service) Scan(ctx context.Context) ([]cast.Device, error) {
|
||||
players, err := s.client.Scan(ctx)
|
||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
devices := make([]cast.Device, len(players))
|
||||
|
||||
for i, p := range players {
|
||||
devices[i] = &Device{p}
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
var _ cast.Service = &Service{}
|
22
pkg/module/cast/arcast/status.go
Normal file
22
pkg/module/cast/arcast/status.go
Normal file
@ -0,0 +1,22 @@
|
||||
package arcast
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/arcad/arcast/pkg/server"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
)
|
||||
|
||||
type DeviceStatus struct {
|
||||
status *server.StatusResponse
|
||||
}
|
||||
|
||||
// State implements cast.DeviceStatus.
|
||||
func (s *DeviceStatus) State() string {
|
||||
return s.status.Status
|
||||
}
|
||||
|
||||
// Title implements cast.DeviceStatus.
|
||||
func (s *DeviceStatus) Title() string {
|
||||
return s.status.Title
|
||||
}
|
||||
|
||||
var _ cast.DeviceStatus = &DeviceStatus{}
|
@ -2,6 +2,7 @@ package cast_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
// Register casting device supported types
|
||||
_ "forge.cadoles.com/arcad/edge/pkg/module/cast/arcast"
|
||||
_ "forge.cadoles.com/arcad/edge/pkg/module/cast/chromecast"
|
||||
)
|
||||
|
||||
@ -39,8 +41,8 @@ func TestCastLoadURL(t *testing.T) {
|
||||
|
||||
t.Logf("DEVICES: %s", spew.Sdump(devices))
|
||||
|
||||
if e, g := 1, len(devices); e != g {
|
||||
t.Fatalf("len(devices): expected '%v', got '%v'", e, g)
|
||||
if e, g := 1, len(devices); e > g {
|
||||
t.Fatalf("len(devices): expected 'value >= %v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
devices, err = cast.ListDevices(ctx, false)
|
||||
@ -50,33 +52,40 @@ func TestCastLoadURL(t *testing.T) {
|
||||
|
||||
t.Logf("CACHED DEVICES: %s", spew.Sdump(devices))
|
||||
|
||||
if e, g := 1, len(devices); e != g {
|
||||
t.Fatalf("len(devices): expected '%v', got '%v'", e, g)
|
||||
if e, g := 1, len(devices); e > g {
|
||||
t.Fatalf("len(devices): expected 'value >= %v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
dev := devices[0]
|
||||
for _, device := range devices {
|
||||
testName := fmt.Sprintf("%s(%s)", device.DeviceType(), device.DeviceID())
|
||||
func(device cast.Device) {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel2 := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel2()
|
||||
ctx, cancel2 := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
if err := cast.LoadURL(ctx, dev.DeviceID(), "https://go.dev"); err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
if err := cast.LoadURL(ctx, device.DeviceID(), "https://go.dev"); err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
|
||||
ctx, cancel3 := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel3()
|
||||
ctx, cancel3 := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel3()
|
||||
|
||||
status, err := cast.GetStatus(ctx, dev.DeviceID())
|
||||
if err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
status, err := cast.GetStatus(ctx, device.DeviceID())
|
||||
if err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
|
||||
t.Logf("DEVICE STATUS: %s", spew.Sdump(status))
|
||||
t.Logf("DEVICE STATUS: %s", spew.Sdump(status))
|
||||
|
||||
ctx, cancel4 := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel4()
|
||||
ctx, cancel4 := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel4()
|
||||
|
||||
if err := cast.StopCast(ctx, dev.DeviceID()); err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
if err := cast.StopCast(ctx, device.DeviceID()); err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
})
|
||||
}(device)
|
||||
}
|
||||
}
|
||||
|
@ -11,3 +11,31 @@ type Device interface {
|
||||
DeviceName() string
|
||||
DeviceID() string
|
||||
}
|
||||
|
||||
type legacyDevice struct {
|
||||
Device `json:"-"`
|
||||
UUID string `goja:"uuid" json:"uuid"`
|
||||
Host string `goja:"host" json:"host"`
|
||||
Port int `goja:"port" json:"port"`
|
||||
Name string `goja:"name" json:"name"`
|
||||
Type string `goja:"type" json:"type"`
|
||||
}
|
||||
|
||||
func toLegacyDevice(device Device) *legacyDevice {
|
||||
return &legacyDevice{
|
||||
Device: device,
|
||||
UUID: device.DeviceID(),
|
||||
Host: device.DeviceHost().String(),
|
||||
Port: device.DevicePort(),
|
||||
Name: device.DeviceName(),
|
||||
Type: string(device.DeviceType()),
|
||||
}
|
||||
}
|
||||
|
||||
func toLegacyDevices(devices ...Device) []*legacyDevice {
|
||||
legacyDevices := make([]*legacyDevice, len(devices))
|
||||
for i, d := range devices {
|
||||
legacyDevices[i] = toLegacyDevice(d)
|
||||
}
|
||||
return legacyDevices
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ func (m *Module) refreshDevices(call goja.FunctionCall, rt *goja.Runtime) goja.V
|
||||
return
|
||||
}
|
||||
|
||||
promise.Resolve(devices)
|
||||
promise.Resolve(toLegacyDevices(devices...))
|
||||
}()
|
||||
|
||||
return rt.ToValue(promise)
|
||||
@ -81,7 +81,7 @@ func (m *Module) getDevices(call goja.FunctionCall, rt *goja.Runtime) goja.Value
|
||||
panic(rt.ToValue(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
return rt.ToValue(devices)
|
||||
return rt.ToValue(toLegacyDevices(devices...))
|
||||
}
|
||||
|
||||
func (m *Module) loadUrl(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
|
@ -62,20 +62,22 @@ func (r *Registry) Scan(ctx context.Context) ([]Device, error) {
|
||||
wg.Add(len(r.index))
|
||||
|
||||
for _, srv := range r.index {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
func(srv Service) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
devices, err := srv.Scan(ctx)
|
||||
if err != nil {
|
||||
lock.Lock()
|
||||
errs = append(errs, errors.WithStack(err))
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
devices, err := srv.Scan(ctx)
|
||||
if err != nil {
|
||||
lock.Lock()
|
||||
errs = append(errs, errors.WithStack(err))
|
||||
results = append(results, devices...)
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
lock.Lock()
|
||||
results = append(results, devices...)
|
||||
lock.Unlock()
|
||||
}()
|
||||
}()
|
||||
}(srv)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
Reference in New Issue
Block a user