window.players = {} scanYouTube = (event, observer) => { event.forEach((mutation) => { if (mutation.type == "childList") { mutation.addedNodes.forEach((node) => { if (node.nodeType == Node.ELEMENT_NODE) { node.querySelectorAll("iframe[src*='youtube.com']:not([src*='enablejsapi'])").forEach( (element)=>{ switchYouTube(element); }) } }) } }) } switchYouTube = (element) => { const pathParts = new URL(element.src).pathname.split("/"); const youtubeid = pathParts[pathParts.length - 1]; const blockContainer = element.closest('.roam-block-container'); const block = element.closest('.roam-block'); if (!block) { return; } const parent = element.parentElement; console.log(element); console.log(block); full_id = block.id + "-" + youtubeid; blockContainer.dataset.full_id = full_id; blockContainer.dataset.youtube = true; parent.id = full_id; element.remove(); const player = new window.YT.Player(parent.id, { height: '300', width: '450', videoId: youtubeid }); const iframe = player.getIframe(); players[full_id] = player; iframe.dataset.youtubeid = youtubeid console.log("Found Youtube ID: " + youtubeid ); setTimeout(()=>{ addTimestampControls(blockContainer, block.id, player) }, 200); height = 300; width = 450; buttonDiv = document.createElement('div'); buttonDiv.dataset.youtubebuttons=true for (i of [1, 1.2,1.5,2,2.5,3]) { console.log(i); const multiplier = i; const button = document.createElement('button'); button.innerText = multiplier.toString(); button.addEventListener('click', () => { console.log("Setting height: " + multiplier * height + "; width: " + multiplier * width); iframe.height = multiplier * height; iframe.width = multiplier * width; }); button.style.marginRight = '8px'; buttonDiv.appendChild(button); } block.parentElement.parentElement.appendChild(buttonDiv); } addTimestampControls = (blockContainer, blockID, player) => { const getTimestampAddButton = (event, observer) => { if (Array.from(event[0].removedNodes).some((el) => { return el.id == blockID})) { console.log("block was removed: " + blockID); observer.disconnect(); removeButtons(); } else { console.log(event); addButtons(); } } const removeButtons = () => { blockContainer.querySelectorAll(":scope .roam-block-container .roam-block[data-youtubets]").forEach(child => { child.parentElement.querySelector('button[data-youtubets="' + child.dataset.youtubets + '"]').remove(); delete child.dataset.youtubets; }); blockContainer.querySelector(":scope div[data-youtubebuttons=true]").remove(); } const addButtons = () => { blockContainer.querySelectorAll(":scope .roam-block-container .roam-block:not([data-youtubets])").forEach(child => { const timestamp = getTimestamp(child); if (timestamp != null) { child.dataset.iframe_id = player.getIframe().id; child.dataset.youtubets = timestamp; addControlButton(child, timestamp, () => { player.seekTo(timestamp, true) pState = player.getPlayerState(); if (pState == -1 || pState == 0 || pState == 2) { player.playVideo(); } }); } }); } addButtons(); const x = new MutationObserver(getTimestampAddButton); x.observe(blockContainer, { childList: true, subtree: true }); }; getTimestamp = (block) => { const matches = block.textContent.match(/^((?:\d+:)?\d+:\d\d)($|\D)/); 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; } addControlButton = (block, timestamp, fn) => { const button = document.createElement('button'); button.innerText = '►'; button.addEventListener('click', fn); button.style.marginRight = '8px'; button.dataset.youtubets = timestamp; const printElement = (e) => { if (Array.from(e[0].removedNodes).some((el) => { return el.id == block.id})) { console.log("Removed Nodes: "); console.log(e[0].removedNodes); button.remove(); } } const x = new MutationObserver(printElement); x.observe(block.parentElement, { childList: true }); block.parentElement.insertBefore(button, block); }; if (document.querySelectorAll("html > script[src*='https://www.youtube.com/iframe_api']").length == 0) { console.log("Load Youtube API"); const tag = document.createElement('script'); tag.src = 'https://www.youtube.com/iframe_api'; const htmlEl = document.getElementsByTagName('html')[0]; htmlEl.appendChild(tag); } if (document.querySelectorAll("head > script[src='https://craig.global.ssl.fastly.net/js/mousetrap/mousetrap.min.js?a4098']").length == 0) { console.log("Installing Mousetrap"); const tag = document.createElement('script'); tag.src = 'https://craig.global.ssl.fastly.net/js/mousetrap/mousetrap.min.js?a4098'; const htmlEl = document.getElementsByTagName('head')[0]; htmlEl.appendChild(tag); } window.onYouTubeIframeAPIReady = () => { document.querySelectorAll("iframe[src*='youtube.com']:not([src*='enablejsapi'])").forEach( (element)=>{ switchYouTube(element); }) const observerYouTube = new MutationObserver(scanYouTube); observerYouTube.observe(document.querySelector('.roam-body-main'), { childList: true, subtree: true }); } setTimeout(()=>{ // Add bindGlobal (function(a){var c={},d=a.prototype.stopCallback;a.prototype.stopCallback=function(e,b,a,f){return this.paused?!0:c[a]||c[f]?!1:d.call(this,e,b,a)};a.prototype.bindGlobal=function(a,b,d){this.bind(a,b,d);if(a instanceof Array)for(b=0;b { console.log("Getting timestamp"); event.preventDefault() if (event.srcElement.localName == "textarea") { bc = event.srcElement.closest('.roam-block-container[data-youtube=true]'); player = players[bc.dataset.full_id]; var timeStr = new Date(player.getCurrentTime() * 1000).toISOString().substr(11, 8) console.log(timeStr); var newValue = timeStr + " " + event.srcElement.innerHTML; document.getElementById(event.srcElement.id).value = newValue; return false; } }); },1000);