@therealtinhtute/baroiplayer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/context/media.js +2 -0
  4. package/dist/context/media.js.map +1 -0
  5. package/dist/context/media.mjs +15 -0
  6. package/dist/context/media.mjs.map +1 -0
  7. package/dist/context/store.js +2 -0
  8. package/dist/context/store.js.map +1 -0
  9. package/dist/context/store.mjs +5 -0
  10. package/dist/context/store.mjs.map +1 -0
  11. package/dist/context-BMteAJDN.js +1140 -0
  12. package/dist/context-BMteAJDN.js.map +1 -0
  13. package/dist/context-DCJ_scmO.cjs +23 -0
  14. package/dist/context-DCJ_scmO.cjs.map +1 -0
  15. package/dist/core-events-B5W_j8yU.js +480 -0
  16. package/dist/core-events-B5W_j8yU.js.map +1 -0
  17. package/dist/core-events-CN0-Vhhq.cjs +2 -0
  18. package/dist/core-events-CN0-Vhhq.cjs.map +1 -0
  19. package/dist/core-plugins-Bq9KLany.js +696 -0
  20. package/dist/core-plugins-Bq9KLany.js.map +1 -0
  21. package/dist/core-plugins-CPDSHMiQ.cjs +2 -0
  22. package/dist/core-plugins-CPDSHMiQ.cjs.map +1 -0
  23. package/dist/core.js +37 -0
  24. package/dist/core.js.map +1 -0
  25. package/dist/core.mjs +1217 -0
  26. package/dist/core.mjs.map +1 -0
  27. package/dist/enhanced-components-DHv0MFvG.js +1073 -0
  28. package/dist/enhanced-components-DHv0MFvG.js.map +1 -0
  29. package/dist/enhanced-components-DTIObgB7.cjs +2 -0
  30. package/dist/enhanced-components-DTIObgB7.cjs.map +1 -0
  31. package/dist/hooks-C_r8wexn.cjs +4 -0
  32. package/dist/hooks-C_r8wexn.cjs.map +1 -0
  33. package/dist/hooks-Da2wVXlF.js +1630 -0
  34. package/dist/hooks-Da2wVXlF.js.map +1 -0
  35. package/dist/index.css +1 -0
  36. package/dist/index.js +112 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +6126 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/minimal-components-DSq5sQpy.js +2002 -0
  41. package/dist/minimal-components-DSq5sQpy.js.map +1 -0
  42. package/dist/minimal-components-DflWiihN.cjs +2 -0
  43. package/dist/minimal-components-DflWiihN.cjs.map +1 -0
  44. package/dist/minimal-components.css +1 -0
  45. package/dist/mona-loading.gif +0 -0
  46. package/dist/performance-monitor-CUW6f5ll.cjs +2 -0
  47. package/dist/performance-monitor-CUW6f5ll.cjs.map +1 -0
  48. package/dist/performance-monitor-rMW1RgV3.js +302 -0
  49. package/dist/performance-monitor-rMW1RgV3.js.map +1 -0
  50. package/dist/utils-BnscpYYd.js +2798 -0
  51. package/dist/utils-BnscpYYd.js.map +1 -0
  52. package/dist/utils-D_czS_0K.cjs +9 -0
  53. package/dist/utils-D_czS_0K.cjs.map +1 -0
  54. package/dist/utils.js +2 -0
  55. package/dist/utils.js.map +1 -0
  56. package/dist/utils.mjs +7 -0
  57. package/dist/utils.mjs.map +1 -0
  58. package/package.json +114 -0
  59. package/src/adapters/videojs-adapter.tsx +470 -0
  60. package/src/assets/mona-loading.gif +0 -0
  61. package/src/components/AnimateLoading.tsx +115 -0
  62. package/src/components/LoadingState.tsx +248 -0
  63. package/src/components/accessibility/keyboard-help-overlay.tsx +213 -0
  64. package/src/components/audio-player.tsx +85 -0
  65. package/src/components/basic/index.ts +37 -0
  66. package/src/components/enhanced/advanced-features.tsx +316 -0
  67. package/src/components/enhanced/debug-panel.tsx +422 -0
  68. package/src/components/enhanced/enhanced-controls.tsx +600 -0
  69. package/src/components/enhanced/enhanced-media-player.tsx +509 -0
  70. package/src/components/enhanced/error-handling.tsx +345 -0
  71. package/src/components/enhanced/index.ts +39 -0
  72. package/src/components/enhanced/media-player-enhanced.tsx +349 -0
  73. package/src/components/examples/basic-video-player.tsx +55 -0
  74. package/src/components/examples/custom-controls-example.tsx +63 -0
  75. package/src/components/examples/enhanced-controls-example.tsx +114 -0
  76. package/src/components/examples/enhanced-media-player-example.tsx +293 -0
  77. package/src/components/examples/loading-states-demo.tsx +142 -0
  78. package/src/components/examples/react-19-demo.tsx +381 -0
  79. package/src/components/examples/volume-orientation-demo.tsx +114 -0
  80. package/src/components/icon-demo.tsx +209 -0
  81. package/src/components/icons/index.tsx +166 -0
  82. package/src/components/icons/svg-icons/ArrowLeftIcon.tsx +18 -0
  83. package/src/components/icons/svg-icons/ArrowRightIcon.tsx +18 -0
  84. package/src/components/icons/svg-icons/AudioIcon.tsx +22 -0
  85. package/src/components/icons/svg-icons/BackwardIcon.tsx +12 -0
  86. package/src/components/icons/svg-icons/CameraIcon.tsx +19 -0
  87. package/src/components/icons/svg-icons/CheckIcon.tsx +19 -0
  88. package/src/components/icons/svg-icons/EnablePiPIcon.tsx +17 -0
  89. package/src/components/icons/svg-icons/ForwardIcon.tsx +12 -0
  90. package/src/components/icons/svg-icons/FullscreenEnterIcon.tsx +23 -0
  91. package/src/components/icons/svg-icons/FullscreenExitIcon.tsx +23 -0
  92. package/src/components/icons/svg-icons/LoadingIcon.tsx +32 -0
  93. package/src/components/icons/svg-icons/PauseIcon.tsx +23 -0
  94. package/src/components/icons/svg-icons/PlayIcon.tsx +21 -0
  95. package/src/components/icons/svg-icons/PlaybackSpeedIcon.tsx +23 -0
  96. package/src/components/icons/svg-icons/QualityIcon.tsx +21 -0
  97. package/src/components/icons/svg-icons/SettingsIcon.tsx +12 -0
  98. package/src/components/icons/svg-icons/SliderIcon.tsx +21 -0
  99. package/src/components/icons/svg-icons/SubtitleIcon.tsx +21 -0
  100. package/src/components/icons/svg-icons/SubtitleOffIcon.tsx +25 -0
  101. package/src/components/icons/svg-icons/UnPiPIcon.tsx +17 -0
  102. package/src/components/icons/svg-icons/VolumeMutedIcon.tsx +16 -0
  103. package/src/components/icons/svg-icons/VolumeOneIcon.tsx +22 -0
  104. package/src/components/icons/svg-icons/VolumeThreeIcon.tsx +16 -0
  105. package/src/components/icons/svg-icons/VolumeTwoIcon.tsx +16 -0
  106. package/src/components/legacy.tsx +93 -0
  107. package/src/components/media-metadata-display.tsx +100 -0
  108. package/src/components/media-player-audio.tsx +39 -0
  109. package/src/components/media-player-controls.tsx +226 -0
  110. package/src/components/media-player-error.tsx +163 -0
  111. package/src/components/media-player-fullscreen.tsx +109 -0
  112. package/src/components/media-player-hls.tsx +111 -0
  113. package/src/components/media-player-loading.tsx +76 -0
  114. package/src/components/media-player-play.tsx +75 -0
  115. package/src/components/media-player-root.tsx +340 -0
  116. package/src/components/media-player-seek.tsx +168 -0
  117. package/src/components/media-player-time.tsx +99 -0
  118. package/src/components/media-player-tooltip.tsx +58 -0
  119. package/src/components/media-player-video.tsx +39 -0
  120. package/src/components/media-player-volume.tsx +155 -0
  121. package/src/components/media-player.tsx +257 -0
  122. package/src/components/media-suspense.tsx +362 -0
  123. package/src/components/player-settings-form.tsx +163 -0
  124. package/src/components/react-player-wrapper.tsx +651 -0
  125. package/src/components/simple-video-player.tsx +60 -0
  126. package/src/components/ui/enhanced-seek-bar.tsx +235 -0
  127. package/src/components/ui/fullscreen-button.tsx +60 -0
  128. package/src/components/ui/media-resource-preloader.tsx +395 -0
  129. package/src/components/ui/play-button.tsx +107 -0
  130. package/src/components/ui/seek-bar.tsx +161 -0
  131. package/src/components/ui/time-display.tsx +40 -0
  132. package/src/components/ui/volume-control.tsx +143 -0
  133. package/src/components/unified/base-media-player.tsx +246 -0
  134. package/src/components/unified/media-player.tsx +75 -0
  135. package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +561 -0
  136. package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +552 -0
  137. package/src/components/variants/minimal/MinimalControls.tsx +142 -0
  138. package/src/components/variants/minimal/MinimalLoading.tsx +32 -0
  139. package/src/components/variants/minimal/MinimalMediaPlayer.tsx +315 -0
  140. package/src/components/variants/minimal/MinimalOverlay.tsx +64 -0
  141. package/src/components/variants/minimal/MinimalPlayButton.tsx +65 -0
  142. package/src/components/variants/minimal/MinimalSeekBar.tsx +150 -0
  143. package/src/components/variants/minimal/MinimalVolumeControl.tsx +80 -0
  144. package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +210 -0
  145. package/src/components/variants/minimal/components/CenterPlayButton.tsx +22 -0
  146. package/src/components/variants/minimal/components/ControlBar.tsx +146 -0
  147. package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +361 -0
  148. package/src/components/variants/minimal/components/PlaybackControls.tsx +51 -0
  149. package/src/components/variants/minimal/components/RightControls.tsx +173 -0
  150. package/src/components/variants/minimal/components/SeekBar.tsx +131 -0
  151. package/src/components/variants/minimal/components/SettingsDropdown.tsx +70 -0
  152. package/src/components/variants/minimal/components/SubtitleDisplay.tsx +21 -0
  153. package/src/components/variants/minimal/components/TimeDisplay.tsx +18 -0
  154. package/src/components/variants/minimal/components/VideoElement.tsx +40 -0
  155. package/src/components/variants/minimal/components/VolumeControl.tsx +217 -0
  156. package/src/components/variants/minimal/index.ts +23 -0
  157. package/src/components/video-player.tsx +100 -0
  158. package/src/context/error-handling.ts +394 -0
  159. package/src/context/media-context.tsx +208 -0
  160. package/src/context/media-provider.tsx +265 -0
  161. package/src/context/media-store.ts +379 -0
  162. package/src/context/persistence.ts +453 -0
  163. package/src/core/events/event-system.ts +442 -0
  164. package/src/core/events/hooks.ts +302 -0
  165. package/src/core/events/index.ts +22 -0
  166. package/src/core/events/middleware.ts +269 -0
  167. package/src/core/index.ts +22 -0
  168. package/src/core/optimization/bundle-optimizer.tsx +308 -0
  169. package/src/core/performance/cicd.ts +96 -0
  170. package/src/core/performance/index.ts +38 -0
  171. package/src/core/performance/media-loading-optimizer.ts +538 -0
  172. package/src/core/performance/performance-budgets.ts +432 -0
  173. package/src/core/performance/performance-monitor.tsx +476 -0
  174. package/src/core/performance/performance-testing.ts +459 -0
  175. package/src/core/plugins/base-plugin.ts +224 -0
  176. package/src/core/plugins/enhanced-plugin-system.ts +411 -0
  177. package/src/core/plugins/examples/analytics-plugin.ts +242 -0
  178. package/src/core/plugins/index.ts +39 -0
  179. package/src/core/plugins/registry.ts +327 -0
  180. package/src/core/plugins/types.ts +159 -0
  181. package/src/core/plugins/utils.ts +316 -0
  182. package/src/core/progressive-loading.tsx +322 -0
  183. package/src/core/providers/enhanced-hls-provider.ts +339 -0
  184. package/src/core/providers/service-provider.tsx +168 -0
  185. package/src/core/utils.tsx +38 -0
  186. package/src/features/media-session/media-session-manager.ts +429 -0
  187. package/src/features/picture-in-picture/pip-manager.ts +429 -0
  188. package/src/features/playlist/playlist-manager.ts +606 -0
  189. package/src/features/subtitles/subtitle-manager.ts +441 -0
  190. package/src/features/subtitles/subtitle-parser.ts +280 -0
  191. package/src/features/thumbnails/thumbnail-generator.ts +519 -0
  192. package/src/hooks/accessibility/index.ts +27 -0
  193. package/src/hooks/accessibility/use-accessibility-preferences.ts +188 -0
  194. package/src/hooks/accessibility/use-focus-management.ts +176 -0
  195. package/src/hooks/accessibility/use-keyboard-shortcuts.ts +144 -0
  196. package/src/hooks/accessibility/use-screen-reader.ts +182 -0
  197. package/src/hooks/core/index.ts +47 -0
  198. package/src/hooks/legacy.ts +27 -0
  199. package/src/hooks/use-enhanced-media-player.ts +468 -0
  200. package/src/hooks/use-intelligent-preloading.ts +358 -0
  201. package/src/hooks/use-keyboard-controls.ts +115 -0
  202. package/src/hooks/use-media-actions.ts +175 -0
  203. package/src/hooks/use-media-metadata.ts +89 -0
  204. package/src/hooks/use-media-optimistic.ts +270 -0
  205. package/src/hooks/use-media-player.ts +312 -0
  206. package/src/hooks/use-media-state.ts +190 -0
  207. package/src/hooks/use-memory-optimization.ts +358 -0
  208. package/src/hooks/use-optimistic-controls.ts +92 -0
  209. package/src/hooks/use-optimized-media.ts +263 -0
  210. package/src/hooks/use-touch-gestures.ts +417 -0
  211. package/src/hooks/useEnhancedSubtitles.ts +258 -0
  212. package/src/hooks/useKeyboardShortcuts.ts +74 -0
  213. package/src/hooks/useMediaControls.ts +52 -0
  214. package/src/hooks/useScreenshot.ts +57 -0
  215. package/src/hooks/useSubtitles.ts +91 -0
  216. package/src/hooks/useVideoPlayer.ts +232 -0
  217. package/src/icons/components/FullscreenIcon.tsx +37 -0
  218. package/src/icons/components/SettingsIcon.tsx +38 -0
  219. package/src/icons/components/animated/LoadingIcon.tsx +43 -0
  220. package/src/icons/components/animated/PlayPauseIcon.tsx +50 -0
  221. package/src/icons/components/media/PauseIcon.tsx +39 -0
  222. package/src/icons/components/media/PlayIcon.tsx +38 -0
  223. package/src/icons/components/volume/VolumeIcon.tsx +39 -0
  224. package/src/icons/components/volume/VolumeOffIcon.tsx +39 -0
  225. package/src/icons/index.ts +56 -0
  226. package/src/icons/provider.tsx +64 -0
  227. package/src/icons/types.ts +40 -0
  228. package/src/index-enhanced.ts +35 -0
  229. package/src/index-legacy.ts +326 -0
  230. package/src/index.css +102 -0
  231. package/src/index.ts +216 -0
  232. package/src/lib/deprecation.ts +51 -0
  233. package/src/lib/format-time.ts +37 -0
  234. package/src/lib/index.ts +3 -0
  235. package/src/lib/utils.ts +6 -0
  236. package/src/styles/globals.css +462 -0
  237. package/src/styles/minimal-player.css +589 -0
  238. package/src/styles/pixel-art.css +46 -0
  239. package/src/styles/videojs.css +100 -0
  240. package/src/test/setup.ts +79 -0
  241. package/src/types/events.ts +47 -0
  242. package/src/types/formats.ts +95 -0
  243. package/src/types/index.ts +190 -0
  244. package/src/types/videojs-plugins.d.ts +14 -0
