I'm trying to display an alert with the time watched of the video with Greasemonkey.
My script:
// ==UserScript==
// @name Time YouTube
// @description Does not work ! Help !
// @namespace time_youtube
// @include *youtube.com/watch*
// @grant GM_setValue
// @icon http://aux3.iconpedia.net/uploads/520882026785186105.png
// @version 1.3
// ==/UserScript==
ytplayer = document.getElementById ("movie_player");
var time = document.createElement ('a');
time.innerHTML = '<a style="position: fixed; top: 200px; left: 3px; color:black;">Time</a>';
//time.addEventListener ('click', function(){GM_setValue('url_time',(document.location.href) + '&t=' + (ytplayer.getCurrentTime()));}, false);
time.addEventListener ('click',
function () {
alert (ytplayer.getCurrentTime() );
},
false
);
document.body.appendChild (time);
But this does not work.
Can you help me? Thank you!
Due to various security and scoping issues, you cannot run ytplayer.getCurrentTime()
from the Greasemonkey script scope.
Likewise, the click
event handler must run in the target page's scope to access that function. Attempts to do otherwise will yield combinations of undefined
and Error: Bad NPObject as private data!
.
This means that you must "Inject" the click
handler for your time button.
But first, here's a few more issues to consider:
- Youtube, and most Google sites, make extensive use of
<iframe>
s. To avoid problems with multiple executions of your script, use checks like window.top === window.self
to ensure that the script only runs in the frame(s) or containing page that you are targeting.
- Avoid using
innerHTML
and inline styles as much as possible. This will make the code easier to maintain, faster in many instances, and less likely to trigger unexpected side effects.
Putting that all together, here is your complete script refactored:
// ==UserScript==
// @name Time YouTube
// @description Does not work ! Help !
// @namespace time_youtube
// @include *youtube.com/watch*
// @icon http://aux3.iconpedia.net/uploads/520882026785186105.png
// @grant GM_setValue
// @grant GM_addStyle
// @version 1.3
// ==/UserScript==
//-- Only run in the top page, not the various iframes.
if (window.top === window.self) {
var timeBtn = document.createElement ('a');
timeBtn.id = "gmTimeBtn";
timeBtn.textContent = "Time";
//-- Button is styled using CSS, in GM_addStyle, below.
document.body.appendChild (timeBtn);
addJS_Node (null, null, activateTimeButton);
}
function activateTimeButton () {
var timeBtn = document.getElementById ("gmTimeBtn");
if (timeBtn) {
timeBtn.addEventListener ('click',
function () {
var ytplayer = document.getElementById ("movie_player");
//-- IMPORTANT: GM_functions will not work here.
console.log ("getCurrentTime(): ", ytplayer.getCurrentTime() );
//alert (ytplayer.getCurrentTime() );
},
false
);
}
else {
alert ("Time button not found!");
}
}
//-- Style and position our button the CSS way.
GM_addStyle ( " \
#gmTimeBtn { \
position: fixed; \
top: 200px; \
left: 3px; \
color: black; \
margin: 0; \
padding: 0; \
} \
" );
//-- This is a standard-ish utility function...
function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
var D = document;
var scriptNode = D.createElement ('script');
if (runOnLoad) {
scriptNode.addEventListener ("load", runOnLoad, false);
}
scriptNode.type = "text/javascript";
if (text) scriptNode.textContent = text;
if (s_URL) scriptNode.src = s_URL;
if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';
var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
targ.appendChild (scriptNode);
}
Finally, from your script, it looks like you hope to eventually use GM_setValue()
, etc., when clicking that button. That requires messaging across scopes. See this answer or open a new question when you get to that part.