Flutter媒体扩展功能插件media_extension的使用

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

Flutter媒体扩展功能插件media_extension的使用

简介

media_extension 是一个帮助 Flutter 应用程序像原生图库一样工作的插件。该插件由 ente 提供。

功能

  1. 处理来自其他应用的调用
    • 可以识别和处理来自不同应用的调用,并获取触发调用的意图动作(如 PICKEDITVIEW)。
    • 使用 setResult 方法将选定媒体文件的 URI 传递给请求的应用,通过在内容提供者路径中创建临时副本。
  2. 打开第三方应用
    • 能够使用第三方应用(如图库、编辑器、壁纸管理器等)打开图片。

实现

  • Native 代码调用 Flutter 代码:通过 onAttachedEngineMethod 调用 Flutter 方法,返回模式异步。在 Dart 代码中使用 Completer 获取状态并设置应用模式。
  • 作为图库行为:从应用程序文档存储目录中选择图像的 URI,并调用带有 URI 参数的方法。
  • 创建临时文件:在缓存目录中创建临时文件,并授予其他应用读取该目录的权限。一旦创建了 content://uri(内容提供者),结果将通过 intent 发送到请求的活动。

数据类型描述

类型 字段
IntentAction 枚举值:mainpickeditview
MediaExtentionAction IntentAction action [调用应用的意图动作],String? uri [其他应用通过意图发送的图片 URI]

方法描述

方法 参数 返回
getIntentAction MediaExtentionAction
setResult String uri [选定文件的路径] void
setAs String uri [选定文件的路径],String mimeType [选定文件的 MIME 类型] void
openWith String uri [选定文件的路径],String mimeType [选定文件的 MIME 类型] void
edit String uri [选定文件的路径],String mimeType [选定文件的 MIME 类型] void

入门指南

安装

  1. 通过终端命令安装
    flutter pub get media_extension
    
  2. pubspec.yaml 文件中添加依赖
    dependencies:
      flutter:
        sdk: flutter
      media_extension:
    

配置 Android

  1. android/src/main/res/xml/provider_paths.xml 中添加以下内容
    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-path
            name="external_files"
            path="." />
    
        <cache-path
            name="embedded"
            path="." />
    </paths>
    
  2. android/src/main/AndroidManifest.xml 中添加以下内容
    <application>
        <activity
            android:name=".MainActivity">
    
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>
    
            <intent-filter>
                <action android:name="android.intent.action.PICK" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>
    
            <intent-filter>
                <action android:name="android.intent.action.GET_CONTENT" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.OPENABLE" />
                <data android:mimeType="image/*" />
            </intent-filter>
        </activity>
    </application>
    

示例代码

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:media_extension_example/download.dart';
import 'package:path_provider/path_provider.dart';
import 'package:media_extension/media_extension_action_types.dart';
import 'package:media_extension/media_extension.dart';
import 'package:video_player/video_player.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final imgUrl = "https://cdn-chat.sstatic.net/chat/img/404_funny_hats.jpg";
  MediaExtentionAction _intentAction = MediaExtentionAction(action: IntentAction.main);
  final _mediaExtensionPlugin = MediaExtension();
  final _downloadHelper = DownloadHelper(Dio());
  late VideoPlayerController _controller;

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

  Future<void> initPlatformState() async {
    MediaExtentionAction intentAction;
    try {
      intentAction = await _mediaExtensionPlugin.getIntentAction();
      if (intentAction.action == IntentAction.view && intentAction.type == MediaType.video) {
        _controller = VideoPlayerController.contentUri(Uri.parse(intentAction.data!));
        await _controller.initialize();
      }
    } on PlatformException {
      intentAction = MediaExtentionAction(action: IntentAction.main);
      throw ("Platform exception");
    } on Exception catch (_) {
      intentAction = MediaExtentionAction(action: IntentAction.main);
    }
    if (!mounted) return;

    setState(() {
      _intentAction = intentAction;
      if (_intentAction.type == MediaType.video) _controller.play();
    });
  }

  Future<String> _getLocalFile(String filename) async {
    var tempDir = await getTemporaryDirectory();
    String fullPath = "${tempDir.path}/image.jpg";
    await _downloadHelper.downloadToFile(imgUrl, fullPath);
    return fullPath;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Intent Action is: ${_intentAction.action!.toShortString()}'),
        ),
        body: Center(
          child: _intentAction.action == IntentAction.view
              ? (_intentAction.type == MediaType.video
                  ? Center(
                      child: _controller.value.isInitialized
                          ? AspectRatio(
                              aspectRatio: _controller.value.aspectRatio,
                              child: VideoPlayer(_controller),
                            )
                          : Container())
                  : Image.memory(base64Decode(_intentAction.data!)))
              : Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    FutureBuilder(
                        future: _getLocalFile("image.jpg"),
                        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
                          return snapshot.data != null
                              ? GestureDetector(
                                  child: Image.file(File(snapshot.data!)),
                                  onTap: () async {
                                    if (_intentAction.action == IntentAction.pick) {
                                      _mediaExtensionPlugin.setResult("file://${snapshot.data!}");
                                    }
                                  },
                                )
                              : Container();
                        }),
                    GestureDetector(
                      child: Text(
                        'Set as',
                        style: Theme.of(context).textTheme.headlineMedium,
                      ),
                      onTap: () async {
                        var tempDir = await getTemporaryDirectory();
                        String fullPath = "${tempDir.path}/image.jpg'";
                        await _downloadHelper.downloadToFile(imgUrl, fullPath);
                        try {
                          final isDone = await _mediaExtensionPlugin.setAs("file://$fullPath", "image/*");
                          debugPrint("Is done $isDone");
                        } on PlatformException {
                          debugPrint("Some error");
                        }
                      },
                    ),
                    GestureDetector(
                      child: Text(
                        'Edit',
                        style: Theme.of(context).textTheme.headlineMedium,
                      ),
                      onTap: () async {
                        var tempDir = await getTemporaryDirectory();
                        String fullPath = "${tempDir.path}/image.jpg'";
                        await _downloadHelper.downloadToFile(imgUrl, fullPath);
                        try {
                          final isDone = await _mediaExtensionPlugin.edit("file://$fullPath", "image/*");
                          debugPrint("Is done $isDone");
                        } on PlatformException {
                          debugPrint("Some error");
                        }
                      },
                    ),
                    GestureDetector(
                      child: Text(
                        'Open With',
                        style: Theme.of(context).textTheme.headlineMedium,
                      ),
                      onTap: () async {
                        var tempDir = await getTemporaryDirectory();
                        String fullPath = "${tempDir.path}/image.jpg'";
                        await _downloadHelper.downloadToFile(imgUrl, fullPath);
                        try {
                          final result = await _mediaExtensionPlugin.openWith("file://$fullPath", "image/*");
                          debugPrint("Is done $result");
                        } on PlatformException {
                          debugPrint("Some error");
                        }
                      },
                    ),
                  ],
                ),
        ),
      ),
    );
  }
}

