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);
}