blob: dfb9bdac566b7965baacfe0cfee7061438b195b2 [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "MediaCapabilities.h"
#include "ContentType.h"
#include "Document.h"
#include "EventLoop.h"
#include "JSDOMPromiseDeferred.h"
#include "JSMediaCapabilitiesDecodingInfo.h"
#include "JSMediaCapabilitiesEncodingInfo.h"
#include "LibWebRTCProvider.h"
#include "Logging.h"
#include "MediaCapabilitiesDecodingInfo.h"
#include "MediaCapabilitiesEncodingInfo.h"
#include "MediaCapabilitiesLogging.h"
#include "MediaDecodingConfiguration.h"
#include "MediaEncodingConfiguration.h"
#include "MediaEngineConfigurationFactory.h"
#include "Page.h"
#include "Settings.h"
#include <wtf/Logger.h>
#include <wtf/SortedArrayMap.h>
namespace WebCore {
static bool isValidMediaMIMEType(const ContentType& contentType)
{
// A "bucket" MIME types is one whose container type does not uniquely specify a codec.
// See: https://tools.ietf.org/html/rfc6381
static constexpr ComparableASCIILiteral bucketMIMETypeArray[] = {
"application/mp21",
"application/mp4",
"audio/3gpp",
"audio/3gpp2",
"audio/mp4",
"audio/ogg",
"audio/vnd.apple.mpegurl",
"audio/webm",
"video/3gpp",
"video/3gpp2",
"video/mp4",
"video/ogg",
"video/quicktime",
"video/vnd.apple.mpegurl",
"video/webm",
};
static constexpr SortedArraySet bucketMIMETypes { bucketMIMETypeArray };
// 2.1.4. MIME types
// https://wicg.github.io/media-capabilities/#valid-media-mime-type
// A valid media MIME type is a string that is a valid MIME type per [mimesniff]. If the MIME type does
// not imply a codec, the string MUST also have one and only one parameter that is named codecs with a
// value describing a single media codec. Otherwise, it MUST contain no parameters.
if (contentType.isEmpty())
return false;
auto codecs = contentType.codecs();
// FIXME: The spec requires that the "codecs" parameter is the only parameter present.
if (bucketMIMETypes.contains(contentType.containerType()))
return codecs.size() == 1;
return !codecs.size();
}
static bool isValidVideoMIMEType(const ContentType& contentType)
{
// 2.1.4 MIME Types
// https://wicg.github.io/media-capabilities/#valid-video-mime-type
// A valid video MIME type is a string that is a valid media MIME type and for which the type per [RFC7231]
// is either video or application.
if (!isValidMediaMIMEType(contentType))
return false;
auto containerType = contentType.containerType();
if (!startsWithLettersIgnoringASCIICase(containerType, "video/") && !startsWithLettersIgnoringASCIICase(containerType, "application/"))
return false;
return true;
}
static bool isValidAudioMIMEType(const ContentType& contentType)
{
// 2.1.4 MIME Types
// https://wicg.github.io/media-capabilities/#valid-audio-mime-type
// A valid audio MIME type is a string that is a valid media MIME type and for which the type per [RFC7231]
// is either audio or application.
if (!isValidMediaMIMEType(contentType))
return false;
auto containerType = contentType.containerType();
if (!startsWithLettersIgnoringASCIICase(containerType, "audio/") && !startsWithLettersIgnoringASCIICase(containerType, "application/"))
return false;
return true;
}
static bool isValidVideoConfiguration(const VideoConfiguration& configuration)
{
// 2.1.5. VideoConfiguration
// https://wicg.github.io/media-capabilities/#valid-video-configuration
// 1. If configuration’s contentType is not a valid video MIME type, return false and abort these steps.
if (!isValidVideoMIMEType(ContentType(configuration.contentType)))
return false;
// 2. If none of the following is true, return false and abort these steps:
// o. Applying the rules for parsing floating-point number values to configuration’s framerate
// results in a number that is finite and greater than 0.
if (!std::isfinite(configuration.framerate) || configuration.framerate <= 0)
return false;
// 3. Return true.
return true;
}
static bool isValidAudioConfiguration(const AudioConfiguration& configuration)
{
// 2.1.6. AudioConfiguration
// https://wicg.github.io/media-capabilities/#audioconfiguration
// 1. If configuration’s contentType is not a valid audio MIME type, return false and abort these steps.
if (!isValidAudioMIMEType(ContentType(configuration.contentType)))
return false;
// 2. Return true.
return true;
}
static bool isValidMediaConfiguration(const MediaConfiguration& configuration)
{
// 2.1.1. MediaConfiguration
// https://wicg.github.io/media-capabilities/#mediaconfiguration
// For a MediaConfiguration to be a valid MediaConfiguration, audio or video MUST be present.
if (!configuration.video && !configuration.audio)
return false;
if (configuration.video && !isValidVideoConfiguration(configuration.video.value()))
return false;
if (configuration.audio && !isValidAudioConfiguration(configuration.audio.value()))
return false;
return true;
}
static void gatherDecodingInfo(Document& document, MediaDecodingConfiguration&& configuration, MediaEngineConfigurationFactory::DecodingConfigurationCallback&& callback)
{
RELEASE_LOG_INFO(Media, "Gathering decoding MediaCapabilities");
MediaEngineConfigurationFactory::DecodingConfigurationCallback decodingCallback = [callback = WTFMove(callback)](auto&& result) mutable {
RELEASE_LOG_INFO(Media, "Finished gathering decoding MediaCapabilities");
callback(WTFMove(result));
};
if (!document.settings().mediaCapabilitiesExtensionsEnabled() && configuration.video)
configuration.video.value().alphaChannel.reset();
#if ENABLE(VP9)
configuration.canExposeVP9 = document.settings().vp9DecoderEnabled();
#endif
#if ENABLE(WEB_RTC)
if (configuration.type == MediaDecodingType::WebRTC) {
if (auto* page = document.page())
page->libWebRTCProvider().createDecodingConfiguration(WTFMove(configuration), WTFMove(decodingCallback));
return;
}
#else
UNUSED_PARAM(document);
#endif
MediaEngineConfigurationFactory::createDecodingConfiguration(WTFMove(configuration), WTFMove(decodingCallback));
}
static void gatherEncodingInfo(Document& document, MediaEncodingConfiguration&& configuration, MediaEngineConfigurationFactory::EncodingConfigurationCallback&& callback)
{
RELEASE_LOG_INFO(Media, "Gathering encoding MediaCapabilities");
MediaEngineConfigurationFactory::EncodingConfigurationCallback encodingCallback = [callback = WTFMove(callback)](auto&& result) mutable {
RELEASE_LOG_INFO(Media, "Finished gathering encoding MediaCapabilities");
callback(WTFMove(result));
};
#if ENABLE(WEB_RTC)
if (configuration.type == MediaEncodingType::WebRTC) {
if (auto* page = document.page())
page->libWebRTCProvider().createEncodingConfiguration(WTFMove(configuration), WTFMove(encodingCallback));
return;
}
#else
UNUSED_PARAM(document);
#endif
MediaEngineConfigurationFactory::createEncodingConfiguration(WTFMove(configuration), WTFMove(encodingCallback));
}
void MediaCapabilities::decodingInfo(ScriptExecutionContext& context, MediaDecodingConfiguration&& configuration, Ref<DeferredPromise>&& promise)
{
// 2.4 Media Capabilities Interface
// https://wicg.github.io/media-capabilities/#media-capabilities-interface
// 1. If configuration is not a valid MediaConfiguration, return a Promise rejected with a TypeError.
// 2. If configuration.video is present and is not a valid video configuration, return a Promise rejected with a TypeError.
// 2.2.3 If configuration is of type MediaDecodingConfiguration, run the following substeps:
// 2.2.3.1. If the user agent is able to decode the media represented by
// configuration, set supported to true. Otherwise set it to false.
// 2.2.3.2. If the user agent is able to decode the media represented by
// configuration at a pace that allows a smooth playback, set smooth to
// true. Otherwise set it to false.
// 2.2.3.3. If the user agent is able to decode the media represented by
// configuration in a power efficient manner, set powerEfficient to
// true. Otherwise set it to false. The user agent SHOULD NOT take into
// consideration the current power source in order to determine the
// decoding power efficiency unless the device’s power source has side
// effects such as enabling different decoding modules.
// 3. If configuration.audio is present and is not a valid audio configuration, return a Promise rejected with a TypeError.
if (!isValidMediaConfiguration(configuration)) {
RELEASE_LOG_INFO(Media, "Invalid decoding media configuration");
promise->reject(TypeError);
return;
}
// 4. Let p be a new promise.
// 5. In parallel, run the create a MediaCapabilitiesInfo algorithm with configuration and resolve p with its result.
// 6. Return p.
MediaEngineConfigurationFactory::DecodingConfigurationCallback callback = [promise = WTFMove(promise), context = Ref { context }](auto info) mutable {
context->eventLoop().queueTask(TaskSource::MediaElement, [promise = WTFMove(promise), info = WTFMove(info)] () mutable {
promise->resolve<IDLDictionary<MediaCapabilitiesDecodingInfo>>(WTFMove(info));
});
};
if (is<Document>(context)) {
gatherDecodingInfo(downcast<Document>(context), WTFMove(configuration), WTFMove(callback));
return;
}
m_decodingTasks.add(++m_nextTaskIdentifier, WTFMove(callback));
context.postTaskToResponsibleDocument([configuration = WTFMove(configuration).isolatedCopy(), contextIdentifier = context.identifier(), weakThis = WeakPtr { this }, taskIdentifier = m_nextTaskIdentifier](auto& document) mutable {
gatherDecodingInfo(document, WTFMove(configuration), [contextIdentifier, weakThis = WTFMove(weakThis), taskIdentifier](auto&& result) mutable {
ScriptExecutionContext::postTaskTo(contextIdentifier, [weakThis = WTFMove(weakThis), taskIdentifier, result = WTFMove(result).isolatedCopy()](auto&) mutable {
if (!weakThis)
return;
if (auto callback = weakThis->m_decodingTasks.take(taskIdentifier))
callback(WTFMove(result));
});
});
});
}
void MediaCapabilities::encodingInfo(ScriptExecutionContext& context, MediaEncodingConfiguration&& configuration, Ref<DeferredPromise>&& promise)
{
// 2.4 Media Capabilities Interface
// https://wicg.github.io/media-capabilities/#media-capabilities-interface
// 1. If configuration is not a valid MediaConfiguration, return a Promise rejected with a TypeError.
// 2. If configuration.video is present and is not a valid video configuration, return a Promise rejected with a TypeError.
// 3. If configuration.audio is present and is not a valid audio configuration, return a Promise rejected with a TypeError.
// 2.2.4. If configuration is of type MediaEncodingConfiguration, run the following substeps:
// 2.2.4.1. If the user agent is able to encode the media
// represented by configuration, set supported to true. Otherwise
// set it to false.
// 2.2.4.2. If the user agent is able to encode the media
// represented by configuration at a pace that allows encoding
// frames at the same pace as they are sent to the encoder, set
// smooth to true. Otherwise set it to false.
// 2.2.4.3. If the user agent is able to encode the media
// represented by configuration in a power efficient manner, set
// powerEfficient to true. Otherwise set it to false. The user agent
// SHOULD NOT take into consideration the current power source in
// order to determine the encoding power efficiency unless the
// device’s power source has side effects such as enabling different
// encoding modules.
if (!isValidMediaConfiguration(configuration)) {
RELEASE_LOG_INFO(Media, "Invalid encoding media configuration");
promise->reject(TypeError);
return;
}
// 4. Let p be a new promise.
// 5. In parallel, run the create a MediaCapabilitiesInfo algorithm with configuration and resolve p with its result.
// 6. Return p.
MediaEngineConfigurationFactory::EncodingConfigurationCallback callback = [promise = WTFMove(promise), context = Ref { context }](auto info) mutable {
context->eventLoop().queueTask(TaskSource::MediaElement, [promise = WTFMove(promise), info = WTFMove(info)] () mutable {
promise->resolve<IDLDictionary<MediaCapabilitiesEncodingInfo>>(WTFMove(info));
});
};
if (is<Document>(context)) {
gatherEncodingInfo(downcast<Document>(context), WTFMove(configuration), WTFMove(callback));
return;
}
m_encodingTasks.add(++m_nextTaskIdentifier, WTFMove(callback));
context.postTaskToResponsibleDocument([configuration = WTFMove(configuration).isolatedCopy(), contextIdentifier = context.identifier(), weakThis = WeakPtr { this }, taskIdentifier = m_nextTaskIdentifier](auto& document) mutable {
gatherEncodingInfo(document, WTFMove(configuration), [contextIdentifier, weakThis = WTFMove(weakThis), taskIdentifier](auto&& result) mutable {
ScriptExecutionContext::postTaskTo(contextIdentifier, [weakThis = WTFMove(weakThis), taskIdentifier, result = WTFMove(result).isolatedCopy()](auto&) mutable {
if (!weakThis)
return;
if (auto callback = weakThis->m_encodingTasks.take(taskIdentifier))
callback(WTFMove(result));
});
});
});
}
}