您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ZenzaWatchをゲームパッドで操作
- // ==UserScript==
- // @name ZenzaGamePad
- // @namespace https://github.com/segabito/
- // @description ZenzaWatchをゲームパッドで操作
- // @include *://*.nicovideo.jp/*
- // @version 1.5.3
- // @author segabito macmoto
- // @license public domain
- // @grant none
- // @noframes
- // ==/UserScript==
- /* eslint-disable */
- // 推奨
- //
- // XInput系 (XboxOne, Xbox360コントローラ等)
- // DualShock4
- // USBサターンパッド
- // 8bitdo FC30系
- // Joy-Con L R
- (async (window) => {
- const monkey = (ZenzaWatch) => {
- if (!window.navigator.getGamepads) {
- window.console.log('%cGamepad APIがサポートされていません', 'background: red; color: yellow;');
- return;
- }
- const PRODUCT = 'ZenzaGamePad';
- const CONSTANT = {
- BASE_Z_INDEX: 150000
- };
- const ButtonMapJoyConL = {
- Y: 0,
- B: 1,
- X: 2,
- A: 3,
- SUP: 4,
- SDN: 5,
- SEL: 8,
- CAP: 13,
- LR: 14,
- META: 15,
- PUSH: 10
- };
- const ButtonMapJoyConR = {
- Y: 3,
- B: 2,
- X: 1,
- A: 0,
- SUP: 5,
- SDN: 4,
- SEL: 9,
- CAP: 12,
- LR: 14,
- META: 15,
- PUSH: 11
- };
- const JoyConAxisCenter = +1.28571;
- const AxisMapJoyConL = {
- CENTER: JoyConAxisCenter,
- UP: +0.71429,
- U_R: +1.00000,
- RIGHT: -1.00000,
- D_R: -0.71429,
- DOWN: -0.42857,
- D_L: -0.14286,
- LEFT: +0.14286,
- U_L: +0.42857,
- };
- const AxisMapJoyConR = {
- CENTER: JoyConAxisCenter,
- UP: -0.42857,
- U_R: -0.14286,
- RIGHT: +0.14286,
- D_R: +0.42857,
- DOWN: +0.71429,
- D_L: +1.00000,
- LEFT: -1.00000,
- U_L: -0.71429,
- };
- let _ = window._ || ZenzaWatch.lib._;
- let $ = window.jQuery || ZenzaWatch.lib.$;
- let util = ZenzaWatch.util;
- let Emitter = ZenzaWatch.modules ? ZenzaWatch.modules.Emitter : ZenzaWatch.lib.AsyncEmitter;
- let isZenzaWatchOpen = false;
- let console;
- let debugMode = !true;
- let dummyConsole = {
- log: _.noop, error: _.noop, time: _.noop, timeEnd: _.noop, trace: _.noop
- };
- console = debugMode ? window.console : dummyConsole;
- let isPauseButtonDown = false;
- let isRate1ButtonDown = false;
- let isMetaButtonDown = false;
- const getVideo = () => {
- return document.querySelector('.zenzaWatchVideoElement');
- };
- const video = {
- get duration() {
- return getVideo().duration;
- }
- };
- const Config = (() => {
- const prefix = PRODUCT + '_config_';
- const emitter = new Emitter();
- const defaultConfig = {
- debug: false,
- enabled: true,
- needFocus: false,
- deviceIndex: 0
- };
- const config = {};
- emitter.refresh = (emitChange = false) => {
- Object.keys(defaultConfig).forEach(key => {
- const storageKey = prefix + key;
- if (localStorage.hasOwnProperty(storageKey)) {
- try {
- let lastValue = config[key];
- let newValue = JSON.parse(localStorage.getItem(storageKey));
- if (lastValue !== newValue) {
- config[key] = newValue;
- if (emitChange) {
- emitter.emit('key', newValue);
- emitter.emit('@update', {key, value: newValue});
- }
- }
- } catch (e) {
- window.console.error('config parse error key:"%s" value:"%s" ', key, localStorage.getItem(storageKey), e);
- config[key] = defaultConfig[key];
- }
- } else {
- config[key] = defaultConfig[key];
- }
- });
- };
- emitter.refresh();
- emitter.get = (key, refresh) => {
- if (refresh) {
- emitter.refreshValue(key);
- }
- return config[key];
- };
- emitter.set = (key, value = undefined) => {
- if (config[key] !== value && value !== undefined) {
- const storageKey = prefix + key;
- localStorage.setItem(storageKey, JSON.stringify(value));
- config[key] = value;
- emitter.emit(key, value);
- emitter.emit('@update', {key, value});
- }
- };
- return emitter;
- })();
- class BaseViewComponent extends Emitter {
- constructor({parentNode = null, name = '', template = '', shadow = '', css = ''}) {
- super();
- this._params = {parentNode, name, template, shadow, css};
- this._bound = {};
- this._state = {};
- this._props = {};
- this._elm = {};
- this._initDom({
- parentNode,
- name,
- template,
- shadow,
- css
- });
- }
- _initDom({parentNode, name, template, css = '', shadow = ''}) {
- let tplId = `${PRODUCT}${name}Template`;
- let tpl = document.getElementById(tplId);
- if (!tpl) {
- if (css) { util.addStyle(css, `${name}Style`); }
- tpl = document.createElement('template');
- tpl.innerHTML = template;
- tpl.id = tplId;
- document.body.appendChild(tpl);
- }
- const onClick = this._bound.onClick = this._onClick.bind(this);
- const view = document.importNode(tpl.content, true);
- this._view = view.querySelector('*') || document.createDocumentFragment();
- if (this._view) {
- this._view.addEventListener('click', onClick);
- }
- this.appendTo(parentNode);
- if (shadow) {
- this._attachShadow({host: this._view, name, shadow});
- if (!this._isDummyShadow) {
- this._shadow.addEventListener('click', onClick);
- }
- }
- }
- _attachShadow ({host, shadow, name, mode = 'open'}) {
- let tplId = `${PRODUCT}${name}Shadow`;
- let tpl = document.getElementById(tplId);
- if (!tpl) {
- tpl = document.createElement('template');
- tpl.innerHTML = shadow;
- tpl.id = tplId;
- document.body.appendChild(tpl);
- }
- if (!host.attachShadow && !host.createShadowRoot) {
- return this._fallbackNoneShadowDom({host, tpl, name});
- }
- const root = host.attachShadow ?
- host.attachShadow({mode}) : host.createShadowRoot();
- const node = document.importNode(tpl.content, true);
- root.appendChild(node);
- this._shadowRoot = root;
- this._shadow = root.querySelector('.root');
- this._isDummyShadow = false;
- }
- _fallbackNoneShadowDom({host, tpl, name}) {
- const node = document.importNode(tpl.content, true);
- const style = node.querySelector('style');
- style.remove();
- util.addStyle(style.innerHTML, `${name}Shadow`);
- host.appendChild(node);
- this._shadow = this._shadowRoot = host.querySelector('.root');
- this._isDummyShadow = true;
- }
- setState(key, val) {
- if (typeof key === 'string') {
- this._setState(key, val);
- }
- Object.keys(key).forEach(k => {
- this._setState(k, key[k]);
- });
- }
- _setState(key, val) {
- if (this._state[key] !== val) {
- this._state[key] = val;
- if (/^is(.*)$/.test(key)) {
- this.toggleClass(`is-${RegExp.$1}`, !!val);
- }
- this.emit('update', {key, val});
- }
- }
- _onClick(e) {
- const target = e.target.classList.contains('command') ?
- e.target : e.target.closest('.command');
- if (!target) { return; }
- const command = target.getAttribute('data-command');
- if (!command) { return; }
- const type = target.getAttribute('data-type') || 'string';
- let param = target.getAttribute('data-param');
- e.stopPropagation();
- e.preventDefault();
- param = this._parseParam(param, type);
- this._onCommand(command, param);
- }
- _parseParam(param, type) {
- switch (type) {
- case 'json':
- case 'bool':
- case 'number':
- param = JSON.parse(param);
- break;
- }
- return param;
- }
- appendTo(parentNode) {
- if (!parentNode) { return; }
- this._parentNode = parentNode;
- parentNode.appendChild(this._view);
- }
- _onCommand(command, param) {
- this.emit('command', command, param);
- }
- toggleClass(className, v) {
- (className || '').split(/ +/).forEach((c) => {
- if (this._view && this._view.classList) {
- this._view.classList.toggle(c, v);
- }
- if (this._shadow && this._shadow.classList) {
- this._shadow.classList.toggle(c, this._view.classList.contains(c));
- }
- });
- }
- addClass(name) { this.toggleClass(name, true); }
- removeClass(name) { this.toggleClass(name, false); }
- }
- class ConfigPanel extends BaseViewComponent {
- constructor({parentNode}) {
- super({
- parentNode,
- name: 'ZenzaGamePadConfigPanel',
- shadow: ConfigPanel.__shadow__,
- template: '<div class="ZenzaGamePadConfigPanelContainer zen-family"></div>',
- css: ''
- });
- this._state = {
- isOpen: false,
- isVisible: false
- };
- Config.on('refresh', this._onBeforeShow.bind(this));
- }
- _initDom(...args) {
- super._initDom(...args);
- const v = this._shadow;
- this._elm.enabled = v.querySelector('[data-config-name="enabled"]');
- this._elm.needFocus = v.querySelector('[data-config-name="needFocus"]');
- this._elm.deviceIndex = v.querySelector('[data-config-name="deviceIndex"]');
- const onChange = e => {
- const target = e.target, name = target.getAttribute('data-config-name');
- switch (target.tagName) {
- case 'SELECT':
- case 'INPUT':
- if (target.type === 'checkbox') {
- Config.set(name, target.checked);
- } else {
- const type = target.getAttribute('data-type');
- const value = this._parseParam(target.value, type);
- Config.set(name, value);
- }
- break;
- default:
- //console.info('target', e, target, name, target.checked);
- Config.set(name, !!target.checked);
- break;
- }
- };
- this._elm.enabled.addEventListener('change', onChange);
- this._elm.needFocus.addEventListener('change', onChange);
- this._elm.deviceIndex.addEventListener('change', onChange);
- v.querySelector('.closeButton')
- .addEventListener('click', this.hide.bind(this));
- }
- _onClick(e) {
- super._onClick(e);
- }
- _onMouseDown(e) {
- this.hide();
- this._onClick(e);
- }
- show() {
- document.body.addEventListener('click', this._bound.onBodyClick);
- this._onBeforeShow();
- this.setState({isOpen: true});
- if (this._shadow.showModal) {
- this._shadow.showModal();
- }
- window.setTimeout(() => {
- this.setState({isVisible: true});
- }, 100);
- }
- hide() {
- document.body.removeEventListener('click', this._bound.onBodyClick);
- if (this._shadow.close) {
- this._shadow.close();
- }
- this.setState({isVisible: false});
- window.setTimeout(() => {
- this.setState({isOpen: false});
- }, 2100);
- }
- toggle() {
- if (this._state.isOpen) {
- this.hide();
- } else {
- this.show();
- }
- }
- _onBeforeShow() {
- this._elm.enabled.checked = !!Config.get('enabled');
- this._elm.needFocus.checked = !!Config.get('needFocus');
- this._elm.deviceIndex.value = Config.get('deviceIndex');
- }
- }
- ConfigPanel.__shadow__ = (`
- <style>
- .ZenzaGamePadConfigPanel {
- display: none;
- position: fixed;
- z-index: ${CONSTANT.BASE_Z_INDEX};
- top: 50vh;
- left: 50vw;
- padding: 8px;
- border: 2px outset;
- box-shadow: 0 0 8px #000;
- background: #ccc;
- transform: translate(-50%, -50%);
- transition: opacity 0.5s;
- transform-origin: center bottom;
- animation-timing-function: steps(10);
- perspective-origin: center bottom;
- user-select: none;
- margin: 0;
- pointer-events: auto !important;
- }
- .ZenzaGamePadConfigPanel[open] {
- display: block;
- opacity: 1;
- }
- .ZenzaGamePadConfigPanel.is-Open {
- display: block;
- opacity: 0;
- }
- .ZenzaGamePadConfigPanel.is-Open.is-Visible {
- opacity: 1;
- }
- .title {
- font-weight: bolder;
- font-size: 120%;
- font-family: 'arial black';
- margin: 0 0 8px;
- text-align: center;
- }
- .closeButton {
- display: block;
- text-align: center;
- }
- .closeButton {
- display: block;
- padding: 8px 16px;
- cursor: pointer;
- margin: auto;
- }
- label {
- cursor: pointer;
- }
- input[type="number"] {
- width: 50px;
- }
- input[type="checkbox"] {
- transform: scale(2);
- margin-right: 16px;
- }
- .ZenzaGamePadConfigPanel>div {
- padding: 8px;
- }
- </style>
- <dialog class="root ZenzaGamePadConfigPanel zen-family">
- <p class="title">†ZenzaGamePad†</p>
- <div class="enableSelect">
- <label>
- <input type="checkbox" data-config-name="enabled" data-type="bool">
- ZenzaGamePadを有効にする
- </label>
- </div>
- <div class="needFocusSelect">
- <label>
- <input type="checkbox" data-config-name="needFocus" data-type="bool">
- ウィンドウフォーカスのあるときのみ有効
- </label>
- </div>
- <div class="deviceIndex">
- <label>
- デバイス番号
- <select class="deviceIndexSelector"
- data-config-name="deviceIndex" data-type="number">
- <option value="0">0</option>
- <option value="1">1</option>
- <option value="2">2</option>
- <option value="3">3</option>
- </select>
- <small>(※リロードが必要)</small>
- </label>
- </div>
- <div class="closeButtonContainer">
- <button class="closeButton" type="button">
- 閉じる
- </button>
- </div>
- </dialog>
- `).trim();
- class ToggleButton extends BaseViewComponent {
- constructor({parentNode}) {
- super({
- parentNode,
- name: 'ZenzaGamePadToggleButton',
- shadow: ToggleButton.__shadow__,
- template: '<div class="ZenzaGamePadToggleButtonContainer"></div>',
- css: ''
- });
- this._state = {
- isEnabled: undefined
- };
- Config.on('enabled', () => {
- this.refresh();
- });
- }
- refresh() {
- this.setState({isEnabled: Config.get('enabled')});
- }
- }
- ToggleButton.__shadow__ = `
- <style>
- .controlButton {
- position: relative;
- display: inline-block;
- transition: opacity 0.4s ease, margin-left 0.2s ease, margin-top 0.2s ease;
- box-sizing: border-box;
- text-align: center;
- cursor: pointer;
- color: #fff;
- opacity: 0.8;
- vertical-align: middle;
- }
- .controlButton:hover {
- cursor: pointer;
- opacity: 1;
- }
- .controlButton .controlButtonInner {
- filter: grayscale(100%);
- }
- .heatSyncSwitch {
- font-size: 16px;
- width: 32px;
- height: 32px;
- line-height: 30px;
- cursor: pointer;
- }
- .is-Enabled .controlButtonInner {
- color: #aef;
- filter: none;
- }
- .controlButton .tooltip {
- display: none;
- pointer-events: none;
- position: absolute;
- left: 16px;
- top: -30px;
- transform: translate(-50%, 0);
- font-size: 12px;
- line-height: 16px;
- padding: 2px 4px;
- border: 1px solid !000;
- background: #ffc;
- color: #000;
- text-shadow: none;
- white-space: nowrap;
- z-index: 100;
- opacity: 0.8;
- }
- .controlButton:hover {
- background: #222;
- }
- .controlButton:hover .tooltip {
- display: block;
- opacity: 1;
- }
- </style>
- <div class="heatSyncSwitch controlButton root command" data-command="toggleZenzaGamePadConfig">
- <div class="controlButtonInner" title="ZenzaGamePad">🎮</div>
- <div class="tooltip">ZenzaGamePad</div>
- </div>
- `.trim();
- const execCommand = (command, param) =>
- ZenzaWatch.external.execCommand(command, param);
- const speedUp = () => {
- // TODO:
- // configを直接参照するのはお行儀が悪いのでexternalのインターフェースをつける
- const current = parseFloat(ZenzaWatch.config.getValue('playbackRate'), 10);
- window.console.log('speedUp', current);
- execCommand('playbackRate', Math.floor(Math.min(current + 0.1, 3) * 10) / 10);
- };
- const speedDown = () => {
- // TODO:
- // configを直接参照するのはお行儀が悪いのでexternalのインターフェースをつける
- const current = parseFloat(ZenzaWatch.config.getValue('playbackRate'), 10);
- window.console.log('speedDown', current);
- execCommand('playbackRate', Math.floor(Math.max(current - 0.1, 0.1) * 10) / 10);
- };
- const scrollUp = () => {
- document.documentElement.scrollTop =
- Math.max(0, document.documentElement.scrollTop - window.innerHeight / 5);
- };
- const scrollDown = () => {
- document.documentElement.scrollTop =
- document.documentElement.scrollTop + window.innerHeight / 5;
- };
- const scrollToVideo = () => {
- getVideo().scrollIntoView({behavior: 'smooth', block: 'center'});
- };
- const swapABXY_FC30 = btn => {
- switch (btn) {
- case 0: return 1;
- case 1: return 0;
- case 3: return 4;
- case 4: return 3;
- }
- return btn;
- };
- const onButtonDown = (button, deviceId) => {
- if (!isZenzaWatchOpen) { return; }
- if (deviceId.match(/Vendor: 04b4 Product: 010a/i)) {
- //USB Gamepad (Vendor: 04b4 Product: 010a)"
- return onButtonDownSaturn(button, deviceId);
- } else
- if (deviceId.match(/Vendor: (3810|05a0|1235|1002)/i)) {
- // FC30なのにみんなVendor違うってどういうことだよ
- // 8Bitdo FC30 Pro (Vendor: 1002 Product: 9000)
- return onButtonDownFC30(button, deviceId);
- } else
- if (deviceId.match(/Vendor: 057e Product: 200[67]/i)) {
- return onButtonDownJoyCon(button, deviceId);
- }
- switch (button) {
- case 0: // A
- isPauseButtonDown = true;
- execCommand('togglePlay');
- break;
- case 1: // B
- execCommand('toggle-mute');
- break;
- case 2: // X
- execCommand('toggle-showComment');
- break;
- case 3: // Y
- isRate1ButtonDown = true;
- execCommand('playbackRate', 0.1);
- break;
- case 4: // LB
- execCommand('playPreviousVideo');
- break;
- case 5: // RB
- execCommand('playNextVideo');
- break;
- case 6: // LT
- execCommand('playbackRate', 0.5);
- break;
- case 7: // RT
- execCommand('playbackRate', 3);
- break;
- case 8: // しいたけの左 ビューボタン (Back)
- execCommand('screenShot');
- break;
- case 9: // しいたけの右 メニューボタン (Start)
- execCommand('deflistAdd');
- break;
- case 10: // Lスティック
- execCommand('seek', 0);
- break;
- case 11: // Rスティック
- execCommand('toggle-fullscreen');
- break;
- case 12: // up
- if (isPauseButtonDown) {
- speedUp();
- } else {
- execCommand('volumeUp');
- }
- break;
- case 13: // down
- if (isPauseButtonDown) {
- speedDown();
- } else {
- execCommand('volumeDown');
- }
- break;
- case 14: // left
- if (isPauseButtonDown) {
- execCommand('seekPrevFrame');
- } else {
- execCommand('seekBy', isRate1ButtonDown ? -1 : -5);
- }
- break;
- case 15: // right
- if (isPauseButtonDown) {
- execCommand('seekNextFrame');
- } else {
- execCommand('seekBy', isRate1ButtonDown ? +1 : +5);
- }
- break;
- }
- };
- const onButtonDownSaturn = (button, deviceId) => {
- switch (button) {
- case 0: // A
- isPauseButtonDown = true;
- execCommand('togglePlay');
- break;
- case 1: // B
- execCommand('toggle-mute');
- break;
- case 2: // C
- execCommand('toggle-showComment');
- break;
- case 3: // X
- execCommand('playbackRate', 0.5);
- break;
- case 4: // Y
- isRate1ButtonDown = true;
- execCommand('playbackRate', 0.1);
- break;
- case 5: // Z
- execCommand('playbackRate', 3);
- break;
- case 6: // L
- execCommand('playPreviousVideo');
- break;
- case 7: // R
- execCommand('playNextVideo');
- break;
- case 8: // START
- execCommand('deflistAdd');
- break;
- }
- };
- const onButtonDownJoyCon = (button, deviceId) => {
- const ButtonMap = deviceId.match(/Vendor: 057e Product: 2006/i) ?
- ButtonMapJoyConL : ButtonMapJoyConR;
- switch (button) {
- case ButtonMap.Y:
- if (isPauseButtonDown) {
- execCommand('seekPrevFrame');
- } else {
- execCommand('toggle-showComment');
- }
- break;
- case ButtonMap.B:
- isPauseButtonDown = true;
- execCommand('togglePlay');
- break;
- case ButtonMap.X:
- if (isMetaButtonDown) {
- execCommand('playbackRate', 2);
- } else {
- isRate1ButtonDown = true;
- execCommand('playbackRate', 0.1);
- }
- break;
- case ButtonMap.A:
- if (isPauseButtonDown) {
- execCommand('seekNextFrame');
- } else {
- execCommand('toggle-mute');
- }
- break;
- case ButtonMap.SUP:
- if (isMetaButtonDown) {
- scrollUp();
- } else {
- execCommand('playPreviousVideo');
- }
- break;
- case ButtonMap.SDN:
- if (isMetaButtonDown) {
- scrollDown();
- } else {
- execCommand('playNextVideo');
- }
- break;
- case ButtonMap.SEL:
- if (isMetaButtonDown) {
- execCommand('toggle-loop');
- } else {
- execCommand('deflistAdd');
- }
- break;
- case ButtonMap.CAP:
- execCommand('screenShot');
- break;
- case ButtonMap.PUSH:
- if (isMetaButtonDown) {
- scrollToVideo();
- } else {
- execCommand('seek', 0);
- }
- break;
- case ButtonMap.LR:
- execCommand('toggle-fullscreen');
- break;
- case ButtonMap.META:
- isMetaButtonDown = true;
- break;
- }
- };
- const onButtonDownFC30 = (button, deviceId) => {
- if (deviceId.match(/Product: (3232)/i)) { // FC30 Zero / FC30
- button = swapABXY_FC30(button);
- }
- switch (button) {
- case 0: // B
- execCommand('toggle-mute');
- break;
- case 1: // A
- isPauseButtonDown = true;
- execCommand('togglePlay');
- break;
- case 2: // ???
- //execCommand('toggle-showComment');
- break;
- case 3: // X
- isRate1ButtonDown = true;
- execCommand('playbackRate', 0.1);
- break;
- case 4: // Y
- execCommand('toggle-showComment');
- break;
- case 5: //
- break;
- case 6: // L1
- if (isPauseButtonDown) {
- execCommand('playPreviousVideo');
- } else {
- execCommand('playbackRate', 0.5);
- }
- break;
- case 7: // R1
- if (isPauseButtonDown) {
- execCommand('playNextVideo');
- } else {
- execCommand('playbackRate', 3);
- }
- break;
- case 8: // L2
- execCommand('playPreviousVideo');
- break;
- case 9: // R2
- execCommand('playNextVideo');
- break;
- case 10: // SELECT
- execCommand('screenShot');
- break;
- case 11: // START
- execCommand('deflistAdd');
- break;
- case 13: // Lスティック
- execCommand('seek', 0);
- break;
- case 14: // Rスティック
- break;
- }
- };
- const onButtonUp = (button, deviceId) => {
- if (!isZenzaWatchOpen) { return; }
- if (deviceId.match(/Vendor: 04b4 Product: 010a/i)) {
- //USB Gamepad (Vendor: 04b4 Product: 010a)"
- return onButtonUpSaturn(button, deviceId);
- } else
- if (deviceId.match(/Vendor: (3810|05a0|1235|1002)/i)) {
- // 8Bitdo FC30 Pro (Vendor: 1002 Product: 9000)
- return onButtonUpFC30(button, deviceId);
- } else
- if (deviceId.match(/Vendor: 057e Product: 200[67]/i)) {
- return onButtonUpJoyCon(button, deviceId);
- }
- switch (button) {
- case 0: // A
- isPauseButtonDown = false;
- break;
- case 3: // Y
- isRate1ButtonDown = false;
- execCommand('playbackRate', 1.0);
- break;
- case 7: // RT
- execCommand('playbackRate', 1.5);
- break;
- }
- };
- const onButtonUpSaturn = (button, deviceId) => {
- switch (button) {
- case 0: // A
- isPauseButtonDown = false;
- break;
- case 1: // B
- break;
- case 2: // C
- break;
- case 3: // X
- break;
- case 4: // Y
- isRate1ButtonDown = false;
- execCommand('playbackRate', 1.0);
- break;
- case 5: // Z
- execCommand('playbackRate', 1.5);
- break;
- case 6: // L
- break;
- case 7: // R
- break;
- case 8: // START
- break;
- }
- };
- const onButtonUpFC30 = (button, deviceId) => {
- if (deviceId.match(/Product: (3232)/i)) { // FC30Zero / FC30
- button = swapABXY_FC30(button);
- }
- switch (button) {
- case 0: // B
- break;
- case 1: // A
- isPauseButtonDown = false;
- break;
- case 2: // ???
- break;
- case 3: // X
- isRate1ButtonDown = false;
- execCommand('playbackRate', 1.0);
- break;
- case 4: // Y
- break;
- case 5: //
- break;
- case 6: // L1
- break;
- case 7: // R1
- if (isPauseButtonDown) { return; }
- execCommand('playbackRate', 1.5);
- break;
- case 8: // L2
- break;
- case 9: // R2
- break;
- case 10: // SELECT
- break;
- case 11: // START
- break;
- case 13: // Lスティック
- break;
- case 14: // Rスティック
- break;
- }
- };
- const onButtonUpJoyCon = (button, deviceId) => {
- const ButtonMap = deviceId.match(/Vendor: 057e Product: 2006/i) ?
- ButtonMapJoyConL : ButtonMapJoyConR;
- switch (button) {
- case ButtonMap.Y:
- break;
- case ButtonMap.B:
- isPauseButtonDown = false;
- break;
- case ButtonMap.X:
- isRate1ButtonDown = false;
- execCommand('playbackRate', 1);
- break;
- case ButtonMap.META:
- isMetaButtonDown = false;
- break;
- }
- };
- const onButtonRepeat = (button, deviceId) => {
- if (!isZenzaWatchOpen) { return; }
- if (deviceId.match(/Vendor: 057e Product: 200[67]/i)) { // Joy-Con
- return onButtonRepeatJoyCon(button, deviceId);
- }
- switch (button) {
- case 12: // up
- if (isPauseButtonDown) {
- speedUp();
- } else {
- execCommand('volumeUp');
- }
- break;
- case 13: // down
- if (isPauseButtonDown) {
- speedUp();
- } else {
- execCommand('volumeDown');
- }
- break;
- case 14: // left
- if (isPauseButtonDown) {
- execCommand('seekPrevFrame');
- } else {
- execCommand('seekBy', isRate1ButtonDown ? -1 : -5);
- }
- break;
- case 15: // right
- if (isPauseButtonDown) {
- execCommand('seekNextFrame');
- } else {
- execCommand('seekBy', isRate1ButtonDown ? +1 : +5);
- }
- break;
- }
- };
- const onButtonRepeatJoyCon = (button, deviceId) => {
- const ButtonMap = deviceId.match(/Vendor: 057e Product: 2006/i) ?
- ButtonMapJoyConL : ButtonMapJoyConR;
- switch (button) {
- case ButtonMap.Y:
- if (isMetaButtonDown) {
- execCommand('seekBy', -15);
- } else if (isPauseButtonDown) {
- execCommand('seekPrevFrame');
- }
- break;
- case ButtonMap.A:
- if (isMetaButtonDown) {
- execCommand('seekBy', 15);
- } else if (isPauseButtonDown) {
- execCommand('seekNextFrame');
- }
- break;
- case ButtonMap.SUP:
- if (isMetaButtonDown) {
- scrollUp();
- } else {
- execCommand('playPreviousVideo');
- }
- break;
- case ButtonMap.SDN:
- if (isMetaButtonDown) {
- scrollDown();
- } else {
- execCommand('playNextVideo');
- }
- break;
- }
- };
- const onAxisChange = (axis, value, deviceId) => {
- if (!isZenzaWatchOpen) { return; }
- if (Math.abs(value) < 0.1) { return; }
- const isFC30 = deviceId.match(/Vendor: 3810/i) ? true : false;
- if (isFC30) {
- switch (axis) {
- case 3: // L2なぜか反応する
- case 4: // R2なぜか反応する
- return;
- case 5: // FC30のRスティック上下?
- axis = 3;
- break;
- }
- }
- if (deviceId.match(/Vendor: 057e Product: 200[67]/i)) { // Joy-Con
- return;
- }
- switch (axis) {
- case 0: {// Lスティック X
- const step = isRate1ButtonDown ? 1 : 5;
- execCommand('seekBy', (value < 0 ? -1 : 1) * step);
- }
- break;
- case 1: // Lスティック Y
- if (isPauseButtonDown) {
- if (value < 0) { speedUp(); }
- else { speedDown(); }
- } else {
- execCommand(value < 0 ? 'volumeUp' : 'volumeDown');
- }
- break;
- case 2: // Rスティック X
- break;
- case 3: // Rスティック Y
- if (value < 0) { speedUp(); }
- else { speedDown(); }
- break;
- }
- };
- const onAxisRepeat = (axis, value, deviceId) => {
- if (!isZenzaWatchOpen) { return; }
- if (Math.abs(value) < 0.1) { return; }
- if (deviceId.match(/Vendor: 057e Product: 2006/i)) { // Joy-Con (L)
- return;
- } else
- if (deviceId.match(/Vendor: 057e Product: 2007/i)) { // Joy-Con (R)
- return;
- }
- switch (axis) {
- case 0: {// Lスティック X
- const step = isRate1ButtonDown ? 1 : +5;
- execCommand('seekBy', (value < 0 ? -1 : 1) * step);
- }
- break;
- case 1: // Lスティック Y
- if (isPauseButtonDown) {
- if (value < 0) { speedUp(); }
- else { speedDown(); }
- } else {
- execCommand(value < 0 ? 'volumeUp' : 'volumeDown');
- }
- break;
- case 2: // Rスティック X
- break;
- case 3: // Rスティック Y
- if (value < 0) { speedUp(); }
- else { speedDown(); }
- break;
- }
- };
- const onPovChange = (pov, deviceId) => {
- switch(pov) {
- case 'UP':
- if (isMetaButtonDown) {
- speedUp();
- } else {
- execCommand('volumeUp');
- }
- break;
- case 'DOWN':
- if (isMetaButtonDown) {
- speedDown();
- } else {
- execCommand('volumeDown');
- }
- break;
- case 'LEFT':
- execCommand('seekBy', isRate1ButtonDown || isMetaButtonDown ? -1 : -5);
- break;
- case 'RIGHT':
- execCommand('seekBy', isRate1ButtonDown || isMetaButtonDown ? +1 : +5);
- break;
- }
- };
- const onPovRepeat = onPovChange;
- class PollingTimer {
- constructor(callback, interval) {
- this._timer = null;
- this._callback = callback;
- if (typeof interval === 'number') {
- this.changeInterval(interval);
- }
- }
- changeInterval(interval) {
- if (this._timer) {
- if (this._currentInterval === interval) {
- return;
- }
- window.clearInterval(this._timer);
- }
- console.log('%cupdate Interval:%s', 'background: lightblue;', interval);
- this._currentInterval = interval;
- this._timer = window.setInterval(this._callback, interval);
- }
- pause() {
- window.clearInterval(this._timer);
- this._timer = null;
- }
- start() {
- if (typeof this._currentInterval !== 'number') {
- return;
- }
- this.changeInterval(this._currentInterval);
- }
- }
- const GamePadModel = ((Emitter) => {
- class GamePad extends Emitter {
- constructor(gamepadStatus) {
- super();
- this._gamepadStatus = gamepadStatus;
- this._buttons = [];
- this._axes = [];
- this._pov = '';
- this._lastTimestamp = 0;
- this._povRepeat = 0;
- this.initialize(gamepadStatus);
- }
- get isConnected() {
- return this._gamepadStatus.connected ? true : false;
- }
- get deviceId() {
- return this._id;
- }
- get deviceIndex() {
- return this._index;
- }
- get buttonCount() {
- return this._buttons ? this._buttons.length : 0;
- }
- get axisCount() {
- return this._axes ? this._axes.length : 0;
- }
- get pov() {
- return this._pov;
- }
- get x() {
- return this._axes.length > 0 ? this._axes[0] : 0;
- }
- get y() {
- return this._axes.length > 1 ? this._axes[1] : 0;
- }
- get z() {
- return this._axes.length > 2 ? this._axes[2] : 0;
- }
- initialize(gamepadStatus) {
- this._buttons.length = gamepadStatus.buttons.length;
- this._axes.length = gamepadStatus.axes.length;
- this._id = gamepadStatus.id;
- this._index = gamepadStatus.index;
- this._isRepeating = false;
- this.reset();
- }
- reset() {
- this._pov = '';
- this._povRepeat = 0;
- for (let i = 0, len = this._gamepadStatus.buttons.length + 16; i < len; i++) {
- this._buttons[i] = {pressed: false, repeat: 0};
- }
- for (let i = 0, len = this._gamepadStatus.axes.length + 16; i < len; i++) {
- this._axes[i] = {value: null, repeat: 0};
- }
- }
- update() {
- const gamepadStatus = (navigator.getGamepads())[this._index];
- // gp || this._gamepadStatus;
- if (!gamepadStatus) { console.log('no status'); return; }
- if (!this._isRepeating && this._lastTimestamp === gamepadStatus.timestamp) {
- return;
- }
- this._gamepadStatus = gamepadStatus;
- this._lastTimestamp = gamepadStatus.timestamp;
- const buttons = gamepadStatus.buttons, axes = gamepadStatus.axes;
- let isRepeating = false;
- for (let i = 0, len = Math.min(this._buttons.length, buttons.length); i < len; i++) {
- const buttonStatus = buttons[i].pressed ? 1 : 0;
- if (this._buttons[i].pressed !== buttonStatus) {
- const eventName = (buttonStatus === 1) ? 'onButtonDown' : 'onButtonUp';
- //console.log('%cbutton%s:%s', 'background: lightblue;', i, buttonStatus, 0);
- this.emit(eventName, i, 0);
- this.emit('onButtonStatusChange', i, buttonStatus);
- }
- this._buttons[i].pressed = buttonStatus;
- if (buttonStatus) {
- this._buttons[i].repeat++;
- isRepeating = true;
- if (this._buttons[i].repeat % 5 === 0) {
- //console.log('%cbuttonRepeat%s', 'background: lightblue;', i);
- this.emit('onButtonRepeat', i);
- }
- } else {
- this._buttons[i].repeat = 0;
- }
- }
- for (let i = 0, len = Math.min(8, this._axes.length); i < len; i++) {
- const axis = Math.round(axes[i] * 1000) / 1000;
- if (this._axes[i].value === null) {
- this._axes[i].value = axis;
- continue;
- }
- const diff = Math.round(Math.abs(axis - this._axes[i].value));
- if (diff >= 1) {
- this.emit('onAxisChange', i, axis);
- }
- if (Math.abs(axis) <= 0.1 && this._axes[i].repeat > 0) {
- this._axes[i].repeat = 0;
- } else if (Math.abs(axis) > 0.1) {
- this._axes[i].repeat++;
- isRepeating = true;
- } else {
- this._axes[i].repeat = 0;
- }
- this._axes[i].value = axis;
- }
- if (typeof axes[9] !== 'number') {
- this._isRepeating = isRepeating;
- return;
- }
- let pov = '';
- if (this._id.match(/Vendor: 057e Product: 200[67]/i)) {
- const b = 100000;
- const axis = Math.trunc(axes[9] * b);
- const margin = b / 10;
- const AxisMap = this._id.match(/Vendor: 057e Product: 2006/i) ? AxisMapJoyConL : AxisMapJoyConR;
- if (Math.abs(JoyConAxisCenter * b - axis) <= margin) {
- pov = '';
- } else {
- Object.keys(AxisMap).forEach(key => {
- if (Math.abs(AxisMap[key] * b - axis) <= margin) {
- pov = key;
- }
- });
- }
- } else if (this._id.match(/Vendor: (3810|05a0|1235|1002)/i)) {
- const p = Math.round(axes[9] * 1000);
- if (p < -500) { pov = 'UP';
- } else if (p < 0) { pov = 'RIGHT';
- } else if (p > 3000) { pov = '';
- } else if (p > 500){ pov = 'LEFT';
- } else { pov = 'DOWN'; }
- }
- if (this._pov !== pov) {
- this._pov = pov;
- this._povRepeat = 0;
- isRepeating = pov !== '';
- this.emit('onPovChange', this._pov);
- } else if (pov !== '') {
- this._povRepeat++;
- isRepeating = true;
- if (this._povRepeat % 5 === 0) {
- this.emit('onPovRepeat', this._pov);
- }
- }
- this._isRepeating = isRepeating;
- //console.log(JSON.stringify(this.dump()));
- }
- dump() {
- const gamepadStatus = this._gamepadStatus, buttons = gamepadStatus.buttons, axes = gamepadStatus.axes;
- const btmp = [], atmp = [];
- for (let i = 0, len = axes.length; i < len; i++) {
- atmp.push('ax' + i + ': ' + axes[i]);
- }
- for (let i = 0, len = buttons.length; i < len; i++) {
- btmp.push('bt' + i + ': ' + (buttons[i].pressed ? 1 : 0));
- }
- return atmp.join('\n') + '\n' + btmp.join(', ');
- }
- getX() {
- return this._axes.length > 0 ? this._axes[0] : 0;
- }
- getY() {
- return this._axes.length > 1 ? this._axes[1] : 0;
- }
- getZ() {
- return this._axes.length > 2 ? this._axes[2] : 0;
- }
- getButtonCount() {
- return this._buttons ? this._buttons.length : 0;
- }
- getButtonStatus(index) {
- return this._buttons[index] || 0;
- }
- getAxisCount() {
- return this._axes ? this._axes.length : 0;
- }
- getAxisValue(index) {
- return this._axes[index] || 0;
- }
- getDeviceIndex() {
- return this._index;
- }
- getDeviceId() {
- return this._id;
- }
- getPov() {
- return this._pov;
- }
- release() {
- // TODO: clear events
- }
- }
- return GamePad;
- })(Emitter);
- const ZenzaGamePad = (($, PollingTimer, GamePadModel) => {
- let activeGamepad = null;
- let pollingTimer = null;
- let ZenzaGamePad = ZenzaWatch.modules ?
- new ZenzaWatch.modules.Emitter() : new Emitter();
- const padIndex = Config.get('deviceIndex') * 1;
- const detectGamepad = () => {
- if (activeGamepad) {
- return;
- }
- const gamepads = navigator.getGamepads();
- if (gamepads.length < 1) {
- return;
- }
- const pad = Array.from(gamepads).find(pad => {
- return pad &&
- pad.connected &&
- pad.id &&
- pad.index === padIndex &&
- // windowsにDualShock4を繋ぐとあらわれる謎のデバイス
- !pad.id.match(/Vendor: 00ff/i);
- });
- if (!pad) { return; }
- window.console.log(
- '%cdetect gamepad index: %s, id: "%s"',
- 'background: lightgreen; font-weight: bolder;',
- pad.index, pad.id
- );
- const gamepad = new GamePadModel(pad);
- activeGamepad = gamepad;
- const self = ZenzaGamePad;
- const onButtonDown = number => {
- self.emit('onButtonDown', number, gamepad.deviceIndex);
- };
- const onButtonRepeat = number => {
- self.emit('onButtonRepeat', number, gamepad.deviceIndex);
- };
- const onButtonUp = number => {
- self.emit('onButtonUp', number, gamepad.deviceIndex);
- };
- const onAxisChange = (number, value) => {
- self.emit('onAxisChange', number, value, gamepad.deviceIndex);
- };
- const onAxisRepeat = (number, value) => {
- self.emit('onAxisRepeat', number, value, gamepad.deviceIndex);
- };
- const onAxisRelease = number => {
- self.emit('onAxisRelease', number, gamepad.deviceIndex);
- };
- const onPovChange = pov => {
- self.emit('onPovChange', pov, gamepad.deviceIndex);
- };
- const onPovRepeat = pov => {
- self.emit('onPovRepeat', pov, gamepad.deviceIndex);
- };
- gamepad.on('onButtonDown', onButtonDown);
- gamepad.on('onButtonRepeat', onButtonRepeat);
- gamepad.on('onButtonUp', onButtonUp);
- gamepad.on('onAxisChange', onAxisChange);
- gamepad.on('onAxisRepeat', onAxisRepeat);
- gamepad.on('onAxisRelease', onAxisRelease);
- gamepad.on('onPovChange', onPovChange);
- gamepad.on('onPovRepeat', onPovRepeat);
- self.emit('onDeviceConnect', gamepad.getDeviceIndex(), gamepad.getDeviceId());
- pollingTimer.changeInterval(30);
- };
- const onGamepadConnectStatusChange = (e, isConnected) => {
- const padIndex = Config.get('deviceIndex') * 1;
- console.log('onGamepadConnetcStatusChange', e, e.gamepad.index, isConnected);
- if (e.gamepad.index !== padIndex) {
- return;
- }
- if (isConnected) {
- console.log('%cgamepad connected id:"%s"', 'background: lightblue;', e.gamepad.id);
- detectGamepad();
- } else {
- ZenzaGamePad.emit('onDeviceDisconnect', activeGamepad.getDeviceIndex());
- if (activeGamepad) {
- activeGamepad.release();
- }
- activeGamepad = null;
- console.log('%cgamepad disconneced id:"%s"', 'background: lightblue;', e.gamepad.id);
- }
- };
- const initializeTimer = () => {
- console.log('%cinitializeGamepadTimer', 'background: lightgreen;');
- const onTimerInterval = () => {
- if (!Config.get('enabled')) {
- return;
- }
- if (Config.get('needFocus') && !document.hasFocus()) {
- return;
- }
- if (!activeGamepad) {
- detectGamepad();
- return;
- }
- if (!activeGamepad.isConnected) {
- return;
- }
- activeGamepad.update();
- };
- pollingTimer = new PollingTimer(onTimerInterval, 1000);
- };
- const initializeGamepadConnectEvent = () => {
- console.log('%cinitializeGamepadConnectEvent', 'background: lightgreen;');
- window.addEventListener('gamepadconnected',
- e => onGamepadConnectStatusChange(e, true));
- window.addEventListener('gamepaddisconnected',
- e => onGamepadConnectStatusChange(e, false));
- if (activeGamepad) {
- return;
- }
- window.setTimeout(detectGamepad, 1000);
- };
- ZenzaGamePad.startDetect = () => {
- ZenzaGamePad.startDetect = _.noop;
- initializeTimer();
- initializeGamepadConnectEvent();
- };
- ZenzaGamePad.startPolling = () => {
- if (pollingTimer) { pollingTimer.start(); }
- };
- ZenzaGamePad.stopPolling = () => {
- if (pollingTimer) { pollingTimer.pause(); }
- };
- return ZenzaGamePad;
- })($, PollingTimer, GamePadModel);
- let hasInitGamePad = false;
- const initGamePad = () => {
- if (hasInitGamePad) { return; }
- hasInitGamePad = true;
- let isActivated = false;
- let isDetected = false;
- let deviceId, deviceIndex;
- const notifyDetect = () => {
- if (!document.hasFocus() || isDetected) { return; }
- isActivated = true;
- isDetected = true;
- // 初めてボタンかキーが押されたタイミングで通知する
- execCommand(
- 'notify',
- 'ゲームパッド "' + deviceId + '" が検出されました'
- );
- };
- const _onButtonDown = number => {
- notifyDetect();
- if (!isActivated) { return; }
- onButtonDown(number, deviceId);
- };
- const _onButtonRepeat = number => {
- if (!isActivated) { return; }
- onButtonRepeat(number, deviceId);
- };
- const _onButtonUp = number => {
- if (!isActivated) { return; }
- onButtonUp(number, deviceId);
- };
- const _onAxisChange = (number, value) => {
- notifyDetect();
- if (!isActivated) { return; }
- onAxisChange(number, value, deviceId);
- };
- const _onAxisRepeat = (number, value) => {
- if (!isActivated) { return; }
- onAxisRepeat(number, value, deviceId);
- };
- const _onAxisRelease = () => {
- if (!isActivated) { return; }
- };
- const _onPovChange = pov => {
- notifyDetect();
- if (!isActivated) { return; }
- onPovChange(pov, deviceId);
- };
- const _onPovRepeat = pov => {
- if (!isActivated) { return; }
- onPovRepeat(pov, deviceId);
- };
- let hasBound = false;
- const bindEvents = () => {
- if (hasBound) { return; }
- hasBound = true;
- ZenzaGamePad.on('onButtonDown', _onButtonDown);
- ZenzaGamePad.on('onButtonRepeat', _onButtonRepeat);
- ZenzaGamePad.on('onButtonUp', _onButtonUp);
- ZenzaGamePad.on('onAxisChange', _onAxisChange);
- ZenzaGamePad.on('onAxisRepeat', _onAxisRepeat);
- ZenzaGamePad.on('onAxisRelease', _onAxisRelease);
- ZenzaGamePad.on('onPovChange', _onPovChange);
- ZenzaGamePad.on('onPovRepeat', _onPovRepeat);
- };
- const onDeviceConnect = (index, id) => {
- deviceIndex = index;
- deviceId = id;
- bindEvents();
- };
- ZenzaGamePad.on('onDeviceConnect', onDeviceConnect);
- //ZenzaGamePad.on('onDeviceDisConnect', onDeviceDisConnect);
- ZenzaGamePad.startDetect();
- window.ZenzaWatch.ZenzaGamePad = ZenzaGamePad;
- };
- const onZenzaWatchOpen = () => {
- isZenzaWatchOpen = true;
- initGamePad();
- ZenzaGamePad.startPolling();
- };
- const onZenzaWatchClose = () => {
- isZenzaWatchOpen = false;
- ZenzaGamePad.stopPolling();
- };
- const initialize = async () => {
- ZenzaWatch.emitter.on('DialogPlayerOpen', onZenzaWatchOpen);
- ZenzaWatch.emitter.on('DialogPlayerClose', onZenzaWatchClose);
- const initButton = (container, handler) => {
- ZenzaGamePad.configPanel = new ConfigPanel({parentNode: document.querySelector('#zenzaVideoPlayerDialog')});
- const toggleButton = new ToggleButton({parentNode: container});
- toggleButton.on('command', handler);
- toggleButton.refresh();
- };
- if (ZenzaWatch.emitter.promise) {
- const {container, handler} = await ZenzaWatch.emitter.promise('videoControBar.addonMenuReady');
- initButton(container, handler);
- } else {
- ZenzaWatch.emitter.on('videoControBar.addonMenuReady', initButton);
- }
- ZenzaWatch.emitter.on('command-toggleZenzaGamePadConfig', () => {
- ZenzaGamePad.configPanel.toggle();
- });
- };
- initialize();
- };
- const loadMonkey = () => {
- const script = document.createElement('script');
- script.id = 'ZenzaGamePadLoader';
- script.setAttribute('type', 'text/javascript');
- script.setAttribute('charset', 'UTF-8');
- script.append(`(${monkey})(window.ZenzaWatch);`);
- document.head.append(script);
- };
- const ZenzaDetector = (() => {
- const promise =
- (window.ZenzaWatch && window.ZenzaWatch.ready) ?
- Promise.resolve(window.ZenzaWatch) :
- new Promise(resolve => {
- [window, (document.body || document.documentElement)]
- .forEach(e => e.addEventListener('ZenzaWatchInitialize', () => {
- resolve(window.ZenzaWatch);
- }));
- });
- return {detect: () => promise};
- })();
- await ZenzaDetector.detect();
- loadMonkey();
- })(globalThis ? globalThis.window : window);