-
-
Save byorgey/867942d509ea87c47e5537f9aa346ad8 to your computer and use it in GitHub Desktop.
Responsive YouTube Player and YouTube Timestamp for Roamresearch
This file contains 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
// ==UserScript== | |
// @author Connected Cognition Crumbs <[email protected]> | |
// @description Add timestamp controls to YouTube videos embedded in Roam and makes the player responsive. | |
// @match https://*.roamresearch.com | |
(function() { | |
let ytApiReady = false; | |
const players = new Map(); | |
const activateYtVideos = () => { | |
if (!ytApiReady) { | |
if (window.YT !== undefined) loadYtApi(); // wait until Roam loads its YT, then insert script on top | |
return null; | |
} | |
Array.from(document.getElementsByTagName('IFRAME')) | |
.filter(iframe => iframe.src.includes('youtube.com')) | |
.forEach(ytEl => { | |
const block = ytEl.closest('.roam-block-container'); | |
const rsideBar = ytEl.closest('#right-sidebar'); | |
if(ytEl.src.indexOf("enablejsapi") === -1){ | |
var ytId = extractVideoID(ytEl.src); | |
const parent = ytEl.parentElement; | |
frameId = rsideBar ? (ytId + '-rSide') : ytId; | |
parent.id = frameId; | |
ytEl.remove(); | |
players[frameId] = new window.YT.Player(parent.id, { | |
height: '300', | |
width: '450', | |
videoId: ytId | |
}); | |
wrapIframe(frameId); | |
} else { | |
const begEmbed = ytEl.src.indexOf("enablejsapi"); | |
var ytId = ytEl.src.substring(begEmbed - 12, begEmbed - 1); | |
frameId = rsideBar ? (ytId + '-rSide') : ytId; | |
} | |
addTimestampControls(block, players[frameId]); | |
var sideBarOpen = document.getElementById("right-sidebar").childElementCount - 1; | |
//Make iframes flexible | |
adjustIframe(frameId, sideBarOpen); | |
}); | |
}; | |
const addTimestampControls = (block, player) => { | |
if (block.children.length < 2) return null; | |
//const childBlocks = Array.from(block.children[1].children); | |
const childBlocks = Array.from(block.children[1].querySelectorAll('.roam-block-container')); | |
childBlocks.forEach(child => { | |
const timestamp = getTimestamp(child); | |
//console.log(timestamp) | |
const buttonIfPresent = child.querySelectorAll('.timestamp-control')[0] | |
if (buttonIfPresent) { | |
buttonIfPresent.remove(); | |
} | |
if (timestamp) { | |
addControlButton(child, () => player.seekTo(timestamp, true)); | |
} | |
}); | |
}; | |
const adjustIframe = (frameId, sideBarOpen) => { | |
var child = document.getElementById(frameId); //Iframe | |
var par = child.parentElement; | |
if(sideBarOpen){ | |
child.style.position = 'absolute'; | |
child.style.margin = '0px'; | |
child.style.border = '0px'; | |
child.style.width = '100%'; | |
child.style.height = '100%'; | |
child.style.borderStyle = 'inset'; | |
child.style.borderRadius = '25px'; | |
par.style.position = 'relative'; | |
par.style.paddingBottom = '56.25%'; | |
par.style.height = '0px'; | |
} else { | |
child.style.position = null; | |
child.style.margin = '0px'; | |
child.style.border = '0px'; | |
child.style.width = '600px'; | |
child.style.height = '400px'; | |
child.style.borderStyle = 'inset'; | |
child.style.borderRadius = '25px'; | |
par.style.position = null; | |
par.style.paddingBottom = '0px'; | |
par.style.height = '420px'; | |
} | |
} | |
const wrapIframe = (frameId) => { | |
var child = document.getElementById(frameId); //Iframe | |
var par = document.createElement('div'); | |
child.parentNode.insertBefore(par, child); | |
par.appendChild(child); | |
child.style.position = 'absolute'; | |
child.style.margin = '0px'; | |
child.style.border = '0px'; | |
child.style.width = '100%'; | |
child.style.height = '100%'; | |
par.style.position = 'relative'; | |
par.style.paddingBottom = '56.25%'; | |
par.style.height = '0px'; | |
}; | |
const getControlButton = (block) => block.querySelectorAll('.timestamp-control')[0]; | |
const addControlButton = (block, fn) => { | |
const button = document.createElement('button'); | |
button.innerText = '►'; | |
button.classList.add('timestamp-control'); | |
button.style.borderRadius = '50%'; | |
button.addEventListener('click', fn); | |
const parentEl = block.children[0].children[0]; | |
parentEl.insertBefore(button, parentEl.querySelectorAll('.roam-block')[0]); | |
}; | |
const getTimestamp = (block) => { | |
const innerBlockSelector = block.querySelectorAll('.roam-block'); | |
const blockText = innerBlockSelector.length ? innerBlockSelector[0].textContent : ''; | |
const matches = blockText.match(/^((?:\d+:)?\d+:\d\d)\D/); // start w/ m:ss or h:mm:ss | |
//console.log(matches) | |
if (!matches || matches.length < 2) return null; | |
const timeParts = matches[1].split(':').map(part => parseInt(part)); | |
if (timeParts.length == 3) return timeParts[0]*3600 + timeParts[1]*60 + timeParts[2]; | |
else if (timeParts.length == 2) return timeParts[0]*60 + timeParts[1]; | |
else return null; | |
}; | |
const loadYtApi = () => { | |
const tag = document.createElement('script'); | |
tag.src = 'https://www.youtube.com/iframe_api'; | |
const firstScriptTag = document.getElementsByTagName('script')[0]; | |
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); | |
window.onYouTubeIframeAPIReady = () => { ytApiReady = true; }; | |
}; | |
const extractVideoID = (url) => { | |
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/; | |
var match = url.match(regExp); | |
if ( match && match[7].length == 11 ){ | |
return match[7]; | |
}else{ | |
return null; //alert("Could not extract video ID."); | |
} | |
}; | |
setInterval(activateYtVideos, 1000); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment