@djangocfg/ui-nextjs 2.1.77 → 2.1.79

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.
package/README.md CHANGED
@@ -57,11 +57,11 @@ All components from `@djangocfg/ui-core` are re-exported.
57
57
  import { Hero } from '@djangocfg/ui-nextjs/blocks';
58
58
  ```
59
59
 
60
- ### Tools (4)
61
- `JsonTree` `PrettyCode` `Mermaid` `LottiePlayer`
60
+ ### Tools (7)
61
+ `JsonTree` `PrettyCode` `Mermaid` `LottiePlayer` `AudioPlayer` `VideoPlayer` `ImageViewer`
62
62
 
63
63
  ```tsx
64
- import { PrettyCode } from '@djangocfg/ui-nextjs/tools';
64
+ import { PrettyCode, AudioPlayer, VideoPlayer } from '@djangocfg/ui-nextjs/tools';
65
65
  ```
66
66
 
67
67
  ## Hooks
@@ -108,6 +108,25 @@ function Example() {
108
108
  import '@djangocfg/ui-nextjs/styles/globals';
109
109
  ```
110
110
 
111
+ ## Logger
112
+
113
+ Universal logger with consola + zustand for Console panel integration.
114
+
115
+ ```tsx
116
+ import { createLogger, createMediaLogger } from '@djangocfg/ui-nextjs/lib';
117
+
118
+ // Basic logger
119
+ const log = createLogger('MyComponent');
120
+ log.info('User logged in', { userId: 123 });
121
+ log.error('Failed to load', { error });
122
+
123
+ // Media logger (with seek/buffer helpers)
124
+ const mediaLog = createMediaLogger('AudioPlayer');
125
+ mediaLog.load(src, 'stream');
126
+ mediaLog.seek(from, to, duration);
127
+ mediaLog.buffer(buffered, duration);
128
+ ```
129
+
111
130
  ## Exports
112
131
 
113
132
  | Path | Content |
@@ -116,7 +135,8 @@ import '@djangocfg/ui-nextjs/styles/globals';
116
135
  | `@djangocfg/ui-nextjs/components` | Components only |
117
136
  | `@djangocfg/ui-nextjs/hooks` | Hooks only |
118
137
  | `@djangocfg/ui-nextjs/blocks` | Landing page blocks |
119
- | `@djangocfg/ui-nextjs/tools` | JsonTree, Mermaid, etc. |
138
+ | `@djangocfg/ui-nextjs/tools` | JsonTree, Mermaid, Media players |
139
+ | `@djangocfg/ui-nextjs/lib` | Logger, utilities |
120
140
  | `@djangocfg/ui-nextjs/theme` | ThemeProvider, ThemeToggle |
121
141
  | `@djangocfg/ui-nextjs/styles` | CSS |
122
142
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-nextjs",
3
- "version": "2.1.77",
3
+ "version": "2.1.79",
4
4
  "description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -58,8 +58,8 @@
58
58
  "check": "tsc --noEmit"
59
59
  },
60
60
  "peerDependencies": {
61
- "@djangocfg/api": "^2.1.77",
62
- "@djangocfg/ui-core": "^2.1.77",
61
+ "@djangocfg/api": "^2.1.79",
62
+ "@djangocfg/ui-core": "^2.1.79",
63
63
  "@types/react": "^19.1.0",
64
64
  "@types/react-dom": "^19.1.0",
65
65
  "consola": "^3.4.2",
@@ -110,7 +110,7 @@
110
110
  "wavesurfer.js": "^7.12.1"
111
111
  },
