flutter开发实战-ijkplayer视频播放器功能

使用better_player播放器进行播放视频时候,在Android上会出现解码失败的问题,better_player使用的是video_player,video_player很多视频无法解码。最终采用ijkplayer播放器插件,在flutter上使用fijkplayer插件。

一、引入fijkplayer

在使用fijkplayer前可以先看下https://fijkplayer.befovy.com/docs/zh/fijkplayer-api.html

在工程的pubspec.yaml中引入插件

fijkplayer: ^0.11.0

fijkPlayer 就是对 native C 层 ijkplayer 的一个 dart 包装,接口都保持一致。 FijkPlayer 处理所有播放相关的工作,实际工作都是由 native C 层 ijkplayer 完成,包含检查 dataSource 中的媒体信息,打开解码器和解码线程、打开音频输出设备、将解码后数据输出给音频设备或显示设备。

二、使用fijkplayer

2.1、IJKVideoPlayerController控制常用操作

使用fijkplayer,这里创建了IJKVideoPlayer来嵌套一下FijkView,使用IJKVideoPlayerController来控制常用功能操作

IJKVideoPlayerController如下

import 'dart:async';

class IJKVideoPlayerController {

FutureOr Function()? stop;

FutureOr Function()? pause;

FutureOr Function()? play;

FutureOr Function(int msec)? seekTo;

FutureOr Function(double volume)? setVolume;

FutureOr Function(double speed)? setSpeed;

FutureOr Function(int loopCount)? setLoop;

FutureOr Function()? isPlaying;

}

IJKVideoPlayerController来控制停止、暂停、播放、seek、设置音量、设置播放速率、设置循环次数、获取是否在播放中等。

播放视频

void play() {

if (videoPlayerController.play != null) {

videoPlayerController.play!.call();

}

}

暂停视频播放

void pause() {

if (videoPlayerController.pause != null) {

videoPlayerController.pause!.call();

}

}

停止视频播放

void stop() {

if (videoPlayerController.stop != null) {

videoPlayerController.stop!.call();

}

}

seek指定位置

void seekTo(int msec) {

if (videoPlayerController.seekTo != null) {

videoPlayerController.seekTo!.call(msec);

}

}

设置音量

void setVolume(double volume) {

if (videoPlayerController.setVolume != null) {

videoPlayerController.setVolume!.call(volume);

}

}

设置播放速率

void setSpeed(double speed) {

if (videoPlayerController.setSpeed != null) {

videoPlayerController.setSpeed!.call(speed);

}

}

设置循环次数

void setLoop(int loopCount) {

if (videoPlayerController.setLoop != null) {

videoPlayerController.setLoop!.call(loopCount);

}

}

获取是否播放中

Future isPlaying() async {

if (videoPlayerController.isPlaying != null) {

bool videoIsPlaying = await videoPlayerController.isPlaying!.call();

return videoIsPlaying;

}

return Future.value(null);

}

2.2、在ijkplayer设置source,使用FijkPlayer

在设置播放器的时候,需要设置source类型。fijkplayer提供了两种方式,一种是本地工程文件、一种是网络视频地址。

设置网络视频源

/// usage

/// autoPlay 为 true 时等同于连续调用 setDataSource、prepareAsync、start

fplayer.setDataSource("http://samplevideo.com/sample.flv", autoPlay: true);

设置本地资源作为播放源

/// pubspec.yml 中需要指定assets 内容

/// assets:

/// - assets/butterfly.mp4

///

/// scheme 是 `asset`, `://` 是 scheme 分隔符, `/` 是路径起始符号

fplayer.setDataSource("asset:///assets/butterfly.mp4", autoPlay: true);

在setDataSource还有autoPlay(自动播放),showCover(是否显示视频封面,视频默认获取第一帧作为视频封面)

2.3、FijkView显示视频的控件Widget

在fijkplayer中,使用FijkView来显示视频。

FijkView({

required this.player,

this.width,

this.height,

this.fit = FijkFit.contain,

this.fsFit = FijkFit.contain,

this.panelBuilder = defaultFijkPanelBuilder,

this.color = const Color(0xFF607D8B),

this.cover,

this.fs = true,

this.onDispose,

});

可以设置显示fit、全屏的fit、背景颜色color、封面图(设置之后会显示在视频播放的上面)、是否全屏等。

在这里我们如果需要自定义样式,可以替换掉panelBuilder。

2.4、自定义控件IJKVideoPanel

