device_media_finder 0.0.1 copy "device_media_finder: ^0.0.1" to clipboard
device_media_finder: ^0.0.1 copied to clipboard

A Flutter plugin for accessing media files (videos and audio) on the device with support for various formats and thumbnails.

Device Media Finder #

A Flutter plugin for accessing media files (videos and audio) on the device with support for various formats and thumbnails.

Features #

  • Get videos from the device
  • Get videos filtered by specific MIME types
  • Get audio files from the device
  • Generate thumbnails for videos
  • Fetch videos with thumbnails in a single call

Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  device_media_finder: ^0.0.1

Usage #

First, import the package:

import 'package:device_media_finder/device_media_finder.dart';

Then create an instance of DeviceMediaFinder:

final deviceMediaFinder = DeviceMediaFinder();

Get All Videos #

Retrieve all videos from the device:

Future<void> getAllVideos() async {
  try {
    final videos = await deviceMediaFinder.getVideos();
    print('Found ${videos.length} videos');

    // Access video properties
    for (final video in videos) {
      print('Video name: ${video.name}');
      print('Video size: ${(video.size / (1024 * 1024)).toStringAsFixed(2)} MB');
      print('Video duration: ${_formatDuration(video.duration)}');
      print('Video path: ${video.path}');
      print('Video MIME type: ${video.mimeType}');
    }
  } catch (e) {
    print('Error getting videos: $e');
  }
}

String _formatDuration(int milliseconds) {
  final seconds = (milliseconds / 1000).floor();
  final minutes = (seconds / 60).floor();
  final hours = (minutes / 60).floor();

  final remainingMinutes = minutes % 60;
  final remainingSeconds = seconds % 60;

  if (hours > 0) {
    return '$hours:${remainingMinutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
  } else {
    return '$minutes:${remainingSeconds.toString().padLeft(2, '0')}';
  }
}

Example 2: Display videos in a ListView:

class VideoListScreen extends StatefulWidget {
  @override
  _VideoListScreenState createState() => _VideoListScreenState();
}

class _VideoListScreenState extends State<VideoListScreen> {
  final deviceMediaFinder = DeviceMediaFinder();
  List<VideoFile> videos = [];
  bool isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadVideos();
  }

  Future<void> _loadVideos() async {
    setState(() {
      isLoading = true;
    });

    try {
      final result = await deviceMediaFinder.getVideos();
      setState(() {
        videos = result;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error loading videos: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Videos')),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : ListView.builder(
              itemCount: videos.length,
              itemBuilder: (context, index) {
                final video = videos[index];
                return ListTile(
                  title: Text(video.name),
                  subtitle: Text('${(video.size / (1024 * 1024)).toStringAsFixed(2)} MB'),
                  trailing: Text(_formatDuration(video.duration)),
                );
              },
            ),
    );
  }
}

Get Videos by MIME Type #

Filter videos by specific MIME types:

Future<void> getVideosByFormat() async {
  try {
    // Get only MP4 and 3GPP videos
    final videos = await deviceMediaFinder.getVideosByMimeType([
      'video/mp4',
      'video/3gpp',
       'video/*', // Fallback to catch any other video types
    ]);

    print('Found ${videos.length} MP4 and 3GPP videos');

    // Process the videos
    for (final video in videos) {
      print('Video name: ${video.name}');
      print('Video MIME type: ${video.mimeType}');
    }
  } catch (e) {
    print('Error getting videos by MIME type: $e');
  }
}

Example 2: Get videos of multiple formats:

Future<void> getAllVideoFormats() async {
  try {
    // Get videos of various formats
    final videos = await deviceMediaFinder.getVideosByMimeType([
      'video/mp4',
      'video/3gpp',
      'video/webm',
      'video/quicktime',
      'video/x-matroska',
      'video/avi',
      'video/mpeg',
      'video/x-ms-wmv',
      'video/*', // Fallback to catch any other video types
    ]);

    // Group videos by format
    final videosByFormat = <String, List<VideoFile>>{};

    for (final video in videos) {
      final format = video.mimeType;
      if (!videosByFormat.containsKey(format)) {
        videosByFormat[format] = [];
      }
      videosByFormat[format]!.add(video);
    }

    // Print summary
    videosByFormat.forEach((format, formatVideos) {
      print('Format: $format, Count: ${formatVideos.length}');
    });
  } catch (e) {
    print('Error getting videos by format: $e');
  }
}

Get Audio Files #

Retrieve audio files from the device:

Future<void> getAudioFiles() async {
  try {
    final audios = await deviceMediaFinder.getAudios();
    print('Found ${audios.length} audio files');

    // Access audio properties
    for (final audio in audios) {
      print('Audio name: ${audio.name}');
      print('Artist: ${audio.artist}');
      print('Album: ${audio.album}');
      print('Duration: ${_formatDuration(audio.duration)}');
      print('Size: ${(audio.size / (1024 * 1024)).toStringAsFixed(2)} MB');
    }
  } catch (e) {
    print('Error getting audio files: $e');
  }
}

Example 2: Display audio files in a ListView with artist and album information:

class AudioListScreen extends StatefulWidget {
  @override
  _AudioListScreenState createState() => _AudioListScreenState();
}

class _AudioListScreenState extends State<AudioListScreen> {
  final deviceMediaFinder = DeviceMediaFinder();
  List<AudioFile> audios = [];
  bool isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadAudios();
  }

  Future<void> _loadAudios() async {
    setState(() {
      isLoading = true;
    });

    try {
      final result = await deviceMediaFinder.getAudios();
      setState(() {
        audios = result;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error loading audio files: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Music')),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : ListView.builder(
              itemCount: audios.length,
              itemBuilder: (context, index) {
                final audio = audios[index];
                return ListTile(
                  leading: CircleAvatar(child: Icon(Icons.music_note)),
                  title: Text(audio.name),
                  subtitle: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('Artist: ${audio.artist}'),
                      Text('Album: ${audio.album}'),
                    ],
                  ),
                  trailing: Text(_formatDuration(audio.duration)),
                );
              },
            ),
    );
  }
}

Get Video Thumbnails #

Generate thumbnails for videos:

Future<void> getVideoThumbnail(String videoId) async {
  try {
    // Get thumbnail with default size (128x128)
    final thumbnail = await deviceMediaFinder.getVideoThumbnail(videoId);

    if (thumbnail != null) {
      print('Thumbnail size: ${thumbnail.length} bytes');

      // Use the thumbnail in an Image widget
      final image = Image.memory(thumbnail);

      // Or save it to a file
      final file = File('path/to/save/thumbnail.jpg');
      await file.writeAsBytes(thumbnail);
    } else {
      print('Failed to generate thumbnail');
    }
  } catch (e) {
    print('Error getting thumbnail: $e');
  }
}

Example 2: Get a larger thumbnail:

Future<Widget> getVideoThumbnailWidget(String videoId) async {
  try {
    // Get a larger thumbnail (256x256)
    final thumbnail = await deviceMediaFinder.getVideoThumbnail(
      videoId,
      width: 256,
      height: 256,
    );

    if (thumbnail != null) {
      // Return an image widget with the thumbnail
      return Image.memory(
        thumbnail,
        fit: BoxFit.cover,
        width: 256,
        height: 256,
      );
    } else {
      // Return a placeholder if thumbnail generation failed
      return Container(
        width: 256,
        height: 256,
        color: Colors.grey,
        child: Icon(Icons.video_file, size: 64),
      );
    }
  } catch (e) {
    print('Error getting thumbnail: $e');
    // Return an error placeholder
    return Container(
      width: 256,
      height: 256,
      color: Colors.red.withOpacity(0.3),
      child: Icon(Icons.error, size: 64),
    );
  }
}

Get Video with Thumbnail #

Fetch a video and its thumbnail in a single call:

Future<void> getVideoWithThumbnail(String videoId) async {
  try {
    // Get the video with its thumbnail
    final video = await deviceMediaFinder.getVideoWithThumbnail(videoId);

    print('Video name: ${video.name}');
    print('Video duration: ${_formatDuration(video.duration)}');

    if (video.thumbnailData != null) {
      print('Thumbnail size: ${video.thumbnailData!.length} bytes');

      // Use the thumbnail in an Image widget
      final image = Image.memory(video.thumbnailData!);
    } else {
      print('No thumbnail available');
    }
  } catch (e) {
    print('Error getting video with thumbnail: $e');
  }
}

Example 2: Display a video with its thumbnail in a card:

class VideoDetailCard extends StatelessWidget {
  final String videoId;
  final DeviceMediaFinder deviceMediaFinder = DeviceMediaFinder();

  VideoDetailCard({required this.videoId});

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<VideoFile>(
      future: deviceMediaFinder.getVideoWithThumbnail(
        videoId,
        width: 192,
        height: 108, // 16:9 aspect ratio
      ),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Card(
            child: Container(
              height: 200,
              child: Center(child: CircularProgressIndicator()),
            ),
          );
        }

        if (snapshot.hasError) {
          return Card(
            child: Container(
              height: 200,
              child: Center(child: Text('Error loading video')),
            ),
          );
        }

        if (!snapshot.hasData) {
          return Card(
            child: Container(
              height: 200,
              child: Center(child: Text('Video not found')),
            ),
          );
        }

        final video = snapshot.data!;

