Skip to content
This repository has been archived by the owner on Oct 14, 2021. It is now read-only.

Commit

Permalink
implement mobile ffmpeg v4.4 features, fixes #157
Browse files Browse the repository at this point in the history
  • Loading branch information
tanersener committed Aug 22, 2020
1 parent 2391679 commit 8506c17
Show file tree
Hide file tree
Showing 14 changed files with 470 additions and 522 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## 0.2.11
## 0.3.0
- LogCallback and StatisticsCallback functions updated with executionId
- Updates getMediaInformation implementation
- Adds setEnvironmentVariable API method
- Depends on mobile-ffmpeg v4.4
- Allows modifying mobile-ffmpeg version for android
- Fixes issue #115, #120 and #178
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
import com.arthenica.mobileffmpeg.MediaInformation;
import com.arthenica.mobileffmpeg.Statistics;
import com.arthenica.mobileffmpeg.StatisticsCallback;
import com.arthenica.mobileffmpeg.StreamInformation;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
Expand All @@ -63,9 +65,12 @@ public class FlutterFFmpegPlugin implements MethodCallHandler, EventChannel.Stre
public static final String KEY_PIPE = "pipe";

public static final String KEY_LAST_COMMAND_OUTPUT = "lastCommandOutput";
public static final String KEY_LOG_TEXT = "log";

public static final String KEY_LOG_EXECUTION_ID = "executionId";
public static final String KEY_LOG_LEVEL = "level";
public static final String KEY_LOG_TEXT = "log";

