Skip to content

Commit 3d0b8cc

Browse files
Zaggy1024gmta
authored andcommitted
LibWeb: Set AudioTrack and VideoTrack fields according to spec
The two classes now inherit from a common base MediaTrackBase, to deduplicate the attributes that are shared between the two. The integer ID from the container is used for each track's id attribute. The kind attribute is set to "main" or "translation" according to: https://dev.w3.org/html5/html-sourcing-inband-tracks/ The label attribute is set to the human-readable name of the track, if one is present. The language attribute is set to a BCP 47 language tag, if one can be parsed successfully.
1 parent 29ab9c5 commit 3d0b8cc

File tree

8 files changed

+148
-87
lines changed

8 files changed

+148
-87
lines changed

Libraries/LibWeb/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ set(SOURCES
568568
HTML/ListOfAvailableImages.cpp
569569
HTML/Location.cpp
570570
HTML/MediaError.cpp
571+
HTML/MediaTrackBase.cpp
571572
HTML/MessageChannel.cpp
572573
HTML/MessageEvent.cpp
573574
HTML/MessagePort.cpp
Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/*
22
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
3+
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
67

7-
#include <AK/IDAllocator.h>
88
#include <LibJS/Runtime/Realm.h>
99
#include <LibJS/Runtime/VM.h>
1010
#include <LibWeb/Bindings/AudioTrackPrototype.h>
@@ -20,36 +20,22 @@ namespace Web::HTML {
2020

2121
GC_DEFINE_ALLOCATOR(AudioTrack);
2222

23-
static IDAllocator s_audio_track_id_allocator;
24-
2523
AudioTrack::AudioTrack(JS::Realm& realm, GC::Ref<HTMLMediaElement> media_element, Media::Track const& track)
26-
: PlatformObject(realm)
27-
, m_media_element(media_element)
28-
, m_track_in_playback_manager(track)
24+
: MediaTrackBase(realm, media_element, track)
2925
{
3026
}
3127

32-
AudioTrack::~AudioTrack()
33-
{
34-
auto id = m_id.to_number<int>();
35-
VERIFY(id.has_value());
36-
37-
s_audio_track_id_allocator.deallocate(id.value());
38-
}
28+
AudioTrack::~AudioTrack() = default;
3929

4030
void AudioTrack::initialize(JS::Realm& realm)
4131
{
4232
WEB_SET_PROTOTYPE_FOR_INTERFACE(AudioTrack);
4333
Base::initialize(realm);
44-
45-
auto id = s_audio_track_id_allocator.allocate();
46-
m_id = String::number(id);
4734
}
4835

4936
void AudioTrack::visit_edges(Cell::Visitor& visitor)
5037
{
5138
Base::visit_edges(visitor);
52-
visitor.visit(m_media_element);
5339
visitor.visit(m_audio_track_list);
5440
}
5541

@@ -66,13 +52,13 @@ void AudioTrack::set_enabled(bool enabled)
6652
// Whenever an audio track in an AudioTrackList that was disabled is enabled, and whenever one that was enabled
6753
// is disabled, the user agent must queue a media element task given the media element to fire an event named
6854
// change at the AudioTrackList object.
69-
m_media_element->queue_a_media_element_task([this]() {
55+
media_element().queue_a_media_element_task([this]() {
7056
m_audio_track_list->dispatch_event(DOM::Event::create(realm(), HTML::EventNames::change));
7157
});
7258
}
7359

7460
m_enabled = enabled;
75-
m_media_element->set_audio_track_enabled({}, this, enabled);
61+
media_element().set_audio_track_enabled({}, this, enabled);
7662
}
7763

7864
}

Libraries/LibWeb/HTML/AudioTrack.h

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,41 @@
11
/*
22
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
3+
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
67

78
#pragma once
89

9-
#include <AK/String.h>
1010
#include <LibMedia/Audio/Forward.h>
1111
#include <LibMedia/Track.h>
1212
#include <LibWeb/Bindings/PlatformObject.h>
13+
#include <LibWeb/HTML/MediaTrackBase.h>
1314

1415
namespace Web::HTML {
1516

16-
class AudioTrack final : public Bindings::PlatformObject {
17-
WEB_PLATFORM_OBJECT(AudioTrack, Bindings::PlatformObject);
17+
class AudioTrack final : public MediaTrackBase {
18+
WEB_PLATFORM_OBJECT(AudioTrack, MediaTrackBase);
1819
GC_DECLARE_ALLOCATOR(AudioTrack);
1920

2021
public:
2122
virtual ~AudioTrack() override;
2223

2324
void set_audio_track_list(Badge<AudioTrackList>, GC::Ptr<AudioTrackList> audio_track_list) { m_audio_track_list = audio_track_list; }
2425

25-
String const& id() const { return m_id; }
26-
String const& kind() const { return m_kind; }
27-
String const& label() const { return m_label; }
28-
String const& language() const { return m_language; }
29-
3026
bool enabled() const { return m_enabled; }
3127
void set_enabled(bool enabled);
3228

33-
Media::Track const& track_in_playback_manager() const { return m_track_in_playback_manager; }
34-
3529
private:
3630
AudioTrack(JS::Realm&, GC::Ref<HTMLMediaElement>, Media::Track const&);
3731

3832
virtual void initialize(JS::Realm&) override;
3933
virtual void visit_edges(Cell::Visitor&) override;
4034

41-
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-id
42-
String m_id;
43-
44-
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-kind
45-
String m_kind;
46-
47-
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-label
48-
String m_label;
49-
50-
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-language
51-
String m_language;
52-
5335
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-enabled
5436
bool m_enabled { false };
5537

56-
GC::Ref<HTMLMediaElement> m_media_element;
5738
GC::Ptr<AudioTrackList> m_audio_track_list;
58-
59-
Media::Track m_track_in_playback_manager;
6039
};
6140

6241
}

Libraries/LibWeb/HTML/HTMLMediaElement.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,18 @@ WebIDL::ExceptionOr<void> HTMLMediaElement::process_media_data(Function<void(Str
12371237
if (enable == TriState::True)
12381238
audio_track->set_enabled(true);
12391239

1240+
// AD-HOC(ish): According to https://dev.w3.org/html5/html-sourcing-inband-tracks/, kind should be set according to format, and the following criteria within
1241+
// the specified formats.
1242+
// WebM:
1243+
// - "main": the FlagDefault element is set on the track
1244+
// - "translation": not first audio (video) track
1245+
// MP4:
1246+
// - "main": first audio (video) track
1247+
// - "translation": not first audio (video) track
1248+
// Though the behavior for WebM is not clear if its first track is not marked with FlagDefault, the idea here seems to be that the preferred
1249+
// track should be marked as "main", and the rest should be marked as "translation".
1250+
audio_track->set_kind(enable == TriState::True ? "main"_utf16 : "translation"_utf16);
1251+
12401252
// 7. Fire an event named addtrack at this AudioTrackList object, using TrackEvent, with the track attribute initialized to the new AudioTrack object.
12411253
TrackEventInit event_init {};
12421254
event_init.track = GC::make_root(audio_track);
@@ -1284,6 +1296,9 @@ WebIDL::ExceptionOr<void> HTMLMediaElement::process_media_data(Function<void(Str
12841296
if (enable == TriState::True)
12851297
video_track->set_selected(true);
12861298

1299+
// AD-HOC(ish): See the comment regarding AudioTrack.kind above with regard to https://dev.w3.org/html5/html-sourcing-inband-tracks/.
1300+
video_track->set_kind(enable == TriState::True ? "main"_utf16 : "translation"_utf16);
1301+
12871302
// 7. Fire an event named addtrack at this VideoTrackList object, using TrackEvent, with the track attribute initialized to the new VideoTrack object.
12881303
TrackEventInit event_init {};
12891304
event_init.track = GC::make_root(video_track);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <LibUnicode/Locale.h>
8+
#include <LibWeb/HTML/HTMLMediaElement.h>
9+
#include <LibWeb/HTML/MediaTrackBase.h>
10+
11+
namespace Web::HTML {
12+
13+
MediaTrackBase::MediaTrackBase(JS::Realm& realm, GC::Ref<HTMLMediaElement> media_element, Media::Track const& track)
14+
: PlatformObject(realm)
15+
, m_media_element(media_element)
16+
, m_track_in_playback_manager(track)
17+
, m_id(Utf16String::number(track.identifier()))
18+
, m_label(track.name())
19+
{
20+
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-language
21+
// https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-language
22+
// The AudioTrack language and VideoTrack language attributes must return the BCP 47 language tag of the language
23+
// of the track, if it has one, or the empty string otherwise. If the user agent is not able to express that language
24+
// as a BCP 47 language tag (for example because the language information in the media resource's format is a free-form
25+
// string without a defined interpretation), then the method must return the empty string, as if the track had no
26+
// language.
27+
m_language = [&] {
28+
auto locale = Unicode::parse_unicode_locale_id(track.language().to_utf8());
29+
if (!locale.has_value())
30+
return Utf16String();
31+
auto language = locale->to_string();
32+
// NOTE: We specifically want to exclude "und" here, as RFC 5646 says:
33+
//
34+
// The 'und' (Undetermined) primary language subtag identifies linguistic content whose language is not
35+
// determined. This subtag SHOULD NOT be used unless a language tag is required and language information is
36+
// not available or cannot be determined. Omitting the language tag (where permitted) is preferred. The 'und'
37+
// subtag might be useful for protocols that require a language tag to be provided or where a primary language
38+
// subtag is required (such as in "und-Latn"). The 'und' subtag MAY also be useful when matching language tags
39+
// in certain situations.
40+
//
41+
// Matroska's TrackEntry->Language element is required, and will use "und" as a placeholder as mentioned above. We
42+
// don't want to return anything when that placeholder is found:
43+
if (language == "und")
44+
return Utf16String();
45+
return Utf16String::from_utf8_without_validation(language);
46+
}();
47+
}
48+
49+
MediaTrackBase::~MediaTrackBase() = default;
50+
51+
void MediaTrackBase::visit_edges(Cell::Visitor& visitor)
52+
{
53+
Base::visit_edges(visitor);
54+
visitor.visit(m_media_element);
55+
}
56+
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <LibMedia/Track.h>
10+
#include <LibWeb/Bindings/PlatformObject.h>
11+
12+
namespace Web::HTML {
13+
14+
class MediaTrackBase : public Bindings::PlatformObject {
15+
WEB_PLATFORM_OBJECT(MediaTrackBase, Bindings::PlatformObject);
16+
17+
public:
18+
virtual ~MediaTrackBase() override;
19+
20+
HTMLMediaElement& media_element() const { return *m_media_element; }
21+
22+
Media::Track const& track_in_playback_manager() const { return m_track_in_playback_manager; }
23+
24+
Utf16String const& id() const { return m_id; }
25+
Utf16String const& kind() const { return m_kind; }
26+
void set_kind(Utf16String const& kind) { m_kind = kind; }
27+
Utf16String const& label() const { return m_label; }
28+
Utf16String const& language() const { return m_language; }
29+
30+
protected:
31+
MediaTrackBase(JS::Realm&, GC::Ref<HTMLMediaElement>, Media::Track const&);
32+
33+
virtual void visit_edges(Cell::Visitor&) override;
34+
35+
private:
36+
GC::Ref<HTMLMediaElement> m_media_element;
37+
38+
Media::Track m_track_in_playback_manager;
39+
40+
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-id
41+
// https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-id
42+
Utf16String m_id;
43+
44+
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-kind
45+
// https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-kind
46+
Utf16String m_kind;
47+
48+
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-label
49+
// https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-label
50+
Utf16String m_label;
51+
52+
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-language
53+
// https://html.spec.whatwg.org/multipage/media.html#dom-videotrack-language
54+
Utf16String m_language;
55+
};
56+
57+
}

Libraries/LibWeb/HTML/VideoTrack.cpp

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/*
22
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
3+
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
67

7-
#include <AK/IDAllocator.h>
88
#include <AK/Time.h>
99
#include <LibJS/Runtime/Realm.h>
1010
#include <LibJS/Runtime/VM.h>
@@ -20,36 +20,22 @@ namespace Web::HTML {
2020

2121
GC_DEFINE_ALLOCATOR(VideoTrack);
2222

23-
static IDAllocator s_video_track_id_allocator;
24-
2523
VideoTrack::VideoTrack(JS::Realm& realm, GC::Ref<HTMLMediaElement> media_element, Media::Track const& track)
26-
: PlatformObject(realm)
27-
, m_media_element(media_element)
28-
, m_track_in_playback_manager(track)
24+
: MediaTrackBase(realm, media_element, track)
2925
{
3026
}
3127

32-
VideoTrack::~VideoTrack()
33-
{
34-
auto id = m_id.to_number<int>();
35-
VERIFY(id.has_value());
36-
37-
s_video_track_id_allocator.deallocate(id.value());
38-
}
28+
VideoTrack::~VideoTrack() = default;
3929

4030
void VideoTrack::initialize(JS::Realm& realm)
4131
{
4232
WEB_SET_PROTOTYPE_FOR_INTERFACE(VideoTrack);
4333
Base::initialize(realm);
44-
45-
auto id = s_video_track_id_allocator.allocate();
46-
m_id = String::number(id);
4734
}
4835

4936
void VideoTrack::visit_edges(Cell::Visitor& visitor)
5037
{
5138
Base::visit_edges(visitor);
52-
visitor.visit(m_media_element);
5339
visitor.visit(m_video_track_list);
5440
}
5541

@@ -77,7 +63,7 @@ void VideoTrack::set_selected(bool selected)
7763
auto selected_track_was_unselected_without_another_selection = m_selected && !selected;
7864

7965
if (previously_unselected_track_is_selected || selected_track_was_unselected_without_another_selection) {
80-
m_media_element->queue_a_media_element_task([this]() {
66+
media_element().queue_a_media_element_task([this]() {
8167
m_video_track_list->dispatch_event(DOM::Event::create(realm(), HTML::EventNames::change));
8268
});
8369
}
@@ -86,7 +72,7 @@ void VideoTrack::set_selected(bool selected)
8672
m_selected = selected;
8773

8874
// AD-HOC: Inform the element node that we have (un)selected a video track for layout.
89-
m_media_element->set_selected_video_track({}, m_selected ? this : nullptr);
75+
media_element().set_selected_video_track({}, m_selected ? this : nullptr);
9076
}
9177

9278
}

0 commit comments

Comments
 (0)