ZenzaGamePad

ZenzaWatchをゲームパッドで操作

当前为 2016-03-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ZenzaGamePad
  3. // @namespace https://github.com/segabito/
  4. // @description ZenzaWatchをゲームパッドで操作
  5. // @include http://www.nicovideo.jp/*
  6. // @version 1.0.1
  7. // @author segabito macmoto
  8. // @license public domain
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13.  
  14. var monkey = function(ZenzaWatch) {
  15. if (!window.navigator.getGamepads) {
  16. window.console.log('%cGamepad APIがサポートされていません', 'background: red; color: yellow;');
  17. return;
  18. }
  19.  
  20. var _ = ZenzaWatch.lib._;
  21. var $ = ZenzaWatch.lib.$;
  22.  
  23. var isZenzaWatchOpen = false;
  24.  
  25. var console;
  26. var debugMode = !true;
  27.  
  28. var dummyConsole = {
  29. log: _.noop, error: _.noop, time: _.noop, timeEnd: _.noop, trace: _.noop
  30. };
  31. console = debugMode ? window.console : dummyConsole;
  32.  
  33. var execCommand = function(command, param) {
  34. ZenzaWatch.external.execCommand(command, param);
  35. };
  36.  
  37. var onButtonDown = function(button) {
  38. if (!isZenzaWatchOpen) { return; }
  39.  
  40. switch (button) {
  41. case 0: // A
  42. execCommand('togglePlay');
  43. break;
  44. case 1: // B
  45. execCommand('toggleMute');
  46. break;
  47. case 2: // X
  48. execCommand('toggleComment');
  49. break;
  50. case 3: // Y
  51. execCommand('playbackRate', 1.0);
  52. break;
  53. case 4: // LB
  54. execCommand('playPreviousVideo');
  55. break;
  56. case 5: // RB
  57. execCommand('playNextVideo');
  58. break;
  59. case 6: // LT
  60. execCommand('playbackRate', 0.5);
  61. break;
  62. case 7: // RT
  63. execCommand('playbackRate', 1.5);
  64. break;
  65. case 8: // ビューボタン (Back)
  66. execCommand('close');
  67. break;
  68. case 9: // メニューボタン (Start)
  69. execCommand('deflistAdd');
  70. break;
  71. case 10: // Lスティック
  72. execCommand('seek', 0);
  73. break;
  74. case 11: // Rスティック
  75. break;
  76. case 12: // up
  77. execCommand('volumeUp');
  78. break;
  79. case 13: // down
  80. execCommand('volumeDown');
  81. break;
  82. case 14: // left
  83. execCommand('seekBy', -5);
  84. break;
  85. case 15: // right
  86. execCommand('seekBy', +5);
  87. break;
  88. }
  89. };
  90.  
  91. var onButtonRepeat = function(button) {
  92. if (!isZenzaWatchOpen) { return; }
  93.  
  94. switch (button) {
  95. case 12: // up
  96. execCommand('volumeUp');
  97. break;
  98. case 13: // down
  99. execCommand('volumeDown');
  100. break;
  101. case 14: // left
  102. execCommand('seekBy', -5);
  103. break;
  104. case 15: // right
  105. execCommand('seekBy', +5);
  106. break;
  107. }
  108. };
  109.  
  110. var onAxisChange = function(axis, value) {
  111. if (!isZenzaWatchOpen) { return; }
  112.  
  113. switch (axis) {
  114. case 0: // Lスティック X
  115. execCommand('seekBy', value < 0 ? -5 : 5);
  116. break;
  117. case 1: // Lスティック Y
  118. execCommand(value < 0 ? 'volumeUp' : 'volumeDown');
  119. break;
  120. case 2: // Rスティック X
  121. break;
  122. case 3: // Rスティック Y
  123. break;
  124. }
  125. };
  126.  
  127.  
  128. var PollingTimer = (function() {
  129. var id = 0;
  130. var PollingTimer = function(callback, interval) {
  131. this._id = id++;
  132. this.initialize(callback, interval);
  133. };
  134. _.assign(PollingTimer.prototype, {
  135. initialize: function(callback, interval) {
  136. this._timer = null;
  137. this._callback = callback;
  138. if (typeof interval === 'number') {
  139. this.changeInterval(interval);
  140. }
  141. },
  142. changeInterval: function(interval) {
  143. if (this._timer) {
  144. if (this._currentInterval === interval) {
  145. return;
  146. }
  147. window.clearInterval(this._timer);
  148. }
  149. console.log('%cupdate Interval:%s', 'background: lightblue;', interval);
  150. this._currentInterval = interval;
  151. this._timer = window.setInterval(this._callback, interval);
  152. },
  153. pause: function() {
  154. window.clearInterval(this._timer);
  155. this._timer = null;
  156. },
  157. start: function() {
  158. if (typeof this._currentInterval !== 'number') {
  159. return;
  160. }
  161. this.changeInterval(this._currentInterval);
  162. }
  163. });
  164. return PollingTimer;
  165. })();
  166.  
  167. var GamePadModel = (function($, _, emitter) {
  168. var GamePadModel = function(gamepadStatus) {
  169. this._gamepadStatus = gamepadStatus;
  170. this._buttons = [];
  171. this._axes = [];
  172. this.initialize(gamepadStatus);
  173. };
  174. _.extend(GamePadModel.prototype, emitter.prototype);
  175.  
  176. _.assign(GamePadModel.prototype, {
  177. initialize: function(gamepadStatus) {
  178. this._buttons.length = gamepadStatus.buttons.length;
  179. this._axes.length = gamepadStatus.axes.length;
  180. this._id = gamepadStatus.id;
  181. this._index = gamepadStatus.index;
  182. this.reset();
  183. },
  184. reset: function() {
  185. var i, len;
  186.  
  187. for (i = 0, len = this._gamepadStatus.buttons.length; i < len; i++) {
  188. this._buttons[i] = {pressed: false, repeat: 0};
  189. }
  190. for (i = 0, len = this._gamepadStatus.axes.length; i < len; i++) {
  191. this._axes[i] = {value: 0, repeat: 0};
  192. }
  193. },
  194. update: function() {
  195. var gamepadStatus = (navigator.getGamepads())[this._index];
  196. // gp || this._gamepadStatus;
  197. if (!gamepadStatus) { console.log('no status'); return; }
  198. this._gamepadStatus = gamepadStatus;
  199.  
  200. var buttons = gamepadStatus.buttons, axes = gamepadStatus.axes;
  201. var i, len;
  202.  
  203. for (i = 0, len = this._buttons.length; i < len; i++) {
  204. var buttonStatus = buttons[i].pressed ? 1 : 0;
  205.  
  206. if (this._buttons[i].pressed !== buttonStatus) {
  207. var eventName = (buttonStatus === 1) ? 'onButtonDown' : 'onButtonUp';
  208. //console.log('%cbutton%s:%s', 'background: lightblue;', i, buttonStatus, 0);
  209. this.emit(eventName, i, 0);
  210. this.emit('onButtonStatusChange', i, buttonStatus);
  211. }
  212. this._buttons[i].pressed = buttonStatus;
  213. if (buttonStatus) {
  214. this._buttons[i].repeat++;
  215. if (this._buttons[i].repeat % 10 === 0) {
  216. //console.log('%cbuttonRepeat%s', 'background: lightblue;', i);
  217. this.emit('onButtonRepeat', i);
  218. }
  219. } else {
  220. this._buttons[i].repeat = 0;
  221. }
  222. }
  223. for (i = 0, len = this._axes.length; i < len; i++) {
  224. var axis = Math.round(axes[i] * 1000) / 1000;
  225. if (Math.round(Math.abs(axis - this._axes[i].value)) >= 1) {
  226. this._axes[i].repeat = 0;
  227.  
  228. //console.log('%c%s %s', 'background: lightblue;', 'onAxisChange', i, axis, 0);
  229. this.emit('onAxisChange', i, axis);
  230. if (axis <= 0.05) {
  231. this.emit('onAxisRelease', i);
  232. } else {
  233. this._axes[i].repeat++;
  234. if (this._axes[i].repeat % 10 === 0) {
  235. //console.log('%caxisRepeat%s:%s', 'background: lightblue;', i, axis);
  236. this.emit('onAxisRepeat', i, axis);
  237. }
  238. }
  239. this._axes[i].value = axis;
  240. }
  241. }
  242. //console.log(JSON.stringify(this.dump()));
  243. },
  244. dump: function() {
  245. var gamepadStatus = this._gamepadStatus, buttons = gamepadStatus.buttons, axes = gamepadStatus.axes;
  246. var i, len, btmp = [], atmp = [];
  247. for (i = 0, len = axes.length; i < len; i++) {
  248. atmp.push('ax' + i + ': ' + axes[i]);
  249. }
  250. for (i = 0, len = buttons.length; i < len; i++) {
  251. btmp.push('bt' + i + ': ' + (buttons[i].pressed ? 1 : 0));
  252. }
  253. return atmp.join('\n') + '\n' + btmp.join(', ');
  254. },
  255. getX: function() {
  256. return this._axes.length > 0 ? this._axes[0] : 0;
  257. },
  258. getY: function() {
  259. return this._axes.length > 1 ? this._axes[1] : 0;
  260. },
  261. getZ: function() {
  262. return this._axes.length > 2 ? this._axes[2] : 0;
  263. },
  264. getButtonCount: function() {
  265. return this._buttons ? this._buttons.length : 0;
  266. },
  267. getButtonStatus: function(index) {
  268. return this._buttons[index] || 0;
  269. },
  270. getAxisCount: function() {
  271. return this._axes ? this._axes.length : 0;
  272. },
  273. getAxisValue: function(index) {
  274. return this._axes[index] || 0;
  275. },
  276. isConnected: function() {
  277. return !!this._gamepadStatus.connected;
  278. },
  279. getDeviceIndex: function() {
  280. return this._index;
  281. },
  282. getDeviceId: function() {
  283. return this._id;
  284. },
  285. release: function() {
  286. // TODO: clear events
  287. }
  288. });
  289.  
  290. return GamePadModel;
  291. })($, _, ZenzaWatch.lib.AsyncEmitter);
  292.  
  293. var ZenzaGamePad = (function ($, PollingTimer, GamePadModel) {
  294. var primaryGamepad = null;
  295. var pollingTimer = null;
  296. var ZenzaGamePad = new ZenzaWatch.lib.AsyncEmitter();
  297.  
  298. var detectGamepad = function() {
  299. if (primaryGamepad) {
  300. return;
  301. }
  302. var gamepads = navigator.getGamepads();
  303. if (gamepads.length > 0) {
  304. var pad = _.find(gamepads, (pad, i) => { return pad !== undefined && pad.id; });
  305. if (!pad) { return; }
  306. window.console.log(
  307. '%cdetect gamepad index: %s, id: "%s"',
  308. 'background: lightgreen; font-weight: bolder;',
  309. pad.index, pad.id
  310. );
  311.  
  312. var gamepad = new GamePadModel(pad);
  313. primaryGamepad = gamepad;
  314.  
  315. var self = ZenzaGamePad;
  316. var onButtonDown = function(number) {
  317. self.emit('onButtonDown', number, gamepad.getDeviceIndex());
  318. };
  319. var onButtonRepeat = function(number) {
  320. self.emit('onButtonRepeat', number, gamepad.getDeviceIndex());
  321. };
  322. var onButtonUp = function(number) {
  323. self.emit('onButtonUp', number, gamepad.getDeviceIndex());
  324. };
  325. var onAxisChange = function(number, value) {
  326. self.emit('onAxisChange', number, value, gamepad.getDeviceIndex());
  327. };
  328. var onAxisRepeat = function(number, value) {
  329. self.emit('onAxisRepeat', number, value, gamepad.getDeviceIndex());
  330. };
  331. var onAxisRelease = function(number) {
  332. self.emit('onAxisRelease', number, gamepad.getDeviceIndex());
  333. };
  334.  
  335. gamepad.on('onButtonDown', onButtonDown);
  336. gamepad.on('onButtonRepeat', onButtonRepeat);
  337. gamepad.on('onButtonUp', onButtonUp);
  338. gamepad.on('onAxisChange', onAxisChange);
  339. gamepad.on('onAxisRepeat', onAxisRepeat);
  340. gamepad.on('onAxisRelease', onAxisRelease);
  341.  
  342. self.emit('onDeviceConnect', gamepad.getDeviceIndex(), gamepad.getDeviceId());
  343.  
  344. pollingTimer.changeInterval(30);
  345. }
  346. };
  347.  
  348.  
  349. var onGamepadConnectStatusChange = function(e, isConnected) {
  350. console.log('onGamepadConnetcStatusChange', e, isConnected);
  351. if (e.gamepad.index !== 0) {
  352. return;
  353. }
  354.  
  355. if (isConnected) {
  356. console.log('%cgamepad connected id:"%s"', 'background: lightblue;', e.gamepad.id);
  357. detectGamepad();
  358. } else {
  359. ZenzaGamePad.emit('onDeviceDisconnect', primaryGamepad.getDeviceIndex());
  360. if (primaryGamepad) {
  361. primaryGamepad.release();
  362. }
  363. primaryGamepad = null;
  364. console.log('%cgamepad disconneced id:"%s"', 'background: lightblue;', e.gamepad.id);
  365. }
  366. };
  367.  
  368. var initializeTimer = function() {
  369. console.log('%cinitializeGamepadTimer', 'background: lightgreen;');
  370. var onTimerInterval = function() {
  371. if (!primaryGamepad) {
  372. detectGamepad();
  373. }
  374. if (!primaryGamepad || !primaryGamepad.isConnected) {
  375. return;
  376. }
  377. primaryGamepad.update();
  378. };
  379. pollingTimer = new PollingTimer(onTimerInterval, 1000);
  380. };
  381.  
  382. var initializeGamepadConnectEvent = function() {
  383. console.log('%cinitializeGamepadConnectEvent', 'background: lightgreen;');
  384.  
  385. window.addEventListener('gamepadconnected',
  386. function(e) { onGamepadConnectStatusChange(e, true); }, false);
  387. window.addEventListener('gamepaddisconnected',
  388. function(e) { onGamepadConnectStatusChange(e, false); }, false);
  389.  
  390. if (primaryGamepad) {
  391. return;
  392. }
  393. window.setTimeout(detectGamepad, 1000);
  394. };
  395.  
  396. ZenzaGamePad.startDetect = function() {
  397. ZenzaGamePad.startDetect = _.noop;
  398. initializeTimer();
  399. initializeGamepadConnectEvent();
  400. };
  401.  
  402. return ZenzaGamePad;
  403. })($, PollingTimer, GamePadModel);
  404.  
  405.  
  406. var initGamePad = function() {
  407. initGamePad = _.noop;
  408.  
  409. var deviceId, deviceIndex;
  410. var notifyDetect = function() {
  411. notifyDetect = _.noop;
  412.  
  413. // 初めてボタンかキーが押されたタイミングで通知する
  414. execCommand(
  415. 'notify',
  416. 'ゲームパッド "' + deviceId + '" が検出されました'
  417. );
  418. };
  419.  
  420. var _onButtonDown = function(number /*, deviceIndex*/) {
  421. notifyDetect();
  422. onButtonDown(number);
  423. //console.log('%conButtonDown: number=%s, device=%s', 'background: lightblue;', number, deviceIndex);
  424. };
  425. var _onButtonRepeat = function(number /*, deviceIndex*/) {
  426. onButtonRepeat(number);
  427. //console.log('%conButtonRepeat: number=%s, device=%s', 'background: lightblue;', number, deviceIndex);
  428. };
  429. var _onButtonUp = function(number /*, deviceIndex*/) {
  430. //console.log('%conButtonUp: number=%s, device=%s', 'background: lightblue;', number, deviceIndex);
  431. };
  432. var _onAxisChange = function(number, value, deviceIndex) {
  433. notifyDetect();
  434. onAxisChange(number, value, deviceIndex);
  435. //console.log('%conAxisChange: number=%s, value=%s, device=%s', 'background: lightblue;', number, value, deviceIndex);
  436. };
  437. var _onAxisRepeat = function(number, value, deviceIndex) {
  438. //console.log('%conAxisChange: number=%s, value=%s, device=%s', 'background: lightblue;', number, value, deviceIndex);
  439. };
  440. var _onAxisRelease = function(/* number, deviceIndex */) {
  441. };
  442.  
  443. var onDeviceConnect = function(index, id) {
  444. onDeviceConnect = _.noop;
  445. deviceIndex = index;
  446. deviceId = id;
  447.  
  448. ZenzaGamePad.on('onButtonDown', _onButtonDown);
  449. ZenzaGamePad.on('onButtonRepeat', _onButtonRepeat);
  450. ZenzaGamePad.on('onButtonUp', _onButtonUp);
  451. ZenzaGamePad.on('onAxisChange', _onAxisChange);
  452. ZenzaGamePad.on('onAxisRepeat', _onAxisRepeat);
  453. ZenzaGamePad.on('onAxisRelease', _onAxisRelease);
  454. };
  455.  
  456. ZenzaGamePad.on('onDeviceConnect', onDeviceConnect);
  457. ZenzaGamePad.startDetect();
  458. };
  459.  
  460. var onZenzaWatchOpen = function() {
  461. isZenzaWatchOpen = true;
  462. initGamePad();
  463. };
  464.  
  465. var onZenzaWatchClose = function() {
  466. isZenzaWatchOpen = false;
  467. };
  468.  
  469.  
  470. var initialize = function() {
  471. ZenzaWatch.emitter.on('DialogPlayerOpen', onZenzaWatchOpen);
  472. ZenzaWatch.emitter.of('DialogPlayerClose', onZenzaWatchClose);
  473. };
  474.  
  475. initialize();
  476. };
  477.  
  478. var loadMonkey = function() {
  479. var script = document.createElement('script');
  480. script.id = 'ZenzaGamePadLoader';
  481. script.setAttribute('type', 'text/javascript');
  482. script.setAttribute('charset', 'UTF-8');
  483. script.appendChild(document.createTextNode('(' + monkey + ')(window.ZenzaWatch);'));
  484. document.body.appendChild(script);
  485. };
  486.  
  487. var waitForZenzaWatch = function() {
  488. if (window.ZenzaWatch && window.ZenzaWatch.ready) {
  489. window.console.log('ZenzaWatch is Ready');
  490. loadMonkey();
  491. } else {
  492. window.jQuery('body').on('ZenzaWatchReady', function() {
  493. window.console.log('onZenzaWatchReady');
  494. loadMonkey();
  495. });
  496. }
  497. };
  498. waitForZenzaWatch();
  499.  
  500. })();