fix hdr playback, lint warnings
This commit is contained in:
@@ -13,7 +13,7 @@ export default function DuplicatesPage() {
|
||||
sortOrder: ['Ascending'],
|
||||
limit: 5000,
|
||||
})
|
||||
const items = data?.Items || []
|
||||
const items = useMemo(() => data?.Items || [], [data?.Items])
|
||||
|
||||
const duplicates = useMemo(() => {
|
||||
// Group by TMDB ID first (most reliable)
|
||||
|
||||
+16
-11
@@ -347,17 +347,19 @@ export default function PlayerPage() {
|
||||
* the resume condition once per item, not re-trigger when item data
|
||||
* refreshes or prefs change mid-playback. */
|
||||
const resumePromptShownRef = useRef<string | null>(null)
|
||||
const resumeItemId = item?.Id
|
||||
const resumePositionTicks = item?.UserData?.PlaybackPositionTicks
|
||||
useEffect(() => {
|
||||
if (!item || resumePromptShownRef.current === item.Id) return
|
||||
const pos = Number(item.UserData?.PlaybackPositionTicks ?? 0)
|
||||
if (!resumeItemId || resumePromptShownRef.current === resumeItemId) return
|
||||
const pos = Number(resumePositionTicks ?? 0)
|
||||
const thresholdSec = usePreferencesStore.getState().resumeThresholdSec ?? 5
|
||||
const threshold = thresholdSec * 10_000_000
|
||||
const fromQueue = searchParams.get('resume') === 'true'
|
||||
if (showResumePromptPref && !fromQueue && pos > threshold) {
|
||||
setResumePromptOpen(true)
|
||||
resumePromptShownRef.current = item.Id ?? null
|
||||
resumePromptShownRef.current = resumeItemId
|
||||
}
|
||||
}, [item?.Id, searchParams, showResumePromptPref])
|
||||
}, [resumeItemId, resumePositionTicks, searchParams, showResumePromptPref])
|
||||
|
||||
/* Auto-rewatch counter: when an already-played item starts playing
|
||||
* again, record the rewatch. We trip it at most once per item-mount
|
||||
@@ -645,23 +647,26 @@ export default function PlayerPage() {
|
||||
const currentMarker: Marker | undefined = markers.find(
|
||||
m => currentTime >= m.startSec && currentTime < m.endSec - 0.5,
|
||||
)
|
||||
const currentMarkerType = currentMarker?.type
|
||||
const currentMarkerStartSec = currentMarker?.startSec
|
||||
const currentMarkerEndSec = currentMarker?.endSec
|
||||
|
||||
/* Auto-skip when entering a marker AND the corresponding pref is on.
|
||||
* Also accumulate the skipped seconds into the per-series tally so the
|
||||
* detail page can surface a "you saved Xh Ym" badge. */
|
||||
useEffect(() => {
|
||||
if (!currentMarker || !playerRef.current) return
|
||||
if (!currentMarkerType || currentMarkerEndSec == null || !playerRef.current) return
|
||||
const p = playerRef.current
|
||||
const from = p.currentTime
|
||||
if (currentMarker.type === 'intro' && skipIntros) {
|
||||
p.currentTime = currentMarker.endSec
|
||||
if (seriesId) recordSkippedSeconds(seriesId, 'intro', currentMarker.endSec - from)
|
||||
} else if (currentMarker.type === 'credits' && skipCredits && duration > 0) {
|
||||
const target = Math.max(currentMarker.endSec, duration - 0.5)
|
||||
if (currentMarkerType === 'intro' && skipIntros) {
|
||||
p.currentTime = currentMarkerEndSec
|
||||
if (seriesId) recordSkippedSeconds(seriesId, 'intro', currentMarkerEndSec - from)
|
||||
} else if (currentMarkerType === 'credits' && skipCredits && duration > 0) {
|
||||
const target = Math.max(currentMarkerEndSec, duration - 0.5)
|
||||
p.currentTime = target
|
||||
if (seriesId) recordSkippedSeconds(seriesId, 'credits', target - from)
|
||||
}
|
||||
}, [currentMarker?.type, currentMarker?.startSec, skipIntros, skipCredits, duration, seriesId])
|
||||
}, [currentMarkerType, currentMarkerStartSec, currentMarkerEndSec, skipIntros, skipCredits, duration, seriesId])
|
||||
|
||||
/* Imperatively switch the active subtitle track. We use 'hidden' rather
|
||||
* than 'showing' so the browser doesn't paint its own caption UI over our
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Activity, MonitorPlay, Users, Server as ServerIcon, Clock } from '../../../lib/icons'
|
||||
import { jellyfinClient, getSystemApi, getSessionApi } from '../../../api/jellyfin'
|
||||
@@ -10,6 +10,15 @@ function useApi() {
|
||||
|
||||
export function ServerDashboardSection() {
|
||||
const api = useApi()
|
||||
const [now, setNow] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const update = () => setNow(Date.now())
|
||||
update()
|
||||
const id = window.setInterval(update, 60_000)
|
||||
return () => window.clearInterval(id)
|
||||
}, [])
|
||||
|
||||
const serverInfo = useQuery({
|
||||
queryKey: ['jellyfin', 'system-info'],
|
||||
queryFn: async () => {
|
||||
@@ -37,13 +46,14 @@ export function ServerDashboardSection() {
|
||||
const transcodes = activeSessions.filter(s => s.PlayState?.PlayMethod === 'Transcode')
|
||||
|
||||
const uptime = useMemo(() => {
|
||||
if (!now) return null
|
||||
const start = (info as { ServerStartTime?: string } | null | undefined)?.ServerStartTime
|
||||
if (!start) return null
|
||||
const ms = Date.now() - new Date(start).getTime()
|
||||
const ms = now - new Date(start).getTime()
|
||||
const days = Math.floor(ms / 86_400_000)
|
||||
const hrs = Math.floor((ms % 86_400_000) / 3_600_000)
|
||||
return days > 0 ? `${days}d ${hrs}h` : `${hrs}h`
|
||||
}, [info])
|
||||
}, [info, now])
|
||||
|
||||
return (
|
||||
<Section id="server-dashboard" title="Server dashboard" description="Live stats and activity">
|
||||
|
||||
@@ -69,24 +69,6 @@ export function ShortcutsSection() {
|
||||
const setPreference = usePreferencesStore(s => s.setPreference)
|
||||
const [capturingId, setCapturingId] = useState<string | null>(null)
|
||||
|
||||
function commit(id: string, binding: string) {
|
||||
const conflict = Object.entries({ ...overrides })
|
||||
.filter(([k]) => k !== id)
|
||||
.find(([, keys]) => (keys as string[]).some(k => normalizeBinding(k) === binding))
|
||||
const defaultConflict = SHORTCUTS.find(sc => sc.id !== id && sc.keys.some(k => normalizeBinding(k) === binding) && !overrides[sc.id])
|
||||
if (conflict) {
|
||||
const other = SHORTCUTS.find(s => s.id === conflict[0])
|
||||
toast(`That combo is bound to "${other?.description || conflict[0]}"`, 'error')
|
||||
return
|
||||
}
|
||||
if (defaultConflict) {
|
||||
toast(`That combo is bound to "${defaultConflict.description}"`, 'error')
|
||||
return
|
||||
}
|
||||
setPreference('keyboardShortcuts', { ...overrides, [id]: [binding] })
|
||||
toast('Shortcut updated', 'success')
|
||||
}
|
||||
|
||||
function reset(id: string) {
|
||||
const next = { ...overrides }
|
||||
delete next[id]
|
||||
@@ -101,6 +83,23 @@ export function ShortcutsSection() {
|
||||
useEffect(() => {
|
||||
if (!capturingId) return
|
||||
const id = capturingId
|
||||
function commit(binding: string) {
|
||||
const conflict = Object.entries({ ...overrides })
|
||||
.filter(([k]) => k !== id)
|
||||
.find(([, keys]) => (keys as string[]).some(k => normalizeBinding(k) === binding))
|
||||
const defaultConflict = SHORTCUTS.find(sc => sc.id !== id && sc.keys.some(k => normalizeBinding(k) === binding) && !overrides[sc.id])
|
||||
if (conflict) {
|
||||
const other = SHORTCUTS.find(s => s.id === conflict[0])
|
||||
toast(`That combo is bound to "${other?.description || conflict[0]}"`, 'error')
|
||||
return
|
||||
}
|
||||
if (defaultConflict) {
|
||||
toast(`That combo is bound to "${defaultConflict.description}"`, 'error')
|
||||
return
|
||||
}
|
||||
setPreference('keyboardShortcuts', { ...overrides, [id]: [binding] })
|
||||
toast('Shortcut updated', 'success')
|
||||
}
|
||||
function onKey(e: KeyboardEvent) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -113,11 +112,11 @@ export function ShortcutsSection() {
|
||||
}
|
||||
const binding = eventToBinding(e)
|
||||
setCapturingId(null)
|
||||
commit(id, binding)
|
||||
commit(binding)
|
||||
}
|
||||
window.addEventListener('keydown', onKey, true)
|
||||
return () => window.removeEventListener('keydown', onKey, true)
|
||||
}, [capturingId, overrides, commit])
|
||||
}, [capturingId, overrides, setPreference])
|
||||
|
||||
return (
|
||||
<Section
|
||||
|
||||
Reference in New Issue
Block a user