免费网站空间有什么用,软件开发工程师,专门做问卷调查的一个网站,wordpress 菜单 移动端flutter开发实战-ijkplayer视频播放器功能
使用better_player播放器进行播放视频时候#xff0c;在Android上会出现解码失败的问题#xff0c;better_player使用的是video_player#xff0c;video_player很多视频无法解码。最终采用ijkplayer播放器插件#xff0c;在flutt…flutter开发实战-ijkplayer视频播放器功能
使用better_player播放器进行播放视频时候在Android上会出现解码失败的问题better_player使用的是video_playervideo_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);}}
获取是否播放中 Futurebool? 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、startfplayer.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;overrideStateIJKVideoPanel createState() _IJKVideoPanelState();
}class _IJKVideoPanelState extends StateIJKVideoPanel {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;overridevoid 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();}}overridevoid 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: Widget[_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();
// },
// )//],),),);}overrideWidget 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: Widget[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;overrideStateIJKVideoPlayer createState() _IJKVideoPlayerState();
}class _IJKVideoPlayerState extends StateIJKVideoPlayer {final FijkPlayer player FijkPlayer();overridevoid 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) {// 触发setLoopif (loopCount 0) {loopCount 1;}player.setLoop(loopCount);};widget.videoPlayerController!.seekTo (int msec) {// 触发seekif (msec 0) {msec 0;}player.seekTo(msec);};widget.videoPlayerController!.setVolume (double volume) {// 触发setVolumeif (volume 0.0) {volume 0.0;}player.setVolume(volume);};widget.videoPlayerController!.setSpeed (double speed) {// 触发setSpeedif (speed 0.0) {speed 1.0;}player.setSpeed(speed);};widget.videoPlayerController!.isPlaying () {// 触发setVolumeif (FijkState.started player.state) {return true;} else {return false;}};}}overridevoid dispose() {super.dispose();player.release();}void onIJKDispose(FijkData fijkData) {}overrideWidget 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;overrideStateIJKVideoPage createState() _IJKVideoPageState();
}class _IJKVideoPageState extends StateIJKVideoPage {final IJKVideoPlayerController videoPlayerController IJKVideoPlayerController();overridevoid initState() {super.initState();}overrideWidget 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,),),),);}overridevoid 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视频播放器功能
学习记录每天不停进步。