Skip to content

Instantly share code, notes, and snippets.

@byorgey
Forked from c3founder/Play-Button.cs
Last active November 20, 2020 00:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save byorgey/867942d509ea87c47e5537f9aa346ad8 to your computer and use it in GitHub Desktop.
Save byorgey/867942d509ea87c47e5537f9aa346ad8 to your computer and use it in GitHub Desktop.
Responsive YouTube Player and YouTube Timestamp for Roamresearch
.timestamp-control{
background-color: rgba(108,109,36,0.1);
color: rgb(251,106,13);
margin-right: 8px;
margin-top: 3px;
margin-bottom: 3px;
border-radius: '50%';
border-style: inset;
border-color: #FF3200;
font-size: 0.9em;
}
.timestamp-control:hover {
background-color: rgba(108,109,36,0.25);
border-style: outset;
color: #FFFFFF;
}
// ==UserScript==
// @author Connected Cognition Crumbs <c3founder@gmail.com>
// @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