        return Card(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // Thumbnail
              video.thumbnailData != null
                  ? Image.memory(
                      video.thumbnailData!,
                      height: 180,
                      width: double.infinity,
                      fit: BoxFit.cover,
                    )
                  : Container(
                      height: 180,
                      width: double.infinity,
                      color: Colors.grey,
                      child: Icon(Icons.video_file, size: 64),
                    ),

              // Video details
              Padding(
                padding: EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      video.name,
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    SizedBox(height: 4),
                    Row(
                      children: [
                        Text('Duration: ${_formatDuration(video.duration)}'),
                        Spacer(),
                        Text('${(video.size / (1024 * 1024)).toStringAsFixed(2)} MB'),
                      ],
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

Get All Videos with Auto-Generated Thumbnails #

Fetch all videos with their thumbnails in a single call:

Future<void> loadAllVideosWithThumbnails() async {
  try {
    // Get all videos with thumbnails (default thumbnail size: 128x128)
    final videos = await deviceMediaFinder.getVideosWithAutoThumbnails();

    print('Loaded ${videos.length} videos with thumbnails');

    // Count videos that have thumbnails
    final videosWithThumbnails = videos.where((v) => v.thumbnailData != null).length;
    print('$videosWithThumbnails videos have thumbnails');

    // Use the videos with thumbnails
    for (final video in videos) {
      if (video.thumbnailData != null) {
        // Use the thumbnail
        final thumbnailWidget = Image.memory(video.thumbnailData!);
      }
    }
  } catch (e) {
    print('Error loading videos with thumbnails: $e');
  }
}

Example 2: Display videos in a grid with custom thumbnail size:

class VideoGridScreen extends StatefulWidget {
  @override
  _VideoGridScreenState createState() => _VideoGridScreenState();
}

class _VideoGridScreenState extends State<VideoGridScreen> {
  final deviceMediaFinder = DeviceMediaFinder();
  bool isLoading = true;
  List<VideoFile> videos = [];

  @override
  void initState() {
    super.initState();
    _loadVideos();
  }

  Future<void> _loadVideos() async {
    setState(() {
      isLoading = true;
    });

    try {
      // Get videos with larger thumbnails for better quality in the grid
      final result = await deviceMediaFinder.getVideosWithAutoThumbnails(
        width: 256,
        height: 144, // 16:9 aspect ratio
      );

      setState(() {
        videos = result;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error loading videos: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Video Gallery')),
      body: isLoading
        ? Center(child: CircularProgressIndicator())
        : videos.isEmpty
          ? Center(child: Text('No videos found'))
          : GridView.builder(
              padding: EdgeInsets.all(8),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                childAspectRatio: 16 / 9,
                crossAxisSpacing: 8,
                mainAxisSpacing: 8,
              ),
              itemCount: videos.length,
              itemBuilder: (context, index) {
                final video = videos[index];
                return GestureDetector(
                  onTap: () {
                    // Handle video tap (e.g., play the video)
                    print('Play video: ${video.path}');
                  },
                  child: Stack(
                    fit: StackFit.expand,
                    children: [
                      // Thumbnail
                      video.thumbnailData != null
                        ? Image.memory(
                            video.thumbnailData!,
                            fit: BoxFit.cover,
                          )
                        : Container(
                            color: Colors.grey.shade300,
                            child: Icon(Icons.video_file, size: 48),
                          ),

                      // Video name overlay
                      Positioned(
                        bottom: 0,
                        left: 0,
                        right: 0,
                        child: Container(
                          color: Colors.black.withOpacity(0.5),
                          padding: EdgeInsets.all(4),
                          child: Text(
                            video.name,
                            style: TextStyle(color: Colors.white),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ),
                      ),

                      // Play icon overlay
                      Center(
                        child: Icon(
                          Icons.play_circle_outline,
                          color: Colors.white.withOpacity(0.7),
                          size: 48,
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
    );
  }
}

Generate Thumbnails for a List of Videos #

Generate thumbnails for a list of videos you already have:

Future<void> getThumbnailsForFilteredVideos() async {
  try {
    // First get all videos
    final allVideos = await deviceMediaFinder.getVideos();

    // Filter videos (e.g., only MP4 videos)
    final mp4Videos = allVideos.where(
      (video) => video.mimeType == 'video/mp4'
    ).toList();

    print('Found ${mp4Videos.length} MP4 videos');

    // Generate thumbnails for the filtered videos
    final videosWithThumbnails = await deviceMediaFinder.generateThumbnailsForVideos(
      mp4Videos,
      width: 192,
      height: 108, // 16:9 aspect ratio
    );

    // Use the videos with thumbnails
    for (final video in videosWithThumbnails) {
      if (video.thumbnailData != null) {
        print('Video ${video.name} has a thumbnail');
      }
    }
  } catch (e) {
    print('Error: $e');
  }
}

Example 2: Get thumbnails for recent videos:

Future<List<VideoFile>> getRecentVideosWithThumbnails() async {
  final deviceMediaFinder = DeviceMediaFinder();

  try {
    // Get all videos
    final allVideos = await deviceMediaFinder.getVideos();

    // Filter videos added in the last 7 days
    final now = DateTime.now();
    final oneWeekAgo = now.subtract(Duration(days: 7));

    final recentVideos = allVideos.where((video) {
      final dateAdded = DateTime.fromMillisecondsSinceEpoch(video.dateAdded * 1000);
      return dateAdded.isAfter(oneWeekAgo);
    }).toList();

    print('Found ${recentVideos.length} videos from the last 7 days');

    // Generate thumbnails for the recent videos
    final videosWithThumbnails = await deviceMediaFinder.generateThumbnailsForVideos(
      recentVideos,
    );

    return videosWithThumbnails;
  } catch (e) {
    print('Error getting recent videos with thumbnails: $e');
    return [];
  }
}

Permissions #

Android #

Add the following permissions to your AndroidManifest.xml:

<!-- For Android 13 and above -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<!-- For devices with Android 12 or lower -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

iOS #

Add the following to your Info.plist:

<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photo library to display your videos and generate thumbnails.</string>

Supported Formats #

Video Formats #

  • MP4 (video/mp4)
  • 3GPP (video/3gpp)
  • WebM (video/webm)
  • QuickTime (video/quicktime)
  • Matroska (video/x-matroska)
  • AVI (video/avi)
  • MPEG (video/mpeg)
  • WMV (video/x-ms-wmv)
  • And others (using video/*)

Audio Formats #

  • MP3
  • AAC
  • WAV
  • FLAC
  • OGG
  • And others supported by the device

Playing Media Files #

Playing Videos from Thumbnails #

When you display thumbnails, you can use the video's path or URI to play it when the user taps on the thumbnail:

import 'package:video_player/video_player.dart';

class VideoThumbnailPlayer extends StatefulWidget {
  final VideoFile video;

  const VideoThumbnailPlayer({Key? key, required this.video}) : super(key: key);

  @override
  _VideoThumbnailPlayerState createState() => _VideoThumbnailPlayerState();
}

class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
  late VideoPlayerController _controller;
  bool _isPlaying = false;

  @override
  void initState() {
    super.initState();
    // Initialize the controller with the video file path
    _controller = VideoPlayerController.file(File(widget.video.path))
      ..initialize().then((_) {
        setState(() {});
      });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        GestureDetector(
          onTap: () {
            setState(() {
              if (_controller.value.isInitialized) {
                if (_isPlaying) {
                  _controller.pause();
                } else {
                  _controller.play();
                }
                _isPlaying = !_isPlaying;
              }
            });
          },
          child: Stack(
            alignment: Alignment.center,
            children: [
              // Show thumbnail until video is initialized
              if (!_controller.value.isInitialized && widget.video.thumbnailData != null)
                Image.memory(
                  widget.video.thumbnailData!,
                  width: 320,
                  height: 180,
                  fit: BoxFit.cover,
                )
              else if (_controller.value.isInitialized)
                AspectRatio(
                  aspectRatio: _controller.value.aspectRatio,
                  child: VideoPlayer(_controller),
                )
              else
                Container(
                  width: 320,
                  height: 180,
                  color: Colors.black,
                  child: Center(child: CircularProgressIndicator()),
                ),

              // Show play/pause button overlay
              if (!_controller.value.isInitialized || !_isPlaying)
                Icon(
                  Icons.play_circle_fill,
                  size: 64,
                  color: Colors.white.withOpacity(0.7),
                ),
            ],
          ),
        ),

        // Video details
        Padding(
          padding: EdgeInsets.all(8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                widget.video.name,
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              Text('Duration: ${_formatDuration(widget.video.duration)}'),
              Text('Path: ${widget.video.path}'),
            ],
          ),
        ),
      ],
    );
  }

  String _formatDuration(int milliseconds) {
    final seconds = (milliseconds / 1000).floor();
    final minutes = (seconds / 60).floor();
    final hours = (minutes / 60).floor();

    final remainingMinutes = minutes % 60;
    final remainingSeconds = seconds % 60;

    if (hours > 0) {
      return '$hours:${remainingMinutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
    } else {
      return '$minutes:${remainingSeconds.toString().padLeft(2, '0')}';
    }
  }
}

Using Video Thumbnails in a Grid with Playback #

Here's an example of how to create a video gallery with thumbnails that can play videos when tapped:

class VideoGalleryScreen extends StatefulWidget {
  @override
  _VideoGalleryScreenState createState() => _VideoGalleryScreenState();
}

class _VideoGalleryScreenState extends State<VideoGalleryScreen> {
  final deviceMediaFinder = DeviceMediaFinder();
  List<VideoFile> videos = [];
  bool isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadVideos();
  }

  Future<void> _loadVideos() async {
    setState(() {
      isLoading = true;
    });

    try {
      // Get videos with thumbnails
      final result = await deviceMediaFinder.getVideosWithAutoThumbnails(
        width: 256,
        height: 144,
      );

      setState(() {
        videos = result;
        isLoading = false;
      });
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error loading videos: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Video Gallery')),
      body: isLoading
        ? Center(child: CircularProgressIndicator())
        : GridView.builder(
            padding: EdgeInsets.all(8),
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              childAspectRatio: 16 / 9,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
            ),
            itemCount: videos.length,
            itemBuilder: (context, index) {
              final video = videos[index];
              return GestureDetector(
                onTap: () {
                  // Navigate to a video player screen when thumbnail is tapped
                  Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (context) => VideoPlayerScreen(video: video),
                    ),
                  );
                },
                child: Stack(
                  fit: StackFit.expand,
                  children: [
                    // Thumbnail
                    video.thumbnailData != null
                      ? Image.memory(
                          video.thumbnailData!,
                          fit: BoxFit.cover,
                        )
                      : Container(
                          color: Colors.grey.shade300,
                          child: Icon(Icons.video_file, size: 48),
                        ),

                    // Video name overlay
                    Positioned(
                      bottom: 0,
                      left: 0,
                      right: 0,
                      child: Container(
                        color: Colors.black.withOpacity(0.5),
                        padding: EdgeInsets.all(4),
                        child: Text(
                          video.name,
                          style: TextStyle(color: Colors.white),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                    ),

                    // Play icon overlay
                    Center(
                      child: Icon(
                        Icons.play_circle_outline,
                        color: Colors.white.withOpacity(0.7),
                        size: 48,
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
    );
  }
}

// Video player screen that uses the video path to play the video
class VideoPlayerScreen extends StatefulWidget {
  final VideoFile video;

  const VideoPlayerScreen({Key? key, required this.video}) : super(key: key);

  @override
  _VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  late VideoPlayerController _controller;
  bool _isInitialized = false;

  @override
  void initState() {
    super.initState();
    // Initialize the controller with the video file path
    _controller = VideoPlayerController.file(File(widget.video.path))
      ..initialize().then((_) {
        setState(() {
          _isInitialized = true;
          // Auto-play when ready
          _controller.play();
        });
      });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.video.name)),
      body: Column(
        children: [
          // Video player
          _isInitialized
            ? AspectRatio(
                aspectRatio: _controller.value.aspectRatio,
                child: VideoPlayer(_controller),
              )
            : AspectRatio(
                aspectRatio: 16 / 9,
                child: Center(child: CircularProgressIndicator()),
              ),

          // Video controls
          VideoProgressIndicator(
            _controller,
            allowScrubbing: true,
            padding: EdgeInsets.all(16),
          ),

          // Play/pause button
          IconButton(
            icon: Icon(
              _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
            ),
            onPressed: () {
              setState(() {
                if (_controller.value.isPlaying) {
                  _controller.pause();
                } else {
                  _controller.play();
                }
              });
            },
          ),

          // Video details
          Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'File Details',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
                ),
                SizedBox(height: 8),
                Text('Name: ${widget.video.name}'),
                Text('Size: ${(widget.video.size / (1024 * 1024)).toStringAsFixed(2)} MB'),
                Text('Duration: ${_formatDuration(widget.video.duration)}'),
                Text('Path: ${widget.video.path}'),
                Text('MIME Type: ${widget.video.mimeType}'),
              ],
            ),
          ),
        ],
      ),
    );
  }

  String _formatDuration(int milliseconds) {
    final seconds = (milliseconds / 1000).floor();
    final minutes = (seconds / 60).floor();
    final hours = (minutes / 60).floor();

    final remainingMinutes = minutes % 60;
    final remainingSeconds = seconds % 60;

    if (hours > 0) {
      return '$hours:${remainingMinutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
    } else {
      return '$minutes:${remainingSeconds.toString().padLeft(2, '0')}';
    }
  }
}

Playing Audio Files #

Similarly, you can use the path or URI of audio files to play them:

import 'package:audioplayers/audioplayers.dart';

class AudioPlayerWidget extends StatefulWidget {
  final AudioFile audio;

  const AudioPlayerWidget({Key? key, required this.audio}) : super(key: key);

  @override
  _AudioPlayerWidgetState createState() => _AudioPlayerWidgetState();
}

class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
  final AudioPlayer _audioPlayer = AudioPlayer();
  bool _isPlaying = false;
  Duration _duration = Duration.zero;
  Duration _position = Duration.zero;

  @override
  void initState() {
    super.initState();

    // Listen to player state changes
    _audioPlayer.onPlayerStateChanged.listen((state) {
      setState(() {
        _isPlaying = state == PlayerState.playing;
      });
    });

    // Listen to duration changes
    _audioPlayer.onDurationChanged.listen((newDuration) {
      setState(() {
        _duration = newDuration;
      });
    });

    // Listen to position changes
    _audioPlayer.onPositionChanged.listen((newPosition) {
      setState(() {
        _position = newPosition;
      });
    });

    // Set the source to the audio file path
    _audioPlayer.setSource(DeviceFileSource(widget.audio.path));
  }

  @override
  void dispose() {
    _audioPlayer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(16),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Audio info
            Row(
              children: [
                CircleAvatar(
                  backgroundColor: Colors.blue.shade200,
                  child: Icon(Icons.music_note),
                ),
                SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        widget.audio.name,
                        style: TextStyle(fontWeight: FontWeight.bold),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                      Text(
                        '${widget.audio.artist} • ${widget.audio.album}',
                        style: TextStyle(fontSize: 12, color: Colors.grey.shade700),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ],
                  ),
                ),
              ],
            ),

            SizedBox(height: 16),

            // Progress bar
            Slider(
              min: 0,
              max: _duration.inSeconds.toDouble(),
              value: _position.inSeconds.toDouble(),
              onChanged: (value) {
                final position = Duration(seconds: value.toInt());
                _audioPlayer.seek(position);
              },
            ),

            // Time indicators
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(_formatDuration(_position)),
                Text(_formatDuration(_duration)),
              ],
            ),

            SizedBox(height: 16),

            // Controls
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  icon: Icon(Icons.replay_10),
                  onPressed: () {
                    _audioPlayer.seek(Duration(seconds: _position.inSeconds - 10));
                  },
                ),
                IconButton(
                  iconSize: 48,
                  icon: Icon(_isPlaying ? Icons.pause_circle_filled : Icons.play_circle_filled),
                  onPressed: () {
                    if (_isPlaying) {
                      _audioPlayer.pause();
                    } else {
                      _audioPlayer.resume();
                    }
                  },
                ),
                IconButton(
                  icon: Icon(Icons.forward_10),
                  onPressed: () {
                    _audioPlayer.seek(Duration(seconds: _position.inSeconds + 10));
                  },
                ),
              ],
            ),

            SizedBox(height: 16),

            // File details
            Text('Path: ${widget.audio.path}'),
            Text('Size: ${(widget.audio.size / (1024 * 1024)).toStringAsFixed(2)} MB'),
          ],
        ),
      ),
    );
  }

  String _formatDuration(Duration duration) {
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final hours = twoDigits(duration.inHours);
    final minutes = twoDigits(duration.inMinutes.remainder(60));
    final seconds = twoDigits(duration.inSeconds.remainder(60));

    return [
      if (duration.inHours > 0) hours,
      minutes,
      seconds,
    ].join(':');
  }
}

Troubleshooting #

If you're having trouble finding videos or audio files:

  1. Make sure you've granted the necessary permissions
  2. Try using the getVideosByMimeType method with specific formats
  3. Check the logs for any error messages
  4. Verify that the files exist on the device and are valid media files
1
likes
160
points
14
downloads

Publisher

verified publisherswanflutterdev.com

Weekly Downloads

A Flutter plugin for accessing media files (videos and audio) on the device with support for various formats and thumbnails.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on device_media_finder

Packages that implement device_media_finder