diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..5cba72ab --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,34 @@ +name: Integration Test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + iOS-Test: + runs-on: macos-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v1 + - run: flutter emulators --launch apple_ios_simulator + - run: flutter drive --target=test_driver/app.dart + working-directory: example + + Android-Test: + # Linux machine doesn't support running Android emulator due to lack of nested virtualization + runs-on: macos-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v1 + - run: $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-27;google_apis_playstore;x86" + - run: flutter emulators --create + - run: flutter emulators --launch flutter_emulator + - run: adb wait-for-device + - run: flutter drive --target=test_driver/app.dart + working-directory: example diff --git a/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt b/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt index b26fd5ce..70f3839c 100644 --- a/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt +++ b/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt @@ -2,14 +2,12 @@ package com.example.video_compress import android.app.Activity import android.content.Context -import android.content.Intent import android.net.Uri import com.otaliastudios.transcoder.Transcoder import com.otaliastudios.transcoder.TranscoderListener -import com.otaliastudios.transcoder.strategy.DefaultVideoStrategies import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy +import com.otaliastudios.transcoder.strategy.PassThroughTrackStrategy import com.otaliastudios.transcoder.strategy.TrackStrategy -import com.otaliastudios.transcoder.strategy.size.* import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler @@ -94,6 +92,9 @@ class VideoCompressPlugin private constructor(private val activity: Activity, pr Transcoder.into(destPath!!) .addDataSource(context, Uri.parse(path)) .setVideoTrackStrategy(strategy) + // Don't transcode audio to workaround + // https://github.com/natario1/Transcoder/issues/102 + .setAudioTrackStrategy(PassThroughTrackStrategy()) .setListener(object : TranscoderListener { override fun onTranscodeProgress(progress: Double) { channel.invokeMethod("updateProgress", progress * 100.00) diff --git a/example/assets/samples/sample.mp4 b/example/assets/samples/sample.mp4 new file mode 100644 index 00000000..dcf6be52 Binary files /dev/null and b/example/assets/samples/sample.mp4 differ diff --git a/example/lib/main.dart b/example/lib/main.dart index c1d3e2c0..207eb123 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,9 @@ import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:video_compress/video_compress.dart'; void main() { @@ -49,7 +52,34 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - String _counter = "video"; + String _status = "init"; + String _outputPath = ""; + String _outputFileSize = ""; + + Future updateOutputState() async { + final outputFile = File(_outputPath); + if (await outputFile.exists()) { + _outputFileSize = (await outputFile.length()).toString(); + } else { + _outputFileSize = "-1"; + } + } + + Future compressVideo(String path) async { + _status = 'compressing'; + setState(() {}); + + final info = await VideoCompress.compressVideo( + path, + quality: VideoQuality.MediumQuality, + deleteOrigin: false, + ); + + _status = 'compressed'; + _outputPath = info.path; + await updateOutputState(); + setState(() {}); + } @override Widget build(BuildContext context) { @@ -86,30 +116,67 @@ class _MyHomePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'You have pushed the button this many times:', + 'Status:', + ), + Text( + '$_status', + style: Theme.of(context).textTheme.bodyText1, + key: Key('status'), + ), + Text( + 'Output Path:', + ), + Text( + '$_outputPath', + style: Theme.of(context).textTheme.bodyText1, + key: Key('output_path'), ), Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, + 'Output File Size:', + ), + Text( + '$_outputFileSize', + style: Theme.of(context).textTheme.bodyText1, + key: Key('output_file_size'), + ), + RaisedButton( + key: Key('use_sample_video'), + onPressed: () async { + final data = await rootBundle.load( + 'assets/samples/sample.mp4', + ); + final bytes = data.buffer.asUint8List(); + final dir = await getTemporaryDirectory(); + final file = + await File('${dir.path}/sample.mp4').writeAsBytes(bytes); + + await this.compressVideo(file.path); + }, + child: const Text('Use sample video'), + ), + RaisedButton( + key: Key('select_video'), + onPressed: () async { + File file = + await ImagePicker.pickVideo(source: ImageSource.gallery); + await this.compressVideo(file.path); + }, + child: const Text('Select video'), + ), + RaisedButton( + key: Key('clear_cache'), + onPressed: () async { + await VideoCompress.deleteAllCache(); + + _status = 'cache cleared'; + await this.updateOutputState(); + setState(() {}); + }, + child: const Text('Clear cache'), ), ], ), ), - floatingActionButton: FloatingActionButton( - onPressed: () async { - File file = await ImagePicker.pickVideo(source: ImageSource.gallery); - final info = await VideoCompress.compressVideo( - file.path, - quality: VideoQuality.MediumQuality, - deleteOrigin: false, - ); - - _counter = info.path; - setState(() {}); - }, - tooltip: 'Increment', - child: Icon(Icons.add), - ), ); } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ad4cd9a9..177f7414 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: cupertino_icons: ^0.1.2 image_picker: ^0.6.0+10 video_player: ^0.10.1+3 + path_provider: dev_dependencies: flutter_test: @@ -22,6 +23,11 @@ dev_dependencies: video_compress: path: ../ + flutter_driver: + sdk: flutter + + test: any + # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec @@ -34,9 +40,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/samples/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/example/test_driver/app.dart b/example/test_driver/app.dart new file mode 100644 index 00000000..17342ac7 --- /dev/null +++ b/example/test_driver/app.dart @@ -0,0 +1,7 @@ +import 'package:flutter_driver/driver_extension.dart'; +import 'package:video_compress_example/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/example/test_driver/app_test.dart b/example/test_driver/app_test.dart new file mode 100644 index 00000000..93c5706e --- /dev/null +++ b/example/test_driver/app_test.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart'; + +void main() { + final useSampleVideoBtnFinder = find.byValueKey('use_sample_video'); + final clearCacheBtnFinder = find.byValueKey('clear_cache'); + final statusFinder = find.byValueKey('status'); + final outputFileSizeFinder = find.byValueKey('output_file_size'); + + FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + tearDownAll(() async { + if (driver != null) { + driver.close(); + } + }); + + test('compress video and clear cache', () async { + expect(await driver.getText(statusFinder), 'init'); + + // Initialize compression + await driver.tap(useSampleVideoBtnFinder); + + // Wait for compression to finish + await driver.waitFor(find.text('compressed')); + + // Output file should not be empty and should be smaller than original size + final originalSize = await File('assets/samples/sample.mp4').length(); + final outputSize = int.parse(await driver.getText(outputFileSizeFinder)); + expect(outputSize, greaterThan(0)); + expect(outputSize, lessThan(originalSize)); + + // Clear the cache + await driver.tap(clearCacheBtnFinder); + + // After clear cache, output file should be deleted + await driver.waitFor(find.text('cache cleared')); + expect(await driver.getText(outputFileSizeFinder), "-1"); + }); +}