问题和即将的变化

  • 实现 setResults 方法,用于 ACTION_PICK 意图的多选图片。

致谢

  • aves:插件中的大多数方法都受到此仓库的启发。

许可证


更多关于Flutter媒体扩展功能插件media_extension的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter媒体扩展功能插件media_extension的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用media_extension插件的示例代码。media_extension插件通常用于扩展Flutter应用中媒体文件的处理功能,比如自定义视频播放器或音频解码等。需要注意的是,实际使用中可能会有不同的API和配置需求,以下示例基于一个假设的场景,即创建一个自定义的视频播放器。

首先,确保你已经在pubspec.yaml文件中添加了media_extension依赖:

dependencies:
  flutter:
    sdk: flutter
  media_extension: ^x.y.z  # 请替换为实际的版本号

然后运行flutter pub get来安装依赖。

1. 配置Info.plist(iOS)和AndroidManifest.xml(Android)

对于iOS,你可能需要在Info.plist中添加一些必要的配置来允许媒体播放。对于Android,确保在AndroidManifest.xml中有相应的权限声明。

iOS (Info.plist)

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
    <string>video-playback</string>
</array>

Android (AndroidManifest.xml)

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

2. 创建一个自定义的视频播放器

接下来,我们将创建一个自定义的视频播放器,利用media_extension插件提供的API。假设media_extension插件提供了一个简单的接口来加载和播放视频。

main.dart

import 'package:flutter/material.dart';
import 'package:media_extension/media_extension.dart'; // 假设插件提供了这个包名

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CustomVideoPlayerScreen(),
    );
  }
}

class CustomVideoPlayerScreen extends StatefulWidget {
  @override
  _CustomVideoPlayerScreenState createState() => _CustomVideoPlayerScreenState();
}

class _CustomVideoPlayerScreenState extends State<CustomVideoPlayerScreen> {
  late MediaExtensionPlayer _player;

  @override
  void initState() {
    super.initState();
    // 初始化播放器
    _player = MediaExtensionPlayer(
      url: 'https://www.example.com/path/to/your/video.mp4',
      onReady: () {
        print('Player is ready');
      },
      onError: (error) {
        print('Error: $error');
      },
      onEnd: () {
        print('Video ended');
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Video Player'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _player.widget, // 假设插件提供了一个widget属性
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                _player.play();
              },
              child: Text('Play'),
            ),
            ElevatedButton(
              onPressed: () {
                _player.pause();
              },
              child: Text('Pause'),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }
}

// 假设MediaExtensionPlayer类
class MediaExtensionPlayer {
  final String url;
  final VoidCallback onReady;
  final Function(dynamic) onError;
  final VoidCallback onEnd;
  late Widget widget;

  MediaExtensionPlayer({
    required this.url,
    required this.onReady,
    required this.onError,
    required this.onEnd,
  }) {
    // 初始化widget,这里只是模拟,实际插件可能有不同的初始化方式
    widget = Platform.isIOS
        ? CupertinoSlider.adaptive(
            value: 0.0, onChanged: (_) {}, min: 0.0, max: 1.0, activeColor: Colors.blue)
        : Slider(
            value: 0.0, onChanged: (_) {}, min: 0.0, max: 1.0, activeColor: Colors.blue);

    // 插件初始化代码(假设)
    _initializePlayer();
  }

  void _initializePlayer() async {
    // 模拟异步初始化过程
    await Future.delayed(Duration(seconds: 2));
    onReady();
    // 注意:这里应该包含实际的播放器初始化代码
  }

  void play() {
    // 播放视频的逻辑
    print('Playing video...');
  }

  void pause() {
    // 暂停视频的逻辑
    print('Pausing video...');
  }

  void dispose() {
    // 清理资源
    print('Player disposed');
  }
}

注意

  1. 插件API: 上述代码中的MediaExtensionPlayer类是一个假设的类,实际插件可能有不同的API。请查阅media_extension插件的官方文档来了解其提供的具体API和用法。
  2. 错误处理: 在实际应用中,应添加更多的错误处理和边界情况检查。
  3. 平台特定代码: 根据不同的平台(iOS和Android),可能需要编写特定的代码来处理媒体播放。

确保在实际项目中根据插件的文档和API进行调整。如果media_extension插件有具体的示例代码或文档,请参考那些资源以获得更准确的信息。

回到顶部