diff --git a/src/App/GamepadModal/GamepadDiagram.tsx b/src/App/GamepadModal/GamepadDiagram.tsx
index 3747e68ed..fa89154fe 100644
--- a/src/App/GamepadModal/GamepadDiagram.tsx
+++ b/src/App/GamepadModal/GamepadDiagram.tsx
@@ -9,6 +9,7 @@ type ActiveButton = string | null;
const CX = 400;
const BTN = { L1: 'L1', L2: 'L2', R1: 'R1', R2: 'R2' };
+const ARROW = { UP: '↑', DOWN: '↓', LEFT: '←', RIGHT: '→' };
const GamepadDiagram = () => {
const { t } = useTranslation();
@@ -30,6 +31,7 @@ const GamepadDiagram = () => {
gamepad?.on('buttonLT', 'gamepad-diagram', flash('l1'));
gamepad?.on('buttonRT', 'gamepad-diagram', flash('r1'));
gamepad?.on('analog', 'gamepad-diagram', (dir: string) => flash('stick-' + dir)());
+ gamepad?.on('analogRight', 'gamepad-diagram', (dir: string) => flash('rstick-' + dir)());
return () => {
clearTimeout(timeout);
@@ -40,6 +42,7 @@ const GamepadDiagram = () => {
gamepad?.off('buttonLT', 'gamepad-diagram');
gamepad?.off('buttonRT', 'gamepad-diagram');
gamepad?.off('analog', 'gamepad-diagram');
+ gamepad?.off('analogRight', 'gamepad-diagram');
};
}, [gamepad]);
@@ -168,8 +171,14 @@ const GamepadDiagram = () => {
{/* ===== RIGHT STICK (CX+STX) ===== */}
-
-
+
+
+
+ {ARROW.UP}
+ {ARROW.DOWN}
+ {ARROW.LEFT}
+ {ARROW.RIGHT}
+
{/* ============================= */}
{/* ===== LABELS — LEFT ===== */}
@@ -184,7 +193,6 @@ const GamepadDiagram = () => {
{t('GAMEPAD_ACTION_NAVIGATE')}
- {t('GAMEPAD_LABEL_STICK_PLAYER')}
{/* □ Square */}
@@ -216,6 +224,11 @@ const GamepadDiagram = () => {
{t('GAMEPAD_ACTION_SELECT')}
{t('GAMEPAD_LABEL_PLAY_PAUSE_PLAYER')}
+ {/* Right stick */}
+
+
+ {t('GAMEPAD_LABEL_SEEK_VOL')}
+
{/* Compat note */}
{t('GAMEPAD_LABEL_COMPAT')}
diff --git a/src/App/GamepadModal/GamepadModal.tsx b/src/App/GamepadModal/GamepadModal.tsx
index c3f68940c..4a6cd0c0a 100644
--- a/src/App/GamepadModal/GamepadModal.tsx
+++ b/src/App/GamepadModal/GamepadModal.tsx
@@ -14,6 +14,7 @@ const CIRCLE = '○';
const TRIANGLE = '△';
const SQUARE = '□';
const L_STICK = 'L stick';
+const R_STICK = 'R stick';
const L1 = 'L1';
const R1 = 'R1';
const LEFT = '←';
@@ -108,22 +109,22 @@ const GamepadModal = ({ onClose }: Props) => {
{t('GAMEPAD_ACTION_PLAY_PAUSE')}
- {L_STICK}
+ {R_STICK}
{LEFT}
{t('GAMEPAD_ACTION_SEEK_BACK')}
- {L_STICK}
+ {R_STICK}
{RIGHT}
{t('GAMEPAD_ACTION_SEEK_FWD')}
- {L_STICK}
+ {R_STICK}
{UP}
{t('GAMEPAD_ACTION_VOL_UP')}
- {L_STICK}
+ {R_STICK}
{DOWN}
{t('GAMEPAD_ACTION_VOL_DOWN')}
diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js
index 382447f0f..dee1c1983 100644
--- a/src/routes/Player/Player.js
+++ b/src/routes/Player/Player.js
@@ -8,6 +8,7 @@ const langs = require('langs');
const { useTranslation } = require('react-i18next');
const { useRouteFocused } = require('stremio-router');
const { useServices, useGamepad } = require('stremio/services');
+const { useContentGamepadNavigation } = require('stremio/services/GamepadNavigation');
const { onFileDrop, useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell, usePlatform, onShortcut } = require('stremio/common');
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
const BufferingLoader = require('./BufferingLoader');
@@ -60,6 +61,7 @@ const Player = ({ urlParams, queryParams }) => {
});
const playbackDevices = React.useMemo(() => streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Ready' ? streamingServer.playbackDevices.content : [], [streamingServer]);
+ const playerRef = React.useRef(null);
const bufferingRef = React.useRef();
const errorRef = React.useRef();
@@ -444,13 +446,15 @@ const Player = ({ urlParams, queryParams }) => {
}
}, [onSeekPrev, onSeekNext, onVolumeUp, onVolumeDown]);
+ useContentGamepadNavigation(playerRef, GAMEPAD_HANDLER_ID);
+
React.useEffect(() => {
gamepad?.on('buttonA', GAMEPAD_HANDLER_ID, onPlayPause);
- gamepad?.on('analog', GAMEPAD_HANDLER_ID, onGamepadSeekAndVol);
+ gamepad?.on('analogRight', GAMEPAD_HANDLER_ID, onGamepadSeekAndVol);
return () => {
gamepad?.off('buttonA', GAMEPAD_HANDLER_ID);
- gamepad?.off('analog', GAMEPAD_HANDLER_ID);
+ gamepad?.off('analogRight', GAMEPAD_HANDLER_ID);
};
}, [onPlayPause, onGamepadSeekAndVol]);
@@ -969,7 +973,7 @@ const Player = ({ urlParams, queryParams }) => {
}, []);
return (
- ([]);
const lastButtonPressedTime = useRef(0);
const axisTimer = useRef(0);
+ const axisTimerRight = useRef(0);
const eventHandlers = useRef(new Map());
const on = useCallback((event: string, id: string, callback: (data?: any) => void) => {
@@ -170,6 +171,37 @@ const GamepadProvider: React.FC<{
}
if (axisHandled) axisTimer.current = currentTime;
+
+ let rightAxisHandled = false;
+
+ if (controller.axes.length > 2) {
+ if (controller.axes[2] < -deadZone) {
+ if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[2]) * 2000)) {
+ emit('analogRight', 'left');
+ rightAxisHandled = true;
+ }
+ }
+ if (controller.axes[2] > deadZone) {
+ if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[2]) * 2000)) {
+ emit('analogRight', 'right');
+ rightAxisHandled = true;
+ }
+ }
+ if (controller.axes[3] < -deadZone) {
+ if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[3]) * 2000)) {
+ emit('analogRight', 'up');
+ rightAxisHandled = true;
+ }
+ }
+ if (controller.axes[3] > deadZone) {
+ if (currentTime - axisTimerRight.current > maxSpeed + (2000 - Math.abs(controller.axes[3]) * 2000)) {
+ emit('analogRight', 'down');
+ rightAxisHandled = true;
+ }
+ }
+ }
+
+ if (rightAxisHandled) axisTimerRight.current = currentTime;
});
}
animationFrameId = requestAnimationFrame(updateStatus);