bonjour rd: Add bonjour renderer discovery submodule
authorMarvin Scholz <epirat07@gmail.com>
Mon, 5 Dec 2016 10:39:48 +0000 (11:39 +0100)
committerMarvin Scholz <epirat07@gmail.com>
Mon, 5 Dec 2016 18:29:33 +0000 (19:29 +0100)
Add a bonjour renderer submodule to the bonjour service discovery
module, so it can discover chromecast renderers (for now) and others
in the future.
There is still some work needed to make it detect chromecast
capabilities correctly and to not hardcode it to chromecast.
(See the TODO comment)

modules/services_discovery/bonjour.m

index 0cc076b..cd9cb3a 100644 (file)
@@ -4,6 +4,7 @@
  * Copyright (C) 2016 VLC authors, VideoLAN and VideoLabs
  *
  * Authors: Felix Paul Kühne <fkuehne@videolan.org>
  * Copyright (C) 2016 VLC authors, VideoLAN and VideoLabs
  *
  * Authors: Felix Paul Kühne <fkuehne@videolan.org>
+ *          Marvin Scholz <epirat07@gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
 #include <vlc_plugin.h>
 #include <vlc_modules.h>
 #include <vlc_services_discovery.h>
 #include <vlc_plugin.h>
 #include <vlc_modules.h>
 #include <vlc_services_discovery.h>
+#include <vlc_renderer_discovery.h>
 
 #import <Foundation/Foundation.h>
 
 
 #import <Foundation/Foundation.h>
 
-static int Open( vlc_object_t * );
-static void Close( vlc_object_t * );
+#pragma mark Function declarations
+
+static int OpenSD( vlc_object_t * );
+static void CloseSD( vlc_object_t * );
+
+static int OpenRD( vlc_object_t * );
+static void CloseRD( vlc_object_t * );
 
 VLC_SD_PROBE_HELPER( "Bonjour", N_("Bonjour Network Discovery"), SD_CAT_LAN )
 
 VLC_SD_PROBE_HELPER( "Bonjour", N_("Bonjour Network Discovery"), SD_CAT_LAN )
+VLC_RD_PROBE_HELPER( "Bonjour_renderer", "Bonjour Renderer Discovery" )
+
+struct services_discovery_sys_t
+{
+    CFTypeRef _Nullable discoveryController;
+};
+
+struct vlc_renderer_discovery_sys
+{
+    CFTypeRef _Nullable discoveryController;
+};
 
 /*
  * Module descriptor
 
 /*
  * Module descriptor
@@ -45,81 +63,137 @@ vlc_module_begin()
     set_category( CAT_PLAYLIST )
     set_subcategory( SUBCAT_PLAYLIST_SD )
     set_capability( "services_discovery", 0 )
     set_category( CAT_PLAYLIST )
     set_subcategory( SUBCAT_PLAYLIST_SD )
     set_capability( "services_discovery", 0 )
-    set_callbacks( Open, Close )
+    set_callbacks( OpenSD, CloseSD )
     add_shortcut( "mdns", "bonjour" )
     add_shortcut( "mdns", "bonjour" )
-
     VLC_SD_PROBE_SUBMODULE
     VLC_SD_PROBE_SUBMODULE
-vlc_module_end ()
-
-NSString *const VLCBonjourProtocolName = @"VLCBonjourProtocolName";
-NSString *const VLCBonjourProtocolServiceName = @"VLCBonjourProtocolServiceName";
-
+    add_submodule() \
+        set_description( N_( "Bonjour Renderer Discovery" ) )
+        set_category( CAT_SOUT )
+        set_subcategory( SUBCAT_SOUT_RENDERER )
+        set_capability( "renderer_discovery", 0 )
+        set_callbacks( OpenRD, CloseRD )
+        add_shortcut( "mdns_renderer", "bonjour_renderer" )
+        VLC_RD_PROBE_SUBMODULE
+vlc_module_end()
+
+NSString *const VLCBonjourProtocolName          = @"VLCBonjourProtocolName";
+NSString *const VLCBonjourProtocolServiceName   = @"VLCBonjourProtocolServiceName";
+NSString *const VLCBonjourIsRenderer            = @"VLCBonjourIsRenderer";
+NSString *const VLCBonjourRendererFlags         = @"VLCBonjourRendererFlags";
+NSString *const VLCBonjourRendererDemux         = @"VLCBonjourRendererDemux";
+
+#pragma mark -
+#pragma mark Interface definition
 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
 {
 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
 {
-#ifdef MAC_OS_X_VERSION_10_11
-    NSArray<NSNetServiceBrowser *> *_serviceBrowsers;
-
-    NSMutableArray<NSNetService *> *_rawNetServices;
-    NSMutableArray<NSNetService *> *_resolvedNetServices;
-    NSMutableArray<NSValue *> *_inputItemsForNetServices;
-#else
+    /* Stores all used service browsers, one for each protocol, usually */
     NSArray *_serviceBrowsers;
 
     NSArray *_serviceBrowsers;
 
