You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter集成imgly视频编辑SDK,Android添加下载音频时崩溃

问题:Flutter Video Editor SDK Android端添加本地下载音频崩溃

在Flutter应用中使用imgly的video_editor_sdk(版本^3.0.0),iOS端运行正常,但Android端添加从远程URL下载的音频叠加视频时崩溃。环境配置:minSdkVersion 24,compileSdkVersion 33,ext.kotlin_version = '1.7.20'。


跳转编辑器页面代码

void onEditPress() async {
      var filePath = Get.arguments["videoPath"];

      final List<Future<String>> downloads = audioSources
          .map((e) => downloadAudioFile(
              "https://storage.googleapis.com/ps_users/imgly-background-music/$e.mp3",
              "$e.mp3"))
          .toList();
      final List<String> localPaths = await Future.wait(downloads);

      // Convert the sources to valid `AudioClip`s.
      final List<AudioClip> audioClips = audioSources
          .asMap()
          .entries
          .map((clip) => AudioClip(clip.value, localPaths[clip.key],
              title: clip.value.replaceAll('_', ' ').toCapitalize().toString()))
          .toList();

      // Create [AudioOptions] to customize the audio tool.
      final audioOptions = AudioOptions(
          categories: [AudioClipCategory("", "", items: audioClips)]);

      Map<String, dynamic>? serialization;
      // Load a serialization from the assets folder.
      final serializationString = sharedPreferencesRepository.getString('editChanges');
      if (serializationString != null) {
        serialization = jsonDecode(serializationString);
      }

      // Create [SerializationOptions] to configure the serialization feature.
      final serializationOptions = SerializationOptions(
        enabled: true, // Enable the serialization feature.
        // For this example, the serialization should be returned as an object to simply logging it in the console.
        exportType: SerializationExportType.object,
      );

      // Create [ExportOptions] to apply the [serializationOptions].
      final exportOptions = ExportOptions(serialization: serializationOptions);

      // Create [Configuration] instance.
      final configuration =
          Configuration(export: exportOptions, audio: audioOptions);

      // Go to editor screen
      final video = Video(filePath);

      Get.back();
      // Open the video editor and handle the export as well as any occurring errors.
      await VESDK.unlockWithLicense("assets/license/vesdk_license");
      final result = await VESDK.openEditor(video,
          configuration: configuration, serialization: serialization);
      if (result != null) {
        // The user exported a new video successfully and the newly generated video is located at `result.video`.
        var videoPath = result.video;
      } else {
        // The user tapped on the cancel button within the editor.
        return;
      }
    }
  }

远程文件下载函数

/// Downloads the file from the [url] to the local directory.
Future<String> downloadAudioFile(String url, String filename) async {
  final fileOutputDirectory = await getApplicationDocumentsDirectory();
  String fileLocation = "${fileOutputDirectory.path}/$filename";
  bool fileExist = _checkFileExistsSync(fileLocation);
  if (fileExist) {
    return fileLocation;
  } else {
    final client = HttpClient();
    final request = await client.getUrl(Uri.parse(url));
    final response = await request.close();
    final bytes = await consolidateHttpClientResponseBytes(response);
    final outputFile = File(fileLocation);
    await outputFile.writeAsBytes(bytes);
    return outputFile.path;
  }
}

错误日志

