Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ group 'me.anharu.video_editor'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.5.10'
repositories {
google()
jcenter()
Expand Down Expand Up @@ -41,5 +41,5 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.github.MasayukiSuda:Mp4Composer-android:v0.3.9'
implementation 'com.github.MasayukiSuda:Mp4Composer-android:v0.4.1'
}
43 changes: 43 additions & 0 deletions android/src/main/kotlin/me/anharu/video_editor/SpeedChanger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package me.anharu.video_editor


import android.app.Activity
import com.daasuu.mp4compose.composer.Mp4Composer
import io.flutter.plugin.common.MethodChannel.Result

class SpeedChanger(inputVideo: String, outputVideo: String, val result: Result, val activity: Activity) {
var composer: Mp4Composer = Mp4Composer(inputVideo, outputVideo)

fun speed(speed:Float) {
composer.timeScale(10F)
.listener(object : Mp4Composer.Listener {
override fun onProgress(progress: Double) {

}

override fun onCurrentWrittenVideoTime(timeUs: Long) {
TODO("Not yet implemented")
}

override fun onCompleted() {
activity.runOnUiThread(Runnable {
result.success(null)
})
}

override fun onCanceled() {
activity.runOnUiThread(Runnable {
result.error("user_cancelled", "Cancelled by user", null)
})
}

override fun onFailed(exception: Exception?) {
exception?.printStackTrace()
activity.runOnUiThread(Runnable {
result.error("video_trim_failed", exception?.localizedMessage, exception?.stackTrace)
})
}

}).start();
}
}
92 changes: 61 additions & 31 deletions android/src/main/kotlin/me/anharu/video_editor/VideoEditorPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
package me.anharu.video_editor

import android.Manifest
import android.app.Application
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Environment
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.daasuu.mp4compose.composer.Mp4Composer
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import me.anharu.video_editor.VideoGeneratorService
import java.io.File
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.Registrar


