Flutter自定义功能插件flutter_silk的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter自定义功能插件flutter_silk的使用

flutter_silk 是一个用于将音频从silk格式转换为pcm/mp3格式的Flutter ffi插件。它支持MacOS、iOS、Linux、Windows和Android平台。

使用

以下是如何使用flutter_silk的基本示例:

final silkData = File("input.silk").readAsBytesSync();
var output = silkToMp3(silkData);
File("output.mp3").writeAsBytesSync(output!);

要查看完整的示例,请访问/example文件夹。

项目结构

此模板使用以下结构:

  • src: 包含本地源代码及用于构建动态库的Cmake文件。
  • lib: 包含插件的Dart代码,并使用dart:ffi调用本地代码。
  • 平台文件夹(如androidioswindows等):包含用于构建和捆绑本地代码库与平台应用程序的构建文件。

绑定到本地代码

为了使用本地代码,需要在Dart中创建绑定。为了避免手动编写这些绑定,可以使用package:ffigen从头文件src/flutter_silk.h生成绑定。

重新生成绑定可以通过运行以下命令完成:

flutter pub run ffigen --config ffigen.yaml

调用本地代码

一些本地函数可以直接从任何隔离区调用。例如,在lib/flutter_silk.dart中可以看到silkToPcmpcmToMp3silkToMp3等函数。

完整示例

以下是完整的示例代码,位于example/lib/main.dart文件中:

import 'dart:io';

