macosx: Implement renderer discovery as NSMenu
authorMarvin Scholz <epirat07@gmail.com>
Sat, 24 Jun 2017 16:56:17 +0000 (18:56 +0200)
committerMarvin Scholz <epirat07@gmail.com>
Sat, 1 Jul 2017 13:20:21 +0000 (15:20 +0200)
extras/package/macosx/VLC.xcodeproj/project.pbxproj
modules/gui/macosx/Makefile.am
modules/gui/macosx/UI/MainMenu.xib
modules/gui/macosx/VLCMainMenu.h
modules/gui/macosx/VLCMainMenu.m
modules/gui/macosx/VLCRendererDialog.h [deleted file]
modules/gui/macosx/VLCRendererDialog.m [deleted file]
modules/gui/macosx/VLCRendererMenuController.h [new file with mode: 0644]
modules/gui/macosx/VLCRendererMenuController.m [new file with mode: 0644]

index bd71b16..ce59f81 100644 (file)
@@ -57,7 +57,7 @@
                1C3113ED1E508C6900D4DD76 /* VLCTimeSelectionPanelController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CD3667A1B7A242E0054E39F /* VLCTimeSelectionPanelController.m */; };
                1C3113EF1E508C7600D4DD76 /* VLCRendererDiscovery.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BBBB8B61D114628001BD9BA /* VLCRendererDiscovery.m */; };
                1C3113F11E508C7600D4DD76 /* VLCRendererItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BBBB8B11D10A994001BD9BA /* VLCRendererItem.m */; };
-               1C3113F31E508C7600D4DD76 /* VLCRendererDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BBBB8A81D108A17001BD9BA /* VLCRendererDialog.m */; };
+               1C3113F31E508C7600D4DD76 /* VLCRendererMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BBBB8A81D108A17001BD9BA /* VLCRendererMenuController.m */; };
                1C3113F51E508C7600D4DD76 /* VLCHUDTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B846FEA1CF5DBFF00112E54 /* VLCHUDTextFieldCell.m */; };
                1C3113F71E508C7600D4DD76 /* VLCHUDButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B846FDD1CF5D88C00112E54 /* VLCHUDButtonCell.m */; };
                1C3113F91E508C7600D4DD76 /* VLCHUDCheckboxCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B846FDF1CF5D88C00112E54 /* VLCHUDCheckboxCell.m */; };
                6BBB05DF1EEFF165003A1019 /* VLCHUDTableCornerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCHUDTableCornerView.m; sourceTree = "<group>"; };
                6BBB05E11EF08181003A1019 /* VLCHUDSegmentedCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCHUDSegmentedCell.h; sourceTree = "<group>"; };
                6BBB05E21EF08181003A1019 /* VLCHUDSegmentedCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCHUDSegmentedCell.m; sourceTree = "<group>"; };
-               6BBBB8A71D108A17001BD9BA /* VLCRendererDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCRendererDialog.h; sourceTree = "<group>"; };
-               6BBBB8A81D108A17001BD9BA /* VLCRendererDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCRendererDialog.m; sourceTree = "<group>"; };
+               6BBBB8A71D108A17001BD9BA /* VLCRendererMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCRendererMenuController.h; sourceTree = "<group>"; };
+               6BBBB8A81D108A17001BD9BA /* VLCRendererMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCRendererMenuController.m; sourceTree = "<group>"; };
                6BBBB8B01D10A994001BD9BA /* VLCRendererItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCRendererItem.h; sourceTree = "<group>"; };
                6BBBB8B11D10A994001BD9BA /* VLCRendererItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VLCRendererItem.m; sourceTree = "<group>"; };
                6BBBB8B51D114628001BD9BA /* VLCRendererDiscovery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VLCRendererDiscovery.h; sourceTree = "<group>"; };
                                6BBBB8B61D114628001BD9BA /* VLCRendererDiscovery.m */,
                                6BBBB8B01D10A994001BD9BA /* VLCRendererItem.h */,
                                6BBBB8B11D10A994001BD9BA /* VLCRendererItem.m */,
