104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
import type { KeyboardShortcutContext } from '~/types/video'
|
|
|
|
export const handlePlayPause = (ctx: KeyboardShortcutContext) => {
|
|
if (ctx.video.paused) {
|
|
ctx.video.play()
|
|
} else {
|
|
ctx.video.pause()
|
|
}
|
|
}
|
|
|
|
export const handleRewind = (ctx: KeyboardShortcutContext, seconds: number = 10) => {
|
|
ctx.video.currentTime = Math.max(0, ctx.video.currentTime - seconds)
|
|
}
|
|
|
|
export const handleForward = (ctx: KeyboardShortcutContext, seconds: number = 10) => {
|
|
ctx.video.currentTime = Math.min(ctx.video.duration, ctx.video.currentTime + seconds)
|
|
}
|
|
|
|
export const handleMute = (ctx: KeyboardShortcutContext) => {
|
|
ctx.video.muted = !ctx.video.muted
|
|
}
|
|
|
|
export const handleVolumeChange = (ctx: KeyboardShortcutContext, delta: number) => {
|
|
const newVolume = Math.max(0, Math.min(1, ctx.video.volume + delta))
|
|
ctx.video.volume = newVolume
|
|
}
|
|
|
|
export const handlePlaybackSpeedChange = (ctx: KeyboardShortcutContext, delta: number) => {
|
|
const currentSpeed = ctx.video.playbackRate
|
|
const newSpeed = Math.max(0.25, Math.min(2, currentSpeed + delta))
|
|
ctx.video.playbackRate = newSpeed
|
|
}
|
|
|
|
export const handleFrameStep = (ctx: KeyboardShortcutContext, direction: 'forward' | 'backward' = 'forward') => {
|
|
if (!ctx.video.paused) {
|
|
ctx.video.pause()
|
|
}
|
|
|
|
// Frame step by ~33ms (approximately 1 frame at 30fps, ~16.7ms at 60fps)
|
|
const frameStep = 'forward' === direction ? 0.033 : -0.033
|
|
ctx.video.currentTime = Math.max(0, Math.min(ctx.video.duration, ctx.video.currentTime + frameStep))
|
|
}
|
|
|
|
export const handleSeekToPercent = (ctx: KeyboardShortcutContext, percent: number) => {
|
|
ctx.video.currentTime = (percent / 100) * ctx.video.duration
|
|
}
|
|
|
|
export const handleSeekBackward = (ctx: KeyboardShortcutContext, seconds: number = 5) => {
|
|
ctx.video.currentTime = Math.max(0, ctx.video.currentTime - seconds)
|
|
}
|
|
|
|
export const handleSeekForward = (ctx: KeyboardShortcutContext, seconds: number = 5) => {
|
|
ctx.video.currentTime = Math.min(ctx.video.duration, ctx.video.currentTime + seconds)
|
|
}
|
|
|
|
export const handleFullscreen = (videoElement: HTMLVideoElement) => {
|
|
if (!document.fullscreenElement) {
|
|
videoElement.requestFullscreen().catch(err => {
|
|
console.error(`Error attempting to enable fullscreen: ${err.message}`)
|
|
})
|
|
} else {
|
|
document.exitFullscreen()
|
|
}
|
|
}
|
|
|
|
export const handlePictureInPicture = async (videoElement: HTMLVideoElement) => {
|
|
try {
|
|
if (document.pictureInPictureElement) {
|
|
await document.exitPictureInPicture()
|
|
} else if (document.pictureInPictureEnabled) {
|
|
await videoElement.requestPictureInPicture()
|
|
}
|
|
} catch (error) {
|
|
console.error(`Picture-in-Picture error: ${error}`)
|
|
}
|
|
}
|
|
|
|
export const handleToggleCaptions = (videoElement: HTMLVideoElement) => {
|
|
const textTracks = videoElement.textTracks
|
|
|
|
if (0 === textTracks.length) {
|
|
return
|
|
}
|
|
|
|
for (let i = 0; i < textTracks.length; i++) {
|
|
const track = textTracks[i] as TextTrack | null
|
|
if (track && ('captions' === track.kind || 'subtitles' === track.kind)) {
|
|
track.mode = 'showing' === track.mode ? 'hidden' : 'showing'
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
export const shouldHandleKeyboardShortcut = (event: KeyboardEvent): boolean => {
|
|
const target = event.target as HTMLElement
|
|
const tagName = target?.tagName?.toLowerCase()
|
|
if ('input' === tagName || 'textarea' === tagName || 'true' === target?.contentEditable || 'true' === target?.getAttribute('contenteditable')) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
export const isModifierKey = (event: KeyboardEvent): boolean => event.ctrlKey || event.metaKey || event.altKey
|