diff --git a/common/app/components/VideoPlayer.tsx b/common/app/components/VideoPlayer.tsx index 9074cc27..66bfd112 100755 --- a/common/app/components/VideoPlayer.tsx +++ b/common/app/components/VideoPlayer.tsx @@ -25,7 +25,12 @@ import { PauseOnHoverMode, allTextSubtitleSettings, } from '@project/common/settings'; -import { surroundingSubtitles, mockSurroundingSubtitles, seekWithNudge } from '@project/common/util'; +import { + surroundingSubtitles, + mockSurroundingSubtitles, + seekWithNudge, + surroundingSubtitlesAroundInterval, +} from '@project/common/util'; import { SubtitleCollection } from '@project/common/subtitle-collection'; import SubtitleTextImage from '@project/common/components/SubtitleTextImage'; import Clock from '../services/clock'; @@ -1097,7 +1102,7 @@ export default function VideoPlayer({ const endTimestamp = clock.time(length); if (endTimestamp > mineIntervalStartTimestamp) { - const currentSubtitle = { + let currentSubtitle: SubtitleModel = { text: '', start: mineIntervalStartTimestamp, originalStart: mineIntervalStartTimestamp, @@ -1105,8 +1110,25 @@ export default function VideoPlayer({ originalEnd: endTimestamp, track: 0, }; + let surroundingSubtitles: SubtitleModel[]; + + if (subtitles.length === 0) { + surroundingSubtitles = mockSurroundingSubtitles(currentSubtitle, length, 5000); + } else { + const calculated = surroundingSubtitlesAroundInterval( + subtitles, + mineIntervalStartTimestamp, + endTimestamp, + settings.surroundingSubtitlesCountRadius, + settings.surroundingSubtitlesTimeRadius + ); + currentSubtitle = { + ...currentSubtitle, + text: calculated.subtitle?.text ?? '', + }; + surroundingSubtitles = calculated.surroundingSubtitles ?? []; + } - const surroundingSubtitles = mockSurroundingSubtitles(currentSubtitle, length, 5000); mineSubtitle( postMineAction, videoFile, @@ -1133,6 +1155,9 @@ export default function VideoPlayer({ selectedAudioTrack, videoFile, videoFileName, + subtitles, + settings.surroundingSubtitlesCountRadius, + settings.surroundingSubtitlesTimeRadius, ] ); @@ -1146,10 +1171,18 @@ export default function VideoPlayer({ if (!subtitle && !surroundingSubtitles && subtitles.length === 0) { toggleSelectMiningInterval(postMineAction, cardTextFieldValues); } else { + if (mineIntervalStartTimestamp !== undefined) { + // Edge case: user started manually recording but are now using an "automatic" mining shortcut + // Cancel the "recording" operation + setAlertDisableAutoHide(false); + setAlertOpen(false); + setMineIntervalStartTimestamp(undefined); + } + mineCurrentSubtitle(postMineAction, subtitle, surroundingSubtitles, cardTextFieldValues); } }, - [mineCurrentSubtitle, toggleSelectMiningInterval, subtitles] + [mineCurrentSubtitle, toggleSelectMiningInterval, mineIntervalStartTimestamp, subtitles] ); useEffect(() => { @@ -1222,6 +1255,16 @@ export default function VideoPlayer({ ); }, [clock, length, keyBinder, lastMinedRecord, mineSubtitle, popOut, ankiDialogOpen, onAnkiDialogRewind]); + useEffect(() => { + return keyBinder.bindToggleRecording( + (event) => { + event.preventDefault(); + toggleSelectMiningInterval(PostMineAction.showAnkiDialog); + }, + () => false + ); + }, [keyBinder, toggleSelectMiningInterval]); + useEffect(() => { return keyBinder.bindCopy( (event, subtitle) => { diff --git a/common/app/services/app-key-binder.ts b/common/app/services/app-key-binder.ts index 908c0c16..4ecabd94 100644 --- a/common/app/services/app-key-binder.ts +++ b/common/app/services/app-key-binder.ts @@ -9,6 +9,7 @@ export default class AppKeyBinder implements KeyBinder { private readonly ankiExportHandlers: ((event: KeyboardEvent) => void)[] = []; private readonly updateLastCardHandlers: ((event: KeyboardEvent) => void)[] = []; private readonly takeScreenshotHandlers: ((event: KeyboardEvent) => void)[] = []; + private readonly toggleRecordingHandlers: ((event: KeyboardEvent) => void)[] = []; private _unsubscribeExtension?: () => void; constructor(keyBinder: DefaultKeyBinder, extension: ChromeExtension) { @@ -36,6 +37,8 @@ export default class AppKeyBinder implements KeyBinder { } } else if (message.data.command === 'take-screenshot') { handlers = this.takeScreenshotHandlers; + } else if (message.data.command === 'toggle-recording') { + handlers = this.toggleRecordingHandlers; } if (handlers !== undefined) { @@ -113,6 +116,22 @@ export default class AppKeyBinder implements KeyBinder { return this.defaultKeyBinder.bindTakeScreenshot(onTakeScreenshot, disabledGetter, useCapture); } + bindToggleRecording( + onToggleRecording: (event: KeyboardEvent) => void, + disabledGetter: () => boolean, + useCapture?: boolean | undefined + ): () => void { + if (this.extension.installed) { + const handler = this.defaultKeyBinder.toggleRecordingHandler(onToggleRecording, disabledGetter); + this.toggleRecordingHandlers.push(handler); + return () => { + this._remove(handler, this.toggleRecordingHandlers); + }; + } + + return this.defaultKeyBinder.bindToggleRecording(onToggleRecording, disabledGetter, useCapture); + } + private _remove(callback: (event: KeyboardEvent) => void, list: ((event: KeyboardEvent) => void)[]) { for (let i = list.length - 1; i >= 0; --i) { if (callback === list[i]) { diff --git a/common/components/SettingsForm.tsx b/common/components/SettingsForm.tsx index 0718ce47..0321c77a 100644 --- a/common/components/SettingsForm.tsx +++ b/common/components/SettingsForm.tsx @@ -309,7 +309,7 @@ function SelectableSetting({ ); } -type AllKeyNames = KeyBindName | 'toggleRecording' | 'selectSubtitleTrack'; +type AllKeyNames = KeyBindName | 'selectSubtitleTrack'; interface KeyBindProperties { label: string; @@ -709,7 +709,6 @@ export default function SettingsForm({ toggleRecording: { label: t('binds.extensionToggleRecording')!, boundViaChrome: true, - hide: !extensionInstalled, }, selectSubtitleTrack: { label: t('binds.extensionSelectSubtitleTrack')!, diff --git a/common/key-binder/key-binder.ts b/common/key-binder/key-binder.ts index 049a59ed..b2f7dd30 100644 --- a/common/key-binder/key-binder.ts +++ b/common/key-binder/key-binder.ts @@ -141,6 +141,11 @@ export interface KeyBinder { disabledGetter: () => boolean, capture?: boolean ): () => void; + bindToggleRecording( + onToggleRecording: (event: KeyboardEvent) => void, + disabledGetter: () => boolean, + capture?: boolean + ): () => void; } export class DefaultKeyBinder implements KeyBinder { @@ -260,6 +265,32 @@ export class DefaultKeyBinder implements KeyBinder { }; } + bindToggleRecording( + onToggleRecording: (event: KeyboardEvent) => void, + disabledGetter: () => boolean, + capture = false + ) { + const shortcut = this.keyBindSet.toggleRecording.keys; + + if (!shortcut) { + return () => {}; + } + + const handler = this.takeScreenshotHandler(onToggleRecording, disabledGetter); + return this._bind(shortcut, capture, handler); + } + + toggleRecordingHandler(onToggleRecording: (event: KeyboardEvent) => void, disabledGetter: () => boolean) { + return (event: KeyboardEvent) => { + if (disabledGetter()) { + return false; + } + + onToggleRecording(event); + return true; + }; + } + bindSeekToSubtitle( onSeekToSubtitle: (event: KeyboardEvent, subtitle: SubtitleModel) => void, disabledGetter: () => boolean, diff --git a/common/locales/en.json b/common/locales/en.json index 57bfd582..52ce9153 100644 --- a/common/locales/en.json +++ b/common/locales/en.json @@ -243,7 +243,7 @@ "error": "Error: {{message}}", "errorNoMessage": "Error", "toggleSubtitlesShortcut": "Press \"{{keys}}\" to toggle subtitle display", - "manualMiningIntervalPrompt": "Use mining shortcut again to select time range" + "manualMiningIntervalPrompt": "Use mining shortcut again" }, "landing": { "cta": "Drag and drop subtitle and media files, or <1>browse.", diff --git a/common/settings/settings-import-export.test.ts b/common/settings/settings-import-export.test.ts index 41f8ba7b..e6a1e6da 100644 --- a/common/settings/settings-import-export.test.ts +++ b/common/settings/settings-import-export.test.ts @@ -89,6 +89,7 @@ it('validates exported settings', () => { seekToNextSubtitle: { keys: 'right' }, seekToPreviousSubtitle: { keys: 'left' }, takeScreenshot: { keys: '⇧+⌃+V' }, + toggleRecording: { keys: '⇧+⌃+R' }, toggleAsbplayerSubtitleTrack1: { keys: 'W+1' }, toggleAsbplayerSubtitleTrack2: { keys: 'W+2' }, unblurAsbplayerTrack1: { keys: 'B+1' }, diff --git a/common/settings/settings-import-export.ts b/common/settings/settings-import-export.ts index 55b85940..808c9ce0 100644 --- a/common/settings/settings-import-export.ts +++ b/common/settings/settings-import-export.ts @@ -255,6 +255,7 @@ const settingsSchema = { ankiExport: { $ref: '/KeyBind' }, updateLastCard: { $ref: '/KeyBind' }, takeScreenshot: { $ref: '/KeyBind' }, + toggleRecording: { $ref: '/KeyBind' }, decreasePlaybackRate: { $ref: '/KeyBind' }, increasePlaybackRate: { $ref: '/KeyBind' }, toggleSidePanel: { $ref: '/KeyBind' }, diff --git a/common/settings/settings-provider.ts b/common/settings/settings-provider.ts index 03b79eeb..c714f455 100755 --- a/common/settings/settings-provider.ts +++ b/common/settings/settings-provider.ts @@ -102,6 +102,7 @@ export const defaultSettings: AsbplayerSettings = { ankiExport: { keys: isMacOs ? '⇧+⌃+X' : 'ctrl+shift+X' }, updateLastCard: { keys: isMacOs ? '⇧+⌃+U' : 'ctrl+shift+U' }, takeScreenshot: { keys: isMacOs ? '⇧+⌃+V' : 'ctrl+shift+V' }, + toggleRecording: { keys: isMacOs ? '⇧+⌃+R' : 'ctrl+shift+R' }, decreasePlaybackRate: { keys: isMacOs ? '⇧+⌃+[' : 'ctrl+shift+[' }, increasePlaybackRate: { keys: isMacOs ? '⇧+⌃+]' : 'ctrl+shift+]' }, toggleSidePanel: { keys: '`' }, diff --git a/common/settings/settings.ts b/common/settings/settings.ts index 7f07c533..9dfdb2d1 100755 --- a/common/settings/settings.ts +++ b/common/settings/settings.ts @@ -239,6 +239,7 @@ export interface KeyBindSet { readonly ankiExport: KeyBind; readonly updateLastCard: KeyBind; readonly takeScreenshot: KeyBind; + readonly toggleRecording: KeyBind; } export interface WebSocketClientSettings { diff --git a/extension/src/background.ts b/extension/src/background.ts index 6a9aa026..cd138109 100755 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -344,6 +344,22 @@ chrome.commands?.onCommand.addListener((command) => { }; return extensionToVideoCommand; }); + tabRegistry.publishCommandToAsbplayers({ + commandFactory: (asbplayer) => { + if (!validAsbplayer(asbplayer)) { + return undefined; + } + + const extensionToPlayerCommand: ExtensionToAsbPlayerCommand = { + sender: 'asbplayer-extension-to-player', + message: { + command: 'toggle-recording', + }, + asbplayerId: asbplayer.id, + }; + return extensionToPlayerCommand; + }, + }); break; default: throw new Error('Unknown command ' + command);