Skip to content

Commit

Permalink
Swipe left/right to seek through subtitles on mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
killergerbah committed Jan 6, 2024
1 parent 361ce95 commit e7c6872
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 31 deletions.
18 changes: 18 additions & 0 deletions common/app/components/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ import { Color } from '@material-ui/lab/Alert';
import Alert from './Alert';
import { useSubtitleDomCache } from '../hooks/use-subtitle-dom-cache';
import { useAppKeyBinder } from '../hooks/use-app-key-binder';
import { Direction, useSwipe } from '../hooks/use-swipe';
import './video-player.css';
import i18n from 'i18next';
import { adjacentSubtitle } from '../../key-binder';

interface ExperimentalHTMLVideoElement extends HTMLVideoElement {
readonly audioTracks: any;
Expand Down Expand Up @@ -1256,6 +1258,22 @@ export default function VideoPlayer({
)
);

const handleSwipe = useCallback(
(direction: Direction) => {
const subtitle = adjacentSubtitle(direction === 'left', clock.time(length), subtitles);
if (subtitle) {
playerChannel.currentTime(subtitle.start / 1000);
}
},
[clock, length, subtitles, playerChannel]
);

useSwipe({
onSwipe: handleSwipe,
distance: 50,
ms: 500,
});

if (!playerChannelSubscribed) {
return null;
}
Expand Down
70 changes: 70 additions & 0 deletions common/app/hooks/use-swipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useEffect, useState, useCallback } from 'react';

export type Direction = 'left' | 'right';

interface Touch {
x: number;
timestamp: number;
}

export const useSwipe = ({
onSwipe,
rect,
distance,
ms,
}: {
onSwipe: (direction: Direction) => void;
rect?: DOMRect;
distance: number;
ms: number;
}) => {
const [start, setStart] = useState<Touch>();
const insideRect = useCallback(
(x: number, y: number) => {
if (rect === undefined) {
return true;
}

return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
},
[rect]
);

useEffect(() => {
const onTouchStart = (e: TouchEvent) => {
const x = e.changedTouches[0].clientX;
const y = e.changedTouches[0].clientY;

if (insideRect(x, y)) {
setStart({ x, timestamp: Date.now() });
}
};

document.addEventListener('touchstart', onTouchStart);
return () => document.removeEventListener('touchstart', onTouchStart);
}, [insideRect]);

useEffect(() => {
if (start === undefined) {
return;
}

const onTouchEnd = (e: TouchEvent) => {
const x = e.changedTouches[0].clientX;
const y = e.changedTouches[0].clientY;

if (insideRect(x, y) && Date.now() - start.timestamp <= ms) {
if (start.x >= x + distance) {
onSwipe('right');
} else if (start.x <= x - distance) {
onSwipe('left');
}
}

setStart(undefined);
};

document.addEventListener('touchend', onTouchEnd);
return () => document.removeEventListener('touchend', onTouchEnd);
}, [start, distance, ms, onSwipe, insideRect]);
};
62 changes: 31 additions & 31 deletions common/key-binder/key-binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@ import { SubtitleModel } from '../src/model';
import hotkeys from 'hotkeys-js';
import { KeyBindSet } from '../settings/settings';

export function adjacentSubtitle(forward: boolean, time: number, subtitles: SubtitleModel[]) {
const now = time;
let adjacentSubtitleIndex = -1;
let minDiff = Number.MAX_SAFE_INTEGER;

for (let i = 0; i < subtitles.length; ++i) {
const s = subtitles[i];
const diff = forward ? s.start - now : now - s.start;

if (minDiff <= diff) {
continue;
}

if (forward && now < s.start) {
minDiff = diff;
adjacentSubtitleIndex = i;
} else if (!forward && now > s.start) {
minDiff = diff;
adjacentSubtitleIndex = now < s.end ? Math.max(0, i - 1) : i;
}
}

if (adjacentSubtitleIndex !== -1) {
return subtitles[adjacentSubtitleIndex];
}

return null;
}

export interface KeyBinder {
bindCopy<T extends SubtitleModel = SubtitleModel>(
onCopy: (event: KeyboardEvent, subtitle: T) => void,
Expand Down Expand Up @@ -235,7 +264,7 @@ export class DefaultKeyBinder implements KeyBinder {
return;
}

const subtitle = this._adjacentSubtitle(forward, timeGetter(), subtitles);
const subtitle = adjacentSubtitle(forward, timeGetter(), subtitles);

if (subtitle !== null && subtitle.start >= 0 && subtitle.end >= 0) {
onSeekToSubtitle(event, subtitle);
Expand Down Expand Up @@ -374,7 +403,7 @@ export class DefaultKeyBinder implements KeyBinder {
}

const time = timeGetter();
const subtitle = this._adjacentSubtitle(forward, time, subtitles);
const subtitle = adjacentSubtitle(forward, time, subtitles);

if (subtitle !== null) {
const subtitleStart = subtitle.originalStart;
Expand Down Expand Up @@ -404,35 +433,6 @@ export class DefaultKeyBinder implements KeyBinder {
};
}

_adjacentSubtitle(forward: boolean, time: number, subtitles: SubtitleModel[]) {
const now = time;
let adjacentSubtitleIndex = -1;
let minDiff = Number.MAX_SAFE_INTEGER;

for (let i = 0; i < subtitles.length; ++i) {
const s = subtitles[i];
const diff = forward ? s.start - now : now - s.start;

if (minDiff <= diff) {
continue;
}

if (forward && now < s.start) {
minDiff = diff;
adjacentSubtitleIndex = i;
} else if (!forward && now > s.start) {
minDiff = diff;
adjacentSubtitleIndex = now < s.end ? Math.max(0, i - 1) : i;
}
}

if (adjacentSubtitleIndex !== -1) {
return subtitles[adjacentSubtitleIndex];
}

return null;
}

bindAdjustOffset(
onOffsetChange: (event: KeyboardEvent, newOffset: number) => void,
disabledGetter: () => boolean,
Expand Down

0 comments on commit e7c6872

Please sign in to comment.