LibWeb: Implement MediaCapabilities.decodingInfo()

This commit is contained in:
Psychpsyo 2025-02-12 17:29:29 +01:00 committed by Andrew Kaster
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

@ -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);
};