福州建设厅官方网站,网络营销案例分析实验报告,广告制作公司经营范围有哪些,wordpress怎么去掉顶栏前言由于Flutter项目中需要使用到播放器功能#xff0c;因此对flutter中各种播放器解决方案进行了一番研究和比对#xff0c;最后决定还是自己通过Plugin的方法去引用原生播放器符合自己的需求#xff0c;本篇文章会对各种解决方案做一个简单的比较#xff0c;以及讲解一下…
前言由于Flutter项目中需要使用到播放器功能因此对flutter中各种播放器解决方案进行了一番研究和比对最后决定还是自己通过Plugin的方法去引用原生播放器符合自己的需求本篇文章会对各种解决方案做一个简单的比较以及讲解一下发Flutter3.0中ios引用原生view的步骤和逻辑方便大家遇到相同问题时可以进行一个参考。 video_playervideo_playerflutter官方出品。
优点集成速度快易上手使用简单同时支持Androidiosweb 缺点ui丑功能简单可定制化差不支持rtmp等协议直播 适用对播放器要求不高不需要直播只要视频能播放出来就可以的用户 better_playerbetter_player国外大神在video_player的基础之上二次开发而来对video_player进行了各种优化并添加了非常多的实用功能。
优点同video_player且比video_player更强大一点 缺点依然是可定制化差不支持rtmp等直播格式 适用对视频播放器稍微有要求比如视频格式播放速度缓存等功能又不想自己动手去写原生插件对ui定制化要求也不高的用户 fijkplayerfijkplayer基于ijkplayer是对 ijkplayer 的 Flutter 封装支持安卓和ios
优缺点做过安卓和ios原生的对ijk应该都非常熟悉了这里就不做过多说明了 fijkplayer支持各种视频格式包括rtmp等协议直播支持各种常用功能支持定制化UI具体可以去它的官网查看文档说明 适用对播放器要求稍高需要简单定制化播放器ui需要支持直播的用户
这里说一些个人的使用体验因为我的项目需要支持rtmp直播并且对ui定制化要求较高所以放弃了官方的video_player优先选择了fijkplayer但实际体验上不太尽如人意。虽然支持ui定制但想要达到自己产品的ui设计还需要费上很大一番功夫。并且该项目作者有一年多未更新维护了后续是否会继续更新维护解决bug不得而知。另外我在播放rtmp时播放失败不知何故不过我并没深究原因因为此时我已经动了自己动手引用双端原生播放器的念头了 IOS制作并引用原生View步骤1使用Xcode运行项目
双击flutter项目/ios目录下的Runner.xcworkspaceXcode会自动打开项目
2检验项目
直接运行查看是否有报错信息
如果你已经在pubspec.yaml中使用了大量的第三方插件此时运行可能会报错xxx Module not Found那么你需要在打开终端并cd到ios目录执行 pod install 一般情况下都可以解决问题
3创建VideoViewPlugin实现FlutterPlugin协议
import Flutter
import UIKitclass VideoViewPlugin: NSObject, FlutterPlugin {objc static func register(with registrar: FlutterPluginRegistrar) {registrar.register(VideoViewFactory(registrar: registrar), withId: plugins.my_video_player/view)}}由于FlutterPlugin是OC写的所以在Swift中实现OC协议前面需要加上NSObject。通过FlutterPluginRegistrar的registrar注册PlatformViewFactory。
plugins.my_video_player/view即为该插件的id在flutter中引用原生view时需要写入并且安卓ios和flutter三方都要保持一致
4创建VideoViewFactory实现FlutterPlatformViewFactory协议
import UIKit
import Flutterclass VideoViewFactory:NSObject, FlutterPlatformViewFactory {private var registrar:FlutterPluginRegistrarinit(registrar: FlutterPluginRegistrar) {self.registrarregistrarsuper.init()}//create方法是在flutter中该widget加载显示出来时才执行//所以自定flutter中使用原生View时传递的参数在create执行后且获取到参数后再创建channelfunc create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) - FlutterPlatformView {let idargs as! Intreturn VideoViewPlayer(id: id, registrar: registrar)}//这个方法一定要写否则接受不到flutter的传参func createArgsCodec() - FlutterMessageCodec NSObjectProtocol {return FlutterStandardMessageCodec.sharedInstance()}
}注意createArgsCodec()一定要重写否则无法接收到flutter在使用view时传过来的参数create方法中的arguments即为传递的参数create方法返回PlatformViewUIView
5创建VideoViewPlayer实现FlutterPlatformView协议
import UIKit
import Flutterclass VideoViewPlayer: NSObject,FlutterPlatformView {//懒加载private var videoView:UIView{let videoViewUIView()return videoView}()//主要就是在这里返回原生viewfunc view() - UIView {return videoView}}6在AppDelegate.swift中注册插件
import UIKit
import FlutterUIApplicationMain
objc class AppDelegate: FlutterAppDelegate {override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - Bool {GeneratedPluginRegistrant.register(with: self)//插件名自定义if let registerself.registrar(forPlugin: VideoViewPlugin){VideoViewPlugin.register(with: register)}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}
}
这里其实需要两步
let registerself.registrar(forPlugin: VideoViewPlugin)
VideoViewPlugin.register(with: register)注意registrar和register是不一样的
7在flutter中引用VideoView
自定义WidgetMyVideoPlayer
import package:flutter/foundation.dart;
import package:flutter/material.dart;
import package:flutter/services.dart;class MyVideoPlayer extends StatefulWidget {final int id;const MyVideoPlayer({super.key,required this.id,});overrideStateStatefulWidget createState() _VideoPlayerState();
}class _VideoPlayerState extends StateMyVideoPlayer {overridevoid initState() {super.initState();}overrideWidget build(BuildContext context) {if (defaultTargetPlatform TargetPlatform.android) {return AndroidView(viewType: plugins.my_video_player/view,creationParams: widget.id, //传递到原生插件的参数任意类型creationParamsCodec: const StandardMessageCodec(),);} else {return UiKitView(viewType: plugins.my_video_player/view,creationParams: widget.id, //传递到原生插件的参数任意类型creationParamsCodec: const StandardMessageCodec(),);}}}
viewType即上面VideoViewPlugin类中注册的插件idplugins.my_video_player/view安卓ios和flutter保持一致
引用MyVideoPlayer
SizedBox(height: 200,child: MyVideoPlayer(id: 0))这里可以看到我给原生View传了一个id需要唯一我用的是时间戳的参数这个是为了后面在创建MethodChannel时加以区分在同时使用了多个MyVideoPlayer时不会相互影响
此时我们就可以来测试一下引用是否成功了为了效果明显我们可以在VideoViewPlayer.swift中为UIView添加一个背景色 private var videoView:UIView{let videoViewUIView()videoView.backgroundColorUIColor.redreturn videoView}()运行效果 如上图说明原生View已经引用成功了不过本篇文章的目的不只是讲解如何引用原生View我们的目标是如何引用原生播放器
如果你本身是ios开发者或者开发过ios项目并且在所开发的项目中使用过第三方的播放器那么你其实可以直接将你所使用的播放器库引入到本项目中直接使用无非就是在VideoViewPlayer.swift中初始化并配置一些参数最后封装进返回的videoView中
如果你未开发过ios项目或者没使用过第三方的播放器那么你就可以在github上自行搜索star数量高的ios开源播放器项目了。由于我使用的是Swift语言所以我优先查找了用Swift写的播放器但查找后发现star量都太低了而且还必须要支持rtmp播放最后还是选择了OC写的star量最高的ZFPlayer 引入开源播放器ZFPlayer1安装Podfile中添加 pod ZFPlayer, ~ 4.0pod ZFPlayer/ControlView, ~ 4.0pod ZFPlayer/AVPlayer, ~ 4.0pod ZFPlayer/ijkplayer, ~ 4.0由于AVPlayer本身不支持直播所以还需要引入ZFPlayer/ijkplayer来支持直播功能 执行pod intall由于github总是间歇性无法访问如果提示SSL超时等问题可以多试几遍
2参考ZFPlayer文档将ZFPlayer封装进UIViewviewPlayer返回给Flutter
VideoViewPlayer中 //zfplayer控制器private var player:ZFPlayerController ZFPlayerController()//播放器进度控制条UIViewprivate var playerControlViewZFPlayerControlView()//AVPlayer管理器private var avPlayerManager:ZFAVPlayerManager?//IjkPlayer管理器private var ijkPlayerManager:ZFIJKPlayerManager?init(id:Int,registrar:FlutterPluginRegistrar) {super.init()//为player设置进度控制条player.controlViewplayerControlView//为player设置containerViewplayer.containerViewvideoView}此时其实就可以播放视频了传入视频Url
//可以根据视频类型选择使用AVPlayer还是IjkPlayer播放
//我这里点播使用AVPlayer直播使用IjkPlayer自己可以根据自身项目情况选择
if url.starts(with: http){avPlayerManagerZFAVPlayerManager()avPlayerManager!.assetURLURL(string: url)player.currentPlayerManageravPlayerManager!playerControlView.portraitControlView.slider.isHiddenfalseplayerControlView.portraitControlView.currentTimeLabel.isHiddenfalseplayerControlView.portraitControlView.totalTimeLabel.isHiddenfalse}if url.starts(with: rtmp){ijkPlayerManagerZFIJKPlayerManager()ijkPlayerManager!.assetURLURL(string: url)player.currentPlayerManagerijkPlayerManager!playerControlView.portraitControlView.slider.isHiddentrueplayerControlView.portraitControlView.currentTimeLabel.isHiddentrueplayerControlView.portraitControlView.totalTimeLabel.isHiddentrue}if avPlayerManager ! nil {avPlayerManager!.play()}if ijkPlayerManager ! nil {ijkPlayerManager!.play()}但这样无法动态从flutter传入url啊所以我们还需要flutter和ios通信即使用MethodChannel
3IOS端创建FlutterMethodChannel //这里就用到了从flutter传入的idfunc initMethodChannel(id:Int,registrar:FlutterPluginRegistrar) {let channel FlutterMethodChannel(name: my_video_player_\(id), binaryMessenger: registrar.messenger())channel.setMethodCallHandler(handleMethod)}//接收flutter发来的消息func handleMethod(call: FlutterMethodCall, result: FlutterResult){switch call.method {case setUrl:let url: Stringcall.arguments as! StringinitPlayer(url: url)breakcase start:play()breakcase pause:pause()breakcase release:stop()breakdefault:result(FlutterMethodNotImplemented)break}}VideoViewPlayer完整代码如下
import UIKit
import Flutter
import ZFPlayerclass VideoViewPlayer: NSObject,FlutterPlatformView {private var videoView:UIView{let videoViewUIView()return videoView}()private var player:ZFPlayerController ZFPlayerController()private var playerControlViewZFPlayerControlView()private var avPlayerManager:ZFAVPlayerManager?private var ijkPlayerManager:ZFIJKPlayerManager?init(id:Int,registrar:FlutterPluginRegistrar) {super.init()player.controlViewplayerControlViewinitMethodChannel(id: id, registrar: registrar)}func view() - UIView {return videoView}}// MARK:- 播放器初始化及控制
extension VideoViewPlayer{//初始化播放器func initPlayer(url:String){player.containerViewvideoViewif avPlayerManager ! nil {avPlayerManager!.pause()avPlayerManagernil}if ijkPlayerManager ! nil {ijkPlayerManager!.pause()ijkPlayerManagernil}if url.starts(with: http){avPlayerManagerZFAVPlayerManager()avPlayerManager!.assetURLURL(string: url)player.currentPlayerManageravPlayerManager!playerControlView.portraitControlView.slider.isHiddenfalseplayerControlView.portraitControlView.currentTimeLabel.isHiddenfalseplayerControlView.portraitControlView.totalTimeLabel.isHiddenfalse}if url.starts(with: rtmp){ijkPlayerManagerZFIJKPlayerManager()ijkPlayerManager!.assetURLURL(string: url)player.currentPlayerManagerijkPlayerManager!playerControlView.portraitControlView.slider.isHiddentrueplayerControlView.portraitControlView.currentTimeLabel.isHiddentrueplayerControlView.portraitControlView.totalTimeLabel.isHiddentrue}}func play(){if avPlayerManager ! nil {avPlayerManager!.play()}if ijkPlayerManager ! nil {ijkPlayerManager!.play()}}func pause(){if avPlayerManager ! nil {avPlayerManager!.pause()}if ijkPlayerManager ! nil {ijkPlayerManager!.pause()}}func stop(){pause()player.stop()}
}// MARK:- flutter消息通道处理
extension VideoViewPlayer{func initMethodChannel(id:Int,registrar:FlutterPluginRegistrar) {let channel FlutterMethodChannel(name: my_video_player_\(id), binaryMessenger: registrar.messenger())channel.setMethodCallHandler(handleMethod)}//接收flutter发来的消息func handleMethod(call: FlutterMethodCall, result: FlutterResult){switch call.method {case setUrl:let url: Stringcall.arguments as! StringinitPlayer(url: url)breakcase start:play()breakcase pause:pause()breakcase release:stop()breakdefault:result(FlutterMethodNotImplemented)break}}
}4flutter端创建MethodChannel init() {methodChannel MethodChannel(my_video_player_$id);methodChannel.setMethodCallHandler((call) flutterMethod(call));}Futurevoid setUrl(String url) async {return methodChannel.invokeMethod(setUrl, url);}Futurevoid start() async {return methodChannel.invokeMethod(start);}...flutter中调用setUrl后再调用start方法即可播放了
我这里给flutter端的MethodChanel封装为了一个Controller
import package:flutter/services.dart;
import package:kjjl_flutter/components/loading.dart;
import package:kjjl_flutter/utils/log_util.dart;class VideoViewController {late MethodChannel methodChannel;int id;VideoViewController({required this.id});init() {methodChannel MethodChannel(my_video_player_$id);methodChannel.setMethodCallHandler((call) flutterMethod(call));}Futurevoid setUrl(String url) async {return methodChannel.invokeMethod(setUrl, url);}Futurevoid start() async {return methodChannel.invokeMethod(start);}Futurevoid pause() async {return methodChannel.invokeMethod(pause);}Futurevoid release() async {return methodChannel.invokeMethod(release);}Futurevoid stopFullScreen() async {return methodChannel.invokeMethod(stopFullScreen);}Futuredynamic flutterMethod(MethodCall methodCall) async {switch (methodCall.method) {}}
}
在State中使用时的部分代码
SizedBox(height: videoHeight,child: currentVideo ! null videoViewController ! null? MyVideoPlayer(id: videoViewController!.id): SizedBox(),)...VideoModel? currentVideo;
VideoViewController? videoViewController;//页面销毁时释放播放器
override
void dispose() {if (videoViewController ! null) videoViewController!.release();super.dispose();
}//点击播放时执行
play(VideoModel video) {if (videoViewController ! null) {if (currentVideo ! null currentVideo!.id video.id) return;setState(() {currentVideo video;videoViewController!.release();startPlay(video);});} else {setState(() {currentVideo video;videoViewController VideoViewController(id: DateTime.now().millisecondsSinceEpoch);videoViewController!.init();//延时500ms执行是为了防止播放器还未初始化完成就调用了播放导致首次播放失败Future.delayed(const Duration(milliseconds: 500), () {startPlay(video);});});}}startPlay(VideoModel video){videoViewController!.setUrl(video.mobileUrl);videoViewController!.start();
}效果图
直播 录播 下一篇Flutter3引用原生播放器-Android篇