@@ -4,14 +4,14 @@ import (
44 "context"
55 "encoding/json"
66 "fmt"
7+ "golang.org/x/sync/errgroup"
78 "net"
89 "net/http"
910 "strconv"
11+ "strings"
1012 "sync"
1113 "time"
1214
13- "golang.org/x/sync/errgroup"
14-
1515 log "github.com/sirupsen/logrus"
1616 "github.com/vishen/go-chromecast/application"
1717 "github.com/vishen/go-chromecast/dns"
@@ -22,28 +22,73 @@ type Handler struct {
2222 apps map [string ]application.App
2323 mux * http.ServeMux
2424
25- verbose bool
26- autoconnect bool
25+ verbose bool
26+
27+ autoconnectPeriod time.Duration
28+ autoconnectTicker * time.Ticker
29+
30+ // autoupdatePeriodSec defines how frequently app.Update method is called in the background.
31+ autoupdatePeriod time.Duration
32+ autoupdateTicker * time.Ticker
2733}
2834
2935func NewHandler (verbose bool ) * Handler {
3036 handler := & Handler {
31- verbose : verbose ,
32- apps : map [string ]application.App {},
33- mux : http .NewServeMux (),
34- mu : sync.Mutex {},
35- autoconnect : false ,
37+ verbose : verbose ,
38+ apps : map [string ]application.App {},
39+ mux : http .NewServeMux (),
40+ mu : sync.Mutex {},
41+
42+ autoconnectPeriod : time .Duration (- 1 ),
43+ autoconnectTicker : nil ,
44+
45+ autoupdatePeriod : time .Duration (- 1 ),
46+ autoupdateTicker : nil ,
3647 }
3748 handler .registerHandlers ()
3849 return handler
3950}
4051
41- // Autoconnect configures the handler to perform auto-discovery of all the cast devices & groups.
52+ // AutoConnect configures the handler to perform periodic auto-discovery of all the cast devices & groups.
53+ // It's intended to be called just after `NewHandler()`, before the handler is registered in the server.
54+ func (h * Handler ) AutoConnect (period time.Duration ) error {
55+ // Setting the autoconnect property - to allow (in future) periodic refresh of the connections.
56+ h .autoconnectPeriod = period
57+ if err := h .connectAllInternal ("" , "3" ); err != nil {
58+ return err
59+ }
60+ if h .autoconnectPeriod > 0 {
61+ h .autoconnectTicker = time .NewTicker (period )
62+ go func () {
63+ for {
64+ <- h .autoconnectTicker .C
65+ if err := h .connectAllInternal ("" , "3" ); err != nil {
66+ log .Printf ("AutoConnect issued connectAllInternal failed: %v" , err )
67+ }
68+ }
69+ }()
70+ }
71+ return nil
72+ }
73+
74+ // AutoUpdate configures the handler to perform auto-update of all the cast devices & groups.
4275// It's intended to be called just after `NewHandler()`, before the handler is registered in the server.
43- func (h * Handler ) Autoconnect () error {
76+ // Thanks to AutoUpdate, /status and /statuses returns relatively recent status 'instantly'.
77+ func (h * Handler ) AutoUpdate (period time.Duration ) error {
4478 // Setting the autoconnect property - to allow (in future) periodic refresh of the connections.
45- h .autoconnect = true
46- return h .connectAllInternal ("" , "3" )
79+ h .autoupdatePeriod = period
80+ if h .autoupdatePeriod > 0 {
81+ h .autoupdateTicker = time .NewTicker (period )
82+ go func () {
83+ for {
84+ <- h .autoupdateTicker .C
85+ if err := h .UpdateAll (); err != nil {
86+ log .Printf ("AutoUpdate issued UpdateAll failed: %v" , err )
87+ }
88+ }
89+ }()
90+ }
91+ return nil
4792}
4893
4994func (h * Handler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
@@ -291,20 +336,42 @@ func (h *Handler) connectAll(w http.ResponseWriter, r *http.Request) {
291336}
292337
293338func (h * Handler ) connectAllInternal (iface string , waitSec string ) error {
294- devices := h .discoverDnsEntries (context .Background (), iface , waitSec )
295- uuidMap := map [string ]application.App {}
339+ ctx := context .Background ()
340+ devices := h .discoverDnsEntries (ctx , iface , waitSec )
341+ apps := make (chan * application.App , len (devices )+ 1 )
342+ g , ctx := errgroup .WithContext (ctx )
296343 for _ , device := range devices {
297- app , err := h .connectInternal (device .Addr , device .Port , device .DeviceName )
344+ g .Go (func () error {
345+ log .Printf ("Connecting to %s:%d (%s)" , device .Addr , device .Port , device .DeviceName )
346+ app , err := h .connectInternal (device .Addr , device .Port , device .DeviceName )
347+ if err != nil {
348+ log .Printf ("Connection to %s:%d (%s) failed: %v" , device .Addr , device .Port , device .DeviceName , err )
349+ return err
350+ }
351+ log .Printf ("Connected to %s:%d (%s)" , device .Addr , device .Port , device .DeviceName )
352+ apps <- & app
353+ return nil
354+ })
355+ }
356+ err := g .Wait ()
357+ log .Printf ("Post wait status: %v" , err )
358+ close (apps )
359+
360+ // Even if we cannot connect to some of the devices, we still update the map for remaining devices.
361+ uuidMap := map [string ]application.App {}
362+ for app := range apps {
363+ info , err := (* app ).Info ()
298364 if err != nil {
299- return err
365+ log .Printf ("Skipping device %v" , app )
366+ } else {
367+ uuidMap [strings .ReplaceAll (info .SsdpUdn , "-" , "" )] = * app
300368 }
301- uuidMap [device .UUID ] = app
302369 }
303370
304371 h .mu .Lock ()
305372 h .apps = uuidMap
306373 h .mu .Unlock ()
307- return nil
374+ return err
308375}
309376
310377func (h * Handler ) disconnect (w http.ResponseWriter , r * http.Request ) {
@@ -353,7 +420,14 @@ func (h *Handler) status(w http.ResponseWriter, r *http.Request) {
353420 return
354421 }
355422 h .log ("status for device" )
356-
423+ syncUpdate := r .URL .Query ().Get ("syncUpdate" ) == "true"
424+ if syncUpdate {
425+ if err := app .Update (); err != nil {
426+ h .log ("error updating status: %v" , err )
427+ httpError (w , fmt .Errorf ("error updating status: %w" , err ))
428+ return
429+ }
430+ }
357431 castApplication , castMedia , castVolume := app .Status ()
358432 info , err := app .Info ()
359433 if err != nil {
@@ -377,8 +451,21 @@ func (h *Handler) status(w http.ResponseWriter, r *http.Request) {
377451 }
378452}
379453
454+ func (h * Handler ) UpdateAll () error {
455+ uuids := h .ConnectedDeviceUUIDs ()
456+ g := new (errgroup.Group )
457+ for _ , deviceUUID := range uuids {
458+ app , ok := h .app (deviceUUID )
459+ if ok {
460+ g .Go (func () error { return app .Update () })
461+ }
462+ }
463+ return g .Wait ()
464+ }
465+
380466func (h * Handler ) statuses (w http.ResponseWriter , r * http.Request ) {
381467 h .log ("statuses for devices" )
468+ syncUpdate := r .URL .Query ().Get ("syncUpdate" ) == "true"
382469 uuids := h .ConnectedDeviceUUIDs ()
383470 mapUUID2Ch := map [string ]chan statusResponse {}
384471 g := new (errgroup.Group )
@@ -388,6 +475,11 @@ func (h *Handler) statuses(w http.ResponseWriter, r *http.Request) {
388475 ch := make (chan statusResponse , 1 )
389476 mapUUID2Ch [deviceUUID ] = ch
390477 g .Go (func () error {
478+ if syncUpdate {
479+ if err := app .Update (); err != nil {
480+ return err
481+ }
482+ }
391483 castApplication , castMedia , castVolume := app .Status ()
392484 info , err := app .Info ()
393485 if err != nil {
@@ -404,8 +496,9 @@ func (h *Handler) statuses(w http.ResponseWriter, r *http.Request) {
404496 }
405497 }
406498 if err := g .Wait (); err != nil {
407- h .log ("%v" , err )
408- httpError (w , err )
499+ h .log ("collecting statuses failed: %v" , err )
500+ httpError (w , fmt .Errorf ("collecting statuses failed: %w" , err ))
501+ return
409502 }
410503
411504 statusResponses := map [string ]statusResponse {}
@@ -560,7 +653,7 @@ func (h *Handler) rewind(w http.ResponseWriter, r *http.Request) {
560653 q := r .URL .Query ()
561654 seconds := q .Get ("seconds" )
562655 if seconds == "" {
563- httpValidationError (w , "missing 'seconds' in query paramater " )
656+ httpValidationError (w , "missing 'seconds' in query parameter " )
564657 return
565658 }
566659
@@ -589,7 +682,7 @@ func (h *Handler) seek(w http.ResponseWriter, r *http.Request) {
589682 q := r .URL .Query ()
590683 seconds := q .Get ("seconds" )
591684 if seconds == "" {
592- httpValidationError (w , "missing 'seconds' in query paramater " )
685+ httpValidationError (w , "missing 'seconds' in query parameter " )
593686 return
594687 }
595688
0 commit comments