-                               6BBBB8A71D108A17001BD9BA /* VLCRendererDialog.h */,
-                               6BBBB8A81D108A17001BD9BA /* VLCRendererDialog.m */,
+                               6BBBB8A71D108A17001BD9BA /* VLCRendererMenuController.h */,
+                               6BBBB8A81D108A17001BD9BA /* VLCRendererMenuController.m */,
                        );
                        name = Renderers;
                        sourceTree = "<group>";
                                1C31140D1E508C8800D4DD76 /* SPMediaKeyTap.m in Sources */,
                                1C3113EF1E508C7600D4DD76 /* VLCRendererDiscovery.m in Sources */,
                                1C3113F11E508C7600D4DD76 /* VLCRendererItem.m in Sources */,
-                               1C3113F31E508C7600D4DD76 /* VLCRendererDialog.m in Sources */,
+                               1C3113F31E508C7600D4DD76 /* VLCRendererMenuController.m in Sources */,
                                1C3113F51E508C7600D4DD76 /* VLCHUDTextFieldCell.m in Sources */,
                                1C3113F71E508C7600D4DD76 /* VLCHUDButtonCell.m in Sources */,
                                6B4D50931E7979CB004479B5 /* VLCSimplePrefsWindow.m in Sources */,
index 35dd88b..7a54fc7 100644 (file)
@@ -87,7 +87,7 @@ libmacosx_plugin_la_SOURCES = \
        gui/macosx/VLCMainWindow.h gui/macosx/VLCMainWindow.m \
        gui/macosx/VLCRendererDiscovery.h gui/macosx/VLCRendererDiscovery.m \
        gui/macosx/VLCRendererItem.h gui/macosx/VLCRendererItem.m \
-       gui/macosx/VLCRendererDialog.h gui/macosx/VLCRendererDialog.m \
+       gui/macosx/VLCRendererMenuController.h gui/macosx/VLCRendererMenuController.m \
        gui/macosx/VLCResumeDialogController.h gui/macosx/VLCResumeDialogController.m \
        gui/macosx/VLCTrackSynchronizationWindowController.h gui/macosx/VLCTrackSynchronizationWindowController.m \
        gui/macosx/VLCVideoEffectsWindowController.h gui/macosx/VLCVideoEffectsWindowController.m \
index 3bcfccf..4487898 100644 (file)
                 <outlet property="rate_slowerLabel" destination="4600" id="vRN-S8-MSb"/>
                 <outlet property="rate_view" destination="4596" id="MPC-hG-6bj"/>
                 <outlet property="record" destination="5137" id="Ka3-jl-auY"/>
-                <outlet property="renderer" destination="Rf6-Uj-7Ja" id="Nzc-Qb-SnK"/>
                 <outlet property="repeat" destination="5143" id="ulG-wD-gtG"/>
                 <outlet property="revealInFinder" destination="3945" id="z7n-A2-K5o"/>
                 <outlet property="save_playlist" destination="1599" id="spC-pk-6W4"/>
                             <menuItem isSeparatorItem="YES" id="5154">
                                 <modifierMask key="keyEquivalentModifierMask" command="YES"/>
                             </menuItem>
-                            <menuItem title="Select Renderer…" id="Rf6-Uj-7Ja">
+                            <menuItem title="Renderer" id="wYl-A2-q30">
                                 <modifierMask key="keyEquivalentModifierMask"/>
-                                <connections>
-                                    <action selector="showRenderers:" target="-2" id="Y9L-YQ-3RJ"/>
-                                </connections>
+                                <menu key="submenu" title="Renderer" id="ODg-VN-8Tv">
+                                    <items>
+                                        <menuItem title="Renderer discovery off" enabled="NO" id="kZC-S6-b2M">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                        </menuItem>
+                                        <menuItem title="Enable renderer discovery" id="5gR-ec-KSL">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleRendererDiscovery:" target="GfD-zq-JqC" id="tGk-pM-MVQ"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="sbF-Ji-lAl"/>
+                                        <menuItem title="No Renderer" state="on" id="eE8-qf-9x9">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="selectRenderer:" target="GfD-zq-JqC" id="JA3-zV-4I0"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                    <connections>
+                                        <outlet property="delegate" destination="GfD-zq-JqC" id="9Fg-VF-zbg"/>
+                                    </connections>
+                                </menu>
                             </menuItem>
                             <menuItem title="Program" id="5150">
                                 <menu key="submenu" title="Program" id="5157"/>
             </constraints>
             <point key="canvasLocation" x="479.5" y="564.5"/>
         </customView>
