Skip to content

Instantly share code, notes, and snippets.

@jpinnix
Forked from c3founder/Play-Button.cs
Created March 14, 2021 21:36
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 jpinnix/1d22bcad9a2a1ee802aeadd449bb07cf to your computer and use it in GitHub Desktop.
Save jpinnix/1d22bcad9a2a1ee802aeadd449bb07cf 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: 0px;
margin-left: 0px;
margin-bottom: 5px;
border-radius: 50% !important;
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==
// @name Responsive YouTube with Timestamp Control for Roamresearch
// @author Connected Cognition Crumbs <c3founder@gmail.com>
// @require Roam42: Wait until Roam42 loads completely
// @version 0.3
// @description Add timestamp controls to YouTube videos embedded in Roam and makes the player responsive.
// Parameters:
// Shortcuts: for grabbing title and current time as a timestamp.
// grabTitleKey: if in a DIRECT child block of the YT video,
// grabs the title and paste it to the beginning of the current block.
// grabTimeKey: if in ANY child blocks of the YT video,
// grabs the current time of the player and paste it to the beginning.
// Player Size: Video height and width when the right sidebar is closed.
// @match https://*.roamresearch.com
const ytParams = {
//Player Size
vidHeight : 480,
vidWidth : 720,
//Shortcuts
grabTitleKey : 'alt+a t',
grabTimeKey : 'alt+a n'
};
const players = new Map();
var ytReady = setInterval(() => {
if(typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') {
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
clearInterval(ytReady);
}
}, 1000);
//Fill out the current block with the given text
function fillTheBlock(givenTxt){
var setValue = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
let newTextArea = document.querySelector("textarea.rm-block-input");
setValue.call(newTextArea, givenTxt);
var e = new Event('input', { bubbles: true });
newTextArea.dispatchEvent(e);
}
var mouseTrapReady = setInterval(() => {
if(Mousetrap === undefined) return;
Mousetrap.bind(ytParams.grabTitleKey, async function(e) {
e.preventDefault()
if (e.srcElement.localName == "textarea") {
var container = e.srcElement.closest('.roam-block-container');
var parContainer = container.parentElement.closest('.roam-block-container');
var myIframe = parContainer.querySelector("iframe");
if(myIframe === null) return false;
var oldTxt = document.querySelector("textarea.rm-block-input").value;
var newValue = players[myIframe.id].getVideoData().title + " " + oldTxt;
fillTheBlock(newValue);
}
return false;
});
Mousetrap.bind(ytParams.grabTimeKey, async function(e) {
e.preventDefault()
for (var plyId in players) {
if(players[plyId].getPlayerState() == 1){
var timeStr = new Date(players[plyId].getCurrentTime() * 1000).toISOString().substr(11, 8)
var oldTxt = document.querySelector("textarea.rm-block-input").value;
fillTheBlock(timeStr + " " + oldTxt);
return false;
}
}
return false;
});
clearInterval(mouseTrapReady);
}, 1000);
const activateYtVideos = () => {
if(typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') return;
Array.from(document.getElementsByTagName('IFRAME'))
.filter(iframe => iframe.src.includes('youtube.com'))
.forEach(ytEl => {
if(ytEl.closest('.rm-zoom-item') !== null) {
return; //ignore breadcrumbs
}
const block = ytEl.closest('.roam-block-container');
if(ytEl.src.indexOf("enablejsapi") === -1){
var ytId = extractVideoID(ytEl.src);
var frameId = "yt-" + ytEl.closest('.roam-block').id;
ytEl.parentElement.id = frameId;
ytEl.remove();
players[frameId] = new window.YT.Player(frameId, {
height: ytParams.vidHeight,
width: ytParams.vidWidth,
videoId: ytId
});
wrapIframe(frameId);
} else {
var frameId = ytEl.id
}
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].querySelectorAll('.roam-block-container'));
childBlocks.forEach(child => {
const timestamp = getTimestamp(child);
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 = ytParams.vidWidth + 'px';
child.style.height = ytParams.vidHeight + 'px';
child.style.borderStyle = 'inset';
child.style.borderRadius = '25px';
par.style.position = null;
par.style.paddingBottom = '0px';
par.style.height = ytParams.vidHeight + 20 + 'px';
}
}
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];
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
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 extractVideoID = (url) => {
var regExp = /^(https?:\/\/)?((www\.)?(youtube(-nocookie)?|youtube.googleapis)\.com.*(v\/|v=|vi=|vi\/|e\/|embed\/\/?|user\/.*\/u\/\d+\/)|youtu\.be\/)([_0-9a-z-]+)/i;
var match = url.match(regExp);
if ( match && match[7].length == 11 ){
return match[7];
}else{
return null;
}
};
setInterval(activateYtVideos, 1000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment