LibWeb: Implement MediaCapabilities.decodingInfo()

This commit is contained in:
Psychpsyo 2025-02-12 17:29:29 +01:00 committed by Andrew Kaster
parent 3b577e6135
commit 0206697d36
Notes: github-actions[bot] 2025-02-18 17:19:43 +00:00
10 changed files with 1047 additions and 1 deletions

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2025, Psychpsyo <psychpsyo@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace Web::Bindings {
// https://w3c.github.io/encrypted-media/#dom-mediakeysrequirement
enum class MediaKeysRequirement {
Required,
Optional,
NotAllowed
};
}

View file

@ -0,0 +1,6 @@
// https://w3c.github.io/encrypted-media/#dom-mediakeysrequirement
enum MediaKeysRequirement {
"required",
"optional",
"not-allowed"
};

View file

@ -64,9 +64,14 @@ enum class CanPlayTypeResult;
enum class CanvasFillRule;
enum class CanvasTextAlign;
enum class CanvasTextBaseline;
enum class ColorGamut;
enum class DOMParserSupportedType;
enum class EndingType;
enum class HdrMetadataType;
enum class ImageSmoothingQuality;
enum class MediaDecodingType;
enum class MediaEncodingType;
enum class MediaKeysRequirement;
enum class ReadableStreamReaderMode;
enum class ReferrerPolicy;
enum class RequestCache;
@ -79,6 +84,7 @@ enum class RequestRedirect;
enum class ResizeObserverBoxOptions;
enum class ResponseType;
enum class TextTrackKind;
enum class TransferFunction;
enum class XMLHttpRequestResponseType;
}
@ -656,6 +662,17 @@ class MathMLElement;
namespace Web::MediaCapabilitiesAPI {
class MediaCapabilities;
struct AudioConfiguration;
struct KeySystemTrackConfiguration;
struct MediaCapabilitiesDecodingInfo;
struct MediaCapabilitiesEncodingInfo;
struct MediaCapabilitiesInfo;
struct MediaCapabilitiesKeySystemConfiguration;
struct MediaConfiguration;
struct MediaDecodingConfiguration;
struct MediaEncodingConfiguration;
struct VideoConfiguration;
}
namespace Web::MediaSourceExtensions {

View file

@ -71,6 +71,9 @@ public:
// https://websockets.spec.whatwg.org/#websocket-task-source
WebSocket,
// https://w3c.github.io/media-capabilities/#media-capabilities-task-source
MediaCapabilities,
// !!! IMPORTANT: Keep this field last!
// This serves as the base value of all unique task sources.
// Some elements, such as the HTMLMediaElement, must have a unique task source per instance.

View file

@ -1,16 +1,114 @@
/*
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (c) 2025, Psychpsyo <psychpsyo@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/BooleanObject.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/MediaCapabilitiesPrototype.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/MediaCapabilitiesAPI/MediaCapabilities.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
namespace Web::MediaCapabilitiesAPI {
// https://w3c.github.io/media-capabilities/#valid-mediaconfiguration
bool MediaConfiguration::is_valid_media_configuration() const
{
// For a MediaConfiguration to be a valid MediaConfiguration, all of the following conditions MUST be true:
// 1. audio and/or video MUST exist.
if (!audio.has_value() && !video.has_value())
return false;
// 2. audio MUST be a valid audio configuration if it exists.
if (audio.has_value() && !audio.value().is_valid_audio_configuration())
return false;
// 3. video MUST be a valid video configuration if it exists.
if (video.has_value() && !video.value().is_valid_video_configuration())
return false;
return true;
}
// https://w3c.github.io/media-capabilities/#valid-mediadecodingconfiguration
bool MediaDecodingConfiguration::is_valid_media_decoding_configuration() const
{
// For a MediaDecodingConfiguration to be a valid MediaDecodingConfiguration, all of the following
// conditions MUST be true:
// 1. It MUST be a valid MediaConfiguration.
if (!is_valid_media_configuration())
return false;
// 2. If keySystemConfiguration exists:
// FIXME: Implement this.
return true;
}
// https://w3c.github.io/media-capabilities/#valid-audio-mime-type
bool is_valid_audio_mime_type(StringView string)
{
// A valid audio MIME type is a string that is a valid media MIME type and for which the type per [RFC9110] is
// either audio or application.
auto mime_type = MimeSniff::MimeType::parse(string);
if (!mime_type.has_value())
return false;
return mime_type->type() == "audio"sv || mime_type->type() == "application"sv;
}
// https://w3c.github.io/media-capabilities/#valid-video-mime-type
bool is_valid_video_mime_type(StringView string)
{
// A valid video MIME type is a string that is a valid media MIME type and for which the type per [RFC9110] is
// either video or application.
auto mime_type = MimeSniff::MimeType::parse(string);
if (!mime_type.has_value())
return false;
return mime_type->type() == "video"sv || mime_type->type() == "application"sv;
}
// https://w3c.github.io/media-capabilities/#valid-video-configuration
bool VideoConfiguration::is_valid_video_configuration() const
{
// To check if a VideoConfiguration configuration is a valid video configuration, the following steps MUST be
// run:
// 1. If configurations contentType is not a valid video MIME type, return false and abort these steps.
if (!is_valid_video_mime_type(content_type))
return false;
// 2. If framerate is not finite or is not greater than 0, return false and abort these steps.
if (!isfinite(framerate) || framerate <= 0)
return false;
// 3. If an optional member is specified for a MediaDecodingType or MediaEncodingType to which its not
// applicable, return false and abort these steps. See applicability rules in the member definitions below.
// FIXME: Implement this.
// 4. Return true.
return true;
}
// https://w3c.github.io/media-capabilities/#valid-video-configuration
bool AudioConfiguration::is_valid_audio_configuration() const
{
// To check if a AudioConfiguration configuration is a valid audio configuration, the following steps MUST be
// run:
// 1. If configurations contentType is not a valid audio MIME type, return false and abort these steps.
if (!is_valid_audio_mime_type(content_type))
return false;
// 2. Return true.
return true;
}
GC_DEFINE_ALLOCATOR(MediaCapabilities);
GC::Ref<MediaCapabilities> MediaCapabilities::create(JS::Realm& realm)
@ -29,4 +127,120 @@ void MediaCapabilities::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaCapabilities);
}
// https://w3c.github.io/media-capabilities/#queue-a-media-capabilities-task
void queue_a_media_capabilities_task(JS::VM& vm, Function<void()> steps)
{
// When an algorithm queues a Media Capabilities task T, the user agent MUST queue a global task T on the
// media capabilities task source using the global object of the the current realm record.
queue_global_task(HTML::Task::Source::MediaCapabilities, vm.current_realm()->global_object(), GC::create_function(vm.current_realm()->heap(), move(steps)));
}
// https://w3c.github.io/media-capabilities/#dom-mediacapabilities-decodinginfo
GC::Ref<WebIDL::Promise> MediaCapabilities::decoding_info(MediaDecodingConfiguration const& configuration)
{
auto& realm = this->realm();
// The decodingInfo() method MUST run the following steps:
// 1. If configuration is not a valid MediaDecodingConfiguration, return a Promise rejected with a newly created
// TypeError.
if (!configuration.is_valid_media_decoding_configuration()) {
return WebIDL::create_rejected_promise_from_exception(realm, vm().throw_completion<JS::TypeError>("The given configuration is not a valid MediaDecodingConfiguration"sv));
}
// 2. If configuration.keySystemConfiguration exists, run the following substeps:
// FIXME: Implement this.
// 3. Let p be a new Promise.
auto p = WebIDL::create_promise(realm);
// 4. Run the following steps in parallel:
auto& vm = this->vm();
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&vm, &realm, p, configuration]() mutable {
HTML::TemporaryExecutionContext context(realm);
// 1. Run the Create a MediaCapabilitiesDecodingInfo algorithm with configuration.
auto result = create_a_media_capabilities_decoding_info(configuration).to_object(realm);
// Queue a Media Capabilities task to resolve p with its result.
queue_a_media_capabilities_task(vm, [&realm, p, result] {
HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
WebIDL::resolve_promise(realm, p, JS::Value(result));
});
}));
// 5. Return p.
return p;
}
// https://w3c.github.io/media-capabilities/#create-a-mediacapabilitiesdecodinginfo
MediaCapabilitiesDecodingInfo create_a_media_capabilities_decoding_info(MediaDecodingConfiguration configuration)
{
// 1. Let info be a new MediaCapabilitiesDecodingInfo instance. Unless stated otherwise, reading and
// writing apply to info for the next steps.
MediaCapabilitiesDecodingInfo info = {};
// 2. Set configuration to be a new MediaDecodingConfiguration. For every property in configuration create
// a new property with the same name and value in configuration.
info.configuration = { { configuration.video, configuration.audio }, configuration.type, configuration.key_system_configuration };
// 3. If configuration.keySystemConfiguration exists:
if (false) {
// FIXME: Implement this.
}
// 4. Otherwise, run the following steps:
else {
// 1. Set keySystemAccess to null.
// FIXME: Implement this.
// 2. If the user agent is able to decode the media represented by configuration, set supported to true.
// 3. Otherwise, set it to false.
info.supported = is_able_to_decode_media(configuration);
}
// 5. If the user agent is able to decode the media represented by configuration at the indicated framerate without
// dropping frames, set smooth to true. Otherwise set it to false.
// FIXME: Actually check this.
info.smooth = false;
// 6. 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.
// FIXME: Actually check this... somehow.
info.power_efficient = false;
// 7. Return info.
return info;
}
bool is_able_to_decode_media(MediaDecodingConfiguration configuration)
{
if (configuration.type != Bindings::MediaDecodingType::MediaSource)
return false;
if (configuration.video.has_value()) {
auto video_mime_type = MimeSniff::MimeType::parse(configuration.video.value().content_type);
if (!video_mime_type.has_value() || !Web::HTML::HTMLMediaElement::supported_video_subtypes.contains_slow(video_mime_type->subtype()))
return false;
}
if (configuration.audio.has_value()) {
auto audio_mime_type = MimeSniff::MimeType::parse(configuration.audio.value().content_type);
if (!audio_mime_type.has_value() || !Web::HTML::HTMLMediaElement::supported_audio_subtypes.contains_slow(audio_mime_type->subtype()))
return false;
}
return true;
}
GC::Ref<JS::Object> MediaCapabilitiesDecodingInfo::to_object(JS::Realm& realm)
{
auto object = JS::Object::create(realm, realm.intrinsics().object_prototype());
// FIXME: Also include configuration in this object.
MUST(object->create_data_property("supported", JS::BooleanObject::create(realm, supported)));
MUST(object->create_data_property("smooth", JS::BooleanObject::create(realm, smooth)));
MUST(object->create_data_property("powerEfficent", JS::BooleanObject::create(realm, power_efficient)));
return object;
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (c) 2025, Psychpsyo <psychpsyo@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,9 +8,100 @@
#pragma once
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/EncryptedMediaExtensions/EncryptedMediaExtensions.h>
namespace Web::MediaCapabilitiesAPI {
// https://w3c.github.io/media-capabilities/#dictdef-videoconfiguration
struct VideoConfiguration {
String content_type;
WebIDL::UnsignedLong width;
WebIDL::UnsignedLong height;
Optional<WebIDL::UnsignedLongLong> bitrate;
double framerate;
Optional<bool> has_alpha_channel;
Optional<Bindings::HdrMetadataType> hdr_metadata_type;
Optional<Bindings::ColorGamut> color_gamut;
Optional<Bindings::TransferFunction> transfer_function;
Optional<String> scalability_mode;
Optional<bool> spatial_scalability;
// https://w3c.github.io/media-capabilities/#valid-video-configuration
bool is_valid_video_configuration() const;
};
// https://w3c.github.io/media-capabilities/#dictdef-audioconfiguration
struct AudioConfiguration {
String content_type;
Optional<String> channels;
Optional<WebIDL::UnsignedLongLong> bitrate;
Optional<WebIDL::UnsignedLong> samplerate;
Optional<bool> spatial_rendering;
// https://w3c.github.io/media-capabilities/#valid-audio-configuration
bool is_valid_audio_configuration() const;
};
// https://w3c.github.io/media-capabilities/#dictdef-mediaconfiguration
struct MediaConfiguration {
Optional<VideoConfiguration> video;
Optional<AudioConfiguration> audio;
// https://w3c.github.io/media-capabilities/#valid-mediaconfiguration
bool is_valid_media_configuration() const;
};
// https://w3c.github.io/media-capabilities/#keysystemtrackconfiguration
struct KeySystemTrackConfiguration {
String robustness;
Optional<String> encryption_scheme;
};
// https://w3c.github.io/media-capabilities/#mediacapabilitieskeysystemconfiguration
struct MediaCapabilitiesKeySystemConfiguration {
String key_system;
String init_data_type;
Bindings::MediaKeysRequirement distinctive_identifier;
Bindings::MediaKeysRequirement persistent_state;
Optional<Vector<String>> session_types;
Optional<KeySystemTrackConfiguration> audio;
Optional<KeySystemTrackConfiguration> video;
};
// https://w3c.github.io/media-capabilities/#dictdef-mediadecodingconfiguration
struct MediaDecodingConfiguration : public MediaConfiguration {
Bindings::MediaDecodingType type;
Optional<MediaCapabilitiesKeySystemConfiguration> key_system_configuration;
// https://w3c.github.io/media-capabilities/#valid-mediadecodingconfiguration
bool is_valid_media_decoding_configuration() const;
};
// https://w3c.github.io/media-capabilities/#dictdef-mediaencodingconfiguration
struct MediaEncodingConfiguration : public MediaConfiguration {
Bindings::MediaEncodingType type;
};
// https://w3c.github.io/media-capabilities/#media-capabilities-info
struct MediaCapabilitiesInfo {
bool supported;
bool smooth;
bool power_efficient;
};
// https://w3c.github.io/media-capabilities/#dictdef-mediacapabilitiesdecodinginfo
struct MediaCapabilitiesDecodingInfo : public MediaCapabilitiesInfo {
MediaDecodingConfiguration configuration;
Optional<MediaCapabilitiesKeySystemConfiguration> key_system_configuration;
GC::Ref<JS::Object> to_object(JS::Realm&);
};
struct MediaCapabilitiesEncodingInfo : public MediaCapabilitiesInfo {
Optional<MediaEncodingConfiguration> configuration;
};
// https://w3c.github.io/media-capabilities/#media-capabilities-interface
class MediaCapabilities final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(MediaCapabilities, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(MediaCapabilities);
@ -18,10 +110,29 @@ public:
static GC::Ref<MediaCapabilities> create(JS::Realm&);
virtual ~MediaCapabilities() override = default;
// https://w3c.github.io/media-capabilities/#dom-mediacapabilities-decodinginfo
GC::Ref<WebIDL::Promise> decoding_info(MediaDecodingConfiguration const&);
private:
MediaCapabilities(JS::Realm&);
virtual void initialize(JS::Realm&) override;
// https://w3c.github.io/media-capabilities/#valid-mediadecodingconfiguration
bool is_valid_media_decoding_configuration() const;
};
// https://w3c.github.io/media-capabilities/#queue-a-media-capabilities-task
void queue_a_media_capabilities_task(JS::VM& vm, Function<void()>);
// https://w3c.github.io/media-capabilities/#create-a-mediacapabilitiesdecodinginfo
MediaCapabilitiesDecodingInfo create_a_media_capabilities_decoding_info(MediaDecodingConfiguration);
bool is_able_to_decode_media(MediaDecodingConfiguration configuration);
// https://w3c.github.io/media-capabilities/#valid-audio-mime-type
bool is_valid_audio_mime_type(StringView);
// https://w3c.github.io/media-capabilities/#valid-video-mime-type
bool is_valid_video_mime_type(StringView);
}

View file

@ -1,6 +1,114 @@
#import <EncryptedMediaExtensions/EncryptedMediaExtensions.idl>
// https://w3c.github.io/media-capabilities/#mediaconfiguration
dictionary MediaConfiguration {
VideoConfiguration video;
AudioConfiguration audio;
};
dictionary MediaDecodingConfiguration : MediaConfiguration {
required MediaDecodingType type;
MediaCapabilitiesKeySystemConfiguration keySystemConfiguration;
};
dictionary MediaEncodingConfiguration : MediaConfiguration {
required MediaEncodingType type;
};
// https://w3c.github.io/media-capabilities/#enumdef-mediadecodingtype
enum MediaDecodingType {
"file",
"media-source",
"webrtc"
};
// https://w3c.github.io/media-capabilities/#enumdef-mediaencodingtype
enum MediaEncodingType {
"record",
"webrtc"
};
// https://w3c.github.io/media-capabilities/#dictdef-videoconfiguration
dictionary VideoConfiguration {
required DOMString contentType;
required unsigned long width;
required unsigned long height;
required unsigned long long bitrate;
required double framerate;
boolean hasAlphaChannel;
HdrMetadataType hdrMetadataType;
ColorGamut colorGamut;
TransferFunction transferFunction;
DOMString scalabilityMode;
boolean spatialScalability;
};
// https://w3c.github.io/media-capabilities/#enumdef-hdrmetadatatype
enum HdrMetadataType {
"smpteSt2086",
"smpteSt2094-10",
"smpteSt2094-40"
};
// https://w3c.github.io/media-capabilities/#enumdef-colorgamut
enum ColorGamut {
"srgb",
"p3",
"rec2020"
};
// https://w3c.github.io/media-capabilities/#enumdef-transferfunction
enum TransferFunction {
"srgb",
"pq",
"hlg"
};
// https://w3c.github.io/media-capabilities/#dictdef-audioconfiguration
dictionary AudioConfiguration {
required DOMString contentType;
DOMString channels;
unsigned long long bitrate;
unsigned long samplerate;
boolean spatialRendering;
};
// https://w3c.github.io/media-capabilities/#mediacapabilitieskeysystemconfiguration
dictionary MediaCapabilitiesKeySystemConfiguration {
required DOMString keySystem;
DOMString initDataType = "";
MediaKeysRequirement distinctiveIdentifier = "optional";
MediaKeysRequirement persistentState = "optional";
sequence<DOMString> sessionTypes;
KeySystemTrackConfiguration audio;
KeySystemTrackConfiguration video;
};
// https://w3c.github.io/media-capabilities/#keysystemtrackconfiguration
dictionary KeySystemTrackConfiguration {
DOMString robustness = "";
DOMString? encryptionScheme = null;
};
// https://w3c.github.io/media-capabilities/#media-capabilities-info
dictionary MediaCapabilitiesInfo {
required boolean supported;
required boolean smooth;
required boolean powerEfficient;
};
dictionary MediaCapabilitiesDecodingInfo : MediaCapabilitiesInfo {
[FIXME, required] MediaKeySystemAccess keySystemAccess;
MediaDecodingConfiguration configuration;
};
dictionary MediaCapabilitiesEncodingInfo : MediaCapabilitiesInfo {
MediaEncodingConfiguration configuration;
};
// https://w3c.github.io/media-capabilities/#mediacapabilities
[Exposed=(Window, Worker)]
interface MediaCapabilities {
[FIXME, NewObject] Promise<MediaCapabilitiesDecodingInfo> decodingInfo(MediaDecodingConfiguration configuration);
[NewObject] Promise<MediaCapabilitiesDecodingInfo> decodingInfo(MediaDecodingConfiguration configuration);
[FIXME, NewObject] Promise<MediaCapabilitiesEncodingInfo> encodingInfo(MediaEncodingConfiguration configuration);
};

View file

@ -0,0 +1,46 @@
Harness status: OK
Found 40 tests
22 Pass
18 Fail
Pass Test that decodingInfo rejects if it doesn't get a configuration
Pass Test that decodingInfo rejects if the MediaConfiguration isn't valid
Pass Test that decodingInfo rejects if the MediaConfiguration does not have a type
Pass Test that decodingInfo rejects if the configuration doesn't have an audio or video field
Pass Test that decodingInfo rejects if the video configuration has a negative framerate
Pass Test that decodingInfo rejects if the video configuration has a framerate set to 0
Pass Test that decodingInfo rejects if the video configuration has a framerate set to Infinity
Pass Test that decodingInfo rejects if the video configuration contentType doesn't parse
Fail Test that decodingInfo rejects if the video configuration contentType is not a valid MIME type string
Pass Test that decodingInfo rejects if the video configuration contentType isn't of type video
Fail Test that decodingInfo rejects if the video configuration contentType is of type audio
Fail Test that decodingInfo rejects if the audio configuration contentType is of type video
Fail Test that decodingInfo rejects if the video configuration contentType has more than one parameter
Fail Test that decodingInfo rejects if the video configuration contentType has one parameter that isn't codecs
Fail Test that decodingInfo rejects if the video configuration contentType does not imply a single media codec but has no codecs parameter
Fail Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates multiple video codecs
Fail Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates both an audio and a video codec
Pass Test that decodingInfo rejects framerate in the form of x/y
Pass Test that decodingInfo rejects framerate in the form of x/0
Pass Test that decodingInfo rejects framerate in the form of 0/y
Pass Test that decodingInfo rejects framerate in the form of -x/y
Pass Test that decodingInfo rejects framerate in the form of x/-y
Pass Test that decodingInfo rejects framerate in the form of x/
Pass Test that decodingInfo rejects framerate with trailing unallowed characters
Pass Test that decodingInfo rejects if the audio configuration contentType doesn't parse
Fail Test that decodingInfo rejects if the audio configuration contentType is not a valid MIME type string
Pass Test that decodingInfo rejects if the audio configuration contentType isn't of type audio
Fail Test that decodingInfo rejects if the audio configuration contentType has more than one parameter
Fail Test that decodingInfo rejects if the audio configuration contentType has one parameter that isn't codecs
Fail Test that decodingInfo rejects if the audio configuration contentType does not imply a single media codec but has no codecs parameter
Fail Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates multiple audio codecs
Fail Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates both an audio and a video codec
Fail Test that decodingInfo returns a valid MediaCapabilitiesInfo objects
Pass Test that decodingInfo rejects if the MediaConfiguration does not have a valid type
Fail Test that decodingInfo with spatialRendering set returns a valid MediaCapabilitiesInfo objects
Fail Test that decodingInfo with hdrMetadataType, colorGamut, and transferFunction set returns a valid MediaCapabilitiesInfo objects
Fail Test that decodingInfo with mismatched codec color space is unsupported
Pass Test that decodingInfo rejects if the video configuration has an empty hdrMetadataType
Pass Test that decodingInfo rejects if the video configuration has a colorGamut set to true
Pass Test that decodingInfo rejects if the video configuration has a transferFunction set to 3

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<meta name="timeout" content="long">
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../media-capabilities/decodingInfo.any.js"></script>

View file

@ -0,0 +1,508 @@
// META: timeout=long
'use strict';
// Minimal VideoConfiguration that will be allowed per spec. All optional
// properties are missing.
const minimalVideoConfiguration = {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
};
// Minimal AudioConfiguration that will be allowed per spec. All optional
// properties are missing.
const minimalAudioConfiguration = {
contentType: 'audio/webm; codecs="opus"',
};
// AudioConfiguration with optional spatialRendering param.
const audioConfigurationWithSpatialRendering = {
contentType: 'audio/webm; codecs="opus"',
spatialRendering: true,
};
// VideoConfiguration with optional hdrMetadataType, colorGamut, and
// transferFunction properties.
const videoConfigurationWithDynamicRange = {
contentType: 'video/webm; codecs="vp09.00.10.08.00.09.16.09.00"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
hdrMetadataType: 'smpteSt2086',
colorGamut: 'rec2020',
transferFunction: 'pq',
};
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo());
}, "Test that decodingInfo rejects if it doesn't get a configuration");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({}));
}, "Test that decodingInfo rejects if the MediaConfiguration isn't valid");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
video: minimalVideoConfiguration,
audio: minimalAudioConfiguration,
}));
}, "Test that decodingInfo rejects if the MediaConfiguration does not have a type");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
}));
}, "Test that decodingInfo rejects if the configuration doesn't have an audio or video field");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: -1,
},
}));
}, "Test that decodingInfo rejects if the video configuration has a negative framerate");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 0,
},
}));
}, "Test that decodingInfo rejects if the video configuration has a framerate set to 0");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: Infinity,
},
}));
}, "Test that decodingInfo rejects if the video configuration has a framerate set to Infinity");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'fgeoa',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
},
}));
}, "Test that decodingInfo rejects if the video configuration contentType doesn't parse");
// See https://mimesniff.spec.whatwg.org/#example-valid-mime-type-string
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm;',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
},
}));
}, "Test that decodingInfo rejects if the video configuration contentType is not a valid MIME type string");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'audio/fgeoa',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
},
}));
}, "Test that decodingInfo rejects if the video configuration contentType isn't of type video");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'application/ogg; codec=vorbis',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
},
}));
}, "Test that decodingInfo rejects if the video configuration contentType is of type audio");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: {
contentType: 'application/ogg; codec=theora',
channels: 2,
},
}));
}, "Test that decodingInfo rejects if the audio configuration contentType is of type video");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"; foo="bar"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
},
}));
}, "Test that decodingInfo rejects if the video configuration contentType has more than one parameter");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; foo="bar"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
},
}));
}, "Test that decodingInfo rejects if the video configuration contentType has one parameter that isn't codecs");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
},
}));
}, "Test that decodingInfo rejects if the video configuration contentType does not imply a single media codec but has no codecs parameter");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08, vp8"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
}
}));
}, "Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates multiple video codecs");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08, opus"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
}
}));
}, "Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates both an audio and a video codec");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: '24000/1001',
}
}));
}, "Test that decodingInfo rejects framerate in the form of x/y");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: '24000/0',
}
}));
}, "Test that decodingInfo rejects framerate in the form of x/0");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: '0/10001',
}
}));
}, "Test that decodingInfo rejects framerate in the form of 0/y");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: '-24000/10001',
}
}));
}, "Test that decodingInfo rejects framerate in the form of -x/y");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: '24000/-10001',
}
}));
}, "Test that decodingInfo rejects framerate in the form of x/-y");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: '24000/',
}
}));
}, "Test that decodingInfo rejects framerate in the form of x/");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: '1/3x',
}
}));
}, "Test that decodingInfo rejects framerate with trailing unallowed characters");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'fgeoa' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType doesn't parse");
// See https://mimesniff.spec.whatwg.org/#example-valid-mime-type-string
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'audio/mpeg;' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType is not a valid MIME type string");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'video/fgeoa' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType isn't of type audio");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'audio/webm; codecs="opus"; foo="bar"' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType has more than one parameter");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'audio/webm; foo="bar"' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType has one parameter that isn't codecs");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'audio/webm' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType does not imply a single media codec but has no codecs parameter");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'audio/webm; codecs="vorbis, opus"' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates multiple audio codecs");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: { contentType: 'audio/webm; codecs="vp09.00.10.08, opus"' },
}));
}, "Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates both an audio and a video codec");
promise_test(t => {
return navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: minimalVideoConfiguration,
audio: minimalAudioConfiguration,
}).then(ability => {
assert_equals(typeof ability.supported, "boolean");
assert_equals(typeof ability.smooth, "boolean");
assert_equals(typeof ability.powerEfficient, "boolean");
assert_equals(typeof ability.keySystemAccess, "object");
});
}, "Test that decodingInfo returns a valid MediaCapabilitiesInfo objects");
async_test(t => {
var validTypes = [ 'file', 'media-source' ];
var invalidTypes = [ undefined, null, '', 'foobar', 'mse', 'MediaSource',
'record', 'transmission' ];
var validPromises = [];
var invalidCaught = 0;
validTypes.forEach(type => {
validPromises.push(navigator.mediaCapabilities.decodingInfo({
type: type,
video: minimalVideoConfiguration,
audio: minimalAudioConfiguration,
}));
});
// validTypes are tested via Promise.all(validPromises) because if one of the
// promises fail, Promise.all() will reject. This mechanism can't be used for
// invalid types which will be tested individually and increment invalidCaught
// when rejected until the amount of rejection matches the expectation.
Promise.all(validPromises).then(t.step_func(() => {
for (var i = 0; i < invalidTypes.length; ++i) {
navigator.mediaCapabilities.decodingInfo({
type: invalidTypes[i],
video: minimalVideoConfiguration,
audio: minimalAudioConfiguration,
}).then(t.unreached_func(), t.step_func(e => {
assert_equals(e.name, 'TypeError');
++invalidCaught;
if (invalidCaught == invalidTypes.length)
t.done();
}));
}
}), t.unreached_func('Promise.all should not reject for valid types'));
}, "Test that decodingInfo rejects if the MediaConfiguration does not have a valid type");
promise_test(t => {
return navigator.mediaCapabilities.decodingInfo({
type: 'file',
audio: audioConfigurationWithSpatialRendering,
}).then(ability => {
assert_equals(typeof ability.supported, "boolean");
assert_equals(typeof ability.smooth, "boolean");
assert_equals(typeof ability.powerEfficient, "boolean");
assert_equals(typeof ability.keySystemAccess, "object");
});
}, "Test that decodingInfo with spatialRendering set returns a valid MediaCapabilitiesInfo objects");
promise_test(t => {
return navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: videoConfigurationWithDynamicRange,
}).then(ability => {
assert_equals(typeof ability.supported, "boolean");
assert_equals(typeof ability.smooth, "boolean");
assert_equals(typeof ability.powerEfficient, "boolean");
assert_equals(typeof ability.keySystemAccess, "object");
});
}, "Test that decodingInfo with hdrMetadataType, colorGamut, and transferFunction set returns a valid MediaCapabilitiesInfo objects");
promise_test(t => {
// VP9 has a default color space of BT.709 in the codec string. So this will
// mismatch against the provided colorGamut and transferFunction.
let bt709Config = videoConfigurationWithDynamicRange;
bt709Config.contentType = 'video/webm; codecs="vp09.00.10.08"';
return navigator.mediaCapabilities
.decodingInfo({
type: 'file',
video: bt709Config,
})
.then(ability => {
assert_equals(typeof ability.supported, 'boolean');
assert_equals(typeof ability.smooth, 'boolean');
assert_equals(typeof ability.powerEfficient, 'boolean');
assert_equals(typeof ability.keySystemAccess, 'object');
assert_false(ability.supported);
});
}, 'Test that decodingInfo with mismatched codec color space is unsupported');
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
hdrMetadataType: ""
},
}));
}, "Test that decodingInfo rejects if the video configuration has an empty hdrMetadataType");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
colorGamut: true
},
}));
}, "Test that decodingInfo rejects if the video configuration has a colorGamut set to true");
promise_test(t => {
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/webm; codecs="vp09.00.10.08"',
width: 800,
height: 600,
bitrate: 3000,
framerate: 24,
transferFunction: 3
},
}));
}, "Test that decodingInfo rejects if the video configuration has a transferFunction set to 3");