Skip to content

Commit dd371f5

Browse files
feat: add readyState binding for media elements (#6843)
Closes #6666 --------- Co-authored-by: Simon H <[email protected]>
1 parent c611f31 commit dd371f5

File tree

9 files changed

+83
-35
lines changed

9 files changed

+83
-35
lines changed

elements/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,7 @@ export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAtt
845845
*/
846846
volume?: number | undefined | null;
847847

848+
readonly 'bind:readyState'?: 0 | 1 | 2 | 3 | 4 | undefined | null;
848849
readonly 'bind:duration'?: number | undefined | null;
849850
readonly 'bind:buffered'?: SvelteMediaTimeRange[] | undefined | null;
850851
readonly 'bind:played'?: SvelteMediaTimeRange[] | undefined | null;

site/content/docs/03-template-syntax.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,14 +714,15 @@ Elements with the `contenteditable` attribute support `innerHTML` and `textConte
714714

715715
---
716716

717-
Media elements (`<audio>` and `<video>`) have their own set of bindings — six *readonly* ones...
717+
Media elements (`<audio>` and `<video>`) have their own set of bindings — seven *readonly* ones...
718718

719719
* `duration` (readonly) — the total duration of the video, in seconds
720720
* `buffered` (readonly) — an array of `{start, end}` objects
721721
* `played` (readonly) — ditto
722722
* `seekable` (readonly) — ditto
723723
* `seeking` (readonly) — boolean
724724
* `ended` (readonly) — boolean
725+
* `readyState` (readonly) — number between (and including) 0 and 4
725726

726727
...and five *two-way* bindings:
727728

@@ -742,6 +743,7 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
742743
bind:seekable
743744
bind:seeking
744745
bind:ended
746+
bind:readyState
745747
bind:currentTime
746748
bind:playbackRate
747749
bind:paused

src/compiler/compile/nodes/Binding.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ const read_only_media_attributes = new Set([
2424
'videoHeight',
2525
'videoWidth',
2626
'naturalWidth',
27-
'naturalHeight'
27+
'naturalHeight',
28+
'readyState'
2829
]);
2930

3031
export default class Binding extends Node {

src/compiler/compile/nodes/Element.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,8 @@ export default class Element extends Node {
980980
name === 'muted' ||
981981
name === 'playbackRate' ||
982982
name === 'seeking' ||
983-
name === 'ended'
983+
name === 'ended' ||
984+
name === 'readyState'
984985
) {
985986
if (this.name !== 'audio' && this.name !== 'video') {
986987
return component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name));

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,11 @@ const events = [
6363
filter: (node: Element, _name: string) =>
6464
node.name === 'input' && node.get_static_attribute_value('type') === 'range'
6565
},
66-
6766
{
6867
event_names: ['elementresize'],
6968
filter: (_node: Element, name: string) =>
7069
regex_dimensions.test(name)
7170
},
72-
7371
// media events
7472
{
7573
event_names: ['timeupdate'],
@@ -131,7 +129,14 @@ const events = [
131129
node.is_media_node() &&
132130
(name === 'videoHeight' || name === 'videoWidth')
133131
},
134-
132+
{
133+
// from https://html.spec.whatwg.org/multipage/media.html#ready-states
134+
// and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
135+
event_names: ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'],
136+
filter: (node: Element, name: string) =>
137+
node.is_media_node() &&
138+
name === 'readyState'
139+
},
135140
// details event
136141
{
137142
event_names: ['toggle'],

test/js/samples/media-bindings/expected.js

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,19 @@ function create_fragment(ctx) {
3030
audio_updating = true;
3131
}
3232

33-
/*audio_timeupdate_handler*/ ctx[13].call(audio);
33+
/*audio_timeupdate_handler*/ ctx[14].call(audio);
3434
}
3535

3636
return {
3737
c() {
3838
audio = element("audio");
39-
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[11].call(audio));
40-
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[12].call(audio));
39+
if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[12].call(audio));
40+
if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[13].call(audio));
4141
if (/*played*/ ctx[2] === void 0 || /*currentTime*/ ctx[3] === void 0 || /*ended*/ ctx[10] === void 0) add_render_callback(audio_timeupdate_handler);
42-
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[14].call(audio));
43-
if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[18].call(audio));
44-
if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[19].call(audio));
42+
if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[15].call(audio));
43+
if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[19].call(audio));
44+
if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[20].call(audio));
45+
if (/*readyState*/ ctx[11] === void 0) add_render_callback(() => /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21].call(audio));
4546
},
4647
m(target, anchor) {
4748
insert(target, audio, anchor);
@@ -58,17 +59,24 @@ function create_fragment(ctx) {
5859

5960
if (!mounted) {
6061
dispose = [
61-
listen(audio, "progress", /*audio_progress_handler*/ ctx[11]),
62-
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[12]),
62+
listen(audio, "progress", /*audio_progress_handler*/ ctx[12]),
63+
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[13]),
6364
listen(audio, "timeupdate", audio_timeupdate_handler),
64-
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[14]),
65-
listen(audio, "play", /*audio_play_pause_handler*/ ctx[15]),
66-
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[15]),
67-
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[16]),
68-
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[17]),
69-
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[18]),
70-
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[18]),
71-
listen(audio, "ended", /*audio_ended_handler*/ ctx[19])
65+
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[15]),
66+
listen(audio, "play", /*audio_play_pause_handler*/ ctx[16]),
67+
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[16]),
68+
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[17]),
69+
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[18]),
70+
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[19]),
71+
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[19]),
72+
listen(audio, "ended", /*audio_ended_handler*/ ctx[20]),
73+
listen(audio, "loadedmetadata", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
74+
listen(audio, "loadeddata", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
75+
listen(audio, "canplay", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
76+
listen(audio, "canplaythrough", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
77+
listen(audio, "playing", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
78+
listen(audio, "waiting", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21]),
79+
listen(audio, "emptied", /*audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[21])
7280
];
7381

7482
mounted = true;
@@ -119,6 +127,7 @@ function instance($$self, $$props, $$invalidate) {
119127
let { playbackRate } = $$props;
120128
let { seeking } = $$props;
121129
let { ended } = $$props;
130+
let { readyState } = $$props;
122131

123132
function audio_progress_handler() {
124133
buffered = time_ranges_to_array(this.buffered);
@@ -173,6 +182,11 @@ function instance($$self, $$props, $$invalidate) {
173182
$$invalidate(10, ended);
174183
}
175184

185+
function audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler() {
186+
readyState = this.readyState;
187+
$$invalidate(11, readyState);
188+
}
189+
176190
$$self.$$set = $$props => {
177191
if ('buffered' in $$props) $$invalidate(0, buffered = $$props.buffered);
178192
if ('seekable' in $$props) $$invalidate(1, seekable = $$props.seekable);
@@ -185,6 +199,7 @@ function instance($$self, $$props, $$invalidate) {
185199
if ('playbackRate' in $$props) $$invalidate(8, playbackRate = $$props.playbackRate);
186200
if ('seeking' in $$props) $$invalidate(9, seeking = $$props.seeking);
187201
if ('ended' in $$props) $$invalidate(10, ended = $$props.ended);
202+
if ('readyState' in $$props) $$invalidate(11, readyState = $$props.readyState);
188203
};
189204

190205
return [
@@ -199,6 +214,7 @@ function instance($$self, $$props, $$invalidate) {
199214
playbackRate,
200215
seeking,
201216
ended,
217+
readyState,
202218
audio_progress_handler,
203219
audio_loadedmetadata_handler,
204220
audio_timeupdate_handler,
@@ -207,7 +223,8 @@ function instance($$self, $$props, $$invalidate) {
207223
audio_volumechange_handler,
208224
audio_ratechange_handler,
209225
audio_seeking_seeked_handler,
210-
audio_ended_handler
226+
audio_ended_handler,
227+
audio_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler
211228
];
212229
}
213230

@@ -226,9 +243,10 @@ class Component extends SvelteComponent {
226243
muted: 7,
227244
playbackRate: 8,
228245
seeking: 9,
229-
ended: 10
246+
ended: 10,
247+
readyState: 11
230248
});
231249
}
232250
}
233251

234-
export default Component;
252+
export default Component;

test/js/samples/media-bindings/input.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
export let playbackRate;
1111
export let seeking;
1212
export let ended;
13+
export let readyState;
1314
</script>
1415

15-
<audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused bind:volume bind:muted bind:playbackRate bind:seeking bind:ended/>
16+
<audio bind:buffered bind:seekable bind:played bind:currentTime bind:duration bind:paused bind:volume bind:muted bind:playbackRate bind:seeking bind:ended bind:readyState/>

test/js/samples/video-bindings/expected.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,31 @@ function create_fragment(ctx) {
3030
video_updating = true;
3131
}
3232

33-
/*video_timeupdate_handler*/ ctx[4].call(video);
33+
/*video_timeupdate_handler*/ ctx[5].call(video);
3434
}
3535

3636
return {
3737
c() {
3838
video = element("video");
39-
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video));
40-
add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video));
39+
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[6].call(video));
40+
add_render_callback(() => /*video_elementresize_handler*/ ctx[7].call(video));
41+
if (/*readyState*/ ctx[4] === void 0) add_render_callback(() => /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8].call(video));
4142
},
4243
m(target, anchor) {
4344
insert(target, video, anchor);
44-
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video));
45+
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[7].bind(video));
4546

4647
if (!mounted) {
4748
dispose = [
4849
listen(video, "timeupdate", video_timeupdate_handler),
49-
listen(video, "resize", /*video_resize_handler*/ ctx[5])
50+
listen(video, "resize", /*video_resize_handler*/ ctx[6]),
51+
listen(video, "loadedmetadata", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
52+
listen(video, "loadeddata", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
53+
listen(video, "canplay", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
54+
listen(video, "canplaythrough", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
55+
listen(video, "playing", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
56+
listen(video, "waiting", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8]),
57+
listen(video, "emptied", /*video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler*/ ctx[8])
5058
];
5159

5260
mounted = true;
@@ -75,6 +83,7 @@ function instance($$self, $$props, $$invalidate) {
7583
let { videoHeight } = $$props;
7684
let { videoWidth } = $$props;
7785
let { offsetWidth } = $$props;
86+
let { readyState } = $$props;
7887

7988
function video_timeupdate_handler() {
8089
currentTime = this.currentTime;
@@ -93,21 +102,29 @@ function instance($$self, $$props, $$invalidate) {
93102
$$invalidate(3, offsetWidth);
94103
}
95104

105+
function video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler() {
106+
readyState = this.readyState;
107+
$$invalidate(4, readyState);
108+
}
109+
96110
$$self.$$set = $$props => {
97111
if ('currentTime' in $$props) $$invalidate(0, currentTime = $$props.currentTime);
98112
if ('videoHeight' in $$props) $$invalidate(1, videoHeight = $$props.videoHeight);
99113
if ('videoWidth' in $$props) $$invalidate(2, videoWidth = $$props.videoWidth);
100114
if ('offsetWidth' in $$props) $$invalidate(3, offsetWidth = $$props.offsetWidth);
115+
if ('readyState' in $$props) $$invalidate(4, readyState = $$props.readyState);
101116
};
102117

103118
return [
104119
currentTime,
105120
videoHeight,
106121
videoWidth,
107122
offsetWidth,
123+
readyState,
108124
video_timeupdate_handler,
109125
video_resize_handler,
110-
video_elementresize_handler
126+
video_elementresize_handler,
127+
video_loadedmetadata_loadeddata_canplay_canplaythrough_playing_waiting_emptied_handler
111128
];
112129
}
113130

@@ -119,9 +136,10 @@ class Component extends SvelteComponent {
119136
currentTime: 0,
120137
videoHeight: 1,
121138
videoWidth: 2,
122-
offsetWidth: 3
139+
offsetWidth: 3,
140+
readyState: 4
123141
});
124142
}
125143
}
126144

127-
export default Component;
145+
export default Component;

test/js/samples/video-bindings/input.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
export let videoHeight;
44
export let videoWidth;
55
export let offsetWidth;
6+
export let readyState;
67
</script>
78

8-
<video bind:currentTime bind:videoHeight bind:videoWidth bind:offsetWidth/>
9+
<video bind:currentTime bind:videoHeight bind:videoWidth bind:offsetWidth bind:readyState/>

0 commit comments

Comments
 (0)