Flutter ijkplayer播放器FijkPlayer浅析

github:
https://github.com/befovy/fijkplayer

demo:
https://github.com/befovy/fijkplayer/tree/master/example

1、视频播放

通过flutter方法,调用ijkplayer native 方法。
页面状态初始化的调用startPlay,进入setDataSource
video_page.dart


class _VideoScreenState extends State<VideoScreen> {
  final FijkPlayer player = FijkPlayer();

  _VideoScreenState();

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

  void startPlay() async {
    await player.setOption(FijkOption.hostCategory, "request-screen-on", 1);
    await player.setOption(FijkOption.hostCategory, "request-audio-focus", 1);
    await player.setDataSource(widget.url, autoPlay: true).catchError((e) {
      print("setDataSource error: $e");
    });
  }

setDataSource 显示setDataSource
await _channel.invokeMethod(“setDataSource”, <String, dynamic>{‘url’: path});
然后autoPlay为true进入start函数

fijkplayer.dart


 /// Set data source for this player
  ///
  /// [path] must be a valid uri, otherwise this method return ArgumentError
  ///
  /// set assets as data source
  /// first add assets in app's pubspec.yml
  ///   assets:
  ///     - assets/butterfly.mp4
  ///
  /// pass "asset:///assets/butterfly.mp4" to [path]
  /// scheme is `asset`, `://` is scheme's separator, `/` is path's separator.
  /// 
  /// If set [autoPlay] true, player will stat to play.
  /// The behavior of [setDataSource(url, autoPlay: true)] is like 
  ///    await setDataSource(url);
  ///    await setOption(FijkOption.playerCategory, "start-on-prepared", 1);
  ///    await prepareAsync();
  /// 
  /// If set [showCover] true, player will display the first video frame and then enter [FijkState.paused] state.
  /// The behavior of [setDataSource(url, showCover: true)] is like 
  ///    await setDataSource(url);
  ///    await setOption(FijkOption.playerCategory, "cover-after-prepared", 1);
  ///    await prepareAsync();
  /// 
  /// If both [autoPlay] and [showCover] are true, [showCover] will be ignored.
  Future<void> setDataSource(
    String path, {
    bool autoPlay = false,
    bool showCover = false,
  }) async {
    if (path == null || path.length == 0 || Uri.tryParse(path) == null) {
      FijkLog.e("$this setDataSource invalid path:$path");
      return Future.error(
          ArgumentError.value(path, "path must be a valid url"));
    }
    if (autoPlay == true && showCover == true) {
      FijkLog.w("call setDataSource with both autoPlay and showCover true, showCover will be ignored");
    }
    await _nativeSetup.future;
    if (state == FijkState.idle || state == FijkState.initialized) {
      try {
        FijkLog.i("$this invoke setDataSource $path");
        _dataSource = path;
        await _channel
            .invokeMethod("setDataSource", <String, dynamic>{'url': path});
      } on PlatformException catch (e) {
        return _errorListener(e);
      }
      if (autoPlay == true) {
        await start();
      } else if (showCover == true) {
        await setOption(FijkOption.playerCategory, "cover-after-prepared", 1);
        await prepareAsync();
      }
    } else {
      FijkLog.e("$this setDataSource invalid state:$state");
      return Future.error(StateError("setDataSource on invalid state $state"));
    }
  }

start函数依次调用,进入播放
await setOption(FijkOption.playerCategory, “start-on-prepared”, 1);
await _channel.invokeMethod(“prepareAsync”);
fijkplayer.dart


    
  /// change player's state to [FijkState.started]
  ///
  /// throw [StateError] if call this method on invalid state.
  /// see [fijkstate zh](https://fijkplayer.befovy.com/docs/zh/fijkstate.html) or
  /// [fijkstate en](https://fijkplayer.befovy.com/docs/en/fijkstate.html) for details
  Future<void> start() async {
    await _nativeSetup.future;
    if (state == FijkState.initialized) {
      _callId += 1;
      int cid = _callId;
      FijkLog.i("$this invoke prepareAsync and start #$cid");
      await setOption(FijkOption.playerCategory, "start-on-prepared", 1);
      await _channel.invokeMethod("prepareAsync");
      FijkLog.i("$this invoke prepareAsync and start #$cid -> done");
    } else if (state == FijkState.asyncPreparing ||
        state == FijkState.prepared ||
        state == FijkState.paused ||
        state == FijkState.started ||
        value.state == FijkState.completed) {
      FijkLog.i("$this invoke start");
      await _channel.invokeMethod("start");
    } else {
      FijkLog.e("$this invoke start invalid state:$state");
      return Future.error(StateError("call start on invalid state $state"));
    }
  }

FijkPlayer 为了flutter 提供ijkplayer native 方法
FijkPlayer.java



public class FijkPlayer implements MethodChannel.MethodCallHandler, IjkEventListener 

@Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        //noinspection IfCanBeSwitch
        if (call.method.equals("setupSurface")) {
            long viewId = setupSurface();
            result.success(viewId);
       } else if (call.method.equals("prepareAsync")) {
            mIjkMediaPlayer.prepareAsync();
            handleEvent(PLAYBACK_STATE_CHANGED, asyncPreparing, -1, null);
            result.success(null);
        } else if (call.method.equals("start")) {
            mIjkMediaPlayer.start();
            result.success(null);
        } else if (call.method.equals("pause")) {
            mIjkMediaPlayer.pause();
            result.success(null);
        } else if (call.method.equals("stop")) {
            mIjkMediaPlayer.stop();
            handleEvent(PLAYBACK_STATE_CHANGED, stopped, -1, null);
            result.success(null);

2、texture设置,外接纹理

1、fijkview初始化 _setupTexture
fijkview.dart



@override
  void initState() {
    super.initState();
    Size s = widget.player.value.size;
    if (s != null) {
      _vWidth = s.width;
      _vHeight = s.height;
    }
    widget.player.addListener(_fijkValueListener);
    _nativeSetup();
  }

  Future<void> _nativeSetup() async {
    if (widget.player.value.prepared) {
      _setupTexture();
    }
    paramNotifier.value = paramNotifier.value + 1;
  }

  void _setupTexture() async {
    final int vid = await widget.player.setupSurface();
    FijkLog.i("view setup, vid:" + vid.toString());
    if (mounted) {
      setState(() {
        _textureId = vid;
      });
    }
  }

2、native FijkPlayer 创建SurfaceTexture,配置ijk播放器,返回_textureId
FijkPlayer.java

    long setupSurface() {
        if (mSurfaceTextureEntry == null) {
            TextureRegistry textureRegistry = mRegistrar.textures();
            TextureRegistry.SurfaceTextureEntry surfaceTextureEntry = textureRegistry.createSurfaceTexture();
            mSurfaceTextureEntry = surfaceTextureEntry;
            mSurfaceTexture = surfaceTextureEntry.surfaceTexture();
            mSurface = new Surface(mSurfaceTexture);
            mIjkMediaPlayer.setSurface(mSurface);
        }
        return mSurfaceTextureEntry.id();
    }

3、buildTexture 创建textureId的widget
fijkview.dart

   Widget buildTexture() {
    Widget tex = _textureId > 0 ? Texture(textureId: _textureId) : Container();
    if (_degree != 0 && _textureId > 0) {
      return RotatedBox(
        quarterTurns: _degree ~/ 90,
        child: tex,
      );
    }
    return tex;
  }


4、Texture

/// A rectangle upon which a backend texture is mapped.
///
/// Backend textures are images that can be applied (mapped) to an area of the
/// Flutter view. They are created, managed, and updated using a
/// platform-specific texture registry. This is typically done by a plugin
/// that integrates with host platform video player, camera, or OpenGL APIs,
/// or similar image sources.
///
/// A texture widget refers to its backend texture using an integer ID. Texture
/// IDs are obtained from the texture registry and are scoped to the Flutter
/// view. Texture IDs may be reused after deregistration, at the discretion
/// of the registry. The use of texture IDs currently unknown to the registry
/// will silently result in a blank rectangle.
///
/// Texture widgets are repainted autonomously as dictated by the backend (e.g.
/// on arrival of a video frame). Such repainting generally does not involve
/// executing Dart code.
///
/// The size of the rectangle is determined by its parent widget, and the
/// texture is automatically scaled to fit.
///
/// See also:
///
///  * <https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.html>
///    for how to create and manage backend textures on Android.
///  * <https://api.flutter.dev/objcdoc/Protocols/FlutterTextureRegistry.html>
///    for how to create and manage backend textures on iOS.
class Texture extends LeafRenderObjectWidget {
  /// Creates a widget backed by the texture identified by [textureId].
  const Texture({
    Key key,
    @required this.textureId,
  }) : assert(textureId != null),
       super(key: key);

  /// The identity of the backend texture.
  final int textureId;

  @override
  TextureBox createRenderObject(BuildContext context) => TextureBox(textureId: textureId);

  @override
  void updateRenderObject(BuildContext context, TextureBox renderObject) {
    renderObject.textureId = textureId;
  }
}

3、横竖屏切换

点击切换横竖屏
panel.dart

            IconButton(
              icon: Icon(widget.player.value.fullScreen
                  ? Icons.fullscreen_exit
                  : Icons.fullscreen),
              padding: EdgeInsets.only(left: 10.0, right: 10.0),
//              color: Colors.transparent,
              onPressed: () {
                widget.player.value.fullScreen
                    ? player.exitFullScreen()
                    : player.enterFullScreen();
              },
            )
                  

fijkplayer.dart 设置fullScreen字段


   /// enter full screen mode, set [FijkValue.fullScreen] to true
  void enterFullScreen() {
    FijkLog.i("$this enterFullScreen");
    _setValue(value.copyWith(fullScreen: true));
  }

fijkview.java 响应监听值

   
  void _fijkValueListener() async {
    FijkValue value = widget.player.value;
    if (value.prepared && _textureId < 0) {
      _setupTexture();
    }

    if (widget.fs) {
      if (value.fullScreen && !_fullScreen) {
        _fullScreen = true;
        await _pushFullScreenWidget(context);
      } else if (_fullScreen && !value.fullScreen) {
        Navigator.of(context).pop();
        _fullScreen = false;
      }

      // save width and height to make judgement about whether to
      // request landscape when enter full screen mode
      if (value.size != null && value.prepared) {
        _vWidth = value.size.width;
        _vHeight = value.size.height;
      }
    }
  }

fijkview.java

  Future<dynamic> _pushFullScreenWidget(BuildContext context) async {
    final TransitionRoute<Null> route = PageRouteBuilder<Null>(
      settings: RouteSettings(isInitialRoute: false),
      pageBuilder: _fullScreenRoutePageBuilder,
    );

    await SystemChrome.setEnabledSystemUIOverlays([]);
    bool changed = false;
    if (_vWidth >= _vHeight) {
      if (MediaQuery.of(context).orientation == Orientation.portrait)
        changed = await FijkPlugin.setOrientationLandscape();
    } else {
      if (MediaQuery.of(context).orientation == Orientation.landscape)
        changed = await FijkPlugin.setOrientationPortrait();
    }

    await Navigator.of(context).push(route);
    print("_pushFullScreenWidget ");
    _fullScreen = false;
    widget.player.exitFullScreen();
    await SystemChrome.setEnabledSystemUIOverlays(
        [SystemUiOverlay.top, SystemUiOverlay.bottom]);
    if (changed) {
      if (_vWidth >= _vHeight) {
        await FijkPlugin.setOrientationPortrait();
      } else {
        await FijkPlugin.setOrientationLandscape();
      }
    }
  }

  AnimatedWidget _defaultRoutePageBuilder(
      BuildContext context, Animation<double> animation) {
    return AnimatedBuilder(
      animation: animation,
      builder: (BuildContext context, Widget child) {
        return Scaffold(
          resizeToAvoidBottomInset: false,
          body: _InnerFijkView(
            fijkViewState: this,
            fullScreen: true,
            cover: widget.cover,
          ),
        );
      },
    );
  }

  Widget _fullScreenRoutePageBuilder(BuildContext context,
      Animation<double> animation, Animation<double> secondaryAnimation) {
    return _defaultRoutePageBuilder(context, animation);
  }

Flutter 提供了一个名为 `video_player` 的插件,可以用于在 Flutter 应用程序中播放视频。使用 `video_player` 插件,你可以轻松地将视频嵌入到你的应用程序中,并控制视频的播放、暂停、停止等操作。 以下是一个简单的示例代码,演示如何使用 `video_player` 插件播放本地视频文件: ```dart import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Video Player Demo', home: VideoPlayerScreen(), ); } } class VideoPlayerScreen extends StatefulWidget { @override _VideoPlayerScreenState createState() => _VideoPlayerScreenState(); } class _VideoPlayerScreenState extends State<VideoPlayerScreen> { VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/sample_video.mp4') ..initialize().then((_) { setState(() {}); }); } @override void dispose() { super.dispose(); _controller.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Video Player Demo'), ), body: Center( child: _controller.value.initialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ) : Container(), ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, child: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, ), ), ); } } ``` 在上面的示例代码中,我们首先创建了一个 `VideoPlayerController` 对象,并使用 `asset` 方法加载了一个本地视频文件。然后,在 `initState` 方法中,我们初始化了 `VideoPlayerController` 对象,并在初始化完成后调用了 `setState` 方法,以便更新 UI。接下来,在 `build` 方法中,我们使用 `AspectRatio` 和 `VideoPlayer` 将视频嵌入到应用程序中,并使用一个浮动操作按钮控制视频的播放和暂停。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值