Tracker-free youtube embedding
Posted 2022-04-03 18:04. Tagged web, kakor, meta, rust, gdpr, r4s.
Sometimes I want to embed a video on my site. Most of the videos I want to embed are on youtube. Klicking “share” on a video and choosing embed, I get a bunch of html code I can copy into a post. Something like this, for example:
That’s very easy. The downside is that youtube gets a request for the iframe (and a bunch of stuff it loads from there) every time someone looks at my page, which makes it possible for them to track you, my reader, across my site and other sites that includes videos.
So, what can I do?
I can replace the iframe
with a “play” button, and make that button
replace itself with the iframe
when pressed.
Like this:
Play
Now, the browser will not talk to youtube unless the reader presses
the Play button.
I can also make the div
contain the title of the video, a notice that
pressing play will send data to youtube, and even style it with a
preview of the video as a background image.
Read on for all the details of how to do that!
I don’t want to put all that (and more) in the markdown source for every blog post where I embed a video. I prefer something like this:
Since I have a program that reads my markdown and converts it to html for the web server, and I already have the code block syntax extended for leaflet maps and qr-codes, adding a syntax for embedding stuff is no big deal.
One thing youtube does nicely is that they support the
oembed api. So I can query
https://www.youtube.com/oembed?url=https://youtu.be/eqWUJ0zAPqc&format=json
for the video I want to embed and get something like this:
This gives me not only the html code to actually embed the video, but also the title and a thumbnail image that I can use as a placeholder, and the width and height of the video so I can get the proper aspect ratio for it.
Now, if I just put that thumbnail_url
in an img
tag (or a
background-image
css property), that kind of defeats the purpose of
not accessing youtube unless the reader presses play.
But I can fetch the image and then serve it from my own server.
So now I have a nice looking preview, and until the reader presses
play, everything is loaded from my site without tracking.
When the user does press play, the browser replaces my preview with an
embedded video … that has a play button to start it.
That won’t do, the reader already pressed play!
Luckily, I can fix that with a little search-and-replace in the
iframe
html code.
I need to add an autoplay=1
query parameter to the src
attribute.
Luckily, the oembed reponse gives me a feature=oembed
query argument
that’s not really needed, so I can just search and replace that.
Rust code
The first thing I need is a struct to get the information I want from the oembed api:
/// The interesting parts of an oembed response.
The Debug
derive is just for trying things out, and possibly for
error handling.
The Deserialize
comes from serde and makes it possible to
get a json response parsed directly from a reqwest response
into the struct, like this:
let embed: EmbedData = client
.get
.query
.send?
.error_for_status?
.json?;
The struct only contains the fields I care about (the extra fields in
the json example above are ignored).
If youtube would change the api so one of the fields I want is missing
(or if I would make a typo in the struct declaration), the question
mark after .json()
would return an error.
Next, I download an image for the preview.
I noticed that the image named in the json data is always named
hqdefault.jpg
, but some videos have a preview in better resolution
named maxresdefault.jpg
, so I check for that first, and if that is
not found, I try the given url.
let img = embed.thumbnail_url;
let img = fetch_content
.or_else?;
let img = loader.store_asset?;
Both fetch_content
and store_asset
are helper functions.
The first takes a client and a url, and gives the
content-type and body data on success.
The second takes a year, a file name, a content-type and some content,
stores that in my assets table and returns a local url for it.
After that, all that remains is writing out the html code:
writeln!?;
The quote replacement on embed.html
is to make it fit in a
javascript string.
The other replacement tells youtube to autoplay the video.
Normally, that is a very bad idea, but in this case, the youtube code
will only execute after the reader presses play, so the autoplay is
really a regular play.
Markup and styling
The generated markup (before pressing play) looks like the following.
I think a figure
is probably a semantically apropriate element, both
for the preview and for wrapping the iframe
when video is loaded.
\
…
⏵ Play
…
The style
attribute may feel a bit off, but the aspect ratio is
different for each video, so it needs to be in the markup, and I see
no real benefit in putting it in a data-aspect
attribute and having
javascript make a style from it.
The width
and height
attributes on the img
tag is not the real
width and height of the preview image, but rather the width and height
of the video in lowest-available resolution.
I don’t think that is a problem either.
As for styling, there is quite a lot of it, but the important parts are as follows:
}
}
}
}
}
The wrapper is a positioning context (by having position: relative),
on which the figcaption and the div is absolutley positioned.
The wrapper has zero height, but a padding-bottom that is a percentage.
Since that percentage is evaluated as percent of the box width, that
is a good way to specify aspect ratio.
The image covers the entire figure
, and the other stuff is placed
above it (by z-index for the caption and by just appearing later in
the markup for the rest).
Example
Do you want to hear some music? If you don’t press play, you can read this post without youtube having any possibility of tracking you. If you do press play, youtube can track you, but at least then you get to hear a nice tune by Väsen.
Feel free to inspect the html and css before (and after) pressing play.
Comments
Write a comment