LibWeb: Add Media Capture and Stream APIs
This commit is contained in:
parent
d388502a3a
commit
baefb51902
35 changed files with 2484 additions and 0 deletions
58
Libraries/LibMedia/Audio/AudioDevices.cpp
Normal file
58
Libraries/LibMedia/Audio/AudioDevices.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibMedia/Audio/AudioDevices.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
AudioDevices& AudioDevices::the()
|
||||
{
|
||||
static AudioDevices devices;
|
||||
return devices;
|
||||
}
|
||||
|
||||
void AudioDevices::refresh()
|
||||
{
|
||||
}
|
||||
|
||||
Vector<AudioDeviceInfo> AudioDevices::input_devices() const
|
||||
{
|
||||
return m_cached_input_devices;
|
||||
}
|
||||
|
||||
Vector<AudioDeviceInfo> AudioDevices::output_devices() const
|
||||
{
|
||||
return m_cached_output_devices;
|
||||
}
|
||||
|
||||
AudioDevices::ListenerId AudioDevices::add_devices_changed_listener(Function<void()> listener)
|
||||
{
|
||||
ListenerId listener_id = m_next_listener_id++;
|
||||
m_listeners.set(listener_id, move(listener));
|
||||
return listener_id;
|
||||
}
|
||||
|
||||
void AudioDevices::remove_devices_changed_listener(ListenerId listener_id)
|
||||
{
|
||||
m_listeners.remove(listener_id);
|
||||
}
|
||||
|
||||
void AudioDevices::notify_listeners()
|
||||
{
|
||||
Vector<ListenerId> listener_ids;
|
||||
listener_ids.ensure_capacity(m_listeners.size());
|
||||
for (auto const& listener : m_listeners)
|
||||
listener_ids.append(listener.key);
|
||||
|
||||
for (auto listener_id : listener_ids) {
|
||||
auto callback = m_listeners.get(listener_id);
|
||||
if (!callback.has_value())
|
||||
continue;
|
||||
callback.value()();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
48
Libraries/LibMedia/Audio/AudioDevices.h
Normal file
48
Libraries/LibMedia/Audio/AudioDevices.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibMedia/Export.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
struct AudioDeviceInfo {
|
||||
ByteString dom_device_id;
|
||||
ByteString label;
|
||||
ByteString group_id;
|
||||
u32 sample_rate_hz { 0 };
|
||||
u32 channel_count { 0 };
|
||||
bool is_default { false };
|
||||
};
|
||||
|
||||
class MEDIA_API AudioDevices {
|
||||
public:
|
||||
static AudioDevices& the();
|
||||
|
||||
void refresh();
|
||||
Vector<AudioDeviceInfo> input_devices() const;
|
||||
Vector<AudioDeviceInfo> output_devices() const;
|
||||
|
||||
using ListenerId = u64;
|
||||
ListenerId add_devices_changed_listener(Function<void()>);
|
||||
void remove_devices_changed_listener(ListenerId);
|
||||
|
||||
private:
|
||||
void notify_listeners();
|
||||
|
||||
Vector<AudioDeviceInfo> m_cached_input_devices;
|
||||
Vector<AudioDeviceInfo> m_cached_output_devices;
|
||||
|
||||
ListenerId m_next_listener_id { 1 };
|
||||
HashMap<ListenerId, Function<void()>> m_listeners;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ include(audio)
|
|||
include(ffmpeg)
|
||||
|
||||
set(SOURCES
|
||||
Audio/AudioDevices.cpp
|
||||
Containers/Matroska/MatroskaDemuxer.cpp
|
||||
Containers/Matroska/Reader.cpp
|
||||
IncrementallyPopulatedStream.cpp
|
||||
|
|
|
|||
|
|
@ -796,6 +796,11 @@ set(SOURCES
|
|||
MathML/MathMLMspaceElement.cpp
|
||||
MathML/TagNames.cpp
|
||||
MediaCapabilitiesAPI/MediaCapabilities.cpp
|
||||
MediaCapture/MediaDeviceInfo.cpp
|
||||
MediaCapture/MediaDevices.cpp
|
||||
MediaCapture/MediaStream.cpp
|
||||
MediaCapture/MediaStreamTrack.cpp
|
||||
MediaCapture/MediaStreamTrackEvent.cpp
|
||||
MediaSourceExtensions/BufferedChangeEvent.cpp
|
||||
MediaSourceExtensions/EventNames.cpp
|
||||
MediaSourceExtensions/ManagedMediaSource.cpp
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ enum class HdrMetadataType : u8;
|
|||
enum class ImageSmoothingQuality : u8;
|
||||
enum class MediaDecodingType : u8;
|
||||
enum class MediaEncodingType : u8;
|
||||
enum class MediaStreamTrackState : u8;
|
||||
enum class OffscreenRenderingContextId : u8;
|
||||
enum class ReadableStreamReaderMode : u8;
|
||||
enum class ReferrerPolicy : u8;
|
||||
|
|
@ -114,6 +115,13 @@ enum class XMLHttpRequestResponseType : u8;
|
|||
|
||||
}
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
class MediaStream;
|
||||
class MediaStreamTrack;
|
||||
|
||||
}
|
||||
|
||||
namespace Web::Clipboard {
|
||||
|
||||
class Clipboard;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ namespace Web::HTML::EventNames {
|
|||
__ENUMERATE_HTML_EVENT(cuechange) \
|
||||
__ENUMERATE_HTML_EVENT(currententrychange) \
|
||||
__ENUMERATE_HTML_EVENT(cut) \
|
||||
__ENUMERATE_HTML_EVENT(devicechange) \
|
||||
__ENUMERATE_HTML_EVENT(disconnect) \
|
||||
__ENUMERATE_HTML_EVENT(dispose) \
|
||||
__ENUMERATE_HTML_EVENT(DOMContentLoaded) \
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/Internals/XRTest.h>
|
||||
#include <LibWeb/Loader/ResourceLoader.h>
|
||||
#include <LibWeb/MediaCapture/MediaDevices.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/ServiceWorker/ServiceWorkerContainer.h>
|
||||
#include <LibWeb/WebXR/XRSystem.h>
|
||||
|
|
@ -77,6 +78,7 @@ void Navigator::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(m_user_activation);
|
||||
visitor.visit(m_service_worker_container);
|
||||
visitor.visit(m_media_capabilities);
|
||||
visitor.visit(m_media_devices);
|
||||
visitor.visit(m_credentials);
|
||||
visitor.visit(m_battery_promise);
|
||||
visitor.visit(m_xr);
|
||||
|
|
@ -163,6 +165,13 @@ GC::Ref<MediaCapabilitiesAPI::MediaCapabilities> Navigator::media_capabilities()
|
|||
return *m_media_capabilities;
|
||||
}
|
||||
|
||||
GC::Ref<MediaCapture::MediaDevices> Navigator::media_devices()
|
||||
{
|
||||
if (!m_media_devices)
|
||||
m_media_devices = realm().create<MediaCapture::MediaDevices>(realm());
|
||||
return *m_media_devices;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/battery/#the-getbattery-method
|
||||
GC::Ref<WebIDL::Promise> Navigator::get_battery()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <LibWeb/HTML/PluginArray.h>
|
||||
#include <LibWeb/HTML/UserActivation.h>
|
||||
#include <LibWeb/MediaCapabilitiesAPI/MediaCapabilities.h>
|
||||
#include <LibWeb/MediaCapture/MediaDevices.h>
|
||||
#include <LibWeb/Serial/Serial.h>
|
||||
#include <LibWeb/StorageAPI/NavigatorStorage.h>
|
||||
|
||||
|
|
@ -71,6 +72,7 @@ public:
|
|||
GC::Ref<ServiceWorker::ServiceWorkerContainer> service_worker();
|
||||
|
||||
GC::Ref<MediaCapabilitiesAPI::MediaCapabilities> media_capabilities();
|
||||
GC::Ref<MediaCapture::MediaDevices> media_devices();
|
||||
|
||||
static WebIDL::Long max_touch_points();
|
||||
|
||||
|
|
@ -108,6 +110,9 @@ private:
|
|||
// https://w3c.github.io/media-capabilities/#dom-navigator-mediacapabilities
|
||||
GC::Ptr<MediaCapabilitiesAPI::MediaCapabilities> m_media_capabilities;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-navigator-mediadevices
|
||||
GC::Ptr<MediaCapture::MediaDevices> m_media_devices;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#framework-credential-management
|
||||
GC::Ptr<CredentialManagement::CredentialsContainer> m_credentials;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#import <HTML/PluginArray.idl>
|
||||
#import <HTML/UserActivation.idl>
|
||||
#import <MediaCapabilitiesAPI/MediaCapabilities.idl>
|
||||
#import <MediaCapture/MediaDevices.idl>
|
||||
#import <Serial/Serial.idl>
|
||||
#import <ServiceWorker/ServiceWorkerContainer.idl>
|
||||
#import <StorageAPI/NavigatorStorage.idl>
|
||||
|
|
@ -45,6 +46,9 @@ interface Navigator {
|
|||
// https://w3c.github.io/media-capabilities/#dom-navigator-mediacapabilities
|
||||
[SameObject] readonly attribute MediaCapabilities mediaCapabilities;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-navigator-mediadevices
|
||||
[SecureContext, SameObject] readonly attribute MediaDevices mediaDevices;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#framework-credential-management
|
||||
[SecureContext, SameObject] readonly attribute CredentialsContainer credentials;
|
||||
|
||||
|
|
|
|||
41
Libraries/LibWeb/MediaCapture/MediaDeviceInfo.cpp
Normal file
41
Libraries/LibWeb/MediaCapture/MediaDeviceInfo.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/MediaDeviceInfoPrototype.h>
|
||||
#include <LibWeb/MediaCapture/MediaDeviceInfo.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(MediaDeviceInfo);
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#device-info
|
||||
GC::Ref<MediaDeviceInfo> MediaDeviceInfo::create(JS::Realm& realm, String device_id, Bindings::MediaDeviceKind kind, String label, String group_id)
|
||||
{
|
||||
auto device_info = realm.create<MediaDeviceInfo>(realm, move(device_id), kind, move(label), move(group_id));
|
||||
|
||||
// AD-HOC: device, mediaDevices, exposure checks handled by the caller.
|
||||
return device_info;
|
||||
}
|
||||
|
||||
MediaDeviceInfo::MediaDeviceInfo(JS::Realm& realm, String device_id, Bindings::MediaDeviceKind kind, String label, String group_id)
|
||||
: Bindings::PlatformObject(realm)
|
||||
, m_device_id(move(device_id))
|
||||
, m_kind(kind)
|
||||
, m_label(move(label))
|
||||
, m_group_id(move(group_id))
|
||||
{
|
||||
}
|
||||
|
||||
MediaDeviceInfo::~MediaDeviceInfo() = default;
|
||||
|
||||
void MediaDeviceInfo::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaDeviceInfo);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
}
|
||||
46
Libraries/LibWeb/MediaCapture/MediaDeviceInfo.h
Normal file
46
Libraries/LibWeb/MediaCapture/MediaDeviceInfo.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
|
||||
namespace Web::Bindings {
|
||||
|
||||
enum class MediaDeviceKind : u8;
|
||||
|
||||
}
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#device-info
|
||||
class MediaDeviceInfo final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(MediaDeviceInfo, Bindings::PlatformObject);
|
||||
GC_DECLARE_ALLOCATOR(MediaDeviceInfo);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static GC::Ref<MediaDeviceInfo> create(JS::Realm&, String device_id, Bindings::MediaDeviceKind kind, String label, String group_id);
|
||||
virtual ~MediaDeviceInfo() override;
|
||||
|
||||
String device_id() const { return m_device_id; }
|
||||
Bindings::MediaDeviceKind kind() const { return m_kind; }
|
||||
String label() const { return m_label; }
|
||||
String group_id() const { return m_group_id; }
|
||||
|
||||
private:
|
||||
MediaDeviceInfo(JS::Realm&, String device_id, Bindings::MediaDeviceKind kind, String label, String group_id);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
String m_device_id;
|
||||
Bindings::MediaDeviceKind m_kind;
|
||||
String m_label;
|
||||
String m_group_id;
|
||||
};
|
||||
|
||||
}
|
||||
16
Libraries/LibWeb/MediaCapture/MediaDeviceInfo.idl
Normal file
16
Libraries/LibWeb/MediaCapture/MediaDeviceInfo.idl
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// https://w3c.github.io/mediacapture-main/#dom-mediadevicekind
|
||||
enum MediaDeviceKind {
|
||||
"audioinput",
|
||||
"audiooutput",
|
||||
"videoinput"
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediadeviceinfo
|
||||
[Exposed=Window, SecureContext]
|
||||
interface MediaDeviceInfo {
|
||||
readonly attribute DOMString deviceId;
|
||||
readonly attribute MediaDeviceKind kind;
|
||||
readonly attribute DOMString label;
|
||||
readonly attribute DOMString groupId;
|
||||
[Default] object toJSON();
|
||||
};
|
||||
762
Libraries/LibWeb/MediaCapture/MediaDevices.cpp
Normal file
762
Libraries/LibWeb/MediaCapture/MediaDevices.cpp
Normal file
|
|
@ -0,0 +1,762 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibMedia/Audio/AudioDevices.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/MediaDeviceInfoPrototype.h>
|
||||
#include <LibWeb/Bindings/MediaDevicesPrototype.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/IDLEventListener.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/MediaCapture/MediaDeviceInfo.h>
|
||||
#include <LibWeb/MediaCapture/MediaDevices.h>
|
||||
#include <LibWeb/MediaCapture/MediaStream.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamConstraints.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamTrack.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
#include <LibWeb/WebIDL/CallbackType.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
#include <LibWeb/WebIDL/Promise.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
static String const AUDIO_INPUT_KIND = "audioinput"_string;
|
||||
static String const AUDIO_OUTPUT_KIND = "audiooutput"_string;
|
||||
static String const VIDEO_INPUT_KIND = "videoinput"_string;
|
||||
|
||||
static Optional<Vector<String>> extract_device_id_constraint(Optional<ConstrainDOMString> const& device_id_value);
|
||||
static void resolve_with_device_info_list(JS::Realm& realm, WebIDL::Promise& promise, GC::RootVector<GC::Ref<MediaDeviceInfo>> const& result_list);
|
||||
|
||||
GC_DEFINE_ALLOCATOR(MediaDevices);
|
||||
|
||||
GC::Ref<MediaDevices> MediaDevices::create(JS::Realm& realm)
|
||||
{
|
||||
return realm.create<MediaDevices>(realm);
|
||||
}
|
||||
|
||||
MediaDevices::MediaDevices(JS::Realm& realm)
|
||||
: DOM::EventTarget(realm)
|
||||
{
|
||||
auto pending_request_state_change_callback_function = JS::NativeFunction::create(realm, [media_devices = GC::Ref(*this)](JS::VM&) {
|
||||
media_devices->process_pending_enumerate_devices_requests();
|
||||
media_devices->process_pending_get_user_media_requests();
|
||||
return JS::js_undefined(); }, 0, Utf16FlyString {}, &realm);
|
||||
|
||||
auto pending_request_state_change_callback = realm.heap().allocate<WebIDL::CallbackType>(*pending_request_state_change_callback_function, realm);
|
||||
m_pending_request_state_change_listener = DOM::IDLEventListener::create(realm, pending_request_state_change_callback);
|
||||
|
||||
auto& window = as<HTML::Window>(realm.global_object());
|
||||
window.associated_document().add_event_listener_without_options(HTML::EventNames::visibilitychange, *m_pending_request_state_change_listener);
|
||||
window.add_event_listener_without_options(HTML::EventNames::focus, *m_pending_request_state_change_listener);
|
||||
window.add_event_listener_without_options(HTML::EventNames::blur, *m_pending_request_state_change_listener);
|
||||
|
||||
m_audio_device_cache_listener_id = Media::AudioDevices::the().add_devices_changed_listener([this] {
|
||||
did_observe_audio_device_cache_update();
|
||||
});
|
||||
|
||||
did_observe_audio_device_cache_update();
|
||||
}
|
||||
|
||||
void MediaDevices::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaDevices);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void MediaDevices::finalize()
|
||||
{
|
||||
if (m_pending_request_state_change_listener) {
|
||||
auto& window = as<HTML::Window>(realm().global_object());
|
||||
window.associated_document().remove_event_listener_without_options(HTML::EventNames::visibilitychange, *m_pending_request_state_change_listener);
|
||||
window.remove_event_listener_without_options(HTML::EventNames::focus, *m_pending_request_state_change_listener);
|
||||
window.remove_event_listener_without_options(HTML::EventNames::blur, *m_pending_request_state_change_listener);
|
||||
}
|
||||
|
||||
Base::finalize();
|
||||
if (m_audio_device_cache_listener_id.has_value())
|
||||
Media::AudioDevices::the().remove_devices_changed_listener(m_audio_device_cache_listener_id.release_value());
|
||||
}
|
||||
|
||||
void MediaDevices::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_pending_enumerate_devices_promises);
|
||||
for (auto const& request : m_pending_get_user_media_requests)
|
||||
visitor.visit(request.promise);
|
||||
visitor.visit(m_pending_request_state_change_listener);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#device-information-exposure
|
||||
bool MediaDevices::device_information_can_be_exposed()
|
||||
{
|
||||
// 1. If camera information can be exposed on mediaDevices, return true.
|
||||
if (camera_information_can_be_exposed())
|
||||
return true;
|
||||
// 2. If microphone information can be exposed on mediaDevices, return true.
|
||||
if (microphone_information_can_be_exposed())
|
||||
return true;
|
||||
// 3. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MediaDevices::microphone_information_can_be_exposed()
|
||||
{
|
||||
// 1. If any of the local devices of kind "audioinput" are attached to a live MediaStreamTrack in
|
||||
// mediaDevices's relevant global object's associated Document, return true.
|
||||
if (has_live_device_of_kind(AUDIO_INPUT_KIND))
|
||||
return true;
|
||||
// 2. Return mediaDevices.[[canExposeMicrophoneInfo]].
|
||||
return m_can_expose_microphone_info;
|
||||
}
|
||||
|
||||
bool MediaDevices::camera_information_can_be_exposed() const
|
||||
{
|
||||
return has_live_device_of_kind(VIDEO_INPUT_KIND) || m_can_expose_camera_info;
|
||||
}
|
||||
|
||||
bool MediaDevices::can_use_microphone_feature() const
|
||||
{
|
||||
auto const& document = as<HTML::Window>(realm().global_object()).associated_document();
|
||||
return document.is_allowed_to_use_feature(DOM::PolicyControlledFeature::Microphone);
|
||||
}
|
||||
|
||||
bool MediaDevices::can_use_camera_feature() const
|
||||
{
|
||||
auto const& document = as<HTML::Window>(realm().global_object()).associated_document();
|
||||
return document.is_allowed_to_use_feature(DOM::PolicyControlledFeature::Camera);
|
||||
}
|
||||
|
||||
bool MediaDevices::device_enumeration_can_proceed()
|
||||
{
|
||||
// 1. The User Agent MAY return true if device information can be exposed on mediaDevices.
|
||||
if (device_information_can_be_exposed())
|
||||
return true;
|
||||
|
||||
// 2. Return the result of is in view with mediaDevices.
|
||||
return is_in_view();
|
||||
}
|
||||
|
||||
bool MediaDevices::get_user_media_can_proceed() const
|
||||
{
|
||||
return is_in_view() && as<HTML::Window>(realm().global_object()).associated_document().has_focus();
|
||||
}
|
||||
|
||||
bool MediaDevices::is_in_view() const
|
||||
{
|
||||
auto const& document = as<HTML::Window>(realm().global_object()).associated_document();
|
||||
return document.is_fully_active() && document.visibility_state_value() == HTML::VisibilityState::Visible;
|
||||
}
|
||||
|
||||
bool MediaDevices::has_live_device_of_kind(StringView kind) const
|
||||
{
|
||||
for (auto const& device : m_stored_device_list) {
|
||||
if (device.kind != kind)
|
||||
continue;
|
||||
|
||||
auto live = m_devices_live_map.get(device.dom_device_id);
|
||||
if (live.value_or(false))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#set-device-information-exposure-0
|
||||
void MediaDevices::set_device_information_exposure(bool audio_requested, bool video_requested, bool value)
|
||||
{
|
||||
// 1. If "video" is in requestedTypes, run the following sub-steps.
|
||||
if (video_requested) {
|
||||
// 1.1 Set mediaDevices.[[canExposeCameraInfo]] to value.
|
||||
m_can_expose_camera_info = value;
|
||||
|
||||
// FIXME: 1.2 If value is true and if device exposure can be extended with "microphone", set mediaDevices.[[canExposeMicrophoneInfo]] to true.
|
||||
}
|
||||
|
||||
// 2. If "audio" is in requestedTypes, run the following sub-steps.
|
||||
if (audio_requested) {
|
||||
// 2.1 Set mediaDevices.[[canExposeMicrophoneInfo]] to value.
|
||||
m_can_expose_microphone_info = value;
|
||||
|
||||
// FIXME: 2.2 If value is true and if device exposure can be extended with "camera", set mediaDevices.[[canExposeCameraInfo]] to true.
|
||||
}
|
||||
process_pending_enumerate_devices_requests();
|
||||
process_pending_get_user_media_requests();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia
|
||||
void MediaDevices::queue_get_user_media_task(GC::Ref<WebIDL::Promise> promise, Optional<Vector<String>> requested_device_ids)
|
||||
{
|
||||
JS::Realm& realm = this->realm();
|
||||
GC::Ref<MediaDevices> media_devices = *this;
|
||||
|
||||
// FIXME: Add camera/video
|
||||
Vector<Media::AudioDeviceInfo> audio_input_devices = Media::AudioDevices::the().input_devices();
|
||||
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [promise = GC::Root(promise), media_devices, requested_device_ids = move(requested_device_ids), audio_input_devices = move(audio_input_devices)] mutable {
|
||||
JS::Realm& realm = HTML::relevant_realm(*promise->promise());
|
||||
HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
auto reject_not_found = [&](Utf16String const& message) {
|
||||
WebIDL::reject_promise(realm, *promise, WebIDL::NotFoundError::create(realm, message));
|
||||
};
|
||||
|
||||
auto reject_permission_failure = [&] {
|
||||
WebIDL::reject_promise(realm, *promise, WebIDL::NotAllowedError::create(realm, "Permission denied"_utf16));
|
||||
};
|
||||
|
||||
// 11. Run the following steps in parallel.
|
||||
// 11.1 While isInView is false, the User Agent MUST wait to proceed to the next step until a task queued to set isInView to the result of the is in view algorithm, would set isInView to true.
|
||||
// 11.7 While hasSystemFocus is false, the User Agent MUST wait to proceed to the next step until a task queued to set hasSystemFocus to the result of the has system focus algorithm, would set hasSystemFocus to true.
|
||||
// AD-HOC: Requests stay pending until both conditions are true, then continue here.
|
||||
|
||||
// 11.2 Let finalSet be an (initially) empty set.
|
||||
Vector<Media::AudioDeviceInfo> final_set;
|
||||
|
||||
// 11.3 For each media type kind in requestedMediaTypes, run the following steps.
|
||||
// 11.3.1 For each possible configuration of each possible source device of media of type kind, conceive a candidate as a placeholder for an eventual MediaStreamTrack holding a source device and configured with a settings dictionary comprised of its specific settings.
|
||||
// Call this set of candidates the candidateSet.
|
||||
Vector<Media::AudioDeviceInfo> candidate_set = audio_input_devices;
|
||||
|
||||
// 11.3.1 If candidateSet is the empty set, jump to the step labeled NotFound Failure below.
|
||||
if (candidate_set.is_empty()) {
|
||||
// 11.13.2 Reject p with a new DOMException object whose name attribute has the value "NotFoundError".
|
||||
reject_not_found("No audio input devices available"_utf16);
|
||||
return;
|
||||
}
|
||||
|
||||
// 11.3.2 If the value of the kind entry of constraints is true, set CS to the empty constraint set (no constraint). Otherwise, continue with CS set to the value of the kind entry of constraints.
|
||||
// 11.3.3 Remove any constrainable property inside of CS that are not defined for MediaStreamTrack objects of type kind.
|
||||
// 11.3.4 If CS contains a member that is a required constraint and whose name is not in the list of allowed required constraints for device selection, then reject p with a TypeError, and abort these steps.
|
||||
// 11.3.5 Run the SelectSettings algorithm on each candidate in candidateSet with CS as the constraint set.
|
||||
if (requested_device_ids.has_value() && !requested_device_ids->is_empty()) {
|
||||
Vector<Media::AudioDeviceInfo> filtered_candidates;
|
||||
for (auto const& device : candidate_set) {
|
||||
String device_id = String::from_utf8_with_replacement_character(device.dom_device_id.view());
|
||||
for (auto const& requested_id : *requested_device_ids) {
|
||||
if (device_id == requested_id) {
|
||||
filtered_candidates.append(device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
candidate_set = move(filtered_candidates);
|
||||
|
||||
if (candidate_set.is_empty()) {
|
||||
// FIXME: 11.14.3 Reject p with a new OverconstrainedError created by calling OverconstrainedError(constraint, message).
|
||||
reject_not_found("Requested audio input device not found"_utf16);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: 11.3.6 Read the current permission state for all candidate devices in candidateSet that are not attached to a live MediaStreamTrack in the current Document. Remove from candidateSet any candidate whose device's permission state is "denied".
|
||||
// FIXME: 11.3.7 Optionally, e.g., based on a previously-established user preference, for security reasons, or due to platform limitations, jump to the step labeled Permission Failure below.
|
||||
|
||||
// 11.3.8 Add all candidates from candidateSet to finalSet.
|
||||
final_set = move(candidate_set);
|
||||
|
||||
// 11.4 Let stream be a new and empty MediaStream object.
|
||||
GC::Ref<MediaStream> stream = MediaStream::create(realm);
|
||||
|
||||
// 11.5 For each media type kind in requestedMediaTypes, run the following sub steps, preferably at the same time.
|
||||
|
||||
// FIXME: 11.5.1 Request permission to use a PermissionDescriptor with its name member set to the permission name associated with kind.
|
||||
// FIXME: 11.5.2 If the result of the request is "denied", jump to the step labeled Permission Failure below.
|
||||
|
||||
// 11.8 Set the device information exposure on mediaDevices with requestedMediaTypes and true.
|
||||
media_devices->set_device_information_exposure(true, false, true);
|
||||
|
||||
// 11.9 For each media type kind in requestedMediaTypes, run the following sub steps.
|
||||
// 11.9.1 Let finalCandidate be the provided media, which MUST be precisely one candidate of type kind from finalSet.
|
||||
Optional<Media::AudioDeviceInfo> final_candidate;
|
||||
for (auto const& device : final_set) {
|
||||
if (!final_candidate.has_value())
|
||||
final_candidate = device;
|
||||
if (!device.is_default)
|
||||
continue;
|
||||
final_candidate = device;
|
||||
break;
|
||||
}
|
||||
if (!final_candidate.has_value()) {
|
||||
WebIDL::reject_promise(realm, *promise, WebIDL::NotReadableError::create(realm, "No readable audio input devices available"_utf16));
|
||||
return;
|
||||
}
|
||||
|
||||
// 11.9.2 The result of the request is "granted".
|
||||
// 11.9.3 Let grantedDevice be finalCandidate's source device.
|
||||
Media::AudioDeviceInfo const& granted_device = final_candidate.value();
|
||||
|
||||
// 11.9.4 Using grantedDevice's deviceId, deviceId, set mediaDevices.[[devicesLiveMap]][deviceId] to true, if it isn't already true, and set mediaDevices.[[devicesAccessibleMap]][deviceId] to true, if it isn't already true.
|
||||
String granted_device_id = String::from_utf8_with_replacement_character(granted_device.dom_device_id.view());
|
||||
media_devices->m_devices_live_map.set(granted_device_id, true);
|
||||
media_devices->m_devices_accessible_map.set(granted_device_id, true);
|
||||
|
||||
// 11.9.5 Let track be the result of creating a MediaStreamTrack with grantedDevice and mediaDevices. The source of the MediaStreamTrack MUST NOT change.
|
||||
GC::Ref<MediaStreamTrack> track = MediaStreamTrack::create(realm,
|
||||
Bindings::MediaStreamTrackKind::Audio,
|
||||
String::from_utf8_with_replacement_character(granted_device.label.view()));
|
||||
|
||||
MediaTrackSettings settings = track->get_settings();
|
||||
settings.device_id = granted_device_id;
|
||||
settings.sample_rate = granted_device.sample_rate_hz;
|
||||
settings.channel_count = granted_device.channel_count;
|
||||
track->set_settings(move(settings));
|
||||
|
||||
// 11.9.6 Add track to stream's track set.
|
||||
stream->add_track(track);
|
||||
|
||||
// FIXME: 11.10 Run the ApplyConstraints algorithm on all tracks in stream with the appropriate constraints.
|
||||
|
||||
// 11.11 For each track in stream, tie track source to MediaDevices with track.[[Source]] and mediaDevices.
|
||||
media_devices->m_media_stream_track_sources.set(track->provider_id());
|
||||
|
||||
// 11.12 Resolve p with stream and abort these steps.
|
||||
WebIDL::resolve_promise(realm, *promise, stream);
|
||||
|
||||
// 11.15 Permission Failure: Reject p with a new DOMException object whose name attribute has the value "NotAllowedError".
|
||||
(void)reject_permission_failure;
|
||||
}));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices
|
||||
GC::Ref<WebIDL::Promise> MediaDevices::enumerate_devices()
|
||||
{
|
||||
JS::Realm& realm = this->realm();
|
||||
|
||||
// 1. Let p be a new promise.
|
||||
GC::Ref<WebIDL::Promise> promise = WebIDL::create_promise(realm);
|
||||
m_stored_device_list = current_audio_device_snapshot();
|
||||
|
||||
// 2. Let proceed be the result of device enumeration can proceed with this.
|
||||
if (!device_enumeration_can_proceed()) {
|
||||
m_pending_enumerate_devices_promises.append(promise);
|
||||
return promise;
|
||||
}
|
||||
// 3. Let mediaDevices be this.
|
||||
// 4. Run the following steps in parallel.
|
||||
queue_enumerate_devices_task(promise);
|
||||
// 5. Return p.
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices
|
||||
void MediaDevices::queue_enumerate_devices_task(GC::Ref<WebIDL::Promise> promise)
|
||||
{
|
||||
JS::Realm& realm = this->realm();
|
||||
GC::Ref<MediaDevices> media_devices = *this;
|
||||
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [promise = GC::Root(promise), media_devices] {
|
||||
JS::Realm& realm = HTML::relevant_realm(*promise->promise());
|
||||
HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
auto result_list = media_devices->create_list_of_device_info_objects(media_devices->m_stored_device_list);
|
||||
resolve_with_device_info_list(realm, *promise, result_list);
|
||||
}));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> MediaDevices::create_list_of_device_info_objects(Vector<StoredDevice> const& device_list)
|
||||
{
|
||||
JS::Realm& realm = this->realm();
|
||||
|
||||
// To perform creating a list of device info objects, given mediaDevices and deviceList, run the following steps:
|
||||
|
||||
// 1. Let resultList be an empty list.
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> result_list { heap() };
|
||||
|
||||
// 2. Let microphoneList, cameraList and otherDeviceList be empty lists.
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> microphone_list { heap() };
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> camera_list { heap() };
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> other_device_list { heap() };
|
||||
|
||||
// 3. Let document be mediaDevices's relevant global object's associated Document.
|
||||
auto const& document = as<HTML::Window>(realm.global_object()).associated_document();
|
||||
(void)document;
|
||||
|
||||
auto create_device_info_object = [&](StoredDevice const& device) -> Optional<GC::Ref<MediaDeviceInfo>> {
|
||||
Optional<Bindings::MediaDeviceKind> kind;
|
||||
if (device.kind == "audioinput"sv)
|
||||
kind = Bindings::MediaDeviceKind::Audioinput;
|
||||
else if (device.kind == "audiooutput"sv)
|
||||
kind = Bindings::MediaDeviceKind::Audiooutput;
|
||||
else
|
||||
return {};
|
||||
|
||||
String device_id = device.dom_device_id;
|
||||
String label = device.label;
|
||||
String group_id = device.group_id;
|
||||
|
||||
if (device.kind == "audioinput"sv && !microphone_information_can_be_exposed()) {
|
||||
device_id = String {};
|
||||
label = String {};
|
||||
group_id = String {};
|
||||
}
|
||||
// FIXME: Implement videoinput creation when camera enumeration is available.
|
||||
|
||||
return MediaDeviceInfo::create(realm, move(device_id), *kind, move(label), move(group_id));
|
||||
};
|
||||
|
||||
// 4. Run the following sub steps for each discovered device in deviceList, device.
|
||||
for (auto const& device : device_list) {
|
||||
// 4.1 If device is not a microphone, or document is not allowed to use the feature identified by microphone, abort these sub steps and continue with the next device.
|
||||
if (device.kind != AUDIO_INPUT_KIND)
|
||||
continue;
|
||||
if (!can_use_microphone_feature())
|
||||
continue;
|
||||
|
||||
// 4.2 Let deviceInfo be the result of creating a device info object to represent device, with mediaDevices.
|
||||
Optional<GC::Ref<MediaDeviceInfo>> device_info = create_device_info_object(device);
|
||||
if (!device_info.has_value())
|
||||
continue;
|
||||
|
||||
// 4.3 If device is the system default microphone, prepend deviceInfo to microphoneList. Otherwise, append deviceInfo to microphoneList.
|
||||
if (device.is_default)
|
||||
microphone_list.insert(0, *device_info);
|
||||
else
|
||||
microphone_list.append(*device_info);
|
||||
}
|
||||
|
||||
// 5. Run the following sub steps for each discovered device in deviceList, device.
|
||||
for (auto const& device : device_list) {
|
||||
// 5.1 If device is not a camera, or document is not allowed to use the feature identified by camera, abort these sub steps and continue with the next device.
|
||||
if (device.kind != VIDEO_INPUT_KIND)
|
||||
continue;
|
||||
if (!can_use_camera_feature())
|
||||
continue;
|
||||
|
||||
// 5.2 Let deviceInfo be the result of creating a device info object to represent device, with mediaDevices.
|
||||
Optional<GC::Ref<MediaDeviceInfo>> device_info = create_device_info_object(device);
|
||||
if (!device_info.has_value())
|
||||
continue;
|
||||
|
||||
// 5.3 If device is the system default camera, prepend deviceInfo to cameraList. Otherwise, append deviceInfo to cameraList.
|
||||
if (device.is_default)
|
||||
camera_list.insert(0, *device_info);
|
||||
else
|
||||
camera_list.append(*device_info);
|
||||
}
|
||||
|
||||
// 6. If microphone information can be exposed on mediaDevices is false, truncate microphoneList to its first item.
|
||||
if (!microphone_information_can_be_exposed()) {
|
||||
while (microphone_list.size() > 1)
|
||||
microphone_list.take_last();
|
||||
}
|
||||
|
||||
// 7. If camera information can be exposed on mediaDevices is false, truncate cameraList to its first item.
|
||||
if (!camera_information_can_be_exposed()) {
|
||||
while (camera_list.size() > 1)
|
||||
camera_list.take_last();
|
||||
}
|
||||
|
||||
// 8. Run the following sub steps for each discovered device in deviceList, device.
|
||||
for (auto const& device : device_list) {
|
||||
// 8.1 If device is a microphone or device is a camera, abort these sub steps and continue with the next device.
|
||||
if (device.kind == AUDIO_INPUT_KIND || device.kind == VIDEO_INPUT_KIND)
|
||||
continue;
|
||||
|
||||
// 8.2 Run the exposure decision algorithm for devices other than camera and microphone.
|
||||
bool expose_other_device = device_information_can_be_exposed();
|
||||
// FIXME: Implement the exact exposure decision algorithm from the relevant output-device spec.
|
||||
if (!expose_other_device)
|
||||
continue;
|
||||
|
||||
// 8.3 Let deviceInfo be the result of creating a device info object to represent device, with mediaDevices.
|
||||
Optional<GC::Ref<MediaDeviceInfo>> device_info = create_device_info_object(device);
|
||||
if (!device_info.has_value())
|
||||
continue;
|
||||
|
||||
// 8.4 Append deviceInfo to otherDeviceList.
|
||||
// 8.5 If device is the system default audio output, prepend it instead.
|
||||
if (device.kind == "audiooutput"sv && device.is_default) {
|
||||
// AD-HOC: 8.5.x Label & id in the LibMedia audio device cache are only mutated in response
|
||||
// to OS notifications. The device manager updates the DOM id & label in the event of changes,
|
||||
// and these will be broadcast to the LibMedia cache. We don't set that state here.
|
||||
other_device_list.insert(0, *device_info);
|
||||
} else {
|
||||
other_device_list.append(*device_info);
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Append to resultList all devices of microphoneList in order.
|
||||
result_list.extend(microphone_list);
|
||||
|
||||
// 10. Append to resultList all devices of cameraList in order.
|
||||
result_list.extend(camera_list);
|
||||
|
||||
// 11. Append to resultList all devices of otherDeviceList in order.
|
||||
result_list.extend(other_device_list);
|
||||
|
||||
// 12. Return resultList.
|
||||
return result_list;
|
||||
}
|
||||
|
||||
void MediaDevices::process_pending_enumerate_devices_requests()
|
||||
{
|
||||
if (!device_enumeration_can_proceed())
|
||||
return;
|
||||
|
||||
run_device_change_notification_steps(current_audio_device_snapshot());
|
||||
|
||||
auto pending_promises = move(m_pending_enumerate_devices_promises);
|
||||
for (auto& promise : pending_promises)
|
||||
queue_enumerate_devices_task(promise);
|
||||
}
|
||||
|
||||
void MediaDevices::process_pending_get_user_media_requests()
|
||||
{
|
||||
if (!get_user_media_can_proceed())
|
||||
return;
|
||||
|
||||
auto pending_requests = move(m_pending_get_user_media_requests);
|
||||
for (auto& request : pending_requests)
|
||||
queue_get_user_media_task(request.promise, move(request.requested_device_ids));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dfn-device-change-notification-steps
|
||||
void MediaDevices::run_device_change_notification_steps(Vector<StoredDevice> const& device_list)
|
||||
{
|
||||
// When new media input and/or output devices are made available to the User Agent, or any available
|
||||
// input and/or output device becomes unavailable, or the system default for input and/or output
|
||||
// devices of a MediaDeviceKind changed, the User Agent MUST run the following device change
|
||||
// notification steps for each MediaDevices object, mediaDevices, for which device enumeration can
|
||||
// proceed is true, but for no other MediaDevices object:
|
||||
|
||||
if (!device_enumeration_can_proceed())
|
||||
return;
|
||||
|
||||
// 1. Let lastExposedDevices be the result of creating a list of device info objects with mediaDevices and mediaDevices.[[storedDeviceList]].
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> last_exposed_devices = create_list_of_device_info_objects(m_stored_device_list);
|
||||
|
||||
// 2. Let deviceList be the list of all media input and/or output devices available to the User Agent.
|
||||
// device_list is the caller-provided snapshot.
|
||||
|
||||
// 3. Let newExposedDevices be the result of creating a list of device info objects with mediaDevices and deviceList.
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> new_exposed_devices = create_list_of_device_info_objects(device_list);
|
||||
|
||||
// 4. If the MediaDeviceInfo objects in newExposedDevices match those in lastExposedDevices and have
|
||||
// the same order, then abort these steps.
|
||||
if (new_exposed_devices.size() == last_exposed_devices.size()) {
|
||||
bool exposed_devices_match = true;
|
||||
for (size_t i = 0; i < new_exposed_devices.size(); ++i) {
|
||||
if (new_exposed_devices[i]->kind() != last_exposed_devices[i]->kind()
|
||||
|| new_exposed_devices[i]->device_id() != last_exposed_devices[i]->device_id()
|
||||
|| new_exposed_devices[i]->label() != last_exposed_devices[i]->label()
|
||||
|| new_exposed_devices[i]->group_id() != last_exposed_devices[i]->group_id()) {
|
||||
exposed_devices_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exposed_devices_match)
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Set mediaDevices.[[storedDeviceList]] to deviceList.
|
||||
m_stored_device_list = device_list;
|
||||
|
||||
// 6. Queue a task that fires an event named devicechange, using the DeviceChangeEvent constructor
|
||||
// with devices initialized to newExposedDevices, at mediaDevices.
|
||||
// FIXME: Dispatch DeviceChangeEvent with devices once DeviceChangeEvent is implemented in LibWeb.
|
||||
HTML::queue_global_task(HTML::Task::Source::DOMManipulation, HTML::relevant_global_object(*this), GC::create_function(heap(), [media_devices = GC::Ref(*this)] {
|
||||
media_devices->dispatch_event(DOM::Event::create(media_devices->realm(), HTML::EventNames::devicechange));
|
||||
}));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-getsupportedconstraints
|
||||
MediaTrackSupportedConstraints MediaDevices::get_supported_constraints()
|
||||
{
|
||||
// Returns a dictionary whose members are the constrainable properties known to the User Agent.
|
||||
MediaTrackSupportedConstraints supported_constraints;
|
||||
supported_constraints.device_id = true;
|
||||
return supported_constraints;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia
|
||||
GC::Ref<WebIDL::Promise> MediaDevices::get_user_media(Optional<MediaStreamConstraints> const& constraints)
|
||||
{
|
||||
JS::Realm& realm = this->realm();
|
||||
JS::VM& vm = realm.vm();
|
||||
|
||||
bool audio_requested = false;
|
||||
bool video_requested = false;
|
||||
Optional<Vector<String>> requested_device_ids;
|
||||
|
||||
// 1. Let constraints be the method's first argument.
|
||||
if (!constraints.has_value())
|
||||
return WebIDL::create_rejected_promise_from_exception(realm, vm.throw_completion<JS::TypeError>("getUserMedia requires constraints"sv));
|
||||
|
||||
// 2. Let requestedMediaTypes be the set of media types in constraints with either a dictionary value or a value of true.
|
||||
auto const& constraints_value = constraints.value();
|
||||
auto const& audio_value = constraints_value.audio;
|
||||
if (audio_value.has<bool>()) {
|
||||
audio_requested = audio_value.get<bool>();
|
||||
} else {
|
||||
audio_requested = true;
|
||||
auto const& audio_constraints = audio_value.get<MediaTrackConstraints>();
|
||||
requested_device_ids = extract_device_id_constraint(audio_constraints.device_id);
|
||||
}
|
||||
|
||||
auto const& video_value = constraints_value.video;
|
||||
if (video_value.has<bool>())
|
||||
video_requested = video_value.get<bool>();
|
||||
else
|
||||
video_requested = true;
|
||||
|
||||
// 3. If requestedMediaTypes is the empty set, return a promise rejected with a TypeError.
|
||||
if (!audio_requested && !video_requested)
|
||||
return WebIDL::create_rejected_promise_from_exception(realm, vm.throw_completion<JS::TypeError>("No media types requested"sv));
|
||||
|
||||
// 4. Let document be the relevant global object's associated Document.
|
||||
auto const& document = as<HTML::Window>(realm.global_object()).associated_document();
|
||||
|
||||
// 5. If document is NOT fully active, return a promise rejected with an InvalidStateError.
|
||||
if (!document.is_fully_active())
|
||||
return WebIDL::create_rejected_promise(realm, WebIDL::InvalidStateError::create(realm, "Document is not fully active"_utf16));
|
||||
|
||||
// 6. If requestedMediaTypes contains "audio" and document is not allowed to use the feature identified by the "microphone" permission name, jump to Permission Failure.
|
||||
// FIXME: Do microphone permission policy checks once PolicyControlledFeature includes microphone.
|
||||
// 7. If requestedMediaTypes contains "video" and document is not allowed to use the feature identified by the "camera" permission name, jump to Permission Failure.
|
||||
// FIXME: Do camera permission policy checks once PolicyControlledFeature includes camera.
|
||||
// 8. Let mediaDevices be this.
|
||||
// 9. Let isInView be the result of the is in view algorithm.
|
||||
|
||||
// 10. Let p be a new promise.
|
||||
if (video_requested)
|
||||
return WebIDL::create_rejected_promise(realm, WebIDL::NotSupportedError::create(realm, "Video capture is not supported"_utf16));
|
||||
|
||||
GC::Ref<WebIDL::Promise> promise = WebIDL::create_promise(realm);
|
||||
|
||||
// 11. Run the following steps in parallel.
|
||||
// AD-HOC: Keep the request queued until isInView and hasSystemFocus allow the deferred task
|
||||
// to continue without spinning in the event loop.
|
||||
if (!get_user_media_can_proceed()) {
|
||||
m_pending_get_user_media_requests.append({ .promise = promise, .requested_device_ids = move(requested_device_ids) });
|
||||
return promise;
|
||||
}
|
||||
|
||||
queue_get_user_media_task(promise, move(requested_device_ids));
|
||||
|
||||
// Return p.
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-ondevicechange
|
||||
WebIDL::CallbackType* MediaDevices::ondevicechange()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::devicechange);
|
||||
}
|
||||
|
||||
void MediaDevices::set_ondevicechange(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
set_event_handler_attribute(HTML::EventNames::devicechange, event_handler);
|
||||
}
|
||||
|
||||
Vector<MediaDevices::StoredDevice> MediaDevices::current_audio_device_snapshot()
|
||||
{
|
||||
Vector<StoredDevice> stored_devices;
|
||||
auto input_devices = Media::AudioDevices::the().input_devices();
|
||||
auto output_devices = Media::AudioDevices::the().output_devices();
|
||||
stored_devices.ensure_capacity(input_devices.size() + output_devices.size());
|
||||
|
||||
for (auto const& device : input_devices) {
|
||||
stored_devices.append(StoredDevice {
|
||||
.dom_device_id = String::from_utf8_with_replacement_character(device.dom_device_id.view()),
|
||||
.kind = AUDIO_INPUT_KIND,
|
||||
.label = String::from_utf8_with_replacement_character(device.label.view()),
|
||||
.group_id = String::from_utf8_with_replacement_character(device.group_id.view()),
|
||||
.is_default = device.is_default,
|
||||
});
|
||||
}
|
||||
|
||||
for (auto const& device : output_devices) {
|
||||
stored_devices.append(StoredDevice {
|
||||
.dom_device_id = String::from_utf8_with_replacement_character(device.dom_device_id.view()),
|
||||
.kind = AUDIO_OUTPUT_KIND,
|
||||
.label = String::from_utf8_with_replacement_character(device.label.view()),
|
||||
.group_id = String::from_utf8_with_replacement_character(device.group_id.view()),
|
||||
.is_default = device.is_default,
|
||||
});
|
||||
}
|
||||
|
||||
return stored_devices;
|
||||
}
|
||||
|
||||
void MediaDevices::did_observe_audio_device_cache_update()
|
||||
{
|
||||
process_pending_enumerate_devices_requests();
|
||||
process_pending_get_user_media_requests();
|
||||
}
|
||||
|
||||
static Optional<Vector<String>> dom_string_values_from_variant(Variant<String, Vector<String>> const& value)
|
||||
{
|
||||
Vector<String> values;
|
||||
if (value.has<String>()) {
|
||||
auto const& string_value = value.get<String>();
|
||||
if (!string_value.is_empty())
|
||||
values.append(string_value);
|
||||
} else {
|
||||
for (auto const& entry : value.get<Vector<String>>()) {
|
||||
if (!entry.is_empty())
|
||||
values.append(entry);
|
||||
}
|
||||
}
|
||||
return values.is_empty() ? Optional<Vector<String>> {} : Optional<Vector<String>> { move(values) };
|
||||
}
|
||||
|
||||
static Optional<Vector<String>> extract_dom_string_constraint_values(ConstrainDOMString const& constraint)
|
||||
{
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindomstring
|
||||
// ConstrainDOMString is (DOMString or sequence<DOMString> or ConstrainDOMStringParameters).
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindomstringparameters
|
||||
// ConstrainDOMStringParameters members:
|
||||
// - exact: (DOMString or sequence<DOMString>) The exact required value for this property.
|
||||
// - ideal: (DOMString or sequence<DOMString>) The ideal (target) value for this property.
|
||||
// https://w3c.github.io/mediacapture-main/#constraint-types
|
||||
// List values MUST be interpreted as disjunctions.
|
||||
if (constraint.has<ConstrainDOMStringParameters>()) {
|
||||
auto const& parameters = constraint.get<ConstrainDOMStringParameters>();
|
||||
if (parameters.exact.has_value())
|
||||
return dom_string_values_from_variant(*parameters.exact);
|
||||
if (parameters.ideal.has_value())
|
||||
return dom_string_values_from_variant(*parameters.ideal);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (constraint.has<String>())
|
||||
return dom_string_values_from_variant(Variant<String, Vector<String>> { constraint.get<String>() });
|
||||
|
||||
return dom_string_values_from_variant(Variant<String, Vector<String>> { constraint.get<Vector<String>>() });
|
||||
}
|
||||
|
||||
static Optional<Vector<String>> extract_device_id_constraint(Optional<ConstrainDOMString> const& device_id_value)
|
||||
{
|
||||
if (!device_id_value.has_value())
|
||||
return {};
|
||||
|
||||
return extract_dom_string_constraint_values(*device_id_value);
|
||||
}
|
||||
|
||||
static void resolve_with_device_info_list(JS::Realm& realm, WebIDL::Promise& promise, GC::RootVector<GC::Ref<MediaDeviceInfo>> const& result_list)
|
||||
{
|
||||
GC::Ref<JS::Array> array = MUST(JS::Array::create(realm, result_list.size()));
|
||||
for (size_t index = 0; index < result_list.size(); ++index) {
|
||||
JS::PropertyKey property_index { index };
|
||||
if (array->create_data_property(property_index, result_list[index]).is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::OperationError::create(realm, "Failed to build enumerateDevices result"_utf16));
|
||||
return;
|
||||
}
|
||||
}
|
||||
WebIDL::resolve_promise(realm, promise, array);
|
||||
}
|
||||
|
||||
}
|
||||
102
Libraries/LibWeb/MediaCapture/MediaDevices.h
Normal file
102
Libraries/LibWeb/MediaCapture/MediaDevices.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGC/Forward.h>
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
#include <LibWeb/DOM/IDLEventListener.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamConstraints.h>
|
||||
#include <LibWeb/WebIDL/Promise.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
class MediaDeviceInfo;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediadevices
|
||||
class MediaDevices final : public DOM::EventTarget {
|
||||
WEB_PLATFORM_OBJECT(MediaDevices, DOM::EventTarget);
|
||||
GC_DECLARE_ALLOCATOR(MediaDevices);
|
||||
|
||||
public:
|
||||
static constexpr bool OVERRIDES_FINALIZE = true;
|
||||
|
||||
[[nodiscard]] static GC::Ref<MediaDevices> create(JS::Realm&);
|
||||
|
||||
GC::Ref<WebIDL::Promise> enumerate_devices();
|
||||
MediaTrackSupportedConstraints get_supported_constraints();
|
||||
GC::Ref<WebIDL::Promise> get_user_media(Optional<MediaStreamConstraints> const& constraints = {});
|
||||
|
||||
void set_ondevicechange(WebIDL::CallbackType* event_handler);
|
||||
WebIDL::CallbackType* ondevicechange();
|
||||
|
||||
private:
|
||||
struct StoredDevice final {
|
||||
String dom_device_id;
|
||||
String kind;
|
||||
String label;
|
||||
String group_id;
|
||||
bool is_default { false };
|
||||
};
|
||||
|
||||
struct PendingGetUserMediaRequest final {
|
||||
GC::Ref<WebIDL::Promise> promise;
|
||||
Optional<Vector<String>> requested_device_ids;
|
||||
};
|
||||
|
||||
explicit MediaDevices(JS::Realm&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void finalize() override;
|
||||
|
||||
bool microphone_information_can_be_exposed();
|
||||
bool can_use_microphone_feature() const;
|
||||
bool can_use_camera_feature() const;
|
||||
bool device_information_can_be_exposed();
|
||||
bool device_enumeration_can_proceed();
|
||||
bool get_user_media_can_proceed() const;
|
||||
bool is_in_view() const;
|
||||
bool has_live_device_of_kind(StringView kind) const;
|
||||
void set_device_information_exposure(bool audio_requested, bool video_requested, bool value);
|
||||
void queue_enumerate_devices_task(GC::Ref<WebIDL::Promise>);
|
||||
void queue_get_user_media_task(GC::Ref<WebIDL::Promise>, Optional<Vector<String>> requested_device_ids);
|
||||
void process_pending_enumerate_devices_requests();
|
||||
void process_pending_get_user_media_requests();
|
||||
GC::RootVector<GC::Ref<MediaDeviceInfo>> create_list_of_device_info_objects(Vector<StoredDevice> const& device_list);
|
||||
void run_device_change_notification_steps(Vector<StoredDevice> const& device_list);
|
||||
static Vector<StoredDevice> current_audio_device_snapshot();
|
||||
void did_observe_audio_device_cache_update();
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediadevices
|
||||
// [[devicesLiveMap]]
|
||||
HashMap<String, bool> m_devices_live_map;
|
||||
// [[devicesAccessibleMap]]
|
||||
HashMap<String, bool> m_devices_accessible_map;
|
||||
// [[storedDeviceList]]
|
||||
Vector<StoredDevice> m_stored_device_list;
|
||||
// [[canExposeCameraInfo]]
|
||||
bool m_can_expose_camera_info { false };
|
||||
bool camera_information_can_be_exposed() const;
|
||||
// [[canExposeMicrophoneInfo]]
|
||||
bool m_can_expose_microphone_info { false };
|
||||
// [[mediaStreamTrackSources]]
|
||||
// FIXME: Replace provider IDs with concrete source objects when MediaCapture source modeling lands.
|
||||
HashTable<u64> m_media_stream_track_sources;
|
||||
Vector<GC::Ref<WebIDL::Promise>> m_pending_enumerate_devices_promises;
|
||||
Vector<PendingGetUserMediaRequest> m_pending_get_user_media_requests;
|
||||
GC::Ptr<DOM::IDLEventListener> m_pending_request_state_change_listener;
|
||||
Optional<u64> m_audio_device_cache_listener_id;
|
||||
};
|
||||
|
||||
}
|
||||
16
Libraries/LibWeb/MediaCapture/MediaDevices.idl
Normal file
16
Libraries/LibWeb/MediaCapture/MediaDevices.idl
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#import <DOM/EventHandler.idl>
|
||||
#import <DOM/EventTarget.idl>
|
||||
#import <MediaCapture/MediaDeviceInfo.idl>
|
||||
#import <MediaCapture/MediaStream.idl>
|
||||
#import <MediaCapture/MediaStreamConstraints.idl>
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediadevices
|
||||
[Exposed=Window, SecureContext]
|
||||
interface MediaDevices : EventTarget {
|
||||
attribute EventHandler ondevicechange;
|
||||
Promise<sequence<MediaDeviceInfo>> enumerateDevices();
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-getsupportedconstraints
|
||||
// Returns a dictionary whose members are the constrainable properties known to the User Agent.
|
||||
MediaTrackSupportedConstraints getSupportedConstraints();
|
||||
Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints = {});
|
||||
};
|
||||
196
Libraries/LibWeb/MediaCapture/MediaStream.cpp
Normal file
196
Libraries/LibWeb/MediaCapture/MediaStream.cpp
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/MediaStreamPrototype.h>
|
||||
#include <LibWeb/Bindings/MediaStreamTrackPrototype.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/MediaCapture/MediaStream.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamTrackEvent.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(MediaStream);
|
||||
|
||||
MediaStream::MediaStream(JS::Realm& realm)
|
||||
: DOM::EventTarget(realm)
|
||||
{
|
||||
}
|
||||
|
||||
GC::Ref<MediaStream> MediaStream::create(JS::Realm& realm)
|
||||
{
|
||||
auto stream = realm.create<MediaStream>(realm);
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-id
|
||||
stream->m_id = Crypto::generate_random_uuid();
|
||||
return stream;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastream
|
||||
GC::Ref<MediaStream> MediaStream::construct_impl(JS::Realm& realm, GC::RootVector<GC::Root<MediaStreamTrack>> const& tracks)
|
||||
{
|
||||
// 1. Let stream be a newly constructed MediaStream object.
|
||||
// 2. Initialize stream.id attribute to a newly generated value.
|
||||
auto stream = create(realm);
|
||||
|
||||
// 3. If the constructor's argument is present, run the following steps:
|
||||
// 3.1. Construct a set of tracks tracks based on the type of argument.
|
||||
// 3.2. For each MediaStreamTrack, track, in tracks:
|
||||
for (auto const& track : tracks) {
|
||||
// 3.2.1. If track is already in stream's track set, skip track.
|
||||
// 3.2.2. Otherwise, add track to stream's track set.
|
||||
stream->add_track(*track);
|
||||
}
|
||||
|
||||
// 4. Return stream.
|
||||
return stream;
|
||||
}
|
||||
|
||||
void MediaStream::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaStream);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void MediaStream::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
for (auto& track : m_tracks)
|
||||
visitor.visit(track);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks
|
||||
Vector<GC::Ref<MediaStreamTrack>> MediaStream::get_audio_tracks() const
|
||||
{
|
||||
// The getAudioTracks method MUST return a sequence that represents a snapshot of all the MediaStreamTrack objects in this stream's track set whose [[Kind]] is equal to "audio".
|
||||
Vector<GC::Ref<MediaStreamTrack>> result;
|
||||
for (auto const& track : m_tracks) {
|
||||
if (track->is_audio())
|
||||
result.append(track);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks
|
||||
Vector<GC::Ref<MediaStreamTrack>> MediaStream::get_video_tracks() const
|
||||
{
|
||||
// The getVideoTracks method MUST return a sequence that represents a snapshot of all the MediaStreamTrack objects in this stream's track set whose [[Kind]] is equal to "video".
|
||||
Vector<GC::Ref<MediaStreamTrack>> result;
|
||||
for (auto const& track : m_tracks) {
|
||||
if (track->is_video())
|
||||
result.append(track);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks
|
||||
Vector<GC::Ref<MediaStreamTrack>> MediaStream::get_tracks() const
|
||||
{
|
||||
// The getTracks method MUST return a sequence that represents a snapshot of all the MediaStreamTrack objects in this stream's track set, regardless of [[Kind]].
|
||||
return m_tracks;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettrackbyid
|
||||
GC::Ptr<MediaStreamTrack> MediaStream::get_track_by_id(String const& track_id) const
|
||||
{
|
||||
// The getTrackById method MUST return either a MediaStreamTrack object from this stream's track set whose [[Id]] is equal to trackId, or null.
|
||||
for (auto const& track : m_tracks) {
|
||||
if (track->id() == track_id)
|
||||
return track;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack
|
||||
void MediaStream::add_track(GC::Ref<MediaStreamTrack> track)
|
||||
{
|
||||
// 1. Let track be the methods argument and stream the MediaStream object on which the method was called.
|
||||
for (auto const& existing_track : m_tracks) {
|
||||
|
||||
// 2. If track is already in stream's track set, then abort these steps.
|
||||
if (existing_track.ptr() == track.ptr())
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Add track to stream's track set.
|
||||
m_tracks.append(track);
|
||||
|
||||
// 4. Fire a track event named addtrack with track at stream.
|
||||
MediaStreamTrackEventInit event_init {};
|
||||
event_init.track = track;
|
||||
auto event = MediaStreamTrackEvent::create(realm(), HTML::EventNames::addtrack, event_init);
|
||||
dispatch_event(event);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack
|
||||
void MediaStream::remove_track(GC::Ref<MediaStreamTrack> track)
|
||||
{
|
||||
// 1. Let track be the methods argument and stream the MediaStream object on which the method was called.
|
||||
// 2. If track is not in stream's track set, then abort these steps.
|
||||
// 3. Remove track from stream's track set.
|
||||
auto removed = m_tracks.remove_first_matching([&](auto const& existing_track) {
|
||||
return existing_track.ptr() == track.ptr();
|
||||
});
|
||||
if (!removed)
|
||||
return;
|
||||
|
||||
// 4. Fire a track event named removetrack with track at stream.
|
||||
MediaStreamTrackEventInit event_init;
|
||||
event_init.track = track;
|
||||
auto event = MediaStreamTrackEvent::create(realm(), HTML::EventNames::removetrack, event_init);
|
||||
dispatch_event(event);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-clone
|
||||
GC::Ref<MediaStream> MediaStream::clone() const
|
||||
{
|
||||
// 1. Let streamClone be a newly constructed MediaStream object.
|
||||
auto stream_clone = create(realm());
|
||||
|
||||
// 3. Clone each track in this MediaStream object and add the result to streamClone's track set.
|
||||
for (auto const& track : m_tracks)
|
||||
stream_clone->add_track(track->clone());
|
||||
|
||||
// 4. Return streamClone.
|
||||
return stream_clone;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-active
|
||||
bool MediaStream::active() const
|
||||
{
|
||||
// The active attribute MUST return true if this MediaStream is active and false otherwise.
|
||||
for (auto const& track : m_tracks) {
|
||||
if (track->ready_state() != Bindings::MediaStreamTrackState::Ended)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-onaddtrack
|
||||
WebIDL::CallbackType* MediaStream::onaddtrack()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::addtrack);
|
||||
}
|
||||
|
||||
void MediaStream::set_onaddtrack(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
// The event type of this event handler is addtrack.
|
||||
set_event_handler_attribute(HTML::EventNames::addtrack, event_handler);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastream-onremovetrack
|
||||
WebIDL::CallbackType* MediaStream::onremovetrack()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::removetrack);
|
||||
}
|
||||
|
||||
void MediaStream::set_onremovetrack(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
// The event type of this event handler is removetrack.
|
||||
set_event_handler_attribute(HTML::EventNames::removetrack, event_handler);
|
||||
}
|
||||
|
||||
}
|
||||
55
Libraries/LibWeb/MediaCapture/MediaStream.h
Normal file
55
Libraries/LibWeb/MediaCapture/MediaStream.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamTrack.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
// Spec: https://w3c.github.io/mediacapture-main/#mediastream
|
||||
class MediaStream final : public DOM::EventTarget {
|
||||
WEB_PLATFORM_OBJECT(MediaStream, DOM::EventTarget);
|
||||
GC_DECLARE_ALLOCATOR(MediaStream);
|
||||
|
||||
public:
|
||||
static GC::Ref<MediaStream> create(JS::Realm&);
|
||||
static GC::Ref<MediaStream> construct_impl(JS::Realm&, GC::RootVector<GC::Root<MediaStreamTrack>> const& tracks);
|
||||
|
||||
virtual ~MediaStream() override = default;
|
||||
|
||||
String id() const { return m_id; }
|
||||
|
||||
Vector<GC::Ref<MediaStreamTrack>> get_audio_tracks() const;
|
||||
Vector<GC::Ref<MediaStreamTrack>> get_video_tracks() const;
|
||||
Vector<GC::Ref<MediaStreamTrack>> get_tracks() const;
|
||||
GC::Ptr<MediaStreamTrack> get_track_by_id(String const& track_id) const;
|
||||
|
||||
void add_track(GC::Ref<MediaStreamTrack> track);
|
||||
void remove_track(GC::Ref<MediaStreamTrack> track);
|
||||
|
||||
GC::Ref<MediaStream> clone() const;
|
||||
bool active() const;
|
||||
|
||||
void set_onaddtrack(WebIDL::CallbackType* event_handler);
|
||||
WebIDL::CallbackType* onaddtrack();
|
||||
void set_onremovetrack(WebIDL::CallbackType* event_handler);
|
||||
WebIDL::CallbackType* onremovetrack();
|
||||
|
||||
private:
|
||||
explicit MediaStream(JS::Realm&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
String m_id;
|
||||
Vector<GC::Ref<MediaStreamTrack>> m_tracks;
|
||||
};
|
||||
|
||||
}
|
||||
21
Libraries/LibWeb/MediaCapture/MediaStream.idl
Normal file
21
Libraries/LibWeb/MediaCapture/MediaStream.idl
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#import <DOM/EventHandler.idl>
|
||||
#import <DOM/EventTarget.idl>
|
||||
#import <MediaCapture/MediaStreamTrack.idl>
|
||||
#import <MediaCapture/MediaStreamTrackEvent.idl>
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastream
|
||||
[Exposed=Window]
|
||||
interface MediaStream : EventTarget {
|
||||
constructor(optional sequence<MediaStreamTrack> tracks = []);
|
||||
readonly attribute DOMString id;
|
||||
sequence<MediaStreamTrack> getAudioTracks();
|
||||
sequence<MediaStreamTrack> getVideoTracks();
|
||||
sequence<MediaStreamTrack> getTracks();
|
||||
MediaStreamTrack? getTrackById(DOMString trackId);
|
||||
undefined addTrack(MediaStreamTrack track);
|
||||
undefined removeTrack(MediaStreamTrack track);
|
||||
MediaStream clone();
|
||||
readonly attribute boolean active;
|
||||
attribute EventHandler onaddtrack;
|
||||
attribute EventHandler onremovetrack;
|
||||
};
|
||||
198
Libraries/LibWeb/MediaCapture/MediaStreamConstraints.h
Normal file
198
Libraries/LibWeb/MediaCapture/MediaStreamConstraints.h
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWeb/WebIDL/Types.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindomstringparameters
|
||||
struct ConstrainDOMStringParameters {
|
||||
// The exact required value for this property.
|
||||
Optional<Variant<String, Vector<String>>> exact;
|
||||
// The ideal (target) value for this property.
|
||||
Optional<Variant<String, Vector<String>>> ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindoublerange
|
||||
struct DoubleRange {
|
||||
// The maximum valid value of this property.
|
||||
Optional<double> max;
|
||||
// The minimum value of this property.
|
||||
Optional<double> min;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindoublerange
|
||||
struct ConstrainDoubleRange : DoubleRange {
|
||||
// The exact required value for this property.
|
||||
Optional<double> exact;
|
||||
// The ideal (target) value for this property.
|
||||
Optional<double> ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainulongrange
|
||||
struct ULongRange {
|
||||
// The maximum valid value of this property.
|
||||
Optional<WebIDL::UnsignedLong> max;
|
||||
// The minimum value of this property.
|
||||
Optional<WebIDL::UnsignedLong> min;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainulongrange
|
||||
struct ConstrainULongRange : ULongRange {
|
||||
// The exact required value for this property.
|
||||
Optional<WebIDL::UnsignedLong> exact;
|
||||
// The ideal (target) value for this property.
|
||||
Optional<WebIDL::UnsignedLong> ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainbooleanparameters
|
||||
struct ConstrainBooleanParameters {
|
||||
// The exact required value for this property.
|
||||
Optional<bool> exact;
|
||||
// The ideal (target) value for this property.
|
||||
Optional<bool> ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainbooleanordomstringparameters
|
||||
struct ConstrainBooleanOrDOMStringParameters {
|
||||
// The exact required value for this property.
|
||||
Optional<Variant<bool, String>> exact;
|
||||
// The ideal (target) value for this property.
|
||||
Optional<Variant<bool, String>> ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainulong
|
||||
// Throughout this specification, the identifier ConstrainULong is used to refer to the
|
||||
// (unsigned long or ConstrainULongRange) type.
|
||||
using ConstrainULong = Variant<WebIDL::UnsignedLong, ConstrainULongRange>;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindouble
|
||||
// Throughout this specification, the identifier ConstrainDouble is used to refer to the
|
||||
// (double or ConstrainDoubleRange) type.
|
||||
using ConstrainDouble = Variant<double, ConstrainDoubleRange>;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainboolean
|
||||
// Throughout this specification, the identifier ConstrainBoolean is used to refer to the
|
||||
// (boolean or ConstrainBooleanParameters) type.
|
||||
using ConstrainBoolean = Variant<bool, ConstrainBooleanParameters>;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindomstring
|
||||
// Throughout this specification, the identifier ConstrainDOMString is used to refer to the
|
||||
// (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) type.
|
||||
using ConstrainDOMString = Variant<String, Vector<String>, ConstrainDOMStringParameters>;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainbooleanordomstring
|
||||
// Throughout this specification, the identifier ConstrainBooleanOrDOMString is used to refer to the
|
||||
// (boolean or DOMString or ConstrainBooleanOrDOMStringParameters) type.
|
||||
using ConstrainBooleanOrDOMString = Variant<bool, String, ConstrainBooleanOrDOMStringParameters>;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dictdef-mediatrackconstraintset
|
||||
struct MediaTrackConstraintSet {
|
||||
Optional<ConstrainULong> width;
|
||||
Optional<ConstrainULong> height;
|
||||
Optional<ConstrainDouble> aspect_ratio;
|
||||
Optional<ConstrainDouble> frame_rate;
|
||||
Optional<ConstrainDOMString> facing_mode;
|
||||
Optional<ConstrainDOMString> resize_mode;
|
||||
Optional<ConstrainULong> sample_rate;
|
||||
Optional<ConstrainULong> sample_size;
|
||||
Optional<ConstrainBooleanOrDOMString> echo_cancellation;
|
||||
Optional<ConstrainBoolean> auto_gain_control;
|
||||
Optional<ConstrainBoolean> noise_suppression;
|
||||
Optional<ConstrainDouble> latency;
|
||||
Optional<ConstrainULong> channel_count;
|
||||
Optional<ConstrainDOMString> device_id;
|
||||
Optional<ConstrainDOMString> group_id;
|
||||
Optional<ConstrainBoolean> background_blur;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediatrackconstraints
|
||||
struct MediaTrackConstraints : MediaTrackConstraintSet {
|
||||
// This is the list of ConstraintSets that the User Agent MUST attempt to satisfy, in order,
|
||||
// skipping only those that cannot be satisfied.
|
||||
Optional<Vector<MediaTrackConstraintSet>> advanced;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastreamconstraints
|
||||
struct MediaStreamConstraints {
|
||||
// If true, it requests that the returned MediaStream contain a video track. If a Constraints
|
||||
// structure is provided, it further specifies the nature and settings of the video Track.
|
||||
// If false, the MediaStream MUST NOT contain a video Track.
|
||||
Variant<bool, MediaTrackConstraints> video { false };
|
||||
|
||||
// If true, it requests that the returned MediaStream contain an audio track. If a Constraints
|
||||
// structure is provided, it further specifies the nature and settings of the audio Track.
|
||||
// If false, the MediaStream MUST NOT contain an audio Track.
|
||||
Variant<bool, MediaTrackConstraints> audio { false };
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediatracksupportedconstraints
|
||||
struct MediaTrackSupportedConstraints {
|
||||
bool width { false };
|
||||
bool height { false };
|
||||
bool aspect_ratio { false };
|
||||
bool frame_rate { false };
|
||||
bool facing_mode { false };
|
||||
bool resize_mode { false };
|
||||
bool sample_rate { false };
|
||||
bool sample_size { false };
|
||||
bool echo_cancellation { false };
|
||||
bool auto_gain_control { false };
|
||||
bool noise_suppression { false };
|
||||
bool latency { false };
|
||||
bool channel_count { false };
|
||||
bool device_id { false };
|
||||
bool group_id { false };
|
||||
bool background_blur { false };
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediatrackcapabilities
|
||||
struct MediaTrackCapabilities {
|
||||
Optional<ULongRange> width;
|
||||
Optional<ULongRange> height;
|
||||
Optional<DoubleRange> aspect_ratio;
|
||||
Optional<DoubleRange> frame_rate;
|
||||
Optional<Vector<String>> facing_mode;
|
||||
Optional<Vector<String>> resize_mode;
|
||||
Optional<ULongRange> sample_rate;
|
||||
Optional<ULongRange> sample_size;
|
||||
Optional<Vector<Variant<bool, String>>> echo_cancellation;
|
||||
Optional<Vector<bool>> auto_gain_control;
|
||||
Optional<Vector<bool>> noise_suppression;
|
||||
Optional<DoubleRange> latency;
|
||||
Optional<ULongRange> channel_count;
|
||||
Optional<String> device_id;
|
||||
Optional<String> group_id;
|
||||
Optional<Vector<bool>> background_blur;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediatracksettings
|
||||
struct MediaTrackSettings {
|
||||
Optional<WebIDL::UnsignedLong> width;
|
||||
Optional<WebIDL::UnsignedLong> height;
|
||||
Optional<double> aspect_ratio;
|
||||
Optional<double> frame_rate;
|
||||
Optional<String> facing_mode;
|
||||
Optional<String> resize_mode;
|
||||
Optional<WebIDL::UnsignedLong> sample_rate;
|
||||
Optional<WebIDL::UnsignedLong> sample_size;
|
||||
Optional<Variant<bool, String>> echo_cancellation;
|
||||
Optional<bool> auto_gain_control;
|
||||
Optional<bool> noise_suppression;
|
||||
Optional<double> latency;
|
||||
Optional<WebIDL::UnsignedLong> channel_count;
|
||||
Optional<String> device_id;
|
||||
Optional<String> group_id;
|
||||
Optional<bool> background_blur;
|
||||
};
|
||||
|
||||
}
|
||||
137
Libraries/LibWeb/MediaCapture/MediaStreamConstraints.idl
Normal file
137
Libraries/LibWeb/MediaCapture/MediaStreamConstraints.idl
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// https://w3c.github.io/mediacapture-main/#dom-constraindomstringparameters
|
||||
dictionary ConstrainDOMStringParameters {
|
||||
(DOMString or sequence<DOMString>) exact;
|
||||
(DOMString or sequence<DOMString>) ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindoublerange
|
||||
dictionary DoubleRange {
|
||||
double max;
|
||||
double min;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constraindoublerange
|
||||
dictionary ConstrainDoubleRange : DoubleRange {
|
||||
double exact;
|
||||
double ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainulongrange
|
||||
dictionary ULongRange {
|
||||
[Clamp] unsigned long max;
|
||||
[Clamp] unsigned long min;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainulongrange
|
||||
dictionary ConstrainULongRange : ULongRange {
|
||||
[Clamp] unsigned long exact;
|
||||
[Clamp] unsigned long ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainbooleanparameters
|
||||
dictionary ConstrainBooleanParameters {
|
||||
boolean exact;
|
||||
boolean ideal;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-constrainbooleanordomstringparameters
|
||||
dictionary ConstrainBooleanOrDOMStringParameters {
|
||||
(boolean or DOMString) exact;
|
||||
(boolean or DOMString) ideal;
|
||||
};
|
||||
|
||||
typedef (unsigned long or ConstrainULongRange) ConstrainULong;
|
||||
typedef (double or ConstrainDoubleRange) ConstrainDouble;
|
||||
typedef (boolean or ConstrainBooleanParameters) ConstrainBoolean;
|
||||
typedef (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) ConstrainDOMString;
|
||||
typedef (boolean or DOMString or ConstrainBooleanOrDOMStringParameters) ConstrainBooleanOrDOMString;
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dictdef-mediatrackconstraintset
|
||||
dictionary MediaTrackConstraintSet {
|
||||
ConstrainULong width;
|
||||
ConstrainULong height;
|
||||
ConstrainDouble aspectRatio;
|
||||
ConstrainDouble frameRate;
|
||||
ConstrainDOMString facingMode;
|
||||
ConstrainDOMString resizeMode;
|
||||
ConstrainULong sampleRate;
|
||||
ConstrainULong sampleSize;
|
||||
ConstrainBooleanOrDOMString echoCancellation;
|
||||
ConstrainBoolean autoGainControl;
|
||||
ConstrainBoolean noiseSuppression;
|
||||
ConstrainDouble latency;
|
||||
ConstrainULong channelCount;
|
||||
ConstrainDOMString deviceId;
|
||||
ConstrainDOMString groupId;
|
||||
ConstrainBoolean backgroundBlur;
|
||||
};
|
||||
|
||||
dictionary MediaTrackConstraints : MediaTrackConstraintSet {
|
||||
sequence<MediaTrackConstraintSet> advanced;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastreamconstraints
|
||||
dictionary MediaStreamConstraints {
|
||||
(boolean or MediaTrackConstraints) video = false;
|
||||
(boolean or MediaTrackConstraints) audio = false;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediatracksupportedconstraints
|
||||
dictionary MediaTrackSupportedConstraints {
|
||||
boolean width = true;
|
||||
boolean height = true;
|
||||
boolean aspectRatio = true;
|
||||
boolean frameRate = true;
|
||||
boolean facingMode = true;
|
||||
boolean resizeMode = true;
|
||||
boolean sampleRate = true;
|
||||
boolean sampleSize = true;
|
||||
boolean echoCancellation = true;
|
||||
boolean autoGainControl = true;
|
||||
boolean noiseSuppression = true;
|
||||
boolean latency = true;
|
||||
boolean channelCount = true;
|
||||
boolean deviceId = true;
|
||||
boolean groupId = true;
|
||||
boolean backgroundBlur = true;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediatrackcapabilities
|
||||
dictionary MediaTrackCapabilities {
|
||||
ULongRange width;
|
||||
ULongRange height;
|
||||
DoubleRange aspectRatio;
|
||||
DoubleRange frameRate;
|
||||
sequence<DOMString> facingMode;
|
||||
sequence<DOMString> resizeMode;
|
||||
ULongRange sampleRate;
|
||||
ULongRange sampleSize;
|
||||
sequence<(boolean or DOMString)> echoCancellation;
|
||||
sequence<boolean> autoGainControl;
|
||||
sequence<boolean> noiseSuppression;
|
||||
DoubleRange latency;
|
||||
ULongRange channelCount;
|
||||
DOMString deviceId;
|
||||
DOMString groupId;
|
||||
sequence<boolean> backgroundBlur;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediatracksettings
|
||||
dictionary MediaTrackSettings {
|
||||
unsigned long width;
|
||||
unsigned long height;
|
||||
double aspectRatio;
|
||||
double frameRate;
|
||||
DOMString facingMode;
|
||||
DOMString resizeMode;
|
||||
unsigned long sampleRate;
|
||||
unsigned long sampleSize;
|
||||
(boolean or DOMString) echoCancellation;
|
||||
boolean autoGainControl;
|
||||
boolean noiseSuppression;
|
||||
double latency;
|
||||
unsigned long channelCount;
|
||||
DOMString deviceId;
|
||||
DOMString groupId;
|
||||
boolean backgroundBlur;
|
||||
};
|
||||
186
Libraries/LibWeb/MediaCapture/MediaStreamTrack.cpp
Normal file
186
Libraries/LibWeb/MediaCapture/MediaStreamTrack.cpp
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/MediaStreamTrackPrototype.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamTrack.h>
|
||||
#include <LibWeb/WebIDL/Promise.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(MediaStreamTrack);
|
||||
|
||||
Atomic<u64> MediaStreamTrack::s_next_provider_id { 1 };
|
||||
|
||||
static constexpr auto audio_track_kind = static_cast<Bindings::MediaStreamTrackKind>(0);
|
||||
static constexpr auto video_track_kind = static_cast<Bindings::MediaStreamTrackKind>(1);
|
||||
|
||||
MediaStreamTrack::MediaStreamTrack(JS::Realm& realm)
|
||||
: DOM::EventTarget(realm)
|
||||
{
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastreamtrack
|
||||
GC::Ref<MediaStreamTrack> MediaStreamTrack::create(JS::Realm& realm, Bindings::MediaStreamTrackKind kind, Optional<String> label, bool muted)
|
||||
{
|
||||
// https://w3c.github.io/mediacapture-main/#dfn-create-a-mediastreamtrack
|
||||
// 1. Let track be a new object of type source's MediaStreamTrack source type.
|
||||
auto track = realm.create<MediaStreamTrack>(realm);
|
||||
|
||||
// Initialize track with the following internal slots.
|
||||
// FIXME: [[Source]], initialized to source.
|
||||
|
||||
// [[Id]]: See MediaStream.id attribute for guidelines on how to generate such an identifier.
|
||||
track->m_id = Crypto::generate_random_uuid();
|
||||
|
||||
// [[Kind]]: "audio" if source is an audio source, or "video" if source is a video source.
|
||||
track->m_kind = kind;
|
||||
|
||||
// [[Label]]: source label or empty string.
|
||||
track->m_label = label.value_or(""_string);
|
||||
|
||||
// [[ReadyState]]: "live".
|
||||
track->m_state = Bindings::MediaStreamTrackState::Live;
|
||||
|
||||
// [[Enabled]]: true.
|
||||
track->m_enabled = true;
|
||||
|
||||
// [[Muted]]: true if source is muted, false otherwise.
|
||||
track->m_muted = muted;
|
||||
|
||||
// [[Capabilities]], [[Constraints]], and [[Settings]], all initialized as specified in the ConstrainablePattern.
|
||||
// [[Restrictable]], initialized to false.
|
||||
track->m_provider_id = s_next_provider_id.fetch_add(1, AK::MemoryOrder::memory_order_relaxed);
|
||||
|
||||
// FIXME: 2. If mediaDevicesToTieSourceTo is not null, tie track source to MediaDevices with source and mediaDevicesToTieSourceTo.
|
||||
// FIXME: 3. Run source's MediaStreamTrack source-specific construction steps with track as parameter.
|
||||
|
||||
// 4. Return track.
|
||||
return track;
|
||||
}
|
||||
|
||||
void MediaStreamTrack::set_settings(MediaTrackSettings settings)
|
||||
{
|
||||
m_settings = move(settings);
|
||||
}
|
||||
|
||||
void MediaStreamTrack::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaStreamTrack);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-stop
|
||||
void MediaStreamTrack::stop()
|
||||
{
|
||||
// 1. Let track be the current MediaStreamTrack object.
|
||||
// 2. If track's [[ReadyState]] is "ended", then abort these steps.
|
||||
if (m_state == Bindings::MediaStreamTrackState::Ended)
|
||||
return;
|
||||
|
||||
// FIXME: 3. Notify track's source that track is ended.
|
||||
|
||||
// 4. Set track's [[ReadyState]] to "ended".
|
||||
m_state = Bindings::MediaStreamTrackState::Ended;
|
||||
|
||||
// AD-HOC: Until track sources are modeled, stop() is the only implemented end-of-life path.
|
||||
// Preserve the observable ended event instead of dropping it on the floor.
|
||||
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::ended));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-clone
|
||||
GC::Ref<MediaStreamTrack> MediaStreamTrack::clone() const
|
||||
{
|
||||
// When the clone() method is invoked, the User Agent MUST return the result of clone a track with this.
|
||||
// https://w3c.github.io/mediacapture-main/#clone-a-track
|
||||
// 1. Let track be the MediaStreamTrack object to be cloned.
|
||||
//
|
||||
// FIXME: 2. Let source be track's [[Source]].
|
||||
|
||||
// 3. Let trackClone be the result of creating a MediaStreamTrack with source and null.
|
||||
auto track_clone = create(realm(), m_kind, m_label, m_muted);
|
||||
|
||||
// 4. Set trackClone's [[ReadyState]] to track's [[ReadyState]] value.
|
||||
track_clone->m_state = m_state;
|
||||
// 5. Set trackClone's [[Capabilities]] to a clone of track's [[Capabilities]].
|
||||
track_clone->m_capabilities = m_capabilities;
|
||||
// 6. Set trackClone's [[Constraints]] to a clone of track's [[Constraints]].
|
||||
track_clone->m_constraints = m_constraints;
|
||||
// 7. Set trackClone's [[Settings]] to a clone of track's [[Settings]].
|
||||
track_clone->m_settings = m_settings;
|
||||
|
||||
// Initialize the remaining internal slots to match the source track.
|
||||
track_clone->m_enabled = m_enabled;
|
||||
track_clone->m_provider_id = s_next_provider_id.fetch_add(1, AK::MemoryOrder::memory_order_relaxed);
|
||||
|
||||
// FIXME: 8. Run source MediaStreamTrack source-specific clone steps with track and trackClone as parameters.
|
||||
|
||||
// 9. Return trackClone.
|
||||
return track_clone;
|
||||
}
|
||||
|
||||
bool MediaStreamTrack::is_audio() const
|
||||
{
|
||||
return m_kind == audio_track_kind;
|
||||
}
|
||||
|
||||
bool MediaStreamTrack::is_video() const
|
||||
{
|
||||
return m_kind == video_track_kind;
|
||||
}
|
||||
|
||||
Optional<String> MediaStreamTrack::device_id() const
|
||||
{
|
||||
return m_settings.device_id;
|
||||
}
|
||||
|
||||
u32 MediaStreamTrack::sample_rate_hz() const
|
||||
{
|
||||
if (m_settings.sample_rate.has_value())
|
||||
return *m_settings.sample_rate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 MediaStreamTrack::channel_count() const
|
||||
{
|
||||
if (m_settings.channel_count.has_value())
|
||||
return *m_settings.channel_count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-getcapabilities
|
||||
MediaTrackCapabilities MediaStreamTrack::get_capabilities() const
|
||||
{
|
||||
return m_capabilities;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-getconstraints
|
||||
MediaTrackConstraints MediaStreamTrack::get_constraints() const
|
||||
{
|
||||
return m_constraints;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-getsettings
|
||||
MediaTrackSettings MediaStreamTrack::get_settings() const
|
||||
{
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-applyconstraints
|
||||
GC::Ref<WebIDL::Promise> MediaStreamTrack::apply_constraints(Optional<MediaTrackConstraints> const& constraints)
|
||||
{
|
||||
if (constraints.has_value())
|
||||
m_constraints = constraints.value();
|
||||
|
||||
// FIXME: Apply constraints to the underlying source and update settings.
|
||||
return WebIDL::create_resolved_promise(realm(), JS::js_undefined());
|
||||
}
|
||||
|
||||
}
|
||||
81
Libraries/LibWeb/MediaCapture/MediaStreamTrack.h
Normal file
81
Libraries/LibWeb/MediaCapture/MediaStreamTrack.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Atomic.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibWeb/Bindings/MediaStreamTrackPrototype.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamConstraints.h>
|
||||
#include <LibWeb/WebIDL/Promise.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
// Spec: https://w3c.github.io/mediacapture-main/#mediastreamtrack
|
||||
class MediaStreamTrack final : public DOM::EventTarget {
|
||||
WEB_PLATFORM_OBJECT(MediaStreamTrack, DOM::EventTarget);
|
||||
GC_DECLARE_ALLOCATOR(MediaStreamTrack);
|
||||
|
||||
public:
|
||||
static GC::Ref<MediaStreamTrack> create(JS::Realm&, Bindings::MediaStreamTrackKind, Optional<String> label = {}, bool muted = false);
|
||||
|
||||
virtual ~MediaStreamTrack() override = default;
|
||||
|
||||
Bindings::MediaStreamTrackKind kind() const { return m_kind; }
|
||||
String id() const { return m_id; }
|
||||
String label() const { return m_label; }
|
||||
|
||||
bool enabled() const { return m_enabled; }
|
||||
void set_enabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
bool muted() const { return m_muted; }
|
||||
|
||||
Bindings::MediaStreamTrackState ready_state() const { return m_state; }
|
||||
|
||||
void stop();
|
||||
GC::Ref<MediaStreamTrack> clone() const;
|
||||
|
||||
bool is_audio() const;
|
||||
bool is_video() const;
|
||||
|
||||
MediaTrackCapabilities get_capabilities() const;
|
||||
MediaTrackConstraints get_constraints() const;
|
||||
MediaTrackSettings get_settings() const;
|
||||
GC::Ref<WebIDL::Promise> apply_constraints(Optional<MediaTrackConstraints> const& constraints);
|
||||
void set_settings(MediaTrackSettings settings);
|
||||
|
||||
Optional<String> device_id() const;
|
||||
u32 sample_rate_hz() const;
|
||||
u32 channel_count() const;
|
||||
|
||||
u64 provider_id() const { return m_provider_id; }
|
||||
|
||||
private:
|
||||
explicit MediaStreamTrack(JS::Realm&);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
static Atomic<u64> s_next_provider_id;
|
||||
|
||||
Bindings::MediaStreamTrackKind m_kind { static_cast<Bindings::MediaStreamTrackKind>(0) };
|
||||
String m_id;
|
||||
String m_label;
|
||||
bool m_enabled { true };
|
||||
bool m_muted { false };
|
||||
Bindings::MediaStreamTrackState m_state { static_cast<Bindings::MediaStreamTrackState>(0) };
|
||||
|
||||
MediaTrackCapabilities m_capabilities;
|
||||
MediaTrackConstraints m_constraints;
|
||||
MediaTrackSettings m_settings;
|
||||
|
||||
u64 m_provider_id { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
22
Libraries/LibWeb/MediaCapture/MediaStreamTrack.idl
Normal file
22
Libraries/LibWeb/MediaCapture/MediaStreamTrack.idl
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#import <DOM/EventTarget.idl>
|
||||
#import <MediaCapture/MediaStreamConstraints.idl>
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastreamtrack
|
||||
|
||||
enum MediaStreamTrackState { "live", "ended" };
|
||||
enum MediaStreamTrackKind { "audio", "video" };
|
||||
|
||||
[Exposed=Window]
|
||||
interface MediaStreamTrack : EventTarget {
|
||||
readonly attribute MediaStreamTrackKind kind;
|
||||
readonly attribute DOMString id;
|
||||
readonly attribute DOMString label;
|
||||
attribute boolean enabled;
|
||||
readonly attribute boolean muted;
|
||||
readonly attribute MediaStreamTrackState readyState;
|
||||
undefined stop();
|
||||
MediaTrackCapabilities getCapabilities();
|
||||
MediaTrackConstraints getConstraints();
|
||||
MediaTrackSettings getSettings();
|
||||
Promise<undefined> applyConstraints(optional MediaTrackConstraints constraints = {});
|
||||
};
|
||||
58
Libraries/LibWeb/MediaCapture/MediaStreamTrackEvent.cpp
Normal file
58
Libraries/LibWeb/MediaCapture/MediaStreamTrackEvent.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/MediaStreamTrackEventPrototype.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamTrackEvent.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
static GC::Ref<MediaStreamTrack> require_track(MediaStreamTrackEventInit const& event_init)
|
||||
{
|
||||
VERIFY(event_init.track);
|
||||
return *event_init.track;
|
||||
}
|
||||
|
||||
GC_DEFINE_ALLOCATOR(MediaStreamTrackEvent);
|
||||
|
||||
GC::Ref<MediaStreamTrackEvent> MediaStreamTrackEvent::create(JS::Realm& realm, FlyString const& event_name, MediaStreamTrackEventInit const& event_init)
|
||||
{
|
||||
return realm.create<MediaStreamTrackEvent>(realm, event_name, event_init);
|
||||
}
|
||||
|
||||
GC::Ref<MediaStreamTrackEvent> MediaStreamTrackEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, MediaStreamTrackEventInit const& event_init)
|
||||
{
|
||||
return create(realm, event_name, event_init);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastreamtrackevent
|
||||
MediaStreamTrackEvent::MediaStreamTrackEvent(JS::Realm& realm, FlyString const& event_name, MediaStreamTrackEventInit const& event_init)
|
||||
: DOM::Event(realm, event_name, [&] {
|
||||
DOM::EventInit base_init {};
|
||||
base_init.bubbles = event_init.bubbles;
|
||||
base_init.cancelable = event_init.cancelable;
|
||||
base_init.composed = event_init.composed;
|
||||
return base_init;
|
||||
}())
|
||||
, m_track(require_track(event_init))
|
||||
{
|
||||
}
|
||||
|
||||
MediaStreamTrackEvent::~MediaStreamTrackEvent() = default;
|
||||
|
||||
void MediaStreamTrackEvent::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(MediaStreamTrackEvent);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void MediaStreamTrackEvent::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_track);
|
||||
}
|
||||
|
||||
}
|
||||
41
Libraries/LibWeb/MediaCapture/MediaStreamTrackEvent.h
Normal file
41
Libraries/LibWeb/MediaCapture/MediaStreamTrackEvent.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2026, The Ladybird developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/MediaCapture/MediaStreamTrack.h>
|
||||
|
||||
namespace Web::MediaCapture {
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dictdef-mediastreamtrackeventinit
|
||||
struct MediaStreamTrackEventInit final : public DOM::EventInit {
|
||||
GC::Ptr<MediaStreamTrack> track;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastreamtrackevent
|
||||
class MediaStreamTrackEvent final : public DOM::Event {
|
||||
WEB_PLATFORM_OBJECT(MediaStreamTrackEvent, DOM::Event);
|
||||
GC_DECLARE_ALLOCATOR(MediaStreamTrackEvent);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static GC::Ref<MediaStreamTrackEvent> create(JS::Realm&, FlyString const& event_name, MediaStreamTrackEventInit const& event_init);
|
||||
[[nodiscard]] static GC::Ref<MediaStreamTrackEvent> construct_impl(JS::Realm&, FlyString const& event_name, MediaStreamTrackEventInit const& event_init);
|
||||
|
||||
virtual ~MediaStreamTrackEvent() override;
|
||||
|
||||
GC::Ref<MediaStreamTrack> track() const { return m_track; }
|
||||
|
||||
private:
|
||||
MediaStreamTrackEvent(JS::Realm&, FlyString const& event_name, MediaStreamTrackEventInit const& event_init);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
GC::Ref<MediaStreamTrack> m_track;
|
||||
};
|
||||
|
||||
}
|
||||
14
Libraries/LibWeb/MediaCapture/MediaStreamTrackEvent.idl
Normal file
14
Libraries/LibWeb/MediaCapture/MediaStreamTrackEvent.idl
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#import <DOM/Event.idl>
|
||||
#import <MediaCapture/MediaStreamTrack.idl>
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#dictdef-mediastreamtrackeventinit
|
||||
dictionary MediaStreamTrackEventInit : EventInit {
|
||||
required MediaStreamTrack track;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/#mediastreamtrackevent
|
||||
[Exposed=Window]
|
||||
interface MediaStreamTrackEvent : Event {
|
||||
constructor(DOMString type, MediaStreamTrackEventInit eventInitDict);
|
||||
[SameObject] readonly attribute MediaStreamTrack track;
|
||||
};
|
||||
|
|
@ -161,6 +161,11 @@ libweb_js_bindings(Geometry/DOMQuad)
|
|||
libweb_js_bindings(Geometry/DOMRect)
|
||||
libweb_js_bindings(Geometry/DOMRectList)
|
||||
libweb_js_bindings(Geometry/DOMRectReadOnly)
|
||||
libweb_js_bindings(MediaCapture/MediaDeviceInfo)
|
||||
libweb_js_bindings(MediaCapture/MediaDevices)
|
||||
libweb_js_bindings(MediaCapture/MediaStream)
|
||||
libweb_js_bindings(MediaCapture/MediaStreamTrack)
|
||||
libweb_js_bindings(MediaCapture/MediaStreamTrackEvent)
|
||||
libweb_js_bindings(HTML/AudioTrack)
|
||||
libweb_js_bindings(HTML/AudioTrackList)
|
||||
libweb_js_bindings(HTML/BarProp)
|
||||
|
|
|
|||
|
|
@ -100,8 +100,13 @@ static bool is_platform_object(Type const& type)
|
|||
"KeyframeEffect"sv,
|
||||
"MediaKeySystemAccess"sv,
|
||||
"MediaList"sv,
|
||||
"MediaDeviceInfo"sv,
|
||||
"MediaDevices"sv,
|
||||
"MediaSource"sv,
|
||||
"Memory"sv,
|
||||
"MediaStream"sv,
|
||||
"MediaStreamTrack"sv,
|
||||
"MediaStreamTrackEvent"sv,
|
||||
"MessagePort"sv,
|
||||
"Module"sv,
|
||||
"MutationRecord"sv,
|
||||
|
|
@ -5438,6 +5443,7 @@ using namespace Web::IndexedDB;
|
|||
using namespace Web::Internals;
|
||||
using namespace Web::IntersectionObserver;
|
||||
using namespace Web::MediaCapabilitiesAPI;
|
||||
using namespace Web::MediaCapture;
|
||||
using namespace Web::MediaSourceExtensions;
|
||||
using namespace Web::NavigationTiming;
|
||||
using namespace Web::NotificationsAPI;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ Crash/HTML/video-track-switch-during-seek.html
|
|||
Text/input/HTML/media-source-buffered.html
|
||||
Text/input/HTML/media-source-setup.html
|
||||
Text/input/css/FontFace-arraybuffer-matching.html
|
||||
Text/input/wpt-import/mediacapture-streams/idlharness.https.window.html
|
||||
|
||||
; pushState with path URL requires HTTP(s) scheme.
|
||||
Text/input/navigation/history-replace-push-then-back.html
|
||||
|
|
|
|||
|
|
@ -296,6 +296,8 @@ ManagedSourceBuffer
|
|||
Map
|
||||
MathMLElement
|
||||
MediaCapabilities
|
||||
MediaDeviceInfo
|
||||
MediaDevices
|
||||
MediaElementAudioSourceNode
|
||||
MediaError
|
||||
MediaKeySystemAccess
|
||||
|
|
@ -304,6 +306,9 @@ MediaQueryList
|
|||
MediaQueryListEvent
|
||||
MediaSource
|
||||
MediaSourceHandle
|
||||
MediaStream
|
||||
MediaStreamTrack
|
||||
MediaStreamTrackEvent
|
||||
MessageChannel
|
||||
MessageEvent
|
||||
MessagePort
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 168 tests
|
||||
|
||||
102 Pass
|
||||
66 Fail
|
||||
Pass idl_test setup
|
||||
Pass idl_test validation
|
||||
Pass Partial interface Navigator: original interface defined
|
||||
Pass Partial interface Navigator: member names are unique
|
||||
Pass Partial interface MediaDevices: original interface defined
|
||||
Pass Partial interface MediaDevices: member names are unique
|
||||
Pass Partial interface Navigator[2]: member names are unique
|
||||
Pass Partial interface mixin NavigatorID: member names are unique
|
||||
Pass Navigator includes NavigatorID: member names are unique
|
||||
Pass Navigator includes NavigatorLanguage: member names are unique
|
||||
Pass Navigator includes NavigatorOnLine: member names are unique
|
||||
Pass Navigator includes NavigatorContentUtils: member names are unique
|
||||
Pass Navigator includes NavigatorCookies: member names are unique
|
||||
Pass Navigator includes NavigatorPlugins: member names are unique
|
||||
Pass Navigator includes NavigatorConcurrentHardware: member names are unique
|
||||
Pass MediaStream interface: existence and properties of interface object
|
||||
Pass MediaStream interface object length
|
||||
Pass MediaStream interface object name
|
||||
Pass MediaStream interface: existence and properties of interface prototype object
|
||||
Pass MediaStream interface: existence and properties of interface prototype object's "constructor" property
|
||||
Pass MediaStream interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Pass MediaStream interface: attribute id
|
||||
Pass MediaStream interface: operation getAudioTracks()
|
||||
Pass MediaStream interface: operation getVideoTracks()
|
||||
Pass MediaStream interface: operation getTracks()
|
||||
Pass MediaStream interface: operation getTrackById(DOMString)
|
||||
Pass MediaStream interface: operation addTrack(MediaStreamTrack)
|
||||
Pass MediaStream interface: operation removeTrack(MediaStreamTrack)
|
||||
Pass MediaStream interface: operation clone()
|
||||
Pass MediaStream interface: attribute active
|
||||
Pass MediaStream interface: attribute onaddtrack
|
||||
Pass MediaStream interface: attribute onremovetrack
|
||||
Fail MediaStream must be primary interface of stream
|
||||
Fail Stringification of stream
|
||||
Fail MediaStream interface: stream must inherit property "id" with the proper type
|
||||
Fail MediaStream interface: stream must inherit property "getAudioTracks()" with the proper type
|
||||
Fail MediaStream interface: stream must inherit property "getVideoTracks()" with the proper type
|
||||
Fail MediaStream interface: stream must inherit property "getTracks()" with the proper type
|
||||
Fail MediaStream interface: stream must inherit property "getTrackById(DOMString)" with the proper type
|
||||
Fail MediaStream interface: calling getTrackById(DOMString) on stream with too few arguments must throw TypeError
|
||||
Fail MediaStream interface: stream must inherit property "addTrack(MediaStreamTrack)" with the proper type
|
||||
Fail MediaStream interface: calling addTrack(MediaStreamTrack) on stream with too few arguments must throw TypeError
|
||||
Fail MediaStream interface: stream must inherit property "removeTrack(MediaStreamTrack)" with the proper type
|
||||
Fail MediaStream interface: calling removeTrack(MediaStreamTrack) on stream with too few arguments must throw TypeError
|
||||
Fail MediaStream interface: stream must inherit property "clone()" with the proper type
|
||||
Fail MediaStream interface: stream must inherit property "active" with the proper type
|
||||
Fail MediaStream interface: stream must inherit property "onaddtrack" with the proper type
|
||||
Fail MediaStream interface: stream must inherit property "onremovetrack" with the proper type
|
||||
Pass MediaStream must be primary interface of new MediaStream()
|
||||
Pass Stringification of new MediaStream()
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "id" with the proper type
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "getAudioTracks()" with the proper type
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "getVideoTracks()" with the proper type
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "getTracks()" with the proper type
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "getTrackById(DOMString)" with the proper type
|
||||
Pass MediaStream interface: calling getTrackById(DOMString) on new MediaStream() with too few arguments must throw TypeError
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "addTrack(MediaStreamTrack)" with the proper type
|
||||
Pass MediaStream interface: calling addTrack(MediaStreamTrack) on new MediaStream() with too few arguments must throw TypeError
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "removeTrack(MediaStreamTrack)" with the proper type
|
||||
Pass MediaStream interface: calling removeTrack(MediaStreamTrack) on new MediaStream() with too few arguments must throw TypeError
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "clone()" with the proper type
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "active" with the proper type
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "onaddtrack" with the proper type
|
||||
Pass MediaStream interface: new MediaStream() must inherit property "onremovetrack" with the proper type
|
||||
Pass MediaStreamTrack interface: existence and properties of interface object
|
||||
Pass MediaStreamTrack interface object length
|
||||
Pass MediaStreamTrack interface object name
|
||||
Pass MediaStreamTrack interface: existence and properties of interface prototype object
|
||||
Pass MediaStreamTrack interface: existence and properties of interface prototype object's "constructor" property
|
||||
Pass MediaStreamTrack interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Pass MediaStreamTrack interface: attribute kind
|
||||
Pass MediaStreamTrack interface: attribute id
|
||||
Pass MediaStreamTrack interface: attribute label
|
||||
Pass MediaStreamTrack interface: attribute enabled
|
||||
Pass MediaStreamTrack interface: attribute muted
|
||||
Fail MediaStreamTrack interface: attribute onmute
|
||||
Fail MediaStreamTrack interface: attribute onunmute
|
||||
Pass MediaStreamTrack interface: attribute readyState
|
||||
Fail MediaStreamTrack interface: attribute onended
|
||||
Fail MediaStreamTrack interface: operation clone()
|
||||
Pass MediaStreamTrack interface: operation stop()
|
||||
Pass MediaStreamTrack interface: operation getCapabilities()
|
||||
Pass MediaStreamTrack interface: operation getConstraints()
|
||||
Pass MediaStreamTrack interface: operation getSettings()
|
||||
Pass MediaStreamTrack interface: operation applyConstraints(optional MediaTrackConstraints)
|
||||
Fail MediaStreamTrack must be primary interface of track
|
||||
Fail Stringification of track
|
||||
Fail MediaStreamTrack interface: track must inherit property "kind" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "id" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "label" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "enabled" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "muted" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "onmute" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "onunmute" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "readyState" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "onended" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "clone()" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "stop()" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "getCapabilities()" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "getConstraints()" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "getSettings()" with the proper type
|
||||
Fail MediaStreamTrack interface: track must inherit property "applyConstraints(optional MediaTrackConstraints)" with the proper type
|
||||
Fail MediaStreamTrack interface: calling applyConstraints(optional MediaTrackConstraints) on track with too few arguments must throw TypeError
|
||||
Pass MediaStreamTrackEvent interface: existence and properties of interface object
|
||||
Pass MediaStreamTrackEvent interface object length
|
||||
Pass MediaStreamTrackEvent interface object name
|
||||
Pass MediaStreamTrackEvent interface: existence and properties of interface prototype object
|
||||
Pass MediaStreamTrackEvent interface: existence and properties of interface prototype object's "constructor" property
|
||||
Pass MediaStreamTrackEvent interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Pass MediaStreamTrackEvent interface: attribute track
|
||||
Fail MediaStreamTrackEvent must be primary interface of trackEvent
|
||||
Fail Stringification of trackEvent
|
||||
Fail MediaStreamTrackEvent interface: trackEvent must inherit property "track" with the proper type
|
||||
Fail OverconstrainedError interface: existence and properties of interface object
|
||||
Fail OverconstrainedError interface object length
|
||||
Fail OverconstrainedError interface object name
|
||||
Fail OverconstrainedError interface: existence and properties of interface prototype object
|
||||
Fail OverconstrainedError interface: existence and properties of interface prototype object's "constructor" property
|
||||
Fail OverconstrainedError interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Fail OverconstrainedError interface: attribute constraint
|
||||
Fail OverconstrainedError must be primary interface of new OverconstrainedError("constraint")
|
||||
Fail Stringification of new OverconstrainedError("constraint")
|
||||
Fail OverconstrainedError interface: new OverconstrainedError("constraint") must inherit property "constraint" with the proper type
|
||||
Pass MediaDevices interface: existence and properties of interface object
|
||||
Pass MediaDevices interface object length
|
||||
Pass MediaDevices interface object name
|
||||
Pass MediaDevices interface: existence and properties of interface prototype object
|
||||
Pass MediaDevices interface: existence and properties of interface prototype object's "constructor" property
|
||||
Pass MediaDevices interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Pass MediaDevices interface: attribute ondevicechange
|
||||
Pass MediaDevices interface: operation enumerateDevices()
|
||||
Pass MediaDevices interface: operation getSupportedConstraints()
|
||||
Pass MediaDevices interface: operation getUserMedia(optional MediaStreamConstraints)
|
||||
Pass MediaDevices must be primary interface of navigator.mediaDevices
|
||||
Pass Stringification of navigator.mediaDevices
|
||||
Pass MediaDevices interface: navigator.mediaDevices must inherit property "ondevicechange" with the proper type
|
||||
Pass MediaDevices interface: navigator.mediaDevices must inherit property "enumerateDevices()" with the proper type
|
||||
Pass MediaDevices interface: navigator.mediaDevices must inherit property "getSupportedConstraints()" with the proper type
|
||||
Pass MediaDevices interface: navigator.mediaDevices must inherit property "getUserMedia(optional MediaStreamConstraints)" with the proper type
|
||||
Pass MediaDevices interface: calling getUserMedia(optional MediaStreamConstraints) on navigator.mediaDevices with too few arguments must throw TypeError
|
||||
Pass MediaDeviceInfo interface: existence and properties of interface object
|
||||
Pass MediaDeviceInfo interface object length
|
||||
Pass MediaDeviceInfo interface object name
|
||||
Pass MediaDeviceInfo interface: existence and properties of interface prototype object
|
||||
Pass MediaDeviceInfo interface: existence and properties of interface prototype object's "constructor" property
|
||||
Pass MediaDeviceInfo interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Pass MediaDeviceInfo interface: attribute deviceId
|
||||
Pass MediaDeviceInfo interface: attribute kind
|
||||
Pass MediaDeviceInfo interface: attribute label
|
||||
Pass MediaDeviceInfo interface: attribute groupId
|
||||
Pass MediaDeviceInfo interface: operation toJSON()
|
||||
Fail InputDeviceInfo interface: existence and properties of interface object
|
||||
Fail InputDeviceInfo interface object length
|
||||
Fail InputDeviceInfo interface object name
|
||||
Fail InputDeviceInfo interface: existence and properties of interface prototype object
|
||||
Fail InputDeviceInfo interface: existence and properties of interface prototype object's "constructor" property
|
||||
Fail InputDeviceInfo interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Fail InputDeviceInfo interface: operation getCapabilities()
|
||||
Fail DeviceChangeEvent interface: existence and properties of interface object
|
||||
Fail DeviceChangeEvent interface object length
|
||||
Fail DeviceChangeEvent interface object name
|
||||
Fail DeviceChangeEvent interface: existence and properties of interface prototype object
|
||||
Fail DeviceChangeEvent interface: existence and properties of interface prototype object's "constructor" property
|
||||
Fail DeviceChangeEvent interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Fail DeviceChangeEvent interface: attribute devices
|
||||
Fail DeviceChangeEvent interface: attribute userInsertedDevices
|
||||
Pass Navigator interface: attribute mediaDevices
|
||||
Pass Navigator interface: navigator must inherit property "mediaDevices" with the proper type
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// GENERATED CONTENT - DO NOT EDIT
|
||||
// Content was automatically extracted by Reffy into webref
|
||||
// (https://github.com/w3c/webref)
|
||||
// Source: Permissions (https://w3c.github.io/permissions/)
|
||||
|
||||
[Exposed=(Window)]
|
||||
partial interface Navigator {
|
||||
[SameObject] readonly attribute Permissions permissions;
|
||||
};
|
||||
|
||||
[Exposed=(Worker)]
|
||||
partial interface WorkerNavigator {
|
||||
[SameObject] readonly attribute Permissions permissions;
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker)]
|
||||
interface Permissions {
|
||||
Promise<PermissionStatus> query(object permissionDesc);
|
||||
};
|
||||
|
||||
dictionary PermissionDescriptor {
|
||||
required DOMString name;
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker)]
|
||||
interface PermissionStatus : EventTarget {
|
||||
readonly attribute PermissionState state;
|
||||
readonly attribute DOMString name;
|
||||
attribute EventHandler onchange;
|
||||
};
|
||||
|
||||
enum PermissionState {
|
||||
"granted",
|
||||
"denied",
|
||||
"prompt",
|
||||
};
|
||||
|
||||
dictionary PermissionSetParameters {
|
||||
required object descriptor;
|
||||
required PermissionState state;
|
||||
};
|
||||
62
Tests/LibWeb/Text/input/wpt-import/interfaces/webidl.idl
Normal file
62
Tests/LibWeb/Text/input/wpt-import/interfaces/webidl.idl
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// GENERATED CONTENT - DO NOT EDIT
|
||||
// Content was automatically extracted by Reffy into webref
|
||||
// (https://github.com/w3c/webref)
|
||||
// Source: Web IDL Standard (https://webidl.spec.whatwg.org/)
|
||||
|
||||
[Exposed=*, Serializable]
|
||||
interface QuotaExceededError : DOMException {
|
||||
constructor(optional DOMString message = "", optional QuotaExceededErrorOptions options = {});
|
||||
|
||||
readonly attribute double? quota;
|
||||
readonly attribute double? requested;
|
||||
};
|
||||
|
||||
dictionary QuotaExceededErrorOptions {
|
||||
double quota;
|
||||
double requested;
|
||||
};
|
||||
|
||||
typedef (Int8Array or Int16Array or Int32Array or
|
||||
Uint8Array or Uint16Array or Uint32Array or Uint8ClampedArray or
|
||||
BigInt64Array or BigUint64Array or
|
||||
Float16Array or Float32Array or Float64Array or DataView) ArrayBufferView;
|
||||
|
||||
typedef (ArrayBufferView or ArrayBuffer) BufferSource;
|
||||
typedef (ArrayBuffer or SharedArrayBuffer or [AllowShared] ArrayBufferView) AllowSharedBufferSource;
|
||||
[Exposed=*,
|
||||
Serializable]
|
||||
interface DOMException { // but see below note about JavaScript binding
|
||||
constructor(optional DOMString message = "", optional DOMString name = "Error");
|
||||
readonly attribute DOMString name;
|
||||
readonly attribute DOMString message;
|
||||
readonly attribute unsigned short code;
|
||||
|
||||
const unsigned short INDEX_SIZE_ERR = 1;
|
||||
const unsigned short DOMSTRING_SIZE_ERR = 2;
|
||||
const unsigned short HIERARCHY_REQUEST_ERR = 3;
|
||||
const unsigned short WRONG_DOCUMENT_ERR = 4;
|
||||
const unsigned short INVALID_CHARACTER_ERR = 5;
|
||||
const unsigned short NO_DATA_ALLOWED_ERR = 6;
|
||||
const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
|
||||
const unsigned short NOT_FOUND_ERR = 8;
|
||||
const unsigned short NOT_SUPPORTED_ERR = 9;
|
||||
const unsigned short INUSE_ATTRIBUTE_ERR = 10;
|
||||
const unsigned short INVALID_STATE_ERR = 11;
|
||||
const unsigned short SYNTAX_ERR = 12;
|
||||
const unsigned short INVALID_MODIFICATION_ERR = 13;
|
||||
const unsigned short NAMESPACE_ERR = 14;
|
||||
const unsigned short INVALID_ACCESS_ERR = 15;
|
||||
const unsigned short VALIDATION_ERR = 16;
|
||||
const unsigned short TYPE_MISMATCH_ERR = 17;
|
||||
const unsigned short SECURITY_ERR = 18;
|
||||
const unsigned short NETWORK_ERR = 19;
|
||||
const unsigned short ABORT_ERR = 20;
|
||||
const unsigned short URL_MISMATCH_ERR = 21;
|
||||
const unsigned short QUOTA_EXCEEDED_ERR = 22;
|
||||
const unsigned short TIMEOUT_ERR = 23;
|
||||
const unsigned short INVALID_NODE_TYPE_ERR = 24;
|
||||
const unsigned short DATA_CLONE_ERR = 25;
|
||||
};
|
||||
|
||||
callback Function = any (any... arguments);
|
||||
callback VoidFunction = undefined ();
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<meta name="timeout" content="long">
|
||||
<script src="../resources/testharness.js"></script>
|
||||
<script src="../resources/testharnessreport.js"></script>
|
||||
<script src="../resources/WebIDLParser.js"></script>
|
||||
<script src="../resources/idlharness.js"></script>
|
||||
<div id=log></div>
|
||||
<script src="../mediacapture-streams/idlharness.https.window.js"></script>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// META: script=/resources/WebIDLParser.js
|
||||
// META: script=/resources/idlharness.js
|
||||
// META: timeout=long
|
||||
|
||||
'use strict';
|
||||
|
||||
// https://w3c.github.io/mediacapture-main/
|
||||
|
||||
idl_test(
|
||||
['mediacapture-streams'],
|
||||
['webidl', 'dom', 'html', 'permissions'],
|
||||
async idl_array => {
|
||||
const inputDevices = [];
|
||||
const outputDevices = [];
|
||||
try {
|
||||
const list = await navigator.mediaDevices.enumerateDevices();
|
||||
for (const device of list) {
|
||||
if (device.kind in self) {
|
||||
continue;
|
||||
}
|
||||
assert_in_array(device.kind, ['audioinput', 'videoinput', 'audiooutput']);
|
||||
self[device.kind] = device;
|
||||
if (device.kind.endsWith('input')) {
|
||||
inputDevices.push(device.kind);
|
||||
} else {
|
||||
outputDevices.push(device.kind);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
self.stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
||||
self.track = stream.getTracks()[0];
|
||||
self.trackEvent = new MediaStreamTrackEvent("type", {
|
||||
track: track,
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
idl_array.add_objects({
|
||||
InputDeviceInfo: inputDevices,
|
||||
MediaStream: ['stream', 'new MediaStream()'],
|
||||
Navigator: ['navigator'],
|
||||
MediaDevices: ['navigator.mediaDevices'],
|
||||
MediaDeviceInfo: outputDevices,
|
||||
MediaStreamTrack: ['track'],
|
||||
MediaStreamTrackEvent: ['trackEvent'],
|
||||
OverconstrainedError: ['new OverconstrainedError("constraint")'],
|
||||
});
|
||||
}
|
||||
);
|
||||
Loading…
Reference in a new issue