@@ -16,10 +16,15 @@ package cmd
1616
1717import (
1818 "context"
19+ "fmt"
1920 "net"
21+ "sort"
22+ "sync"
2023 "time"
2124
25+ "github.com/seancfoley/ipaddress-go/ipaddr"
2226 "github.com/spf13/cobra"
27+ "github.com/vishen/go-chromecast/application"
2328 castdns "github.com/vishen/go-chromecast/dns"
2429)
2530
@@ -30,30 +35,179 @@ var lsCmd = &cobra.Command{
3035 Run : func (cmd * cobra.Command , args []string ) {
3136 ifaceName , _ := cmd .Flags ().GetString ("iface" )
3237 dnsTimeoutSeconds , _ := cmd .Flags ().GetInt ("dns-timeout" )
38+ broadSearch , _ := cmd .Flags ().GetBool ("broad-search" )
39+
3340 var iface * net.Interface
3441 var err error
3542 if ifaceName != "" {
3643 if iface , err = net .InterfaceByName (ifaceName ); err != nil {
3744 exit ("unable to find interface %q: %v" , ifaceName , err )
3845 }
46+ } else {
47+ // If no interface was specified, try to auto-detect the best interface
48+ if iface , err = detectBestInterface (); err != nil {
49+ // If auto-detection fails, continue without interface (original behavior)
50+ iface = nil
51+ }
3952 }
40- ctx , cancel := context .WithTimeout (context .Background (), time .Second * time .Duration (dnsTimeoutSeconds ))
41- defer cancel ()
42- castEntryChan , err := castdns .DiscoverCastDNSEntries (ctx , iface )
43- if err != nil {
44- exit ("unable to discover chromecast devices: %v" , err )
53+
54+ if broadSearch {
55+ // Use hybrid approach: mDNS + port scanning
56+ foundDevices := performBroadSearch (iface , dnsTimeoutSeconds )
57+ if len (foundDevices ) == 0 {
58+ outputError ("no cast devices found on network" )
59+ } else {
60+ for i , device := range foundDevices {
61+ outputInfo ("%d) device=%q device_name=%q address=\" %s:%d\" uuid=%q" ,
62+ i + 1 , device .Device , device .DeviceName , device .AddrV4 , device .Port , device .UUID )
63+ }
64+ }
65+ } else {
66+ // Use original mDNS-only approach
67+ ctx , cancel := context .WithTimeout (context .Background (), time .Second * time .Duration (dnsTimeoutSeconds ))
68+ defer cancel ()
69+ castEntryChan , err := castdns .DiscoverCastDNSEntries (ctx , iface )
70+ if err != nil {
71+ exit ("unable to discover chromecast devices: %v" , err )
72+ }
73+ i := 1
74+ for d := range castEntryChan {
75+ outputInfo ("%d) device=%q device_name=%q address=\" %s:%d\" uuid=%q" , i , d .Device , d .DeviceName , d .AddrV4 , d .Port , d .UUID )
76+ i ++
77+ }
78+ if i == 1 {
79+ outputError ("no cast devices found on network" )
80+ }
4581 }
46- i := 1
82+ },
83+ }
84+
85+ // CastDevice represents a discovered Chromecast device
86+ type CastDevice struct {
87+ Device string
88+ DeviceName string
89+ AddrV4 string
90+ Port int
91+ UUID string
92+ }
93+
94+ // performBroadSearch does a comprehensive search using both mDNS and port scanning
95+ func performBroadSearch (iface * net.Interface , dnsTimeoutSeconds int ) []CastDevice {
96+ var allDevices []CastDevice
97+ deviceMap := make (map [string ]CastDevice ) // Use UUID as key to deduplicate
98+
99+ // First, try mDNS discovery
100+ outputInfo ("Performing mDNS discovery..." )
101+ ctx , cancel := context .WithTimeout (context .Background (), time .Second * time .Duration (dnsTimeoutSeconds * 3 )) // Use 3x timeout for broad search
102+ castEntryChan , err := castdns .DiscoverCastDNSEntries (ctx , iface )
103+ if err == nil {
47104 for d := range castEntryChan {
48- outputInfo ("%d) device=%q device_name=%q address=\" %s:%d\" uuid=%q" , i , d .Device , d .DeviceName , d .AddrV4 , d .Port , d .UUID )
49- i ++
105+ device := CastDevice {
106+ Device : d .Device ,
107+ DeviceName : d .DeviceName ,
108+ AddrV4 : d .AddrV4 .String (),
109+ Port : d .Port ,
110+ UUID : d .UUID ,
111+ }
112+ if device .UUID != "" {
113+ deviceMap [device .UUID ] = device
114+ } else {
115+ // If no UUID, use address:port as key
116+ key := fmt .Sprintf ("%s:%d" , device .AddrV4 , device .Port )
117+ deviceMap [key ] = device
118+ }
50119 }
51- if i == 1 {
52- outputError ("no cast devices found on network" )
120+ }
121+ cancel ()
122+
123+ outputInfo ("Found %d devices via mDNS, performing port scan to find additional devices..." , len (deviceMap ))
124+
125+ // Then, do a targeted port scan on the local subnet
126+ if localSubnet , err := detectLocalSubnet ("" ); err == nil {
127+ ipRange , err := ipaddr .NewIPAddressString (localSubnet ).ToSequentialRange ()
128+ if err == nil {
129+ // Use a smaller set of ports for ls to keep it reasonably fast
130+ ports := []int {8009 , 8008 , 8443 , 32236 } // Common ports + known group port
131+
132+ var wg sync.WaitGroup
133+ ipCh := make (chan * ipaddr.IPAddress , 100 )
134+
135+ // Send IPs to scan
136+ go func () {
137+ it := ipRange .Iterator ()
138+ for it .HasNext () {
139+ ip := it .Next ()
140+ ipCh <- ip
141+ }
142+ close (ipCh )
143+ }()
144+
145+ // Scan IPs in parallel
146+ for i := 0 ; i < 20 ; i ++ { // Use fewer goroutines than scan command
147+ wg .Add (1 )
148+ go func () {
149+ defer wg .Done ()
150+ dialer := & net.Dialer {
151+ Timeout : 300 * time .Millisecond ,
152+ }
153+ for ip := range ipCh {
154+ for _ , port := range ports {
155+ conn , err := dialer .Dial ("tcp" , fmt .Sprintf ("%v:%d" , ip , port ))
156+ if err != nil {
157+ continue
158+ }
159+ conn .Close ()
160+
161+ // Try to get device info
162+ if info , err := application .GetInfo (ip .String ()); err == nil {
163+ device := CastDevice {
164+ Device : "Unknown Device" ,
165+ DeviceName : info .Name ,
166+ AddrV4 : ip .String (),
167+ Port : port ,
168+ UUID : "" , // Port scan doesn't give us UUID
169+ }
170+
171+ // Use address:port as key since we don't have UUID from port scan
172+ key := fmt .Sprintf ("%s:%d" , device .AddrV4 , device .Port )
173+
174+ // Only add if we haven't seen this device yet
175+ if _ , exists := deviceMap [key ]; ! exists {
176+ // Also check if we have this device by name on a different port
177+ found := false
178+ for _ , existing := range deviceMap {
179+ if existing .DeviceName == device .DeviceName && existing .AddrV4 == device .AddrV4 {
180+ found = true
181+ break
182+ }
183+ }
184+ if ! found {
185+ deviceMap [key ] = device
186+ }
187+ }
188+ }
189+ }
190+ }
191+ }()
192+ }
193+ wg .Wait ()
53194 }
54- },
195+ }
196+
197+ // Convert map to slice and sort
198+ for _ , device := range deviceMap {
199+ allDevices = append (allDevices , device )
200+ }
201+
202+ // Sort by device name for consistent output
203+ sort .Slice (allDevices , func (i , j int ) bool {
204+ return allDevices [i ].DeviceName < allDevices [j ].DeviceName
205+ })
206+
207+ return allDevices
55208}
56209
57210func init () {
211+ lsCmd .Flags ().Bool ("broad-search" , false , "perform comprehensive search using both mDNS and port scanning" )
58212 rootCmd .AddCommand (lsCmd )
59213}
0 commit comments