{
audioTrack: 'Audio Track',
settings: 'Settings',
level: 'Level',
+ play: 'Play',
+ pause: 'Pause',
+ mute: 'Mute',
+ unmute: 'Unmute',
+ enterFullscreen: 'Enter fullscreen',
+ exitFullscreen: 'Exit fullscreen',
+ enterPictureInPicture: 'Enter picture-in-picture',
+ exitPictureInPicture: 'Exit picture-in-picture',
+ videoProgress: 'Video progress',
+ volume: 'Volume',
+ live: 'LIVE',
},
}
})
diff --git a/src/hooks/useKeyboardShortcuts.test.tsx b/src/hooks/useKeyboardShortcuts.test.tsx
index ab69342..8d1dcda 100644
--- a/src/hooks/useKeyboardShortcuts.test.tsx
+++ b/src/hooks/useKeyboardShortcuts.test.tsx
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { fireEvent, render } from '@testing-library/react'
+import { fireEvent, render, screen } from '@testing-library/react'
import { useKeyboardShortcuts } from './useKeyboardShortcuts'
const { contextState } = vi.hoisted(() => ({
@@ -17,6 +17,23 @@ const TestComponent = ({ enabled = true }: { enabled?: boolean }) => {
return keyboard-shortcuts-test
}
+const ActivePlayerTestComponent = ({ enabled = true }: { enabled?: boolean }) => {
+ useKeyboardShortcuts(enabled)
+
+ return (
+ {
+ if (contextState.value?.containerRef) {
+ contextState.value.containerRef.current = node
+ }
+ }}
+ >
+ keyboard-shortcuts-active-test
+
+ )
+}
+
describe('useKeyboardShortcuts', () => {
beforeEach(() => {
contextState.value = {
@@ -71,4 +88,16 @@ describe('useKeyboardShortcuts', () => {
fireEvent.keyDown(document.querySelector('input')!, { key: 'k' })
expect(contextState.value.togglePlay).not.toHaveBeenCalled()
})
+
+ it('triggers shortcuts only for active/focused player', () => {
+ contextState.value.containerRef = { current: null }
+ render()
+
+ fireEvent.keyDown(window, { key: 'k' })
+ expect(contextState.value.togglePlay).not.toHaveBeenCalled()
+
+ fireEvent.mouseDown(screen.getByTestId('player'))
+ fireEvent.keyDown(window, { key: 'k' })
+ expect(contextState.value.togglePlay).toHaveBeenCalledTimes(1)
+ })
})
diff --git a/src/hooks/useKeyboardShortcuts.ts b/src/hooks/useKeyboardShortcuts.ts
index e8d03a6..1e6c2ad 100644
--- a/src/hooks/useKeyboardShortcuts.ts
+++ b/src/hooks/useKeyboardShortcuts.ts
@@ -1,9 +1,10 @@
-import { useEffect } from 'react'
+import { useEffect, useState } from 'react'
import { usePlayerContext } from '../contexts/PlayerContext'
export const useKeyboardShortcuts = (enabled: boolean = true) => {
const {
videoState,
+ containerRef,
togglePlay,
seek,
setVolume,
@@ -11,13 +12,76 @@ export const useKeyboardShortcuts = (enabled: boolean = true) => {
toggleFullscreen,
togglePictureInPicture,
} = usePlayerContext()
+ const [isActivePlayer, setIsActivePlayer] = useState(false)
+
+ useEffect(() => {
+ if (!enabled) {
+ setIsActivePlayer(false)
+ return
+ }
+
+ const handlePointerDown = (e: MouseEvent | TouchEvent) => {
+ const container = containerRef?.current
+ if (!container) {
+ return
+ }
+
+ const target = e.target as Node | null
+ const isInsidePlayer = !!target && container.contains(target)
+ setIsActivePlayer(isInsidePlayer)
+
+ if (isInsidePlayer && document.activeElement !== container) {
+ container.focus({ preventScroll: true })
+ }
+ }
+
+ const handleFocusIn = (e: FocusEvent) => {
+ const container = containerRef?.current
+ if (!container) {
+ return
+ }
+
+ const target = e.target as Node | null
+ setIsActivePlayer(!!target && container.contains(target))
+ }
+
+ const handleWindowBlur = () => {
+ setIsActivePlayer(false)
+ }
+
+ const container = containerRef?.current
+ if (container && container.contains(document.activeElement)) {
+ setIsActivePlayer(true)
+ }
+
+ document.addEventListener('mousedown', handlePointerDown)
+ document.addEventListener('touchstart', handlePointerDown, { passive: true })
+ document.addEventListener('focusin', handleFocusIn)
+ window.addEventListener('blur', handleWindowBlur)
+
+ return () => {
+ document.removeEventListener('mousedown', handlePointerDown)
+ document.removeEventListener('touchstart', handlePointerDown)
+ document.removeEventListener('focusin', handleFocusIn)
+ window.removeEventListener('blur', handleWindowBlur)
+ }
+ }, [enabled, containerRef])
useEffect(() => {
if (!enabled) return
const handleKeyDown = (e: KeyboardEvent) => {
+ const container = containerRef?.current
+ if (container && !isActivePlayer) {
+ return
+ }
+
// Don't trigger if user is typing in an input
- if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
+ if (
+ e.target instanceof HTMLInputElement ||
+ e.target instanceof HTMLTextAreaElement ||
+ (e.target instanceof HTMLElement && e.target.isContentEditable)
+ ) {
return
}
@@ -108,6 +172,8 @@ export const useKeyboardShortcuts = (enabled: boolean = true) => {
return () => window.removeEventListener('keydown', handleKeyDown)
}, [
enabled,
+ isActivePlayer,
+ containerRef,
videoState.currentTime,
videoState.duration,
videoState.volume,
diff --git a/src/types/index.ts b/src/types/index.ts
index 32506b9..63a2d0b 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -37,12 +37,17 @@ export interface PlayerTheme {
export interface VideoPlayerProps {
src: string
poster?: string
+ protocol?: 'auto' | VideoProtocol
autoplay?: boolean
loop?: boolean
muted?: boolean
volume?: number // 0-1 arası ses seviyesi
playbackRate?: number // Oynatma hızı (0.25, 0.5, 1, 1.5, 2, vb.)
currentTime?: number // Başlangıç zamanı (saniye)
+ crossOrigin?: '' | 'anonymous' | 'use-credentials'
+ preload?: 'none' | 'metadata' | 'auto'
+ playsInline?: boolean
+ controlsList?: string
controls?: boolean
subtitles?: SubtitleTrack[]
theme?: PlayerTheme