Nicorenizer

動画クリックで一時停止/再開 ダブルクリックでフルスクリーン切換え

当前为 2015-07-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Nicorenizer
  3. // @namespace https://github.com/segabito/
  4. // @description 動画クリックで一時停止/再開 ダブルクリックでフルスクリーン切換え
  5. // @include http://www.nicovideo.jp/watch/*
  6. // @version 0.1.7
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. // TODO:
  11. // ダブルクリック時にフルスクリーンにする設定を無効・ブラウザ全体・モニター全体から選べるようにする
  12.  
  13. // ver 0.1.2
  14. // - Watch It Laterと併用時、動画選択画面でのダブルクリックでフルスクリーンにならないのを修正
  15.  
  16. // ver 0.1.0 最初のバージョン
  17.  
  18. (function() {
  19. var monkey = (function(){
  20. 'use strict';
  21.  
  22.  
  23. if (!window.WatchApp || !window.WatchJsApi) {
  24. return;
  25. }
  26.  
  27. window.Nicorenizer = {};
  28.  
  29. window.WatchApp.mixin(window.Nicorenizer, {
  30. initialize: function() {
  31. this._watchInfoModel = require('watchapp/model/WatchInfoModel').getInstance();//window.WatchApp.ns.init.CommonModelInitializer.watchInfoModel;
  32. var PlayerInitializer = require('watchapp/init/PlayerInitializer');
  33. this._playerAreaConnector = PlayerInitializer.playerAreaConnector;
  34. this._nicoPlayerConnector = PlayerInitializer.nicoPlayerConnector;
  35. this._playerScreenMode = PlayerInitializer.playerScreenMode;
  36. this._videoExplorer = require('watchapp/init/VideoExplorerInitializer').videoExplorer;
  37.  
  38. this._vastStatus = this._nicoPlayerConnector.vastStatus;
  39.  
  40. this.initializeUserConfig();
  41. this.initializeSettingPanel();
  42. this.initializeShield();
  43.  
  44. this.initializePlayerApp();
  45.  
  46. this.initializeCss();
  47. },
  48. addStyle: function(styles, id) {
  49. var elm = document.createElement('style');
  50. elm.type = 'text/css';
  51. if (id) { elm.id = id; }
  52.  
  53. var text = styles.toString();
  54. text = document.createTextNode(text);
  55. elm.appendChild(text);
  56. var head = document.getElementsByTagName('head');
  57. head = head[0];
  58. head.appendChild(elm);
  59. return elm;
  60. },
  61. initializeCss: function() {
  62. var __css__ = (function() {/*
  63.  
  64. #nicorenaiShield {
  65. display: none;
  66. position: absolute;
  67. top: 0;
  68. left: 0;
  69. right: 0;
  70. bottom: 85px;
  71. z-index: 9950;
  72. cursor: none;
  73. }
  74.  
  75. #nicorenaiShield.disable, #nicorenaiShield.vast, #nicorenaiShield.disableTemp {
  76. display: none !important;
  77. }
  78.  
  79. #nicorenaiShield.debug {
  80. background: red; opacity: 0.5;
  81. }
  82.  
  83. #nicorenaiShield.initialized {
  84. display: block;
  85. }
  86.  
  87. #nicorenaiShield.showCursor {
  88. cursor: crosshair; {* 現在有効である事をわかりやすくするためにcrosshair。本当はオリジナルのカーソルを用意したいところ *}
  89. }
  90.  
  91. body.setting_panel #nicorenaiShield, body.videoErrorOccurred #nicorenaiShield,
  92. body.setting_panel #nicorenaiShield, body.videoErrorOccurred #nicorenaiShieldToggle {
  93. display: none;
  94. }
  95.  
  96. body.videoExplorer #content:not(.w_adjusted) #nicorenaiShield {
  97. {* 動画選択画面ではクリックで解除させるために邪魔なので消す *}
  98. {* ただしWatch It Lterの検索モードでは有効にする *}
  99. display: none;
  100. }
  101.  
  102. #nicorenaiShieldToggle {
  103. position: absolute;
  104. z-index: 9951;
  105. top: 10px;
  106. left: 10px;
  107. border-color: blue;
  108. opacity: 0;
  109. cursor: pointer;
  110. transition: opacity 0.5s ease;
  111. padding: 4px 8px;
  112. }
  113.  
  114. #nicorenaiShieldToggle.disable, #nicorenaiShieldToggle.disableTemp {
  115. border-color: black;
  116. }
  117.  
  118. #nicorenaiShieldToggle.debug {
  119. opacity: 1 !important;
  120. }
  121.  
  122. #nicorenaiShieldToggle.initialized:hover, #nicorenaiShieldToggle.show, #nicorenaiShieldToggle.disableTemp {
  123. opacity: 1;
  124. transition: none;
  125. }
  126.  
  127. #nicorenaiShieldToggle:after {
  128. content: ':ON';
  129. }
  130. #nicorenaiShieldToggle.disable:after, #nicorenaiShieldToggle.disableTemp:after {
  131. content: ':OFF';
  132. }
  133.  
  134. #nicorenizerSettingPanel {
  135. position: fixed;
  136. bottom: 2000px; right: 8px;
  137. z-index: -1;
  138. width: 500px;
  139. background: #f0f0f0; border: 1px solid black;
  140. padding: 8px;
  141. transition: bottom 0.4s ease-out;
  142. }
  143. #nicorenizerSettingPanel.open {
  144. display: block;
  145. bottom: 8px;
  146. box-shadow: 0 0 8px black;
  147. z-index: 10000;
  148. }
  149. #nicorenizerSettingPanel .close {
  150. position: absolute;
  151. cursor: pointer;
  152. right: 8px; top: 8px;
  153. }
  154. #nicorenizerSettingPanel .panelInner {
  155. background: #fff;
  156. border: 1px inset;
  157. padding: 8px;
  158. min-height: 300px;
  159. overflow-y: scroll;
  160. max-height: 500px;
  161. }
  162. #nicorenizerSettingPanel .panelInner .item {
  163. border-bottom: 1px dotted #888;
  164. margin-bottom: 8px;
  165. padding-bottom: 8px;
  166. }
  167. #nicorenizerSettingPanel .panelInner .item:hover {
  168. background: #eef;
  169. }
  170. #nicorenizerSettingPanel .windowTitle {
  171. font-size: 150%;
  172. }
  173. #nicorenizerSettingPanel .itemTitle {
  174. }
  175. #nicorenizerSettingPanel label {
  176.  
  177. }
  178. #nicorenizerSettingPanel small {
  179. color: #666;
  180. }
  181. #nicorenizerSettingPanel .expert {
  182. margin: 32px 0 16px;
  183. font-size: 150%;
  184. background: #ccc;
  185. }
  186.  
  187. {* Chromeの不具合対策 *}
  188. body.full_with_browser {
  189. width: 100%;
  190. }
  191.  
  192.  
  193. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  194.  
  195. this.addStyle(__css__, 'NicorenizerCss');
  196. },
  197. initializeUserConfig: function() {
  198. var prefix = 'Nicorenizer_';
  199. var conf = {
  200. fullScreenType: 'browser', // none, browser, monitor
  201. togglePlay: true
  202. };
  203. this.config = {
  204. get: function(key) {
  205. try {
  206. if (window.localStorage.hasOwnProperty(prefix + key)) {
  207. return JSON.parse(window.localStorage.getItem(prefix + key));
  208. }
  209. return conf[key];
  210. } catch (e) {
  211. return conf[key];
  212. }
  213. },
  214. set: function(key, value) {
  215. window.localStorage.setItem(prefix + key, JSON.stringify(value));
  216. }
  217. };
  218. },
  219. initializeSettingPanel: function() {
  220. var $menu = $('<li class="nicorenizerSettingMenu"><a href="javascript:;" title="Nicorenizerの設定変更">Nicorenizer設定</a></li>');
  221. var $panel = $('<div id="nicorenizerSettingPanel" />');//.addClass('open');
  222. var $button = $('<button class="toggleSetting playerBottomButton">設定</botton>');
  223.  
  224. $button.on('click', function(e) {
  225. e.stopPropagation(); e.preventDefault();
  226. $panel.toggleClass('open');
  227. });
  228.  
  229. var config = this.config;
  230. $menu.find('a').on('click', function() { $panel.toggleClass('open'); });
  231.  
  232. var __tpl__ = (function() {/*
  233. <div class="panelHeader">
  234. <h1 class="windowTitle">Nicorenizerの設定</h1>
  235. <button class="close" title="閉じる">×</button>
  236. </div>
  237. <div class="panelInner">
  238. <div class="item" data-setting-name="togglePlay" data-menu-type="radio">
  239. <h3 class="itemTitle">画面クリックで一時停止/再生</h3>
  240. <label><input type="radio" value="true" >する</label>
  241. <label><input type="radio" value="false">しない</label>
  242. </div>
  243.  
  244. <div class="item" data-setting-name="fullScreenType" data-menu-type="radio">
  245. <h3 class="itemTitle">ダブルクリック時のフルスクリーン</h3>
  246. <label><input type="radio" value="&quot;browser&quot;" >ブラウザ全体</label>
  247. <label><input type="radio" value="&quot;monitor&quot;">モニター全体</label>
  248. <label><input type="radio" value="&quot;none&quot;">切り換えない</label>
  249. </div>
  250. </div>
  251. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  252. $panel.html(__tpl__);
  253. $panel.find('.item').on('click', function(e) {
  254. var $this = $(this);
  255. var settingName = $this.attr('data-setting-name');
  256. var value = JSON.parse($this.find('input:checked').val());
  257. console.log('seting-name', settingName, 'value', value);
  258. config.set(settingName, value);
  259. }).each(function(e) {
  260. var $this = $(this);
  261. var settingName = $this.attr('data-setting-name');
  262. var value = config.get(settingName);
  263. $this.addClass(settingName);
  264. $this.find('input').attr('name', settingName).val([JSON.stringify(value)]);
  265. });
  266. $panel.find('.close').click(function() {
  267. $panel.removeClass('open');
  268. });
  269.  
  270.  
  271. $('#playerAlignmentArea').append($button);
  272. $('#siteHeaderRightMenuFix').after($menu);
  273. $('body').append($panel);
  274.  
  275.  
  276. },
  277. initializeShield: function() {
  278. var nicoPlayerConnector = this._nicoPlayerConnector;
  279. var playerAreaConnector = this._playerAreaConnector;
  280. var playerScreenMode = this._playerScreenMode;
  281. var videoExplorer = this._videoExplorer;
  282. var nicoPlayer = $("#external_nicoplayer")[0];
  283. var $shield = $('<div id="nicorenaiShield"></div>');
  284. var $toggle = $('<button id="nicorenaiShieldToggle">シールド</botton>');
  285.  
  286. var config = this.config;
  287.  
  288. var FullScreen = {
  289. now: function() {
  290. if (document.fullScreenElement || document.mozFullScreen || document.webkitIsFullScreen) {
  291. return true;
  292. }
  293. return false;
  294. },
  295. request: function(target) {
  296. var elm = typeof target === 'string' ? document.getElementById(target) : target;
  297. if (!elm) { return; }
  298. if (elm.requestFullScreen) {
  299. elm.requestFullScreen();
  300. } else if (elm.webkitRequestFullScreen) {
  301. elm.webkitRequestFullScreen();
  302. } else if (elm.mozRequestFullScreen) {
  303. elm.mozRequestFullScreen();
  304. }
  305. },
  306. cancel: function() {
  307. if (!this.now()) { return; }
  308.  
  309. if (document.cancelFullScreen) {
  310. document.cancelFullScreen();
  311. } else if (document.webkitCancelFullScreen) {
  312. document.webkitCancelFullScreen();
  313. } else if (document.mozCancelFullScreen) {
  314. document.mozCancelFullScreen();
  315. }
  316. }
  317. };
  318.  
  319. var toggleMonitorFull = function(v) {
  320. var now = FullScreen.now();
  321. if (now || v === false) {
  322. FullScreen.cancel();
  323. } else if (!now || v === true) {
  324. FullScreen.request(document.body);
  325. }
  326. };
  327.  
  328. var lastScreenMode = '';
  329. var onScreenModeChange = function(sc) {
  330. var mode = sc.mode;
  331. if (lastScreenMode === 'browserFull' && mode !== 'browserFull') {
  332. toggleMonitorFull(false);
  333. }
  334. lastScreenMode = mode;
  335. };
  336.  
  337. playerScreenMode.addEventListener('change', onScreenModeChange);
  338.  
  339. var click = function(e) {
  340. // TODO: YouTubeみたいに中央に停止/再生マーク出す?
  341. if (e.button !== 0) { return; }
  342. if (!config.get('togglePlay')) { return; }
  343. //var $shield = $(this).addClass('showCursor');
  344. var status = nicoPlayer.ext_getStatus();
  345. if (status === 'playing') {
  346. nicoPlayerConnector.stopVideo();
  347. } else {
  348. nicoPlayerConnector.playVideo();
  349. }
  350. };
  351.  
  352. var dblclick = function(e) {
  353. if (e.button !== 0) return;
  354. e.preventDefault(); e.stopPropagation();
  355. var fullScreenType = config.get('fullScreenType');
  356. if (fullScreenType === 'none') { return; }
  357.  
  358. if (videoExplorer.isOpen()) {
  359. videoExplorer.changeState(false);
  360. if (fullScreenType === 'monitor') {
  361. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  362. toggleMonitorFull(true);
  363. } else {
  364. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  365. }
  366. } else
  367. if ($('body').hasClass('full_with_browser')) {
  368. window.WatchJsApi.player.changePlayerScreenMode('notFull');
  369. toggleMonitorFull(false);
  370. } else {
  371. if (fullScreenType === 'monitor') {
  372. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  373. toggleMonitorFull(true);
  374. } else {
  375. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  376. }
  377. }
  378. };
  379.  
  380. var cursorHideTimer = null;
  381. var mousemove = function() {
  382. $shield.addClass('showCursor');
  383. if (cursorHideTimer) {
  384. window.clearTimeout(cursorHideTimer);
  385. cursorHideTimer = null;
  386. }
  387. cursorHideTimer = window.setTimeout(function() {
  388. $shield.off('mousemove', mousemove);
  389. $shield.removeClass('showCursor');
  390. window.setTimeout(function() { $shield.on('mousemove', mousemove); }, 500);
  391. }, 3000);
  392. };
  393.  
  394. var mousedown = function(e) {
  395. if (e.button === 0) return;
  396. // 左ボタン以外でクリックされたら5秒間だけシールドを解除するよ
  397. e.preventDefault(); e.stopPropagation();
  398.  
  399. $shield.addClass('disableTemp');
  400. $toggle.addClass('disableTemp');
  401.  
  402. $toggle
  403. .css('opacity', 1)
  404. .animate({'opacity': 0.3}, 5000, function() { $toggle.css('opacity', ''); });
  405. window.setTimeout(function() {
  406. $toggle.removeClass('disableTemp');
  407. $shield.removeClass('disableTemp');
  408. $toggle.css('opacity', '');
  409. }, 5000);
  410. };
  411.  
  412. var toggleDisable = function(f, showButtonTemporary) {
  413. var isDisable = $toggle.toggleClass('disable', f).hasClass('disable');
  414. $shield.toggleClass('disable', isDisable);
  415.  
  416. if (showButtonTemporary) { // 状態が変わった事を通知するために一時的に表示する
  417. $toggle.addClass('show');
  418. window.setTimeout(function() { $toggle.removeClass('show'); }, 2000);
  419. }
  420. };
  421.  
  422. // 最初に再生開始されるまでは表示しない。 ローカルストレージ~が出たときにクリックできるようにするため。
  423. // でも自動再生にしてると詰む。
  424. playerAreaConnector.addEventListener(
  425. 'onVideoStarted', function() {
  426. $shield.addClass('initialized'); $toggle.addClass('initialized');
  427. toggleDisable(false);
  428. }
  429. );
  430.  
  431. // 再生後メニューがクリックできないのも困るので無効化する
  432. playerAreaConnector.addEventListener(
  433. 'onVideoEnded', function() {
  434. toggleDisable(true, true);
  435. }
  436. );
  437. playerAreaConnector.addEventListener(
  438. 'onVideoSeeked', function(vpos, b, c) {
  439. // もう一度再生する場合など
  440. if (parseInt(vpos, 10) === 0) toggleDisable(false);
  441. }
  442. );
  443. $shield
  444. .on('click' , click)
  445. .on('dblclick', dblclick)
  446. .on('mousedown', mousedown)
  447. .on('mousemove', mousemove);
  448.  
  449. $toggle
  450. .attr('title', 'クリックで無効化ON/OFF')
  451. .on('click', toggleDisable);
  452.  
  453.  
  454. var vastStatus = this._vastStatus;
  455. vastStatus.addEventListener('linearStart', function() {
  456. $shield.addClass('vast');
  457. });
  458. vastStatus.addEventListener('linearEnd', function() {
  459. $shield.removeClass('vast');
  460. });
  461.  
  462. $('#external_nicoplayer').after($shield).after($toggle);
  463.  
  464. },
  465. initializePlayerApp: function() {
  466. // 実装が漏れててエラーが出てるっぽいのを修正
  467. // フルスクリーン時に動画プレイヤー以外にフォーカスがある時に出る
  468. var np = window.PlayerApp.ns.player.Nicoplayer.getInstance();
  469. var ep = $('#external_nicoplayer')[0];
  470. if (!np.ext_getVolume) {
  471. np.ext_getVolume = function() { return ep.ext_getVolume(); };
  472. }
  473. if (!np.ext_setVolume) {
  474. np.ext_setVolume = function(v) { ep.ext_setVolume(v); };
  475. }
  476. if (!np.ext_getStatus) {
  477. np.ext_getStatus = function() { return ep.ext_getStatus(); };
  478. }
  479. }
  480. });
  481.  
  482. if (window.PlayerApp) {
  483. (function() {
  484. var watchInfoModel = WatchApp.ns.model.WatchInfoModel.getInstance();
  485. if (watchInfoModel.initialized) {
  486. window.Nicorenizer.initialize();
  487. } else {
  488. var onReset = function() {
  489. watchInfoModel.removeEventListener('reset', onReset);
  490. window.setTimeout(function() {
  491. watchInfoModel.removeEventListener('reset', onReset);
  492. window.Nicorenizer.initialize();
  493. }, 0);
  494. };
  495. watchInfoModel.addEventListener('reset', onReset);
  496. }
  497. })();
  498. }
  499.  
  500.  
  501. });
  502.  
  503. var script = document.createElement("script");
  504. script.id = "NicorenizerLoader";
  505. script.setAttribute("type", "text/javascript");
  506. script.setAttribute("charset", "UTF-8");
  507. script.appendChild(document.createTextNode("(" + monkey + ")()"));
  508. document.body.appendChild(script);
  509.  
  510. })();