+    /* Holds a required reference to all NSNetServices */
     NSMutableArray *_rawNetServices;
     NSMutableArray *_rawNetServices;
+
+    /* Holds all successfully resolved NSNetServices */
     NSMutableArray *_resolvedNetServices;
     NSMutableArray *_resolvedNetServices;
+
+    /* Holds the respective pointers to a vlc_object for each resolved and added NSNetService */
     NSMutableArray *_inputItemsForNetServices;
     NSMutableArray *_inputItemsForNetServices;
-#endif
 
 
+    /* Stores all protocols that are currently discovered */
     NSArray *_activeProtocols;
 }
 
     NSArray *_activeProtocols;
 }
 
-@property (readwrite, nonatomic) services_discovery_t *p_sd;
+@property (readonly) BOOL isRendererDiscovery;
+@property (readonly, nonatomic) vlc_object_t *p_this;
+
+- (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd;
+- (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd;
 
 - (void)startDiscovery;
 - (void)stopDiscovery;
 
 @end
 
 
 - (void)startDiscovery;
 - (void)stopDiscovery;
 
 @end
 
-struct services_discovery_sys_t
+@implementation VLCNetServiceDiscoveryController
+
+- (instancetype)initWithRendererDiscoveryObject:(vlc_renderer_discovery_t *)p_rd
 {
 {
-    CFTypeRef _Nullable discoveryController;
-};
+    self = [super init];
+    if (self) {
+        _p_this = VLC_OBJECT( p_rd );
+        _isRendererDiscovery = YES;
+    }
 
 
-@implementation VLCNetServiceDiscoveryController
+    return self;
+}
+
+- (instancetype)initWithServicesDiscoveryObject:(services_discovery_t *)p_sd
+{
+    self = [super init];
+    if (self) {
+        _p_this = VLC_OBJECT( p_sd );
+        _isRendererDiscovery = NO;
+    }
+
+    return self;
+}
 
 - (void)startDiscovery
 {
 
 - (void)startDiscovery
 {
+    NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName        : @"ftp",
+                                      VLCBonjourProtocolServiceName : @"_ftp._tcp.",
+                                      VLCBonjourIsRenderer          : @(NO)
+                                      };
+    NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName        : @"smb",
+                                      VLCBonjourProtocolServiceName : @"_smb._tcp.",
+                                      VLCBonjourIsRenderer          : @(NO)
+                                      };
+    NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName        : @"nfs",
+                                      VLCBonjourProtocolServiceName : @"_nfs._tcp.",
+                                      VLCBonjourIsRenderer          : @(NO)
+                                      };
+    NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName       : @"sftp",
+                                       VLCBonjourProtocolServiceName: @"_sftp-ssh._tcp.",
+                                       VLCBonjourIsRenderer         : @(NO)
+                                       };
+    NSDictionary *VLCCastProtocol = @{ VLCBonjourProtocolName       : @"chromecast",
+                                       VLCBonjourProtocolServiceName: @"_googlecast._tcp.",
+                                       VLCBonjourIsRenderer         : @(YES),
+                                       VLCBonjourRendererFlags      : @(VLC_RENDERER_CAN_AUDIO),
+                                       VLCBonjourRendererDemux      : @"cc_demux"
+                                       };
+
+    NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
+                                      VLCSmbProtocol,
+                                      VLCNfsProtocol,
+                                      VLCSftpProtocol,
+                                      VLCCastProtocol];
+
     _rawNetServices = [[NSMutableArray alloc] init];
     _resolvedNetServices = [[NSMutableArray alloc] init];
     _inputItemsForNetServices = [[NSMutableArray alloc] init];
 
     _rawNetServices = [[NSMutableArray alloc] init];
     _resolvedNetServices = [[NSMutableArray alloc] init];
     _inputItemsForNetServices = [[NSMutableArray alloc] init];
 