/** VideoEditorPlugin */
Expand Down Expand Up @@ -57,30 +51,66 @@ public class VideoEditorPlugin : FlutterPlugin, MethodCallHandler, PluginRegistr
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else if (call.method == "writeVideofile") {

var getActivity = activity ?: return
checkPermission(getActivity)

val srcFilePath: String = call.argument("srcFilePath") ?: run {
result.error("src_file_path_not_found", "the src file path is not found.", null)
return
when (call.method) {
"getPlatformVersion" -> {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
}
"writeVideofile" -> {

val getActivity = activity ?: return
checkPermission(getActivity)

val srcFilePath: String = call.argument("srcFilePath") ?: run {
result.error("src_file_path_not_found", "the src file path is not found.", null)
return
}
val destFilePath: String = call.argument("destFilePath") ?: run {
result.error("dest_file_path_not_found", "the dest file path is not found.", null)
return
}
val processing: HashMap<String, HashMap<String, Any>> = call.argument("processing")
?: run {
result.error("processing_data_not_found", "the processing is not found.", null)
return
}

val startTime: Long = call.argument<Int>("startTime")?.toLong() ?: 0
val endTime: Long = call.argument<Int>("endTime")?.toLong() ?: -1
val generator = VideoGeneratorService(Mp4Composer(srcFilePath, destFilePath))
generator.writeVideofile(processing, result, getActivity, startTime = startTime, endTime = endTime)
}
"trim_video" -> {
val getActivity = activity ?: return
val srcFilePath: String = call.argument("srcFilePath") ?: run {
result.error("src_file_path_not_found", "the src file path is not found.", null)
return
}
val destFilePath: String = call.argument("destFilePath") ?: run {
result.error("dest_file_path_not_found", "the dest file path is not found.", null)
return
}
val startTime: Long = call.argument<Int>("startTime")?.toLong() ?: 0
val endTime: Long = call.argument<Int>("endTime")?.toLong() ?: -1
VideoTrimmer(srcFilePath, destFilePath, result, getActivity).trimVideo(startTime, endTime)
}
"speed_change" -> {
val getActivity = activity ?: return
val srcFilePath: String = call.argument("srcFilePath") ?: run {
result.error("src_file_path_not_found", "the src file path is not found.", null)
return
}
val destFilePath: String = call.argument("destFilePath") ?: run {
result.error("dest_file_path_not_found", "the dest file path is not found.", null)
return
}
val speed: Float = call.argument<Double>("speed")?.toFloat() ?: 0F
print("===>$speed");
SpeedChanger(srcFilePath, destFilePath, result, getActivity).speed(speed)
}
val destFilePath: String = call.argument("destFilePath") ?: run {
result.error("dest_file_path_not_found", "the dest file path is not found.", null)
return
else -> {
print("===>sxxxxxxxx");
result.notImplemented()
}
val processing: HashMap<String, HashMap<String, Any>> = call.argument("processing")
?: run {
result.error("processing_data_not_found", "the processing is not found.", null)
return
}
val generator = VideoGeneratorService(Mp4Composer(srcFilePath, destFilePath))
generator.writeVideofile(processing, result, getActivity)
} else {
result.notImplemented()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import me.anharu.video_editor.filter.GlTextOverlayFilter


interface VideoGeneratorServiceInterface {
fun writeVideofile(processing: HashMap<String,HashMap<String,Any>>, result: Result, activity: Activity);
fun writeVideofile(processing: HashMap<String,HashMap<String,Any>>, result: Result, activity: Activity, startTime: Long = 0, endTime: Long = -1);
}

class VideoGeneratorService(
private val composer: Mp4Composer
) : VideoGeneratorServiceInterface {
override fun writeVideofile(processing: HashMap<String,HashMap<String,Any>>, result: Result, activity: Activity ) {
override fun writeVideofile(processing: HashMap<String,HashMap<String,Any>>, result: Result, activity: Activity,startTime: Long, endTime: Long) {
val filters: MutableList<GlFilter> = mutableListOf()
try {
processing.forEach { (k, v) ->
Expand All @@ -50,10 +50,14 @@ class VideoGeneratorService(
})
}
composer.filter(GlFilterGroup( filters))
.trim(startTime, endTime)
.listener(object : Mp4Composer.Listener {
override fun onProgress(progress: Double) {
println("onProgress = " + progress)
}
override fun onCurrentWrittenVideoTime(timeUs: Long) {
TODO("Not yet implemented")
}

override fun onCompleted() {
activity.runOnUiThread(Runnable {
Expand All @@ -68,7 +72,7 @@ class VideoGeneratorService(
}

override fun onFailed(exception: Exception) {
println(exception);
exception.printStackTrace()
activity.runOnUiThread(Runnable {
result.error("video_processing_failed", "video processing is failed.", null)
})
Expand Down
42 changes: 42 additions & 0 deletions android/src/main/kotlin/me/anharu/video_editor/VideoTrimmer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package me.anharu.video_editor

import android.app.Activity
import com.daasuu.mp4compose.composer.Mp4Composer
import io.flutter.plugin.common.MethodChannel.Result

class VideoTrimmer(inputVideo: String, outputVideo: String, val result: Result, val activity: Activity) {
var composer: Mp4Composer = Mp4Composer(inputVideo, outputVideo)

fun trimVideo(startTime: Long = 0, endTime: Long = -1) {
composer.trim(startTime, endTime)
.listener(object : Mp4Composer.Listener {
override fun onProgress(progress: Double) {

}

override fun onCurrentWrittenVideoTime(timeUs: Long) {

}

override fun onCompleted() {
activity.runOnUiThread(Runnable {
result.success(null)
})
}

override fun onCanceled() {
activity.runOnUiThread(Runnable {
result.error("user_cancelled", "Cancelled by user", null)
})
}

override fun onFailed(exception: Exception?) {
exception?.printStackTrace()
activity.runOnUiThread(Runnable {
result.error("video_trim_failed", exception?.localizedMessage, exception?.stackTrace)
})
}

}).start();
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package me.anharu.video_editor.filter

import com.daasuu.mp4compose.filter.GlOverlayFilter;
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import com.daasuu.mp4compose.filter.GlOverlayFilter
import me.anharu.video_editor.ImageOverlay

class GlImageOverlayFilter(imageOverlay: ImageOverlay) : GlOverlayFilter() {
private val imageOverlay: ImageOverlay = imageOverlay;

protected override fun drawCanvas(canvas: Canvas) {
canvas.drawBitmap(BitmapFactory.decodeByteArray(imageOverlay.bitmap, 0, imageOverlay.bitmap.size),imageOverlay.x.toFloat(),imageOverlay.y.toFloat(),null);
canvas.drawBitmap(BitmapFactory.decodeByteArray(imageOverlay.bitmap, 0, imageOverlay.bitmap.size), imageOverlay.x.toFloat(), imageOverlay.y.toFloat(), null);
}
}
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.4.32'
repositories {
google()
jcenter()
Expand Down
79 changes: 15 additions & 64 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,81 +10,32 @@ project 'Runner', {
'Release' => :release,
}

def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
generated_key_values
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
use_frameworks!
use_modular_headers!

# Flutter Pod

copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.

generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];

unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end

# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'

# Plugin Pods

# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
install! 'cocoapods', :disable_input_output_paths => true

post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
flutter_additional_ios_build_settings(target)
end
end
Loading