I/CCodec  (26189): state->set(FLUSHING)
I/CCodec  (26189): state->set(FLUSHED)
I/CCodec  (26189): state->set(RESUMING)
I/CCodecConfig(26189): query failed after returning 7 values (BAD_INDEX)
W/Codec2Client(26189): query -- param skipped: index = 1342179345.
W/Codec2Client(26189): query -- param skipped: index = 2415921170.
I/CCodec  (26189): state->set(RUNNING)
I/CCodecBufferChannel(26189): [c2.sec.mp3.decoder#558] 4 initial input buffers available
I/CCodec  (26189): state->set(FLUSHING)
I/CCodec  (26189): state->set(FLUSHED)
I/CCodec  (26189): state->set(RESUMING)
I/CCodecConfig(26189): query failed after returning 7 values (BAD_INDEX)
W/Codec2Client(26189): query -- param skipped: index = 1342179345.
W/Codec2Client(26189): query -- param skipped: index = 2415921170.
I/CCodec  (26189): state->set(RUNNING)
I/CCodecBufferChannel(26189): [c2.sec.mp3.decoder#558] 4 initial input buffers available
D/CCodecBuffers(26189): [c2.android.aac.decoder#447:1D-Input.Impl[N]] codec released a buffer owned by client (index 2)
I/CCodec  (26189): state->set(FLUSHING)
I/CCodec  (26189): state->set(FLUSHED)
I/CCodec  (26189): state->set(RESUMING)
I/CCodecConfig(26189): query failed after returning 7 values (BAD_INDEX)
W/Codec2Client(26189): query -- param skipped: index = 1342179345.
W/Codec2Client(26189): query -- param skipped: index = 2415921170.
I/CCodec  (26189): state->set(RUNNING)
I/CCodecBufferChannel(26189): [c2.sec.mp3.decoder#558] 4 initial input buffers available
D/NativeAudioDecoder(26189): dequeueOutputBuffer timed out! /data/user/0/com.proshort.enterprise/app_flutter/summer_walk.mp3
I/CCodec  (26189): state->set(FLUSHING)
I/CCodec  (26189): state->set(FLUSHED)
I/CCodec  (26189): state->set(RESUMING)
I/CCodecConfig(26189): query failed after returning 7 values (BAD_INDEX)
W/Codec2Client(26189): query -- param skipped: index = 1342179345.
W/Codec2Client(26189): query -- param skipped: index = 2415921170.
I/CCodec  (26189): state->set(RUNNING)
I/CCodecBufferChannel(26189): [c2.sec.mp3.decoder#558] 4 initial input buffers available
D/NativeAudioDecoder(26189): dequeueOutputBuffer timed out! /data/user/0/com.proshort.enterprise/app_flutter/summer_walk.mp3
I/ViewRootImpl@2379ad7[FlutterVESDKActivity](26189): ViewPostIme pointer 0
I/ViewRootImpl@2379ad7[FlutterVESDKActivity](26189): ViewPostIme pointer 1
E/AndroidRuntime(26189): FATAL EXCEPTION: Thread-59
E/AndroidRuntime(26189): Process: com.proshort.enterprise, PID: 26189
E/AndroidRuntime(26189): java.io.FileNotFoundException
E/AndroidRuntime(26189):    at ly.img.android.pesdk.backend.decoder.Decoder.getUncachedInputStream(Decoder.java:151)
E/AndroidRuntime(26189):    at ly.img.android.pesdk.utils.UriHelper.copyAsFile(UriHelper.kt:63)
E/AndroidRuntime(26189):    at ly.img.android.pesdk.utils.UriHelper.copyAsFile$default(UriHelper.kt:62)
E/AndroidRuntime(26189):    at ly.img.android.pesdk.utils.UriHelper.convertToLocalUri(UriHelper.kt:207)
E/AndroidRuntime(26189):    at ly.img.android.pesdk.utils.DownloadUtils.downloadIfNeeded(DownloadUtils.kt:67)
E/AndroidRuntime(26189):    at ly.img.android.pesdk.backend.decoder.AudioSource.cacheUriIfNeeded(AudioSource.kt:88)
E/AndroidRuntime(26189):    at ly.img.android.pesdk.ui.panels.AudioGalleryToolPanel$onBeforeDetach$$inlined$runAsync$1.run(ThreadUtils.kt:435)
E/AndroidRuntime(26189):    at java.lang.Thread.run(Thread.java:1012)
D/AudioTrack(26189): stop(1061): called with 74424 frames delivered
D/BufferPoolAccessor2.0(26189): bufferpool2 0xe1ec6ed8 : 5(40960 size) total buffers - 4(32768 size) used buffers - 0/5 (recycle/alloc) - 4/800 (fetch/transfer)
D/BufferPoolAccessor2.0(26189): evictor expired: 1, evicted: 1
E/FirebaseCrashlytics(26189): Cannot send reports. Timed out while fetching settings.
I/Process (26189): Sending signal. PID: 26189 SIG: 9

解决方案

1. 传递file://格式的Uri给AudioClip

Android端SDK无法直接识别本地文件路径字符串,需要转换为file://开头的Uri格式,避免SDK将路径当作远程URL尝试下载:

final List<AudioClip> audioClips = audioSources
    .asMap()
    .entries
    .map((clip) => AudioClip(
        clip.value,
        Uri.file(localPaths[clip.key]).toString(), // 转换为file Uri
        title: clip.value.replaceAll('_', ' ').toCapitalize().toString()))
    .toList();

2. 禁用SDK自动下载逻辑

由于已提前下载音频文件,关闭SDK的自动下载功能,防止重复触发下载流程:

final audioOptions = AudioOptions(
    categories: [AudioClipCategory("", "", items: audioClips)],
    downloadEnabled: false); // 禁用自动下载

3. 确保文件下载完整

修改下载函数,添加状态码校验和更可靠的文件写入方式,避免损坏的音频文件导致解码超时:

// 替换原下载逻辑部分
final response = await request.close();
if (response.statusCode == HttpStatus.ok) {
  await response.pipe(outputFile.openWrite());
} else {
  throw Exception('下载失败,状态码:${response.statusCode}');
}

4. 显式设置文件权限

下载完成后设置文件可读权限,确保SDK能正常读取文件:

await outputFile.writeAsBytes(bytes);
// 设置文件可读权限
await outputFile.chmod(0644);

内容的提问来源于stack exchange,提问作者Sankit Saini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 19:07:01