- // ==UserScript==
- // @name JoyRemocon
- // @namespace https://github.com/segabito/
- // @description Nintendo SwitchのJoy-Conを動画プレイヤーのリモコンにする.
- // @include *://*.nicovideo.jp/watch/*
- // @include *://www.youtube.com/*
- // @include *://www.bilibili.com/video/*
- // @include *://www.amazon.co.jp/gp/video/*
- // @version 1.6.0
- // @author segabito macmoto
- // @license public domain
- // @grant none
- // @noframes
- // ==/UserScript==
-
-
-
-
- (() => {
-
- const monkey = () => {
- if (!window.navigator.getGamepads) {
- window.console.log('%cGamepad APIがサポートされていません', 'background: red; color: yellow;');
- return;
- }
-
- const PRODUCT = 'JoyRemocon';
- let isPauseButtonDown = false;
- let isRate1ButtonDown = false;
- let isMetaButtonDown = false;
-
- const getVideo = () => {
- switch (location.host) {
- case 'www.nicovideo.jp':
- return document.querySelector('.MainVideoPlayer video');
- case 'www.amazon.co.jp':
- return document.querySelector('video[width="100%"]');
- default:
- return Array.from(document.querySelectorAll('video')).find(v => {
- return !!v.src;
- });
- }
- };
-
- const video = {
- get currentTime() {
- try {
- return window.__videoPlayer ?
- __videoplayer.currentTime() : getVideo().currentTime;
- } catch (e) {
- console.warn(e);
- return 0;
- }
- },
- set currentTime(v) {
- try {
- if (v <= video.currentTime && location.host === 'www.nicovideo.jp') {
- return seekNico(v);
- } else if (location.host === 'www.amazon.co.jp') {
- return seekPrimeVideo(v);
- }
- getVideo().currentTime = v;
- } catch (e) {
- console.warn(e);
- }
- },
-
- get muted() {
- try {
- return getVideo().muted;
- } catch (e) {
- console.warn(e);
- return false;
- }
- },
-
- set muted(v) {
- try {
- getVideo().muted = v;
- } catch (e) {
- console.warn(e);
- }
- },
-
- get playbackRate() {
- try {
- return window.__videoPlayer ?
- __videoplayer.playbackRate() : getVideo().playbackRate;
- } catch (e) {
- console.warn(e);
- return 1;
- }
- },
- set playbackRate(v) {
- try {
- if (window.__videoPlayer) {
- window.__videoPlayer.playbackRate(v);
- return;
- }
- getVideo().playbackRate = Math.max(0.01, v);
- } catch (e) {
- console.warn(e);
- }
- },
-
- get volume() {
- try {
- if (location.host === 'www.nicovideo.jp') {
- return getVolumeNico();
- }
- return getVideo().volume;
- } catch (e) {
- console.warn(e);
- return 1;
- }
- },
-
- set volume(v) {
- try {
- v = Math.max(0, Math.min(1, v));
- if (location.host === 'www.nicovideo.jp') {
- return setVolumeNico(v);
- }
- getVideo().volume = v;
- } catch (e) {
- console.warn(e);
- }
- },
-
- get duration() {
- try {
- return getVideo().duration;
- } catch (e) {
- console.warn(e);
- return 1;
- }
- },
-
- play() {
- try {
- return getVideo().play();
- } catch (e) {
- console.warn(e);
- return Promise.reject();
- }
- },
-
- pause() {
- try {
- return getVideo().pause();
- } catch (e) {
- console.warn(e);
- return Promise.reject();
- }
- },
-
- get paused() {
- try {
- return getVideo().paused;
- } catch (e) {
- console.warn(e);
- return true;
- }
- },
-
- };
-
- const seekNico = time => {
- const xs = document.querySelector('.SeekBar .XSlider');
- let [min, sec] = document.querySelector`.PlayerPlayTime-duration`.textContent.split(':');
- let duration = min * 60 + sec * 1;
- let left = xs.getBoundingClientRect().left;
- let offsetWidth = xs.offsetWidth;
- let per = time / duration * 100;
- let clientX = offsetWidth * per / 100 + left;
- xs.dispatchEvent(new MouseEvent('mousedown', {clientX}));
- document.dispatchEvent(new MouseEvent('mouseup', {clientX}));
- };
-
- const setVolumeNico = vol => {
- const xs = document.querySelector('.VolumeBar .XSlider');
- let left = xs.getBoundingClientRect().left;
- let offsetWidth = xs.offsetWidth;
- let per = vol * 100;
- let clientX = offsetWidth * per / 100 + left;
- xs.dispatchEvent(new MouseEvent('mousedown', {clientX}));
- document.dispatchEvent(new MouseEvent('mouseup', {clientX}));
- };
-
- const seekPrimeVideo = time => {
- const xs = document.querySelector('.seekBar .progressBarContainer');
- xs.closest('.bottomPanelItem').style.display = '';
- let left = xs.getBoundingClientRect().left;
- let offsetWidth = xs.offsetWidth;
- let per = (time - 10) / video.duration * 100; // 何故か10秒分ズレてる?
- let clientX = offsetWidth * per / 100 + left;
- // console.log('seek', video.currentTime, time, left, offsetWidth, per, clientX);
- xs.dispatchEvent(new PointerEvent('pointerdown', {clientX}));
- xs.dispatchEvent(new PointerEvent('pointerup', {clientX}));
- };
-
- const getVolumeNico = () => {
- try {
- const xp = document.querySelector('.VolumeBar .XSlider .ProgressBar-inner');
- return (xp.style.transform || '1').replace(/scaleX\(([0-9\.]+)\)/, '$1') * 1;
- } catch (e) {
- console.warn(e);
- return 1;
- }
- };
-
- const execCommand = (command, param) => {
- switch (command) {
- case 'playbackRate':
- video.playbackRate = param;
- break;
- case 'toggle-play': {
- const btn = document.querySelector(
- '.ytp-ad-skip-button, .PlayerPlayButton, .PlayerPauseButton, .html5-main-videom, .bilibili-player-video-btn-start, .pausedOverlay');
- if (btn) {
- if (location.host === 'www.amazon.co.jp') {
- btn.dispatchEvent(new CustomEvent('pointerup'));
- } else {
- btn.click();
- }
- } else
- if (video.paused) {
- video.play();
- } else {
- video.pause();
- }
- break;
- }
- case 'toggle-mute': {
- const btn = document.querySelector(
- '.MuteVideoButton, .UnMuteVideoButton, .ytp-mute-button, .bilibili-player-iconfont-volume-max');
- if (btn) {
- btn.click();
- } else {
- video.muted = !video.muted;
- }
- break;
- }
- case 'seek':
- video.currentTime = param * 1;
- break;
- case 'seekBy':
- video.currentTime += param * 1;
- break;
- case 'seekNextFrame':
- video.currentTime += 1 / 60;
- break;
- case 'seekPrevFrame':
- video.currentTime -= 1 / 60;
- break;
- case 'volumeUp': {
- let v = video.volume;
- let r = v < 0.05 ? 1.3 : 1.1;
- video.volume = Math.max(0.05, v * r + 0.01);
- break;
- }
- case 'volumeDown': {
- let v = video.volume;
- let r = 1 / 1.2;
- video.volume = Math.max(0.01, v * r);
- break;
- }
- case 'toggle-showComment': {
- const btn = document.querySelector('.CommentOnOffButton, .bilibili-player-video-danmaku-switch input');
- if (btn) {
- btn.click();
- }
- break;
- }
- case 'toggle-fullscreen': {
- const btn = document.querySelector(
- '.EnableFullScreenButton, .DisableFullScreenButton, .ytp-fullscreen-button, .bilibili-player-video-btn-fullscreen, .imageButton.fullscreenButton');
- if (btn) {
- btn.click();
- }
- break;
- }
- case 'playNextVideo': {
- const btn = document.querySelector(
- '.PlayerSkipNextButton, .ytp-next-button, .nextTitleButton, .skipAdButton');
- if (btn) {
- btn.click();
- }
- break;
- }
- case 'playPreviousVideo': {
- const btn = document.querySelector(
- '.PlayerSeekBackwardButton');
- if (btn) {
- btn.click();
- }
- if (['www.youtube.com'].includes(location.host)) {
- history.back();
- }
- break;
- }
- case 'screenShot': {
- screenShot();
- break;
- }
- case 'deflistAdd': {
- const btn = document.querySelector(
- '.InstantMylistButton');
- if (btn) {
- btn.click();
- }
- break;
- }
- case 'notify':
- notify(param);
- break;
- case 'unlink':
- if (document.hasFocus()) {
- JoyRemocon.unlink();
- }
- break;
- default:
- console.warn('unknown command "%s" "%o"', command, param);
- break;
- }
- };
-
- const notify = message => {
- const div = document.createElement('div');
- div.textContent = message;
- Object.assign(div.style, {
- position: 'fixed',
- display: 'inline-block',
- zIndex: 1000000,
- left: 0,
- bottom: 0,
- transition: 'opacity 0.4s linear, transform 0.5s ease',
- padding: '8px 16px',
- background: '#00c',
- color: 'rgba(255, 255, 255, 0.8)',
- fontSize: '16px',
- fontWeight: 'bolder',
- whiteSpace: 'nowrap',
- textAlign: 'center',
- boxShadow: '2px 2px 0 #ccc',
- userSelect: 'none',
- pointerEvents: 'none',
- willChange: 'transform',
- opacity: 0,
- transform: 'translate(0, +100%) translate(48px, +48px) ',
- });
-
-
- const parent = document.querySelector('.MainContainer') || document.body;
- parent.append(div);
-
- setTimeout(() => {
- Object.assign(div.style, { opacity: 1, transform: 'translate(48px, -48px)' });
- }, 100);
- setTimeout(() => {
- Object.assign(div.style, { opacity: 0, transform: 'translate(48px, -48px) scaleY(0)' });
- }, 2000);
- setTimeout(() => {
- div.remove();
- }, 3000);
- };
-
- const getVideoTitle = () => {
- switch (location.host) {
- case 'www.nicovideo.jp':
- return document.title;
- case 'www.youtube.com':
- return document.title;
- default:
- return document.title;
- }
- };
-
- const toSafeName = function(text) {
- text = text.trim()
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/\?/g, '?')
- .replace(/:/g, ':')
- .replace(/\|/g, '|')
- .replace(/\//g, '/')
- .replace(/\\/g, '¥')
- .replace(/"/g, '”')
- .replace(/\./g, '.')
- ;
- return text;
- };
-
- const speedUp = () => {
- let current = video.playbackRate;
- execCommand('playbackRate', Math.floor(Math.min(current + 0.1, 3) * 10) / 10);
- };
-
- const speedDown = () => {
- let current = video.playbackRate;
- 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 screenShot = video => {
- video = video || getVideo();
- if (!video) {
- return;
- }
- // draw canvas
- const width = video.videoWidth;
- const height = video.videoHeight;
- const canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- const context = canvas.getContext('2d');
- context.drawImage(video, 0, 0);
- document.body.append(canvas);
- // fileName
- const videoTitle = getVideoTitle();
- const currentTime = video.currentTime;
- const min = Math.floor(currentTime / 60);
- const sec = (currentTime % 60 + 100).toString().substr(1, 6);
- const time = `${min}_${sec}`;
- const fileName = `${toSafeName(videoTitle)}@${time}.png`;
-
- // to objectURL
- console.time('canvas to DataURL');
- const dataURL = canvas.toDataURL('image/png');
- console.timeEnd('canvas to DataURL');
-
- console.time('dataURL to objectURL');
- const bin = atob(dataURL.split(',')[1]);
- const buf = new Uint8Array(bin.length);
- for (let i = 0, len = buf.length; i < len; i++) {
- buf[i] = bin.charCodeAt(i);
- }
- const blob = new Blob([buf.buffer], {type: 'image/png'});
- const objectURL = URL.createObjectURL(blob);
- console.timeEnd('dataURL to objectURL');
-
- // save
- const link = document.createElement('a');
- link.setAttribute('download', fileName);
- link.setAttribute('href', objectURL);
- document.body.append(link);
- link.click();
- setTimeout(() => { link.remove(); URL.revokeObjectURL(objectURL); }, 1000);
- };
-
- 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,
- };
-
-
- const onButtonDown = (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('toggle-play');
- 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('unlink');
- } else {
- execCommand('deflistAdd');
- }
- break;
- case ButtonMap.CAP:
- if (location.host === 'www.amazon.co.jp') {
- return;
- }
- 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 onButtonUp = (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) => {
- 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) => {};
- const onAxisRepeat = (axis, value, deviceId) => {};
- 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 Handler {
- constructor(...args) {
- this._list = new Array(...args);
- }
-
- get length() {
- return this._list.length;
- }
-
- exec(...args) {
- if (!this._list.length) {
- return;
- } else if (this._list.length === 1) {
- this._list[0](...args);
- return;
- }
- for (let i = this._list.length - 1; i >= 0; i--) {
- this._list[i](...args);
- }
- }
-
- execMethod(name, ...args) {
- if (!this._list.length) {
- return;
- } else if (this._list.length === 1) {
- this._list[0][name](...args);
- return;
- }
- for (let i = this._list.length - 1; i >= 0; i--) {
- this._list[i][name](...args);
- }
- }
-
- add(member) {
- if (this._list.includes(member)) {
- return this;
- }
- this._list.unshift(member);
- return this;
- }
-
- remove(member) {
- _.pull(this._list, member);
- return this;
- }
-
- clear() {
- this._list.length = 0;
- return this;
- }
-
- get isEmpty() {
- return this._list.length < 1;
- }
- }
-
-
- const {Emitter} = (() => {
- class Emitter {
-
- on(name, callback) {
- if (!this._events) {
- Emitter.totalCount++;
- this._events = {};
- }
-
- name = name.toLowerCase();
- let e = this._events[name];
- if (!e) {
- e = this._events[name] = new Handler(callback);
- } else {
- e.add(callback);
- }
- if (e.length > 10) {
- Emitter.warnings.push(this);
- }
- return this;
- }
-
- off(name, callback) {
- if (!this._events) {
- return;
- }
-
- name = name.toLowerCase();
- const e = this._events[name];
-
- if (!this._events[name]) {
- return;
- } else if (!callback) {
- delete this._events[name];
- } else {
- e.remove(callback);
-
- if (e.isEmpty) {
- delete this._events[name];
- }
- }
-
- if (Object.keys(this._events).length < 1) {
- delete this._events;
- }
- return this;
- }
-
- once(name, func) {
- const wrapper = (...args) => {
- func(...args);
- this.off(name, wrapper);
- wrapper._original = null;
- };
- wrapper._original = func;
- return this.on(name, wrapper);
- }
-
- clear(name) {
- if (!this._events) {
- return;
- }
-
- if (name) {
- delete this._events[name];
- } else {
- delete this._events;
- Emitter.totalCount--;
- }
- return this;
- }
-
- emit(name, ...args) {
- if (!this._events) {
- return;
- }
-
- name = name.toLowerCase();
- const e = this._events[name];
-
- if (!e) {
- return;
- }
-
- e.exec(...args);
- return this;
- }
-
- emitAsync(...args) {
- if (!this._events) {
- return;
- }
-
- setTimeout(() => {
- this.emit(...args);
- }, 0);
- return this;
- }
- }
-
- Emitter.totalCount = 0;
- Emitter.warnings = [];
-
- return {
- Emitter
- };
- })();
-
- 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);
- }
- }
-
- 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);
- }
- 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() {
- let i, len;
- this._pov = '';
- this._povRepeat = 0;
-
- for (i = 0, len = this._gamepadStatus.buttons.length + 16; i < len; i++) {
- this._buttons[i] = {pressed: false, repeat: 0};
- }
- for (i = 0, len = this._gamepadStatus.axes.length; i < len; i++) {
- this._axes[i] = {value: null, repeat: 0};
- }
- }
-
- update() {
- let gamepadStatus = (navigator.getGamepads())[this._index];
-
- if (!gamepadStatus || !gamepadStatus.connected) { console.log('no status'); return; }
-
- if (!this._isRepeating && this._lastTimestamp === gamepadStatus.timestamp) {
- return;
- }
- this._gamepadStatus = gamepadStatus;
- this._lastTimestamp = gamepadStatus.timestamp;
-
- let buttons = gamepadStatus.buttons, axes = gamepadStatus.axes;
- let i, len, axis, isRepeating = false;
-
- for (i = 0, len = Math.min(this._buttons.length, buttons.length); i < len; i++) {
- let buttonStatus = buttons[i].pressed ? 1 : 0;
-
- if (this._buttons[i].pressed !== buttonStatus) {
- let eventName = (buttonStatus === 1) ? 'onButtonDown' : 'onButtonUp';
- 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 (i = 0, len = Math.min(8, this._axes.length); i < len; i++) {
- axis = Math.round(axes[i] * 1000) / 1000;
-
- if (this._axes[i].value === null) {
- this._axes[i].value = axis;
- continue;
- }
-
- let 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;
- }
- {
- const b = 100000;
- const axis = Math.trunc(axes[9] * b);
- const margin = b / 10;
- let pov = '';
- 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;
- }
- });
- }
- 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;
- }
-
- dump() {
- let gamepadStatus = this._gamepadStatus, buttons = gamepadStatus.buttons, axes = gamepadStatus.axes;
- let i, len, btmp = [], atmp = [];
- for (i = 0, len = axes.length; i < len; i++) {
- atmp.push('ax' + i + ': ' + axes[i]);
- }
- for (i = 0, len = buttons.length; i < len; i++) {
- btmp.push('bt' + i + ': ' + (buttons[i].pressed ? 1 : 0));
- }
- return atmp.join('\n') + '\n' + btmp.join(', ');
- }
-
- getButtonStatus(index) {
- return this._buttons[index] || 0;
- }
-
- getAxisValue(index) {
- return this._axes[index] || 0;
- }
-
- release() {
- this.clear();
- }
-
- 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;
- }
- }
-
- const noop = () => {};
-
- const JoyRemocon = (() => {
- let activeGamepad = null;
- let pollingTimer = null;
- let emitter = new Emitter();
- let unlinked = false;
-
- const detectGamepad = () => {
- if (activeGamepad) {
- return;
- }
- const gamepads = navigator.getGamepads();
- if (gamepads.length < 1) {
- return;
- }
- const pad = Array.from(gamepads).reverse().find(pad => {
- return pad &&
- pad.connected &&
- pad.id.match(/^Joy-Con/i);
- });
- if (!pad) { return; }
-
- window.console.log(
- '%cdetect gamepad index: %s, id: "%s", buttons: %s, axes: %s',
- 'background: lightgreen; font-weight: bolder;',
- pad.index, pad.id, pad.buttons.length, pad.axes.length
- );
-
- const gamepad = new GamePad(pad);
- activeGamepad = gamepad;
-
- gamepad.on('onButtonDown',
- number => emitter.emit('onButtonDown', number, gamepad.deviceIndex));
- gamepad.on('onButtonRepeat',
- number => emitter.emit('onButtonRepeat', number, gamepad.deviceIndex));
- gamepad.on('onButtonUp',
- number => emitter.emit('onButtonUp', number, gamepad.deviceIndex));
- gamepad.on('onPovChange',
- pov => emitter.emit('onPovChange', pov, gamepad.deviceIndex));
- gamepad.on('onPovRepeat',
- pov => emitter.emit('onPovRepeat', pov, gamepad.deviceIndex));
-
- emitter.emit('onDeviceConnect', gamepad.deviceIndex, gamepad.deviceId);
-
- pollingTimer.changeInterval(30);
- };
-
-
- const onGamepadConnectStatusChange = (e, isConnected) => {
- console.log('onGamepadConnetcStatusChange', e, e.gamepad.index, isConnected);
-
- if (isConnected) {
- console.log('%cgamepad connected id:"%s"', 'background: lightblue;', e.gamepad.id);
- detectGamepad();
- } else {
- emitter.emit('onDeviceDisconnect', activegamepad.deviceIndex);
- // 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 (unlinked) {
- return;
- }
- if (!activeGamepad) {
- return detectGamepad();
- }
- if (!activeGamepad.isConnected) {
- return;
- }
- activeGamepad.update();
- };
-
- pollingTimer = new PollingTimer(onTimerInterval, 1000);
- };
-
- const initializeGamepadConnectEvent = () => {
- console.log('%cinitializeGamepadConnectEvent', 'background: lightgreen;');
-
- window.addEventListener('gamepadconnected',
- function(e) { onGamepadConnectStatusChange(e, true); });
- window.addEventListener('gamepaddisconnected',
- function(e) { onGamepadConnectStatusChange(e, false); });
-
- if (activeGamepad) {
- return;
- }
- window.setTimeout(detectGamepad, 1000);
- };
-
-
- let hasStartDetect = false;
- return {
- on: (...args) => { emitter.on(...args); },
- startDetect: () => {
- if (hasStartDetect) { return; }
- hasStartDetect = true;
- initializeTimer();
- initializeGamepadConnectEvent();
- },
- startPolling: () => {
- if (pollingTimer) { pollingTimer.start(); }
- },
- stopPolling: () => {
- if (pollingTimer) { pollingTimer.pause(); }
- },
- unlink: () => {
- if (!activeGamepad) {
- return;
- }
- unlinked = true;
- activeGamepad.release();
- activeGamepad = null;
- pollingTimer.changeInterval(1000);
- execCommand(
- 'notify',
- 'JoyRemocon と切断しました'
- );
- }
- };
- })();
-
-
- const initGamepad = () => {
-
- let isActivated = false;
- let deviceId, deviceIndex;
-
- let notifyDetect = () => {
- if (!document.hasFocus()) { return; }
- isActivated = true;
- notifyDetect = noop;
-
- // 初めてボタンかキーが押されたタイミングで通知する
- execCommand(
- 'notify',
- 'ゲームパッド "' + deviceId + '" とリンクしました'
- );
- };
-
-
- let bindEvents = () => {
- bindEvents = noop;
-
- JoyRemocon.on('onButtonDown', number => {
- notifyDetect();
- if (!isActivated) { return; }
- onButtonDown(number, deviceId);
- });
- JoyRemocon.on('onButtonRepeat', number => {
- if (!isActivated) { return; }
- onButtonRepeat(number, deviceId);
- });
- JoyRemocon.on('onButtonUp', number => {
- if (!isActivated) { return; }
- onButtonUp(number, deviceId);
- });
- JoyRemocon.on('onPovChange', pov => {
- if (!isActivated) { return; }
- onPovChange(pov, deviceId);
- });
- JoyRemocon.on('onPovRepeat', pov => {
- if (!isActivated) { return; }
- onPovRepeat(pov, deviceId);
- });
- };
-
- let onDeviceConnect = function(index, id) {
- deviceIndex = index;
- deviceId = id;
-
- bindEvents();
- };
-
- JoyRemocon.on('onDeviceConnect', onDeviceConnect);
- JoyRemocon.startDetect();
- };
-
-
- const initialize = () => {
- initGamepad();
- };
-
- initialize();
- };
-
- const script = document.createElement('script');
- script.id = 'JoyRemoconLoader';
- script.setAttribute('type', 'text/javascript');
- script.setAttribute('charset', 'UTF-8');
- script.appendChild(document.createTextNode(`(${monkey})();`));
- document.documentElement.append(script);
-
- })();