зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1846848 - part5 : display MFCDM capabilites in `about:support`. r=fluent-reviewers,gerard-majax,jolin,niklas
This patch implements retrieving the capabilities from MFCDMs. I will add non-MFCDM support in following patches, eg. Widevine L3, and ClearKey. Differential Revision: https://phabricator.services.mozilla.com/D194837
This commit is contained in:
Родитель
f115f2ea3e
Коммит
999157ad9b
|
@ -52,6 +52,8 @@
|
|||
#include "mozilla/ipc/UtilityProcessManager.h"
|
||||
#include "mozilla/ipc/UtilityProcessHost.h"
|
||||
#include "mozilla/net/UrlClassifierFeatureFactory.h"
|
||||
#include "mozilla/RemoteDecoderManagerChild.h"
|
||||
#include "mozilla/KeySystemConfig.h"
|
||||
#include "mozilla/WheelHandlingHelper.h"
|
||||
#include "IOActivityMonitor.h"
|
||||
#include "nsNativeTheme.h"
|
||||
|
@ -74,6 +76,10 @@
|
|||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
# include "mozilla/MFCDMParent.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
/* static */
|
||||
|
@ -1922,4 +1928,20 @@ void ChromeUtils::NotifyDevToolsClosed(GlobalObject& aGlobal) {
|
|||
ChromeUtils::sDevToolsOpenedCount--;
|
||||
}
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
/* static */
|
||||
already_AddRefed<Promise> ChromeUtils::GetWMFContentDecryptionModuleInformation(
|
||||
GlobalObject& aGlobal, ErrorResult& aRv) {
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
MOZ_ASSERT(global);
|
||||
RefPtr<Promise> domPromise = Promise::Create(global, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(domPromise);
|
||||
MFCDMCapabilities::GetAllKeySystemsCapabilities(domPromise);
|
||||
return domPromise.forget();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -308,6 +308,11 @@ class ChromeUtils {
|
|||
GlobalObject& aGlobal, JSRFPTarget aTarget,
|
||||
const Nullable<uint64_t>& aOverriddenFingerprintingSettings);
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
static already_AddRefed<Promise> GetWMFContentDecryptionModuleInformation(
|
||||
GlobalObject& aGlobal, ErrorResult& aRv);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Number of DevTools session debugging the current process
|
||||
static std::atomic<uint32_t> sDevToolsOpenedCount;
|
||||
|
|
|
@ -297,6 +297,15 @@ namespace ChromeUtils {
|
|||
LibcConstants getLibcConstants();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
/**
|
||||
* Returns the information about all Media Foundation based content decryption
|
||||
* modules, which would include key system names and their capabilities.
|
||||
*/
|
||||
[NewObject]
|
||||
Promise<sequence<CDMInformation>> getWMFContentDecryptionModuleInformation();
|
||||
#endif
|
||||
|
||||
/**
|
||||
* IF YOU ADD NEW METHODS HERE, MAKE SURE THEY ARE THREAD-SAFE.
|
||||
*/
|
||||
|
@ -1079,3 +1088,8 @@ dictionary LibcConstants {
|
|||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
dictionary CDMInformation {
|
||||
required DOMString keySystemName;
|
||||
required DOMString capabilities;
|
||||
};
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
#include "mozilla/dom/KeySystemNames.h"
|
||||
#include "mozilla/dom/UnionTypes.h"
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
# include "mozilla/PMFCDM.h"
|
||||
# include "KeySystemConfig.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LogModule* GetEMELog() {
|
||||
|
@ -140,4 +145,45 @@ const char* EncryptionSchemeStr(const CryptoScheme& aScheme) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
void MFCDMCapabilitiesIPDLToKeySystemConfig(
|
||||
const MFCDMCapabilitiesIPDL& aCDMConfig,
|
||||
KeySystemConfig& aKeySystemConfig) {
|
||||
aKeySystemConfig.mKeySystem = aCDMConfig.keySystem();
|
||||
|
||||
for (const auto& type : aCDMConfig.initDataTypes()) {
|
||||
aKeySystemConfig.mInitDataTypes.AppendElement(type);
|
||||
}
|
||||
|
||||
for (const auto& type : aCDMConfig.sessionTypes()) {
|
||||
aKeySystemConfig.mSessionTypes.AppendElement(type);
|
||||
}
|
||||
|
||||
for (const auto& c : aCDMConfig.videoCapabilities()) {
|
||||
if (!c.robustness().IsEmpty() &&
|
||||
!aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) {
|
||||
aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness());
|
||||
}
|
||||
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
|
||||
NS_ConvertUTF16toUTF8(c.contentType()));
|
||||
}
|
||||
for (const auto& c : aCDMConfig.audioCapabilities()) {
|
||||
if (!c.robustness().IsEmpty() &&
|
||||
!aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) {
|
||||
aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness());
|
||||
}
|
||||
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
|
||||
NS_ConvertUTF16toUTF8(c.contentType()));
|
||||
}
|
||||
aKeySystemConfig.mPersistentState = aCDMConfig.persistentState();
|
||||
aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID();
|
||||
for (const auto& scheme : aCDMConfig.encryptionSchemes()) {
|
||||
aKeySystemConfig.mEncryptionSchemes.AppendElement(
|
||||
NS_ConvertUTF8toUTF16(EncryptionSchemeStr(scheme)));
|
||||
}
|
||||
EME_LOG("New Capabilities=%s",
|
||||
NS_ConvertUTF16toUTF8(aKeySystemConfig.GetDebugInfo()).get());
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
namespace mozilla {
|
||||
|
||||
enum class CryptoScheme : uint8_t;
|
||||
#ifdef MOZ_WMF_CDM
|
||||
class MFCDMCapabilitiesIPDL;
|
||||
#endif
|
||||
struct KeySystemConfig;
|
||||
|
||||
namespace dom {
|
||||
class ArrayBufferViewOrArrayBuffer;
|
||||
|
@ -82,6 +86,11 @@ bool IsHardwareDecryptionSupported(
|
|||
|
||||
const char* EncryptionSchemeStr(const CryptoScheme& aScheme);
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
void MFCDMCapabilitiesIPDLToKeySystemConfig(
|
||||
const MFCDMCapabilitiesIPDL& aCDMConfig, KeySystemConfig& aKeySystemConfig);
|
||||
#endif
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // EME_LOG_H_
|
||||
|
|
|
@ -29,47 +29,6 @@ bool WMFCDMImpl::Supports(const nsAString& aKeySystem) {
|
|||
return s;
|
||||
}
|
||||
|
||||
static void MFCDMCapabilitiesIPDLToKeySystemConfig(
|
||||
const MFCDMCapabilitiesIPDL& aCDMConfig,
|
||||
KeySystemConfig& aKeySystemConfig) {
|
||||
aKeySystemConfig.mKeySystem = aCDMConfig.keySystem();
|
||||
|
||||
for (const auto& type : aCDMConfig.initDataTypes()) {
|
||||
aKeySystemConfig.mInitDataTypes.AppendElement(type);
|
||||
}
|
||||
|
||||
for (const auto& type : aCDMConfig.sessionTypes()) {
|
||||
aKeySystemConfig.mSessionTypes.AppendElement(type);
|
||||
}
|
||||
|
||||
for (const auto& c : aCDMConfig.videoCapabilities()) {
|
||||
if (!c.robustness().IsEmpty() &&
|
||||
!aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) {
|
||||
aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness());
|
||||
}
|
||||
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
|
||||
NS_ConvertUTF16toUTF8(c.contentType()));
|
||||
}
|
||||
for (const auto& c : aCDMConfig.audioCapabilities()) {
|
||||
if (!c.robustness().IsEmpty() &&
|
||||
!aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) {
|
||||
aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness());
|
||||
}
|
||||
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
|
||||
NS_ConvertUTF16toUTF8(c.contentType()));
|
||||
}
|
||||
aKeySystemConfig.mPersistentState = aCDMConfig.persistentState();
|
||||
aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID();
|
||||
|
||||
for (const auto& scheme : aCDMConfig.encryptionSchemes()) {
|
||||
aKeySystemConfig.mEncryptionSchemes.AppendElement(
|
||||
NS_ConvertUTF8toUTF16(CryptoSchemeToString(scheme)));
|
||||
}
|
||||
|
||||
EME_LOG("New Capabilities=%s",
|
||||
NS_ConvertUTF16toUTF8(aKeySystemConfig.GetDebugInfo()).get());
|
||||
}
|
||||
|
||||
bool WMFCDMImpl::GetCapabilities(nsTArray<KeySystemConfig>& aOutConfigs) {
|
||||
nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue;
|
||||
NS_CreateBackgroundTaskQueue(__func__, getter_AddRefs(backgroundTaskQueue));
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
#include <propkeydef.h> // For DEFINE_PROPERTYKEY() definition
|
||||
#include <propvarutil.h> // For InitPropVariantFrom*()
|
||||
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/KeySystemNames.h"
|
||||
#include "mozilla/ipc/UtilityAudioDecoderChild.h"
|
||||
#include "mozilla/ipc/UtilityProcessManager.h"
|
||||
#include "mozilla/ipc/UtilityProcessParent.h"
|
||||
#include "mozilla/EMEUtils.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/StaticPrefs_media.h"
|
||||
|
@ -1067,6 +1071,76 @@ already_AddRefed<MFCDMProxy> MFCDMParent::GetMFCDMProxy() {
|
|||
return proxy.forget();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void MFCDMCapabilities::GetAllKeySystemsCapabilities(dom::Promise* aPromise) {
|
||||
const static auto kSandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM;
|
||||
LaunchMFCDMProcessIfNeeded(kSandboxKind)
|
||||
->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
[promise = RefPtr(aPromise)]() {
|
||||
RefPtr<ipc::UtilityAudioDecoderChild> uadc =
|
||||
ipc::UtilityAudioDecoderChild::GetSingleton(kSandboxKind);
|
||||
if (NS_WARN_IF(!uadc)) {
|
||||
promise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
uadc->GetKeySystemCapabilities(promise);
|
||||
},
|
||||
[promise = RefPtr(aPromise)](nsresult aError) {
|
||||
promise->MaybeReject(NS_ERROR_FAILURE);
|
||||
});
|
||||
}
|
||||
|
||||
/* static */
|
||||
RefPtr<GenericNonExclusivePromise>
|
||||
MFCDMCapabilities::LaunchMFCDMProcessIfNeeded(ipc::SandboxingKind aSandbox) {
|
||||
MOZ_ASSERT(aSandbox == ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM);
|
||||
RefPtr<ipc::UtilityProcessManager> utilityProc =
|
||||
ipc::UtilityProcessManager::GetSingleton();
|
||||
if (NS_WARN_IF(!utilityProc)) {
|
||||
NS_WARNING("Failed to get UtilityProcessManager");
|
||||
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
|
||||
__func__);
|
||||
}
|
||||
|
||||
// Check if the MFCDM process exists or not. If not, launch it.
|
||||
if (utilityProc->Process(aSandbox)) {
|
||||
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
|
||||
}
|
||||
|
||||
RefPtr<ipc::UtilityAudioDecoderChild> uadc =
|
||||
ipc::UtilityAudioDecoderChild::GetSingleton(aSandbox);
|
||||
if (NS_WARN_IF(!uadc)) {
|
||||
NS_WARNING("Failed to get UtilityAudioDecoderChild");
|
||||
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
|
||||
__func__);
|
||||
}
|
||||
return utilityProc->StartUtility(uadc, aSandbox)
|
||||
->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
[uadc, utilityProc, aSandbox]() {
|
||||
RefPtr<ipc::UtilityProcessParent> parent =
|
||||
utilityProc->GetProcessParent(aSandbox);
|
||||
if (!parent) {
|
||||
NS_WARNING("UtilityAudioDecoderParent lost in the middle");
|
||||
return GenericNonExclusivePromise::CreateAndReject(
|
||||
NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
if (!uadc->CanSend()) {
|
||||
NS_WARNING("UtilityAudioDecoderChild lost in the middle");
|
||||
return GenericNonExclusivePromise::CreateAndReject(
|
||||
NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
|
||||
},
|
||||
[](nsresult aError) {
|
||||
NS_WARNING("Failed to start the MFCDM process!");
|
||||
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
|
||||
__func__);
|
||||
});
|
||||
}
|
||||
|
||||
#undef MFCDM_REJECT_IF_FAILED
|
||||
#undef MFCDM_REJECT_IF
|
||||
#undef MFCDM_RETURN_IF_FAILED
|
||||
|
|
|
@ -140,6 +140,16 @@ class MFCDMParent final : public PMFCDMParent {
|
|||
MediaEventListener mExpirationListener;
|
||||
};
|
||||
|
||||
// A helper class to display CDM capabilites in `about:support`.
|
||||
class MFCDMCapabilities {
|
||||
public:
|
||||
static void GetAllKeySystemsCapabilities(dom::Promise* aPromise);
|
||||
|
||||
private:
|
||||
static RefPtr<GenericNonExclusivePromise> LaunchMFCDMProcessIfNeeded(
|
||||
ipc::SandboxingKind aSandbox);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // DOM_MEDIA_IPC_MFCDMPARENT_H_
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#endif
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
# include "mozilla/dom/Promise.h"
|
||||
# include "mozilla/EMEUtils.h"
|
||||
# include "mozilla/PMFCDM.h"
|
||||
#endif
|
||||
|
@ -179,11 +180,14 @@ bool UtilityAudioDecoderChild::CreateVideoBridge() {
|
|||
#endif
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
void UtilityAudioDecoderChild::GetKeySystemCapabilities() {
|
||||
void UtilityAudioDecoderChild::GetKeySystemCapabilities(
|
||||
dom::Promise* aPromise) {
|
||||
EME_LOG("Ask capabilities for all supported CDMs");
|
||||
SendGetKeySystemCapabilities()->Then(
|
||||
NS_GetCurrentThread(), __func__,
|
||||
[](CopyableTArray<MFCDMCapabilitiesIPDL>&& result) {
|
||||
[promise = RefPtr<dom::Promise>(aPromise)](
|
||||
CopyableTArray<MFCDMCapabilitiesIPDL>&& result) {
|
||||
FallibleTArray<dom::CDMInformation> cdmInfo;
|
||||
for (const auto& capabilities : result) {
|
||||
EME_LOG("Received capabilities for %s",
|
||||
NS_ConvertUTF16toUTF8(capabilities.keySystem()).get());
|
||||
|
@ -199,7 +203,18 @@ void UtilityAudioDecoderChild::GetKeySystemCapabilities() {
|
|||
EME_LOG(" capabilities: encryptionScheme=%s",
|
||||
EncryptionSchemeStr(e));
|
||||
}
|
||||
auto* info = cdmInfo.AppendElement(fallible);
|
||||
if (!info) {
|
||||
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
info->mKeySystemName = capabilities.keySystem();
|
||||
|
||||
KeySystemConfig config;
|
||||
MFCDMCapabilitiesIPDLToKeySystemConfig(capabilities, config);
|
||||
info->mCapabilities = config.GetDebugInfo();
|
||||
}
|
||||
promise->MaybeResolve(cdmInfo);
|
||||
},
|
||||
[](const mozilla::ipc::ResponseRejectReason& aReason) {
|
||||
EME_LOG("IPC failure for GetKeySystemCapabilities!");
|
||||
|
|
|
@ -108,7 +108,7 @@ class UtilityAudioDecoderChild final : public PUtilityAudioDecoderChild
|
|||
#endif
|
||||
|
||||
#ifdef MOZ_WMF_CDM
|
||||
void GetKeySystemCapabilities();
|
||||
void GetKeySystemCapabilities(dom::Promise* aPromise);
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
|
|
@ -1105,6 +1105,94 @@ var snapshotFormatters = {
|
|||
.catch(e => {});
|
||||
}
|
||||
|
||||
function createCDMInfoRow(cdmInfo) {
|
||||
function findElementInArray(array, name) {
|
||||
const rv = array.find(element => element.includes(name));
|
||||
return rv ? rv.split("=")[1] : "Unknown";
|
||||
}
|
||||
|
||||
function getAudioRobustness(array) {
|
||||
return findElementInArray(array, "audio-robustness");
|
||||
}
|
||||
|
||||
function getVideoRobustness(array) {
|
||||
return findElementInArray(array, "video-robustness");
|
||||
}
|
||||
|
||||
function getSupportedCodecs(array) {
|
||||
const mp4Content = findElementInArray(array, "MP4");
|
||||
const webContent = findElementInArray(array, "WEBM");
|
||||
|
||||
const mp4DecodingAndDecryptingCodecs = mp4Content
|
||||
.match(/decoding-and-decrypting:\[([^\]]*)\]/)[1]
|
||||
.split(",");
|
||||
const webmDecodingAndDecryptingCodecs = webContent
|
||||
.match(/decoding-and-decrypting:\[([^\]]*)\]/)[1]
|
||||
.split(",");
|
||||
|
||||
const mp4DecryptingOnlyCodecs = mp4Content
|
||||
.match(/decrypting-only:\[([^\]]*)\]/)[1]
|
||||
.split(",");
|
||||
const webmDecryptingOnlyCodecs = webContent
|
||||
.match(/decrypting-only:\[([^\]]*)\]/)[1]
|
||||
.split(",");
|
||||
|
||||
// Combine and get unique codecs for decoding-and-decrypting (always)
|
||||
// and decrypting-only (only set when it's not empty)
|
||||
let rv = {};
|
||||
rv.decodingAndDecrypting = [
|
||||
...new Set(
|
||||
[
|
||||
...mp4DecodingAndDecryptingCodecs,
|
||||
...webmDecodingAndDecryptingCodecs,
|
||||
].filter(Boolean)
|
||||
),
|
||||
];
|
||||
let temp = [
|
||||
...new Set(
|
||||
[...mp4DecryptingOnlyCodecs, ...webmDecryptingOnlyCodecs].filter(
|
||||
Boolean
|
||||
)
|
||||
),
|
||||
];
|
||||
if (temp.length) {
|
||||
rv.decryptingOnly = temp;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
function getCapabilities(array) {
|
||||
let capabilities = {};
|
||||
capabilities.persistent = findElementInArray(array, "persistent");
|
||||
capabilities.distinctive = findElementInArray(array, "distinctive");
|
||||
capabilities.sessionType = findElementInArray(array, "sessionType");
|
||||
capabilities.scheme = findElementInArray(array, "scheme");
|
||||
capabilities.codec = getSupportedCodecs(array);
|
||||
return JSON.stringify(capabilities);
|
||||
}
|
||||
|
||||
const rvArray = cdmInfo.capabilities.split(" ");
|
||||
return $.new("tr", [
|
||||
$.new("td", cdmInfo.keySystemName),
|
||||
$.new("td", getVideoRobustness(rvArray)),
|
||||
$.new("td", getAudioRobustness(rvArray)),
|
||||
$.new("td", getCapabilities(rvArray)),
|
||||
]);
|
||||
}
|
||||
|
||||
async function insertContentDecryptionModuleInfo() {
|
||||
let rows = [];
|
||||
// Retrieve information from WMFCDM, only works when MOZ_WMF_CDM is true
|
||||
if (ChromeUtils.getWMFContentDecryptionModuleInformation !== undefined) {
|
||||
const cdmInfo =
|
||||
await ChromeUtils.getWMFContentDecryptionModuleInformation();
|
||||
for (let info of cdmInfo) {
|
||||
rows.push(createCDMInfoRow(info));
|
||||
}
|
||||
}
|
||||
$.append($("media-content-decryption-modules-tbody"), rows);
|
||||
}
|
||||
|
||||
// Basic information
|
||||
insertBasicInfo("audio-backend", data.currentAudioBackend);
|
||||
insertBasicInfo("max-audio-channels", data.currentMaxAudioChannels);
|
||||
|
@ -1258,6 +1346,9 @@ var snapshotFormatters = {
|
|||
if (["win", "macosx", "linux", "android"].includes(AppConstants.platform)) {
|
||||
insertBasicInfo("media-codec-support-info", supportInfo);
|
||||
}
|
||||
|
||||
// CDM info
|
||||
insertContentDecryptionModuleInfo();
|
||||
},
|
||||
|
||||
remoteAgent(data) {
|
||||
|
|
|
@ -565,6 +565,18 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="media-content-decryption-modules-tbody">
|
||||
<tr>
|
||||
<th colspan="4" class="title-column" data-l10n-id="media-content-decryption-modules-title"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<th data-l10n-id="media-key-system-name"/>
|
||||
<th data-l10n-id="media-video-robustness"/>
|
||||
<th data-l10n-id="media-audio-robustness"/>
|
||||
<th data-l10n-id="media-cdm-capabilities"/>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - -->
|
||||
|
|
|
@ -192,6 +192,16 @@ media-codec-support-unsupported = Unsupported
|
|||
media-codec-support-error = Codec support information unavailable. Try again after playing back a media file.
|
||||
media-codec-support-lack-of-extension = Install extension
|
||||
|
||||
## Media Content Decryption Modules (CDM)
|
||||
## See EME Spec for more explanation for following technical terms
|
||||
## https://w3c.github.io/encrypted-media/
|
||||
|
||||
media-content-decryption-modules-title = Content Decryption Modules Information
|
||||
media-key-system-name = Key System Name
|
||||
media-video-robustness = Video Robustness
|
||||
media-audio-robustness = Audio Robustness
|
||||
media-cdm-capabilities = Capabilities
|
||||
|
||||
##
|
||||
|
||||
intl-title = Internationalization & Localization
|
||||
|
|
Загрузка…
Ссылка в новой задаче