使用Native(以Android为例)播放器构建Flutter播放插件


Flutter官方提供了一系列的插件的插件

来为Flutter提供众多原生系统级API调用,包括传感器、文件读写、数据库、轻量存储等等,这些插件大都是以原生、Dart间通过MethodChannelEventChannel相互通信实现的。但google还提拱了一个Video_Player,专门用于视频播放的插件。插件原理是通过Native提供播放器能力,dart层通过Channel调用,以及双方通信控制播放,但画面是如何渲染到FlutterView(Android继承自SurfaceView)上的呢? 在此之前我们先回顾下Android上的视图都是如何渲染到屏幕上的。

在Android 中负责绘制UI的服务是SurfaceFlingerSurfaceFlinger服务运行在Android系统的System进程中,它负责管理Android系统的帧缓冲区(Frame Buffer)。Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,它们就必须要与SurfaceFlinger服务进行通信,由于Android应用程序与SurfaceFlinger服务是运行在不同的进程中的,因此,它们采用Binder进程间通信机制来进行通信。

Android应用程序在通知SurfaceFlinger服务来绘制自己的UI的时候,需要将大量的UI相关数据传递给SurfaceFlinger服务(如要绘制UI的区域、位置等信息)。一个Android应用程序可能会有很多个Window,而每一个Window都有自己的UI元数据,为了解决客户端与服务端通信效率问题,每一个客户端利用Android系统的匿名共享内存机制(Anonymous Shared Memory)与SurfaceFlinger服务进程开辟一块共享内存。

并对该共享内存进行封装,成为一个ShareClient。ShareClient中存储着ShareBufferStack,可以简单的理解为对应的Surface

Android 应用程序通过Canvas 绘制其UI并存储进FramBuffer中,而Canvas是其Surface 的成员,所以SurfaceFlinger可以读取FramBuffer数据并绘制渲染UI。

简而言之,Android是通过SurfaceFlinger服务进行绘制UI,而FlutterApplication中通过FlutterView(SurfaceView)将渲染UI的工作交给了Flutter ,dart->组装LayerTree->Skia(SurfaceFlinger服务底层绘制二维图像同样使用开源的Skia)

其实我们可以发现Android的绘制流程和Flutter的回执流程大体相当,但是否可以在Dart层将绘制能力返还给Android呢(Java),由于Surface持有Canvas,我们只要能够得到Surface就可以将Surface交由播放器绘制了,事实上FlutterSDK也提供了相关的API。下面我们来用一个简单的例子看下如何实现。

首先创建一个FlutterPlugin:MySurfaceTestPlugin

package com.example.darttest;

import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.view.Surface;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.view.TextureRegistry;

public class MySurfaceTestPlugin implements MethodChannel.MethodCallHandler {
    private final Registrar flutterRegistrar;
    private TextureRegistry textureRegistry;
    private TextureRegistry.SurfaceTextureEntry surfaceTextureEntry;
    private Surface surface;
    Random random;

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {

        String method = methodCall.method;

        switch (method) {
            
            case "init":
                //flutterRegistrar的textures方法获取textureRegistry,textureRegistry是包含了当前Flutter应用所有SurfaceTexture的登记表
                //同时也可以创建一个新的surfaceTexture
                textureRegistry = flutterRegistrar.textures();
                surfaceTextureEntry = textureRegistry.createSurfaceTexture();
                //通过一个刚刚创建的SurfaceTexure作为参数创建一个Surface
                surface = new Surface(surfaceTextureEntry.surfaceTexture());
                Map<String, Long> reply = new HashMap<>();
                long textureId = surfaceTextureEntry.id();
                //textureId回传给Flutter 用来创建Texure控件
                reply.put("textureId", textureId);
                result.success(reply);
                break;

            case "render":
                Canvas canvas = surface.lockCanvas(null);
                //这里的canvas 宽和高只有1个像素 因为surface得创建不是surfaceView拖管的,所以不能够draw实际内容,但是仍然可以绘制背景色
                //int height = canvas.getHeight();
                //int width = canvas.getWidth();
                canvas.drawColor(Color.argb(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)));
                surface.unlockCanvasAndPost(canvas);
                result.success(null);
                break;
                
            default:
                break;
        }
    }

    public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter.io/SurfaceTest");
        channel.setMethodCallHandler(new MySurfaceTestPlugin(registrar));
    }


    private MySurfaceTestPlugin(Registrar registrar) {
        this.flutterRegistrar = registrar;
        random = new Random();

    }


}


复制代码

可以看到通过FlutterSdk提供的方法获取了一个SurfaceTexure,通过SurfaceTexure我们new了一个Surface,官方文档对于Surface有如下描述:

SurfaceTexture对象中创建的Surface类对象可被用作下述类的输出目标: android.hardware.camera2, MediaCodec, MediaPlayer, 和 Allocation APIs。

也就是说Surface可以直接交由MediaPlayer、或者更低一级的MediaCodec作为输出对象使用。通过**new Surface(SurfaceTexture texture) 方式创建的Surface.LockCanvas(DirtyRect rect)**获取到的画布只有1px,显然私自创建的Surface官方意图并非让我们拿来绘制,而是交由MediaCodec, MediaPlayer输出,但我们仍然可以drawColor背景色来确认我们的java层与dart建立了关联。

接下来我们注册该插件

MySurfaceTestPlugin.registerWith(getFlutterView().getPluginRegistry().registrarFor("flutter.io/SurfaceTest"));
复制代码

注册完毕我们就可以在Dart中使用该插件了,我们创建一个简单的DartApp 上面是通过java层渲染过的"播放窗口",下面是一个控制渲染的按钮。

import 'dart:async';

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

main() async {
  await _initVideoPlugin();
  runApp(new MyApp());
}

final MethodChannel _channel = const MethodChannel('flutter.io/SurfaceTest');
int _textureId;

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Test',
      home: new Scaffold(
          body: Column(
        children: <Widget>[
          AspectRatio(child: new VideoView(), aspectRatio: 16 / 9),
          Expanded(
              child: Center(
                  child: new FlatButton(
                      padding: EdgeInsets.only(
                          left: 25.0, right: 25.0, top: 15.0, bottom: 15.0),
                      onPressed: _render,
                      child: new Text("render"))))
        ],
      )
          ),
    );
  }
}

Future<void> _render() async {
  _channel.invokeMethod(
    'render',
    <String, dynamic>{},
  );
}

class VideoView extends StatefulWidget {
  @override
  State createState() {
    return new VideoState();
  }
}

_initVideoPlugin() async {
  final Map<dynamic, dynamic> response = await _channel.invokeMethod('init');
  _textureId = response['textureId'];
}

class VideoState extends State<VideoView> {
  @override
  Widget build(BuildContext context) {
	//这里注意Texure是dart提供的控件 参数必须是java Plugin传过来的texureId
    return new Texture(textureId: _textureId);
  }
}

复制代码

需要注意的是播放窗口实际上是一个Dart控件Texture,这个Texure就是Plugin里通过flutterRegistrar.textures()获取到的textureRegistry所管理的SurfaceTexture 一一对应的。运行看一下效果

可见由java层绘制背景已经可以渲染到屏幕上,接下来我们只要将Surface交于播放器即可实现视频播放。如官方的Video_Player使用的ExoPlayer,我们也用ExoPlayer快速实现视频播放。

case "init":
                //flutterRegistrar的textures方法获取textureRegistry,textureRegistry是包含了当前Flutter应用所有SurfaceTexture的登记表
                //同时也可以创建一个新的surfaceTexture
                textureRegistry = flutterRegistrar.textures();
                surfaceTextureEntry = textureRegistry.createSurfaceTexture();
                //通过一个刚刚创建的SurfaceTexure作为参数创建一个Surface
                surface = new Surface(surfaceTextureEntry.surfaceTexture());
                initPlayer();
                Map<String, Long> reply = new HashMap<>();
                long textureId = surfaceTextureEntry.id();
                //textureId回传给Flutter 用来创建Texure控件
                reply.put("textureId", textureId);
                result.success(reply);
                break;

            case "render":
//                Canvas canvas = surface.lockCanvas(null);
//                canvas.drawColor(Color.argb(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)));
//                surface.unlockCanvasAndPost(canvas);
                exoPlayer.setPlayWhenReady(true);
                exoPlayer.setRepeatMode(REPEAT_MODE_ALL);
                result.success(null);
                break;
复制代码

这里 ,我们在init中执行initPlayer() 其中将Surface直接交给播放器,render中执行播放

private void initPlayer() {
        DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(flutterRegistrar.activeContext(), "ExoPlayer");
        MediaSource mediaSource = new ExtractorMediaSource(Uri.parse("asset:///flutter_assets/assets/Butterfly.mp4"), dataSourceFactory, new DefaultExtractorsFactory(), null, null);
        exoPlayer = ExoPlayerFactory.newSimpleInstance(flutterRegistrar.activeContext(), new DefaultTrackSelector());
        exoPlayer.setVideoSurface(surface);
        exoPlayer.prepare(mediaSource);
    }
复制代码

ok 现在我们开下效果

参考:

source.android.com/devices/gra… blog.csdn.net/luoshengyan…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值