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 msg_Info(_p_this, "looking up %s", [protocol[VLCBonjourProtocolName] UTF8String]);
182 /* Only discover services if we actually have a module that can handle those */
183 if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]) && !_isRendererDiscovery) {
184 msg_Info(_p_this, "no module for %s, skipping", [protocol[VLCBonjourProtocolName] UTF8String]);
188 /* Only discover hosts it they match the current mode (renderer or service) */
189 if ([protocol[VLCBonjourIsRenderer] boolValue] != _isRendererDiscovery) {
190 msg_Info(_p_this, "%s does not match current discovery mode, skipping", [protocol[VLCBonjourProtocolName] UTF8String]);
194 NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
195 [serviceBrowser setDelegate:self];
196 msg_Info(_p_this, "starting discovery for type %s", [protocol[VLCBonjourProtocolServiceName] UTF8String]);
197 [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
198 [discoverers addObject:serviceBrowser];
199 [protocols addObject:protocol];
202 _serviceBrowsers = [discoverers copy];
203 _activeProtocols = [protocols copy];
206 - (void)stopDiscovery
208 [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
210 for (NSValue *item in _inputItemsForNetServices) {
211 if (_isRendererDiscovery) {
212 [self removeRawRendererItem:item];
214 [self removeRawInputItem:item];
218 [_inputItemsForNetServices removeAllObjects];
219 [_resolvedNetServices removeAllObjects];
220 msg_Info(_p_this, "stopped discovery");
224 #pragma mark Delegate methods
226 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
228 msg_Info(_p_this, "found something, looking up");
229 msg_Dbg(self.p_this, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
230 [_rawNetServices addObject:aNetService];
231 aNetService.delegate = self;
232 [aNetService resolveWithTimeout:5.];
235 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
237 msg_Dbg(self.p_this, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
239 /* If the item was not looked-up yet, just remove it */
240 if ([_rawNetServices containsObject:aNetService])
241 [_rawNetServices removeObject:aNetService];
243 /* If the item was already resolved, the associated input or renderer items needs to be removed as well */
244 if ([_resolvedNetServices containsObject:aNetService]) {
245 NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
246 if (index == NSNotFound) {
250 [_resolvedNetServices removeObjectAtIndex:index];
252 if (_isRendererDiscovery) {
253 [self removeRawRendererItem:_inputItemsForNetServices[index]];
255 [self removeRawInputItem:_inputItemsForNetServices[index]];
258 /* Remove item pointer from our lookup array */
259 [_inputItemsForNetServices removeObjectAtIndex:index];
263 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
265 msg_Info(_p_this, "resolved something");
266 if (![_resolvedNetServices containsObject:aNetService]) {
267 NSString *serviceType = aNetService.type;
268 NSString *protocol = nil;
269 for (NSDictionary *protocolDefinition in _activeProtocols) {
270 if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
271 protocol = protocolDefinition[VLCBonjourProtocolName];
275 if (_isRendererDiscovery) {
276 [self addResolvedRendererItem:aNetService withProtocol:protocol];
278 [self addResolvedInputItem:aNetService withProtocol:protocol];
282 [_rawNetServices removeObject:aNetService];
285 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
287 msg_Dbg(_p_this, "failed to resolve: %s", [aNetService.name UTF8String]);
288 [_rawNetServices removeObject:aNetService];
292 #pragma mark Helper methods
294 - (void)addResolvedRendererItem:(NSNetService *)netService withProtocol:(NSString *)protocol
296 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
298 NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
299 NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData:[netService TXTRecordData]];
301 // TODO: Detect rendered capabilities and adapt to work with not just chromecast
302 vlc_renderer_item_t *p_renderer_item = vlc_renderer_item_new( "chromecast", [netService.name UTF8String],
303 [uri UTF8String], NULL, "cc_demux",
304 "", VLC_RENDERER_CAN_VIDEO );
305 if (p_renderer_item != NULL) {
306 vlc_rd_add_item( p_rd, p_renderer_item );
307 [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_renderer_item]];
308 [_resolvedNetServices addObject:netService];
312 - (void)removeRawRendererItem:(NSValue *)item
314 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
315 vlc_renderer_item_t *input_item = [item pointerValue];
317 if (input_item != NULL) {
318 vlc_rd_remove_item( p_rd, input_item );
319 vlc_renderer_item_release( input_item );
323 - (void)addResolvedInputItem:(NSNetService *)netService withProtocol:(NSString *)protocol
325 services_discovery_t *p_sd = (services_discovery_t *)_p_this;
327 NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
328 input_item_t *p_input_item = input_item_NewDirectory([uri UTF8String], [netService.name UTF8String], ITEM_NET );
329 if (p_input_item != NULL) {
330 services_discovery_AddItem( p_sd, p_input_item, NULL);
331 [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
332 [_resolvedNetServices addObject:netService];
336 - (void)removeRawInputItem:(NSValue *)item
338 services_discovery_t *p_sd = (services_discovery_t *)_p_this;
339 input_item_t *input_item = [item pointerValue];
341 if (input_item != NULL) {
342 services_discovery_RemoveItem( p_sd, input_item );
343 input_item_Release( input_item );
349 static int OpenSD(vlc_object_t *p_this)
351 services_discovery_t *p_sd = (services_discovery_t *)p_this;
352 services_discovery_sys_t *p_sys = NULL;
354 p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
359 p_sd->description = _("Bonjour Network Discovery");
361 VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithServicesDiscoveryObject:p_sd];
363 p_sys->discoveryController = CFBridgingRetain(discoveryController);
365 [discoveryController startDiscovery];
370 static void CloseSD(vlc_object_t *p_this)
372 services_discovery_t *p_sd = (services_discovery_t *)p_this;
373 services_discovery_sys_t *p_sys = p_sd->p_sys;
375 VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
376 [discoveryController stopDiscovery];
378 CFBridgingRelease(p_sys->discoveryController);
379 discoveryController = nil;
384 static int OpenRD(vlc_object_t *p_this)
386 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
387 vlc_renderer_discovery_sys *p_sys = NULL;
389 p_rd->p_sys = p_sys = calloc(1, sizeof(vlc_renderer_discovery_sys));
394 VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithRendererDiscoveryObject:p_rd];
396 p_sys->discoveryController = CFBridgingRetain(discoveryController);
398 [discoveryController startDiscovery];
403 static void CloseRD(vlc_object_t *p_this)
405 vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
406 vlc_renderer_discovery_sys *p_sys = p_rd->p_sys;
408 VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
409 [discoveryController stopDiscovery];
411 CFBridgingRelease(p_sys->discoveryController);
412 discoveryController = nil;