+        <customObject id="GfD-zq-JqC" customClass="VLCRendererMenuController">
+            <connections>
+                <outlet property="rendererDiscoveryState" destination="kZC-S6-b2M" id="Ur7-YL-R4x"/>
+                <outlet property="rendererDiscoveryToggle" destination="5gR-ec-KSL" id="CEI-18-tX4"/>
+                <outlet property="rendererMenu" destination="ODg-VN-8Tv" id="bMa-lk-ryz"/>
+                <outlet property="rendererNoneItem" destination="eE8-qf-9x9" id="p9J-VU-Dg9"/>
+            </connections>
+        </customObject>
         <userDefaultsController representsSharedInstance="YES" id="uFO-sP-uo6"/>
     </objects>
 </document>
index 36a69dd..2c25d38 100644 (file)
@@ -99,7 +99,6 @@
 @property (readwrite, weak) IBOutlet NSMenu *titleMenu;
 @property (readwrite, weak) IBOutlet NSMenuItem *chapter;
 @property (readwrite, weak) IBOutlet NSMenu *chapterMenu;
-@property (readwrite, weak) IBOutlet NSMenuItem *renderer;
 
 @property (readwrite, weak) IBOutlet NSMenu *audioMenu;
 @property (readwrite, weak) IBOutlet NSMenuItem *vol_up;
index 38b572e..af87e3b 100644 (file)
@@ -49,7 +49,6 @@
 #import "VLCDebugMessageWindowController.h"
 #import "VLCAddonsWindowController.h"
 #import "VLCTimeSelectionPanelController.h"
-#import "VLCRendererDialog.h"
 #import "NSScreen+VLCAdditions.h"
 
 #ifdef HAVE_SPARKLE
@@ -61,7 +60,6 @@
     VLCAboutWindowController *_aboutWindowController;
     VLCHelpWindowController  *_helpWindowController;
     VLCAddonsWindowController *_addonsController;
-    VLCRendererDialog *_rendererDialog;
 
     NSMenu *_playlistTableColumnsContextMenu;
 
     [_titleMenu setTitle: _NS("Title")];
     [_chapter setTitle: _NS("Chapter")];
     [_chapterMenu setTitle: _NS("Chapter")];
-    [_renderer setTitle: _NS("Select Renderer…")];
 
     [_audioMenu setTitle: _NS("Audio")];
     [_vol_up setTitle: _NS("Increase Volume")];
     [_helpWindowController showHelp];
 }
 
