Skip to content

Commit 3217a01

Browse files
committed
feat: implement caching mechanism and refactor player component
1 parent d0a66a7 commit 3217a01

7 files changed

Lines changed: 136 additions & 63 deletions

File tree

.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ VITE_ENABLE_PLAYBACK_TRACKER=false
33
VITE_TIMEZONE=America/New_York
44
# Available adapters: NetworkFile, GarlicHub
55
VITE_CMS_ADAPTER=GarlicHub
6-
VITE_CMS_ADAPTER_URL=https://garlic-hub.com/
6+
# URL examples: /demo/playlist_data.json, https://garlic-hub.com
7+
VITE_CMS_ADAPTER_URL=https://garlic-hub.com

src/App.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,37 @@
11
import { useMemo } from 'react'
22
import { useCMSAdapter } from './hooks/useCMSAdapter'
3-
import { useCurrentTimestamp } from './hooks/useCurrentTimestamp'
4-
import { usePlaylist } from './hooks/usePlaylist'
5-
import { usePlaylistCache } from './hooks/usePlaylistCache'
6-
import { PlaylistRenderer } from './PlaylistRenderer'
73
import { getCMSAdapter } from './utils/getCMSAdapter'
4+
import { Player } from './Player'
5+
import { useCachedData } from './hooks/useCachedData'
86

97
export const App = () => {
108
const searchParams = new URLSearchParams(window.location.search)
9+
1110
const adapterParam = searchParams.get('adapter')
1211

1312
const adapter = useMemo(() => getCMSAdapter(adapterParam), [adapterParam])
13+
1414
const data = useCMSAdapter({ adapter})
1515

16-
const timezone = 'America/Los_Angeles'
17-
const currentTimestamp = useCurrentTimestamp(timezone)
18-
19-
const { currentPlaylist, elapsedSinceStart } = usePlaylist(data, currentTimestamp)
20-
const { isPreloaded } = usePlaylistCache(currentPlaylist)
16+
const timezone = import.meta.env.VITE_TIMEZONE
2117

22-
if (!currentPlaylist || elapsedSinceStart === null) {
18+
const { cachedData, isCaching } = useCachedData(data)
19+
20+
if(cachedData.length > 0) return <Player data={ data } timezone={timezone} />
21+
22+
if (!isCaching) {
2323
return (
2424
<div className='bg-black w-screen h-screen overflow-hidden'>
25-
<h1 className='text-white text-3xl font-bold'>No active playlist</h1>
25+
<h1 className='text-white text-3xl font-bold'>Schedule is empty</h1>
2626
</div>
2727
)
2828
}
2929

30-
if (!isPreloaded) {
30+
if (isCaching) {
3131
return (
3232
<div className='bg-black w-screen h-screen overflow-hidden'>
33-
<h1 className='text-white text-3xl font-bold'>Loading playlist...</h1>
33+
<h1 className='text-white text-3xl font-bold'>Loading...</h1>
3434
</div>
3535
)
3636
}
37-
38-
return <PlaylistRenderer playlist={ currentPlaylist } elapsedSinceStart={ elapsedSinceStart } />
3937
}

src/Player.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useCurrentTimestamp } from './hooks/useCurrentTimestamp'
2+
import { usePlaylist } from './hooks/usePlaylist'
3+
import { PlaylistRenderer } from './PlaylistRenderer'
4+
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
export const Player = ({ data, timezone }: { data: any, timezone: string }) => {
7+
const currentTimestamp = useCurrentTimestamp(timezone)
8+
9+
const { currentPlaylist, elapsedSinceStart } = usePlaylist(data, currentTimestamp)
10+
11+
if (!currentPlaylist || elapsedSinceStart === null) {
12+
return (
13+
<div className='bg-black w-screen h-screen overflow-hidden'>
14+
<h1 className='text-white text-3xl font-bold'>No active playlist</h1>
15+
</div>
16+
)
17+
}
18+
19+
return <PlaylistRenderer playlist={ currentPlaylist } elapsedSinceStart={ elapsedSinceStart } />
20+
}

src/hooks/useCachedData.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useEffect, useState } from 'react'
2+
import type { Playlist } from '../types'
3+
4+
const LOCAL_STORAGE_KEY = 'cached_playlists'
5+
6+
// Mock function for caching, can be replaced with real implementation or for testing
7+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8+
export function mockCachePlaylists(playlists: Playlist[]) {
9+
// For now, just log to console
10+
console.log('Mock caching playlists')
11+
return true
12+
}
13+
14+
function arePlaylistsEqual(a: Playlist[], b: Playlist[]): boolean {
15+
return JSON.stringify(a) === JSON.stringify(b)
16+
}
17+
18+
export function useCachedData(playlists: Playlist[] | null) {
19+
const [cachedPlaylists, setCachedPlaylists] = useState<Playlist[]>(() => {
20+
const cachedRaw = localStorage.getItem(LOCAL_STORAGE_KEY)
21+
22+
return cachedRaw ? JSON.parse(cachedRaw) : []
23+
})
24+
25+
const [loadingPlaylist, setLoadingPlaylist] = useState<boolean>(false)
26+
27+
useEffect(() => {
28+
const updateData = async () => {
29+
if(!playlists || !Array.isArray(playlists)) {
30+
console.warn('Invalid playlists data, skipping caching.')
31+
return
32+
}
33+
34+
if (!arePlaylistsEqual(playlists, cachedPlaylists)) {
35+
setLoadingPlaylist(true)
36+
37+
console.log('Playlists changed, updating cached data...')
38+
39+
const result = mockCachePlaylists(playlists)
40+
41+
if (result !== false) {
42+
console.warn('Caching function returned false, not updating local storage.')
43+
44+
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(playlists))
45+
setCachedPlaylists(playlists)
46+
}
47+
48+
console.log('Playlists cached:', playlists)
49+
50+
setLoadingPlaylist(false)
51+
}
52+
}
53+
54+
updateData()
55+
// eslint-disable-next-line react-hooks/exhaustive-deps
56+
}, [playlists])
57+
58+
return { cachedData: cachedPlaylists, isCaching: loadingPlaylist }
59+
}

src/hooks/usePlaylistCache.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/utils/cacheMediaFile.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export const cacheMediaFile = async (src: string): Promise<void> => {
2+
try {
3+
const cache = await caches.open('screenlite-cache')
4+
5+
const cachedResponse = await cache.match(src)
6+
7+
if (cachedResponse) return
8+
9+
const response = await fetch(src, { mode: 'no-cors' })
10+
11+
if (!response.ok && response.type !== 'opaque') {
12+
throw new Error(`Failed to fetch ${src}: ${response.statusText}`)
13+
}
14+
15+
await cache.put(src, response.clone())
16+
} catch (err) {
17+
console.error('Caching error:', err)
18+
throw err
19+
}
20+
}

src/utils/isDataCached.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Playlist } from '../types'
2+
3+
export const isDataCached = async (playlist: Playlist | null): Promise<boolean> => {
4+
if (!playlist) return false
5+
6+
try {
7+
const cache = await caches.open('screenlite-cache')
8+
9+
const allPaths = playlist.sections.flatMap(section =>
10+
section.items.map(item => item.content_path)
11+
)
12+
13+
const results = await Promise.all(
14+
allPaths.map(path => cache.match(path))
15+
)
16+
17+
return results.every(res => !!res)
18+
} catch (err) {
19+
console.error('Cache check error:', err)
20+
return false
21+
}
22+
}

0 commit comments

Comments
 (0)