-    NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName : @"ftp",
-                                      VLCBonjourProtocolServiceName : @"_ftp._tcp." };
-    NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName : @"smb",
-                                      VLCBonjourProtocolServiceName : @"_smb._tcp." };
-    NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName : @"nfs",
-                                      VLCBonjourProtocolServiceName : @"_nfs._tcp." };
-    NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName : @"sftp",
-                                       VLCBonjourProtocolServiceName : @"_sftp-ssh._tcp." };
-
-    NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
-                                       VLCSmbProtocol,
-                                       VLCNfsProtocol,
-                                       VLCSftpProtocol];
-
-    NSUInteger count = VLCSupportedProtocols.count;
     NSMutableArray *discoverers = [[NSMutableArray alloc] init];
     NSMutableArray *protocols = [[NSMutableArray alloc] init];
 
     NSMutableArray *discoverers = [[NSMutableArray alloc] init];
     NSMutableArray *protocols = [[NSMutableArray alloc] init];
 
-    for (NSUInteger i = 0; i < count; i++) {
-        NSDictionary *protocol = VLCSupportedProtocols[i];
+    msg_Info(_p_this, "starting discovery");
+    for (NSDictionary *protocol in VLCSupportedProtocols) {
+        msg_Info(_p_this, "looking up %s", [protocol[VLCBonjourProtocolName] UTF8String]);
 
 
-        /* only discover hosts if we actually have a module that can handle those */
-        if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]))
+        /* Only discover services if we actually have a module that can handle those */
+        if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]) && !_isRendererDiscovery) {
+            msg_Info(_p_this, "no module for %s, skipping", [protocol[VLCBonjourProtocolName] UTF8String]);
             continue;
             continue;
+        }
+
+        /* Only discover hosts it they match the current mode (renderer or service) */
+        if ([protocol[VLCBonjourIsRenderer] boolValue] != _isRendererDiscovery) {
+            msg_Info(_p_this, "%s does not match current discovery mode, skipping", [protocol[VLCBonjourProtocolName] UTF8String]);
+            continue;
+        }
 
         NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
 
         NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
