/* * Copyright (c) 2026, The Ladybird developers * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include namespace Web::MediaCapture { GC_DEFINE_ALLOCATOR(MediaStream); MediaStream::MediaStream(JS::Realm& realm) : DOM::EventTarget(realm) { } GC::Ref MediaStream::create(JS::Realm& realm) { auto stream = realm.create(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::construct_impl(JS::Realm& realm, GC::RootVector> 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> 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> 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> 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> 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> 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 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 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 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::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); } }