fix four audit bugs - offline playback, audio passthrough, notification dedup, dashboard fixes

This commit is contained in:
2026-04-06 04:33:21 +03:00
parent 0af7ba64cb
commit 1a84b348ad
5 changed files with 19 additions and 40 deletions
+3 -1
View File
@@ -4,6 +4,7 @@ import { jellyfinClient, getItemsApi, getTvShowsApi, getUserViewsApi, getSearchA
import { getLiveTvApi } from '@jellyfin/sdk/lib/utils/api/live-tv-api'
import { debugLog } from '../lib/log'
import { browserDeviceProfile } from '../lib/device-profile'
import { usePreferencesStore } from '../stores/preferences-store'
function useApi() {
return jellyfinClient.getApi()
@@ -621,6 +622,7 @@ export function usePlaybackInfo(
maxStreamingBitrate?: number,
) {
const api = useApi()
const audioPassthrough = usePreferencesStore(s => s.audioPassthrough)
return useQuery({
queryKey: [
'jellyfin',
@@ -644,7 +646,7 @@ export function usePlaybackInfo(
// picks the default; when set, the returned TranscodingUrl muxes
// that audio track into the stream.
AudioStreamIndex: audioStreamIndex,
DeviceProfile: browserDeviceProfile() as any,
DeviceProfile: browserDeviceProfile(audioPassthrough) as any,
AutoOpenLiveStream: true,
EnableDirectPlay: true,
EnableDirectStream: true,
+2 -2
View File
@@ -13,7 +13,7 @@ export function useNewReleaseNotifications(enabled: boolean) {
const qc = useQueryClient()
const lastNotifiedRef = useRef<string | null>(
(() => {
try { return localStorage.getItem('jf_last_notify_date') } catch { return null }
try { return localStorage.getItem('jf_bell_opened') } catch { return null }
})()
)
@@ -51,7 +51,7 @@ export function useNewReleaseNotifications(enabled: boolean) {
const newest = items[0]?.DateCreated
if (newest) {
lastNotifiedRef.current = newest
try { localStorage.setItem('jf_last_notify_date', newest) } catch { /* noop */ }
try { localStorage.setItem('jf_bell_opened', newest) } catch { /* noop */ }
}
// Refresh the home page recently-added cache
qc.invalidateQueries({ queryKey: ['jellyfin', 'home', 'recentlyAdded'] })
+4 -2
View File
@@ -75,7 +75,7 @@ function supportedVideoRanges(): string {
return ranges.join('|')
}
export function browserDeviceProfile() {
export function browserDeviceProfile(audioPassthrough = false) {
const videoCodecs = supportedVideoCodecs()
const videoCodecsCsv = videoCodecs.join(',')
const videoRanges = supportedVideoRanges()
@@ -91,7 +91,9 @@ export function browserDeviceProfile() {
Container: 'mp4,m4v',
Type: 'Video',
VideoCodec: videoCodecsCsv,
AudioCodec: 'aac,mp3,ac3,eac3,flac,opus',
AudioCodec: audioPassthrough
? 'aac,mp3,ac3,eac3,flac,opus,truehd,dts'
: 'aac,mp3,ac3,eac3,flac,opus',
},
{
Container: 'webm',
+7
View File
@@ -291,6 +291,12 @@ export default function PlayerPage() {
// native audioTracks switch isn't possible (transcoded streams, or
// single-audio sources where the alternate track isn't in the file).
// maxBitrate threads the user-picked quality cap into the same call.
// Offline fallback: if this item was downloaded, use the Blob URL
// instead of hitting the server. PlaybackInfo and reporting gracefully
// no-op when there's no connection.
const downloaded = useDownloads(s => s.getByItemId(id || ''))
const offlineUrl = downloaded?.status === 'done' ? downloaded.localPath : undefined
const { data: playbackInfo } = usePlaybackInfo(
id,
startTimeTicks,
@@ -299,6 +305,7 @@ export default function PlayerPage() {
)
const resolvedSource = playbackInfo?.MediaSources?.[0]
const streamUrl = (() => {
if (offlineUrl) return offlineUrl
if (!resolvedSource || !serverUrl) return ''
// Direct play: server-confirmed the browser can decode the source as-is
if (resolvedSource.SupportsDirectPlay) {
@@ -1,8 +1,8 @@
import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { motion } from 'framer-motion'
import { Activity, MonitorPlay, Users, Server as ServerIcon, Clock, Film } from '../../../lib/icons'
import { jellyfinClient, getSystemApi, getSessionApi, getActivityLogApi } from '../../../api/jellyfin'
import { Activity, MonitorPlay, Users, Server as ServerIcon, Clock } from '../../../lib/icons'
import { jellyfinClient, getSystemApi, getSessionApi } from '../../../api/jellyfin'
import { Section, SubHeading } from '../_ui'
function useApi() {
@@ -33,17 +33,6 @@ export function ServerDashboardSection() {
refetchInterval: 10_000,
})
const activity = useQuery({
queryKey: ['jellyfin', 'activity-log'],
queryFn: async () => {
if (!api) return []
const res = await getActivityLogApi(api).getLogEntries({ limit: 20 })
return res.data.Items || []
},
enabled: !!api,
refetchInterval: 60_000,
})
const info = serverInfo.data
const activeSessions = sessions.data?.filter(s => s.NowPlayingItem) || []
const transcodes = activeSessions.filter(s => s.PlayState?.PlayMethod === 'Transcode')
@@ -107,28 +96,7 @@ export function ServerDashboardSection() {
</>
)}
{activity.data && activity.data.length > 0 && (
<>
<SubHeading label="Recent activity" />
<div className="space-y-1">
{activity.data.slice(0, 10).map((entry: any, i: number) => (
<motion.div
key={entry.Id || i}
initial={{ opacity: 0, x: -6 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: Math.min(i * 0.03, 0.3) }}
className="flex items-center gap-3 px-2 py-1.5 text-[11.5px]"
>
<Film size={11} className="text-text-4 shrink-0" />
<span className="text-text-3 truncate flex-1">{entry.Name}</span>
<span className="text-text-4 tabular-nums shrink-0">
{entry.DateCreated ? new Date(entry.DateCreated).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }) : ''}
</span>
</motion.div>
))}
</div>
</>
)}
</Section>
)
}