import 'package:audioplayers/audioplayers.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_silk/flutter_silk.dart';
import 'package:path/path.dart' as path;

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String? inputPath;
  String? outputDir;
  String outputName = "output.mp3";
  bool working = false;
  bool done = false;
  final player = AudioPlayer();

  void selectInputFile() async {
    final result = await FilePicker.platform.pickFiles(
        type: FileType.custom,
        dialogTitle: "Select Silk file",
        allowedExtensions: ["silk"],
        allowMultiple: false);

    if (result == null) {
      return;
    }

    if (result.paths.isEmpty) {
      return;
    }

    setState(() {
      inputPath = result.paths[0];
    });
  }

  void encodeMp3() async {
    if (inputPath == null) {
      throw StateError("inputPath should not be null");
    }

    outputDir = await FilePicker.platform.getDirectoryPath(
        dialogTitle: "Pick a directory to save output MP3 file");
    if (outputDir == null) {
      return;
    }

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

    try {
      print("input file: ${inputPath}");
      final silkData = File(inputPath!).readAsBytesSync();

      var output = silkToMp3(silkData);

      final File f = File(path.join(outputDir!, outputName));
      if (output != null) {
        f.writeAsBytesSync(output!);
        print("Output file: ${f.path}");
      }
      print("Done ${silkData.length}, output -> ${output == null}");
      setState(() {
        done = true;
      });
    } catch (e) {
      showDialog(
          context: context,
          builder: (context) => AlertDialog(
                title: const Text("Error"),
                content: Text(e.toString()),
              ));
    } finally {
      setState(() {
        working = false;
      });
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    const textStyle = TextStyle(fontSize: 25);
    const spacerSmall = SizedBox(height: 10);
    const spacerLarge = SizedBox(height: 30);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter SILK & LAME Example'),
        ),
        body: SingleChildScrollView(
          child: Container(
            padding: const EdgeInsets.all(10),
            child: Column(
              children: [
                const Text(
                  'Call SILK/LAME API through FFI that is shipped as source in the package. '
                  'The native code is built as part of the Flutter Runner build.',
                  style: textStyle,
                  textAlign: TextAlign.center,
                ),
                const Divider(),
                spacerLarge,
                ElevatedButton(
                    onPressed: !working ? selectInputFile : null,
                    child: const Text(
                      "Select Silk file",
                      style: textStyle,
                    )),
                spacerSmall,
                RichText(
                  text: TextSpan(
                      style: const TextStyle(fontSize: 25, color: Colors.black),
                      children: [
                        const TextSpan(
                            text: "Input Silk file: ",
                            style: TextStyle(fontWeight: FontWeight.bold)),
                        TextSpan(text: inputPath)
                      ]),
                  textAlign: TextAlign.center,
                ),
                spacerSmall,
                TextFormField(
                    onChanged: (v) => setState(() {
                          outputName = v;
                        }),
                    decoration:
                        const InputDecoration(labelText: "Output MP3 filename"),
                    initialValue: outputName),
                spacerSmall,
                ElevatedButton(
                    onPressed:
                        inputPath != null && outputName.isNotEmpty && !working
                            ? encodeMp3
                            : null,
                    child: const Text(
                      "Encode to MP3",
                      style: textStyle,
                    )),
                spacerSmall,
                ElevatedButton(
                  child: const Text(
                    'Play',
                    style: textStyle,
                  ),
                  onPressed: () async {
                    done
                        ? await player.play(
                            DeviceFileSource(path.join(outputDir!, outputName)))
                        : null;
                  },
                ),
                spacerSmall,
                if (working) const CircularProgressIndicator(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

更多关于Flutter自定义功能插件flutter_silk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义功能插件flutter_silk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,flutter_silk 是一个假设的 Flutter 插件名称,为了演示如何自定义和使用 Flutter 插件,我将基于一般 Flutter 插件开发的步骤和假设的 flutter_silk 插件功能来编写一个代码案例。请注意,实际插件的名称和功能可能与假设的不同,这里主要是演示流程。

1. 创建 Flutter 插件项目

首先,你需要使用 Flutter 命令行工具来创建一个新的 Flutter 插件项目。

flutter create --org com.example --template=plugin flutter_silk

2. 配置插件的 pubspec.yaml

在插件项目的根目录下,找到并编辑 pubspec.yaml 文件,添加插件的基本信息和依赖。

name: flutter_silk
description: A new Flutter plugin.
version: 0.1.0
homepage: https://github.com/example/flutter_silk

environment:
  sdk: ">=2.12.0 <3.0.0"
  flutter: ">=2.0.0"

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  plugin:
    platforms:
      android:
        package: com.example.flutter_silk
        pluginClass: FlutterSilkPlugin
      ios:
        pluginClass: FlutterSilkPlugin

3. 实现平台特定代码

Android 平台

android/src/main/java/com/example/flutter_silk/FlutterSilkPlugin.java 中实现插件的功能。

package com.example.flutter_silk;

import androidx.annotation.NonNull;
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.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.embedding.android.FlutterActivity;
import android.app.Activity;
import android.content.Context;

public class FlutterSilkPlugin implements FlutterPlugin, ActivityAware {
  private MethodChannel channel;
  private Activity activity;

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_silk");
    channel.setMethodCallHandler(
      (call, result) -> {
        if (call.method.equals("getPlatformVersion")) {
          String version = android.os.Build.VERSION.RELEASE;
          result.success(version);
        } else {
          result.notImplemented();
        }
      }
    );
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }

  @Override
  public void onAttachedToActivity(ActivityPluginBinding binding) {
    activity = binding.getActivity();
  }

  @Override
  public void onDetachedFromActivityForConfigChanges() {
    activity = null;
  }

  @Override
  public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
    activity = binding.getActivity();
  }

  @Override
  public void onDetachedFromActivity() {
    activity = null;
  }
}

iOS 平台

ios/Classes/FlutterSilkPlugin.swift 中实现插件的功能。

import Flutter

public class FlutterSilkPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "flutter_silk", binaryMessenger: registrar.messenger())
    let instance = FlutterSilkPlugin()
    _ = channel.setMethodCallHandler(instance.handle(_:result:))
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    if call.method == "getPlatformVersion" {
      let version = UIDevice.current.systemVersion
      result(version)
    } else {
      result(FlutterMethodNotImplemented)
    }
  }
}

4. 在 Flutter 项目中使用插件

现在,你已经创建了一个基本的 Flutter 插件。接下来,在你的 Flutter 应用项目中使用这个插件。

添加插件依赖

在你的 Flutter 应用项目的 pubspec.yaml 文件中添加对 flutter_silk 插件的依赖。

dependencies:
  flutter:
    sdk: flutter
  flutter_silk:
    path: ../path/to/flutter_silk  # 指向你的插件项目路径

使用插件

在你的 Flutter 应用项目的 Dart 代码中导入并使用插件。

import 'package:flutter/material.dart';
import 'package:flutter_silk/flutter_silk.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  String _platformVersion = 'Unknown platform version';

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

  Future<void> _getPlatformVersion() async {
    String version;
    try {
      version = await FlutterSilk.platformVersion;
    } on PlatformException {
      version = 'Failed to get platform version.';
    }

    if (!mounted) return;

    setState(() {
      _platformVersion = version;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text('Running on: $_platformVersion\n');
  }
}

在这个示例中,FlutterSilk.platformVersion 调用插件的 getPlatformVersion 方法。注意,这里的 FlutterSilk 类和 platformVersion 方法是假设的,实际使用时你需要根据插件的 API 文档来调用正确的方法和属性。

请注意,这只是一个基本的插件开发示例,实际开发中可能涉及更多复杂的逻辑和平台特定的实现。希望这个示例能帮助你理解如何创建和使用 Flutter 自定义插件。

回到顶部