Skip to content

Instantly share code, notes, and snippets.

@c3founder
Last active April 6, 2023 11:19
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save c3founder/0d47fe3f15f677a0a6f0e96b4868e46a to your computer and use it in GitHub Desktop.
Save c3founder/0d47fe3f15f677a0a6f0e96b4868e46a 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);
@LeximusMG
Copy link

I'm using a mac. I've changed the shortcuts to ctrl+a n and ctrl+a t but for some reason it's not picking up the change. The new shortcut is does nothing but the original alt+ still works (even though I'm on a mac). Any ideas?

@knuckleheader
Copy link

knuckleheader commented Jun 29, 2021

Hi having trouble installing. When I copy from https://c3founder.github.io/Roam-Enhancement/ nothing changes to my embedded youtube videos. I am indenting and placing it correctly, I have installed other roam/js code blocks without issue.

I can get the rounded corners on my youtube videos but only if I place this https://gist.github.com/c3founder/0d47fe3f15f677a0a6f0e96b4868e46a#file-responsive-timestamped-youtube-js
in to the javascript code block. But I don't have the timestamp button. I also don't have the ability to resize the youtube window. I really like the idea of working with youtube videos in Roam research. Any help would be appreciated!

Also the page, if entered in to a browser window, https://c3founder.github.io/Roam-Enhancement/enhancedX.css
States that the above file can not be found. It is giving me a 404 error.

@c3founder
Copy link
Author

X

Why is it like that?

20210419170830

20210419170912

I have not written the corresponding smart block and have no idea why it is not working with the YT extension. If you are using it for the button that inserts timestamps you can add it as a feature request in issues here.

@c3founder
Copy link
Author

Hi having trouble installing. When I copy from https://c3founder.github.io/Roam-Enhancement/ nothing changes to my embedded youtube videos. I am indenting and placing it correctly, I have installed other roam/js code blocks without issue.

I can get the rounded corners on my youtube videos but only if I place this https://gist.github.com/c3founder/0d47fe3f15f677a0a6f0e96b4868e46a#file-responsive-timestamped-youtube-js
in to the javascript code block. But I don't have the timestamp button. I also don't have the ability to resize the youtube window. I really like the idea of working with youtube videos in Roam research. Any help would be appreciated!

Also the page, if entered in to a browser window, https://c3founder.github.io/Roam-Enhancement/enhancedX.css
States that the above file can not be found. It is giving me a 404 error.

Hey,
X here is a template!
https://c3founder.github.io/Roam-Enhancement/enhanced**X**.css

e.g., use this for YT:
@import url('https://c3founder.github.io/Roam-Enhancement/enhancedYouTube.css');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment