Line data Source code
1 : import 'package:cross_file/cross_file.dart';
2 : import 'package:flutter/services.dart';
3 : import 'package:media_source/src/media_type.dart';
4 : import 'package:media_source/src/sources/file_media_source.dart';
5 : import 'package:media_source/src/sources/media_source.dart';
6 : import 'package:media_source/src/sources/memory_media_source.dart';
7 : import 'package:path/path.dart' as p;
8 : import 'package:file_sized/file_sized.dart';
9 : import 'package:file_type_plus/file_type_plus.dart';
10 :
11 : /// Abstract base class for Flutter asset-based media sources.
12 : ///
13 : /// This class manages media content stored in the Flutter asset bundle. It provides:
14 : /// - Asset loading with automatic size detection
15 : /// - Type-specific asset media implementations
16 : /// - Conversion to file or in-memory representation
17 : /// - Factory methods for creating instances from asset paths
18 : ///
19 : /// Note: Assets are read-only resources bundled with the app. To get the size,
20 : /// the asset must be loaded, which reads the entire asset into memory.
21 : ///
22 : /// Subclasses include:
23 : /// - [VideoAssetMedia] for video assets
24 : /// - [AudioAssetMedia] for audio assets
25 : /// - [ImageAssetMedia] for image assets
26 : /// - [DocumentAssetMedia] for document assets
27 : /// - [OtherTypeAssetMedia] for unclassified assets
28 : abstract class AssetMediaSource<M extends FileType> extends MediaSource<M>
29 : implements ToFileConvertableMedia<M>, ToMemoryConvertableMedia<M> {
30 : /// The asset path in the Flutter asset bundle (e.g., 'assets/video.mp4').
31 : final String assetPath;
32 :
33 : /// Optional custom asset bundle. If null, [rootBundle] is used.
34 : final AssetBundle? bundle;
35 :
36 : /// Internal constructor for creating asset media sources.
37 : ///
38 : /// Parameters:
39 : /// - [assetPath]: Path to the asset in the bundle
40 : /// - [bundle]: Optional custom AssetBundle, defaults to rootBundle
41 : /// - [metadata]: Type-specific metadata (VideoType, AudioType, etc.)
42 : /// - [name]: Optional custom name, defaults to basename of asset path
43 : /// - [size]: File size (computed by loading asset if not provided)
44 : /// - [mimeType]: Optional MIME type, auto-detected from path if not provided
45 1 : AssetMediaSource._({
46 : required this.assetPath,
47 : this.bundle,
48 : required super.metadata,
49 : required String? name,
50 : required super.size,
51 : required String? mimeType,
52 1 : }) : super(
53 1 : mimeType: mimeType ?? FileUtil.getMimeTypeFromPath(assetPath),
54 1 : name: name ?? p.basename(assetPath),
55 : );
56 :
57 : /// Saves this asset to the file system at the specified path.
58 : ///
59 : /// Loads the asset bytes and writes them to disk as a file.
60 : /// Returns a new [FileMediaSource] instance pointing to the saved file.
61 : ///
62 : /// Parameters:
63 : /// - [path]: The destination file path
64 : Future<FileMediaSource<M>> saveTo(String path);
65 :
66 : /// Saves this asset to a folder, preserving the original filename.
67 : ///
68 : /// Convenience method that combines the folder path with the asset's name.
69 : ///
70 : /// Parameters:
71 : /// - [folderPath]: The directory where the file should be saved
72 : ///
73 : /// Returns a new [FileMediaSource] instance with the saved file.
74 4 : Future<FileMediaSource<M>> saveToFolder(String folderPath) => saveTo(p.join(folderPath, name));
75 :
76 : /// Includes the asset path and bundle in equality comparisons.
77 1 : @override
78 4 : List<Object?> get props => [assetPath, bundle, ...super.props];
79 :
80 : /// Loads an asset from the bundle and returns its bytes.
81 : ///
82 : /// This is a utility method used by all asset media types to load
83 : /// the raw bytes from the asset bundle.
84 : ///
85 : /// Parameters:
86 : /// - [assetPath]: Path to the asset in the bundle
87 : /// - [bundle]: Optional custom AssetBundle, defaults to rootBundle
88 : ///
89 : /// Returns the asset data as a [Uint8List].
90 1 : static Future<Uint8List> loadAsset(String assetPath, [AssetBundle? bundle]) async {
91 0 : final assetBundle = bundle ?? rootBundle;
92 :
93 : // Load the asset bytes
94 1 : final byteData = await assetBundle.load(assetPath);
95 2 : final binary = byteData.buffer.asUint8List();
96 : return binary;
97 : }
98 : }
99 :
100 : /// Represents video assets from the Flutter asset bundle.
101 : ///
102 : /// Stores video metadata including optional duration information.
103 : /// Supports conversion to file (by saving) and in-memory representation.
104 : ///
105 : /// Example:
106 : /// ```dart
107 : /// final video = await VideoAssetMedia.load(
108 : /// 'assets/videos/intro.mp4',
109 : /// duration: Duration(seconds: 30),
110 : /// );
111 : ///
112 : /// // Save to file system
113 : /// final fileMedia = await video.saveTo('/storage/intro.mp4');
114 : ///
115 : /// // Or convert to memory
116 : /// final memoryMedia = await video.convertToMemory();
117 : /// ```
118 : class VideoAssetMedia extends AssetMediaSource<VideoType> {
119 : /// Internal constructor for creating video asset media.
120 : ///
121 : /// Parameters:
122 : /// - [assetPath]: Path to the video asset
123 : /// - [bundle]: Optional custom AssetBundle
124 : /// - [name]: Display name
125 : /// - [duration]: Optional video duration
126 : /// - [size]: Asset size in bytes
127 : /// - [mimeType]: MIME type of the video
128 1 : VideoAssetMedia._({
129 : required super.assetPath,
130 : super.bundle,
131 : required super.name,
132 : required Duration? duration,
133 : required super.size,
134 : required super.mimeType,
135 2 : }) : super._(metadata: VideoType(duration));
136 :
137 : /// Loads a video asset from the Flutter asset bundle.
138 : ///
139 : /// This method loads the asset to determine its size. For large assets,
140 : /// consider providing the size parameter to avoid loading the asset twice.
141 : ///
142 : /// Parameters:
143 : /// - [assetPath]: Path to the video asset (e.g., 'assets/videos/intro.mp4')
144 : /// - [bundle]: Optional custom AssetBundle, defaults to rootBundle
145 : /// - [name]: Optional custom display name, defaults to asset filename
146 : /// - [duration]: Optional video duration
147 : /// - [mimeType]: Optional MIME type override, auto-detected from path
148 : /// - [size]: Optional pre-computed asset size to avoid loading asset
149 : ///
150 : /// Returns a [VideoAssetMedia] instance.
151 1 : static Future<VideoAssetMedia> load(
152 : String assetPath, {
153 : AssetBundle? bundle,
154 : String? name,
155 : Duration? duration,
156 : String? mimeType,
157 : FileSize? size,
158 : }) async {
159 1 : return VideoAssetMedia._(
160 : assetPath: assetPath,
161 : bundle: bundle,
162 : name: name,
163 : duration: duration,
164 3 : size: size ?? (await AssetMediaSource.loadAsset(assetPath, bundle)).lengthInBytes.b,
165 : mimeType: mimeType,
166 : );
167 : }
168 :
169 : /// Saves this video asset to the file system.
170 : ///
171 : /// Loads the asset bytes and writes them to the specified path.
172 : /// Creates the directory structure if it doesn't exist.
173 : ///
174 : /// Parameters:
175 : /// - [path]: The destination file path
176 : ///
177 : /// Returns a [VideoFileMedia] instance pointing to the saved file.
178 1 : @override
179 : Future<VideoFileMedia> saveTo(String path) async {
180 3 : final bytes = await AssetMediaSource.loadAsset(assetPath, bundle);
181 1 : final file = XFile.fromData(
182 : bytes,
183 1 : name: name,
184 1 : mimeType: mimeType,
185 1 : length: bytes.lengthInBytes,
186 : );
187 1 : final fileMedia = await VideoFileMedia.fromFile(
188 : file,
189 2 : duration: metadata.duration,
190 1 : mimeType: mimeType,
191 1 : name: name,
192 1 : size: size,
193 : );
194 1 : return fileMedia.saveTo(path);
195 : }
196 :
197 : /// Converts this video asset to an in-memory representation.
198 : ///
199 : /// Loads the asset bytes into a [VideoMemoryMedia] instance.
200 : /// Useful for uploading, processing, or passing to APIs that expect byte arrays.
201 : ///
202 : /// Returns a [VideoMemoryMedia] with the asset's data in memory.
203 1 : @override
204 : Future<MemoryMediaSource<VideoType>> convertToMemory() async {
205 1 : return VideoMemoryMedia(
206 3 : await AssetMediaSource.loadAsset(assetPath, bundle),
207 1 : name: name,
208 2 : duration: metadata.duration,
209 1 : mimeType: mimeType,
210 : );
211 : }
212 : }
213 :
214 : /// Represents audio assets from the Flutter asset bundle.
215 : ///
216 : /// Stores audio metadata including optional duration information.
217 : /// Supports conversion to file (by saving) and in-memory representation.
218 : ///
219 : /// Example:
220 : /// ```dart
221 : /// final audio = await AudioAssetMedia.load(
222 : /// 'assets/audio/song.mp3',
223 : /// duration: Duration(minutes: 3, seconds: 45),
224 : /// );
225 : /// ```
226 : class AudioAssetMedia extends AssetMediaSource<AudioType> {
227 : /// Internal constructor for creating audio asset media.
228 : ///
229 : /// Parameters:
230 : /// - [assetPath]: Path to the audio asset
231 : /// - [bundle]: Optional custom AssetBundle
232 : /// - [name]: Display name
233 : /// - [duration]: Optional audio duration
234 : /// - [size]: Asset size in bytes
235 : /// - [mimeType]: MIME type of the audio
236 1 : AudioAssetMedia._({
237 : required super.assetPath,
238 : super.bundle,
239 : required super.name,
240 : required Duration? duration,
241 : required super.size,
242 : required super.mimeType,
243 2 : }) : super._(metadata: AudioType(duration));
244 :
245 : /// Loads an audio asset from the Flutter asset bundle.
246 : ///
247 : /// This method loads the asset to determine its size. For large assets,
248 : /// consider providing the size parameter to avoid loading the asset twice.
249 : ///
250 : /// Parameters:
251 : /// - [assetPath]: Path to the audio asset (e.g., 'assets/audio/song.mp3')
252 : /// - [bundle]: Optional custom AssetBundle, defaults to rootBundle
253 : /// - [name]: Optional custom display name, defaults to asset filename
254 : /// - [duration]: Optional audio duration
255 : /// - [mimeType]: Optional MIME type override, auto-detected from path
256 : /// - [size]: Optional pre-computed asset size to avoid loading asset
257 : ///
258 : /// Returns an [AudioAssetMedia] instance.
259 1 : static Future<AudioAssetMedia> load(
260 : String assetPath, {
261 : AssetBundle? bundle,
262 : String? name,
263 : Duration? duration,
264 : String? mimeType,
265 : FileSize? size,
266 : }) async {
267 1 : return AudioAssetMedia._(
268 : assetPath: assetPath,
269 : bundle: bundle,
270 : name: name,
271 : duration: duration,
272 3 : size: size ?? (await AssetMediaSource.loadAsset(assetPath, bundle)).lengthInBytes.b,
273 : mimeType: mimeType,
274 : );
275 : }
276 :
277 : /// Saves this audio asset to the file system.
278 : ///
279 : /// Loads the asset bytes and writes them to the specified path.
280 : /// Creates the directory structure if it doesn't exist.
281 : ///
282 : /// Parameters:
283 : /// - [path]: The destination file path
284 : ///
285 : /// Returns an [AudioFileMedia] instance pointing to the saved file.
286 1 : @override
287 : Future<AudioFileMedia> saveTo(String path) async {
288 3 : final bytes = await AssetMediaSource.loadAsset(assetPath, bundle);
289 1 : final file = XFile.fromData(
290 : bytes,
291 1 : name: name,
292 1 : mimeType: mimeType,
293 1 : length: bytes.lengthInBytes,
294 : );
295 1 : final fileMedia = await AudioFileMedia.fromFile(
296 : file,
297 2 : duration: metadata.duration,
298 1 : mimeType: mimeType,
299 1 : name: name,
300 1 : size: size,
301 : );
302 1 : return fileMedia.saveTo(path);
303 : }
304 :
305 : /// Converts this audio asset to an in-memory representation.
306 : ///
307 : /// Loads the asset bytes into an [AudioMemoryMedia] instance.
308 : /// Useful for uploading, processing, or passing to APIs that expect byte arrays.
309 : ///
310 : /// Returns an [AudioMemoryMedia] with the asset's data in memory.
311 1 : @override
312 : Future<MemoryMediaSource<AudioType>> convertToMemory() async {
313 1 : return AudioMemoryMedia(
314 3 : await AssetMediaSource.loadAsset(assetPath, bundle),
315 1 : name: name,
316 2 : duration: metadata.duration,
317 1 : mimeType: mimeType,
318 : );
319 : }
320 : }
321 :
322 : /// Represents image assets from the Flutter asset bundle.
323 : ///
324 : /// Stores image metadata and supports conversion to file (by saving)
325 : /// and in-memory representation.
326 : ///
327 : /// Example:
328 : /// ```dart
329 : /// final image = await ImageAssetMedia.load('assets/images/logo.png');
330 : ///
331 : /// // Save to cache directory
332 : /// final cachedFile = await image.saveTo('/cache/logo.png');
333 : /// ```
334 : class ImageAssetMedia extends AssetMediaSource<ImageType> {
335 : /// Internal constructor for creating image asset media.
336 : ///
337 : /// Parameters:
338 : /// - [assetPath]: Path to the image asset
339 : /// - [bundle]: Optional custom AssetBundle
340 : /// - [name]: Display name
341 : /// - [size]: Asset size in bytes
342 : /// - [mimeType]: MIME type of the image
343 1 : ImageAssetMedia._({
344 : required super.assetPath,
345 : super.bundle,
346 : required super.name,
347 : required super.size,
348 : required super.mimeType,
349 2 : }) : super._(metadata: ImageType());
350 :
351 : /// Loads an image asset from the Flutter asset bundle.
352 : ///
353 : /// This method loads the asset to determine its size. For large assets,
354 : /// consider providing the size parameter to avoid loading the asset twice.
355 : ///
356 : /// Parameters:
357 : /// - [assetPath]: Path to the image asset (e.g., 'assets/images/logo.png')
358 : /// - [bundle]: Optional custom AssetBundle, defaults to rootBundle
359 : /// - [name]: Optional custom display name, defaults to asset filename
360 : /// - [mimeType]: Optional MIME type override, auto-detected from path
361 : /// - [size]: Optional pre-computed asset size to avoid loading asset
362 : ///
363 : /// Returns an [ImageAssetMedia] instance.
364 1 : static Future<ImageAssetMedia> load(
365 : String assetPath, {
366 : AssetBundle? bundle,
367 : String? name,
368 : String? mimeType,
369 : FileSize? size,
370 : }) async {
371 1 : return ImageAssetMedia._(
372 : assetPath: assetPath,
373 : bundle: bundle,
374 : name: name,
375 3 : size: size ?? (await AssetMediaSource.loadAsset(assetPath, bundle)).lengthInBytes.b,
376 : mimeType: mimeType,
377 : );
378 : }
379 :
380 : /// Saves this image asset to the file system.
381 : ///
382 : /// Loads the asset bytes and writes them to the specified path.
383 : /// Creates the directory structure if it doesn't exist.
384 : ///
385 : /// Parameters:
386 : /// - [path]: The destination file path
387 : ///
388 : /// Returns an [ImageFileMedia] instance pointing to the saved file.
389 1 : @override
390 : Future<ImageFileMedia> saveTo(String path) async {
391 3 : final bytes = await AssetMediaSource.loadAsset(assetPath, bundle);
392 1 : final file = XFile.fromData(
393 : bytes,
394 1 : name: name,
395 1 : mimeType: mimeType,
396 1 : length: bytes.lengthInBytes,
397 : );
398 1 : final fileMedia = await ImageFileMedia.fromFile(
399 : file,
400 1 : mimeType: mimeType,
401 1 : name: name,
402 1 : size: size,
403 : );
404 1 : return fileMedia.saveTo(path);
405 : }
406 :
407 : /// Converts this image asset to an in-memory representation.
408 : ///
409 : /// Loads the asset bytes into an [ImageMemoryMedia] instance.
410 : /// Useful for image processing, uploading, or displaying in widgets.
411 : ///
412 : /// Returns an [ImageMemoryMedia] with the asset's data in memory.
413 1 : @override
414 : Future<MemoryMediaSource<ImageType>> convertToMemory() async {
415 1 : return ImageMemoryMedia(
416 3 : await AssetMediaSource.loadAsset(assetPath, bundle),
417 1 : name: name,
418 1 : mimeType: mimeType,
419 : );
420 : }
421 : }
422 :
423 : /// Represents document assets from the Flutter asset bundle.
424 : ///
425 : /// Supports documents like PDF, DOCX, XLSX, etc. bundled with the app.
426 : /// Provides conversion to file (by saving) and in-memory representation.
427 : ///
428 : /// Example:
429 : /// ```dart
430 : /// final pdf = await DocumentAssetMedia.load('assets/docs/manual.pdf');
431 : /// final fileMedia = await pdf.saveTo('/downloads/manual.pdf');
432 : /// ```
433 : class DocumentAssetMedia extends AssetMediaSource<DocumentType> {
434 : /// Internal constructor for creating document asset media.
435 : ///
436 : /// Parameters:
437 : /// - [assetPath]: Path to the document asset
438 : /// - [bundle]: Optional custom AssetBundle
439 : /// - [name]: Display name
440 : /// - [size]: Asset size in bytes
441 : /// - [mimeType]: MIME type of the document
442 1 : DocumentAssetMedia._({
443 : required super.assetPath,
444 : super.bundle,
445 : required super.name,
446 : required super.size,
447 : required super.mimeType,
448 2 : }) : super._(metadata: DocumentType());
449 :
450 : /// Loads a document asset from the Flutter asset bundle.
451 : ///
452 : /// This method loads the asset to determine its size. For large assets,
453 : /// consider providing the size parameter to avoid loading the asset twice.
454 : ///
455 : /// Parameters:
456 : /// - [assetPath]: Path to the document asset (e.g., 'assets/docs/manual.pdf')
457 : /// - [bundle]: Optional custom AssetBundle, defaults to rootBundle
458 : /// - [name]: Optional custom display name, defaults to asset filename
459 : /// - [mimeType]: Optional MIME type override, auto-detected from path
460 : /// - [size]: Optional pre-computed asset size to avoid loading asset
461 : ///
462 : /// Returns a [DocumentAssetMedia] instance.
463 1 : static Future<DocumentAssetMedia> load(
464 : String assetPath, {
465 : AssetBundle? bundle,
466 : String? name,
467 : String? mimeType,
468 : FileSize? size,
469 : }) async {
470 1 : return DocumentAssetMedia._(
471 : assetPath: assetPath,
472 : bundle: bundle,
473 : name: name,
474 3 : size: size ?? (await AssetMediaSource.loadAsset(assetPath, bundle)).lengthInBytes.b,
475 : mimeType: mimeType,
476 : );
477 : }
478 :
479 : /// Saves this document asset to the file system.
480 : ///
481 : /// Loads the asset bytes and writes them to the specified path.
482 : /// Creates the directory structure if it doesn't exist.
483 : ///
484 : /// Parameters:
485 : /// - [path]: The destination file path
486 : ///
487 : /// Returns a [DocumentFileMedia] instance pointing to the saved file.
488 1 : @override
489 : Future<DocumentFileMedia> saveTo(String path) async {
490 3 : final bytes = await AssetMediaSource.loadAsset(assetPath, bundle);
491 1 : final file = XFile.fromData(
492 : bytes,
493 1 : name: name,
494 1 : mimeType: mimeType,
495 1 : length: bytes.lengthInBytes,
496 : );
497 1 : final fileMedia = await DocumentFileMedia.fromFile(
498 : file,
499 1 : mimeType: mimeType,
500 1 : name: name,
501 1 : size: size,
502 : );
503 1 : return fileMedia.saveTo(path);
504 : }
505 :
506 : /// Converts this document asset to an in-memory representation.
507 : ///
508 : /// Loads the asset bytes into a [DocumentMemoryMedia] instance.
509 : /// Useful for sharing, uploading, or processing documents.
510 : ///
511 : /// Returns a [DocumentMemoryMedia] with the asset's data in memory.
512 1 : @override
513 : Future<MemoryMediaSource<DocumentType>> convertToMemory() async {
514 1 : return DocumentMemoryMedia(
515 3 : await AssetMediaSource.loadAsset(assetPath, bundle),
516 1 : name: name,
517 1 : mimeType: mimeType,
518 : );
519 : }
520 : }
521 :
522 : /// Represents assets of unclassified or unknown types.
523 : ///
524 : /// Used for asset files that don't fit into the standard categories
525 : /// (video, audio, image, document). Provides the same operations as
526 : /// other asset media types.
527 : ///
528 : /// Example:
529 : /// ```dart
530 : /// final data = await OtherTypeAssetMedia.load('assets/data/config.json');
531 : /// ```
532 : class OtherTypeAssetMedia extends AssetMediaSource<OtherType> {
533 : /// Internal constructor for creating other type asset media.
534 : ///
535 : /// Parameters:
536 : /// - [assetPath]: Path to the asset
537 : /// - [bundle]: Optional custom AssetBundle
538 : /// - [name]: Display name
539 : /// - [size]: Asset size in bytes
540 : /// - [mimeType]: MIME type of the asset
541 1 : @override
542 : OtherTypeAssetMedia._({
543 : required super.assetPath,
544 : super.bundle,
545 : required super.name,
546 : required super.size,
547 : required super.mimeType,
548 2 : }) : super._(metadata: OtherType());
549 :
550 : /// Loads an asset of unknown type from the Flutter asset bundle.
551 : ///
552 : /// This method loads the asset to determine its size. For large assets,
553 : /// consider providing the size parameter to avoid loading the asset twice.
554 : ///
555 : /// Parameters:
556 : /// - [assetPath]: Path to the asset (e.g., 'assets/data/config.json')
557 : /// - [bundle]: Optional custom AssetBundle, defaults to rootBundle
558 : /// - [name]: Optional custom display name, defaults to asset filename
559 : /// - [mimeType]: Optional MIME type override, auto-detected from path
560 : /// - [size]: Optional pre-computed asset size to avoid loading asset
561 : ///
562 : /// Returns an [OtherTypeAssetMedia] instance.
563 1 : static Future<OtherTypeAssetMedia> load(
564 : String assetPath, {
565 : AssetBundle? bundle,
566 : String? name,
567 : String? mimeType,
568 : FileSize? size,
569 : }) async {
570 1 : return OtherTypeAssetMedia._(
571 : assetPath: assetPath,
572 : bundle: bundle,
573 : name: name,
574 3 : size: size ?? (await AssetMediaSource.loadAsset(assetPath, bundle)).lengthInBytes.b,
575 : mimeType: mimeType,
576 : );
577 : }
578 :
579 : /// Saves this asset to the file system.
580 : ///
581 : /// Loads the asset bytes and writes them to the specified path.
582 : /// Creates the directory structure if it doesn't exist.
583 : ///
584 : /// Parameters:
585 : /// - [path]: The destination file path
586 : ///
587 : /// Returns an [OtherTypeFileMedia] instance pointing to the saved file.
588 1 : @override
589 : Future<OtherTypeFileMedia> saveTo(String path) async {
590 3 : final bytes = await AssetMediaSource.loadAsset(assetPath, bundle);
591 1 : final file = XFile.fromData(
592 : bytes,
593 1 : name: name,
594 1 : mimeType: mimeType,
595 1 : length: bytes.lengthInBytes,
596 : );
597 1 : final fileMedia = await OtherTypeFileMedia.fromFile(
598 : file,
599 1 : mimeType: mimeType,
600 1 : name: name,
601 1 : size: size,
602 : );
603 1 : return fileMedia.saveTo(path);
604 : }
605 :
606 : /// Converts this asset to an in-memory representation.
607 : ///
608 : /// Loads the asset bytes into an [OtherTypeMemoryMedia] instance.
609 : /// Useful for processing or handling custom file types.
610 : ///
611 : /// Returns an [OtherTypeMemoryMedia] with the asset's data in memory.
612 1 : @override
613 : Future<MemoryMediaSource<OtherType>> convertToMemory() async {
614 1 : return OtherTypeMemoryMedia(
615 3 : await AssetMediaSource.loadAsset(assetPath, bundle),
616 1 : name: name,
617 1 : mimeType: mimeType,
618 : );
619 : }
620 : }
|