112
112
  "devDependencies": {
113
- "@djangocfg/typescript-config": "^2.1.77",
113
+ "@djangocfg/typescript-config": "^2.1.79",
114
114
  "@types/node": "^24.7.2",
115
115
  "eslint": "^9.37.0",
116
116
  "tailwindcss-animate": "1.0.7",
package/src/lib/index.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  // Re-export from ui-core
2
- export * from '@djangocfg/ui-core/lib';
2
+ export * from '@djangocfg/ui-core/lib';
3
+
4
+ // Logger (consola + zustand for Console panel)
5
+ export * from './logger';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Universal Logger
3
+ *
4
+ * Combines console logging with zustand store for Console panel.
5
+ * Use createMediaLogger for media tools (AudioPlayer, VideoPlayer, ImageViewer).
6
+ */
7
+
8
+ export { createLogger, createMediaLogger, logger, log } from './logger';
9
+ export { useLogStore, useFilteredLogs, useLogCount, useErrorCount } from './logStore';
10
+ export type { LogEntry, LogLevel, LogFilter, LogStore, Logger, MediaLogger } from './types';
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Log Store
3
+ *
4
+ * Zustand store for log accumulation and filtering.
5
+ * Keeps logs in memory for Console panel display.
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { create } from 'zustand';
11
+ import type { LogStore, LogEntry, LogFilter } from './types';
12
+
13
+ const DEFAULT_FILTER: LogFilter = {
14
+ levels: ['debug', 'info', 'warn', 'error', 'success'],
15
+ component: undefined,
16
+ search: undefined,
17
+ since: undefined,
18
+ };
19
+
20
+ const MAX_LOGS = 1000;
21
+
22
+ function generateId(): string {
23
+ return `log-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
24
+ }
25
+
26
+ function matchesFilter(entry: LogEntry, filter: LogFilter): boolean {
27
+ // Level filter
28
+ if (!filter.levels.includes(entry.level)) {
29
+ return false;
30
+ }
31
+
32
+ // Component filter (case-insensitive partial match)
33
+ if (filter.component) {
34
+ const comp = filter.component.toLowerCase();
35
+ if (!entry.component.toLowerCase().includes(comp)) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ // Search filter (case-insensitive, searches message and data)
41
+ if (filter.search) {
42
+ const search = filter.search.toLowerCase();
43
+ const inMessage = entry.message.toLowerCase().includes(search);
44
+ const inData = entry.data
45
+ ? JSON.stringify(entry.data).toLowerCase().includes(search)
46
+ : false;
47
+ if (!inMessage && !inData) {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ // Time filter
53
+ if (filter.since && entry.timestamp < filter.since) {
54
+ return false;
55
+ }
56
+
57
+ return true;
58
+ }
59
+
60
+ export const useLogStore = create<LogStore>((set, get) => ({
61
+ logs: [],
62
+ filter: DEFAULT_FILTER,
63
+ maxLogs: MAX_LOGS,
64
+
65
+ addLog: (entry) => {
66
+ const newEntry: LogEntry = {
67
+ ...entry,
68
+ id: generateId(),
69
+ timestamp: new Date(),
70
+ };
71
+
72
+ set((state) => {
73
+ const newLogs = [...state.logs, newEntry];
74
+ // Trim to max size
75
+ if (newLogs.length > state.maxLogs) {
76
+ return { logs: newLogs.slice(-state.maxLogs) };
77
+ }
78
+ return { logs: newLogs };
79
+ });
80
+ },
81
+
82
+ clearLogs: () => {
83
+ set({ logs: [] });
84
+ },
85
+
86
+ setFilter: (filter) => {
87
+ set((state) => ({
88
+ filter: { ...state.filter, ...filter },
89
+ }));
90
+ },
91
+
92
+ resetFilter: () => {
93
+ set({ filter: DEFAULT_FILTER });
94
+ },
95
+
96
+ getFilteredLogs: () => {
97
+ const { logs, filter } = get();
98
+ return logs.filter((entry) => matchesFilter(entry, filter));
99
+ },
100
+
101
+ exportLogs: () => {
102
+ const { logs } = get();
103
+ return JSON.stringify(logs, null, 2);
104
+ },
105
+ }));
106
+
107
+ // Selector hooks for performance
108
+ export const useFilteredLogs = () => {
109
+ const logs = useLogStore((state) => state.logs);
110
+ const filter = useLogStore((state) => state.filter);
111
+ return logs.filter((entry) => matchesFilter(entry, filter));
112
+ };
113
+
114
+ export const useLogCount = () => {
115
+ return useLogStore((state) => state.logs.length);
116
+ };
117
+
118
+ export const useErrorCount = () => {
119
+ return useLogStore((state) =>
120
+ state.logs.filter((log) => log.level === 'error').length
121
+ );
122
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Logger
3
+ *
4
+ * Universal logger with consola + zustand store integration.
5
+ * Logs are accumulated for Console panel display.
6
+ * In production, only logs to store (no console output).
7
+ */
8
+
9
+ 'use client';
10
+
11
+ import { consola, type ConsolaInstance } from 'consola';
12
+ import { useLogStore } from './logStore';
13
+ import type { Logger, LogLevel, MediaLogger } from './types';
14
+
15
+ // Check environment
16
+ const isDev = process.env.NODE_ENV !== 'production';
17
+ const isBrowser = typeof window !== 'undefined';
18
+
19
+ // Create base consola instance
20
+ const baseConsola = consola.create({
21
+ level: isDev ? 4 : 2, // 4 = debug, 2 = warn
22
+ });
23
+
24
+ /**
25
+ * Extract error details from unknown error type
26
+ */
27
+ function extractErrorData(data?: Record<string, unknown>): {
28
+ cleanData: Record<string, unknown> | undefined;
29
+ stack: string | undefined;
30
+ } {
31
+ if (!data) return { cleanData: undefined, stack: undefined };
32
+
33
+ const cleanData = { ...data };
34
+ let stack: string | undefined;
35
+
36
+ // Extract stack from error object
37
+ if (data.error instanceof Error) {
38
+ stack = data.error.stack;
39
+ cleanData.error = {
40
+ name: data.error.name,
41
+ message: data.error.message,
42
+ };
43
+ } else if (typeof data.error === 'object' && data.error !== null) {
44
+ const errObj = data.error as Record<string, unknown>;
45
+ if (typeof errObj.stack === 'string') {
46
+ stack = errObj.stack;
47
+ }
48
+ if (typeof errObj.message === 'string') {
49
+ cleanData.error = errObj.message;
50
+ }
51
+ }
52
+
53
+ return { cleanData, stack };
54
+ }
55
+
56
+ /**
57
+ * Format buffer ranges for logging
58
+ */
59
+ function formatBufferRanges(buffered: TimeRanges, duration: number): string {
60
+ if (buffered.length === 0) return 'empty';
61
+
62
+ const ranges: string[] = [];
63
+ for (let i = 0; i < buffered.length; i++) {
64
+ const start = buffered.start(i);
65
+ const end = buffered.end(i);
66
+ const percent = ((end - start) / duration * 100).toFixed(1);
67
+ ranges.push(`${start.toFixed(1)}-${end.toFixed(1)}s (${percent}%)`);
68
+ }
69
+ return ranges.join(', ');
70
+ }
71
+
72
+ /**
73
+ * Create a logger for a specific component/module
74
+ */
75
+ export function createLogger(component: string): Logger {
76
+ const consolaTagged = baseConsola.withTag(component);
77
+
78
+ const log = (level: LogLevel, message: string, data?: Record<string, unknown>) => {
79
+ const { cleanData, stack } = extractErrorData(data);
80
+
81
+ // Add to store (for Console panel)
82
+ if (isBrowser) {
83
+ useLogStore.getState().addLog({
84
+ level,
85
+ component,
86
+ message,
87
+ data: cleanData,
88
+ stack,
89
+ });
90
+ }
91
+
92
+ // Log to console via consola (in dev mode)
93
+ if (isDev) {
94
+ const consolaMethod = level === 'success' ? 'success' : level;
95
+ if (cleanData) {
96
+ consolaTagged[consolaMethod](message, cleanData);
97
+ } else {
98
+ consolaTagged[consolaMethod](message);
99
+ }
100
+ }
101
+ };
102
+
103
+ return {
104
+ debug: (message, data) => log('debug', message, data),
105
+ info: (message, data) => log('info', message, data),
106
+ warn: (message, data) => log('warn', message, data),
107
+ error: (message, data) => log('error', message, data),
108
+ success: (message, data) => log('success', message, data),
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Create a media-specific logger with helper methods
114
+ * for AudioPlayer, VideoPlayer, ImageViewer
115
+ */
116
+ export function createMediaLogger(component: string): MediaLogger {
117
+ const baseLogger = createLogger(component);
118
+
119
+ return {
120
+ ...baseLogger,
121
+
122
+ load: (src: string, type?: string) => {
123
+ const typeStr = type ? ` (${type})` : '';
124
+ baseLogger.info(`LOAD: ${src}${typeStr}`);
125
+ },
126
+
127
+ state: (state: string, details?: Record<string, unknown>) => {
128
+ baseLogger.debug(`STATE: ${state}`, details);
129
+ },
130
+
131
+ seek: (from: number, to: number, duration: number) => {
132
+ baseLogger.debug(`SEEK: ${from.toFixed(2)}s -> ${to.toFixed(2)}s`, {
133
+ from,
134
+ to,
135
+ duration,
136
+ progress: `${((to / duration) * 100).toFixed(1)}%`,
137
+ });
138
+ },
139
+
140
+ buffer: (buffered: TimeRanges, duration: number) => {
141
+ if (buffered.length > 0) {
142
+ baseLogger.debug(`BUFFER: ${formatBufferRanges(buffered, duration)}`);
143
+ }
144
+ },
145
+
146
+ event: (name: string, data?: unknown) => {
147
+ if (data !== undefined) {
148
+ baseLogger.debug(`EVENT: ${name}`, { data });
149
+ } else {
150
+ baseLogger.debug(`EVENT: ${name}`);
151
+ }
152
+ },
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Global logger for non-component code
158
+ */
159
+ export const logger = createLogger('App');
160
+
161
+ /**
162
+ * Quick access for one-off logs
163
+ */
164
+ export const log = {
165
+ debug: (component: string, message: string, data?: Record<string, unknown>) =>
166
+ createLogger(component).debug(message, data),
167
+ info: (component: string, message: string, data?: Record<string, unknown>) =>
168
+ createLogger(component).info(message, data),
169
+ warn: (component: string, message: string, data?: Record<string, unknown>) =>
170
+ createLogger(component).warn(message, data),
171
+ error: (component: string, message: string, data?: Record<string, unknown>) =>
172
+ createLogger(component).error(message, data),
173
+ success: (component: string, message: string, data?: Record<string, unknown>) =>
174
+ createLogger(component).success(message, data),
175
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Logger Types
3
+ *
4
+ * Type definitions for the universal logging system.
5
+ * Compatible with Console panel in FileWorkspace IDE layout.
6
+ */
7
+
8
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
9
+
10
+ export interface LogEntry {
11
+ /** Unique log ID */
12
+ id: string;
13
+ /** Timestamp when log was created */
14
+ timestamp: Date;
15
+ /** Log level */
16
+ level: LogLevel;
17
+ /** Component/module name that created the log */
18
+ component: string;
19
+ /** Log message */
20
+ message: string;
21
+ /** Additional data (objects, errors, etc.) */
22
+ data?: Record<string, unknown>;
23
+ /** Error stack trace (for error level) */
24
+ stack?: string;
25
+ }
26
+
27
+ export interface LogFilter {
28
+ /** Filter by log levels */
29
+ levels: LogLevel[];
30
+ /** Filter by component name (partial match) */
31
+ component?: string;
32
+ /** Filter by message text (partial match) */
33
+ search?: string;
34
+ /** Filter by time range */
35
+ since?: Date;
36
+ }
37
+
38
+ export interface LogStore {
39
+ /** All accumulated logs */
40
+ logs: LogEntry[];
41
+ /** Current filter settings */
42
+ filter: LogFilter;
43
+ /** Maximum logs to keep */
44
+ maxLogs: number;
45
+
46
+ /** Add new log entry */
47
+ addLog: (entry: Omit<LogEntry, 'id' | 'timestamp'>) => void;
48
+ /** Clear all logs */
49
+ clearLogs: () => void;
50
+ /** Update filter settings */
51
+ setFilter: (filter: Partial<LogFilter>) => void;
52
+ /** Reset filter to defaults */
53
+ resetFilter: () => void;
54
+ /** Get filtered logs */
55
+ getFilteredLogs: () => LogEntry[];
56
+ /** Export logs as JSON string */
57
+ exportLogs: () => string;
58
+ }
59
+
60
+ export interface Logger {
61
+ debug: (message: string, data?: Record<string, unknown>) => void;
62
+ info: (message: string, data?: Record<string, unknown>) => void;
63
+ warn: (message: string, data?: Record<string, unknown>) => void;
64
+ error: (message: string, data?: Record<string, unknown>) => void;
65
+ success: (message: string, data?: Record<string, unknown>) => void;
66
+ }
67
+
68
+ /**
69
+ * Media-specific log helper methods
70
+ */
71
+ export interface MediaLogger extends Logger {
72
+ /** Log source load event */
73
+ load: (src: string, type?: string) => void;
74
+ /** Log state change */
75
+ state: (state: string, details?: Record<string, unknown>) => void;
76
+ /** Log seek event */
77
+ seek: (from: number, to: number, duration: number) => void;
78
+ /** Log buffer ranges */
79
+ buffer: (buffered: TimeRanges, duration: number) => void;
80
+ /** Log generic event */
81
+ event: (name: string, data?: unknown) => void;
82
+ }
@@ -3,6 +3,9 @@
3
3
  import { create } from 'zustand';
4
4
  import { persist, devtools } from 'zustand/middleware';
5
5
  import { useShallow } from 'zustand/react/shallow';
6
+ import { createLogger } from '../lib/logger';
7
+
8
+ const cacheDebug = createLogger('MediaCache');
6
9
 
7
10
  // Types
8
11
  interface BlobUrlEntry {
@@ -120,6 +123,7 @@ export const useMediaCacheStore = create<MediaCacheStore>()(
120
123
  const existing = get().blobUrls.get(key);
121
124
  if (existing) {
122
125
  // Increment ref count
126
+ cacheDebug.debug(`Blob URL reused: ${key}`, { refCount: existing.refCount + 1 });
123
127
  set(
124
128
  (state) => ({
125
129
  blobUrls: new Map(state.blobUrls).set(key, {
@@ -136,6 +140,8 @@ export const useMediaCacheStore = create<MediaCacheStore>()(
136
140
  // Create new blob URL
137
141
  const blob = new Blob([content], { type: mimeType });
138
142
  const url = URL.createObjectURL(blob);
143
+ const sizeMB = (content.byteLength / 1024 / 1024).toFixed(2);
144
+ cacheDebug.debug(`Blob URL created: ${key}`, { mimeType, size: `${sizeMB}MB` });
139
145
  set(
140
146
  (state) => ({
141
147
  blobUrls: new Map(state.blobUrls).set(key, {
@@ -156,6 +162,7 @@ export const useMediaCacheStore = create<MediaCacheStore>()(
156
162
 
157
163
  if (entry.refCount <= 1) {
158
164
  // Last reference - revoke and remove
165
+ cacheDebug.debug(`Blob URL revoked: ${key}`);
159
166
  URL.revokeObjectURL(entry.url);
160
167
  set(
161
168
  (state) => {
@@ -168,6 +175,7 @@ export const useMediaCacheStore = create<MediaCacheStore>()(
168
175
  );
169
176
  } else {
170
177
  // Decrement ref count
178
+ cacheDebug.debug(`Blob URL released: ${key}`, { refCount: entry.refCount - 1 });
171
179
  set(
172
180
  (state) => ({
173
181
  blobUrls: new Map(state.blobUrls).set(key, {
@@ -315,6 +323,8 @@ export const useMediaCacheStore = create<MediaCacheStore>()(
315
323
  // ========== Global ==========
316
324
 
317
325
  clearCache: () => {
326
+ const stats = get().getCacheStats();
327
+ cacheDebug.info('Clearing cache', stats);
318
328
  // Revoke all blob URLs before clearing
319
329
  get().blobUrls.forEach(({ url }) => URL.revokeObjectURL(url));
320
330
  set(initialState, false, 'clearCache');
@@ -46,6 +46,7 @@ import { SimpleAudioPlayer } from '@djangocfg/ui-nextjs';
46
46
  | Prop | Type | Default | Description |
47
47
  |------|------|---------|-------------|
48
48
  | `src` | `string` | required | Audio URL |
49
+ | `prefetch` | `boolean` | `true` | Pre-fetch audio as blob (required for streaming URLs to enable seek) |
49
50
  | `title` | `string` | - | Track title |
50
51
  | `artist` | `string` | - | Artist name |
51
52
  | `coverArt` | `string \| ReactNode` | - | Cover image URL or custom element |
@@ -54,6 +55,7 @@ import { SimpleAudioPlayer } from '@djangocfg/ui-nextjs';
54
55
  | `showEqualizer` | `boolean` | `false` | Show equalizer bars |
55
56
  | `showTimer` | `boolean` | `true` | Show time display |
56
57
  | `showVolume` | `boolean` | `true` | Show volume control |
58
+ | `showLoop` | `boolean` | `true` | Show loop/repeat button |
57
59
  | `reactiveCover` | `boolean` | `true` | Enable reactive effects |
58
60
  | `variant` | `VisualizationVariant` | - | Effect variant |
59
61
  | `intensity` | `EffectIntensity` | - | Effect intensity |
@@ -61,6 +63,8 @@ import { SimpleAudioPlayer } from '@djangocfg/ui-nextjs';
61
63
  | `autoPlay` | `boolean` | `false` | Auto-play on load |
62
64
  | `layout` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout direction |
63
65
 
66
+ > **Note:** The `prefetch` option is enabled by default. This fetches the entire audio file as a blob before loading into WaveSurfer, which is required for seeking to work correctly with streaming URLs. For very large files (> 50MB), consider using `prefetch={false}` and the Progressive player mode instead.
67
+
64
68
  ---
65
69
 
66
70
  ## Advanced Usage
@@ -99,7 +103,10 @@ Context provider for audio state. Wraps all audio components.
99
103
 
100
104
  ```tsx
101
105
  <AudioProvider
102
- source={{ uri: 'https://example.com/audio.mp3' }}
106
+ source={{
107
+ uri: 'https://example.com/audio.mp3',
108
+ prefetch: true // Fetch as blob for seek support (default: false)
109
+ }}
103
110
  containerRef={containerRef}
104
111
  autoPlay={false}
105
112
  waveformOptions={{
@@ -115,6 +122,13 @@ Context provider for audio state. Wraps all audio components.
115
122
  </AudioProvider>
116
123
  ```
117
124
 
125
+ #### AudioSource Options
126
+
127
+ | Prop | Type | Default | Description |
128
+ |------|------|---------|-------------|
129
+ | `uri` | `string` | required | Audio URL |
130
+ | `prefetch` | `boolean` | `false` | Pre-fetch as blob (enables seek for streaming URLs) |
131
+
118
132
  ### AudioPlayer
119
133
 
120
134
  Main player component with waveform and controls.
@@ -294,6 +308,7 @@ AudioPlayer/
294
308
  │ └── effects.ts # Visualization effect types
295
309
  ├── hooks/
296
310
  │ ├── index.ts
311
+ │ ├── useAudioSource.ts # Audio source loading with prefetch
297
312
  │ ├── useAudioHotkeys.ts # Keyboard shortcuts
298
313
  │ ├── useVisualization.tsx # Visualization settings
299
314
  │ ├── useAudioAnalysis.ts # Web Audio frequency analysis
@@ -53,6 +53,13 @@ export interface SimpleAudioPlayerProps {
53
53
  /** Audio source URL */
54
54
  src: string;
55
55
 
56
+ /**
57
+ * Pre-fetch audio as blob before loading into WaveSurfer.
58
+ * Required for streaming URLs because WaveSurfer needs complete file for seek to work.
59
+ * @default true
60
+ */
61
+ prefetch?: boolean;
62
+
56
63
  /** Track title */
57
64
  title?: string;
58
65
 
@@ -132,6 +139,7 @@ export function SimpleAudioPlayer(props: SimpleAudioPlayerProps) {
132
139
 
133
140
  function SimpleAudioPlayerContent({
134
141
  src,
142
+ prefetch = true,
135
143
  title,
136
144
  artist,
137
145
  coverArt,
@@ -190,7 +198,7 @@ function SimpleAudioPlayerContent({
190
198
 
191
199
  return (
192
200
  <AudioProvider
193
- source={{ uri: src }}
201
+ source={{ uri: src, prefetch }}
194
202
  containerRef={containerRef}
195
203
  autoPlay={autoPlay}
196
204
  waveformOptions={waveformOptions}