在这里我们如果需要自定义样式,可以替换掉panelBuilder。我们自定义一个IJKVideoPanel,这个大部分代码来源default,这里调整了部分样式。

IJKVideoPanel完整代码如下

import 'dart:async';

import 'dart:math';

import 'package:fijkplayer/fijkplayer.dart';

import 'package:flutter/material.dart';

class IJKVideoPanel extends StatefulWidget {

const IJKVideoPanel({

super.key,

required this.player,

required this.buildContext,

required this.viewSize,

required this.texturePos,

});

final FijkPlayer player;

final BuildContext buildContext;

final Size viewSize;

final Rect texturePos;

@override

State createState() => _IJKVideoPanelState();

}

class _IJKVideoPanelState extends State {

FijkPlayer get player => widget.player;

Duration _duration = Duration();

Duration _currentPos = Duration();

Duration _bufferPos = Duration();

bool _playing = false;

bool _prepared = false;

String? _exception;

// bool _buffering = false;

double _seekPos = -1.0;

StreamSubscription? _currentPosSubs;

StreamSubscription? _bufferPosSubs;

//StreamSubscription _bufferingSubs;

Timer? _hideTimer;

bool _hideStuff = true;

double _volume = 1.0;

final barHeight = 40.0;

@override

void initState() {

super.initState();

_duration = player.value.duration;

_currentPos = player.currentPos;

_bufferPos = player.bufferPos;

_prepared = player.state.index >= FijkState.prepared.index;

_playing = player.state == FijkState.started;

_exception = player.value.exception.message;

// _buffering = player.isBuffering;

player.addListener(_playerValueChanged);

_currentPosSubs = player.onCurrentPosUpdate.listen((v) {

setState(() {

_currentPos = v;

});

});

_bufferPosSubs = player.onBufferPosUpdate.listen((v) {

setState(() {

_bufferPos = v;

});

});

}

void _playerValueChanged() {

FijkValue value = player.value;

if (value.duration != _duration) {

setState(() {

_duration = value.duration;

});

}

bool playing = (value.state == FijkState.started);

bool prepared = value.prepared;

String? exception = value.exception.message;

if (playing != _playing ||

prepared != _prepared ||

exception != _exception) {

setState(() {

_playing = playing;

_prepared = prepared;

_exception = exception;

});

}

}

void _playOrPause() {

if (_playing == true) {

player.pause();

} else {

player.start();

}

}

@override

void dispose() {

super.dispose();

_hideTimer?.cancel();

player.removeListener(_playerValueChanged);

_currentPosSubs?.cancel();

_bufferPosSubs?.cancel();

}

void _startHideTimer() {

_hideTimer?.cancel();

_hideTimer = Timer(const Duration(seconds: 3), () {

setState(() {

_hideStuff = true;

});

});

}

void _cancelAndRestartTimer() {

if (_hideStuff == true) {

_startHideTimer();

}

setState(() {

_hideStuff = !_hideStuff;

});

}

Widget _buildVolumeButton() {

IconData iconData;

if (_volume <= 0) {

iconData = Icons.volume_off;

} else {

iconData = Icons.volume_up;

}

return IconButton(

icon: Icon(iconData, color: Colors.white),

padding: EdgeInsets.only(left: 10.0, right: 10.0),

onPressed: () {

setState(() {

_volume = _volume > 0 ? 0.0 : 1.0;

player.setVolume(_volume);

});

},

);

}

AnimatedOpacity _buildBottomBar(BuildContext context) {

double duration = _duration.inMilliseconds.toDouble();

double currentValue =

_seekPos > 0 ? _seekPos : _currentPos.inMilliseconds.toDouble();

currentValue = min(currentValue, duration);

currentValue = max(currentValue, 0);

return AnimatedOpacity(

opacity: _hideStuff ? 0.0 : 0.8,

duration: Duration(milliseconds: 400),

child: Container(

height: barHeight,

decoration: BoxDecoration(

gradient: LinearGradient(

colors: [Colors.transparent, Colors.black45],

begin: Alignment.topCenter,

end: Alignment.bottomCenter,

),

),

child: Row(

children: [

_buildVolumeButton(),

Padding(

padding: EdgeInsets.only(right: 5.0, left: 5),

child: Text(

'${_duration2String(_currentPos)}',

style: TextStyle(fontSize: 14.0, color: Colors.white),

),

),

_duration.inMilliseconds == 0

? Expanded(child: Center())

: Expanded(

child: Padding(

padding: EdgeInsets.only(right: 0, left: 0),

child: FijkSlider(

value: currentValue,

cacheValue: _bufferPos.inMilliseconds.toDouble(),

min: 0.0,

max: duration,

onChanged: (v) {

_startHideTimer();

setState(() {

_seekPos = v;

});

},

onChangeEnd: (v) {

setState(() {

player.seekTo(v.toInt());

print("seek to $v");

_currentPos =

Duration(milliseconds: _seekPos.toInt());

_seekPos = -1;

});

},

),

),

),

// duration / position

_duration.inMilliseconds == 0

? Container(child: const Text("LIVE"))

: Padding(

padding: EdgeInsets.only(right: 5.0, left: 5),

child: Text(

'${_duration2String(_duration)}',

style: TextStyle(fontSize: 14.0, color: Colors.white),

),

),

// 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();

// },

// )

//

],

),

),

);

}

@override

Widget build(BuildContext context) {

// Rect rect = player.value.fullScreen

// ? Rect.fromLTWH(0, 0, widget.viewSize.width, widget.viewSize.height)

// : Rect.fromLTRB(

// max(0.0, widget.texturePos.left),

// max(0.0, widget.texturePos.top),

// min(widget.viewSize.width, widget.texturePos.right),

// min(widget.viewSize.height, widget.texturePos.bottom));

Rect rect =

Rect.fromLTWH(0, 0, widget.viewSize.width, widget.viewSize.height);

return Positioned.fromRect(

rect: rect,

child: GestureDetector(

onTap: _cancelAndRestartTimer,

child: AbsorbPointer(

absorbing: _hideStuff,

child: Column(

children: [

Container(height: barHeight),

Expanded(

child: GestureDetector(

onTap: () {

_cancelAndRestartTimer();

},

child: Container(

color: Colors.transparent,

height: double.infinity,

width: double.infinity,

child: Center(

child: _exception != null

? Text(

_exception!,

style: TextStyle(

color: Colors.white,

fontSize: 25,

),

)

: (_prepared ||

player.state == FijkState.initialized)

? AnimatedOpacity(

opacity: _hideStuff ? 0.0 : 0.85,

duration: Duration(milliseconds: 400),

child: IconButton(

iconSize: barHeight * 2,

icon: Icon(

_playing

? Icons.pause

: Icons.play_arrow,

color: Colors.white,

size: 44,

),

padding: EdgeInsets.only(

left: 10.0, right: 10.0),

onPressed: _playOrPause))

: SizedBox(

width: barHeight * 1.5,

height: barHeight * 1.5,

child: CircularProgressIndicator(

valueColor: AlwaysStoppedAnimation(

Colors.white)),

)),

),

),

),

_buildBottomBar(context),

],

),

),

),

);

}

}

