Stepping one frame at a time when the browser will not say what a frame is
Saving a still from a video needs no video engine. To show the video at all, the browser has already decoded the frame on screen, and a canvas drawn at the video’s own width and height copies that frame pixel for pixel. This is why the page works the moment a video loads, with nothing to download, and why the saved image is the video’s full resolution rather than the size of the player: a 4K recording gives a 3840 by 2160 image even when the player on the page is a few hundred pixels wide. PNG keeps those pixels exactly. JPEG throws a little away for a file several times smaller.
The hard part is landing on the exact frame. A video element lets a page set a time but not ask for a frame, and it does not report the frame rate, which in a phone or screen recording can change from second to second anyway. Browsers do offer a callback that fires when a frame is shown and says which one, and the first plan was to use it. It was measured first. In Chromium, seeking a 15 frames per second clip to 1.01, 1.02 and 1.05 seconds reported the frame shown as starting at 1.0 each time, and 1.07 reported 1.067, which is exactly the next frame. In Firefox the same seeks reported 1.01, 1.02 and 1.05: it echoes the time that was asked for, so it cannot tell two different frames from the same frame shown twice. Building on the callback would have made stepping exact in one browser and silently wrong in another.
So the page compares pictures instead. When you press Next frame it draws the current frame into a tiny 64 by 36 canvas and then searches forward for the nearest moment that looks different. A frame decoded twice is decoded identically, so any changed pixel means the video has moved past the frame you were on, and from that point on every later moment differs too. That makes a fast search safe: the page seeks a 240th of a second ahead, then twice as far, then four times, until the picture changes, and then halves the gap back and forth until the last identical moment and the first different one are a 240th of a second apart. The different side of that gap is always inside the very next frame, however far the doubling overshot, because no real frame is shorter than the gap. The first version stepped a 240th of a second at a time instead, and in Firefox, where a seek takes about 50 milliseconds, that needed 16 seeks for every frame of a 15 frames per second clip; the search needs about 8, and crosses a two second frame in a slideshow video that the step-by-step version gave up on. Previous frame searches backwards the same way. The browser tests step five frames forward and five back through a clip that changes every frame, checking the frame after every press in both Chromium and Firefox, and the search itself is tested against variable frame rate timelines where a 16 millisecond frame follows a two second one.
Two consequences are worth knowing. Consecutive frames that really are identical, such as a paused screen recording, are stepped over as one, which for picking a still is the same picture anyway. And a video can only be read if this browser can play it, because the frames come from its own player. MP4 with H.264 plays everywhere, while some iPhone HEVC and MOV files play only in Safari, and the page says so when a file will not load instead of showing an empty player. The saved frame is the picture the browser shows, colours included, and browsers do not always agree on those: a video that does not state its colour standard was saved by Chromium matching the older BT.601 conversion to within 0.75 of 255 per channel and by Firefox matching BT.709 to within 1.02, the same frame in both, a few shades apart. Copy image uses the clipboard’s PNG format, which is the one image type every browser that allows copying images accepts, and nothing about the video ever leaves the tab.