Reputation: 266
While watching a netflix video on a Netflix site, my goal is to have a userscript invoke the playback controls programmatically. Specifically, the volume, level, play/pause state, and time position of the video.
I've been able to manipulate the html5 video element itself, but controlling that directly does not provide the needed netflix control bar feedback to the user. (i.e. video is paused, but the control bar still shows it as playing).
My approach thus far has been to try and locate the elements that represent the "buttons" you click when using the normal controls, and trigger their click events through the userscript. But I can't seem to isolate the proper elements. Additionally netflix is using a javascript compressor/obfuscator which increases the difficulty of finding the proper elements that represent the buttons on the control bar.
On a site like this, how can one identify the element that is receiving the click event of an element and then create a userscript to invoke it through tampermonkey and/or greasemonkey?
In the sample code below, I've added a button on the view for testing purposes.
// ==UserScript==
// @name Jump a minute ahead in netflix
// @version 0.1
// @description A Test by trying to jump a minute or so ahead into a netflix movie
// @match *://www.netflix.com/*
// @grant GM_addStyle
// @require http://code.jquery.com/jquery-latest.js
// ==/UserScript==
var zNode = document.createElement ('div');
zNode.innerHTML = '<button id="myButton" type="button">Try It</button>';
zNode.setAttribute ('id', 'myContainer');
document.body.appendChild (zNode);
//--- Activate the newly added button.
document.getElementById ("myButton").addEventListener (
"click", ButtonClickAction, false
);
function ButtonClickAction (zEvent) {
/*--- For our dummy action, we'll just add a line of text to the top of the screen.*/
var zNode = document.createElement ('p');
var video = $("video:first-of-type");
var playerSlider = document.getElementsByClassName("player-slider")[0];
console.log(netflix);
console.log(playerSlider);
console.log(netflix.player);
console.log(netflix.cadmium);
console.log(netflix.cadmium.ui);
console.log(netflix.cadmium.ui.playback);
//video.get(0).pause();
//video.get(0).currentTime = 2000.0;
console.log(video.get(0).currentTime);
console.log(netflix.cadmium.ui.volume);
zNode.innerHTML = 'The button was clicked.';
document.getElementById ("myContainer").appendChild (zNode);
}
//--- Style our newly added elements using CSS.
GM_addStyle ( multilineStr ( function () {/*!
#myContainer {
position: absolute;
top: 0;
left: 0;
font-size: 20px;
background: orange;
border: 3px outset black;
margin: 5px;
opacity: 0.9;
z-index: 222;
padding: 5px 20px;
}
#myButton {
cursor: pointer;
}
#myContainer p {
color: red;
background: white;
}
*/} ) );
function multilineStr (dummyFunc) {
var str = dummyFunc.toString ();
str = str.replace (/^[^\/]+\/\*!?/, '') // Strip function () { /*!
.replace (/\s*\*\/\s*\}\s*$/, '') // Strip */ }
.replace (/\/\/.+$/gm, '') // Double-slash comments wreck CSS. Strip them.
;
return str;
}
The console.log statements are showing some of the things I've found so far. But I haven't figured out how to invoke functions off them, or which of them might have what I'm looking for (I think largely due to the javascript compressor making it difficult for me to follow the code).
Upvotes: 9
Views: 7051
Reputation: 152
I know this an older question, but I wanted to post this answer for the benefit of future users.
You can now control the playback of Netflix by accessing their player API, by executing the following Javascript (originally contributed by Dmitry Paloskin, on this topic):
const videoPlayer = netflix.appContext.state.playerApp.getAPI().videoPlayer;
// Getting player id
const playerSessionId = videoPlayer.getAllPlayerSessionIds()[0];
const player = videoPlayer.getVideoPlayerBySessionId(playerSessionId);
You can then use the API to perform various commands on the Netflix player, such as seeking (by using player.seek()
with the number of milliseconds in the parentheses), playing or pausing the video (by using player.play()
or player.pause()
respectively), or controlling the volume (by using player.setVolume()
with the value in the parentheses, with 1 being 100 percent and 0 being 0 percent).
This method avoids the issue with Netflix crashing when trying to change the video element's currentTime
property, as mentioned in the reply above from rogerdpack.
Upvotes: 8
Reputation: 66741
Well, the good news is that you can somewhat control playback by detecting the HTML5 VIDEO tag
ex's:
https://gist.github.com/rdp/93c761b3524529e591e5286073545362 find_html5_video.js
https://github.com/igrigorik/videospeed
then calling methods on that as any normal HTMLMediaElement object (mute, pause, etc.) works fine.
video_element.paused = true // pause playback
video_element.currentTime // get current timestamp
If you use this trick for most sites (amazon instant video, youtube), you can seek with video_element.currentTime = 3
and it just works.
However if you seek like with netflix, you get "Whoops, something went wrong... Unexpected Error There was an unexpected error. Please reload the page and try again. Error Code: M7375"
And I haven't figured out a way around that yet (though if your seek is just "fast forward" a bit, you could decrease the video size, set the playback rate to super high, then bring it back down to normal when it reaches the desired location I suppose).
So we need to find a different way to send the seek command. Apparently at one point there was a netflix.cadmium.objects.videoPlayer()
or netflix.player
javascript objects available with a seek method, however they appear to be absent now.
So back to your original question, it does appear possible to mimic the "click" on the slider that controls location, thus sending a seek message, as you were attempting.
Netflix Party
(chrome extension, and also chrome extension `http://showgoers.tv/) does something either like it, explained here.
A poignant part seems to be
var showControls = function() {
uiEventsHappening += 1;
var scrubber = $('#scrubber-component');
var eventOptions = {
'bubbles': true,
'button': 0,
'currentTarget': scrubber[0]
};
scrubber[0].dispatchEvent(new MouseEvent('mousemove', eventOptions));
return delay(10)().then(function() {
uiEventsHappening -= 1;
});
};
var seek = function(milliseconds) {
uiEventsHappening += 1;
var eventOptions, scrubber;
return showControls().then(function() {
// compute the parameters for the mouse events
scrubber = $('#scrubber-component');
var factor = milliseconds / getDuration();
var mouseX = scrubber.offset().left + Math.round(scrubber.width() * factor); // relative to the document
var mouseY = scrubber.offset().top + scrubber.height() / 2; // relative to the document
eventOptions = {
'bubbles': true,
'button': 0,
'screenX': mouseX - $(window).scrollLeft(),
'screenY': mouseY - $(window).scrollTop(),
'clientX': mouseX - $(window).scrollLeft(),
'clientY': mouseY - $(window).scrollTop(),
'offsetX': mouseX - scrubber.offset().left,
'offsetY': mouseY - scrubber.offset().top,
'pageX': mouseX,
'pageY': mouseY,
'currentTarget': scrubber[0]
};
// make the "trickplay preview" show up
scrubber[0].dispatchEvent(new MouseEvent('mouseover', eventOptions));
}).then(delay(10)).then(function() {
// simulate a click on the scrubber
scrubber[0].dispatchEvent(new MouseEvent('mousedown', eventOptions));
scrubber[0].dispatchEvent(new MouseEvent('mouseup', eventOptions));
scrubber[0].dispatchEvent(new MouseEvent('mouseout', eventOptions));
}).then(delay(1)).then(hideControls).then(function() {
uiEventsHappening -= 1;
});
};
Upvotes: 3
Reputation: 266
I think this was a case of my thinking too hard about the problem. Once I stepped back, I realized that I had been looking in the right direction, but wasn't triggering the click event properly.
So, for example, to get the "button" that controls play and pause, you could use: document.getElementsByClassName("player-control-button player-play-pause")[0]
. Then to click it programmatically in tampermonkey, you simply invoke the click event using:
document.getElementsByClassName("player-control-button player-play-pause")[0].click();
Volume and other controls in the bar are similar. The playback position is looking to be a bit trickier, but I'll do some more digging and add a comment to this answer once I figure it out.
Upvotes: 6