String _duration2String(Duration duration) {

if (duration.inMilliseconds < 0) return "-: negtive";

String twoDigits(int n) {

if (n >= 10) return "$n";

return "0$n";

}

String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));

String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));

int inHours = duration.inHours;

return inHours > 0

? "$inHours:$twoDigitMinutes:$twoDigitSeconds"

: "$twoDigitMinutes:$twoDigitSeconds";

}

2.5、嵌套FijkView的IJKVideoPlayer

在使用时候,使用了IJKVideoPlayer来封装了一层FijkView。 在IJKVideoPlayer中设置了videoPlayerController控制播放的操作 如停止、暂停、播放、seek、设置音量、设置播放速率、设置循环次数、获取是否在播放中。

IJKVideoPlayer完整代码如下

import 'package:fijkplayer/fijkplayer.dart';

import 'package:flutter/material.dart';

import 'package:flutter_app_demolab/ijk_player/ijk_video_panel.dart';

import 'ijk_video_player_controller.dart';

/// usage

/// autoPlay 为 true 时等同于连续调用 setDataSource、prepareAsync、start

/// fplayer.setDataSource("http://samplevideo.com/sample.flv", autoPlay: true);

///

/// 设置本地资源作为播放源,

/// pubspec.yml 中需要指定assets 内容

/// assets:

/// - assets/butterfly.mp4

///

/// scheme 是 `asset`, `://` 是 scheme 分隔符, `/` 是路径起始符号

/// fplayer.setDataSource("asset:///assets/butterfly.mp4", autoPlay: true);

