Skip to main content

Part 3 / Advanced bindings / Media elements

This exercise doesn't currently work. You can switch to the old tutorial instead: https://svelte.dev/tutorial/media-elements

The <audio> and <video> elements have several properties that you can bind to. This example demonstrates a few of them.

On line 62, add currentTime={time}, duration and paused bindings:

App.svelte
<video
	poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
	src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
	on:mousemove={handleMove}
	on:touchmove|preventDefault={handleMove}
	on:mousedown={handleMousedown}
	on:mouseup={handleMouseup}
	bind:currentTime={time}
	bind:duration
	bind:paused>
	<track kind="captions">
</video>

bind:duration is equivalent to bind:duration={duration}

Now, when you click on the video, it will update time, duration and paused as appropriate. This means we can use them to build custom controls.

Ordinarily on the web, you would track currentTime by listening for timeupdate events. But these events fire too infrequently, resulting in choppy UI. Svelte does better — it checks currentTime using requestAnimationFrame.

The complete set of bindings for <audio> and <video> is as follows — six readonly bindings...

  • duration (readonly) — the total duration of the video, in seconds
  • buffered (readonly) — an array of {start, end} objects
  • seekable (readonly) — ditto
  • played (readonly) — ditto
  • seeking (readonly) — boolean
  • ended (readonly) — boolean

...and five two-way bindings:

  • currentTime — the current point in the video, in seconds
  • playbackRate — how fast to play the video, where 1 is 'normal'
  • paused — this one should be self-explanatory
  • volume — a value between 0 and 1
  • muted — a boolean value where true is muted

Videos additionally have readonly videoWidth and videoHeight bindings.

Next: Dimensions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<script>
	// These values are bound to properties of the video
	let time = 0;
	let duration;
	let paused = true;
 
	let showControls = true;
	let showControlsTimeout;
 
	// Used to track time of last mouse down event
	let lastMouseDown;
 
	function handleMove(e) {
		// Make the controls visible, but fade out after
		// 2.5 seconds of inactivity
		clearTimeout(showControlsTimeout);
		showControlsTimeout = setTimeout(
			() => (showControls = false),
			2500
		);
		showControls = true;
 
		if (!duration) return; // video not loaded yet
		if (
			e.type !== 'touchmove' &&
			!(e.buttons & 1)
		)
			return; // mouse not down
 
		const clientX =
			e.type === 'touchmove'
				? e.touches[0].clientX
				: e.clientX;
		const { left, right } =
			this.getBoundingClientRect();
		time =
			(duration * (clientX - left)) /
			(right - left);
	}
 
	// we can't rely on the built-in click event, because it fires
	// after a drag — we have to listen for clicks ourselves
	function handleMousedown(e) {
		lastMouseDown = new Date();
	}
 
	function handleMouseup(e) {
		if (new Date() - lastMouseDown < 300) {
			if (paused) e.target.play();
			else e.target.pause();
		}
	}
 
	function format(seconds) {
		if (isNaN(seconds)) return '...';
 
		const minutes = Math.floor(seconds / 60);
		seconds = Math.floor(seconds % 60);
		if (seconds < 10) seconds = '0' + seconds;
 
		return `${minutes}:${seconds}`;
	}
</script>
 
<h1>Caminandes: Llamigos</h1>
<p>
	From <a
		href="https://cloud.blender.org/open-projects"
		>Blender Open Projects</a
	>. CC-BY
</p>
 
<div>
	<video
		poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
		src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
		on:mousemove={handleMove}
		on:touchmove|preventDefault={handleMove}
		on:mousedown={handleMousedown}
		on:mouseup={handleMouseup}
	>
		<track kind="captions" />
	</video>
 
	<div
		class="controls"
		style="opacity: {duration && showControls
			? 1
			: 0}"
	>
		<progress value={time / duration || 0} />
 
		<div class="info">
			<span class="time">{format(time)}</span>
			<span
				>click anywhere to {paused
					? 'play'
					: 'pause'} / drag to seek</span
			>
			<span class="time">{format(duration)}</span>
		</div>
	</div>
</div>
 
<style>
	div {
		position: relative;
	}
 
	.controls {
		position: absolute;
		top: 0;
		width: 100%;
		transition: opacity 1s;
	}
 
	.info {
		display: flex;
		width: 100%;
		justify-content: space-between;
	}
 
	span {
		padding: 0.2em 0.5em;
		color: white;
		text-shadow: 0 0 8px black;
		font-size: 1.4em;
		opacity: 0.7;
	}
 
	.time {
		width: 3em;
	}
 
	.time:last-child {
		text-align: right;
	}
 
	progress {
		display: block;
		width: 100%;
		height: 10px;
		-webkit-appearance: none;
		appearance: none;
	}
 
	progress::-webkit-progress-bar {
		background-color: rgba(0, 0, 0, 0.2);
	}
 
	progress::-webkit-progress-value {
		background-color: rgba(255, 255, 255, 0.6);
	}
 
	video {
		width: 100%;
	}
</style>
 
initialising