/src/mozilla-central/dom/media/eme/MediaKeySystemAccess.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
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 "mozilla/dom/MediaKeySystemAccess.h" |
8 | | #include "mozilla/dom/MediaKeySystemAccessBinding.h" |
9 | | #include "mozilla/dom/MediaKeySession.h" |
10 | | #include "mozilla/Preferences.h" |
11 | | #include "mozilla/StaticPrefs.h" |
12 | | #include "MediaContainerType.h" |
13 | | #include "nsMimeTypes.h" |
14 | | #ifdef XP_WIN |
15 | | #include "WMFDecoderModule.h" |
16 | | #endif |
17 | | #include "nsContentCID.h" |
18 | | #include "nsServiceManagerUtils.h" |
19 | | #include "mozIGeckoMediaPluginService.h" |
20 | | #include "VideoUtils.h" |
21 | | #include "mozilla/Services.h" |
22 | | #include "nsIObserverService.h" |
23 | | #include "mozilla/EMEUtils.h" |
24 | | #include "GMPUtils.h" |
25 | | #include "nsAppDirectoryServiceDefs.h" |
26 | | #include "nsDirectoryServiceUtils.h" |
27 | | #include "nsDirectoryServiceDefs.h" |
28 | | #include "nsXULAppAPI.h" |
29 | | #include "DecoderDoctorDiagnostics.h" |
30 | | #include "WebMDecoder.h" |
31 | | #include "mozilla/StaticPtr.h" |
32 | | #include "mozilla/ClearOnShutdown.h" |
33 | | #include "nsUnicharUtils.h" |
34 | | #include "mozilla/dom/MediaSource.h" |
35 | | #include "DecoderTraits.h" |
36 | | #ifdef MOZ_WIDGET_ANDROID |
37 | | #include "FennecJNIWrappers.h" |
38 | | #include "GeneratedJNIWrappers.h" |
39 | | #endif |
40 | | #include <functional> |
41 | | |
42 | | namespace mozilla { |
43 | | namespace dom { |
44 | | |
45 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess, |
46 | | mParent) |
47 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess) |
48 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess) |
49 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess) |
50 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
51 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
52 | 0 | NS_INTERFACE_MAP_END |
53 | | |
54 | | static nsCString |
55 | | ToCString(const MediaKeySystemConfiguration& aConfig); |
56 | | |
57 | | MediaKeySystemAccess::MediaKeySystemAccess(nsPIDOMWindowInner* aParent, |
58 | | const nsAString& aKeySystem, |
59 | | const MediaKeySystemConfiguration& aConfig) |
60 | | : mParent(aParent) |
61 | | , mKeySystem(aKeySystem) |
62 | | , mConfig(aConfig) |
63 | 0 | { |
64 | 0 | EME_LOG("Created MediaKeySystemAccess for keysystem=%s config=%s", |
65 | 0 | NS_ConvertUTF16toUTF8(mKeySystem).get(), mozilla::dom::ToCString(mConfig).get()); |
66 | 0 | } |
67 | | |
68 | | MediaKeySystemAccess::~MediaKeySystemAccess() |
69 | 0 | { |
70 | 0 | } |
71 | | |
72 | | JSObject* |
73 | | MediaKeySystemAccess::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
74 | 0 | { |
75 | 0 | return MediaKeySystemAccess_Binding::Wrap(aCx, this, aGivenProto); |
76 | 0 | } |
77 | | |
78 | | nsPIDOMWindowInner* |
79 | | MediaKeySystemAccess::GetParentObject() const |
80 | 0 | { |
81 | 0 | return mParent; |
82 | 0 | } |
83 | | |
84 | | void |
85 | | MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const |
86 | 0 | { |
87 | 0 | aOutKeySystem.Assign(mKeySystem); |
88 | 0 | } |
89 | | |
90 | | void |
91 | | MediaKeySystemAccess::GetConfiguration(MediaKeySystemConfiguration& aConfig) |
92 | 0 | { |
93 | 0 | aConfig = mConfig; |
94 | 0 | } |
95 | | |
96 | | already_AddRefed<Promise> |
97 | | MediaKeySystemAccess::CreateMediaKeys(ErrorResult& aRv) |
98 | 0 | { |
99 | 0 | RefPtr<MediaKeys> keys(new MediaKeys(mParent, |
100 | 0 | mKeySystem, |
101 | 0 | mConfig)); |
102 | 0 | return keys->Init(aRv); |
103 | 0 | } |
104 | | |
105 | | static bool |
106 | | HavePluginForKeySystem(const nsCString& aKeySystem) |
107 | 0 | { |
108 | 0 | nsCString api = NS_LITERAL_CSTRING(CHROMIUM_CDM_API); |
109 | 0 |
|
110 | 0 | bool havePlugin = HaveGMPFor(api, { aKeySystem }); |
111 | | #ifdef MOZ_WIDGET_ANDROID |
112 | | // Check if we can use MediaDrm for this keysystem. |
113 | | if (!havePlugin) { |
114 | | havePlugin = mozilla::java::MediaDrmProxy::IsSchemeSupported(aKeySystem); |
115 | | } |
116 | | #endif |
117 | | return havePlugin; |
118 | 0 | } |
119 | | |
120 | | static MediaKeySystemStatus |
121 | | EnsureCDMInstalled(const nsAString& aKeySystem, |
122 | | nsACString& aOutMessage) |
123 | 0 | { |
124 | 0 | if (!HavePluginForKeySystem(NS_ConvertUTF16toUTF8(aKeySystem))) { |
125 | 0 | aOutMessage = NS_LITERAL_CSTRING("CDM is not installed"); |
126 | 0 | return MediaKeySystemStatus::Cdm_not_installed; |
127 | 0 | } |
128 | 0 |
|
129 | 0 | return MediaKeySystemStatus::Available; |
130 | 0 | } |
131 | | |
132 | | /* static */ |
133 | | MediaKeySystemStatus |
134 | | MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem, |
135 | | nsACString& aOutMessage) |
136 | 0 | { |
137 | 0 | MOZ_ASSERT(StaticPrefs::MediaEmeEnabled() || |
138 | 0 | IsClearkeyKeySystem(aKeySystem)); |
139 | 0 |
|
140 | 0 | if (IsClearkeyKeySystem(aKeySystem)) { |
141 | 0 | return EnsureCDMInstalled(aKeySystem, aOutMessage); |
142 | 0 | } |
143 | 0 | |
144 | 0 | if (IsWidevineKeySystem(aKeySystem)) { |
145 | 0 | if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) { |
146 | 0 | if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) { |
147 | 0 | aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled"); |
148 | 0 | return MediaKeySystemStatus::Cdm_disabled; |
149 | 0 | } |
150 | 0 | return EnsureCDMInstalled(aKeySystem, aOutMessage); |
151 | | #ifdef MOZ_WIDGET_ANDROID |
152 | | } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible", false)) { |
153 | | nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem); |
154 | | bool supported = mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem); |
155 | | if (!supported) { |
156 | | aOutMessage = NS_LITERAL_CSTRING("KeySystem or Minimum API level not met for Widevine EME"); |
157 | | return MediaKeySystemStatus::Cdm_not_supported; |
158 | | } |
159 | | return MediaKeySystemStatus::Available; |
160 | | #endif |
161 | | } |
162 | 0 | } |
163 | 0 |
|
164 | 0 | return MediaKeySystemStatus::Cdm_not_supported; |
165 | 0 | } |
166 | | |
167 | | typedef nsCString EMECodecString; |
168 | | |
169 | | static NS_NAMED_LITERAL_CSTRING(EME_CODEC_AAC, "aac"); |
170 | | static NS_NAMED_LITERAL_CSTRING(EME_CODEC_OPUS, "opus"); |
171 | | static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VORBIS, "vorbis"); |
172 | | static NS_NAMED_LITERAL_CSTRING(EME_CODEC_FLAC, "flac"); |
173 | | static NS_NAMED_LITERAL_CSTRING(EME_CODEC_H264, "h264"); |
174 | | static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP8, "vp8"); |
175 | | static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP9, "vp9"); |
176 | | |
177 | | EMECodecString |
178 | | ToEMEAPICodecString(const nsString& aCodec) |
179 | 0 | { |
180 | 0 | if (IsAACCodecString(aCodec)) { |
181 | 0 | return EME_CODEC_AAC; |
182 | 0 | } |
183 | 0 | if (aCodec.EqualsLiteral("opus")) { |
184 | 0 | return EME_CODEC_OPUS; |
185 | 0 | } |
186 | 0 | if (aCodec.EqualsLiteral("vorbis")) { |
187 | 0 | return EME_CODEC_VORBIS; |
188 | 0 | } |
189 | 0 | if (aCodec.EqualsLiteral("flac")) { |
190 | 0 | return EME_CODEC_FLAC; |
191 | 0 | } |
192 | 0 | if (IsH264CodecString(aCodec)) { |
193 | 0 | return EME_CODEC_H264; |
194 | 0 | } |
195 | 0 | if (IsVP8CodecString(aCodec)) { |
196 | 0 | return EME_CODEC_VP8; |
197 | 0 | } |
198 | 0 | if (IsVP9CodecString(aCodec)) { |
199 | 0 | return EME_CODEC_VP9; |
200 | 0 | } |
201 | 0 | return EmptyCString(); |
202 | 0 | } |
203 | | |
204 | | // A codec can be decrypted-and-decoded by the CDM, or only decrypted |
205 | | // by the CDM and decoded by Gecko. Not both. |
206 | | struct KeySystemContainerSupport |
207 | | { |
208 | | bool IsSupported() const |
209 | 0 | { |
210 | 0 | return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty(); |
211 | 0 | } |
212 | | |
213 | | // CDM decrypts and decodes using a DRM robust decoder, and passes decoded |
214 | | // samples back to Gecko for rendering. |
215 | | bool DecryptsAndDecodes(EMECodecString aCodec) const |
216 | 0 | { |
217 | 0 | return mCodecsDecoded.Contains(aCodec); |
218 | 0 | } |
219 | | |
220 | | // CDM decrypts and passes the decrypted samples back to Gecko for decoding. |
221 | | bool Decrypts(EMECodecString aCodec) const |
222 | 0 | { |
223 | 0 | return mCodecsDecrypted.Contains(aCodec); |
224 | 0 | } |
225 | | |
226 | | void SetCanDecryptAndDecode(EMECodecString aCodec) |
227 | 0 | { |
228 | 0 | // Can't both decrypt and decrypt-and-decode a codec. |
229 | 0 | MOZ_ASSERT(!Decrypts(aCodec)); |
230 | 0 | // Prevent duplicates. |
231 | 0 | MOZ_ASSERT(!DecryptsAndDecodes(aCodec)); |
232 | 0 | mCodecsDecoded.AppendElement(aCodec); |
233 | 0 | } |
234 | | |
235 | | void SetCanDecrypt(EMECodecString aCodec) |
236 | 0 | { |
237 | 0 | // Prevent duplicates. |
238 | 0 | MOZ_ASSERT(!Decrypts(aCodec)); |
239 | 0 | // Can't both decrypt and decrypt-and-decode a codec. |
240 | 0 | MOZ_ASSERT(!DecryptsAndDecodes(aCodec)); |
241 | 0 | mCodecsDecrypted.AppendElement(aCodec); |
242 | 0 | } |
243 | | |
244 | | private: |
245 | | nsTArray<EMECodecString> mCodecsDecoded; |
246 | | nsTArray<EMECodecString> mCodecsDecrypted; |
247 | | }; |
248 | | |
249 | | enum class KeySystemFeatureSupport |
250 | | { |
251 | | Prohibited = 1, |
252 | | Requestable = 2, |
253 | | Required = 3, |
254 | | }; |
255 | | |
256 | | struct KeySystemConfig |
257 | | { |
258 | | nsString mKeySystem; |
259 | | nsTArray<nsString> mInitDataTypes; |
260 | | KeySystemFeatureSupport mPersistentState = KeySystemFeatureSupport::Prohibited; |
261 | | KeySystemFeatureSupport mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited; |
262 | | nsTArray<MediaKeySessionType> mSessionTypes; |
263 | | nsTArray<nsString> mVideoRobustness; |
264 | | nsTArray<nsString> mAudioRobustness; |
265 | | KeySystemContainerSupport mMP4; |
266 | | KeySystemContainerSupport mWebM; |
267 | | }; |
268 | | |
269 | | static nsTArray<KeySystemConfig> |
270 | | GetSupportedKeySystems() |
271 | 0 | { |
272 | 0 | nsTArray<KeySystemConfig> keySystemConfigs; |
273 | 0 |
|
274 | 0 | { |
275 | 0 | if (HavePluginForKeySystem(kEMEKeySystemClearkey)) { |
276 | 0 | KeySystemConfig clearkey; |
277 | 0 | clearkey.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemClearkey); |
278 | 0 | clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc")); |
279 | 0 | clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids")); |
280 | 0 | clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm")); |
281 | 0 | clearkey.mPersistentState = KeySystemFeatureSupport::Requestable; |
282 | 0 | clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited; |
283 | 0 | clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary); |
284 | 0 | if (StaticPrefs::MediaClearkeyPersistentLicenseEnabled()) { |
285 | 0 | clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license); |
286 | 0 | } |
287 | | #if defined(XP_WIN) |
288 | | // Clearkey CDM uses WMF's H.264 decoder on Windows. |
289 | | if (WMFDecoderModule::HasH264()) { |
290 | | clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264); |
291 | | } else { |
292 | | clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264); |
293 | | } |
294 | | #else |
295 | | clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264); |
296 | 0 | #endif |
297 | 0 | clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC); |
298 | 0 | clearkey.mMP4.SetCanDecrypt(EME_CODEC_FLAC); |
299 | 0 | clearkey.mMP4.SetCanDecrypt(EME_CODEC_OPUS); |
300 | 0 | if (Preferences::GetBool("media.eme.vp9-in-mp4.enabled", false)) { |
301 | 0 | clearkey.mMP4.SetCanDecrypt(EME_CODEC_VP9); |
302 | 0 | } |
303 | 0 | clearkey.mWebM.SetCanDecrypt(EME_CODEC_VORBIS); |
304 | 0 | clearkey.mWebM.SetCanDecrypt(EME_CODEC_OPUS); |
305 | 0 | clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP8); |
306 | 0 | clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP9); |
307 | 0 | keySystemConfigs.AppendElement(std::move(clearkey)); |
308 | 0 | } |
309 | 0 | } |
310 | 0 | { |
311 | 0 | if (HavePluginForKeySystem(kEMEKeySystemWidevine)) { |
312 | 0 | KeySystemConfig widevine; |
313 | 0 | widevine.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemWidevine); |
314 | 0 | widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc")); |
315 | 0 | widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids")); |
316 | 0 | widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm")); |
317 | 0 | widevine.mPersistentState = KeySystemFeatureSupport::Requestable; |
318 | 0 | widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited; |
319 | 0 | widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary); |
320 | | #ifdef MOZ_WIDGET_ANDROID |
321 | | widevine.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license); |
322 | | #endif |
323 | 0 | widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO")); |
324 | 0 | widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO")); |
325 | 0 | widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE")); |
326 | | #if defined(XP_WIN) |
327 | | // Widevine CDM doesn't include an AAC decoder. So if WMF can't |
328 | | // decode AAC, and a codec wasn't specified, be conservative |
329 | | // and reject the MediaKeys request, since we assume Widevine |
330 | | // will be used with AAC. |
331 | | if (WMFDecoderModule::HasAAC()) { |
332 | | widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC); |
333 | | } |
334 | | #elif !defined(MOZ_WIDGET_ANDROID) |
335 | | widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC); |
336 | 0 | #endif |
337 | 0 | widevine.mMP4.SetCanDecrypt(EME_CODEC_FLAC); |
338 | 0 | widevine.mMP4.SetCanDecrypt(EME_CODEC_OPUS); |
339 | 0 |
|
340 | | #if defined(MOZ_WIDGET_ANDROID) |
341 | | using namespace mozilla::java; |
342 | | // MediaDrm.isCryptoSchemeSupported only allows passing |
343 | | // "video/mp4" or "video/webm" for mimetype string. |
344 | | // See https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID, java.lang.String) |
345 | | // for more detail. |
346 | | typedef struct { |
347 | | const nsCString& mMimeType; |
348 | | const nsCString& mEMECodecType; |
349 | | const char16_t* mCodecType; |
350 | | KeySystemContainerSupport* mSupportType; |
351 | | } DataForValidation; |
352 | | |
353 | | DataForValidation validationList[] = { |
354 | | { nsCString(VIDEO_MP4), EME_CODEC_H264, MediaDrmProxy::AVC, &widevine.mMP4 }, |
355 | | { nsCString(VIDEO_MP4), EME_CODEC_VP9, MediaDrmProxy::AVC, &widevine.mMP4 }, |
356 | | { nsCString(AUDIO_MP4), EME_CODEC_AAC, MediaDrmProxy::AAC, &widevine.mMP4 }, |
357 | | { nsCString(AUDIO_MP4), EME_CODEC_FLAC, MediaDrmProxy::FLAC, &widevine.mMP4 }, |
358 | | { nsCString(AUDIO_MP4), EME_CODEC_OPUS, MediaDrmProxy::OPUS, &widevine.mMP4 }, |
359 | | { nsCString(VIDEO_WEBM), EME_CODEC_VP8, MediaDrmProxy::VP8, &widevine.mWebM }, |
360 | | { nsCString(VIDEO_WEBM), EME_CODEC_VP9, MediaDrmProxy::VP9, &widevine.mWebM}, |
361 | | { nsCString(AUDIO_WEBM), EME_CODEC_VORBIS, MediaDrmProxy::VORBIS, &widevine.mWebM}, |
362 | | { nsCString(AUDIO_WEBM), EME_CODEC_OPUS, MediaDrmProxy::OPUS, &widevine.mWebM}, |
363 | | }; |
364 | | |
365 | | for (const auto& data: validationList) { |
366 | | if (MediaDrmProxy::IsCryptoSchemeSupported(kEMEKeySystemWidevine, |
367 | | data.mMimeType)) { |
368 | | if (MediaDrmProxy::CanDecode(data.mCodecType)) { |
369 | | data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType); |
370 | | } else { |
371 | | data.mSupportType->SetCanDecrypt(data.mEMECodecType); |
372 | | } |
373 | | } |
374 | | } |
375 | | #else |
376 | | widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264); |
377 | 0 | if (Preferences::GetBool("media.eme.vp9-in-mp4.enabled", false)) { |
378 | 0 | widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_VP9); |
379 | 0 | } |
380 | 0 | widevine.mWebM.SetCanDecrypt(EME_CODEC_VORBIS); |
381 | 0 | widevine.mWebM.SetCanDecrypt(EME_CODEC_OPUS); |
382 | 0 | widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8); |
383 | 0 | widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9); |
384 | 0 | #endif |
385 | 0 | keySystemConfigs.AppendElement(std::move(widevine)); |
386 | 0 | } |
387 | 0 | } |
388 | 0 |
|
389 | 0 | return keySystemConfigs; |
390 | 0 | } |
391 | | |
392 | | static bool |
393 | | GetKeySystemConfig(const nsAString& aKeySystem, KeySystemConfig& aOutKeySystemConfig) |
394 | 0 | { |
395 | 0 | for (auto&& config : GetSupportedKeySystems()) { |
396 | 0 | if (config.mKeySystem.Equals(aKeySystem)) { |
397 | 0 | aOutKeySystemConfig = std::move(config); |
398 | 0 | return true; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | // No matching key system found. |
402 | 0 | return false; |
403 | 0 | } |
404 | | |
405 | | /* static */ |
406 | | bool |
407 | | MediaKeySystemAccess::KeySystemSupportsInitDataType(const nsAString& aKeySystem, |
408 | | const nsAString& aInitDataType) |
409 | 0 | { |
410 | 0 | KeySystemConfig implementation; |
411 | 0 | return GetKeySystemConfig(aKeySystem, implementation) && |
412 | 0 | implementation.mInitDataTypes.Contains(aInitDataType); |
413 | 0 | } |
414 | | |
415 | | enum CodecType |
416 | | { |
417 | | Audio, |
418 | | Video, |
419 | | Invalid |
420 | | }; |
421 | | |
422 | | static bool |
423 | | CanDecryptAndDecode(const nsString& aKeySystem, |
424 | | const nsString& aContentType, |
425 | | CodecType aCodecType, |
426 | | const KeySystemContainerSupport& aContainerSupport, |
427 | | const nsTArray<EMECodecString>& aCodecs, |
428 | | DecoderDoctorDiagnostics* aDiagnostics) |
429 | 0 | { |
430 | 0 | MOZ_ASSERT(aCodecType != Invalid); |
431 | 0 | for (const EMECodecString& codec : aCodecs) { |
432 | 0 | MOZ_ASSERT(!codec.IsEmpty()); |
433 | 0 |
|
434 | 0 | if (aContainerSupport.DecryptsAndDecodes(codec)) { |
435 | 0 | // GMP can decrypt-and-decode this codec. |
436 | 0 | continue; |
437 | 0 | } |
438 | 0 | |
439 | 0 | if (aContainerSupport.Decrypts(codec) && |
440 | 0 | NS_SUCCEEDED(MediaSource::IsTypeSupported(aContentType, aDiagnostics))) { |
441 | 0 | // GMP can decrypt and is allowed to return compressed samples to |
442 | 0 | // Gecko to decode, and Gecko has a decoder. |
443 | 0 | continue; |
444 | 0 | } |
445 | 0 | |
446 | 0 | // Neither the GMP nor Gecko can both decrypt and decode. We don't |
447 | 0 | // support this codec. |
448 | 0 | |
449 | | #if defined(XP_WIN) |
450 | | // Widevine CDM doesn't include an AAC decoder. So if WMF can't |
451 | | // decode AAC, and a codec wasn't specified, be conservative |
452 | | // and reject the MediaKeys request, since we assume Widevine |
453 | | // will be used with AAC. |
454 | | if (codec == EME_CODEC_AAC && |
455 | | IsWidevineKeySystem(aKeySystem) && |
456 | | !WMFDecoderModule::HasAAC()) { |
457 | | if (aDiagnostics) { |
458 | | aDiagnostics->SetKeySystemIssue( |
459 | | DecoderDoctorDiagnostics::eWidevineWithNoWMF); |
460 | | } |
461 | | } |
462 | | #endif |
463 | 0 | return false; |
464 | 0 | } |
465 | 0 | return true; |
466 | 0 | } |
467 | | |
468 | | static bool |
469 | | ToSessionType(const nsAString& aSessionType, MediaKeySessionType& aOutType) |
470 | 0 | { |
471 | 0 | if (aSessionType.Equals(ToString(MediaKeySessionType::Temporary))) { |
472 | 0 | aOutType = MediaKeySessionType::Temporary; |
473 | 0 | return true; |
474 | 0 | } |
475 | 0 | if (aSessionType.Equals(ToString(MediaKeySessionType::Persistent_license))) { |
476 | 0 | aOutType = MediaKeySessionType::Persistent_license; |
477 | 0 | return true; |
478 | 0 | } |
479 | 0 | return false; |
480 | 0 | } |
481 | | |
482 | | // 5.2.1 Is persistent session type? |
483 | | static bool |
484 | | IsPersistentSessionType(MediaKeySessionType aSessionType) |
485 | 0 | { |
486 | 0 | return aSessionType == MediaKeySessionType::Persistent_license; |
487 | 0 | } |
488 | | |
489 | | CodecType |
490 | | GetMajorType(const MediaMIMEType& aMIMEType) |
491 | 0 | { |
492 | 0 | if (aMIMEType.HasAudioMajorType()) { |
493 | 0 | return Audio; |
494 | 0 | } |
495 | 0 | if (aMIMEType.HasVideoMajorType()) { |
496 | 0 | return Video; |
497 | 0 | } |
498 | 0 | return Invalid; |
499 | 0 | } |
500 | | |
501 | | static CodecType |
502 | | GetCodecType(const EMECodecString& aCodec) |
503 | 0 | { |
504 | 0 | if (aCodec.Equals(EME_CODEC_AAC) || |
505 | 0 | aCodec.Equals(EME_CODEC_OPUS) || |
506 | 0 | aCodec.Equals(EME_CODEC_VORBIS) || |
507 | 0 | aCodec.Equals(EME_CODEC_FLAC)) { |
508 | 0 | return Audio; |
509 | 0 | } |
510 | 0 | if (aCodec.Equals(EME_CODEC_H264) || |
511 | 0 | aCodec.Equals(EME_CODEC_VP8) || |
512 | 0 | aCodec.Equals(EME_CODEC_VP9)) { |
513 | 0 | return Video; |
514 | 0 | } |
515 | 0 | return Invalid; |
516 | 0 | } |
517 | | |
518 | | static bool |
519 | | AllCodecsOfType(const nsTArray<EMECodecString>& aCodecs, const CodecType aCodecType) |
520 | 0 | { |
521 | 0 | for (const EMECodecString& codec : aCodecs) { |
522 | 0 | if (GetCodecType(codec) != aCodecType) { |
523 | 0 | return false; |
524 | 0 | } |
525 | 0 | } |
526 | 0 | return true; |
527 | 0 | } |
528 | | |
529 | | static bool |
530 | | IsParameterUnrecognized(const nsAString& aContentType) |
531 | 0 | { |
532 | 0 | nsAutoString contentType(aContentType); |
533 | 0 | contentType.StripWhitespace(); |
534 | 0 |
|
535 | 0 | nsTArray<nsString> params; |
536 | 0 | nsAString::const_iterator start, end, semicolon, equalSign; |
537 | 0 | contentType.BeginReading(start); |
538 | 0 | contentType.EndReading(end); |
539 | 0 | semicolon = start; |
540 | 0 | // Find any substring between ';' & '='. |
541 | 0 | while (semicolon != end) { |
542 | 0 | if (FindCharInReadable(';', semicolon, end)) { |
543 | 0 | equalSign = ++semicolon; |
544 | 0 | if (FindCharInReadable('=', equalSign, end)) { |
545 | 0 | params.AppendElement(Substring(semicolon, equalSign)); |
546 | 0 | semicolon = equalSign; |
547 | 0 | } |
548 | 0 | } |
549 | 0 | } |
550 | 0 |
|
551 | 0 | for (auto param : params) { |
552 | 0 | if (!param.LowerCaseEqualsLiteral("codecs") && |
553 | 0 | !param.LowerCaseEqualsLiteral("profiles")) { |
554 | 0 | return true; |
555 | 0 | } |
556 | 0 | } |
557 | 0 | return false; |
558 | 0 | } |
559 | | |
560 | | // 3.1.2.3 Get Supported Capabilities for Audio/Video Type |
561 | | static Sequence<MediaKeySystemMediaCapability> |
562 | | GetSupportedCapabilities( |
563 | | const CodecType aCodecType, |
564 | | const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities, |
565 | | const MediaKeySystemConfiguration& aPartialConfig, |
566 | | const KeySystemConfig& aKeySystem, |
567 | | DecoderDoctorDiagnostics* aDiagnostics, |
568 | | const std::function<void(const char*)>& aDeprecationLogFn) |
569 | 0 | { |
570 | 0 | // Let local accumulated configuration be a local copy of partial configuration. |
571 | 0 | // (Note: It's not necessary for us to maintain a local copy, as we don't need |
572 | 0 | // to test whether capabilites from previous calls to this algorithm work with |
573 | 0 | // the capabilities currently being considered in this call. ) |
574 | 0 |
|
575 | 0 | // Let supported media capabilities be an empty sequence of |
576 | 0 | // MediaKeySystemMediaCapability dictionaries. |
577 | 0 | Sequence<MediaKeySystemMediaCapability> supportedCapabilities; |
578 | 0 |
|
579 | 0 | // For each requested media capability in requested media capabilities: |
580 | 0 | for (const MediaKeySystemMediaCapability& capabilities : aRequestedCapabilities) { |
581 | 0 | // Let content type be requested media capability's contentType member. |
582 | 0 | const nsString& contentTypeString = capabilities.mContentType; |
583 | 0 | // Let robustness be requested media capability's robustness member. |
584 | 0 | const nsString& robustness = capabilities.mRobustness; |
585 | 0 | // If content type is the empty string, return null. |
586 | 0 | if (contentTypeString.IsEmpty()) { |
587 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
588 | 0 | "MediaKeySystemMediaCapability('%s','%s') rejected; " |
589 | 0 | "audio or video capability has empty contentType.", |
590 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
591 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
592 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
593 | 0 | return Sequence<MediaKeySystemMediaCapability>(); |
594 | 0 | } |
595 | 0 | // If content type is an invalid or unrecognized MIME type, continue |
596 | 0 | // to the next iteration. |
597 | 0 | Maybe<MediaContainerType> maybeContainerType = |
598 | 0 | MakeMediaContainerType(contentTypeString); |
599 | 0 | if (!maybeContainerType) { |
600 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
601 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
602 | 0 | "failed to parse contentTypeString as MIME type.", |
603 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
604 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
605 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
606 | 0 | continue; |
607 | 0 | } |
608 | 0 | const MediaContainerType& containerType = *maybeContainerType; |
609 | 0 | bool invalid = false; |
610 | 0 | nsTArray<EMECodecString> codecs; |
611 | 0 | for (const auto& codecString : containerType.ExtendedType().Codecs().Range()) { |
612 | 0 | EMECodecString emeCodec = ToEMEAPICodecString(nsString(codecString)); |
613 | 0 | if (emeCodec.IsEmpty()) { |
614 | 0 | invalid = true; |
615 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
616 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
617 | 0 | "'%s' is an invalid codec string.", |
618 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
619 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
620 | 0 | NS_ConvertUTF16toUTF8(robustness).get(), |
621 | 0 | NS_ConvertUTF16toUTF8(codecString).get()); |
622 | 0 | break; |
623 | 0 | } |
624 | 0 | codecs.AppendElement(emeCodec); |
625 | 0 | } |
626 | 0 | if (invalid) { |
627 | 0 | continue; |
628 | 0 | } |
629 | 0 | |
630 | 0 | // If the user agent does not support container, continue to the next iteration. |
631 | 0 | // The case-sensitivity of string comparisons is determined by the appropriate RFC. |
632 | 0 | // (Note: Per RFC 6838 [RFC6838], "Both top-level type and subtype names are |
633 | 0 | // case-insensitive."'. We're using nsContentTypeParser and that is |
634 | 0 | // case-insensitive and converts all its parameter outputs to lower case.) |
635 | 0 | const bool isMP4 = |
636 | 0 | DecoderTraits::IsMP4SupportedType(containerType, aDiagnostics); |
637 | 0 | if (isMP4 && !aKeySystem.mMP4.IsSupported()) { |
638 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
639 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
640 | 0 | "MP4 requested but unsupported.", |
641 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
642 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
643 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
644 | 0 | continue; |
645 | 0 | } |
646 | 0 | const bool isWebM = WebMDecoder::IsSupportedType(containerType); |
647 | 0 | if (isWebM && !aKeySystem.mWebM.IsSupported()) { |
648 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
649 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
650 | 0 | "WebM requested but unsupported.", |
651 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
652 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
653 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
654 | 0 | continue; |
655 | 0 | } |
656 | 0 | if (!isMP4 && !isWebM) { |
657 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
658 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
659 | 0 | "Unsupported or unrecognized container requested.", |
660 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
661 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
662 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
663 | 0 | continue; |
664 | 0 | } |
665 | 0 |
|
666 | 0 | // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by |
667 | 0 | // content type. |
668 | 0 | // If the user agent does not recognize one or more parameters, continue to |
669 | 0 | // the next iteration. |
670 | 0 | if (IsParameterUnrecognized(contentTypeString)) { |
671 | 0 | continue; |
672 | 0 | } |
673 | 0 | |
674 | 0 | // Let media types be the set of codecs and codec constraints specified by |
675 | 0 | // parameters. The case-sensitivity of string comparisons is determined by |
676 | 0 | // the appropriate RFC or other specification. |
677 | 0 | // (Note: codecs array is 'parameter'). |
678 | 0 | |
679 | 0 | // If media types is empty: |
680 | 0 | if (codecs.IsEmpty()) { |
681 | 0 | // Log deprecation warning to encourage authors to not do this! |
682 | 0 | aDeprecationLogFn("MediaEMENoCodecsDeprecatedWarning"); |
683 | 0 | // TODO: Remove this once we're sure it doesn't break the web. |
684 | 0 | // If container normatively implies a specific set of codecs and codec constraints: |
685 | 0 | // Let parameters be that set. |
686 | 0 | if (isMP4) { |
687 | 0 | if (aCodecType == Audio) { |
688 | 0 | codecs.AppendElement(EME_CODEC_AAC); |
689 | 0 | } else if (aCodecType == Video) { |
690 | 0 | codecs.AppendElement(EME_CODEC_H264); |
691 | 0 | } |
692 | 0 | } else if (isWebM) { |
693 | 0 | if (aCodecType == Audio) { |
694 | 0 | codecs.AppendElement(EME_CODEC_VORBIS); |
695 | 0 | } else if (aCodecType == Video) { |
696 | 0 | codecs.AppendElement(EME_CODEC_VP8); |
697 | 0 | } |
698 | 0 | } |
699 | 0 | // Otherwise: Continue to the next iteration. |
700 | 0 | // (Note: all containers we support have implied codecs, so don't continue here.) |
701 | 0 | } |
702 | 0 |
|
703 | 0 | // If container type is not strictly a audio/video type, continue to the next iteration. |
704 | 0 | const auto majorType = GetMajorType(containerType.Type()); |
705 | 0 | if (majorType == Invalid) { |
706 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
707 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
708 | 0 | "MIME type is not an audio or video MIME type.", |
709 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
710 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
711 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
712 | 0 | continue; |
713 | 0 | } |
714 | 0 | if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) { |
715 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
716 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
717 | 0 | "MIME type mixes audio codecs in video capabilities " |
718 | 0 | "or video codecs in audio capabilities.", |
719 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
720 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
721 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
722 | 0 | continue; |
723 | 0 | } |
724 | 0 | // If robustness is not the empty string and contains an unrecognized |
725 | 0 | // value or a value not supported by implementation, continue to the |
726 | 0 | // next iteration. String comparison is case-sensitive. |
727 | 0 | if (!robustness.IsEmpty()) { |
728 | 0 | if (majorType == Audio && !aKeySystem.mAudioRobustness.Contains(robustness)) { |
729 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
730 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
731 | 0 | "unsupported robustness string.", |
732 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
733 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
734 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
735 | 0 | continue; |
736 | 0 | } |
737 | 0 | if (majorType == Video && !aKeySystem.mVideoRobustness.Contains(robustness)) { |
738 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
739 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
740 | 0 | "unsupported robustness string.", |
741 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
742 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
743 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
744 | 0 | continue; |
745 | 0 | } |
746 | 0 | // Note: specified robustness requirements are satisfied. |
747 | 0 | } |
748 | 0 |
|
749 | 0 | // If the user agent and implementation definitely support playback of |
750 | 0 | // encrypted media data for the combination of container, media types, |
751 | 0 | // robustness and local accumulated configuration in combination with |
752 | 0 | // restrictions... |
753 | 0 | const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM; |
754 | 0 | if (!CanDecryptAndDecode(aKeySystem.mKeySystem, |
755 | 0 | contentTypeString, |
756 | 0 | majorType, |
757 | 0 | containerSupport, |
758 | 0 | codecs, |
759 | 0 | aDiagnostics)) { |
760 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') " |
761 | 0 | "MediaKeySystemMediaCapability('%s','%s') unsupported; " |
762 | 0 | "codec unsupported by CDM requested.", |
763 | 0 | NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), |
764 | 0 | NS_ConvertUTF16toUTF8(contentTypeString).get(), |
765 | 0 | NS_ConvertUTF16toUTF8(robustness).get()); |
766 | 0 | continue; |
767 | 0 | } |
768 | 0 |
|
769 | 0 | // ... add requested media capability to supported media capabilities. |
770 | 0 | if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) { |
771 | 0 | NS_WARNING("GetSupportedCapabilities: Malloc failure"); |
772 | 0 | return Sequence<MediaKeySystemMediaCapability>(); |
773 | 0 | } |
774 | 0 |
|
775 | 0 | // Note: omitting steps 3.13.2, our robustness is not sophisticated enough |
776 | 0 | // to require considering all requirements together. |
777 | 0 | } |
778 | 0 | return supportedCapabilities; |
779 | 0 | } |
780 | | |
781 | | // "Get Supported Configuration and Consent" algorithm, steps 4-7 for |
782 | | // distinctive identifier, and steps 8-11 for persistent state. The steps |
783 | | // are the same for both requirements/features, so we factor them out into |
784 | | // a single function. |
785 | | static bool |
786 | | CheckRequirement(const MediaKeysRequirement aRequirement, |
787 | | const KeySystemFeatureSupport aFeatureSupport, |
788 | | MediaKeysRequirement& aOutRequirement) |
789 | 0 | { |
790 | 0 | // Let requirement be the value of candidate configuration's member. |
791 | 0 | MediaKeysRequirement requirement = aRequirement; |
792 | 0 | // If requirement is "optional" and feature is not allowed according to |
793 | 0 | // restrictions, set requirement to "not-allowed". |
794 | 0 | if (aRequirement == MediaKeysRequirement::Optional && |
795 | 0 | aFeatureSupport == KeySystemFeatureSupport::Prohibited) { |
796 | 0 | requirement = MediaKeysRequirement::Not_allowed; |
797 | 0 | } |
798 | 0 |
|
799 | 0 | // Follow the steps for requirement from the following list: |
800 | 0 | switch (requirement) { |
801 | 0 | case MediaKeysRequirement::Required: { |
802 | 0 | // If the implementation does not support use of requirement in combination |
803 | 0 | // with accumulated configuration and restrictions, return NotSupported. |
804 | 0 | if (aFeatureSupport == KeySystemFeatureSupport::Prohibited) { |
805 | 0 | return false; |
806 | 0 | } |
807 | 0 | break; |
808 | 0 | } |
809 | 0 | case MediaKeysRequirement::Optional: { |
810 | 0 | // Continue with the following steps. |
811 | 0 | break; |
812 | 0 | } |
813 | 0 | case MediaKeysRequirement::Not_allowed: { |
814 | 0 | // If the implementation requires use of feature in combination with |
815 | 0 | // accumulated configuration and restrictions, return NotSupported. |
816 | 0 | if (aFeatureSupport == KeySystemFeatureSupport::Required) { |
817 | 0 | return false; |
818 | 0 | } |
819 | 0 | break; |
820 | 0 | } |
821 | 0 | default: { |
822 | 0 | return false; |
823 | 0 | } |
824 | 0 | } |
825 | 0 | |
826 | 0 | // Set the requirement member of accumulated configuration to equal |
827 | 0 | // calculated requirement. |
828 | 0 | aOutRequirement = requirement; |
829 | 0 |
|
830 | 0 | return true; |
831 | 0 | } |
832 | | |
833 | | // 3.1.2.2, step 12 |
834 | | // Follow the steps for the first matching condition from the following list: |
835 | | // If the sessionTypes member is present in candidate configuration. |
836 | | // Let session types be candidate configuration's sessionTypes member. |
837 | | // Otherwise let session types be ["temporary"]. |
838 | | // Note: This returns an empty array on malloc failure. |
839 | | static Sequence<nsString> |
840 | | UnboxSessionTypes(const Optional<Sequence<nsString>>& aSessionTypes) |
841 | 0 | { |
842 | 0 | Sequence<nsString> sessionTypes; |
843 | 0 | if (aSessionTypes.WasPassed()) { |
844 | 0 | sessionTypes = aSessionTypes.Value(); |
845 | 0 | } else { |
846 | 0 | // Note: fallible. Results in an empty array. |
847 | 0 | sessionTypes.AppendElement(ToString(MediaKeySessionType::Temporary), |
848 | 0 | mozilla::fallible); |
849 | 0 | } |
850 | 0 | return sessionTypes; |
851 | 0 | } |
852 | | |
853 | | // 3.1.2.2 Get Supported Configuration and Consent |
854 | | static bool |
855 | | GetSupportedConfig(const KeySystemConfig& aKeySystem, |
856 | | const MediaKeySystemConfiguration& aCandidate, |
857 | | MediaKeySystemConfiguration& aOutConfig, |
858 | | DecoderDoctorDiagnostics* aDiagnostics, |
859 | | bool aInPrivateBrowsing, |
860 | | const std::function<void(const char*)>& aDeprecationLogFn) |
861 | 0 | { |
862 | 0 | // Let accumulated configuration be a new MediaKeySystemConfiguration dictionary. |
863 | 0 | MediaKeySystemConfiguration config; |
864 | 0 | // Set the label member of accumulated configuration to equal the label member of |
865 | 0 | // candidate configuration. |
866 | 0 | config.mLabel = aCandidate.mLabel; |
867 | 0 | // If the initDataTypes member of candidate configuration is non-empty, run the |
868 | 0 | // following steps: |
869 | 0 | if (!aCandidate.mInitDataTypes.IsEmpty()) { |
870 | 0 | // Let supported types be an empty sequence of DOMStrings. |
871 | 0 | nsTArray<nsString> supportedTypes; |
872 | 0 | // For each value in candidate configuration's initDataTypes member: |
873 | 0 | for (const nsString& initDataType : aCandidate.mInitDataTypes) { |
874 | 0 | // Let initDataType be the value. |
875 | 0 | // If the implementation supports generating requests based on initDataType, |
876 | 0 | // add initDataType to supported types. String comparison is case-sensitive. |
877 | 0 | // The empty string is never supported. |
878 | 0 | if (aKeySystem.mInitDataTypes.Contains(initDataType)) { |
879 | 0 | supportedTypes.AppendElement(initDataType); |
880 | 0 | } |
881 | 0 | } |
882 | 0 | // If supported types is empty, return NotSupported. |
883 | 0 | if (supportedTypes.IsEmpty()) { |
884 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
885 | 0 | "no supported initDataTypes provided.", |
886 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
887 | 0 | return false; |
888 | 0 | } |
889 | 0 | // Set the initDataTypes member of accumulated configuration to supported types. |
890 | 0 | if (!config.mInitDataTypes.Assign(supportedTypes)) { |
891 | 0 | return false; |
892 | 0 | } |
893 | 0 | } |
894 | 0 | |
895 | 0 | if (!CheckRequirement(aCandidate.mDistinctiveIdentifier, |
896 | 0 | aKeySystem.mDistinctiveIdentifier, |
897 | 0 | config.mDistinctiveIdentifier)) { |
898 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
899 | 0 | "distinctiveIdentifier requirement not satisfied.", |
900 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
901 | 0 | return false; |
902 | 0 | } |
903 | 0 |
|
904 | 0 | if (!CheckRequirement(aCandidate.mPersistentState, |
905 | 0 | aKeySystem.mPersistentState, |
906 | 0 | config.mPersistentState)) { |
907 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
908 | 0 | "persistentState requirement not satisfied.", |
909 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
910 | 0 | return false; |
911 | 0 | } |
912 | 0 |
|
913 | 0 | if (config.mPersistentState == MediaKeysRequirement::Required && |
914 | 0 | aInPrivateBrowsing) { |
915 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
916 | 0 | "persistentState requested in Private Browsing window.", |
917 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
918 | 0 | return false; |
919 | 0 | } |
920 | 0 |
|
921 | 0 | Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes)); |
922 | 0 | if (sessionTypes.IsEmpty()) { |
923 | 0 | // Malloc failure. |
924 | 0 | return false; |
925 | 0 | } |
926 | 0 | |
927 | 0 | // For each value in session types: |
928 | 0 | for (const auto& sessionTypeString : sessionTypes) { |
929 | 0 | // Let session type be the value. |
930 | 0 | MediaKeySessionType sessionType; |
931 | 0 | if (!ToSessionType(sessionTypeString, sessionType)) { |
932 | 0 | // (Assume invalid sessionType is unsupported as per steps below). |
933 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
934 | 0 | "invalid session type specified.", |
935 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
936 | 0 | return false; |
937 | 0 | } |
938 | 0 | // If accumulated configuration's persistentState value is "not-allowed" |
939 | 0 | // and the Is persistent session type? algorithm returns true for session |
940 | 0 | // type return NotSupported. |
941 | 0 | if (config.mPersistentState == MediaKeysRequirement::Not_allowed && |
942 | 0 | IsPersistentSessionType(sessionType)) { |
943 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
944 | 0 | "persistent session requested but keysystem doesn't" |
945 | 0 | "support persistent state.", |
946 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
947 | 0 | return false; |
948 | 0 | } |
949 | 0 | // If the implementation does not support session type in combination |
950 | 0 | // with accumulated configuration and restrictions for other reasons, |
951 | 0 | // return NotSupported. |
952 | 0 | if (!aKeySystem.mSessionTypes.Contains(sessionType)) { |
953 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
954 | 0 | "session type '%s' unsupported by keySystem.", |
955 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(), |
956 | 0 | NS_ConvertUTF16toUTF8(sessionTypeString).get()); |
957 | 0 | return false; |
958 | 0 | } |
959 | 0 | // If accumulated configuration's persistentState value is "optional" |
960 | 0 | // and the result of running the Is persistent session type? algorithm |
961 | 0 | // on session type is true, change accumulated configuration's |
962 | 0 | // persistentState value to "required". |
963 | 0 | if (config.mPersistentState == MediaKeysRequirement::Optional && |
964 | 0 | IsPersistentSessionType(sessionType)) { |
965 | 0 | config.mPersistentState = MediaKeysRequirement::Required; |
966 | 0 | } |
967 | 0 | } |
968 | 0 | // Set the sessionTypes member of accumulated configuration to session types. |
969 | 0 | config.mSessionTypes.Construct(std::move(sessionTypes)); |
970 | 0 |
|
971 | 0 | // If the videoCapabilities and audioCapabilities members in candidate |
972 | 0 | // configuration are both empty, return NotSupported. |
973 | 0 | if (aCandidate.mAudioCapabilities.IsEmpty() && |
974 | 0 | aCandidate.mVideoCapabilities.IsEmpty()) { |
975 | 0 | // TODO: Most sites using EME still don't pass capabilities, so we |
976 | 0 | // can't reject on it yet without breaking them. So add this later. |
977 | 0 | // Log deprecation warning to encourage authors to not do this! |
978 | 0 | aDeprecationLogFn("MediaEMENoCapabilitiesDeprecatedWarning"); |
979 | 0 | } |
980 | 0 |
|
981 | 0 | // If the videoCapabilities member in candidate configuration is non-empty: |
982 | 0 | if (!aCandidate.mVideoCapabilities.IsEmpty()) { |
983 | 0 | // Let video capabilities be the result of executing the Get Supported |
984 | 0 | // Capabilities for Audio/Video Type algorithm on Video, candidate |
985 | 0 | // configuration's videoCapabilities member, accumulated configuration, |
986 | 0 | // and restrictions. |
987 | 0 | Sequence<MediaKeySystemMediaCapability> caps = |
988 | 0 | GetSupportedCapabilities(Video, |
989 | 0 | aCandidate.mVideoCapabilities, |
990 | 0 | config, |
991 | 0 | aKeySystem, |
992 | 0 | aDiagnostics, |
993 | 0 | aDeprecationLogFn); |
994 | 0 | // If video capabilities is null, return NotSupported. |
995 | 0 | if (caps.IsEmpty()) { |
996 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
997 | 0 | "no supported video capabilities.", |
998 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
999 | 0 | return false; |
1000 | 0 | } |
1001 | 0 | // Set the videoCapabilities member of accumulated configuration to video capabilities. |
1002 | 0 | config.mVideoCapabilities = std::move(caps); |
1003 | 0 | } else { |
1004 | 0 | // Otherwise: |
1005 | 0 | // Set the videoCapabilities member of accumulated configuration to an empty sequence. |
1006 | 0 | } |
1007 | 0 |
|
1008 | 0 | // If the audioCapabilities member in candidate configuration is non-empty: |
1009 | 0 | if (!aCandidate.mAudioCapabilities.IsEmpty()) { |
1010 | 0 | // Let audio capabilities be the result of executing the Get Supported Capabilities |
1011 | 0 | // for Audio/Video Type algorithm on Audio, candidate configuration's audioCapabilities |
1012 | 0 | // member, accumulated configuration, and restrictions. |
1013 | 0 | Sequence<MediaKeySystemMediaCapability> caps = |
1014 | 0 | GetSupportedCapabilities(Audio, |
1015 | 0 | aCandidate.mAudioCapabilities, |
1016 | 0 | config, |
1017 | 0 | aKeySystem, |
1018 | 0 | aDiagnostics, |
1019 | 0 | aDeprecationLogFn); |
1020 | 0 | // If audio capabilities is null, return NotSupported. |
1021 | 0 | if (caps.IsEmpty()) { |
1022 | 0 | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
1023 | 0 | "no supported audio capabilities.", |
1024 | 0 | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
1025 | 0 | return false; |
1026 | 0 | } |
1027 | 0 | // Set the audioCapabilities member of accumulated configuration to audio capabilities. |
1028 | 0 | config.mAudioCapabilities = std::move(caps); |
1029 | 0 | } else { |
1030 | 0 | // Otherwise: |
1031 | 0 | // Set the audioCapabilities member of accumulated configuration to an empty sequence. |
1032 | 0 | } |
1033 | 0 |
|
1034 | 0 | // If accumulated configuration's distinctiveIdentifier value is "optional", follow the |
1035 | 0 | // steps for the first matching condition from the following list: |
1036 | 0 | if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) { |
1037 | 0 | // If the implementation requires use Distinctive Identifier(s) or |
1038 | 0 | // Distinctive Permanent Identifier(s) for any of the combinations |
1039 | 0 | // in accumulated configuration |
1040 | 0 | if (aKeySystem.mDistinctiveIdentifier == KeySystemFeatureSupport::Required) { |
1041 | 0 | // Change accumulated configuration's distinctiveIdentifier value to "required". |
1042 | 0 | config.mDistinctiveIdentifier = MediaKeysRequirement::Required; |
1043 | 0 | } else { |
1044 | 0 | // Otherwise, change accumulated configuration's distinctiveIdentifier |
1045 | 0 | // value to "not-allowed". |
1046 | 0 | config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed; |
1047 | 0 | } |
1048 | 0 | } |
1049 | 0 |
|
1050 | 0 | // If accumulated configuration's persistentState value is "optional", follow the |
1051 | 0 | // steps for the first matching condition from the following list: |
1052 | 0 | if (config.mPersistentState == MediaKeysRequirement::Optional) { |
1053 | 0 | // If the implementation requires persisting state for any of the combinations |
1054 | 0 | // in accumulated configuration |
1055 | 0 | if (aKeySystem.mPersistentState == KeySystemFeatureSupport::Required) { |
1056 | 0 | // Change accumulated configuration's persistentState value to "required". |
1057 | 0 | config.mPersistentState = MediaKeysRequirement::Required; |
1058 | 0 | } else { |
1059 | 0 | // Otherwise, change accumulated configuration's persistentState |
1060 | 0 | // value to "not-allowed". |
1061 | 0 | config.mPersistentState = MediaKeysRequirement::Not_allowed; |
1062 | 0 | } |
1063 | 0 | } |
1064 | 0 |
|
1065 | 0 | // Note: Omitting steps 20-22. We don't ask for consent. |
1066 | 0 |
|
1067 | | #if defined(XP_WIN) |
1068 | | // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC, |
1069 | | // and a codec wasn't specified, be conservative and reject the MediaKeys request. |
1070 | | if (IsWidevineKeySystem(aKeySystem.mKeySystem) && |
1071 | | (aCandidate.mAudioCapabilities.IsEmpty() || |
1072 | | aCandidate.mVideoCapabilities.IsEmpty()) && |
1073 | | !WMFDecoderModule::HasAAC()) { |
1074 | | if (aDiagnostics) { |
1075 | | aDiagnostics->SetKeySystemIssue( |
1076 | | DecoderDoctorDiagnostics::eWidevineWithNoWMF); |
1077 | | } |
1078 | | EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; " |
1079 | | "WMF required for Widevine decoding, but it's not available.", |
1080 | | NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); |
1081 | | return false; |
1082 | | } |
1083 | | #endif |
1084 | |
|
1085 | 0 | // Return accumulated configuration. |
1086 | 0 | aOutConfig = config; |
1087 | 0 |
|
1088 | 0 | return true; |
1089 | 0 | } |
1090 | | |
1091 | | /* static */ |
1092 | | bool |
1093 | | MediaKeySystemAccess::GetSupportedConfig( |
1094 | | const nsAString& aKeySystem, |
1095 | | const Sequence<MediaKeySystemConfiguration>& aConfigs, |
1096 | | MediaKeySystemConfiguration& aOutConfig, |
1097 | | DecoderDoctorDiagnostics* aDiagnostics, |
1098 | | bool aIsPrivateBrowsing, |
1099 | | const std::function<void(const char*)>& aDeprecationLogFn) |
1100 | 0 | { |
1101 | 0 | KeySystemConfig implementation; |
1102 | 0 | if (!GetKeySystemConfig(aKeySystem, implementation)) { |
1103 | 0 | return false; |
1104 | 0 | } |
1105 | 0 | for (const MediaKeySystemConfiguration& candidate : aConfigs) { |
1106 | 0 | if (mozilla::dom::GetSupportedConfig(implementation, |
1107 | 0 | candidate, |
1108 | 0 | aOutConfig, |
1109 | 0 | aDiagnostics, |
1110 | 0 | aIsPrivateBrowsing, |
1111 | 0 | aDeprecationLogFn)) { |
1112 | 0 | return true; |
1113 | 0 | } |
1114 | 0 | } |
1115 | 0 |
|
1116 | 0 | return false; |
1117 | 0 | } |
1118 | | |
1119 | | |
1120 | | /* static */ |
1121 | | void |
1122 | | MediaKeySystemAccess::NotifyObservers(nsPIDOMWindowInner* aWindow, |
1123 | | const nsAString& aKeySystem, |
1124 | | MediaKeySystemStatus aStatus) |
1125 | 0 | { |
1126 | 0 | RequestMediaKeySystemAccessNotification data; |
1127 | 0 | data.mKeySystem = aKeySystem; |
1128 | 0 | data.mStatus = aStatus; |
1129 | 0 | nsAutoString json; |
1130 | 0 | data.ToJSON(json); |
1131 | 0 | EME_LOG("MediaKeySystemAccess::NotifyObservers() %s", NS_ConvertUTF16toUTF8(json).get()); |
1132 | 0 | nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
1133 | 0 | if (obs) { |
1134 | 0 | obs->NotifyObservers(aWindow, "mediakeys-request", json.get()); |
1135 | 0 | } |
1136 | 0 | } |
1137 | | |
1138 | | static nsCString |
1139 | | ToCString(const nsString& aString) |
1140 | 0 | { |
1141 | 0 | nsCString str("'"); |
1142 | 0 | str.Append(NS_ConvertUTF16toUTF8(aString)); |
1143 | 0 | str.AppendLiteral("'"); |
1144 | 0 | return str; |
1145 | 0 | } |
1146 | | |
1147 | | static nsCString |
1148 | | ToCString(const MediaKeysRequirement aValue) |
1149 | 0 | { |
1150 | 0 | nsCString str("'"); |
1151 | 0 | str.Append(nsDependentCString( |
1152 | 0 | MediaKeysRequirementValues::strings[static_cast<uint32_t>(aValue)].value)); |
1153 | 0 | str.AppendLiteral("'"); |
1154 | 0 | return str; |
1155 | 0 | } |
1156 | | |
1157 | | static nsCString |
1158 | | ToCString(const MediaKeySystemMediaCapability& aValue) |
1159 | 0 | { |
1160 | 0 | nsCString str; |
1161 | 0 | str.AppendLiteral("{contentType="); |
1162 | 0 | str.Append(ToCString(aValue.mContentType)); |
1163 | 0 | str.AppendLiteral(", robustness="); |
1164 | 0 | str.Append(ToCString(aValue.mRobustness)); |
1165 | 0 | str.AppendLiteral("}"); |
1166 | 0 | return str; |
1167 | 0 | } |
1168 | | |
1169 | | template<class Type> |
1170 | | static nsCString |
1171 | | ToCString(const Sequence<Type>& aSequence) |
1172 | 0 | { |
1173 | 0 | nsCString str; |
1174 | 0 | str.AppendLiteral("["); |
1175 | 0 | for (size_t i = 0; i < aSequence.Length(); i++) { |
1176 | 0 | if (i != 0) { |
1177 | 0 | str.AppendLiteral(","); |
1178 | 0 | } |
1179 | 0 | str.Append(ToCString(aSequence[i])); |
1180 | 0 | } |
1181 | 0 | str.AppendLiteral("]"); |
1182 | 0 | return str; |
1183 | 0 | } Unexecuted instantiation: Unified_cpp_dom_media_eme0.cpp:nsTString<char> mozilla::dom::ToCString<nsTString<char16_t> >(mozilla::dom::Sequence<nsTString<char16_t> > const&) Unexecuted instantiation: Unified_cpp_dom_media_eme0.cpp:nsTString<char> mozilla::dom::ToCString<mozilla::dom::MediaKeySystemMediaCapability>(mozilla::dom::Sequence<mozilla::dom::MediaKeySystemMediaCapability> const&) Unexecuted instantiation: Unified_cpp_dom_media_eme0.cpp:nsTString<char> mozilla::dom::ToCString<mozilla::dom::MediaKeySystemConfiguration>(mozilla::dom::Sequence<mozilla::dom::MediaKeySystemConfiguration> const&) |
1184 | | |
1185 | | template<class Type> |
1186 | | static nsCString |
1187 | | ToCString(const Optional<Sequence<Type>>& aOptional) |
1188 | 0 | { |
1189 | 0 | nsCString str; |
1190 | 0 | if (aOptional.WasPassed()) { |
1191 | 0 | str.Append(ToCString(aOptional.Value())); |
1192 | 0 | } else { |
1193 | 0 | str.AppendLiteral("[]"); |
1194 | 0 | } |
1195 | 0 | return str; |
1196 | 0 | } |
1197 | | |
1198 | | static nsCString |
1199 | | ToCString(const MediaKeySystemConfiguration& aConfig) |
1200 | 0 | { |
1201 | 0 | nsCString str; |
1202 | 0 | str.AppendLiteral("{label="); |
1203 | 0 | str.Append(ToCString(aConfig.mLabel)); |
1204 | 0 |
|
1205 | 0 | str.AppendLiteral(", initDataTypes="); |
1206 | 0 | str.Append(ToCString(aConfig.mInitDataTypes)); |
1207 | 0 |
|
1208 | 0 | str.AppendLiteral(", audioCapabilities="); |
1209 | 0 | str.Append(ToCString(aConfig.mAudioCapabilities)); |
1210 | 0 |
|
1211 | 0 | str.AppendLiteral(", videoCapabilities="); |
1212 | 0 | str.Append(ToCString(aConfig.mVideoCapabilities)); |
1213 | 0 |
|
1214 | 0 | str.AppendLiteral(", distinctiveIdentifier="); |
1215 | 0 | str.Append(ToCString(aConfig.mDistinctiveIdentifier)); |
1216 | 0 |
|
1217 | 0 | str.AppendLiteral(", persistentState="); |
1218 | 0 | str.Append(ToCString(aConfig.mPersistentState)); |
1219 | 0 |
|
1220 | 0 | str.AppendLiteral(", sessionTypes="); |
1221 | 0 | str.Append(ToCString(aConfig.mSessionTypes)); |
1222 | 0 |
|
1223 | 0 | str.AppendLiteral("}"); |
1224 | 0 |
|
1225 | 0 | return str; |
1226 | 0 | } |
1227 | | |
1228 | | /* static */ |
1229 | | nsCString |
1230 | | MediaKeySystemAccess::ToCString( |
1231 | | const Sequence<MediaKeySystemConfiguration>& aConfig) |
1232 | 0 | { |
1233 | 0 | return mozilla::dom::ToCString(aConfig); |
1234 | 0 | } |
1235 | | |
1236 | | } // namespace dom |
1237 | | } // namespace mozilla |