@@ -0,0 +1,453 @@
1
+ "use client"
2
+
3
+ import type { MediaState } from "./media-context"
4
+
5
+ // Storage keys
6
+ export const STORAGE_KEYS = {
7
+ USER_PREFERENCES: "baroi-player-preferences",
8
+ SESSION_STATE: "baroi-player-session",
9
+ PLAYBACK_HISTORY: "baroi-player-history",
10
+ CACHE_TIMESTAMP: "baroi-player-cache-ts"
11
+ } as const
12
+
13
+ // User preferences that should be persisted long-term
14
+ export interface UserPreferences {
15
+ volume: number
16
+ isMuted: boolean
17
+ playbackRate: number
18
+ subtitlesShowing: boolean
19
+ selectedSubtitleTrack: string | null
20
+ preferredRendition: string | null
21
+ autoplay: boolean
22
+ keyboardShortcuts: boolean
23
+ }
24
+
25
+ // Session state that should be persisted for the session
26
+ export interface SessionState {
27
+ currentTime: number
28
+ mediaId?: string
29
+ src?: string
30
+ playbackPosition: Record<string, number> // mediaId -> position
31
+ }
32
+
33
+ // Playback history item
34
+ export interface PlaybackHistoryItem {
35
+ mediaId: string
36
+ src: string
37
+ title?: string
38
+ currentTime: number
39
+ duration: number
40
+ timestamp: number
41
+ completed: boolean
42
+ }
43
+
44
+ // Persistence configuration
45
+ export interface PersistenceConfig {
46
+ // Storage options
47
+ userPreferences: boolean
48
+ sessionState: boolean
49
+ playbackHistory: boolean
50
+
51
+ // Limits
52
+ maxHistoryItems: number
53
+ maxCacheAge: number // milliseconds
54
+
55
+ // Debouncing
56
+ debounceMs: number
57
+
58
+ // Custom storage adapter
59
+ storage?: StorageAdapter
60
+ }
61
+
62
+ // Storage adapter interface for custom storage implementations
63
+ export interface StorageAdapter {
64
+ getItem(key: string): Promise<string | null>
65
+ setItem(key: string, value: string): Promise<void>
66
+ removeItem(key: string): Promise<void>
67
+ clear(): Promise<void>
68
+ }
69
+
70
+ // Default localStorage adapter
71
+ class LocalStorageAdapter implements StorageAdapter {
72
+ async getItem(key: string): Promise<string | null> {
73
+ if (typeof window === 'undefined') return null
74
+ try {
75
+ return localStorage.getItem(key)
76
+ } catch (error) {
77
+ console.warn('Failed to read from localStorage:', error)
78
+ return null
79
+ }
80
+ }
81
+
82
+ async setItem(key: string, value: string): Promise<void> {
83
+ if (typeof window === 'undefined') return
84
+ try {
85
+ localStorage.setItem(key, value)
86
+ } catch (error) {
87
+ console.warn('Failed to write to localStorage:', error)
88
+ }
89
+ }
90
+
91
+ async removeItem(key: string): Promise<void> {
92
+ if (typeof window === 'undefined') return
93
+ try {
94
+ localStorage.removeItem(key)
95
+ } catch (error) {
96
+ console.warn('Failed to remove from localStorage:', error)
97
+ }
98
+ }
99
+
100
+ async clear(): Promise<void> {
101
+ if (typeof window === 'undefined') return
102
+ try {
103
+ // Only clear baroi-player keys
104
+ Object.values(STORAGE_KEYS).forEach(key => {
105
+ localStorage.removeItem(key)
106
+ })
107
+ } catch (error) {
108
+ console.warn('Failed to clear localStorage:', error)
109
+ }
110
+ }
111
+ }
112
+
113
+ // Default persistence configuration
114
+ const DEFAULT_CONFIG: PersistenceConfig = {
115
+ userPreferences: true,
116
+ sessionState: true,
117
+ playbackHistory: true,
118
+ maxHistoryItems: 50,
119
+ maxCacheAge: 7 * 24 * 60 * 60 * 1000, // 7 days
120
+ debounceMs: 1000,
121
+ storage: new LocalStorageAdapter()
122
+ }
123
+
124
+ // Persistence manager class
125
+ export class MediaPersistenceManager {
126
+ private config: PersistenceConfig
127
+ private storage: StorageAdapter
128
+ private debounceTimeouts: Map<string, NodeJS.Timeout> = new Map()
129
+
130
+ constructor(config: Partial<PersistenceConfig> = {}) {
131
+ this.config = { ...DEFAULT_CONFIG, ...config }
132
+ this.storage = this.config.storage!
133
+ }
134
+
135
+ // User preferences methods
136
+ async getUserPreferences(): Promise<UserPreferences | null> {
137
+ if (!this.config.userPreferences) return null
138
+
139
+ try {
140
+ const data = await this.storage.getItem(STORAGE_KEYS.USER_PREFERENCES)
141
+ if (!data) return null
142
+
143
+ const parsed = JSON.parse(data) as { preferences: UserPreferences; timestamp: number }
144
+
145
+ // Validate cache age
146
+ if (this.isCacheExpired(parsed.timestamp)) {
147
+ await this.clearUserPreferences()
148
+ return null
149
+ }
150
+
151
+ return parsed.preferences
152
+ } catch (error) {
153
+ console.warn('Failed to get user preferences:', error)
154
+ return null
155
+ }
156
+ }
157
+
158
+ async saveUserPreferences(preferences: Partial<UserPreferences>): Promise<void> {
159
+ if (!this.config.userPreferences) return
160
+
161
+ this.debounce(STORAGE_KEYS.USER_PREFERENCES, async () => {
162
+ try {
163
+ const existing = await this.getUserPreferences() || {}
164
+ const updated = { ...existing, ...preferences }
165
+
166
+ await this.storage.setItem(STORAGE_KEYS.USER_PREFERENCES, JSON.stringify({
167
+ preferences: updated,
168
+ timestamp: Date.now()
169
+ }))
170
+ } catch (error) {
171
+ console.warn('Failed to save user preferences:', error)
172
+ }
173
+ })
174
+ }
175
+
176
+ async clearUserPreferences(): Promise<void> {
177
+ try {
178
+ await this.storage.removeItem(STORAGE_KEYS.USER_PREFERENCES)
179
+ } catch (error) {
180
+ console.warn('Failed to clear user preferences:', error)
181
+ }
182
+ }
183
+
184
+ // Session state methods
185
+ async getSessionState(): Promise<SessionState | null> {
186
+ if (!this.config.sessionState) return null
187
+
188
+ try {
189
+ const data = await this.storage.getItem(STORAGE_KEYS.SESSION_STATE)
190
+ if (!data) return null
191
+
192
+ const parsed = JSON.parse(data) as { state: SessionState; timestamp: number }
193
+ return parsed.state
194
+ } catch (error) {
195
+ console.warn('Failed to get session state:', error)
196
+ return null
197
+ }
198
+ }
199
+
200
+ async saveSessionState(state: Partial<SessionState>): Promise<void> {
201
+ if (!this.config.sessionState) return
202
+
203
+ this.debounce(STORAGE_KEYS.SESSION_STATE, async () => {
204
+ try {
205
+ const existing = await this.getSessionState() || {
206
+ currentTime: 0,
207
+ playbackPosition: {}
208
+ }
209
+ const updated = { ...existing, ...state }
210
+
211
+ await this.storage.setItem(STORAGE_KEYS.SESSION_STATE, JSON.stringify({
212
+ state: updated,
213
+ timestamp: Date.now()
214
+ }))
215
+ } catch (error) {
216
+ console.warn('Failed to save session state:', error)
217
+ }
218
+ })
219
+ }
220
+
221
+ async clearSessionState(): Promise<void> {
222
+ try {
223
+ await this.storage.removeItem(STORAGE_KEYS.SESSION_STATE)
224
+ } catch (error) {
225
+ console.warn('Failed to clear session state:', error)
226
+ }
227
+ }
228
+
229
+ // Playback history methods
230
+ async getPlaybackHistory(): Promise<PlaybackHistoryItem[]> {
231
+ if (!this.config.playbackHistory) return []
232
+
233
+ try {
234
+ const data = await this.storage.getItem(STORAGE_KEYS.PLAYBACK_HISTORY)
235
+ if (!data) return []
236
+
237
+ const parsed = JSON.parse(data) as { history: PlaybackHistoryItem[]; timestamp: number }
238
+
239
+ // Validate cache age
240
+ if (this.isCacheExpired(parsed.timestamp)) {
241
+ await this.clearPlaybackHistory()
242
+ return []
243
+ }
244
+
245
+ return parsed.history || []
246
+ } catch (error) {
247
+ console.warn('Failed to get playback history:', error)
248
+ return []
249
+ }
250
+ }
251
+
252
+ async addToPlaybackHistory(item: Omit<PlaybackHistoryItem, 'timestamp'>): Promise<void> {
253
+ if (!this.config.playbackHistory) return
254
+
255
+ try {
256
+ const history = await this.getPlaybackHistory()
257
+ const newItem: PlaybackHistoryItem = {
258
+ ...item,
259
+ timestamp: Date.now()
260
+ }
261
+
262
+ // Remove existing entry for the same media
263
+ const filteredHistory = history.filter(h => h.mediaId !== item.mediaId)
264
+
265
+ // Add new item at the beginning
266
+ const updatedHistory = [newItem, ...filteredHistory]
267
+ .slice(0, this.config.maxHistoryItems)
268
+
269
+ await this.storage.setItem(STORAGE_KEYS.PLAYBACK_HISTORY, JSON.stringify({
270
+ history: updatedHistory,
271
+ timestamp: Date.now()
272
+ }))
273
+ } catch (error) {
274
+ console.warn('Failed to add to playback history:', error)
275
+ }
276
+ }
277
+
278
+ async clearPlaybackHistory(): Promise<void> {
279
+ try {
280
+ await this.storage.removeItem(STORAGE_KEYS.PLAYBACK_HISTORY)
281
+ } catch (error) {
282
+ console.warn('Failed to clear playback history:', error)
283
+ }
284
+ }
285
+
286
+ // Utility methods
287
+ async savePlaybackPosition(mediaId: string, position: number): Promise<void> {
288
+ const session = await this.getSessionState()
289
+ const playbackPosition = session?.playbackPosition || {}
290
+
291
+ await this.saveSessionState({
292
+ playbackPosition: {
293
+ ...playbackPosition,
294
+ [mediaId]: position
295
+ }
296
+ })
297
+ }
298
+
299
+ async getPlaybackPosition(mediaId: string): Promise<number> {
300
+ const session = await this.getSessionState()
301
+ return session?.playbackPosition?.[mediaId] || 0
302
+ }
303
+
304
+ // Extract preferences from MediaState
305
+ extractUserPreferences(state: MediaState): UserPreferences {
306
+ return {
307
+ volume: state.volume,
308
+ isMuted: state.isMuted,
309
+ playbackRate: state.playbackRate,
310
+ subtitlesShowing: state.subtitlesShowing,
311
+ selectedSubtitleTrack: state.selectedSubtitleTrack?.id || null,
312
+ preferredRendition: state.selectedRendition,
313
+ autoplay: false, // This would come from config
314
+ keyboardShortcuts: true // This would come from config
315
+ }
316
+ }
317
+
318
+ // Extract session state from MediaState
319
+ extractSessionState(state: MediaState, mediaId?: string): SessionState {
320
+ return {
321
+ currentTime: state.currentTime,
322
+ mediaId,
323
+ playbackPosition: {}
324
+ }
325
+ }
326
+
327
+ // Clear all persisted data
328
+ async clearAll(): Promise<void> {
329
+ await this.storage.clear()
330
+ this.debounceTimeouts.clear()
331
+ }
332
+
333
+ // Private helper methods
334
+ private isCacheExpired(timestamp?: number): boolean {
335
+ if (!timestamp) return true
336
+ return Date.now() - timestamp > this.config.maxCacheAge
337
+ }
338
+
339
+ private debounce(key: string, fn: () => Promise<void>): void {
340
+ const existingTimeout = this.debounceTimeouts.get(key)
341
+ if (existingTimeout) {
342
+ clearTimeout(existingTimeout)
343
+ }
344
+
345
+ const timeout = setTimeout(async () => {
346
+ await fn()
347
+ this.debounceTimeouts.delete(key)
348
+ }, this.config.debounceMs)
349
+
350
+ this.debounceTimeouts.set(key, timeout)
351
+ }
352
+ }
353
+
354
+ // Hook for using persistence in components
355
+ export function useMediaPersistence(config?: Partial<PersistenceConfig>) {
356
+ const [manager] = React.useState(() => new MediaPersistenceManager(config))
357
+
358
+ React.useEffect(() => {
359
+ return () => {
360
+ // Clear any pending debounced operations
361
+ manager.clearAll()
362
+ }
363
+ }, [manager])
364
+
365
+ return manager
366
+ }
367
+
368
+ // React 19 integration for automatic persistence
369
+ export function useAutoMediaPersistence(
370
+ state: MediaState,
371
+ mediaId?: string,
372
+ config?: Partial<PersistenceConfig>
373
+ ) {
374
+ const manager = useMediaPersistence(config)
375
+ const [isRehydrated, setIsRehydrated] = React.useState(false)
376
+
377
+ // Rehydrate on mount
378
+ React.useEffect(() => {
379
+ let isMounted = true
380
+
381
+ const rehydrate = async () => {
382
+ try {
383
+ const [preferences, session] = await Promise.all([
384
+ manager.getUserPreferences(),
385
+ manager.getSessionState()
386
+ ])
387
+
388
+ if (!isMounted) return
389
+
390
+ // Return the rehydrated state for the store to apply
391
+ const rehydratedState: Partial<MediaState> = {
392
+ ...(preferences && {
393
+ volume: preferences.volume,
394
+ isMuted: preferences.isMuted,
395
+ playbackRate: preferences.playbackRate,
396
+ subtitlesShowing: preferences.subtitlesShowing
397
+ }),
398
+ ...(session && mediaId === session.mediaId && {
399
+ currentTime: session.currentTime
400
+ })
401
+ }
402
+
403
+ // Apply rehydrated state via callback if provided
404
+ if (Object.keys(rehydratedState).length > 0) {
405
+ // This would be handled by the store
406
+ console.log('Rehydrated state:', rehydratedState)
407
+ }
408
+
409
+ setIsRehydrated(true)
410
+ } catch (error) {
411
+ console.warn('Failed to rehydrate state:', error)
412
+ setIsRehydrated(true)
413
+ }
414
+ }
415
+
416
+ rehydrate()
417
+
418
+ return () => {
419
+ isMounted = false
420
+ }
421
+ }, [manager, mediaId])
422
+
423
+ // Auto-persist changes
424
+ React.useEffect(() => {
425
+ if (!isRehydrated) return
426
+
427
+ // Save user preferences
428
+ const preferences = manager.extractUserPreferences(state)
429
+ manager.saveUserPreferences(preferences)
430
+
431
+ // Save session state
432
+ if (mediaId) {
433
+ const session = manager.extractSessionState(state, mediaId)
434
+ manager.saveSessionState(session)
435
+
436
+ // Save playback position
437
+ if (state.currentTime > 0 && state.duration > 0) {
438
+ manager.savePlaybackPosition(mediaId, state.currentTime)
439
+ }
440
+ }
441
+ }, [manager, state, mediaId, isRehydrated])
442
+
443
+ return {
444
+ manager,
445
+ isRehydrated
446
+ }
447
+ }
448
+
449
+ // Export default storage adapter
450
+ export const defaultStorageAdapter = new LocalStorageAdapter()
451
+
452
+ // Import React for hooks
453
+ import React from "react"