1 /*****************************************************************************
2 * bonjour.m: mDNS services discovery module based on Bonjour
3 *****************************************************************************
4 * Copyright (C) 2016 VLC authors, VideoLAN and VideoLabs
6 * Authors: Felix Paul Kühne <fkuehne@videolan.org>
7 * Marvin Scholz <epirat07@gmail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_modules.h>
31 #include <vlc_services_discovery.h>
32 #include <vlc_renderer_discovery.h>
34 #import <Foundation/Foundation.h>
36 #pragma mark Function declarations
38 static int OpenSD( vlc_object_t * );
39 static void CloseSD( vlc_object_t * );
41 static int OpenRD( vlc_object_t * );
42 static void CloseRD( vlc_object_t * );
44 VLC_SD_PROBE_HELPER( "Bonjour", N_("Bonjour Network Discovery"), SD_CAT_LAN )
45 VLC_RD_PROBE_HELPER( "Bonjour_renderer", "Bonjour Renderer Discovery" )
47 struct services_discovery_sys_t
49 CFTypeRef _Nullable discoveryController;
52 struct vlc_renderer_discovery_sys
54 CFTypeRef _Nullable discoveryController;
61 set_shortname( "Bonjour" )
62 set_description( N_( "Bonjour Network Discovery" ) )
63 set_category( CAT_PLAYLIST )
64 set_subcategory( SUBCAT_PLAYLIST_SD )
65 set_capability( "services_discovery", 0 )
66 set_callbacks( OpenSD, CloseSD )
67 add_shortcut( "mdns", "bonjour" )
68 VLC_SD_PROBE_SUBMODULE
70 set_description( N_( "Bonjour Renderer Discovery" ) )
71 set_category( CAT_SOUT )
72 set_subcategory( SUBCAT_SOUT_RENDERER )
73 set_capability( "renderer_discovery", 0 )
74 set_callbacks( OpenRD, CloseRD )
75 add_shortcut( "mdns_renderer", "bonjour_renderer" )
76 VLC_RD_PROBE_SUBMODULE
79 NSString *const VLCBonjourProtocolName = @"VLCBonjourProtocolName";
80 NSString *const VLCBonjourProtocolServiceName = @"VLCBonjourProtocolServiceName";
81 NSString *const VLCBonjourIsRenderer = @"VLCBonjourIsRenderer";
82 NSString *const VLCBonjourRendererFlags = @"VLCBonjourRendererFlags";
83 NSString *const VLCBonjourRendererDemux = @"VLCBonjourRendererDemux";
86 #pragma mark Interface definition
87 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
89 /* Stores all used service browsers, one for each protocol, usually */
90 NSArray *_serviceBrowsers;
92 /* Holds a required reference to all NSNetServices */
93 NSMutableArray *_rawNetServices;
95 /* Holds all successfully resolved NSNetServices */
96 NSMutableArray *_resolvedNetServices;
98 /* Holds the respective pointers to a vlc_object for each resolved and added NSNetService */
99 NSMutableArray *_inputItemsForNetServices;
101 /* Stores all protocols that are currently discovered */
102 NSArray *_activeProtocols;
105 @property (readonly) BOOL isRendererDiscovery;
106 @property (readonly, nonatomic) vlc_object_t *p_this;
108 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd;
109 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd;
111 - (void)startDiscovery;
112 - (void)stopDiscovery;
116 @implementation VLCNetServiceDiscoveryController
118 - (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd
122 _p_this = VLC_OBJECT( p_rd );
123 _isRendererDiscovery = YES;
129 - (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd
133 _p_this = VLC_OBJECT( p_sd );
134 _isRendererDiscovery = NO;
140 - (void)startDiscovery
142 NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName : @"ftp",
143 VLCBonjourProtocolServiceName : @"_ftp._tcp.",
144 VLCBonjourIsRenderer : @(NO)
146 NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName : @"smb",
147 VLCBonjourProtocolServiceName : @"_smb._tcp.",
148 VLCBonjourIsRenderer : @(NO)
150 NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName : @"nfs",
151 VLCBonjourProtocolServiceName : @"_nfs._tcp.",
152 VLCBonjourIsRenderer : @(NO)
154 NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName : @"sftp",
155 VLCBonjourProtocolServiceName: @"_sftp-ssh._tcp.",
156 VLCBonjourIsRenderer : @(NO)
158 NSDictionary *VLCCastProtocol = @{ VLCBonjourProtocolName : @"chromecast",
159 VLCBonjourProtocolServiceName: @"_googlecast._tcp.",
160 VLCBonjourIsRenderer : @(YES),
161 VLCBonjourRendererFlags : @(VLC_RENDERER_CAN_AUDIO),
162 VLCBonjourRendererDemux : @"cc_demux"
165 NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
171 _rawNetServices = [[NSMutableArray alloc] init];
172 _resolvedNetServices = [[NSMutableArray alloc] init];
173 _inputItemsForNetServices = [[NSMutableArray alloc] init];
175 NSMutableArray *discoverers = [[NSMutableArray alloc] init];
176 NSMutableArray *protocols = [[NSMutableArray alloc] init];
178 msg_Info(_p_this, "starting discovery");
179 for (NSDictionary *protocol in VLCSupportedProtocols) {
180 /* Only discover services if we actually have a module that can handle those */
181 if (!module_exists([[protocol objectForKey: VLCBonjourProtocolName] UTF8String]) && !_isRendererDiscovery) {
182 msg_Dbg(_p_this, "no module for %s, skipping", [[protocol objectForKey: VLCBonjourProtocolName] UTF8String]);
186 /* Only discover hosts it they match the current mode (renderer or service) */
187 if ([[protocol objectForKey: VLCBonjourIsRenderer] boolValue] != _isRendererDiscovery) {
188 msg_Dbg(_p_this, "%s does not match current discovery mode, skipping", [[protocol objectForKey: VLCBonjourProtocolName] UTF8String]);
192 NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
193 [serviceBrowser setDelegate:self];
194 msg_Dbg(_p_this, "starting discovery for type %s", [[protocol objectForKey: VLCBonjourProtocolServiceName] UTF8String]);
195 [serviceBrowser searchForServicesOfType:[protocol objectForKey: VLCBonjourProtocolServiceName] inDomain:@"local."];
196 [discoverers addObject:serviceBrowser];
197 [protocols addObject:protocol];
200 _serviceBrowsers = [discoverers copy];
201 _activeProtocols = [protocols copy];
204 - (void)stopDiscovery
206 [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
208 /* Work around a macOS 10.12 bug, see https://openradar.appspot.com/28943305 */
209 [_serviceBrowsers makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil];
210 [_resolvedNetServices makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil];
212 for (NSValue *item in _inputItemsForNetServices) {
213 if (_isRendererDiscovery) {
214 [self removeRawRendererItem:item];
216 [self removeRawInputItem:item];
220 [_inputItemsForNetServices removeAllObjects];
221 [_resolvedNetServices removeAllObjects];
222 msg_Info(_p_this, "stopped discovery");
226 #pragma mark Delegate methods
228 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
230 msg_Dbg(_p_this, "service found: %s (%s), resolving", [aNetService.name UTF8String], [aNetService.type UTF8String]);
231 [_rawNetServices addObject:aNetService];
232 aNetService.delegate = self;
233 [aNetService resolveWithTimeout:5.];
236 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
238 msg_Dbg(self.p_this, "service disappeared: %s (%s), removing", [aNetService.name UTF8String], [aNetService.type UTF8String]);
240 /* If the item was not looked-up yet, just remove it */
241 if ([_rawNetServices containsObject:aNetService])
242 [_rawNetServices removeObject:aNetService];
244 /* If the item was already resolved, the associated input or renderer items needs to be removed as well */
245 if ([_resolvedNetServices containsObject:aNetService]) {
246 NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
247 if (index == NSNotFound) {
251 [_resolvedNetServices removeObjectAtIndex:index];
253 if (_isRendererDiscovery) {
254 [self removeRawRendererItem:[_inputItemsForNetServices objectAtIndex:index]];
256 [self removeRawInputItem:[_inputItemsForNetServices objectAtIndex:index]];
259 /* Remove item pointer from our lookup array */
260 [_inputItemsForNetServices removeObjectAtIndex:index];
264 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
266 msg_Dbg(_p_this, "service resolved: %s", [aNetService.name UTF8String]);
267 if (![_resolvedNetServices containsObject:aNetService]) {
268 NSString *serviceType = aNetService.type;
269 NSString *protocol = nil;
270 for (NSDictionary *protocolDefinition in _activeProtocols) {
271 if ([serviceType isEqualToString:[protocolDefinition objectForKey:VLCBonjourProtocolServiceName]]) {
272 protocol = [protocolDefinition objectForKey:VLCBonjourProtocolName];
276 if (_isRendererDiscovery) {
277 [self addResolvedRendererItem:aNetService withProtocol:protocol];
279 [self addResolvedInputItem:aNetService withProtocol:protocol];
283 [_rawNetServices removeObject:aNetService];
286 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
288 msg_Warn(_p_this, "service resolution failed: %s, removing", [aNetService.name UTF8String]);
289 [_rawNetServices removeObject:aNetService];
293 #pragma mark Helper methods
295 - (void)addResolvedRendererItem:(NSNetService *)netService withProtocol:(NSString *)protocol
297 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
299 NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
300 NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData:[netService TXTRecordData]];
301 NSString *displayName = netService.name;
303 if ([netService.type isEqualToString:@"_googlecast._tcp."]) {
304 NSData *modelData = [txtDict objectForKey:@"md"];
305 NSData *nameData = [txtDict objectForKey:@"fn"];
306 if (modelData && nameData) {
307 NSString *model = [[NSString alloc] initWithData:modelData encoding:NSUTF8StringEncoding];
308 NSString *name = [[NSString alloc] initWithData:nameData encoding:NSUTF8StringEncoding];
309 displayName = [NSString stringWithFormat:@"%@ (%@)", name, model];
312 // TODO: Detect rendered capabilities and adapt to work with not just chromecast
313 vlc_renderer_item_t *p_renderer_item = vlc_renderer_item_new( "chromecast", [displayName UTF8String],
314 [uri UTF8String], NULL, "cc_demux",
315 "", VLC_RENDERER_CAN_VIDEO );
316 if (p_renderer_item != NULL) {
317 vlc_rd_add_item( p_rd, p_renderer_item );
318 [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_renderer_item]];
319 [_resolvedNetServices addObject:netService];
323 - (void)removeRawRendererItem:(NSValue *)item
325 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
326 vlc_renderer_item_t *input_item = [item pointerValue];
328 if (input_item != NULL) {
329 vlc_rd_remove_item( p_rd, input_item );
330 vlc_renderer_item_release( input_item );
334 - (void)addResolvedInputItem:(NSNetService *)netService withProtocol:(NSString *)protocol
336 services_discovery_t *p_sd = (services_discovery_t *)_p_this;
338 NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
339 input_item_t *p_input_item = input_item_NewDirectory([uri UTF8String], [netService.name UTF8String], ITEM_NET );
340 if (p_input_item != NULL) {
341 services_discovery_AddItem(p_sd, p_input_item);
342 [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
343 [_resolvedNetServices addObject:netService];
347 - (void)removeRawInputItem:(NSValue *)item
349 services_discovery_t *p_sd = (services_discovery_t *)_p_this;
350 input_item_t *input_item = [item pointerValue];
352 if (input_item != NULL) {
353 services_discovery_RemoveItem( p_sd, input_item );
354 input_item_Release( input_item );
360 static int OpenSD(vlc_object_t *p_this)
362 services_discovery_t *p_sd = (services_discovery_t *)p_this;
363 services_discovery_sys_t *p_sys = NULL;
365 p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
370 p_sd->description = _("Bonjour Network Discovery");
372 VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithServicesDiscoveryObject:p_sd];
374 p_sys->discoveryController = CFBridgingRetain(discoveryController);
376 [discoveryController startDiscovery];
381 static void CloseSD(vlc_object_t *p_this)
383 services_discovery_t *p_sd = (services_discovery_t *)p_this;
384 services_discovery_sys_t *p_sys = p_sd->p_sys;
386 VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
387 [discoveryController stopDiscovery];
389 CFBridgingRelease(p_sys->discoveryController);
390 discoveryController = nil;
395 static int OpenRD(vlc_object_t *p_this)
397 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
398 vlc_renderer_discovery_sys *p_sys = NULL;
400 p_rd->p_sys = p_sys = calloc(1, sizeof(vlc_renderer_discovery_sys));
405 VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithRendererDiscoveryObject:p_rd];
407 p_sys->discoveryController = CFBridgingRetain(discoveryController);
409 [discoveryController startDiscovery];
414 static void CloseRD(vlc_object_t *p_this)
416 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
417 vlc_renderer_discovery_sys *p_sys = p_rd->p_sys;
419 VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
420 [discoveryController stopDiscovery];
422 CFBridgingRelease(p_sys->discoveryController);
423 discoveryController = nil;