1 /*
2 * Copyright (C) 2011, Google 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'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
23 * DAMAGE.
24 */
25
26 #include "third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.h"
27
28 #include "third_party/blink/public/platform/task_type.h"
29 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_element_audio_source_options.h"
30 #include "third_party/blink/renderer/core/frame/deprecation.h"
31 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
32 #include "third_party/blink/renderer/core/inspector/console_message.h"
33 #include "third_party/blink/renderer/modules/webaudio/audio_context.h"
34 #include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
35 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
36 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
37 #include "third_party/blink/renderer/platform/heap/heap.h"
38 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
39 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
40
41 namespace blink {
42
43 class MediaElementAudioSourceHandlerLocker final {
44 STACK_ALLOCATED();
45
46 public:
MediaElementAudioSourceHandlerLocker(MediaElementAudioSourceHandler & lockable)47 MediaElementAudioSourceHandlerLocker(MediaElementAudioSourceHandler& lockable)
48 : lockable_(lockable) {
49 lockable_.lock();
50 }
~MediaElementAudioSourceHandlerLocker()51 ~MediaElementAudioSourceHandlerLocker() { lockable_.unlock(); }
52
53 private:
54 MediaElementAudioSourceHandler& lockable_;
55
56 DISALLOW_COPY_AND_ASSIGN(MediaElementAudioSourceHandlerLocker);
57 };
58
MediaElementAudioSourceHandler(AudioNode & node,HTMLMediaElement & media_element)59 MediaElementAudioSourceHandler::MediaElementAudioSourceHandler(
60 AudioNode& node,
61 HTMLMediaElement& media_element)
62 : AudioHandler(kNodeTypeMediaElementAudioSource,
63 node,
64 node.context()->sampleRate()),
65 media_element_(media_element),
66 source_number_of_channels_(0),
67 source_sample_rate_(0),
68 is_origin_tainted_(false) {
69 DCHECK(IsMainThread());
70 // Default to stereo. This could change depending on what the media element
71 // .src is set to.
72 AddOutput(2);
73
74 if (Context()->GetExecutionContext()) {
75 task_runner_ = Context()->GetExecutionContext()->GetTaskRunner(
76 TaskType::kMediaElementEvent);
77 }
78
79 Initialize();
80 }
81
82 scoped_refptr<MediaElementAudioSourceHandler>
Create(AudioNode & node,HTMLMediaElement & media_element)83 MediaElementAudioSourceHandler::Create(AudioNode& node,
84 HTMLMediaElement& media_element) {
85 return base::AdoptRef(
86 new MediaElementAudioSourceHandler(node, media_element));
87 }
88
~MediaElementAudioSourceHandler()89 MediaElementAudioSourceHandler::~MediaElementAudioSourceHandler() {
90 Uninitialize();
91 }
92
MediaElement() const93 HTMLMediaElement* MediaElementAudioSourceHandler::MediaElement() const {
94 return media_element_.Get();
95 }
96
Dispose()97 void MediaElementAudioSourceHandler::Dispose() {
98 AudioHandler::Dispose();
99 }
100
SetFormat(uint32_t number_of_channels,float source_sample_rate)101 void MediaElementAudioSourceHandler::SetFormat(uint32_t number_of_channels,
102 float source_sample_rate) {
103 DCHECK(MediaElement());
104 bool is_tainted = WouldTaintOrigin();
105
106 if (is_tainted) {
107 PrintCorsMessage(MediaElement()->currentSrc().GetString());
108 }
109
110 {
111 // Make sure |is_origin_tainted_| matches |is_tainted|. But need to
112 // synchronize with process() to set this.
113 MediaElementAudioSourceHandlerLocker locker(*this);
114 is_origin_tainted_ = is_tainted;
115 }
116
117 if (number_of_channels != source_number_of_channels_ ||
118 source_sample_rate != source_sample_rate_) {
119 if (!number_of_channels ||
120 number_of_channels > BaseAudioContext::MaxNumberOfChannels() ||
121 !audio_utilities::IsValidAudioBufferSampleRate(source_sample_rate)) {
122 // process() will generate silence for these uninitialized values.
123 DLOG(ERROR) << "setFormat(" << number_of_channels << ", "
124 << source_sample_rate << ") - unhandled format change";
125 // Synchronize with process().
126 MediaElementAudioSourceHandlerLocker locker(*this);
127 source_number_of_channels_ = 0;
128 source_sample_rate_ = 0;
129 return;
130 }
131
132 // Synchronize with process() to protect |source_number_of_channels_|,
133 // |source_sample_rate_|, |multi_channel_resampler_|.
134 MediaElementAudioSourceHandlerLocker locker(*this);
135
136 source_number_of_channels_ = number_of_channels;
137 source_sample_rate_ = source_sample_rate;
138
139 if (source_sample_rate != Context()->sampleRate()) {
140 double scale_factor = source_sample_rate / Context()->sampleRate();
141 multi_channel_resampler_.reset(new MediaMultiChannelResampler(
142 number_of_channels, scale_factor,
143 audio_utilities::kRenderQuantumFrames,
144 CrossThreadBindRepeating(
145 &MediaElementAudioSourceHandler::ProvideResamplerInput,
146 CrossThreadUnretained(this))));
147 } else {
148 // Bypass resampling.
149 multi_channel_resampler_.reset();
150 }
151
152 {
153 // The context must be locked when changing the number of output channels.
154 BaseAudioContext::GraphAutoLocker context_locker(Context());
155
156 // Do any necesssary re-configuration to the output's number of channels.
157 Output(0).SetNumberOfChannels(number_of_channels);
158 }
159 }
160 }
161
WouldTaintOrigin()162 bool MediaElementAudioSourceHandler::WouldTaintOrigin() {
163 DCHECK(MediaElement());
164 return MediaElement()->GetWebMediaPlayer()->WouldTaintOrigin();
165 }
166
PrintCorsMessage(const String & message)167 void MediaElementAudioSourceHandler::PrintCorsMessage(const String& message) {
168 if (Context()->GetExecutionContext()) {
169 Context()->GetExecutionContext()->AddConsoleMessage(
170 MakeGarbageCollected<ConsoleMessage>(
171 mojom::ConsoleMessageSource::kSecurity,
172 mojom::ConsoleMessageLevel::kInfo,
173 "MediaElementAudioSource outputs zeroes due to "
174 "CORS access restrictions for " +
175 message));
176 }
177 }
178
ProvideResamplerInput(int resampler_frame_delay,AudioBus * dest)179 void MediaElementAudioSourceHandler::ProvideResamplerInput(
180 int resampler_frame_delay,
181 AudioBus* dest) {
182 DCHECK(Context()->IsAudioThread());
183 DCHECK(MediaElement());
184 DCHECK(dest);
185 MediaElement()->GetAudioSourceProvider().ProvideInput(dest, dest->length());
186 }
187
Process(uint32_t number_of_frames)188 void MediaElementAudioSourceHandler::Process(uint32_t number_of_frames) {
189 AudioBus* output_bus = Output(0).Bus();
190
191 // Use a tryLock() to avoid contention in the real-time audio thread.
192 // If we fail to acquire the lock then the HTMLMediaElement must be in the
193 // middle of reconfiguring its playback engine, so we output silence in this
194 // case.
195 MutexTryLocker try_locker(process_lock_);
196 if (try_locker.Locked()) {
197 if (!MediaElement() || !source_sample_rate_) {
198 output_bus->Zero();
199 return;
200 }
201
202 // TODO(crbug.com/811516): Although OnSetFormat() requested the output bus
203 // channels, the actual channel count might have not been changed yet.
204 // Output silence for such case until the channel count is resolved.
205 if (source_number_of_channels_ != output_bus->NumberOfChannels()) {
206 output_bus->Zero();
207 return;
208 }
209
210 AudioSourceProvider& provider = MediaElement()->GetAudioSourceProvider();
211 // Grab data from the provider so that the element continues to make
212 // progress, even if we're going to output silence anyway.
213 if (multi_channel_resampler_.get()) {
214 DCHECK_NE(source_sample_rate_, Context()->sampleRate());
215 multi_channel_resampler_->Resample(number_of_frames, output_bus);
216 } else {
217 // Bypass the resampler completely if the source is at the context's
218 // sample-rate.
219 DCHECK_EQ(source_sample_rate_, Context()->sampleRate());
220 provider.ProvideInput(output_bus, number_of_frames);
221 }
222 // Output silence if we don't have access to the element.
223 if (is_origin_tainted_) {
224 output_bus->Zero();
225 }
226 } else {
227 // We failed to acquire the lock.
228 output_bus->Zero();
229 }
230 }
231
lock()232 void MediaElementAudioSourceHandler::lock() {
233 process_lock_.lock();
234 }
235
unlock()236 void MediaElementAudioSourceHandler::unlock() {
237 process_lock_.unlock();
238 }
239
240 // ----------------------------------------------------------------
241
MediaElementAudioSourceNode(AudioContext & context,HTMLMediaElement & media_element)242 MediaElementAudioSourceNode::MediaElementAudioSourceNode(
243 AudioContext& context,
244 HTMLMediaElement& media_element)
245 : AudioNode(context), media_element_(&media_element) {
246 SetHandler(MediaElementAudioSourceHandler::Create(*this, media_element));
247 }
248
Create(AudioContext & context,HTMLMediaElement & media_element,ExceptionState & exception_state)249 MediaElementAudioSourceNode* MediaElementAudioSourceNode::Create(
250 AudioContext& context,
251 HTMLMediaElement& media_element,
252 ExceptionState& exception_state) {
253 DCHECK(IsMainThread());
254
255 // First check if this media element already has a source node.
256 if (media_element.AudioSourceNode()) {
257 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
258 "HTMLMediaElement already connected "
259 "previously to a different "
260 "MediaElementSourceNode.");
261 return nullptr;
262 }
263
264 MediaElementAudioSourceNode* node =
265 MakeGarbageCollected<MediaElementAudioSourceNode>(context, media_element);
266
267 if (node) {
268 media_element.SetAudioSourceNode(node);
269 // context keeps reference until node is disconnected
270 context.NotifySourceNodeStartedProcessing(node);
271 if (!context.HasRealtimeConstraint()) {
272 Deprecation::CountDeprecation(
273 node->GetExecutionContext(),
274 WebFeature::kMediaElementSourceOnOfflineContext);
275 }
276 }
277
278 return node;
279 }
280
Create(AudioContext * context,const MediaElementAudioSourceOptions * options,ExceptionState & exception_state)281 MediaElementAudioSourceNode* MediaElementAudioSourceNode::Create(
282 AudioContext* context,
283 const MediaElementAudioSourceOptions* options,
284 ExceptionState& exception_state) {
285 return Create(*context, *options->mediaElement(), exception_state);
286 }
287
Trace(Visitor * visitor)288 void MediaElementAudioSourceNode::Trace(Visitor* visitor) {
289 visitor->Trace(media_element_);
290 AudioSourceProviderClient::Trace(visitor);
291 AudioNode::Trace(visitor);
292 }
293
294 MediaElementAudioSourceHandler&
GetMediaElementAudioSourceHandler() const295 MediaElementAudioSourceNode::GetMediaElementAudioSourceHandler() const {
296 return static_cast<MediaElementAudioSourceHandler&>(Handler());
297 }
298
mediaElement() const299 HTMLMediaElement* MediaElementAudioSourceNode::mediaElement() const {
300 return media_element_;
301 }
302
SetFormat(uint32_t number_of_channels,float sample_rate)303 void MediaElementAudioSourceNode::SetFormat(uint32_t number_of_channels,
304 float sample_rate) {
305 GetMediaElementAudioSourceHandler().SetFormat(number_of_channels,
306 sample_rate);
307 }
308
lock()309 void MediaElementAudioSourceNode::lock() {
310 GetMediaElementAudioSourceHandler().lock();
311 }
312
unlock()313 void MediaElementAudioSourceNode::unlock() {
314 GetMediaElementAudioSourceHandler().unlock();
315 }
316
ReportDidCreate()317 void MediaElementAudioSourceNode::ReportDidCreate() {
318 GraphTracer().DidCreateAudioNode(this);
319 }
320
ReportWillBeDestroyed()321 void MediaElementAudioSourceNode::ReportWillBeDestroyed() {
322 GraphTracer().WillDestroyAudioNode(this);
323 }
324
325 } // namespace blink
326