/* * Copyright (c) 2024, Jamie Mansfield * Copyright (c) 2025, Psychpsyo * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace Web::MediaCapabilitiesAPI { // https://w3c.github.io/media-capabilities/#valid-mediaconfiguration bool is_valid_media_configuration(Bindings::MediaConfiguration const& configuration) { // For a MediaConfiguration to be a valid MediaConfiguration, all of the following conditions MUST be true: // 1. audio and/or video MUST exist. if (!configuration.audio.has_value() && !configuration.video.has_value()) return false; // 2. audio MUST be a valid audio configuration if it exists. if (configuration.audio.has_value() && !is_valid_audio_configuration(configuration.audio.value())) return false; // 3. video MUST be a valid video configuration if it exists. if (configuration.video.has_value() && !is_valid_video_configuration(configuration.video.value())) return false; return true; } // https://w3c.github.io/media-capabilities/#valid-mediadecodingconfiguration bool is_valid_media_decoding_configuration(Bindings::MediaDecodingConfiguration const& configuration) { // 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(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 is_valid_video_configuration(Bindings::VideoConfiguration const& configuration) { // To check if a VideoConfiguration configuration is a valid video configuration, the following steps MUST be // run: // 1. If configuration’s contentType is not a valid video MIME type, return false and abort these steps. if (!is_valid_video_mime_type(configuration.content_type)) return false; // 2. If framerate is not finite or is not greater than 0, return false and abort these steps. if (!isfinite(configuration.framerate) || configuration.framerate <= 0) return false; // 3. If an optional member is specified for a MediaDecodingType or MediaEncodingType to which it’s 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 is_valid_audio_configuration(Bindings::AudioConfiguration const& configuration) { // To check if a AudioConfiguration configuration is a valid audio configuration, the following steps MUST be // run: // 1. If configuration’s contentType is not a valid audio MIME type, return false and abort these steps. if (!is_valid_audio_mime_type(configuration.content_type)) return false; // 2. Return true. return true; } GC_DEFINE_ALLOCATOR(MediaCapabilities); GC::Ref MediaCapabilities::create(JS::Realm& realm) { return realm.create(realm); } MediaCapabilities::MediaCapabilities(JS::Realm& realm) : PlatformObject(realm) { } void MediaCapabilities::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaCapabilities); Base::initialize(realm); } // https://w3c.github.io/media-capabilities/#queue-a-media-capabilities-task void queue_a_media_capabilities_task(JS::VM& vm, Function 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 MediaCapabilities::decoding_info(Bindings::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 (!is_valid_media_decoding_configuration(configuration)) { return WebIDL::create_rejected_promise_from_exception(realm, vm().throw_completion("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 = to_object(realm, create_a_media_capabilities_decoding_info(configuration)); // 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 Bindings::MediaCapabilitiesDecodingInfo create_a_media_capabilities_decoding_info(Bindings::MediaDecodingConfiguration configuration) { // 1. Let info be a new MediaCapabilitiesDecodingInfo instance. Unless stated otherwise, reading and // writing apply to info for the next steps. Bindings::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. Bindings::MediaDecodingConfiguration info_configuration {}; info_configuration.audio = configuration.audio; info_configuration.video = configuration.video; info_configuration.type = configuration.type; info_configuration.key_system_configuration = configuration.key_system_configuration; info.configuration.emplace(move(info_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(Bindings::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 to_object(JS::Realm& realm, Bindings::MediaCapabilitiesDecodingInfo const& info) { auto object = JS::Object::create(realm, realm.intrinsics().object_prototype()); // FIXME: Also include configuration in this object. MUST(object->create_data_property("supported"_utf16_fly_string, JS::BooleanObject::create(realm, info.supported))); MUST(object->create_data_property("smooth"_utf16_fly_string, JS::BooleanObject::create(realm, info.smooth))); MUST(object->create_data_property("powerEfficient"_utf16_fly_string, JS::BooleanObject::create(realm, info.power_efficient))); return object; } }