-- (IBAction)showRenderers:(id)sender
-{
-    if (!_rendererDialog)
-        _rendererDialog = [[VLCRendererDialog alloc] init];
-
-    [_rendererDialog showWindow:self];
-}
-
 - (IBAction)openDocumentation:(id)sender
 {
     NSURL *url = [NSURL URLWithString: @"http://www.videolan.org/doc/"];
diff --git a/modules/gui/macosx/VLCRendererDialog.h b/modules/gui/macosx/VLCRendererDialog.h
deleted file mode 100644 (file)
index 1203610..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*****************************************************************************
- * VLCRendererDialog.h: View controller class for the renderer dialog
- *****************************************************************************
- * Copyright (C) 2016 VLC authors and VideoLAN
- * $Id$
- *
- * Authors: Marvin Scholz <epirat07 at gmail dot com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#import <Cocoa/Cocoa.h>
-
-#import "VLCRendererItem.h"
-#import "VLCRendererDiscovery.h"
-
-@interface VLCRendererDialog : NSWindowController <VLCRendererDiscoveryDelegate, NSWindowDelegate>
-
-#ifdef MAC_OS_X_VERSION_10_11
-@property NSMutableArray<VLCRendererItem*> *rendererItems;
-#else
-@property NSMutableArray *rendererItems;
-#endif
-
-- (IBAction)selectRenderer:(id)sender;
-
-@end
diff --git a/modules/gui/macosx/VLCRendererDialog.m b/modules/gui/macosx/VLCRendererDialog.m
deleted file mode 100644 (file)
index 3cd9819..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*****************************************************************************
- * VLCRendererDialog.m: View controller class for the renderer dialog
- *****************************************************************************
- * Copyright (C) 2016 VLC authors and VideoLAN
- * $Id$
- *
- * Authors: Marvin Scholz <epirat07 at gmail dot com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
-
-#import "VLCRendererDialog.h"
-
-#import "VLCRendererItem.h"
-#import "VLCMain.h"
-
-#include <vlc_renderer_discovery.h>
-
-
-@interface VLCRendererDialog ()
-{
-    IBOutlet NSTableView *tableView;
-    IBOutlet NSArrayController *arrayController;
-
-#ifdef MAC_OS_X_VERSION_10_11
-    NSMutableArray<VLCRendererDiscovery*> *renderer_discoveries;
-#else
-    NSMutableArray *renderer_discoveries;
-#endif
-
-    intf_thread_t          *p_intf;
-    vlc_renderer_discovery_t *p_rd;
-}
-@end
-
-@implementation VLCRendererDialog
-
-- (id)init
-{
-    self = [super initWithWindowNibName:@"VLCRendererDialog"];
-    if (self) {
-        _rendererItems = [[NSMutableArray alloc] init];
-        renderer_discoveries = [[NSMutableArray alloc] initWithCapacity:1];
-        p_intf = getIntf();
-    }
-    return self;
-}
-
-- (void)dealloc
-{
-    [self stopRendererDiscoveries];
-    [self clearRendererDiscoveries];
-}
-
-- (void)windowDidLoad
-{
-    [super windowDidLoad];
-    [self.window setDelegate:self];
-    [self.window setTitle:_NS("Renderer selection")];
-
-    [self loadRendererDiscoveries];
-}
-
-- (void)windowWillClose:(NSNotification *)notification
-{
-    // Stop all renderer discoveries here!
-    [self stopRendererDiscoveries];
-}
-
-- (void)showWindow:(id)sender
-{
-    // Start all renderer discoveries here!
-    [self startRendererDiscoveries];
-    return [super showWindow:sender];
-}
-
-- (void)loadRendererDiscoveries
-{
-    playlist_t *playlist = pl_Get(p_intf);
-
-    // Service Discovery subnodes
-    char **ppsz_longnames;
-    char **ppsz_names;
-
-    if (vlc_rd_get_names(playlist, &ppsz_names, &ppsz_longnames) != VLC_SUCCESS) {
-        return;
-    }
-    char **ppsz_name = ppsz_names;
-    char **ppsz_longname = ppsz_longnames;
-
-    for( ; *ppsz_name; ppsz_name++, ppsz_longname++) {
-        VLCRendererDiscovery *dc = [[VLCRendererDiscovery alloc] initWithName:*ppsz_name andLongname:*ppsz_longname];
-        [dc setDelegate:self];
-        [dc startDiscovery];
-        [renderer_discoveries addObject:dc];
-        free(*ppsz_name);
-        free(*ppsz_longnames);
-    }
-    free(ppsz_names);
-    free(ppsz_longnames);
-}
-
-- (void)clearRendererDiscoveries
-{
-    [renderer_discoveries removeAllObjects];
-}
-
-- (void)startRendererDiscoveries
-{
-    for (VLCRendererDiscovery *dc in renderer_discoveries) {
-        [dc startDiscovery];
-    }
-}
-
-- (void)stopRendererDiscoveries
-{
-    for (VLCRendererDiscovery *dc in renderer_discoveries) {
-        [dc stopDiscovery];
-    }
-}
-
-- (IBAction)selectRenderer:(id)sender
-{
-    playlist_t *playlist = pl_Get(p_intf);
-    VLCRendererItem* item = [arrayController.selectedObjects firstObject];
-    if (item) {
-        [item setSoutForPlaylist:playlist];
-    } else {
-        [self unsetSoutForPlaylist:playlist];
-    }
-}
-
-- (void)unsetSoutForPlaylist:(playlist_t*)playlist
-{
-    var_SetString(playlist, "sout", "");
-}
-
-#pragma mark VLCRendererDiscoveryDelegate methods
-- (void)addedRendererItem:(VLCRendererItem *)item from:(VLCRendererDiscovery *)sender
-{
-    [arrayController addObject:item];
-}
-
-- (void)removedRendererItem:(VLCRendererItem *)item from:(VLCRendererDiscovery *)sender
-{
-    [arrayController removeObject:item];
-}
-
-@end
diff --git a/modules/gui/macosx/VLCRendererMenuController.h b/modules/gui/macosx/VLCRendererMenuController.h
new file mode 100644 (file)
index 0000000..64e864c
--- /dev/null
@@ -0,0 +1,36 @@
+/*****************************************************************************
+ * VLCRendererDialog.h: Controller class for the renderer menu
+ *****************************************************************************
+ * Copyright (C) 2016 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Authors: Marvin Scholz <epirat07 at gmail dot com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import <Cocoa/Cocoa.h>
+
+#import "VLCRendererItem.h"
+#import "VLCRendererDiscovery.h"
+
+@interface VLCRendererMenuController : NSObject <VLCRendererDiscoveryDelegate>
+
+@property (readwrite, weak) IBOutlet NSMenu     *rendererMenu;
+@property (readwrite, weak) IBOutlet NSMenuItem *rendererDiscoveryState;
+@property (readwrite, weak) IBOutlet NSMenuItem *rendererDiscoveryToggle;
+@property (readwrite, weak) IBOutlet NSMenuItem *rendererNoneItem;
+
+@end
diff --git a/modules/gui/macosx/VLCRendererMenuController.m b/modules/gui/macosx/VLCRendererMenuController.m
new file mode 100644 (file)
index 0000000..dab5b43
--- /dev/null
@@ -0,0 +1,203 @@
+/*****************************************************************************
+ * VLCRendererDialog.m: Controller class for the renderer menu
+ *****************************************************************************
+ * Copyright (C) 2016 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Authors: Marvin Scholz <epirat07 at gmail dot com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import "VLCRendererMenuController.h"
+
+#import "VLCRendererItem.h"
+#import "VLCMain.h"
+
+#include <vlc_renderer_discovery.h>
+
+@interface VLCRendererMenuController ()
+{
+    NSMutableDictionary         *_rendererItems;
+    NSMutableArray              *_rendererDiscoveries;
+    BOOL                         _isDiscoveryEnabled;
+    NSMenuItem                  *_selectedItem;
+
+    intf_thread_t               *p_intf;
+    vlc_renderer_discovery_t    *p_rd;
+}
+@end
+
+@implementation VLCRendererMenuController
+
+- (instancetype)init
+{
+    self = [super init];
+    if (self) {
+        _rendererItems = [NSMutableDictionary dictionary];
+        _rendererDiscoveries = [NSMutableArray array];
+        _isDiscoveryEnabled = NO;
+        p_intf = getIntf();
+
+        [self loadRendererDiscoveries];
+    }
+    return self;
+}
+
+- (void)awakeFromNib
+{
+    _selectedItem = _rendererNoneItem;
+    [self setupMenu];
+}
+
+- (void)dealloc
+{
+    [self stopRendererDiscoveries];
+}
+
+- (void)setupMenu
+{
+    [_rendererDiscoveryState setTitle:_NS("Renderer discovery off")];
+    [_rendererDiscoveryState setEnabled:NO];
+
+    [_rendererDiscoveryToggle setTitle:_NS("Enable renderer discovery")];
+
+    [_rendererNoneItem setTitle:_NS("No renderer")];
+    [_rendererNoneItem setState:NSOnState];
+}
+
+- (void)loadRendererDiscoveries
+{
+    playlist_t *playlist = pl_Get(p_intf);
+
+    // Service Discovery subnodes
+    char **ppsz_longnames;
+    char **ppsz_names;
+
+    if (vlc_rd_get_names(playlist, &ppsz_names, &ppsz_longnames) != VLC_SUCCESS) {
+        return;
+    }
+    char **ppsz_name = ppsz_names;
+    char **ppsz_longname = ppsz_longnames;
+
+    for( ; *ppsz_name; ppsz_name++, ppsz_longname++) {
+        VLCRendererDiscovery *dc = [[VLCRendererDiscovery alloc] initWithName:*ppsz_name andLongname:*ppsz_longname];
+        [dc setDelegate:self];
+        [_rendererDiscoveries addObject:dc];
+        free(*ppsz_name);
+        free(*ppsz_longnames);
+    }
+    free(ppsz_names);
+    free(ppsz_longnames);
+}
+
+#pragma mark - Renderer item management
+
+- (void)addRendererItem:(VLCRendererItem *)item
+{
+    // Create a menu item
+    NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:item.name
+                                                      action:@selector(selectRenderer:)
+                                               keyEquivalent:@""];
+    [menuItem setTarget:self];
+    [menuItem setRepresentedObject:item];
+    [_rendererMenu insertItem:menuItem atIndex:[_rendererMenu indexOfItem:_rendererNoneItem] + 1];
+}
+
+- (void)removeRendererItem:(VLCRendererItem *)item
+{
+    NSInteger index = [_rendererMenu indexOfItemWithRepresentedObject:item];
+    if (index != NSNotFound) {
+        NSMenuItem *menuItem = [_rendererMenu itemAtIndex:index];
+        if (menuItem == _selectedItem) {
+            [self selectRenderer:_rendererNoneItem];
+        }
+        [_rendererMenu removeItemAtIndex:index];
+    }
+}
+
+- (void)startRendererDiscoveries
+{
+    _isDiscoveryEnabled = YES;
+    [_rendererDiscoveryState setTitle:_NS("Renderer discovery on")];
+    [_rendererDiscoveryToggle setTitle:_NS("Disable renderer discovery")];
+    for (VLCRendererDiscovery *dc in _rendererDiscoveries) {
+        [dc startDiscovery];
+    }
+}
+
+- (void)stopRendererDiscoveries
+{
+    _isDiscoveryEnabled = NO;
+    [_rendererDiscoveryState setTitle:_NS("Renderer discovery off")];
+    [_rendererDiscoveryToggle setTitle:_NS("Enable renderer discovery")];
+    for (VLCRendererDiscovery *dc in _rendererDiscoveries) {
+        [dc stopDiscovery];
+    }
+}
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+    if (menuItem == _rendererNoneItem ||
+        [[menuItem representedObject] isKindOfClass:[VLCRendererItem class]]) {
+        return _isDiscoveryEnabled;
+    }
+    return [menuItem isEnabled];
+}
+
+- (IBAction)selectRenderer:(NSMenuItem *)sender
+{
+    [_selectedItem setState:NSOffState];
+    [sender setState:NSOnState];
+    _selectedItem = sender;
+
+    VLCRendererItem* item = [sender representedObject];
+    playlist_t *playlist = pl_Get(p_intf);
+
+    if (!playlist)
+        return;
+
+    if (item) {
+        [item setSoutForPlaylist:playlist];
+    } else {
+        [self unsetSoutForPlaylist:playlist];
+    }
+}
+
+- (void)unsetSoutForPlaylist:(playlist_t*)playlist
+{
+    var_SetString(playlist, "sout", "");
+}
+
+#pragma mark VLCRendererDiscovery delegate methods
+- (void)addedRendererItem:(VLCRendererItem *)item from:(VLCRendererDiscovery *)sender
+{
+    [self addRendererItem:item];
+}
+
+- (void)removedRendererItem:(VLCRendererItem *)item from:(VLCRendererDiscovery *)sender
+{
+    [self removeRendererItem:item];
+}
+
+#pragma mark Menu actions
+- (IBAction)toggleRendererDiscovery:(id)sender {
+    if (_isDiscoveryEnabled) {
+        [self stopRendererDiscoveries];
+    } else {
+        [self startRendererDiscoveries];
+    }
+}
+
+@end