public static final String KEY_STAT_EXECUTION_ID = "executionId";
public static final String KEY_STAT_TIME = "time";
public static final String KEY_STAT_SIZE = "size";
public static final String KEY_STAT_BITRATE = "bitrate";
Expand Down Expand Up @@ -252,6 +257,12 @@ public void apply(final Statistics statistics) {
final String pipe = Config.registerNewFFmpegPipe(getActiveContext());
flutterFFmpegResultHandler.success(result, toStringMap(KEY_PIPE, pipe));

} else if (call.method.equals("setEnvironmentVariable")) {
String variableName = call.argument("variableName");
String variableValue = call.argument("variableValue");

Config.setEnvironmentVariable(variableName, variableValue);

} else {
flutterFFmpegResultHandler.notImplemented(result);
}
Expand All @@ -271,6 +282,7 @@ protected void emitLogMessage(final LogMessage logMessage) {
final HashMap<String, Object> logWrapperMap = new HashMap<>();
final HashMap<String, Object> logMap = new HashMap<>();

logMap.put(KEY_LOG_EXECUTION_ID, logMessage.getExecutionId());
logMap.put(KEY_LOG_LEVEL, levelToInt(logMessage.getLevel()));
logMap.put(KEY_LOG_TEXT, logMessage.getText());

Expand Down Expand Up @@ -305,6 +317,8 @@ public static Map<String, Object> toMap(final Statistics statistics) {
final HashMap<String, Object> statisticsMap = new HashMap<>();

if (statistics != null) {
statisticsMap.put(KEY_STAT_EXECUTION_ID, statistics.getExecutionId());

statisticsMap.put(KEY_STAT_TIME, statistics.getTime());
statisticsMap.put(KEY_STAT_SIZE, (statistics.getSize() < Integer.MAX_VALUE) ? (int) statistics.getSize() : (int) (statistics.getSize() % Integer.MAX_VALUE));
statisticsMap.put(KEY_STAT_BITRATE, statistics.getBitrate());
Expand All @@ -318,138 +332,55 @@ public static Map<String, Object> toMap(final Statistics statistics) {
return statisticsMap;
}

public static HashMap<String, Object> toMediaInformationMap(final MediaInformation mediaInformation) {
final HashMap<String, Object> map = new HashMap<>();
public static Map<String, Object> toMediaInformationMap(final MediaInformation mediaInformation) {
Map<String, Object> map = new HashMap<>();

if (mediaInformation != null) {
if (mediaInformation.getFormat() != null) {
map.put("format", mediaInformation.getFormat());
}
if (mediaInformation.getPath() != null) {
map.put("path", mediaInformation.getPath());
}
if (mediaInformation.getStartTime() != null) {
map.put("startTime", mediaInformation.getStartTime().intValue());
}
if (mediaInformation.getDuration() != null) {
map.put("duration", mediaInformation.getDuration().intValue());
}
if (mediaInformation.getBitrate() != null) {
map.put("bitrate", mediaInformation.getBitrate().intValue());
}
if (mediaInformation.getRawInformation() != null) {
map.put("rawInformation", mediaInformation.getRawInformation());
}

final Set<Map.Entry<String, String>> metadata = mediaInformation.getMetadataEntries();
if ((metadata != null) && (metadata.size() > 0)) {
final HashMap<String, String> metadataMap = new HashMap<>();

for (Map.Entry<String, String> entry : metadata) {
metadataMap.put(entry.getKey(), entry.getValue());
}

map.put("metadata", metadataMap);
}

final List<StreamInformation> streams = mediaInformation.getStreams();
if ((streams != null) && (streams.size() > 0)) {
final ArrayList<Map<String, Object>> array = new ArrayList<>();

for (StreamInformation streamInformation : streams) {
array.add(toStreamInformationMap(streamInformation));
if (mediaInformation.getAllProperties() != null) {
JSONObject allProperties = mediaInformation.getAllProperties();
if (allProperties != null) {
map = toMap(allProperties);
}

map.put("streams", array);
}
}

return map;
}

public static Map<String, Object> toStreamInformationMap(final StreamInformation streamInformation) {
public static Map<String, Object> toMap(final JSONObject jsonObject) {
final HashMap<String, Object> map = new HashMap<>();

if (streamInformation != null) {
if (streamInformation.getIndex() != null) {
map.put("index", streamInformation.getIndex().intValue());
}
if (streamInformation.getType() != null) {
map.put("type", streamInformation.getType());
}
if (streamInformation.getCodec() != null) {
map.put("codec", streamInformation.getCodec());
}
if (streamInformation.getFullCodec() != null) {
map.put("fullCodec", streamInformation.getFullCodec());
}
if (streamInformation.getFormat() != null) {
map.put("format", streamInformation.getFormat());
}
if (streamInformation.getFullFormat() != null) {
map.put("fullFormat", streamInformation.getFullFormat());
}
if (streamInformation.getWidth() != null) {
map.put("width", streamInformation.getWidth().intValue());
}
if (streamInformation.getHeight() != null) {
map.put("height", streamInformation.getHeight().intValue());
}
if (streamInformation.getBitrate() != null) {
map.put("bitrate", streamInformation.getBitrate().intValue());
}
if (streamInformation.getSampleRate() != null) {
map.put("sampleRate", streamInformation.getSampleRate().intValue());
}
if (streamInformation.getSampleFormat() != null) {
map.put("sampleFormat", streamInformation.getSampleFormat());
}
if (streamInformation.getChannelLayout() != null) {
map.put("channelLayout", streamInformation.getChannelLayout());
}
if (streamInformation.getSampleAspectRatio() != null) {
map.put("sampleAspectRatio", streamInformation.getSampleAspectRatio());
}
if (streamInformation.getDisplayAspectRatio() != null) {
map.put("displayAspectRatio", streamInformation.getDisplayAspectRatio());
}
if (streamInformation.getAverageFrameRate() != null) {
map.put("averageFrameRate", streamInformation.getAverageFrameRate());
}
if (streamInformation.getRealFrameRate() != null) {
map.put("realFrameRate", streamInformation.getRealFrameRate());
}
if (streamInformation.getTimeBase() != null) {
map.put("timeBase", streamInformation.getTimeBase());
}
if (streamInformation.getCodecTimeBase() != null) {
map.put("codecTimeBase", streamInformation.getCodecTimeBase());
}

final Set<Map.Entry<String, String>> metadata = streamInformation.getMetadataEntries();
if ((metadata != null) && (metadata.size() > 0)) {
final HashMap<String, String> metadataMap = new HashMap<>();

for (Map.Entry<String, String> entry : metadata) {
metadataMap.put(entry.getKey(), entry.getValue());
if (jsonObject != null) {
Iterator<String> keys = jsonObject.keys();
while (keys.hasNext()) {
String key = keys.next();
Object value = jsonObject.opt(key);
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
} else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}

map.put("metadata", metadataMap);
map.put(key, value);
}
}

final Set<Map.Entry<String, String>> sidedata = streamInformation.getSidedataEntries();
if ((sidedata != null) && (sidedata.size() > 0)) {
final HashMap<String, String> sidedataMap = new HashMap<>();
return map;
}

for (Map.Entry<String, String> entry : sidedata) {
sidedataMap.put(entry.getKey(), entry.getValue());
}
public static List<Object> toList(final JSONArray array) {
final List<Object> list = new ArrayList<>();

map.put("sidedata", sidedataMap);
for (int i = 0; i < array.length(); i++) {
Object value = array.opt(i);
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
} else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
list.add(value);
}

return map;
return list;
}

}
1 change: 1 addition & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ unlinked_spec.ds
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
**/ios/Flutter/.last_build_id

# Exceptions to above rules.
!**/ios/**/default.mode1v3
Expand Down
102 changes: 40 additions & 62 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '9.3'
platform :ios, '11.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand All @@ -10,83 +10,61 @@ 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

target 'Runner' do
# Flutter Pod
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

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.
flutter_ios_podfile_setup

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'];
def flutter_install_ios_plugin_pods(ios_application_path = nil)
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
ios_application_path ||= File.dirname(defined_in_file.realpath) if self.respond_to?(:defined_in_file)
raise 'Could not find iOS application path' unless ios_application_path

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
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.

# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
symlink_dir = File.expand_path('.symlinks', ios_application_path)
system('rm', '-rf', symlink_dir) # Avoid the complication of dependencies like FileUtils.

# Plugin Pods
symlink_plugins_dir = File.expand_path('plugins', symlink_dir)
system('mkdir', '-p', symlink_plugins_dir)

# 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)
if name == 'flutter_ffmpeg'
pod name+'/full', :path => File.join(symlink, 'ios')
else
pod name, :path => File.join(symlink, 'ios')
plugins_file = File.join(ios_application_path, '..', '.flutter-plugins-dependencies')
plugin_pods = flutter_parse_plugins_file(plugins_file)
plugin_pods.each do |plugin_hash|
plugin_name = plugin_hash['name']
plugin_path = plugin_hash['path']
if (plugin_name && plugin_path)
symlink = File.join(symlink_plugins_dir, plugin_name)
File.symlink(plugin_path, symlink)

if plugin_name == 'flutter_ffmpeg'
pod 'flutter_ffmpeg/audio', :path => File.join('.symlinks', 'plugins', plugin_name, 'ios')
else
pod plugin_name, :path => File.join('.symlinks', 'plugins', plugin_name, 'ios')
end
end
end
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
target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

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

0 comments on commit 8506c17

Please sign in to comment.