21b2536b1250d155c55aaaad7770d1211c16ae75
[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 <WebCore/FloatRect.h>
32 #import <WebCore/MediaPlaybackTarget.h>
33 #import <WebCore/SoftLinking.h>
34 #import <objc/runtime.h>
35 #import <wtf/MainThread.h>
36
37 @class AVOutputDevicePickerContext;
38 @interface AVOutputDevicePickerMenuController : NSObject
39 @property (readonly) AVOutputDevicePickerContext * outputDevicePickerContext;
40 @property (readonly, getter = isExternalOutputDeviceAvailable) BOOL externalOutputDeviceAvailable;
41 - (instancetype)initWithOutputDevicePickerContext:(AVOutputDevicePickerContext *)outputDevicePickerContext NS_DESIGNATED_INITIALIZER;
42 - (void)showMenuForRect:(NSRect)screenRect appearanceName:(NSString *)appearanceName;
43 @end
44
45 typedef AVOutputDevicePickerContext AVOutputDevicePickerContextType;
46 typedef AVOutputDevicePickerMenuController AVOutputDevicePickerMenuControllerType;
47
48 SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
49 SOFT_LINK_FRAMEWORK_OPTIONAL(AVKit)
50
51 SOFT_LINK_CLASS(AVFoundation, AVOutputDevicePickerContext)
52 SOFT_LINK_CLASS(AVKit, AVOutputDevicePickerMenuController)
53 SOFT_LINK_CLASS(AVKit, AVOutputDevicePickerDelegate)
54
55 using namespace WebCore;
56
57 static NSString *externalOutputDeviceAvailableKeyName = @"externalOutputDeviceAvailable";
58 static NSString *externalOutputDevicePickedKeyName = @"externalOutputDevicePicked";
59
60 @interface WebAVOutputDevicePickerMenuControllerHelper : NSObject {
61     MediaPlaybackTargetPickerMac* m_callback;
62 }
63
64 - (instancetype)initWithCallback:(MediaPlaybackTargetPickerMac*)callback;
65 - (void)clearCallback;
66 - (void)observeValueForKeyPath:(id)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
67 @end
68
69 namespace WebCore {
70
71 std::unique_ptr<MediaPlaybackTargetPickerMac> MediaPlaybackTargetPickerMac::create(MediaPlaybackTargetPicker::Client& client)
72 {
73     return std::unique_ptr<MediaPlaybackTargetPickerMac>(new MediaPlaybackTargetPickerMac(client));
74 }
75
76 MediaPlaybackTargetPickerMac::MediaPlaybackTargetPickerMac(MediaPlaybackTargetPicker::Client& client)
77     : MediaPlaybackTargetPicker(client)
78     , m_devicePickerMenuControllerDelegate(adoptNS([[WebAVOutputDevicePickerMenuControllerHelper alloc] initWithCallback:this]))
79     , m_deviceChangeTimer(RunLoop::main(), this, &MediaPlaybackTargetPickerMac::outputeDeviceAvailabilityChangedTimerFired)
80 {
81 }
82
83 MediaPlaybackTargetPickerMac::~MediaPlaybackTargetPickerMac()
84 {
85     m_deviceChangeTimer.stop();
86     [m_devicePickerMenuControllerDelegate clearCallback];
87
88     if (m_devicePickerMenuController) {
89         [m_devicePickerMenuController removeObserver:m_devicePickerMenuControllerDelegate.get() forKeyPath:externalOutputDeviceAvailableKeyName];
90         [m_devicePickerMenuController removeObserver:m_devicePickerMenuControllerDelegate.get() forKeyPath:externalOutputDevicePickedKeyName];
91         m_devicePickerMenuController = nil;
92     }
93 }
94
95 void MediaPlaybackTargetPickerMac::outputeDeviceAvailabilityChangedTimerFired()
96 {
97     if (!m_devicePickerMenuController || !m_client)
98         return;
99
100     m_client->externalOutputDeviceAvailableDidChange(devicePicker().externalOutputDeviceAvailable);
101 }
102
103 void MediaPlaybackTargetPickerMac::availableDevicesDidChange()
104 {
105     if (!m_client)
106         return;
107
108     m_deviceChangeTimer.stop();
109     m_deviceChangeTimer.startOneShot(0);
110 }
111
112 AVOutputDevicePickerMenuControllerType *MediaPlaybackTargetPickerMac::devicePicker()
113 {
114     if (!m_devicePickerMenuController) {
115         RetainPtr<AVOutputDevicePickerContextType> context = adoptNS([[getAVOutputDevicePickerContextClass() alloc] init]);
116         m_devicePickerMenuController = adoptNS([[getAVOutputDevicePickerMenuControllerClass() alloc] initWithOutputDevicePickerContext:context.get()]);
117
118         [m_devicePickerMenuController addObserver:m_devicePickerMenuControllerDelegate.get() forKeyPath:externalOutputDeviceAvailableKeyName options:NSKeyValueObservingOptionNew context:nil];
119         [m_devicePickerMenuController addObserver:m_devicePickerMenuControllerDelegate.get() forKeyPath:externalOutputDevicePickedKeyName options:NSKeyValueObservingOptionNew context:nil];
120
121         if (m_devicePickerMenuController.get().externalOutputDeviceAvailable)
122             availableDevicesDidChange();
123     }
124
125     return m_devicePickerMenuController.get();
126 }
127
128 void MediaPlaybackTargetPickerMac::showPlaybackTargetPicker(const FloatRect& location, bool)
129 {
130     if (!m_client)
131         return;
132
133     [devicePicker() showMenuForRect:location appearanceName:NSAppearanceNameVibrantLight];
134 }
135
136 void MediaPlaybackTargetPickerMac::currentDeviceDidChange()
137 {
138     if (!m_client)
139         return;
140
141     m_client->didChoosePlaybackTarget(MediaPlaybackTarget([devicePicker() outputDevicePickerContext]));
142 }
143
144 void MediaPlaybackTargetPickerMac::startingMonitoringPlaybackTargets()
145 {
146     devicePicker();
147 }
148
149 void MediaPlaybackTargetPickerMac::stopMonitoringPlaybackTargets()
150 {
151 }
152
153 } // namespace WebCore
154
155 @implementation WebAVOutputDevicePickerMenuControllerHelper
156 - (instancetype)initWithCallback:(MediaPlaybackTargetPickerMac*)callback
157 {
158     if (!(self = [super init]))
159         return nil;
160
161     m_callback = callback;
162
163     return self;
164 }
165
166 - (void)clearCallback
167 {
168     m_callback = nil;
169 }
170
171 - (void)observeValueForKeyPath:(id)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
172 {
173     UNUSED_PARAM(object);
174     UNUSED_PARAM(change);
175     UNUSED_PARAM(context);
176
177     if (!m_callback)
178         return;
179
180     if (![keyPath isEqualToString:externalOutputDeviceAvailableKeyName] && ![keyPath isEqualToString:externalOutputDevicePickedKeyName])
181         return;
182
183     RetainPtr<WebAVOutputDevicePickerMenuControllerHelper> strongSelf = self;
184     RetainPtr<NSString> strongKeyPath = keyPath;
185     callOnMainThread([strongSelf, strongKeyPath] {
186         MediaPlaybackTargetPickerMac* callback = strongSelf->m_callback;
187         if (!callback)
188             return;
189
190         if ([strongKeyPath isEqualToString:externalOutputDeviceAvailableKeyName])
191             callback->availableDevicesDidChange();
192         else if ([strongKeyPath isEqualToString:externalOutputDevicePickedKeyName])
193             callback->currentDeviceDidChange();
194     });
195 }
196 @end
197
198 #endif // ENABLE(WIRELESS_PLAYBACK_TARGET)