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:
<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 tobind: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 fortimeupdate
events. But these events fire too infrequently, resulting in choppy UI. Svelte does better — it checkscurrentTime
usingrequestAnimationFrame
.
The complete set of bindings for <audio>
and <video>
is as follows — six readonly bindings...
duration
(readonly) — the total duration of the video, in secondsbuffered
(readonly) — an array of{start, end}
objectsseekable
(readonly) — dittoplayed
(readonly) — dittoseeking
(readonly) — booleanended
(readonly) — boolean
...and five two-way bindings:
currentTime
— the current point in the video, in secondsplaybackRate
— how fast to play the video, where1
is 'normal'paused
— this one should be self-explanatoryvolume
— a value between 0 and 1muted
— a boolean value where true is muted
Videos additionally have readonly videoWidth
and videoHeight
bindings.
<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>