fix four audit bugs - offline playback, audio passthrough, notification dedup, dashboard fixes
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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'] })
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user