a8037ec5fdbee3c480071373cbe1b3e5b6e17e99
[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)
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 typedef AVOutputContext AVOutputContextWKType;
41 typedef AVOutputDeviceMenuController AVOutputDeviceMenuControllerWKType;
42
43
44 SOFTLINK_AVKIT_FRAMEWORK()
45 SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
46
47 SOFT_LINK_CLASS_OPTIONAL(AVFoundation, AVOutputContext)
48 SOFT_LINK_CLASS_OPTIONAL(AVKit, AVOutputDeviceMenuController)
49
50 using namespace WebCore;
51
52 static NSString *externalOutputDeviceAvailableKeyName = @"externalOutputDeviceAvailable";
53 static NSString *externalOutputDevicePickedKeyName = @"externalOutputDevicePicked";
54
55 @interface WebAVOutputDeviceMenuControllerHelper : NSObject {
56     MediaPlaybackTargetPickerMac* m_callback;
57 }
58
59 - (instancetype)initWithCallback:(MediaPlaybackTargetPickerMac*)callback;
60 - (void)clearCallback;
61 - (void)observeValueForKeyPath:(id)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
62 @end
63
64 namespace WebCore {
65
66 MediaPlaybackTargetPickerMac::MediaPlaybackTargetPickerMac(MediaPlaybackTargetPicker::Client& client)
67     : MediaPlaybackTargetPicker(client)
68     , m_outputDeviceMenuControllerDelegate(adoptNS([[WebAVOutputDeviceMenuControllerHelper alloc] initWithCallback:this]))
69 {
70 }
71
72 MediaPlaybackTargetPickerMac::~MediaPlaybackTargetPickerMac()
73 {
74     setClient(nullptr);
75     [m_outputDeviceMenuControllerDelegate clearCallback];
76 }
77
78 bool MediaPlaybackTargetPickerMac::externalOutputDeviceAvailable()
79 {
80     return devicePicker().externalOutputDeviceAvailable;
81 }
82
83 Ref<MediaPlaybackTarget> MediaPlaybackTargetPickerMac::playbackTarget()
84 {
85     AVOutputContext* context = m_outputDeviceMenuController ? [m_outputDeviceMenuController.get() outputContext] : nullptr;
86
87     return WebCore::MediaPlaybackTargetMac::create(context);
88 }
89
90 AVOutputDeviceMenuControllerWKType *MediaPlaybackTargetPickerMac::devicePicker()
91 {
92     if (!getAVOutputDeviceMenuControllerClass())
93         return nullptr;
94
95     if (!m_outputDeviceMenuController) {
96         LOG(Media, "MediaPlaybackTargetPickerMac::devicePicker - allocating picker");
97
98         RetainPtr<AVOutputContextWKType> context = adoptNS([allocAVOutputContextInstance() init]);
99         m_outputDeviceMenuController = adoptNS([allocAVOutputDeviceMenuControllerInstance() initWithOutputContext:context.get()]);
100
101         [m_outputDeviceMenuController.get() addObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDeviceAvailableKeyName options:NSKeyValueObservingOptionNew context:nullptr];
102         [m_outputDeviceMenuController.get() addObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDevicePickedKeyName options:NSKeyValueObservingOptionNew context:nullptr];
103
104         LOG(Media, "MediaPlaybackTargetPickerMac::devicePicker - allocated menu controller %p", m_outputDeviceMenuController.get());
105
106         if (m_outputDeviceMenuController.get().externalOutputDeviceAvailable)
107             availableDevicesDidChange();
108     }
109
110     return m_outputDeviceMenuController.get();
111 }
112
113 void MediaPlaybackTargetPickerMac::showPlaybackTargetPicker(const FloatRect& location, bool checkActiveRoute)
114 {
115     if (!client() || m_showingMenu)
116         return;
117
118     LOG(Media, "MediaPlaybackTargetPickerMac::showPlaybackTargetPicker - checkActiveRoute = %i", (int)checkActiveRoute);
119
120     m_showingMenu = true;
121
122     if ([devicePicker() showMenuForRect:location appearanceName:NSAppearanceNameVibrantLight allowReselectionOfSelectedOutputDevice:!checkActiveRoute]) {
123         if (!checkActiveRoute)
124             currentDeviceDidChange();
125     }
126     m_showingMenu = false;
127 }
128
129 void MediaPlaybackTargetPickerMac::startingMonitoringPlaybackTargets()
130 {
131     LOG(Media, "MediaPlaybackTargetPickerMac::startingMonitoringPlaybackTargets");
132
133     devicePicker();
134 }
135
136 void MediaPlaybackTargetPickerMac::stopMonitoringPlaybackTargets()
137 {
138     LOG(Media, "MediaPlaybackTargetPickerMac::stopMonitoringPlaybackTargets");
139     // Nothing to do, AirPlay takes care of this automatically.
140 }
141
142 void MediaPlaybackTargetPickerMac::invalidatePlaybackTargets()
143 {
144     LOG(Media, "MediaPlaybackTargetPickerMac::invalidatePlaybackTargets");
145
146     if (m_outputDeviceMenuController) {
147         [m_outputDeviceMenuController removeObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDeviceAvailableKeyName];
148         [m_outputDeviceMenuController removeObserver:m_outputDeviceMenuControllerDelegate.get() forKeyPath:externalOutputDevicePickedKeyName];
149         m_outputDeviceMenuController = nullptr;
150     }
151     currentDeviceDidChange();
152 }
153
154 } // namespace WebCore
155
156 @implementation WebAVOutputDeviceMenuControllerHelper
157 - (instancetype)initWithCallback:(MediaPlaybackTargetPickerMac*)callback
158 {
159     if (!(self = [super init]))
160         return nil;
161
162     m_callback = callback;
163
164     return self;
165 }
166
167 - (void)clearCallback
168 {
169     m_callback = nil;
170 }
171
172 - (void)observeValueForKeyPath:(id)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
173 {
174     UNUSED_PARAM(object);
175     UNUSED_PARAM(change);
176     UNUSED_PARAM(context);
177
178     if (!m_callback)
179         return;
180
181     LOG(Media, "MediaPlaybackTargetPickerMac::observeValueForKeyPath - key = %s", [keyPath UTF8String]);
182
183     if (![keyPath isEqualToString:externalOutputDeviceAvailableKeyName] && ![keyPath isEqualToString:externalOutputDevicePickedKeyName])
184         return;
185
186     RetainPtr<WebAVOutputDeviceMenuControllerHelper> protectedSelf = self;
187     RetainPtr<NSString> protectedKeyPath = keyPath;
188     callOnMainThread([protectedSelf = WTFMove(protectedSelf), protectedKeyPath = WTFMove(protectedKeyPath)] {
189         MediaPlaybackTargetPickerMac* callback = protectedSelf->m_callback;
190         if (!callback)
191             return;
192
193         if ([protectedKeyPath isEqualToString:externalOutputDeviceAvailableKeyName])
194             callback->availableDevicesDidChange();
195         else if ([protectedKeyPath isEqualToString:externalOutputDevicePickedKeyName])
196             callback->currentDeviceDidChange();
197     });
198 }
199 @end
200
201 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET)