使用Tampermonkey/javascript控制Netflix(HTML5)播放

9
观看Netflix网站上的视频时,我的目标是使用用户脚本以编程方式调用播放控件。具体而言,视频的音量,级别,播放/暂停状态以及时间位置。
我已经能够操作html5视频元素本身,但是直接控制它不会提供所需的Netflix控件条反馈给用户(即,视频被暂停,但控件条仍然显示正在播放)。
到目前为止,我的方法一直是尝试找到代表正常控件使用时点击的“按钮”的元素,并通过用户脚本触发它们的单击事件。但我似乎无法隔离出正确的元素。此外,Netflix使用了JavaScript压缩器/混淆器,这增加了查找控制条上的按钮的正确元素的难度。
在像这样的网站上,如何确定正在接收元素的点击事件并创建一个用户脚本通过tampermonkey和/或greasemonkey来调用它?
在下面的示例代码中,我添加了一个按钮进行测试。
// ==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;
}

console.log语句显示了我现在已经找到的一些东西。但是我还没有搞清楚如何调用它们中的函数,或者哪一个可能拥有我正在寻找的内容(我认为这主要是由于Javascript压缩器使得我难以跟踪代码)。


有关Silverlight播放器控件,请参见https://dev59.com/41fUa4cB1Zd3GeqPIHFN - rogerdpack
截至2017年9月,这是唯一有效的答案。https://dev59.com/Lp7ha4cB1Zd3GeqPq_FL#46816934 - Fethi Dilmi
3个回答

8

我知道这是一个较早的问题,但我想发布此答案,以便未来用户受益。

通过执行以下Javascript(最初由Dmitry Paloskin此主题上提供),您现在可以访问Netflix播放器API并控制其播放:

const videoPlayer = netflix.appContext.state.playerApp.getAPI().videoPlayer;

// Getting player id
const playerSessionId = videoPlayer.getAllPlayerSessionIds()[0];

const player = videoPlayer.getVideoPlayerBySessionId(playerSessionId);

你可以使用API对Netflix播放器执行各种命令,例如寻找(使用带有括号中毫秒数的player.seek()),播放或暂停视频(使用player.play()player.pause()),或控制音量(使用带有值的player.setVolume()括号, 其中1表示100%,0表示0%)。
此方法避免了更改视频元素的currentTime属性时Netflix崩溃的问题,如rogerdpack在上面的回复中提到的。

document.querySelector('video') 怎么样? - AaronJ
很遗憾,截至2021年12月,仅使用document.querySelector('video')并设置currentTime属性时,Netflix仍会崩溃。这就是为什么需要Dmitry Paloskin的解决方案来进行视频搜索。但是,您可以使用document.querySelector('video')更改一些视频元素属性而不会使Netflix崩溃,例如muted属性。感谢您的反馈,@AaronJ。 - The-Coder-Who-Knew-Too-Little
1
这是对我来说最好的解决方案。实际上,我只想获取播放器当前时间,所以我将上面的代码放在一个带有 return player.getCurrentTime()/1000 结尾的函数中,并且每当我需要当前视频的已过时间时,就调用该函数。很有效,谢谢! - Nick K9

6

我认为这是我过于深思熟虑的问题。一旦我退后一步,我意识到我一直在正确的方向上寻找,但没有正确地触发点击事件。

所以,例如,要获取控制播放和暂停的“按钮”,您可以使用:document.getElementsByClassName(“ player-control-button player-play-pause”)[0] 。然后,要在Tampermonkey中以编程方式单击它,只需使用以下方法调用单击事件:

document.getElementsByClassName("player-control-button player-play-pause")[0].click();

音量和其他控制按钮的条形位置类似。播放位置似乎有点棘手,但我会再查一些资料,并在找到答案后添加评论。


3
我将此标记为答案,因为它确实回答了原始问题。但是,我发现更好的方法来操纵Netflix视频播放是引用videoPlayer对象本身。例如:var vp = netflix.cadmium.objects.videoPlayer(); vp.seek(60500);使用此对象还可以获取暂停、音量、当前位置和持续时间等功能。 - Chad Michael
你用这个构建了什么吗? - user1732529
2
@ChadMichael,看起来netflix.cadmium对象最近已被删除,不幸的是[?] - rogerdpack

3
好的,好消息是你可以通过检测HTML5 VIDEO标签来在一定程度上控制播放。
例如: https://gist.github.com/rdp/93c761b3524529e591e5286073545362 find_html5_video.js

https://github.com/igrigorik/videospeed

然后对其调用方法,就像任何普通的HTMLMediaElement对象一样(静音、暂停等),都可以正常工作。
 video_element.paused = true // pause playback
 video_element.currentTime // get current timestamp

如果您在大多数网站(亚马逊即时视频、YouTube)上使用此技巧,您可以使用video_element.currentTime = 3进行搜索,它会正常工作。
但是,如果您像在Netflix上那样搜索,您会收到“糟糕,出了些问题...意外错误,发生了意外错误。请重新加载页面并重试。错误代码:M7375”。
我还没有找到解决方法(尽管如果您的搜索只是“快进”一点,您可以减小视频大小,将播放速度设置为超高,然后在达到所需位置时将其恢复到正常状态)。
因此,我们需要找到一种不同的发送搜索命令的方法。显然,曾经有一个netflix.cadmium.objects.videoPlayer()netflix.player JavaScript对象可用,其中包含一个搜索方法,但现在似乎不存在了。
所以回到您最初的问题,似乎可以模拟控制位置的滑块上的“单击”,从而发送搜索消息,就像您尝试的那样。
"Netflix Party"是一个Chrome扩展程序,也是Chrome扩展程序“http://showgoers.tv/”,类似于这个解释here。其中一个感人的部分似乎是:
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;
  });
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接