-        serviceBrowser.delegate = self;
+        [serviceBrowser setDelegate:self];
+        msg_Info(_p_this, "starting discovery for type %s", [protocol[VLCBonjourProtocolServiceName] UTF8String]);
         [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
         [discoverers addObject:serviceBrowser];
         [protocols addObject:protocol];
         [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
         [discoverers addObject:serviceBrowser];
         [protocols addObject:protocol];
@@ -133,24 +207,26 @@ struct services_discovery_sys_t
 {
     [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
 
 {
     [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
 
-    NSUInteger inputItemCount = _inputItemsForNetServices.count;
-    for (NSUInteger i = 0; i < inputItemCount; i++) {
-        input_item_t *p_input_item = [_inputItemsForNetServices[i] pointerValue];
-        if (p_input_item != NULL) {
-            services_discovery_RemoveItem(self.p_sd, p_input_item);
-            input_item_Release(p_input_item);
+    for (NSValue *item in _inputItemsForNetServices) {
+        if (_isRendererDiscovery) {
+            [self removeRawRendererItem:item];
+        } else {
+            [self removeRawInputItem:item];
         }
     }
 
     [_inputItemsForNetServices removeAllObjects];
     [_resolvedNetServices removeAllObjects];
         }
     }
 
     [_inputItemsForNetServices removeAllObjects];
     [_resolvedNetServices removeAllObjects];
+    msg_Info(_p_this, "stopped discovery");
 }
 
 }
 
-#pragma mark - functional delegation
+#pragma mark - 
+#pragma mark Delegate methods
 
 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
 {
 
 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
 {
-    msg_Dbg(self.p_sd, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
+    msg_Info(_p_this, "found something, looking up");
+    msg_Dbg(self.p_this, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
     [_rawNetServices addObject:aNetService];
     aNetService.delegate = self;
     [aNetService resolveWithTimeout:5.];
     [_rawNetServices addObject:aNetService];
     aNetService.delegate = self;
     [aNetService resolveWithTimeout:5.];
@@ -158,50 +234,48 @@ struct services_discovery_sys_t
 
 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
 {
 
 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
 {
-    msg_Dbg(self.p_sd, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
+    msg_Dbg(self.p_this, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
+
+    /* If the item was not looked-up yet, just remove it */
     if ([_rawNetServices containsObject:aNetService])
         [_rawNetServices removeObject:aNetService];
 
     if ([_rawNetServices containsObject:aNetService])
         [_rawNetServices removeObject:aNetService];
 
+    /* If the item was already resolved, the associated input or renderer items needs to be removed as well */
     if ([_resolvedNetServices containsObject:aNetService]) {
         NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
     if ([_resolvedNetServices containsObject:aNetService]) {
         NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
-        if (index == NSNotFound)
+        if (index == NSNotFound) {
             return;
             return;
+        }
 
         [_resolvedNetServices removeObjectAtIndex:index];
 
         [_resolvedNetServices removeObjectAtIndex:index];
-        input_item_t *p_input_item = [_inputItemsForNetServices[index] pointerValue];
-        if (p_input_item != NULL) {
-            services_discovery_RemoveItem(self.p_sd, p_input_item);
-            input_item_Release(p_input_item);
+
+        if (_isRendererDiscovery) {
+            [self removeRawRendererItem:_inputItemsForNetServices[index]];
+        } else {
+            [self removeRawInputItem:_inputItemsForNetServices[index]];
         }
         }
+
+        /* Remove item pointer from our lookup array */
+        [_inputItemsForNetServices removeObjectAtIndex:index];
     }
 }
 
 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
 {
     }
 }
 
 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
 {
+    msg_Info(_p_this, "resolved something");
     if (![_resolvedNetServices containsObject:aNetService]) {
         NSString *serviceType = aNetService.type;
     if (![_resolvedNetServices containsObject:aNetService]) {
         NSString *serviceType = aNetService.type;
-        NSUInteger count = _activeProtocols.count;
         NSString *protocol = nil;
         NSString *protocol = nil;
-        for (NSUInteger i = 0; i < count; i++) {
-            NSDictionary *protocolDefinition = _activeProtocols[i];
+        for (NSDictionary *protocolDefinition in _activeProtocols) {
             if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
                 protocol = protocolDefinition[VLCBonjourProtocolName];
             }
         }
 
             if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
                 protocol = protocolDefinition[VLCBonjourProtocolName];
             }
         }
 
-        NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld",
-                         protocol,
-                         aNetService.hostName,
-                         aNetService.port];
-
-        input_item_t *p_input_item = input_item_NewDirectory([uri UTF8String],
-                                                             [aNetService.name UTF8String],
-                                                             ITEM_NET );
-
-        if (p_input_item != NULL) {
-            services_discovery_AddItem(self.p_sd, p_input_item, NULL);
-            [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
-            [_resolvedNetServices addObject:aNetService];
+        if (_isRendererDiscovery) {
+            [self addResolvedRendererItem:aNetService withProtocol:protocol];
+        } else {
+            [self addResolvedInputItem:aNetService withProtocol:protocol];
         }
     }
 
         }
     }
 
@@ -210,13 +284,69 @@ struct services_discovery_sys_t
 
 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
 {
 
 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
 {
-    msg_Dbg(self.p_sd, "failed to resolve: %s", [aNetService.name UTF8String]);
+    msg_Dbg(_p_this, "failed to resolve: %s", [aNetService.name UTF8String]);
     [_rawNetServices removeObject:aNetService];
 }
 
     [_rawNetServices removeObject:aNetService];
 }
 
+#pragma mark -
+#pragma mark Helper methods
+
+- (void)addResolvedRendererItem:(NSNetService *)netService withProtocol:(NSString *)protocol
+{
+    vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
+
+    NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
+    NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData:[netService TXTRecordData]];
+
+    // TODO: Detect rendered capabilities and adapt to work with not just chromecast
+    vlc_renderer_item_t *p_renderer_item = vlc_renderer_item_new( "chromecast", [netService.name UTF8String],
+                                                                 [uri UTF8String], NULL, "cc_demux",
+                                                                 "", VLC_RENDERER_CAN_VIDEO );
+    if (p_renderer_item != NULL) {
+        vlc_rd_add_item( p_rd, p_renderer_item );
+        [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_renderer_item]];
+        [_resolvedNetServices addObject:netService];
+    }
+}
+
+- (void)removeRawRendererItem:(NSValue *)item
+{
+    vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)_p_this;
+    vlc_renderer_item_t *input_item = [item pointerValue];
+
+    if (input_item != NULL) {
+        vlc_rd_remove_item( p_rd, input_item );
+        vlc_renderer_item_release( input_item );
+    }
+}
+
+- (void)addResolvedInputItem:(NSNetService *)netService withProtocol:(NSString *)protocol
+{
+    services_discovery_t *p_sd = (services_discovery_t *)_p_this;
+
+    NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld", protocol, netService.hostName, netService.port];
+    input_item_t *p_input_item = input_item_NewDirectory([uri UTF8String], [netService.name UTF8String], ITEM_NET );
+    if (p_input_item != NULL) {
+        services_discovery_AddItem( p_sd, p_input_item, NULL);
+        [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
+        [_resolvedNetServices addObject:netService];
+    }
+}
+
+- (void)removeRawInputItem:(NSValue *)item
+{
+    services_discovery_t *p_sd = (services_discovery_t *)_p_this;
+    input_item_t *input_item = [item pointerValue];
+
+    if (input_item != NULL) {
+        services_discovery_RemoveItem( p_sd, input_item );
+        input_item_Release( input_item );
+    }
+}
+
 @end
 
 @end
 
-static int Open(vlc_object_t *p_this)
+static int OpenSD(vlc_object_t *p_this)
 {
     services_discovery_t *p_sd = (services_discovery_t *)p_this;
     services_discovery_sys_t *p_sys = NULL;
 {
     services_discovery_t *p_sd = (services_discovery_t *)p_this;
     services_discovery_sys_t *p_sys = NULL;
@@ -228,8 +358,7 @@ static int Open(vlc_object_t *p_this)
 
     p_sd->description = _("Bonjour Network Discovery");
 
 
     p_sd->description = _("Bonjour Network Discovery");
 
-    VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] init];
-    discoveryController.p_sd = p_sd;
+    VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithServicesDiscoveryObject:p_sd];
 
     p_sys->discoveryController = CFBridgingRetain(discoveryController);
 
 
     p_sys->discoveryController = CFBridgingRetain(discoveryController);
 
@@ -238,7 +367,7 @@ static int Open(vlc_object_t *p_this)
     return VLC_SUCCESS;
 }
 
     return VLC_SUCCESS;
 }
 
-static void Close(vlc_object_t *p_this)
+static void CloseSD(vlc_object_t *p_this)
 {
     services_discovery_t *p_sd = (services_discovery_t *)p_this;
     services_discovery_sys_t *p_sys = p_sd->p_sys;
 {
     services_discovery_t *p_sd = (services_discovery_t *)p_this;
     services_discovery_sys_t *p_sys = p_sd->p_sys;
@@ -251,3 +380,36 @@ static void Close(vlc_object_t *p_this)
 
     free(p_sys);
 }
 
     free(p_sys);
 }
+
+static int OpenRD(vlc_object_t *p_this)
+{
+    vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
+    vlc_renderer_discovery_sys *p_sys = NULL;
+
+    p_rd->p_sys = p_sys = calloc(1, sizeof(vlc_renderer_discovery_sys));
+    if (!p_sys) {
+        return VLC_ENOMEM;
+    }
+
+    VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] initWithRendererDiscoveryObject:p_rd];
+
+    p_sys->discoveryController = CFBridgingRetain(discoveryController);
+
+    [discoveryController startDiscovery];
+
+    return VLC_SUCCESS;
+}
+
+static void CloseRD(vlc_object_t *p_this)
+{
+    vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this;
+    vlc_renderer_discovery_sys *p_sys = p_rd->p_sys;
+
+    VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
+    [discoveryController stopDiscovery];
+
+    CFBridgingRelease(p_sys->discoveryController);
+    discoveryController = nil;
+
+    free(p_sys);
+}