/src/mozilla-central/dom/media/mediacapabilities/MediaCapabilities.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "MediaCapabilities.h" |
8 | | #include "AllocationPolicy.h" |
9 | | #include "Benchmark.h" |
10 | | #include "DecoderTraits.h" |
11 | | #include "Layers.h" |
12 | | #include "MediaInfo.h" |
13 | | #include "MediaRecorder.h" |
14 | | #include "PDMFactory.h" |
15 | | #include "VPXDecoder.h" |
16 | | #include "mozilla/Move.h" |
17 | | #include "mozilla/StaticPrefs.h" |
18 | | #include "mozilla/TaskQueue.h" |
19 | | #include "mozilla/dom/DOMMozPromiseRequestHolder.h" |
20 | | #include "mozilla/dom/MediaCapabilitiesBinding.h" |
21 | | #include "mozilla/dom/MediaSource.h" |
22 | | #include "mozilla/dom/Promise.h" |
23 | | #include "mozilla/dom/WorkerPrivate.h" |
24 | | #include "mozilla/dom/WorkerRef.h" |
25 | | #include "mozilla/layers/KnowsCompositor.h" |
26 | | #include "nsContentUtils.h" |
27 | | |
28 | | #include <inttypes.h> |
29 | | |
30 | | static mozilla::LazyLogModule sMediaCapabilitiesLog("MediaCapabilities"); |
31 | | |
32 | | #define LOG(msg, ...) \ |
33 | 0 | DDMOZ_LOG(sMediaCapabilitiesLog, LogLevel::Debug, msg, ##__VA_ARGS__) |
34 | | |
35 | | namespace mozilla { |
36 | | namespace dom { |
37 | | |
38 | | static nsCString |
39 | | VideoConfigurationToStr(const VideoConfiguration* aConfig) |
40 | 0 | { |
41 | 0 | if (!aConfig) { |
42 | 0 | return nsCString(); |
43 | 0 | } |
44 | 0 | auto str = nsPrintfCString( |
45 | 0 | "[contentType:%s width:%d height:%d bitrate:%" PRIu64 " framerate:%s]", |
46 | 0 | NS_ConvertUTF16toUTF8(aConfig->mContentType.Value()).get(), |
47 | 0 | aConfig->mWidth.Value(), |
48 | 0 | aConfig->mHeight.Value(), |
49 | 0 | aConfig->mBitrate.Value(), |
50 | 0 | NS_ConvertUTF16toUTF8(aConfig->mFramerate.Value()).get()); |
51 | 0 | return std::move(str); |
52 | 0 | } |
53 | | |
54 | | static nsCString |
55 | | AudioConfigurationToStr(const AudioConfiguration* aConfig) |
56 | 0 | { |
57 | 0 | if (!aConfig) { |
58 | 0 | return nsCString(); |
59 | 0 | } |
60 | 0 | auto str = nsPrintfCString( |
61 | 0 | "[contentType:%s channels:%s bitrate:%" PRIu64 " samplerate:%d]", |
62 | 0 | NS_ConvertUTF16toUTF8(aConfig->mContentType.Value()).get(), |
63 | 0 | aConfig->mChannels.WasPassed() |
64 | 0 | ? NS_ConvertUTF16toUTF8(aConfig->mChannels.Value()).get() |
65 | 0 | : "?", |
66 | 0 | aConfig->mBitrate.WasPassed() ? aConfig->mBitrate.Value() : 0, |
67 | 0 | aConfig->mSamplerate.WasPassed() ? aConfig->mSamplerate.Value() : 0); |
68 | 0 | return std::move(str); |
69 | 0 | } |
70 | | |
71 | | static nsCString |
72 | | MediaCapabilitiesInfoToStr(const MediaCapabilitiesInfo* aInfo) |
73 | 0 | { |
74 | 0 | if (!aInfo) { |
75 | 0 | return nsCString(); |
76 | 0 | } |
77 | 0 | auto str = nsPrintfCString("[supported:%s smooth:%s powerEfficient:%s]", |
78 | 0 | aInfo->Supported() ? "true" : "false", |
79 | 0 | aInfo->Smooth() ? "true" : "false", |
80 | 0 | aInfo->PowerEfficient() ? "true" : "false"); |
81 | 0 | return std::move(str); |
82 | 0 | } |
83 | | |
84 | | static nsCString |
85 | | MediaDecodingConfigurationToStr(const MediaDecodingConfiguration& aConfig) |
86 | 0 | { |
87 | 0 | nsCString str; |
88 | 0 | str += NS_LITERAL_CSTRING("["); |
89 | 0 | if (aConfig.mVideo.IsAnyMemberPresent()) { |
90 | 0 | str += |
91 | 0 | NS_LITERAL_CSTRING("video:") + VideoConfigurationToStr(&aConfig.mVideo); |
92 | 0 | if (aConfig.mAudio.IsAnyMemberPresent()) { |
93 | 0 | str += NS_LITERAL_CSTRING(" "); |
94 | 0 | } |
95 | 0 | } |
96 | 0 | if (aConfig.mAudio.IsAnyMemberPresent()) { |
97 | 0 | str += |
98 | 0 | NS_LITERAL_CSTRING("audio:") + AudioConfigurationToStr(&aConfig.mAudio); |
99 | 0 | } |
100 | 0 | str += NS_LITERAL_CSTRING("]"); |
101 | 0 | return str; |
102 | 0 | } |
103 | | |
104 | | MediaCapabilities::MediaCapabilities(nsIGlobalObject* aParent) |
105 | | : mParent(aParent) |
106 | 0 | { |
107 | 0 | } |
108 | | |
109 | | static void |
110 | | ThrowWithMemberName(ErrorResult& aRv, |
111 | | const char* aCategory, |
112 | | const char* aMember) |
113 | 0 | { |
114 | 0 | auto str = nsPrintfCString("'%s' member of %s", aMember, aCategory); |
115 | 0 | aRv.ThrowTypeError<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>( |
116 | 0 | NS_ConvertUTF8toUTF16(str)); |
117 | 0 | } |
118 | | |
119 | | static void |
120 | | CheckVideoConfigurationSanity(const VideoConfiguration& aConfig, |
121 | | const char* aCategory, |
122 | | ErrorResult& aRv) |
123 | 0 | { |
124 | 0 | if (!aConfig.mContentType.WasPassed()) { |
125 | 0 | ThrowWithMemberName(aRv, "contentType", aCategory); |
126 | 0 | return; |
127 | 0 | } |
128 | 0 | if (!aConfig.mWidth.WasPassed()) { |
129 | 0 | ThrowWithMemberName(aRv, "width", aCategory); |
130 | 0 | return; |
131 | 0 | } |
132 | 0 | if (!aConfig.mHeight.WasPassed()) { |
133 | 0 | ThrowWithMemberName(aRv, "height", aCategory); |
134 | 0 | return; |
135 | 0 | } |
136 | 0 | if (!aConfig.mBitrate.WasPassed()) { |
137 | 0 | ThrowWithMemberName(aRv, "bitrate", aCategory); |
138 | 0 | return; |
139 | 0 | } |
140 | 0 | if (!aConfig.mFramerate.WasPassed()) { |
141 | 0 | ThrowWithMemberName(aRv, "framerate", aCategory); |
142 | 0 | return; |
143 | 0 | } |
144 | 0 | } |
145 | | |
146 | | static void |
147 | | CheckAudioConfigurationSanity(const AudioConfiguration& aConfig, |
148 | | const char* aCategory, |
149 | | ErrorResult& aRv) |
150 | 0 | { |
151 | 0 | if (!aConfig.mContentType.WasPassed()) { |
152 | 0 | ThrowWithMemberName(aRv, "contentType", aCategory); |
153 | 0 | return; |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | already_AddRefed<Promise> |
158 | | MediaCapabilities::DecodingInfo( |
159 | | const MediaDecodingConfiguration& aConfiguration, |
160 | | ErrorResult& aRv) |
161 | 0 | { |
162 | 0 | RefPtr<Promise> promise = Promise::Create(mParent, aRv); |
163 | 0 | if (aRv.Failed()) { |
164 | 0 | return nullptr; |
165 | 0 | } |
166 | 0 | |
167 | 0 | // If configuration is not a valid MediaConfiguration, return a Promise |
168 | 0 | // rejected with a TypeError. |
169 | 0 | if (!aConfiguration.IsAnyMemberPresent() || |
170 | 0 | (!aConfiguration.mVideo.IsAnyMemberPresent() && |
171 | 0 | !aConfiguration.mAudio.IsAnyMemberPresent())) { |
172 | 0 | aRv.ThrowTypeError<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>( |
173 | 0 | NS_LITERAL_STRING("'audio' or 'video'")); |
174 | 0 | return nullptr; |
175 | 0 | } |
176 | 0 |
|
177 | 0 | // Here we will throw rather than rejecting a promise in order to simulate |
178 | 0 | // optional dictionaries with required members (see bug 1368949) |
179 | 0 | if (aConfiguration.mVideo.IsAnyMemberPresent()) { |
180 | 0 | // Check that all VideoConfiguration required members are present. |
181 | 0 | CheckVideoConfigurationSanity( |
182 | 0 | aConfiguration.mVideo, "MediaDecodingConfiguration", aRv); |
183 | 0 | if (aRv.Failed()) { |
184 | 0 | return nullptr; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | if (aConfiguration.mAudio.IsAnyMemberPresent()) { |
188 | 0 | // Check that all AudioConfiguration required members are present. |
189 | 0 | CheckAudioConfigurationSanity( |
190 | 0 | aConfiguration.mAudio, "MediaDecodingConfiguration", aRv); |
191 | 0 | if (aRv.Failed()) { |
192 | 0 | return nullptr; |
193 | 0 | } |
194 | 0 | } |
195 | 0 | |
196 | 0 | LOG("Processing %s", MediaDecodingConfigurationToStr(aConfiguration).get()); |
197 | 0 |
|
198 | 0 | bool supported = true; |
199 | 0 | Maybe<MediaContainerType> videoContainer; |
200 | 0 | Maybe<MediaContainerType> audioContainer; |
201 | 0 |
|
202 | 0 | // If configuration.video is present and is not a valid video configuration, |
203 | 0 | // return a Promise rejected with a TypeError. |
204 | 0 | if (aConfiguration.mVideo.IsAnyMemberPresent()) { |
205 | 0 | videoContainer = CheckVideoConfiguration(aConfiguration.mVideo); |
206 | 0 | if (!videoContainer) { |
207 | 0 | aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>(); |
208 | 0 | return nullptr; |
209 | 0 | } |
210 | 0 | |
211 | 0 | // We have a video configuration and it is valid. Check if it is supported. |
212 | 0 | supported &= |
213 | 0 | aConfiguration.mType == MediaDecodingType::File |
214 | 0 | ? CheckTypeForFile(aConfiguration.mVideo.mContentType.Value()) |
215 | 0 | : CheckTypeForMediaSource(aConfiguration.mVideo.mContentType.Value()); |
216 | 0 | } |
217 | 0 | if (aConfiguration.mAudio.IsAnyMemberPresent()) { |
218 | 0 | audioContainer = CheckAudioConfiguration(aConfiguration.mAudio); |
219 | 0 | if (!audioContainer) { |
220 | 0 | aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>(); |
221 | 0 | return nullptr; |
222 | 0 | } |
223 | 0 | // We have an audio configuration and it is valid. Check if it is supported. |
224 | 0 | supported &= |
225 | 0 | aConfiguration.mType == MediaDecodingType::File |
226 | 0 | ? CheckTypeForFile(aConfiguration.mAudio.mContentType.Value()) |
227 | 0 | : CheckTypeForMediaSource(aConfiguration.mAudio.mContentType.Value()); |
228 | 0 | } |
229 | 0 |
|
230 | 0 | if (!supported) { |
231 | 0 | auto info = MakeUnique<MediaCapabilitiesInfo>( |
232 | 0 | false /* supported */, false /* smooth */, false /* power efficient */); |
233 | 0 | LOG("%s -> %s", |
234 | 0 | MediaDecodingConfigurationToStr(aConfiguration).get(), |
235 | 0 | MediaCapabilitiesInfoToStr(info.get()).get()); |
236 | 0 | promise->MaybeResolve(std::move(info)); |
237 | 0 | return promise.forget(); |
238 | 0 | } |
239 | 0 |
|
240 | 0 | nsTArray<UniquePtr<TrackInfo>> tracks; |
241 | 0 | if (aConfiguration.mVideo.IsAnyMemberPresent()) { |
242 | 0 | MOZ_ASSERT(videoContainer.isSome(), "configuration is valid and supported"); |
243 | 0 | auto videoTracks = DecoderTraits::GetTracksInfo(*videoContainer); |
244 | 0 | // If the MIME type does not imply a codec, the string MUST |
245 | 0 | // also have one and only one parameter that is named codecs with a value |
246 | 0 | // describing a single media codec. Otherwise, it MUST contain no |
247 | 0 | // parameters. |
248 | 0 | if (videoTracks.Length() != 1) { |
249 | 0 | promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR); |
250 | 0 | return promise.forget(); |
251 | 0 | } |
252 | 0 | MOZ_DIAGNOSTIC_ASSERT(videoTracks.ElementAt(0), |
253 | 0 | "must contain a valid trackinfo"); |
254 | 0 | tracks.AppendElements(std::move(videoTracks)); |
255 | 0 | } |
256 | 0 | if (aConfiguration.mAudio.IsAnyMemberPresent()) { |
257 | 0 | MOZ_ASSERT(audioContainer.isSome(), "configuration is valid and supported"); |
258 | 0 | auto audioTracks = DecoderTraits::GetTracksInfo(*audioContainer); |
259 | 0 | // If the MIME type does not imply a codec, the string MUST |
260 | 0 | // also have one and only one parameter that is named codecs with a value |
261 | 0 | // describing a single media codec. Otherwise, it MUST contain no |
262 | 0 | // parameters. |
263 | 0 | if (audioTracks.Length() != 1) { |
264 | 0 | promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR); |
265 | 0 | return promise.forget(); |
266 | 0 | } |
267 | 0 | MOZ_DIAGNOSTIC_ASSERT(audioTracks.ElementAt(0), |
268 | 0 | "must contain a valid trackinfo"); |
269 | 0 | tracks.AppendElements(std::move(audioTracks)); |
270 | 0 | } |
271 | 0 |
|
272 | 0 | typedef MozPromise<MediaCapabilitiesInfo, |
273 | 0 | MediaResult, |
274 | 0 | /* IsExclusive = */ true> |
275 | 0 | CapabilitiesPromise; |
276 | 0 | nsTArray<RefPtr<CapabilitiesPromise>> promises; |
277 | 0 |
|
278 | 0 | RefPtr<TaskQueue> taskQueue = |
279 | 0 | new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), |
280 | 0 | "MediaCapabilities::TaskQueue"); |
281 | 0 | for (auto&& config : tracks) { |
282 | 0 | TrackInfo::TrackType type = |
283 | 0 | config->IsVideo() ? TrackInfo::kVideoTrack : TrackInfo::kAudioTrack; |
284 | 0 |
|
285 | 0 | MOZ_ASSERT(type == TrackInfo::kAudioTrack || |
286 | 0 | videoContainer->ExtendedType().GetFramerate().isSome(), |
287 | 0 | "framerate is a required member of VideoConfiguration"); |
288 | 0 |
|
289 | 0 | if (type == TrackInfo::kAudioTrack) { |
290 | 0 | // There's no need to create an audio decoder has we only want to know if |
291 | 0 | // such codec is supported |
292 | 0 | RefPtr<PDMFactory> pdm = new PDMFactory(); |
293 | 0 | if (!pdm->Supports(*config, nullptr /* decoder doctor */)) { |
294 | 0 | auto info = |
295 | 0 | MakeUnique<MediaCapabilitiesInfo>(false /* supported */, |
296 | 0 | false /* smooth */, |
297 | 0 | false /* power efficient */); |
298 | 0 | LOG("%s -> %s", |
299 | 0 | MediaDecodingConfigurationToStr(aConfiguration).get(), |
300 | 0 | MediaCapabilitiesInfoToStr(info.get()).get()); |
301 | 0 | promise->MaybeResolve(std::move(info)); |
302 | 0 | return promise.forget(); |
303 | 0 | } |
304 | 0 | // We can assume that if we could create the decoder, then we can play it. |
305 | 0 | // We report that we can play it smoothly and in an efficient fashion. |
306 | 0 | promises.AppendElement(CapabilitiesPromise::CreateAndResolve( |
307 | 0 | MediaCapabilitiesInfo( |
308 | 0 | true /* supported */, true /* smooth */, true /* power efficient */), |
309 | 0 | __func__)); |
310 | 0 | continue; |
311 | 0 | } |
312 | 0 | |
313 | 0 | // On Windows, the MediaDataDecoder expects to be created on a thread |
314 | 0 | // supporting MTA, which the main thread doesn't. So we use our task queue |
315 | 0 | // to create such decoder and perform initialization. |
316 | 0 | |
317 | 0 | RefPtr<layers::KnowsCompositor> compositor = GetCompositor(); |
318 | 0 | double frameRate = videoContainer->ExtendedType().GetFramerate().ref(); |
319 | 0 | promises.AppendElement(InvokeAsync( |
320 | 0 | taskQueue, |
321 | 0 | __func__, |
322 | 0 | [taskQueue, frameRate, compositor, config = std::move(config)]() mutable |
323 | 0 | -> RefPtr<CapabilitiesPromise> { |
324 | 0 | // MediaDataDecoder keeps a reference to the config object, so we must |
325 | 0 | // keep it alive until the decoder has been shutdown. |
326 | 0 | CreateDecoderParams params{ *config, |
327 | 0 | taskQueue, |
328 | 0 | compositor, |
329 | 0 | CreateDecoderParams::VideoFrameRate( |
330 | 0 | frameRate), |
331 | 0 | TrackInfo::kVideoTrack }; |
332 | 0 | return AllocationWrapper::CreateDecoder(params)->Then( |
333 | 0 | taskQueue, |
334 | 0 | __func__, |
335 | 0 | [taskQueue, frameRate, config = std::move(config)]( |
336 | 0 | const AllocationWrapper::AllocateDecoderPromise:: |
337 | 0 | ResolveOrRejectValue& aValue) mutable { |
338 | 0 | if (aValue.IsReject()) { |
339 | 0 | return CapabilitiesPromise::CreateAndReject(aValue.RejectValue(), |
340 | 0 | __func__); |
341 | 0 | } |
342 | 0 | RefPtr<MediaDataDecoder> decoder = aValue.ResolveValue(); |
343 | 0 | // We now query the decoder to determine if it's power efficient. |
344 | 0 | RefPtr<CapabilitiesPromise> p = decoder->Init()->Then( |
345 | 0 | taskQueue, |
346 | 0 | __func__, |
347 | 0 | [taskQueue, decoder, frameRate, config = std::move(config)]( |
348 | 0 | const MediaDataDecoder::InitPromise::ResolveOrRejectValue& |
349 | 0 | aValue) mutable { |
350 | 0 | RefPtr<CapabilitiesPromise> p; |
351 | 0 | if (aValue.IsReject()) { |
352 | 0 | p = CapabilitiesPromise::CreateAndReject(aValue.RejectValue(), |
353 | 0 | __func__); |
354 | 0 | } else { |
355 | 0 | MOZ_ASSERT(config->IsVideo()); |
356 | 0 | nsAutoCString reason; |
357 | 0 | bool powerEfficient = true; |
358 | 0 | bool smooth = true; |
359 | 0 | if (config->GetAsVideoInfo()->mImage.height > 480) { |
360 | 0 | // Assume that we can do stuff at 480p or less in a power |
361 | 0 | // efficient manner and smoothly. If greater than 480p we |
362 | 0 | // assume that if the video decoding is hardware accelerated |
363 | 0 | // it will be smooth and power efficient, otherwise we use |
364 | 0 | // the benchmark to estimate |
365 | 0 | powerEfficient = decoder->IsHardwareAccelerated(reason); |
366 | 0 | if (!powerEfficient && |
367 | 0 | VPXDecoder::IsVP9(config->mMimeType)) { |
368 | 0 | smooth = |
369 | 0 | VP9Benchmark::IsVP9DecodeFast(true /* default */); |
370 | 0 | uint32_t fps = VP9Benchmark::MediaBenchmarkVp9Fps(); |
371 | 0 | if (!smooth && fps > 0) { |
372 | 0 | // The VP9 estimizer decode a 1280x720 video. Let's |
373 | 0 | // adjust the result for the resolution and frame rate |
374 | 0 | // of what we actually want. If the result is twice that |
375 | 0 | // we need we assume it will be smooth. |
376 | 0 | const auto& videoConfig = *config->GetAsVideoInfo(); |
377 | 0 | double needed = ((1280.0 * 720.0) / |
378 | 0 | (videoConfig.mImage.width * |
379 | 0 | videoConfig.mImage.height) * |
380 | 0 | fps) / |
381 | 0 | frameRate; |
382 | 0 | smooth = needed > 2; |
383 | 0 | } |
384 | 0 | } |
385 | 0 | } |
386 | 0 | p = CapabilitiesPromise::CreateAndResolve( |
387 | 0 | MediaCapabilitiesInfo( |
388 | 0 | true /* supported */, smooth, powerEfficient), |
389 | 0 | __func__); |
390 | 0 | } |
391 | 0 | MOZ_ASSERT(p.get(), "the promise has been created"); |
392 | 0 | // Let's keep alive the decoder and the config object until the |
393 | 0 | // decoder has shutdown. |
394 | 0 | decoder->Shutdown()->Then( |
395 | 0 | taskQueue, |
396 | 0 | __func__, |
397 | 0 | [taskQueue, decoder, config = std::move(config)]( |
398 | 0 | const ShutdownPromise::ResolveOrRejectValue& aValue) {}); |
399 | 0 | return p; |
400 | 0 | }); |
401 | 0 | return p; |
402 | 0 | }); |
403 | 0 | })); |
404 | 0 | } |
405 | 0 |
|
406 | 0 | auto holder = |
407 | 0 | MakeRefPtr<DOMMozPromiseRequestHolder<CapabilitiesPromise::AllPromiseType>>( |
408 | 0 | mParent); |
409 | 0 | RefPtr<nsISerialEventTarget> targetThread; |
410 | 0 | RefPtr<StrongWorkerRef> workerRef; |
411 | 0 |
|
412 | 0 | if (NS_IsMainThread()) { |
413 | 0 | targetThread = mParent->AbstractMainThreadFor(TaskCategory::Other); |
414 | 0 | } else { |
415 | 0 | WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); |
416 | 0 | MOZ_ASSERT(wp, "Must be called from a worker thread"); |
417 | 0 | targetThread = wp->HybridEventTarget(); |
418 | 0 | workerRef = StrongWorkerRef::Create( |
419 | 0 | wp, "MediaCapabilities", [holder, targetThread]() { |
420 | 0 | MOZ_ASSERT(targetThread->IsOnCurrentThread()); |
421 | 0 | holder->DisconnectIfExists(); |
422 | 0 | }); |
423 | 0 | if (NS_WARN_IF(!workerRef)) { |
424 | 0 | // The worker is shutting down. |
425 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
426 | 0 | return nullptr; |
427 | 0 | } |
428 | 0 | } |
429 | 0 | |
430 | 0 | MOZ_ASSERT(targetThread); |
431 | 0 |
|
432 | 0 | // this is only captured for use with the LOG macro. |
433 | 0 | RefPtr<MediaCapabilities> self = this; |
434 | 0 |
|
435 | 0 | CapabilitiesPromise::All(targetThread, promises) |
436 | 0 | ->Then( |
437 | 0 | targetThread, |
438 | 0 | __func__, |
439 | 0 | [promise, |
440 | 0 | tracks = std::move(tracks), |
441 | 0 | workerRef, |
442 | 0 | holder, |
443 | 0 | aConfiguration, |
444 | 0 | self, |
445 | 0 | this](const CapabilitiesPromise::AllPromiseType::ResolveOrRejectValue& |
446 | 0 | aValue) { |
447 | 0 | holder->Complete(); |
448 | 0 | if (aValue.IsReject()) { |
449 | 0 | auto info = |
450 | 0 | MakeUnique<MediaCapabilitiesInfo>(false /* supported */, |
451 | 0 | false /* smooth */, |
452 | 0 | false /* power efficient */); |
453 | 0 | LOG("%s -> %s", |
454 | 0 | MediaDecodingConfigurationToStr(aConfiguration).get(), |
455 | 0 | MediaCapabilitiesInfoToStr(info.get()).get()); |
456 | 0 | promise->MaybeResolve(std::move(info)); |
457 | 0 | return; |
458 | 0 | } |
459 | 0 | bool powerEfficient = true; |
460 | 0 | bool smooth = true; |
461 | 0 | for (auto&& capability : aValue.ResolveValue()) { |
462 | 0 | smooth &= capability.Smooth(); |
463 | 0 | powerEfficient &= capability.PowerEfficient(); |
464 | 0 | } |
465 | 0 | auto info = MakeUnique<MediaCapabilitiesInfo>( |
466 | 0 | true /* supported */, smooth, powerEfficient); |
467 | 0 | LOG("%s -> %s", |
468 | 0 | MediaDecodingConfigurationToStr(aConfiguration).get(), |
469 | 0 | MediaCapabilitiesInfoToStr(info.get()).get()); |
470 | 0 | promise->MaybeResolve(std::move(info)); |
471 | 0 | }) |
472 | 0 | ->Track(*holder); |
473 | 0 |
|
474 | 0 | return promise.forget(); |
475 | 0 | } |
476 | | |
477 | | already_AddRefed<Promise> |
478 | | MediaCapabilities::EncodingInfo( |
479 | | const MediaEncodingConfiguration& aConfiguration, |
480 | | ErrorResult& aRv) |
481 | 0 | { |
482 | 0 | RefPtr<Promise> promise = Promise::Create(mParent, aRv); |
483 | 0 | if (aRv.Failed()) { |
484 | 0 | return nullptr; |
485 | 0 | } |
486 | 0 | |
487 | 0 | // If configuration is not a valid MediaConfiguration, return a Promise |
488 | 0 | // rejected with a TypeError. |
489 | 0 | if (!aConfiguration.IsAnyMemberPresent() || |
490 | 0 | (!aConfiguration.mVideo.IsAnyMemberPresent() && |
491 | 0 | !aConfiguration.mAudio.IsAnyMemberPresent())) { |
492 | 0 | aRv.ThrowTypeError<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>( |
493 | 0 | NS_LITERAL_STRING("'audio' or 'video'")); |
494 | 0 | return nullptr; |
495 | 0 | } |
496 | 0 |
|
497 | 0 | // Here we will throw rather than rejecting a promise in order to simulate |
498 | 0 | // optional dictionaries with required members (see bug 1368949) |
499 | 0 | if (aConfiguration.mVideo.IsAnyMemberPresent()) { |
500 | 0 | // Check that all VideoConfiguration required members are present. |
501 | 0 | CheckVideoConfigurationSanity( |
502 | 0 | aConfiguration.mVideo, "MediaDecodingConfiguration", aRv); |
503 | 0 | if (aRv.Failed()) { |
504 | 0 | return nullptr; |
505 | 0 | } |
506 | 0 | } |
507 | 0 | if (aConfiguration.mAudio.IsAnyMemberPresent()) { |
508 | 0 | // Check that all AudioConfiguration required members are present. |
509 | 0 | CheckAudioConfigurationSanity( |
510 | 0 | aConfiguration.mAudio, "MediaDecodingConfiguration", aRv); |
511 | 0 | if (aRv.Failed()) { |
512 | 0 | return nullptr; |
513 | 0 | } |
514 | 0 | } |
515 | 0 | |
516 | 0 | bool supported = true; |
517 | 0 |
|
518 | 0 | // If configuration.video is present and is not a valid video configuration, |
519 | 0 | // return a Promise rejected with a TypeError. |
520 | 0 | if (aConfiguration.mVideo.IsAnyMemberPresent()) { |
521 | 0 | if (!CheckVideoConfiguration(aConfiguration.mVideo)) { |
522 | 0 | aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>(); |
523 | 0 | return nullptr; |
524 | 0 | } |
525 | 0 | // We have a video configuration and it is valid. Check if it is supported. |
526 | 0 | supported &= |
527 | 0 | CheckTypeForEncoder(aConfiguration.mVideo.mContentType.Value()); |
528 | 0 | } |
529 | 0 | if (aConfiguration.mAudio.IsAnyMemberPresent()) { |
530 | 0 | if (!CheckAudioConfiguration(aConfiguration.mAudio)) { |
531 | 0 | aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>(); |
532 | 0 | return nullptr; |
533 | 0 | } |
534 | 0 | // We have an audio configuration and it is valid. Check if it is supported. |
535 | 0 | supported &= |
536 | 0 | CheckTypeForEncoder(aConfiguration.mAudio.mContentType.Value()); |
537 | 0 | } |
538 | 0 |
|
539 | 0 | auto info = MakeUnique<MediaCapabilitiesInfo>(supported, supported, false); |
540 | 0 | promise->MaybeResolve(std::move(info)); |
541 | 0 |
|
542 | 0 | return promise.forget(); |
543 | 0 | } |
544 | | |
545 | | Maybe<MediaContainerType> |
546 | | MediaCapabilities::CheckVideoConfiguration( |
547 | | const VideoConfiguration& aConfig) const |
548 | 0 | { |
549 | 0 | Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig); |
550 | 0 | if (!container) { |
551 | 0 | return Nothing(); |
552 | 0 | } |
553 | 0 | // A valid video MIME type is a string that is a valid media MIME type and for |
554 | 0 | // which the type per [RFC7231] is either video or application. |
555 | 0 | if (!container->Type().HasVideoMajorType() && |
556 | 0 | !container->Type().HasApplicationMajorType()) { |
557 | 0 | return Nothing(); |
558 | 0 | } |
559 | 0 | |
560 | 0 | // If the MIME type does not imply a codec, the string MUST also have one and |
561 | 0 | // only one parameter that is named codecs with a value describing a single |
562 | 0 | // media codec. Otherwise, it MUST contain no parameters. |
563 | 0 | // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of |
564 | 0 | // parameters) |
565 | 0 | |
566 | 0 | return Some(MediaContainerType(std::move(*container))); |
567 | 0 | } |
568 | | |
569 | | Maybe<MediaContainerType> |
570 | | MediaCapabilities::CheckAudioConfiguration( |
571 | | const AudioConfiguration& aConfig) const |
572 | 0 | { |
573 | 0 | Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig); |
574 | 0 | if (!container) { |
575 | 0 | return Nothing(); |
576 | 0 | } |
577 | 0 | // A valid audio MIME type is a string that is valid media MIME type and for |
578 | 0 | // which the type per [RFC7231] is either audio or application. |
579 | 0 | if (!container->Type().HasAudioMajorType() && |
580 | 0 | !container->Type().HasApplicationMajorType()) { |
581 | 0 | return Nothing(); |
582 | 0 | } |
583 | 0 | |
584 | 0 | // If the MIME type does not imply a codec, the string MUST also have one and |
585 | 0 | // only one parameter that is named codecs with a value describing a single |
586 | 0 | // media codec. Otherwise, it MUST contain no parameters. |
587 | 0 | // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of |
588 | 0 | // parameters) |
589 | 0 | |
590 | 0 | return Some(MediaContainerType(std::move(*container))); |
591 | 0 | } |
592 | | |
593 | | bool |
594 | | MediaCapabilities::CheckTypeForMediaSource(const nsAString& aType) |
595 | 0 | { |
596 | 0 | return NS_SUCCEEDED(MediaSource::IsTypeSupported( |
597 | 0 | aType, nullptr /* DecoderDoctorDiagnostics */)); |
598 | 0 | } |
599 | | |
600 | | bool |
601 | | MediaCapabilities::CheckTypeForFile(const nsAString& aType) |
602 | 0 | { |
603 | 0 | Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); |
604 | 0 | if (!containerType) { |
605 | 0 | return false; |
606 | 0 | } |
607 | 0 | |
608 | 0 | return DecoderTraits::CanHandleContainerType( |
609 | 0 | *containerType, nullptr /* DecoderDoctorDiagnostics */) != |
610 | 0 | CANPLAY_NO; |
611 | 0 | } |
612 | | |
613 | | bool |
614 | | MediaCapabilities::CheckTypeForEncoder(const nsAString& aType) |
615 | 0 | { |
616 | 0 | return MediaRecorder::IsTypeSupported(aType); |
617 | 0 | } |
618 | | |
619 | | already_AddRefed<layers::KnowsCompositor> |
620 | | MediaCapabilities::GetCompositor() |
621 | 0 | { |
622 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetParentObject()); |
623 | 0 | if (NS_WARN_IF(!window)) { |
624 | 0 | return nullptr; |
625 | 0 | } |
626 | 0 | |
627 | 0 | nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); |
628 | 0 | if (NS_WARN_IF(!doc)) { |
629 | 0 | return nullptr; |
630 | 0 | } |
631 | 0 | RefPtr<layers::LayerManager> layerManager = |
632 | 0 | nsContentUtils::LayerManagerForDocument(doc); |
633 | 0 | if (NS_WARN_IF(!layerManager)) { |
634 | 0 | return nullptr; |
635 | 0 | } |
636 | 0 | RefPtr<layers::KnowsCompositor> knows = layerManager->AsKnowsCompositor(); |
637 | 0 | if (NS_WARN_IF(!knows)) { |
638 | 0 | return nullptr; |
639 | 0 | } |
640 | 0 | return knows->GetForMedia().forget(); |
641 | 0 | } |
642 | | |
643 | | bool |
644 | | MediaCapabilities::Enabled(JSContext* aCx, JSObject* aGlobal) |
645 | 0 | { |
646 | 0 | return StaticPrefs::MediaCapabilitiesEnabled(); |
647 | 0 | } |
648 | | |
649 | | JSObject* |
650 | | MediaCapabilities::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
651 | 0 | { |
652 | 0 | return MediaCapabilities_Binding::Wrap(aCx, this, aGivenProto); |
653 | 0 | } |
654 | | |
655 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaCapabilities) |
656 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
657 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
658 | 0 | NS_INTERFACE_MAP_END |
659 | | |
660 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaCapabilities) |
661 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaCapabilities) |
662 | | |
663 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaCapabilities, mParent) |
664 | | |
665 | | // MediaCapabilitiesInfo |
666 | | bool |
667 | | MediaCapabilitiesInfo::WrapObject(JSContext* aCx, |
668 | | JS::Handle<JSObject*> aGivenProto, |
669 | | JS::MutableHandle<JSObject*> aReflector) |
670 | 0 | { |
671 | 0 | return MediaCapabilitiesInfo_Binding::Wrap( |
672 | 0 | aCx, this, aGivenProto, aReflector); |
673 | 0 | } |
674 | | |
675 | | } // namespace dom |
676 | | } // namespace mozilla |