// ==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);
})();