9d03f98f090c1ad84cd9fed8d1e7d91f832926e9
[WebKit-https.git] / Source / WebCore / platform / graphics / avfoundation / objc / MediaPlaybackTargetPickerMac.mm
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "MediaPlaybackTargetPickerMac.h"
28
29 #if ENABLE(WIRELESS_PLAYBACK_TARGET) && !PLATFORM(IOS_FAMILY)
30
31 #import "Logging.h"
32 #import <WebCore/FloatRect.h>
33 #import <WebCore/MediaPlaybackTargetMac.h>
34 #import <objc/runtime.h>
35 #import <pal/cf/CoreMediaSoftLink.h>
36 #import <pal/spi/cocoa/AVKitSPI.h>
37 #import <pal/spi/mac/AVFoundationSPI.h>
38 #import <wtf/MainThread.h>
39
40 #import <pal/cocoa/AVFoundationSoftLink.h>
41
42 SOFTLINK_AVKIT_FRAMEWORK()
43 SOFT_LINK_CLASS_OPTIONAL(AVKit, AVOutputDeviceMenuController)
44
45 using namespace WebCore;
46
47 static NSString *externalOutputDeviceAvailableKeyName = @"externalOutputDeviceAvailable";
48 static NSString *externalOutputDevicePickedKeyName = @"externalOutputDevicePicked";
49
50 @interface WebAVOutputDeviceMenuControllerHelper : NSObject {
51     MediaPlaybackTargetPickerMac* m_callback;
52 }
53
54 - (instancetype)initWithCallback:(MediaPlaybackTargetPickerMac*)callback;
55 - (void)clearCallback;
56 - (void)observeValueForKeyPath:(id)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
57 @end
58
59 namespace WebCore {
60
61 MediaPlaybackTargetPickerMac::MediaPlaybackTargetPickerMac(MediaPlaybackTargetPicker::Client& client)
62     : MediaPlaybackTargetPicker(client)
63     , m_outputDeviceMenuControllerDelegate(adoptNS([[WebAVOutputDeviceMenuControllerHelper alloc] initWithCallback:this]))
64 {
65 }
66
67 MediaPlaybackTargetPickerMac::~MediaPlaybackTargetPickerMac()
68 {
69     setClient(nullptr);
70     [m_outputDeviceMenuControllerDelegate clearCallback];
71 }
72
73 bool MediaPlaybackTargetPickerMac::externalOutputDeviceAvailable()
74 {
75     return devicePicker().externalOutputDeviceAvailable;
76 }
77
78 Ref<MediaPlaybackTarget> MediaPlaybackTargetPickerMac::playbackTarget()
79 {
80     AVOutputContext* context = m_outputDeviceMenuController ? [m_outputDeviceMenuController.get() outputContext] : nullptr;
81
82     return WebCore::MediaPlaybackTargetMac::create(context);
83 }
84
85 AVOutputDeviceMenuController *MediaPlaybackTargetPickerMac::devicePicker()
86 {
87     if (!getAVOutputDeviceMenuControllerClass())
88         return nullptr;
89
90     if (!m_outputDeviceMenuController) {
91         LOG(Media, "MediaPlaybackTargetPickerMac::devicePicker - allocating picker");
92
93         RetainPtr<AVOutputContext> context = adoptNS([PAL::allocAVOutputContextInstance() init]);
94         m_outputDeviceMenuController = adoptNS([allocAVOutputDeviceMenuControllerInstance() initWithOutputContext:context.get()]);
95
96         [m_outputDeviceMenuController.get() addObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDeviceAvailableKeyName options:NSKeyValueObservingOptionNew context:nullptr];
97         [m_outputDeviceMenuController.get() addObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDevicePickedKeyName options:NSKeyValueObservingOptionNew context:nullptr];
98
99         LOG(Media, "MediaPlaybackTargetPickerMac::devicePicker - allocated menu controller %p", m_outputDeviceMenuController.get());
100
101         if (m_outputDeviceMenuController.get().externalOutputDeviceAvailable)
102             availableDevicesDidChange();
103     }
104
105     return m_outputDeviceMenuController.get();
106 }
107
108 void MediaPlaybackTargetPickerMac::showPlaybackTargetPicker(const FloatRect& location, bool checkActiveRoute, bool useDarkAppearance)
109 {
110     if (!client() || m_showingMenu)
111         return;
112
113     LOG(Media, "MediaPlaybackTargetPickerMac::showPlaybackTargetPicker - checkActiveRoute = %i", (int)checkActiveRoute);
114
115     m_showingMenu = true;
116
117     if ([devicePicker() showMenuForRect:location appearanceName:(useDarkAppearance ? NSAppearanceNameVibrantDark : NSAppearanceNameVibrantLight) allowReselectionOfSelectedOutputDevice:!checkActiveRoute]) {
118         if (!checkActiveRoute)
119             currentDeviceDidChange();
120     }
121     m_showingMenu = false;
122 }
123
124 void MediaPlaybackTargetPickerMac::startingMonitoringPlaybackTargets()
125 {
126     LOG(Media, "MediaPlaybackTargetPickerMac::startingMonitoringPlaybackTargets");
127
128     devicePicker();
129 }
130
131 void MediaPlaybackTargetPickerMac::stopMonitoringPlaybackTargets()
132 {
133     LOG(Media, "MediaPlaybackTargetPickerMac::stopMonitoringPlaybackTargets");
134     // Nothing to do, AirPlay takes care of this automatically.
135 }
136
137 void MediaPlaybackTargetPickerMac::invalidatePlaybackTargets()
138 {
139     LOG(Media, "MediaPlaybackTargetPickerMac::invalidatePlaybackTargets");
140
141     if (m_outputDeviceMenuController) {
142         [m_outputDeviceMenuController removeObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDeviceAvailableKeyName];
143         [m_outputDeviceMenuController removeObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDevicePickedKeyName];
144         m_outputDeviceMenuController = nullptr;
145     }
146     currentDeviceDidChange();
147 }
148
149 } // namespace WebCore
150
151 @implementation WebAVOutputDeviceMenuControllerHelper
152 - (instancetype)initWithCallback:(MediaPlaybackTargetPickerMac*)callback
153 {
154     if (!(self = [super init]))
155         return nil;
156
157     m_callback = callback;
158
159     return self;
160 }
161
162 - (void)clearCallback
163 {
164     m_callback = nil;
165 }
166
167 - (void)observeValueForKeyPath:(id)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
168 {
169     UNUSED_PARAM(object);
170     UNUSED_PARAM(change);
171     UNUSED_PARAM(context);
172
173     if (!m_callback)
174         return;
175
176     LOG(Media, "MediaPlaybackTargetPickerMac::observeValueForKeyPath - key = %s", [keyPath UTF8String]);
177
178     if (![keyPath isEqualToString:externalOutputDeviceAvailableKeyName] && ![keyPath isEqualToString:externalOutputDevicePickedKeyName])
179         return;
180
181     RetainPtr<WebAVOutputDeviceMenuControllerHelper> protectedSelf = self;
182     RetainPtr<NSString> protectedKeyPath = keyPath;
183     callOnMainThread([protectedSelf = WTFMove(protectedSelf), protectedKeyPath = WTFMove(protectedKeyPath)] {
184         MediaPlaybackTargetPickerMac* callback = protectedSelf->m_callback;
185         if (!callback)
186             return;
187
188         if ([protectedKeyPath isEqualToString:externalOutputDeviceAvailableKeyName])
189             callback->availableDevicesDidChange();
190         else if ([protectedKeyPath isEqualToString:externalOutputDevicePickedKeyName])
191             callback->currentDeviceDidChange();
192     });
193 }
194 @end
195
196 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET)