Created
February 4, 2023 21:55
-
-
Save headquarters/b595e718753d7d75708e4bd662706208 to your computer and use it in GitHub Desktop.
Bookmarklet: adjust video playback speed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Bookmarklet: | |
javascript:(function()%7Bconst%20template%20%3D%20document.createElement(%22template%22)%3B%0Atemplate.innerHTML%20%3D%20%60%0A%20%20%20%20%20%20%20%20%3Cstyle%3E%0A%20%20%20%20%20%20%20%20%20%20%3Ahost%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20box-sizing%3A%20border-box%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20padding%3A%200.5rem%201rem%201.5rem%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20320px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border%3A%202px%20solid%20darkgray%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20box-shadow%3A%20grey%200%200%204px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20background-color%3A%20white%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20right%3A%2010px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20top%3A%2010px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20z-index%3A%2010000%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-family%3A%20sans-serif%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20h1%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-size%3A%201.2rem%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20label%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20align-items%3A%20center%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20input%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20margin-left%3A%205px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20margin-right%3A%205px%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%3C%2Fstyle%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20id%3D%22content%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Ch1%3EAdjust%20Video%20Playback%20Rate%3C%2Fh1%3E%0A%20%20%20%20%20%20%20%20%20%20%3Csection%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20Searching%20for%20a%20%3Ccode%3E%26lt%3Bvideo%20%2F%26gt%3B%3C%2Fcode%3E%20tag...%0A%20%20%20%20%20%20%20%20%20%20%3C%2Fsection%3E%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%60%3B%0A%0Aconst%20playbackRateContent%20%3D%20%60%3Clabel%3E%0A%20%20Playback%20rate%3A%0A%20%20%3Cinput%20type%3D%22range%22%20id%3D%22playback-range%22%20name%3D%22volume%22%20list%3D%22values%22%20min%3D%220.25%22%20max%3D%222%22%20step%3D%220.25%22%3E%0A%20%20(%3Cspan%20id%3D%22currentrate%22%3E%3C%2Fspan%3E)%0A%3C%2Flabel%3E%60%3B%0A%0A%2F%2F%20TODO%3A%20add%20close%20button%2C%20make%20sure%20opening%20bookmarklet%20repeatedly%20doesn't%20break%3B%0Aclass%20VideoPlaybackControls%20extends%20HTMLElement%20%7B%0A%20%20constructor()%20%7B%0A%20%20%20%20super()%3B%0A%20%20%20%20this.attachShadow(%7B%20mode%3A%20%22open%22%20%7D)%3B%0A%0A%20%20%20%20this.update%20%3D%20this.update.bind(this)%3B%0A%20%20%7D%0A%0A%20%20%23videoplayer%20%3D%20null%3B%0A%0A%20%20%23loading%20%3D%20true%3B%0A%0A%20%20get%20videoplayer()%20%7B%0A%20%20%20%20return%20this.%23videoplayer%3B%0A%20%20%7D%0A%0A%20%20set%20videoplayer(v)%20%7B%0A%20%20%20%20this.%23videoplayer%20%3D%20v%3B%0A%20%20%20%20this.render()%3B%0A%20%20%7D%0A%0A%20%20get%20loading()%20%7B%0A%20%20%20%20return%20this.%23loading%3B%0A%20%20%7D%0A%0A%20%20set%20loading(l)%20%7B%0A%20%20%20%20this.%23loading%20%3D%20l%3B%0A%20%20%20%20this.render()%3B%0A%20%20%7D%0A%0A%20%20update(event)%20%7B%0A%20%20%20%20const%20adjustedRate%20%3D%20event.target.value%3B%0A%20%20%20%20this.shadowRoot.getElementById(%22currentrate%22).textContent%20%3D%20adjustedRate%3B%0A%20%20%20%20this.videoplayer.playbackRate%20%3D%20adjustedRate%3B%0A%20%20%7D%0A%0A%20%20render()%20%7B%0A%20%20%20%20if%20(this.loading)%20%7B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(!this.videoplayer)%20%7B%0A%20%20%20%20%20%20this.shadowRoot.querySelector(%0A%20%20%20%20%20%20%20%20%22section%22%0A%20%20%20%20%20%20).innerHTML%20%3D%20%60No%20%3Ccode%3E%26lt%3Bvideo%20%2F%26gt%3B%3C%2Fcode%3E%20elements%20found%20on%20this%20page.%60%3B%0A%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20this.shadowRoot.querySelector(%22section%22).innerHTML%20%3D%20playbackRateContent%3B%0A%0A%20%20%20%20this.currentRate%20%3D%20this.videoplayer%3F.playbackRate%3B%0A%0A%20%20%20%20this.shadowRoot.getElementById(%22currentrate%22).textContent%20%3D%0A%20%20%20%20%20%20this.currentRate%3B%0A%0A%20%20%20%20this.shadowRoot%0A%20%20%20%20%20%20.getElementById(%22playback-range%22)%0A%20%20%20%20%20%20.addEventListener(%22input%22%2C%20this.update)%3B%0A%20%20%7D%0A%0A%20%20connectedCallback()%20%7B%0A%20%20%20%20this.shadowRoot.appendChild(template.content.cloneNode(true))%3B%0A%20%20%20%20%2F%2F%20First%2C%20check%20main%20window%20for%20video%20element%0A%20%20%20%20this.videoplayer%20%3D%20document.querySelector(%22video%22)%3B%0A%0A%20%20%20%20if%20(this.videoplayer)%20%7B%0A%20%20%20%20%20%20this.loading%20%3D%20false%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(!this.videoplayer%20%26%26%20window.frames.length)%20%7B%0A%20%20%20%20%20%20%2F%2F%20No%20video%20in%20main%20window%2C%20but%20there%20is%20an%20iframe.%0A%20%20%20%20%20%20%2F%2F%20Check%20it%20for%20a%20video%20element%20when%20it%20finishes%20loading.%0A%0A%20%20%20%20%20%20%2F%2F%20Is%20the%20iframe%20already%20loaded%3F%20Naive%20check%20of%20body's%20children%20count.%0A%20%20%20%20%20%20if%20(window.frames%5B0%5D.document.body.children.length%20%3E%200)%20%7B%0A%20%20%20%20%20%20%20%20this.loading%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20this.videoplayer%20%3D%20window.frames%5B0%5D.document.querySelector(%22video%22)%3B%0A%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20window.frames%5B0%5D.onload%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20this.loading%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20%20%20this.videoplayer%20%3D%20window.frames%5B0%5D.document.querySelector(%22video%22)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F%2F%20No%20video%20found%0A%20%20%20%20this.loading%20%3D%20false%3B%0A%20%20%7D%0A%0A%20%20disconnectedCallback()%20%7B%7D%0A%7D%0A%0A%2F%2F%20If%20first%20time%20running%2C%20this%20custom%20element%20will%20not%20be%20defined%0Aif%20(!customElements.get(%22video-playback-controls%22))%20%7B%0A%20%20customElements.define(%22video-playback-controls%22%2C%20VideoPlaybackControls)%3B%0A%7D%0A%0A%2F%2F%20Remove%20any%20existing%20element%20to%20replace%20with%20a%20new%20instance%0Adocument.querySelector(%22video-playback-controls%22)%3F.remove()%3B%0A%0Aconst%20v%20%3D%20document.createElement(%22video-playback-controls%22)%3B%0Adocument.body.appendChild(v)%3B%7D)()%3B | |
// Source: | |
const template = document.createElement("template"); | |
template.innerHTML = ` | |
<style> | |
:host { | |
box-sizing: border-box; | |
padding: 0.5rem 1rem 1.5rem; | |
width: 320px; | |
border: 2px solid darkgray; | |
box-shadow: grey 0 0 4px; | |
background-color: white; | |
position: absolute; | |
right: 10px; | |
top: 10px; | |
z-index: 10000; | |
font-family: sans-serif; | |
} | |
h1 { | |
font-size: 1.2rem; | |
} | |
label { | |
display: flex; | |
align-items: center; | |
} | |
input { | |
margin-left: 5px; | |
margin-right: 5px; | |
} | |
</style> | |
<div id="content"> | |
<h1>Adjust Video Playback Rate</h1> | |
<section> | |
Searching for a <code><video /></code> tag... | |
</section> | |
</div>`; | |
const playbackRateContent = `<label> | |
Playback rate: | |
<input type="range" id="playback-range" name="volume" list="values" min="0.25" max="2" step="0.25"> | |
(<span id="currentrate"></span>) | |
</label>`; | |
// TODO: add close button, make sure opening bookmarklet repeatedly doesn't break; | |
class VideoPlaybackControls extends HTMLElement { | |
constructor() { | |
super(); | |
this.attachShadow({ mode: "open" }); | |
this.update = this.update.bind(this); | |
} | |
#videoplayer = null; | |
#loading = true; | |
get videoplayer() { | |
return this.#videoplayer; | |
} | |
set videoplayer(v) { | |
this.#videoplayer = v; | |
this.render(); | |
} | |
get loading() { | |
return this.#loading; | |
} | |
set loading(l) { | |
this.#loading = l; | |
this.render(); | |
} | |
update(event) { | |
const adjustedRate = event.target.value; | |
this.shadowRoot.getElementById("currentrate").textContent = adjustedRate; | |
this.videoplayer.playbackRate = adjustedRate; | |
} | |
render() { | |
if (this.loading) { | |
return; | |
} | |
if (!this.videoplayer) { | |
this.shadowRoot.querySelector( | |
"section" | |
).innerHTML = `No <code><video /></code> elements found on this page.`; | |
return; | |
} | |
this.shadowRoot.querySelector("section").innerHTML = playbackRateContent; | |
this.currentRate = this.videoplayer?.playbackRate; | |
this.shadowRoot.getElementById("currentrate").textContent = | |
this.currentRate; | |
this.shadowRoot | |
.getElementById("playback-range") | |
.addEventListener("input", this.update); | |
} | |
connectedCallback() { | |
this.shadowRoot.appendChild(template.content.cloneNode(true)); | |
// First, check main window for video element | |
this.videoplayer = document.querySelector("video"); | |
if (this.videoplayer) { | |
this.loading = false; | |
return; | |
} | |
if (!this.videoplayer && window.frames.length) { | |
// No video in main window, but there is an iframe. | |
// Check it for a video element when it finishes loading. | |
// Is the iframe already loaded? Naive check of body's children count. | |
if (window.frames[0].document.body.children.length > 0) { | |
this.loading = false; | |
this.videoplayer = window.frames[0].document.querySelector("video"); | |
} else { | |
window.frames[0].onload = () => { | |
this.loading = false; | |
this.videoplayer = window.frames[0].document.querySelector("video"); | |
}; | |
} | |
return; | |
} | |
// No video found | |
this.loading = false; | |
} | |
disconnectedCallback() {} | |
} | |
// If first time running, this custom element will not be defined | |
if (!customElements.get("video-playback-controls")) { | |
customElements.define("video-playback-controls", VideoPlaybackControls); | |
} | |
// Remove any existing element to replace with a new instance | |
document.querySelector("video-playback-controls")?.remove(); | |
const v = document.createElement("video-playback-controls"); | |
document.body.appendChild(v); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Adjust playback speed of a
<video />
tag on the page, even if the element does not expose that in the UI.