video_trimmer_2 0.1.3 copy "video_trimmer_2: ^0.1.3" to clipboard
video_trimmer_2: ^0.1.3 copied to clipboard

A Flutter plugin to trim videos on Android and iOS. An alternate to ffmpeg to trim and export videos.

example/lib/main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:video_trimmer_2/video_trimmer_2.dart';
import 'package:video_player/video_player.dart';
import 'package:saver_gallery/saver_gallery.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Video Trimmer Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final ImagePicker _picker = ImagePicker();
  File? _videoFile;
  File? _trimmedVideoFile;
  VideoPlayerController? _controller;
  bool _isProcessing = false;
  bool _isSaving = false;
  final Trimmer _trimmer = Trimmer();

  // Values for trim start and end points
  double _startValue = 0.0;
  double _endValue = 0.0;
  double _videoDuration = 0.0;

  @override
  void initState() {
    super.initState();
    _requestPermissions();
    _getPlatformVersion();
  }

  Future<void> _requestPermissions() async {
    if (Platform.isAndroid) {
      await [
        Permission.storage,
        Permission.photos,
        Permission.videos,
        Permission.camera,
        Permission.microphone,
      ].request();
    }
  }

  Future<void> _getPlatformVersion() async {
    try {
      final version = await _trimmer.getPlatformVersion();
      print('Running on platform: $version');
    } catch (e) {
      print('Error getting platform version: $e');
    }
  }

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

  Future<void> _pickVideo() async {
    try {
      final XFile? pickedVideo =
          await _picker.pickVideo(source: ImageSource.gallery);

      if (pickedVideo != null) {
        final File videoFile = File(pickedVideo.path);

        setState(() {
          _videoFile = videoFile;
          _trimmedVideoFile = null;
        });

        await _initializeVideoPlayer();
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error picking video: $e')),
      );
    }
  }

  // IMPROVED: Add camera recording option
  Future<void> _recordVideo() async {
    try {
      final XFile? recordedVideo =
          await _picker.pickVideo(source: ImageSource.camera);

      if (recordedVideo != null) {
        final File videoFile = File(recordedVideo.path);

        setState(() {
          _videoFile = videoFile;
          _trimmedVideoFile = null;
        });

        await _initializeVideoPlayer();
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error recording video: $e')),
      );
    }
  }

  Future<void> _initializeVideoPlayer() async {
    if (_videoFile != null) {
      try {
        // Dispose previous controller
        await _controller?.dispose();

        _controller = VideoPlayerController.file(_videoFile!);
        await _controller!.initialize();

        setState(() {
          _videoDuration =
              _controller!.value.duration.inMilliseconds.toDouble();
          _startValue = 0;
          _endValue = _videoDuration;
        });

        // Auto-play the video
        await _controller!.play();
      } catch (e) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Error initializing video: $e')),
        );
      }
    }
  }

  Future<void> _trimVideo() async {
    if (_videoFile == null) return;

    setState(() {
      _isProcessing = true;
    });

    try {
      final File trimmedFile = await _trimmer.trimVideo(
        file: _videoFile!,
        startMs: _startValue.toInt(),
        endMs: _endValue.toInt(),
      );

      setState(() {
        _trimmedVideoFile = trimmedFile;
        _isProcessing = false;
      });

      // Play the trimmed video
      await _controller?.dispose();
      _controller = VideoPlayerController.file(_trimmedVideoFile!);
      await _controller!.initialize();
      await _controller!.play();
      setState(() {});

      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Video trimmed successfully')),
      );
    } catch (e) {
      setState(() {
        _isProcessing = false;
      });

      print('Error trimming video: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error trimming video: $e')),
      );
    }
  }

  Future<void> _saveVideoToGallery() async {
    if (_trimmedVideoFile == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('No trimmed video to save')),
      );
      return;
    }

    setState(() {
      _isSaving = true;
    });

    try {
      final fileName =
          'trimmed_video_${DateTime.now().millisecondsSinceEpoch}.mp4';

      final result = await SaverGallery.saveFile(
        filePath: _trimmedVideoFile!.path,
        fileName: fileName,
        androidRelativePath: "Movies",
        skipIfExists: false,
      );

      setState(() {
        _isSaving = false;
      });

      if (result.isSuccess) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Video saved to gallery successfully')),
        );
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
              content: Text(
                  'Failed to save video to gallery: ${result.errorMessage}')),
        );
      }
    } catch (e) {
      setState(() {
        _isSaving = false;
      });

      print('Error saving video to gallery: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error saving video to gallery: $e')),
      );
    }
  }

  // IMPROVED: Better video display widget that handles orientation
  Widget _buildVideoPlayer() {
    if (_controller == null || !_controller!.value.isInitialized) {
      return const SizedBox(
        height: 200,
        child: Center(
          child: Text('No video selected'),
        ),
      );
    }

    return Container(
      constraints: BoxConstraints(
        maxHeight: MediaQuery.of(context).size.height * 0.4,
      ),
      child: AspectRatio(
        aspectRatio: _controller!.value.aspectRatio,
        child: VideoPlayer(_controller!),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Video Trimmer Demo'),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            // IMPROVED: Better video player display
            _buildVideoPlayer(),

            // Video player controls
            if (_controller != null && _controller!.value.isInitialized)
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    IconButton(
                      icon: Icon(
                        _controller!.value.isPlaying
                            ? Icons.pause
                            : Icons.play_arrow,
                      ),
                      onPressed: () {
                        setState(() {
                          _controller!.value.isPlaying
                              ? _controller!.pause()
                              : _controller!.play();
                        });
                      },
                    ),
                    Text(
                      '${_formatDuration(_controller!.value.position)} / ${_formatDuration(_controller!.value.duration)}',
                      style: const TextStyle(fontSize: 12),
                    ),
                  ],
                ),
              ),

            // Trim controls
            if (_videoFile != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  children: [
                    const Text(
                      'Select trim range:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 8),
                    // Slider for trim start and end
                    RangeSlider(
                      values: RangeValues(_startValue, _endValue),
                      min: 0,
                      max: _videoDuration,
                      divisions: 100,
                      labels: RangeLabels(
                        '${(_startValue / 1000).toStringAsFixed(1)}s',
                        '${(_endValue / 1000).toStringAsFixed(1)}s',
                      ),
                      onChanged: (RangeValues values) {
                        setState(() {
                          _startValue = values.start;
                          _endValue = values.end;
                        });
                      },
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Text(
                            'Start: ${(_startValue / 1000).toStringAsFixed(1)}s'),
                        Text('End: ${(_endValue / 1000).toStringAsFixed(1)}s'),
                      ],
                    ),
                    const SizedBox(height: 20),
                    ElevatedButton(
                      onPressed: _isProcessing ? null : _trimVideo,
                      child: _isProcessing
                          ? const Row(
                              mainAxisSize: MainAxisSize.min,
                              children: [
                                SizedBox(
                                  width: 16,
                                  height: 16,
                                  child: CircularProgressIndicator(
                                    strokeWidth: 2,
                                    color: Colors.white,
                                  ),
                                ),
                                SizedBox(width: 8),
                                Text('Processing...'),
                              ],
                            )
                          : const Text('Trim Video'),
                    ),
                  ],
                ),
              ),

            // Trimmed video info and save button
            if (_trimmedVideoFile != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Trimmed Video:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      _trimmedVideoFile!.path,
                      style: const TextStyle(fontSize: 12),
                    ),
                    const SizedBox(height: 16),
                    ElevatedButton.icon(
                      onPressed: _isSaving ? null : _saveVideoToGallery,
                      icon: const Icon(Icons.save_alt),
                      label: _isSaving
                          ? const Row(
                              mainAxisSize: MainAxisSize.min,
                              children: [
                                SizedBox(
                                  width: 16,
                                  height: 16,
                                  child: CircularProgressIndicator(
                                    strokeWidth: 2,
                                    color: Colors.white,
                                  ),
                                ),
                                SizedBox(width: 8),
                                Text('Saving...'),
                              ],
                            )
                          : const Text('Save to Gallery'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                      ),
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
      // IMPROVED: Added both camera and gallery options
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: _recordVideo,
            tooltip: 'Record Video',
            heroTag: "record",
            child: const Icon(Icons.videocam),
          ),
          const SizedBox(height: 16),
          FloatingActionButton(
            onPressed: _pickVideo,
            tooltip: 'Pick Video',
            heroTag: "pick",
            child: const Icon(Icons.video_library),
          ),
        ],
      ),
    );
  }

  // Helper function to format duration
  String _formatDuration(Duration duration) {
    String twoDigits(int n) => n.toString().padLeft(2, "0");
    String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
    String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
    return "$twoDigitMinutes:$twoDigitSeconds";
  }
}
3
likes
150
points
267
downloads

Publisher

verified publishermosthandsomedeveloper.com

Weekly Downloads

A Flutter plugin to trim videos on Android and iOS. An alternate to ffmpeg to trim and export videos.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, path, path_provider, plugin_platform_interface

More

Packages that depend on video_trimmer_2

Packages that implement video_trimmer_2