class IJKVideoPlayer extends StatefulWidget {

const IJKVideoPlayer({

super.key,

required this.path,

this.autoPlay = false,

this.showCover = true,

this.fit = FijkFit.contain,

this.cover,

this.color = Colors.black,

this.width,

this.height,

this.videoPlayerController,

});

final double? width;

final double? height;

final String path;

final bool autoPlay;

final bool showCover;

final FijkFit fit;

final Widget? cover;

final Color color;

final IJKVideoPlayerController? videoPlayerController;

@override

State createState() => _IJKVideoPlayerState();

}

class _IJKVideoPlayerState extends State {

final FijkPlayer player = FijkPlayer();

@override

void initState() {

super.initState();

player.setDataSource(

widget.path,

autoPlay: widget.autoPlay,

showCover: widget.showCover,

);

addVideoPlayerFun();

}

void addVideoPlayerFun() {

if (widget.videoPlayerController != null) {

widget.videoPlayerController!.play = () {

// 触发播放

player.start();

};

widget.videoPlayerController!.stop = () {

// 触发停止

player.stop();

};

widget.videoPlayerController!.pause = () {

// 触发暂停

player.pause();

};

widget.videoPlayerController!.setLoop = (int loopCount) {

// 触发setLoop

if (loopCount < 0) {

loopCount = 1;

}

player.setLoop(loopCount);

};

widget.videoPlayerController!.seekTo = (int msec) {

// 触发seek

if (msec < 0) {

msec = 0;

}

player.seekTo(msec);

};

widget.videoPlayerController!.setVolume = (double volume) {

// 触发setVolume

if (volume < 0.0) {

volume = 0.0;

}

player.setVolume(volume);

};

widget.videoPlayerController!.setSpeed = (double speed) {

// 触发setSpeed

if (speed < 0.0) {

speed = 1.0;

}

player.setSpeed(speed);

};

widget.videoPlayerController!.isPlaying = () {

// 触发setVolume

if (FijkState.started == player.state) {

return true;

} else {

return false;

}

};

}

}

@override

void dispose() {

super.dispose();

player.release();

}

void onIJKDispose(FijkData fijkData) {}

@override

Widget build(BuildContext context) {

return Container(

alignment: Alignment.center,

child: Stack(

alignment: Alignment.center,

children: [

widget.cover != null ? widget.cover! : Container(),

FijkView(

width: widget.width,

height: widget.height,

player: player,

fit: widget.fit,

fsFit: widget.fit,

color: widget.color,

onDispose: onIJKDispose,

panelBuilder: (FijkPlayer player, FijkData data,

BuildContext context, Size viewSize, Rect texturePos) {

return IJKVideoPanel(

player: player,

buildContext: context,

viewSize: viewSize,

texturePos: texturePos,

);

},

),

],

),

);

}

}

三、最后使用IJKVideoPlayer的IJKVideoPage页面

这里我创建了一个IJKVideoPage来使用IJKVideoPlayer视频播放,IJKVideoPlayer中需要path与videoPlayerController

IJKVideoPage完整代码如下

import 'dart:async';

import 'package:flutter/material.dart';

import 'ijk_player/ijk_video_player.dart';

import 'ijk_player/ijk_video_player_controller.dart';

class IJKVideoPage extends StatefulWidget {

const IJKVideoPage({

super.key,

required this.url,

});

final String url;

@override

State createState() => _IJKVideoPageState();

}

class _IJKVideoPageState extends State {

final IJKVideoPlayerController videoPlayerController =

IJKVideoPlayerController();

@override

void initState() {

super.initState();

}

@override

Widget build(BuildContext context) {

Size size = MediaQuery.of(context).size;

return Scaffold(

appBar: AppBar(title: Text("Fijkplayer Example")),

body: Center(

child: Container(

width: size.width,

height: size.width * 9.0 / 16.0,

alignment: Alignment.center,

child: IJKVideoPlayer(

path: widget.url,

videoPlayerController: videoPlayerController,

color: Colors.black,

),

),

),

);

}

@override

void dispose() {

super.dispose();

}

}

如果外面的页面跳转到播放页面,需要设置url

void testIJKVideoPage(BuildContext context) {

Navigator.of(context)

.push(MaterialPageRoute(builder: (BuildContext context) {

return IJKVideoPage(

url: "https://vd2.bdstatic.com/mda-maif0tt1rirqp27q/540p/h264_cae/1611052585/mda-maif0tt1rirqp27q.mp4");

}));

}

https://brucegwo.blog.csdn.net/article/details/136024588

四、小结

flutter开发实战-ijkplayer视频播放器功能

学习记录,每天不停进步。

推荐链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。