├── .gitignore ├── .idea ├── .gitignore ├── libraries │ ├── Dart_SDK.xml │ └── Flutter_Plugins.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── example_lib_main_dart.xml └── vcs.xml ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── icu │ └── bughub │ └── plugins │ └── video_player │ └── flt_video_player │ ├── Constants.kt │ ├── FltBasePlayer.kt │ ├── FltVideoPlayerPlugin.kt │ ├── FltVideoView.kt │ ├── FltVodPlayer.kt │ └── PlayerEventSink.kt ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── icu │ │ │ │ │ └── bughub │ │ │ │ │ └── plugins │ │ │ │ │ └── video_player │ │ │ │ │ └── flt_video_player_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── define │ │ └── function_define.dart │ ├── extensions │ │ └── extension.dart │ ├── full │ │ ├── full_player.dart │ │ ├── full_player_demo.dart │ │ ├── full_screen_button.dart │ │ ├── mute_button.dart │ │ ├── play_button.dart │ │ ├── player_control_overlay.dart │ │ └── rate_button.dart │ ├── main.dart │ ├── platform_view_demo.dart │ └── simple_demo.dart ├── pubspec.lock ├── pubspec.yaml ├── resources │ ├── arrow.down.right.and.arrow.up.left.circle.png │ ├── arrow.up.backward.and.arrow.down.forward.circle.png │ ├── pause_circle.png │ ├── play_circle.png │ ├── speaker.circle.png │ └── speaker.slash.circle.png └── test │ └── widget_test.dart ├── flt_video_player.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── EventSinkQueue.h │ ├── EventSinkQueue.m │ ├── FltBasePlayer.h │ ├── FltBasePlayer.m │ ├── FltVideoPlayerPlugin.h │ ├── FltVideoPlayerPlugin.m │ ├── FltVideoView.h │ ├── FltVideoView.m │ ├── FltVideoViewFactory.h │ ├── FltVideoViewFactory.m │ ├── FltVodPlayer.h │ └── FltVodPlayer.m └── flt_video_player.podspec ├── lib ├── flt_video_player.dart └── src │ ├── plugin.dart │ └── vod │ ├── platform_video_view.dart │ ├── player_config.dart │ ├── player_define.dart │ ├── vod_player.dart │ └── vodplayer_controller.dart ├── pubspec.lock ├── pubspec.yaml └── test └── flt_video_player_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .idea/Dart_SDK.xml 10 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flt_video_player 2 | 3 | 基于腾讯云原生播放器封装的 Flutter 版本。 4 | 使用本播放器前建议查看腾讯云[官方文档](https://cloud.tencent.com/document/product/881/20191) 5 | 6 | ## 安装 7 | 8 | ```yaml 9 | //import 10 | dependencies: 11 | flt_video_player: 12 | git: 13 | url: https://github.com/RandyWei/flt_video_player.git 14 | ``` 15 | ## Android 16 | 17 | 在 app/build.gradle 的 defaultConfig 中添加配置 18 | ```groovy 19 | ndk { 20 | abiFilters "armeabi", "armeabi-v7a", "arm64-v8a" 21 | } 22 | ``` 23 | 在 android/app/src/main/AndroidManifest.xml 中增加如下配置 24 | ```xml 25 | 29 | 30 | 32 | ... 33 | 34 | ... 35 | _SimpleDemoState(); 53 | } 54 | 55 | class _SimpleDemoState extends State { 56 | late VodPlayerController controller; 57 | double _aspectRation = 16 / 9; 58 | 59 | @override 60 | void initState() { 61 | super.initState(); 62 | 63 | var playerConfig = PlayerConfig(); 64 | 65 | //可以通过renderType来配置使用外接纹理方式还是 PlatformView 方式对接,经实测同一视频在安卓机型中两者在内存占用上区别不大,iOS 环境下外接纹理稍高 66 | controller = VodPlayerController(config: playerConfig); 67 | 68 | //监听播放状态 69 | controller.playState.listen((event) { 70 | debugPrint("playerState:$event"); 71 | }); 72 | 73 | controller.onPlayerEvent.listen((event) { 74 | debugPrint("PlayerEvent:$event"); 75 | }); 76 | 77 | controller.onNetEvent.listen((event) { 78 | //获取视频宽度高度 79 | double w = (event["VIDEO_WIDTH"]).toDouble(); 80 | double h = (event["VIDEO_HEIGHT"]).toDouble(); 81 | 82 | //计算比例 83 | if (w > 0 && h > 0) { 84 | _aspectRation = 1.0 * w / h; 85 | setState(() {}); 86 | } 87 | }); 88 | 89 | controller.initialize(); 90 | } 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | return Scaffold( 95 | appBar: AppBar( 96 | title: const Text('Plugin example app'), 97 | ), 98 | body: Center( 99 | child: AspectRatio( 100 | aspectRatio: _aspectRation, 101 | child: VodPlayer( 102 | controller: controller, 103 | ), 104 | ), 105 | ), 106 | floatingActionButton: FloatingActionButton( 107 | onPressed: () { 108 | controller.play( 109 | "https://stream7.iqilu.com/10339/article/202002/18/2fca1c77730e54c7b500573c2437003f.mp4"); 110 | }, 111 | ), 112 | ); 113 | } 114 | 115 | @override 116 | void dispose() { 117 | controller.dispose(); 118 | super.dispose(); 119 | } 120 | } 121 | ``` 122 | 123 | 更多示例,可以查看 example 示例 124 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'icu.bughub.plugins.video_player.flt_video_player' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.6.21' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:4.1.0' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 30 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | minSdkVersion 16 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 50 | implementation 'com.tencent.liteav:LiteAVSDK_Player:9.5.29008' 51 | } 52 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flt_video_player' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/icu/bughub/plugins/video_player/flt_video_player/Constants.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.plugins.video_player.flt_video_player 2 | 3 | object Constants { 4 | val CHANNEL_PREFIX = "plugins.bughub.icu" 5 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/icu/bughub/plugins/video_player/flt_video_player/FltBasePlayer.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.plugins.video_player.flt_video_player 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import com.tencent.rtmp.ITXVodPlayListener 6 | import com.tencent.rtmp.TXVodPlayConfig 7 | import com.tencent.rtmp.TXVodPlayer 8 | import io.flutter.embedding.engine.plugins.FlutterPlugin 9 | import io.flutter.plugin.common.EventChannel 10 | import io.flutter.plugin.common.MethodCall 11 | import io.flutter.plugin.common.MethodChannel 12 | import java.util.concurrent.atomic.AtomicInteger 13 | import kotlin.math.max 14 | import kotlin.math.min 15 | 16 | open class FltBasePlayer(private val flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) : 17 | MethodChannel.MethodCallHandler, ITXVodPlayListener { 18 | private val mAtomicId = AtomicInteger(0) 19 | private var mPlayerId: Int = -1 20 | 21 | private val uninitialized = -1 22 | protected var textureId: Long = -1 23 | 24 | protected var methodChannel: MethodChannel? = null 25 | protected var eventChannel: EventChannel? = null 26 | protected var netChannel: EventChannel? = null 27 | 28 | protected var eventSink: PlayerEventSink = PlayerEventSink() 29 | protected var netEventSink: PlayerEventSink = PlayerEventSink() 30 | protected var vodPlayer: TXVodPlayer? = null 31 | 32 | init { 33 | this.mPlayerId = mAtomicId.incrementAndGet() 34 | } 35 | 36 | open fun getPlayerId(): Int { 37 | return mPlayerId 38 | } 39 | 40 | /** 41 | * 初始化播放器 42 | * 43 | * @return texture id 44 | */ 45 | protected open fun initPlayer(playConfig: TXVodPlayConfig) { 46 | if (vodPlayer == null) { 47 | vodPlayer = TXVodPlayer(flutterPluginBinding.applicationContext) 48 | 49 | vodPlayer?.setConfig(playConfig) 50 | vodPlayer?.enableHardwareDecode(true) 51 | vodPlayer?.setVodListener(this) 52 | } 53 | } 54 | 55 | 56 | private fun stopPlay(isNeedClearLastImg: Boolean): Int? { 57 | return vodPlayer?.stopPlay(isNeedClearLastImg) 58 | } 59 | 60 | 61 | private fun startPlay(url: String): Int { 62 | return vodPlayer?.startPlay(url) ?: uninitialized 63 | } 64 | 65 | 66 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { 67 | 68 | when (call.method) { 69 | "init" -> { 70 | 71 | val playConfig = TXVodPlayConfig() 72 | 73 | playConfig.setConnectRetryCount(call.argument("connectRetryCount") ?: 3) 74 | playConfig.setConnectRetryInterval(call.argument("connectRetryInterval") ?: 3) 75 | playConfig.setTimeout(call.argument("timeout") ?: 10) 76 | playConfig.setFirstStartPlayBufferTime( 77 | call.argument("firstStartPlayBufferTime") ?: 100 78 | ) 79 | playConfig.setNextStartPlayBufferTime( 80 | call.argument("nextStartPlayBufferTime") ?: 250 81 | ) 82 | 83 | playConfig.setCacheFolderPath(call.argument("cacheFolderPath")) 84 | playConfig.setMaxCacheItems(call.argument("maxCacheItems") ?: 0) 85 | playConfig.setHeaders(call.argument("headers")) 86 | //< 是否精确 seek,默认YES。开启精确后seek,seek 的时间平均多出200ms 87 | playConfig.setEnableAccurateSeek( 88 | call.argument("enableAccurateSeek") ?: false 89 | ) 90 | playConfig.setProgressInterval( 91 | (call.argument("progressInterval")?.toInt() ?: 0) 92 | ) 93 | 94 | playConfig.setMaxBufferSize(call.argument("maxBufferSize") ?: 0) 95 | 96 | playConfig.setOverlayKey(call.argument("overlayKey")) 97 | playConfig.setOverlayIv(call.argument("overlayIv")) 98 | 99 | 100 | initPlayer(playConfig) 101 | 102 | result.success(textureId) 103 | } 104 | 105 | "play" -> { 106 | val url = call.argument("url") 107 | if (url?.isNotEmpty() == true) { 108 | val r = startPlay(url) 109 | result.success(r) 110 | } else { 111 | result.error("404", "url为空", "url为空") 112 | } 113 | } 114 | 115 | "stop" -> { 116 | val isNeedClearLastImg = call.argument("isNeedClearLastImg") ?: false 117 | val r: Int? = stopPlay(isNeedClearLastImg) 118 | result.success(r) 119 | } 120 | 121 | 122 | "isPlaying" -> { 123 | result.success(vodPlayer?.isPlaying ?: false) 124 | } 125 | 126 | "pause" -> { 127 | vodPlayer?.pause() 128 | result.success(null) 129 | } 130 | 131 | "resume" -> { 132 | vodPlayer?.resume() 133 | result.success(null) 134 | } 135 | 136 | "seek" -> { 137 | val time = call.argument("time") 138 | if (time != null) { 139 | vodPlayer?.seek(time) 140 | } 141 | result.success(null) 142 | } 143 | 144 | "setStartTime" -> { 145 | val time = call.argument("time")?.toFloat() 146 | if (time != null) { 147 | vodPlayer?.setStartTime(time) 148 | } 149 | result.success(null) 150 | } 151 | 152 | "currentPlaybackTime" -> { 153 | result.success(vodPlayer?.currentPlaybackTime) 154 | } 155 | 156 | "duration" -> result.success(vodPlayer?.duration) 157 | 158 | "playableDuration" -> result.success(vodPlayer?.playableDuration) 159 | 160 | "setMute" -> { 161 | val enable = call.argument("enable") ?: false 162 | vodPlayer?.setMute(enable) 163 | result.success(null) 164 | } 165 | 166 | "setAudioPlayoutVolume" -> { 167 | var volume = call.argument("volume") ?: 0 168 | volume = max(0, volume) 169 | volume = min(100, volume) 170 | vodPlayer?.setAudioPlayoutVolume(volume) 171 | result.success(null) 172 | } 173 | 174 | "setRate" -> { 175 | val rate = (call.argument("rate") ?: 1.0).toFloat() 176 | vodPlayer?.setRate(rate) 177 | result.success(null) 178 | } 179 | 180 | "setMirror" -> { 181 | val mirror = call.argument("mirror") ?: false 182 | vodPlayer?.setMirror(mirror) 183 | result.success(null) 184 | } 185 | 186 | "setLoop" -> { 187 | val loop = call.argument("loop") ?: false 188 | vodPlayer?.isLoop = loop 189 | result.success(null) 190 | } 191 | 192 | "setRenderRotation" -> { 193 | val rotation = call.argument("rotation") ?: 1 194 | vodPlayer?.setRenderRotation(rotation) 195 | result.success(null) 196 | } 197 | } 198 | } 199 | 200 | 201 | private fun getParams(event: Int, bundle: Bundle?): Map { 202 | val param = HashMap() 203 | 204 | if (event != 0) { 205 | param["event"] = event 206 | } 207 | 208 | if (bundle?.isEmpty == false) { 209 | val keySet = bundle.keySet() 210 | for (key in keySet) { 211 | val value = bundle.get(key) 212 | param[key] = value 213 | } 214 | } 215 | 216 | return param 217 | } 218 | 219 | 220 | override fun onPlayEvent(player: TXVodPlayer?, i: Int, bundle: Bundle?) { 221 | eventSink.success(getParams(i, bundle)) 222 | } 223 | 224 | override fun onNetStatus(player: TXVodPlayer?, bundle: Bundle?) { 225 | netEventSink.success(getParams(0, bundle)) 226 | } 227 | 228 | open fun destroy() { 229 | stopPlay(false) 230 | vodPlayer = null 231 | } 232 | 233 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/icu/bughub/plugins/video_player/flt_video_player/FltVideoPlayerPlugin.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.plugins.video_player.flt_video_player 2 | 3 | import android.util.SparseArray 4 | import androidx.annotation.NonNull 5 | import icu.bughub.plugins.video_player.flt_video_player.Constants.CHANNEL_PREFIX 6 | 7 | import io.flutter.embedding.engine.plugins.FlutterPlugin 8 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 9 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 10 | import io.flutter.plugin.common.MethodCall 11 | import io.flutter.plugin.common.MethodChannel 12 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 13 | import io.flutter.plugin.common.MethodChannel.Result 14 | 15 | /** FltVideoPlayerPlugin */ 16 | class FltVideoPlayerPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { 17 | 18 | 19 | /// The MethodChannel that will the communication between Flutter and native Android 20 | /// 21 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 22 | /// when the Flutter Engine is detached from the Activity 23 | private lateinit var channel: MethodChannel 24 | 25 | //存储播放器 26 | private val players = SparseArray() 27 | 28 | private var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding? = null 29 | 30 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 31 | this.flutterPluginBinding = flutterPluginBinding 32 | channel = MethodChannel( 33 | flutterPluginBinding.binaryMessenger, 34 | "${CHANNEL_PREFIX}/flt_video_player" 35 | ) 36 | channel.setMethodCallHandler(this) 37 | flutterPluginBinding.platformViewRegistry.registerViewFactory( 38 | "FltVideoView", 39 | FltVideoViewFactory(flutterPluginBinding) { viewId: Int, view: FltVideoView -> 40 | players.append(viewId, view) 41 | } 42 | ) 43 | } 44 | 45 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 46 | when (call.method) { 47 | "getPlatformVersion" -> { 48 | result.success("Android ${android.os.Build.VERSION.RELEASE}") 49 | } 50 | "createVodPlayer" -> { 51 | val vodPlayer = flutterPluginBinding?.let { FltVodPlayer(it) } 52 | val playerId = vodPlayer?.getPlayerId() ?: -1 53 | players.append(playerId, vodPlayer) 54 | result.success(playerId) 55 | } 56 | "releaseVodPlayer" -> { 57 | val playerId = call.argument("playerId") ?: -1 58 | val player = players[playerId] 59 | player.destroy() 60 | players.remove(playerId) 61 | } 62 | else -> { 63 | result.notImplemented() 64 | } 65 | } 66 | } 67 | 68 | 69 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 70 | channel.setMethodCallHandler(null) 71 | } 72 | 73 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 74 | 75 | } 76 | 77 | override fun onDetachedFromActivityForConfigChanges() { 78 | 79 | } 80 | 81 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 82 | 83 | } 84 | 85 | override fun onDetachedFromActivity() { 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /android/src/main/kotlin/icu/bughub/plugins/video_player/flt_video_player/FltVideoView.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.plugins.video_player.flt_video_player 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import com.tencent.rtmp.TXVodPlayConfig 6 | import com.tencent.rtmp.ui.TXCloudVideoView 7 | import io.flutter.embedding.engine.plugins.FlutterPlugin 8 | import io.flutter.plugin.common.EventChannel 9 | import io.flutter.plugin.common.MethodChannel 10 | import io.flutter.plugin.common.StandardMessageCodec 11 | import io.flutter.plugin.platform.PlatformView 12 | import io.flutter.plugin.platform.PlatformViewFactory 13 | 14 | class FltVideoView( 15 | flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, 16 | viewId: Int 17 | ) : PlatformView, FltBasePlayer(flutterPluginBinding) { 18 | 19 | private var container: TXCloudVideoView = 20 | TXCloudVideoView(flutterPluginBinding.applicationContext) 21 | 22 | init { 23 | methodChannel = MethodChannel( 24 | flutterPluginBinding.binaryMessenger, 25 | "${Constants.CHANNEL_PREFIX}/vodplayer/${viewId}" 26 | ) 27 | methodChannel?.setMethodCallHandler(this) 28 | 29 | //注册通信通道 30 | eventChannel = EventChannel( 31 | flutterPluginBinding.binaryMessenger, 32 | "${Constants.CHANNEL_PREFIX}/vodplayer/event/${viewId}" 33 | ) 34 | eventChannel?.setStreamHandler(object : EventChannel.StreamHandler { 35 | override fun onListen(p0: Any?, p1: EventChannel.EventSink?) { 36 | eventSink.setSinkProxy(p1) 37 | } 38 | 39 | override fun onCancel(p0: Any?) { 40 | eventSink.setSinkProxy(null) 41 | } 42 | }) 43 | 44 | netChannel = EventChannel( 45 | flutterPluginBinding.binaryMessenger, 46 | "${Constants.CHANNEL_PREFIX}/vodplayer/net/${viewId}" 47 | ) 48 | netChannel?.setStreamHandler(object : EventChannel.StreamHandler { 49 | override fun onListen(p0: Any?, p1: EventChannel.EventSink?) { 50 | netEventSink.setSinkProxy(p1) 51 | } 52 | 53 | override fun onCancel(p0: Any?) { 54 | netEventSink.setSinkProxy(null) 55 | } 56 | }) 57 | } 58 | 59 | override fun initPlayer(playConfig: TXVodPlayConfig) { 60 | super.initPlayer(playConfig) 61 | vodPlayer?.setPlayerView(container) 62 | } 63 | 64 | override fun getView(): View { 65 | return container 66 | } 67 | 68 | override fun dispose() { 69 | } 70 | } 71 | 72 | class FltVideoViewFactory( 73 | private val 74 | flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, 75 | private val onViewCreated: (Int, FltVideoView) -> Unit 76 | ) : PlatformViewFactory( 77 | StandardMessageCodec.INSTANCE 78 | ) { 79 | 80 | override fun create(context: Context?, viewId: Int, args: Any?): PlatformView { 81 | val view = FltVideoView(flutterPluginBinding, viewId) 82 | onViewCreated.invoke(viewId, view) 83 | return view 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/icu/bughub/plugins/video_player/flt_video_player/FltVodPlayer.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.plugins.video_player.flt_video_player 2 | 3 | import android.graphics.SurfaceTexture 4 | import android.view.Surface 5 | import com.tencent.rtmp.TXVodPlayConfig 6 | import io.flutter.embedding.engine.plugins.FlutterPlugin 7 | import io.flutter.plugin.common.EventChannel 8 | import io.flutter.plugin.common.MethodChannel 9 | import io.flutter.view.TextureRegistry 10 | 11 | class FltVodPlayer(private val flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) : 12 | FltBasePlayer(flutterPluginBinding) { 13 | 14 | private var surfaceTextureEntry: TextureRegistry.SurfaceTextureEntry? = null 15 | private var surfaceTexture: SurfaceTexture? = null 16 | private var surface: Surface? = null 17 | 18 | 19 | init { 20 | methodChannel = MethodChannel( 21 | flutterPluginBinding.binaryMessenger, 22 | "${Constants.CHANNEL_PREFIX}/vodplayer/${getPlayerId()}" 23 | ) 24 | methodChannel?.setMethodCallHandler(this) 25 | 26 | //注册通信通道 27 | eventChannel = EventChannel( 28 | flutterPluginBinding.binaryMessenger, 29 | "${Constants.CHANNEL_PREFIX}/vodplayer/event/${getPlayerId()}" 30 | ) 31 | eventChannel?.setStreamHandler(object : EventChannel.StreamHandler { 32 | override fun onListen(p0: Any?, p1: EventChannel.EventSink?) { 33 | eventSink.setSinkProxy(p1) 34 | } 35 | 36 | override fun onCancel(p0: Any?) { 37 | eventSink.setSinkProxy(null) 38 | } 39 | }) 40 | 41 | netChannel = EventChannel( 42 | flutterPluginBinding.binaryMessenger, 43 | "${Constants.CHANNEL_PREFIX}/vodplayer/net/${getPlayerId()}" 44 | ) 45 | netChannel?.setStreamHandler(object : EventChannel.StreamHandler { 46 | override fun onListen(p0: Any?, p1: EventChannel.EventSink?) { 47 | netEventSink.setSinkProxy(p1) 48 | } 49 | 50 | override fun onCancel(p0: Any?) { 51 | netEventSink.setSinkProxy(null) 52 | } 53 | }) 54 | } 55 | 56 | override fun destroy() { 57 | 58 | super.destroy() 59 | 60 | surfaceTextureEntry?.release() 61 | surfaceTextureEntry = null 62 | 63 | surfaceTexture?.release() 64 | surfaceTexture = null 65 | 66 | surface?.release() 67 | surface = null 68 | 69 | 70 | methodChannel?.setMethodCallHandler(null) 71 | methodChannel = null 72 | 73 | eventChannel?.setStreamHandler(null) 74 | eventChannel = null 75 | netChannel?.setStreamHandler(null) 76 | netChannel = null 77 | 78 | 79 | } 80 | 81 | override fun initPlayer(playConfig: TXVodPlayConfig) { 82 | super.initPlayer(playConfig) 83 | setupPlayer() 84 | } 85 | 86 | /** 87 | * 配置播放器 88 | * 89 | */ 90 | private fun setupPlayer() { 91 | surfaceTextureEntry = flutterPluginBinding.textureRegistry.createSurfaceTexture() 92 | surfaceTexture = surfaceTextureEntry?.surfaceTexture() 93 | surface = Surface(surfaceTexture) 94 | 95 | vodPlayer?.setSurface(surface) 96 | 97 | textureId = surfaceTextureEntry?.id() ?: -1 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/icu/bughub/plugins/video_player/flt_video_player/PlayerEventSink.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.plugins.video_player.flt_video_player 2 | 3 | import io.flutter.plugin.common.EventChannel 4 | import java.util.* 5 | 6 | class PlayerEventSink : EventChannel.EventSink { 7 | 8 | private var eventSink: EventChannel.EventSink? = null 9 | 10 | private val eventQueue: Queue = LinkedList() 11 | 12 | private var isEnd = false 13 | 14 | fun setSinkProxy(sink: EventChannel.EventSink?) { 15 | this.eventSink = sink 16 | consume() 17 | } 18 | 19 | private fun enqueue(event: Any?) { 20 | if (isEnd) return 21 | eventQueue.offer(event) 22 | } 23 | 24 | private fun consume() { 25 | while (!eventQueue.isEmpty()) { 26 | when (val event = eventQueue.poll()) { 27 | is EndEvent -> { 28 | eventSink?.endOfStream() 29 | } 30 | is ErrorEvent -> { 31 | val errorEvent: ErrorEvent = event 32 | eventSink?.error(errorEvent.code, errorEvent.message, errorEvent.details) 33 | } 34 | else -> { 35 | eventSink?.success(event) 36 | } 37 | } 38 | } 39 | } 40 | 41 | override fun success(event: Any?) { 42 | enqueue(event) 43 | consume() 44 | } 45 | 46 | override fun error(code: String?, message: String?, details: Any?) { 47 | enqueue(ErrorEvent(code, message, details)) 48 | consume() 49 | } 50 | 51 | override fun endOfStream() { 52 | enqueue(EndEvent()) 53 | consume() 54 | isEnd = true 55 | } 56 | 57 | private class EndEvent 58 | 59 | private data class ErrorEvent(val code: String?, val message: String?, val details: Any?) 60 | } -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 3595343e20a61ff16d14e8ecc25f364276bb1b8b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flt_video_player_example 2 | 3 | Demonstrates how to use the flt_video_player plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "icu.bughub.plugins.video_player.flt_video_player_example" 47 | minSdkVersion 16 48 | targetSdkVersion 30 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | 52 | ndk { 53 | abiFilters "armeabi", "armeabi-v7a", "arm64-v8a" 54 | } 55 | } 56 | 57 | buildTypes { 58 | release { 59 | // TODO: Add your own signing config for the release build. 60 | // Signing with the debug keys for now, so `flutter run --release` works. 61 | signingConfig signingConfigs.debug 62 | } 63 | } 64 | } 65 | 66 | flutter { 67 | source '../..' 68 | } 69 | 70 | dependencies { 71 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 72 | } 73 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 16 | 20 | 23 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/icu/bughub/plugins/video_player/flt_video_player_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.plugins.video_player.flt_video_player_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/central/' } 5 | maven { url 'https://repo1.maven.org/maven2/'} 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:7.0.3' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/central/' } 19 | maven { url 'https://repo1.maven.org/maven2/'} 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | rootProject.buildDir = '../build' 26 | subprojects { 27 | project.buildDir = "${rootProject.buildDir}/${project.name}" 28 | project.evaluationDependsOn(':app') 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - flt_video_player (1.0.0): 3 | - Flutter 4 | - TXLiteAVSDK_Player 5 | - Flutter (1.0.0) 6 | - orientation (0.0.1): 7 | - Flutter 8 | - TXLiteAVSDK_Player (9.2.10637) 9 | 10 | DEPENDENCIES: 11 | - flt_video_player (from `.symlinks/plugins/flt_video_player/ios`) 12 | - Flutter (from `Flutter`) 13 | - orientation (from `.symlinks/plugins/orientation/ios`) 14 | 15 | SPEC REPOS: 16 | trunk: 17 | - TXLiteAVSDK_Player 18 | 19 | EXTERNAL SOURCES: 20 | flt_video_player: 21 | :path: ".symlinks/plugins/flt_video_player/ios" 22 | Flutter: 23 | :path: Flutter 24 | orientation: 25 | :path: ".symlinks/plugins/orientation/ios" 26 | 27 | SPEC CHECKSUMS: 28 | flt_video_player: b22e5e3f4a408e50e5bf28a81f52a891bced35a3 29 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 30 | orientation: 6c9203efe86ce4cff379756910f18b2d745628c3 31 | TXLiteAVSDK_Player: 300e6fc7262ae095ee13b18d7d821c5fae0996f9 32 | 33 | PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d 34 | 35 | COCOAPODS: 1.10.1 36 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 22B0993FE1A8142609529A27 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B5F01449A1B301F58B80EC3 /* libPods-Runner.a */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 14 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 15 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 16 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 17 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXCopyFilesBuildPhase section */ 21 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 22 | isa = PBXCopyFilesBuildPhase; 23 | buildActionMask = 2147483647; 24 | dstPath = ""; 25 | dstSubfolderSpec = 10; 26 | files = ( 27 | ); 28 | name = "Embed Frameworks"; 29 | runOnlyForDeploymentPostprocessing = 0; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 0B5F01449A1B301F58B80EC3 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 36 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 37 | 2F42C48AAC58E6A891FCF93A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 38 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 39 | 5E7C7C8BEA8999B70296E77C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 40 | 712BD8BE1FD6D50F937D6C7F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 41 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 42 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 45 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 46 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 51 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 22B0993FE1A8142609529A27 /* libPods-Runner.a in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 9740EEB11CF90186004384FC /* Flutter */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 70 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 71 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 72 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 73 | ); 74 | name = Flutter; 75 | sourceTree = ""; 76 | }; 77 | 97C146E51CF9000F007C117D = { 78 | isa = PBXGroup; 79 | children = ( 80 | 9740EEB11CF90186004384FC /* Flutter */, 81 | 97C146F01CF9000F007C117D /* Runner */, 82 | 97C146EF1CF9000F007C117D /* Products */, 83 | F65B27B078732A68DA518C24 /* Pods */, 84 | D1D8283238D29F69C25A8514 /* Frameworks */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | 97C146EF1CF9000F007C117D /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 97C146EE1CF9000F007C117D /* Runner.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 97C146F01CF9000F007C117D /* Runner */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 100 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 101 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 102 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 103 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 104 | 97C147021CF9000F007C117D /* Info.plist */, 105 | 97C146F11CF9000F007C117D /* Supporting Files */, 106 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 107 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 108 | ); 109 | path = Runner; 110 | sourceTree = ""; 111 | }; 112 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 97C146F21CF9000F007C117D /* main.m */, 116 | ); 117 | name = "Supporting Files"; 118 | sourceTree = ""; 119 | }; 120 | D1D8283238D29F69C25A8514 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 0B5F01449A1B301F58B80EC3 /* libPods-Runner.a */, 124 | ); 125 | name = Frameworks; 126 | sourceTree = ""; 127 | }; 128 | F65B27B078732A68DA518C24 /* Pods */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 712BD8BE1FD6D50F937D6C7F /* Pods-Runner.debug.xcconfig */, 132 | 2F42C48AAC58E6A891FCF93A /* Pods-Runner.release.xcconfig */, 133 | 5E7C7C8BEA8999B70296E77C /* Pods-Runner.profile.xcconfig */, 134 | ); 135 | path = Pods; 136 | sourceTree = ""; 137 | }; 138 | /* End PBXGroup section */ 139 | 140 | /* Begin PBXNativeTarget section */ 141 | 97C146ED1CF9000F007C117D /* Runner */ = { 142 | isa = PBXNativeTarget; 143 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 144 | buildPhases = ( 145 | 9BBA38C4201FA0D419BA3140 /* [CP] Check Pods Manifest.lock */, 146 | 9740EEB61CF901F6004384FC /* Run Script */, 147 | 97C146EA1CF9000F007C117D /* Sources */, 148 | 97C146EB1CF9000F007C117D /* Frameworks */, 149 | 97C146EC1CF9000F007C117D /* Resources */, 150 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 151 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = Runner; 158 | productName = Runner; 159 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | 97C146E61CF9000F007C117D /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastUpgradeCheck = 1020; 169 | ORGANIZATIONNAME = ""; 170 | TargetAttributes = { 171 | 97C146ED1CF9000F007C117D = { 172 | CreatedOnToolsVersion = 7.3.1; 173 | }; 174 | }; 175 | }; 176 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 177 | compatibilityVersion = "Xcode 9.3"; 178 | developmentRegion = en; 179 | hasScannedForEncodings = 0; 180 | knownRegions = ( 181 | en, 182 | Base, 183 | ); 184 | mainGroup = 97C146E51CF9000F007C117D; 185 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 186 | projectDirPath = ""; 187 | projectRoot = ""; 188 | targets = ( 189 | 97C146ED1CF9000F007C117D /* Runner */, 190 | ); 191 | }; 192 | /* End PBXProject section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | 97C146EC1CF9000F007C117D /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 200 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 201 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 202 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXShellScriptBuildPhase section */ 209 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputPaths = ( 215 | ); 216 | name = "Thin Binary"; 217 | outputPaths = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | shellPath = /bin/sh; 221 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 222 | }; 223 | 9740EEB61CF901F6004384FC /* Run Script */ = { 224 | isa = PBXShellScriptBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | inputPaths = ( 229 | ); 230 | name = "Run Script"; 231 | outputPaths = ( 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | shellPath = /bin/sh; 235 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 236 | }; 237 | 9BBA38C4201FA0D419BA3140 /* [CP] Check Pods Manifest.lock */ = { 238 | isa = PBXShellScriptBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | ); 242 | inputFileListPaths = ( 243 | ); 244 | inputPaths = ( 245 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 246 | "${PODS_ROOT}/Manifest.lock", 247 | ); 248 | name = "[CP] Check Pods Manifest.lock"; 249 | outputFileListPaths = ( 250 | ); 251 | outputPaths = ( 252 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | shellPath = /bin/sh; 256 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 257 | showEnvVarsInLog = 0; 258 | }; 259 | /* End PBXShellScriptBuildPhase section */ 260 | 261 | /* Begin PBXSourcesBuildPhase section */ 262 | 97C146EA1CF9000F007C117D /* Sources */ = { 263 | isa = PBXSourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 267 | 97C146F31CF9000F007C117D /* main.m in Sources */, 268 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | /* End PBXSourcesBuildPhase section */ 273 | 274 | /* Begin PBXVariantGroup section */ 275 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 276 | isa = PBXVariantGroup; 277 | children = ( 278 | 97C146FB1CF9000F007C117D /* Base */, 279 | ); 280 | name = Main.storyboard; 281 | sourceTree = ""; 282 | }; 283 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 284 | isa = PBXVariantGroup; 285 | children = ( 286 | 97C147001CF9000F007C117D /* Base */, 287 | ); 288 | name = LaunchScreen.storyboard; 289 | sourceTree = ""; 290 | }; 291 | /* End PBXVariantGroup section */ 292 | 293 | /* Begin XCBuildConfiguration section */ 294 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_ANALYZER_NONNULL = YES; 299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 300 | CLANG_CXX_LIBRARY = "libc++"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_COMMA = YES; 306 | CLANG_WARN_CONSTANT_CONVERSION = YES; 307 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INFINITE_RECURSION = YES; 312 | CLANG_WARN_INT_CONVERSION = YES; 313 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 315 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 317 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 318 | CLANG_WARN_STRICT_PROTOTYPES = YES; 319 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 323 | COPY_PHASE_STRIP = NO; 324 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 325 | ENABLE_NS_ASSERTIONS = NO; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu99; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | SDKROOT = iphoneos; 338 | SUPPORTED_PLATFORMS = iphoneos; 339 | TARGETED_DEVICE_FAMILY = "1,2"; 340 | VALIDATE_PRODUCT = YES; 341 | }; 342 | name = Profile; 343 | }; 344 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 345 | isa = XCBuildConfiguration; 346 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 347 | buildSettings = { 348 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 349 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 350 | DEVELOPMENT_TEAM = L4674B5247; 351 | ENABLE_BITCODE = NO; 352 | INFOPLIST_FILE = Runner/Info.plist; 353 | LD_RUNPATH_SEARCH_PATHS = ( 354 | "$(inherited)", 355 | "@executable_path/Frameworks", 356 | ); 357 | PRODUCT_BUNDLE_IDENTIFIER = icu.bughub.plugins.videoplayer.fltVideoPlayerExample; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | VERSIONING_SYSTEM = "apple-generic"; 360 | }; 361 | name = Profile; 362 | }; 363 | 97C147031CF9000F007C117D /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | CLANG_ANALYZER_NONNULL = YES; 368 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 369 | CLANG_CXX_LIBRARY = "libc++"; 370 | CLANG_ENABLE_MODULES = YES; 371 | CLANG_ENABLE_OBJC_ARC = YES; 372 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 373 | CLANG_WARN_BOOL_CONVERSION = YES; 374 | CLANG_WARN_COMMA = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 377 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 378 | CLANG_WARN_EMPTY_BODY = YES; 379 | CLANG_WARN_ENUM_CONVERSION = YES; 380 | CLANG_WARN_INFINITE_RECURSION = YES; 381 | CLANG_WARN_INT_CONVERSION = YES; 382 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 383 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 384 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 386 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 387 | CLANG_WARN_STRICT_PROTOTYPES = YES; 388 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 389 | CLANG_WARN_UNREACHABLE_CODE = YES; 390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 391 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 392 | COPY_PHASE_STRIP = NO; 393 | DEBUG_INFORMATION_FORMAT = dwarf; 394 | ENABLE_STRICT_OBJC_MSGSEND = YES; 395 | ENABLE_TESTABILITY = YES; 396 | GCC_C_LANGUAGE_STANDARD = gnu99; 397 | GCC_DYNAMIC_NO_PIC = NO; 398 | GCC_NO_COMMON_BLOCKS = YES; 399 | GCC_OPTIMIZATION_LEVEL = 0; 400 | GCC_PREPROCESSOR_DEFINITIONS = ( 401 | "DEBUG=1", 402 | "$(inherited)", 403 | ); 404 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 405 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 406 | GCC_WARN_UNDECLARED_SELECTOR = YES; 407 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 408 | GCC_WARN_UNUSED_FUNCTION = YES; 409 | GCC_WARN_UNUSED_VARIABLE = YES; 410 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 411 | MTL_ENABLE_DEBUG_INFO = YES; 412 | ONLY_ACTIVE_ARCH = YES; 413 | SDKROOT = iphoneos; 414 | TARGETED_DEVICE_FAMILY = "1,2"; 415 | }; 416 | name = Debug; 417 | }; 418 | 97C147041CF9000F007C117D /* Release */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | ALWAYS_SEARCH_USER_PATHS = NO; 422 | CLANG_ANALYZER_NONNULL = YES; 423 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 424 | CLANG_CXX_LIBRARY = "libc++"; 425 | CLANG_ENABLE_MODULES = YES; 426 | CLANG_ENABLE_OBJC_ARC = YES; 427 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 428 | CLANG_WARN_BOOL_CONVERSION = YES; 429 | CLANG_WARN_COMMA = YES; 430 | CLANG_WARN_CONSTANT_CONVERSION = YES; 431 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 432 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 433 | CLANG_WARN_EMPTY_BODY = YES; 434 | CLANG_WARN_ENUM_CONVERSION = YES; 435 | CLANG_WARN_INFINITE_RECURSION = YES; 436 | CLANG_WARN_INT_CONVERSION = YES; 437 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 438 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 439 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 441 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 442 | CLANG_WARN_STRICT_PROTOTYPES = YES; 443 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 444 | CLANG_WARN_UNREACHABLE_CODE = YES; 445 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 446 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 447 | COPY_PHASE_STRIP = NO; 448 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 449 | ENABLE_NS_ASSERTIONS = NO; 450 | ENABLE_STRICT_OBJC_MSGSEND = YES; 451 | GCC_C_LANGUAGE_STANDARD = gnu99; 452 | GCC_NO_COMMON_BLOCKS = YES; 453 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 454 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 455 | GCC_WARN_UNDECLARED_SELECTOR = YES; 456 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 457 | GCC_WARN_UNUSED_FUNCTION = YES; 458 | GCC_WARN_UNUSED_VARIABLE = YES; 459 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 460 | MTL_ENABLE_DEBUG_INFO = NO; 461 | SDKROOT = iphoneos; 462 | SUPPORTED_PLATFORMS = iphoneos; 463 | TARGETED_DEVICE_FAMILY = "1,2"; 464 | VALIDATE_PRODUCT = YES; 465 | }; 466 | name = Release; 467 | }; 468 | 97C147061CF9000F007C117D /* Debug */ = { 469 | isa = XCBuildConfiguration; 470 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 471 | buildSettings = { 472 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 473 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 474 | DEVELOPMENT_TEAM = L4674B5247; 475 | ENABLE_BITCODE = NO; 476 | INFOPLIST_FILE = Runner/Info.plist; 477 | LD_RUNPATH_SEARCH_PATHS = ( 478 | "$(inherited)", 479 | "@executable_path/Frameworks", 480 | ); 481 | PRODUCT_BUNDLE_IDENTIFIER = icu.bughub.plugins.videoplayer.fltVideoPlayerExample; 482 | PRODUCT_NAME = "$(TARGET_NAME)"; 483 | VERSIONING_SYSTEM = "apple-generic"; 484 | }; 485 | name = Debug; 486 | }; 487 | 97C147071CF9000F007C117D /* Release */ = { 488 | isa = XCBuildConfiguration; 489 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 490 | buildSettings = { 491 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 492 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 493 | DEVELOPMENT_TEAM = L4674B5247; 494 | ENABLE_BITCODE = NO; 495 | INFOPLIST_FILE = Runner/Info.plist; 496 | LD_RUNPATH_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "@executable_path/Frameworks", 499 | ); 500 | PRODUCT_BUNDLE_IDENTIFIER = icu.bughub.plugins.videoplayer.fltVideoPlayerExample; 501 | PRODUCT_NAME = "$(TARGET_NAME)"; 502 | VERSIONING_SYSTEM = "apple-generic"; 503 | }; 504 | name = Release; 505 | }; 506 | /* End XCBuildConfiguration section */ 507 | 508 | /* Begin XCConfigurationList section */ 509 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | 97C147031CF9000F007C117D /* Debug */, 513 | 97C147041CF9000F007C117D /* Release */, 514 | 249021D3217E4FDB00AE95B9 /* Profile */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 520 | isa = XCConfigurationList; 521 | buildConfigurations = ( 522 | 97C147061CF9000F007C117D /* Debug */, 523 | 97C147071CF9000F007C117D /* Release */, 524 | 249021D4217E4FDB00AE95B9 /* Profile */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | /* End XCConfigurationList section */ 530 | }; 531 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 532 | } 533 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flt_video_player_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/define/function_define.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | 5 | typedef ControlButtonCallback = Function(T t); -------------------------------------------------------------------------------- /example/lib/extensions/extension.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | 5 | extension BoolExtension on bool { 6 | bool toggle() { 7 | return !this; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/full/full_player.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | 5 | import 'package:flt_video_player/flt_video_player.dart'; 6 | import 'package:flt_video_player_example/full/player_control_overlay.dart'; 7 | import 'package:flutter/material.dart'; 8 | import "package:flutter/widgets.dart"; 9 | 10 | class FullPlayer extends StatefulWidget { 11 | const FullPlayer( 12 | {Key? key, 13 | required this.controller, 14 | this.title, 15 | this.playCall, 16 | this.aspectRatio = 16 / 9, 17 | this.coverUrl}) 18 | : super(key: key); 19 | 20 | final double aspectRatio; 21 | final String? coverUrl; 22 | final String? title; 23 | final Function? playCall; 24 | final VodPlayerController controller; 25 | 26 | @override 27 | _FullPlayerState createState() => _FullPlayerState(); 28 | } 29 | 30 | class _FullPlayerState extends State { 31 | @override 32 | Widget build(BuildContext context) { 33 | return Stack( 34 | fit: StackFit.expand, 35 | children: [ 36 | VodPlayer(controller: widget.controller), 37 | ControlOverlay( 38 | controller: widget.controller, 39 | callPlay: widget.playCall, 40 | coverUrl: widget.coverUrl, 41 | title: widget.title, 42 | ) 43 | ], 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/lib/full/full_player_demo.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | import 'package:flt_video_player/flt_video_player.dart'; 5 | import 'package:flt_video_player_example/full/full_player.dart'; 6 | import 'package:flutter/material.dart'; 7 | import "package:flutter/widgets.dart"; 8 | 9 | class FullPlayerDemo extends StatefulWidget { 10 | const FullPlayerDemo({Key? key}) : super(key: key); 11 | 12 | @override 13 | _FullPlayerDemoState createState() => _FullPlayerDemoState(); 14 | } 15 | 16 | class _FullPlayerDemoState extends State { 17 | late VodPlayerController controller; 18 | double _aspectRation = 16 / 9; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | var playerConfig = PlayerConfig(); 24 | playerConfig.headers = {"Referer": "https://videoadmin.chinahrt.com"}; 25 | 26 | controller = VodPlayerController(config: playerConfig); 27 | 28 | //监听播放状态 29 | controller.playState.listen((event) { 30 | debugPrint("playerState:$event"); 31 | }); 32 | 33 | controller.onNetEvent.listen((event) { 34 | //获取视频宽度高度 35 | double w = (event["VIDEO_WIDTH"]).toDouble(); 36 | double h = (event["VIDEO_HEIGHT"]).toDouble(); 37 | 38 | //计算比例 39 | if (w > 0 && h > 0) { 40 | _aspectRation = 1.0 * w / h; 41 | setState(() {}); 42 | } 43 | }); 44 | 45 | controller.initialize(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return OrientationBuilder(builder: (context, orientation) { 51 | return Scaffold( 52 | appBar: orientation == Orientation.portrait 53 | ? AppBar( 54 | title: const Text("Full Player Demo"), 55 | ) 56 | : null, 57 | body: orientation == Orientation.portrait 58 | ? Column( 59 | children: [ 60 | AspectRatio( 61 | aspectRatio: _aspectRation, 62 | child: buildFullPlayer(), 63 | ) 64 | ], 65 | ) 66 | : Container( 67 | constraints: const BoxConstraints.expand(), 68 | child: buildFullPlayer(), 69 | ), 70 | ); 71 | }); 72 | } 73 | 74 | FullPlayer buildFullPlayer() { 75 | return FullPlayer( 76 | playCall: () { 77 | if (controller.value.state == PlayerState.stopped) { 78 | controller.play( 79 | "https://look.chinahrt.com.cn/courseyun/rxsl2content/transcode/20211/be3b6935-f678-4303-a1f8-b2a006352656/283006-mp4.mp4"); 80 | } else { 81 | controller.resume(); 82 | } 83 | }, 84 | controller: controller, 85 | aspectRatio: _aspectRation, 86 | title: "测试视频", 87 | coverUrl: 88 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1114%2F060421091316%2F210604091316-6-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639190767&t=831d19c414f872da0b3cf565b3019bfd", 89 | ); 90 | } 91 | 92 | @override 93 | void dispose() { 94 | controller.dispose(); 95 | super.dispose(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example/lib/full/full_screen_button.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | import 'package:flt_video_player_example/define/function_define.dart'; 5 | import 'package:flutter/material.dart'; 6 | import "package:flutter/widgets.dart"; 7 | 8 | class FullScreenButton extends StatefulWidget { 9 | const FullScreenButton({Key? key, this.callback}) : super(key: key); 10 | 11 | final ControlButtonCallback? callback; 12 | 13 | @override 14 | _FullScreenButtonState createState() => _FullScreenButtonState(); 15 | } 16 | 17 | class _FullScreenButtonState extends State { 18 | bool _full = false; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return InkWell( 23 | child: Image.asset( 24 | _full 25 | ? "resources/arrow.down.right.and.arrow.up.left.circle.png" 26 | : "resources/arrow.up.backward.and.arrow.down.forward.circle.png", 27 | ), 28 | onTap: () { 29 | _full = !_full; 30 | setState(() {}); 31 | widget.callback?.call(_full); 32 | }, 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/lib/full/mute_button.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | import 'package:flt_video_player_example/define/function_define.dart'; 5 | import 'package:flutter/material.dart'; 6 | import "package:flutter/widgets.dart"; 7 | 8 | class MuteButton extends StatefulWidget { 9 | const MuteButton({Key? key,this.callback}) : super(key: key); 10 | 11 | final ControlButtonCallback? callback; 12 | @override 13 | _MuteButtonState createState() => _MuteButtonState(); 14 | } 15 | 16 | class _MuteButtonState extends State { 17 | bool _mute = false; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return InkWell( 22 | child: Image.asset( 23 | _mute ? "resources/speaker.slash.circle.png" : "resources/speaker.circle.png", 24 | ), 25 | onTap: () { 26 | _mute = !_mute; 27 | setState(() {}); 28 | widget.callback?.call(_mute); 29 | }, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/lib/full/play_button.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | 5 | import 'package:flt_video_player_example/define/function_define.dart'; 6 | import 'package:flutter/material.dart'; 7 | import "package:flutter/widgets.dart"; 8 | 9 | class PlayButton extends StatefulWidget { 10 | const PlayButton({Key? key, required this.isPlaying, this.callback}) 11 | : super(key: key); 12 | 13 | final ControlButtonCallback? callback; 14 | final bool isPlaying; 15 | 16 | @override 17 | _PlayButtonState createState() => _PlayButtonState(); 18 | } 19 | 20 | class _PlayButtonState extends State { 21 | bool _isPlaying = false; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _isPlaying = widget.isPlaying; 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return InkWell( 32 | child: Image.asset( 33 | _isPlaying ? "resources/pause_circle.png" : "resources/play_circle.png", 34 | ), 35 | onTap: () { 36 | widget.callback?.call(_isPlaying); 37 | _isPlaying = !_isPlaying; 38 | setState(() {}); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/lib/full/player_control_overlay.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | import 'dart:async'; 5 | import 'dart:io'; 6 | 7 | import 'package:flt_video_player/flt_video_player.dart'; 8 | import 'package:flt_video_player_example/full/full_screen_button.dart'; 9 | import 'package:flt_video_player_example/full/play_button.dart'; 10 | import 'package:flt_video_player_example/full/rate_button.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter/services.dart'; 13 | import 'package:orientation/orientation.dart'; 14 | 15 | class ControlOverlay extends StatefulWidget { 16 | const ControlOverlay( 17 | {Key? key, 18 | required this.controller, 19 | this.callPlay, 20 | this.title, 21 | this.coverUrl}) 22 | : super(key: key); 23 | 24 | final String? coverUrl; 25 | final String? title; 26 | final VodPlayerController controller; 27 | 28 | final Function? callPlay; 29 | 30 | @override 31 | _ControlOverlayState createState() => _ControlOverlayState(); 32 | } 33 | 34 | class _ControlOverlayState extends State { 35 | bool _showLoading = false; 36 | bool _showCover = true; 37 | bool _showBigPlayButton = true; 38 | 39 | bool _showControlBar = false; 40 | final bool _showTitleBar = false; 41 | 42 | bool _isPlaying = false; 43 | 44 | Timer? _timer; 45 | 46 | double duration = 0; 47 | double progress = 0; 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | 53 | var playState = widget.controller.value.state; 54 | 55 | if (playState != PlayerState.stopped) { 56 | _showCover = false; 57 | _showBigPlayButton = false; 58 | } 59 | 60 | widget.controller.playState.listen((PlayerState state) { 61 | debugPrint(state.toString()); 62 | //状态一旦变化封面要隐藏 63 | _showLoading = false; 64 | _showBigPlayButton = true; 65 | _isPlaying = false; 66 | if (state == PlayerState.buffering) { 67 | _showLoading = true; 68 | _showBigPlayButton = false; 69 | } else if (state == PlayerState.playing) { 70 | _showBigPlayButton = false; 71 | _isPlaying = true; 72 | } 73 | setState(() {}); 74 | }); 75 | 76 | widget.controller.onPlayerEvent.listen((event) { 77 | switch (event["event"]) { 78 | case 2005: 79 | if (_showControlBar && mounted) { 80 | duration = event["EVT_PLAY_DURATION"] * 1.0; 81 | progress = event["EVT_PLAY_PROGRESS"] * 1.0; 82 | setState(() {}); 83 | } 84 | break; 85 | } 86 | }); 87 | } 88 | 89 | _switchScreenOrientation() { 90 | //屏幕旋转方向 91 | final List orientations = []; 92 | if (isPortraitUp) { 93 | if (Platform.isIOS) { 94 | orientations.add(DeviceOrientation.landscapeRight); 95 | SystemChrome.setPreferredOrientations(orientations); 96 | } 97 | OrientationPlugin.forceOrientation(DeviceOrientation.landscapeRight); 98 | } else { 99 | orientations.add(DeviceOrientation.portraitUp); 100 | //设置屏幕旋转方向 101 | SystemChrome.setPreferredOrientations(orientations); 102 | OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp); 103 | } 104 | } 105 | 106 | get isPortraitUp { 107 | Size screenSize = MediaQueryData.fromView(View.of(context)).size; 108 | return screenSize.width < screenSize.height; 109 | } 110 | 111 | @override 112 | Widget build(BuildContext context) { 113 | return InkWell( 114 | onTap: () async { 115 | //如果当前状态非播放状态,不可呼出控制条 116 | if (!await widget.controller.isPlaying) return; 117 | _timer?.cancel(); 118 | _timer = null; 119 | _timer = Timer(const Duration(seconds: 3), () { 120 | if (!mounted) return; 121 | _showControlBar = false; 122 | setState(() {}); 123 | }); 124 | _showControlBar = !_showControlBar; 125 | setState(() {}); 126 | }, 127 | child: Stack( 128 | children: [ 129 | if (_showLoading) const Center(child: CircularProgressIndicator()), 130 | 131 | //封面 132 | if (_showCover && widget.coverUrl != null) 133 | Image.network( 134 | widget.coverUrl!, 135 | ), 136 | 137 | //大的播放按钮 138 | if (_showBigPlayButton) 139 | Center( 140 | child: SizedBox( 141 | width: 70, 142 | child: PlayButton( 143 | isPlaying: _isPlaying, 144 | callback: (isPlaying) { 145 | _showCover = false; 146 | setState(() {}); 147 | widget.callPlay?.call(); 148 | }, 149 | ), 150 | ), 151 | ), 152 | 153 | if (_showControlBar) 154 | Column( 155 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 156 | children: [ 157 | _showTitleBar 158 | ? Container( 159 | padding: const EdgeInsets.only(left: 8), 160 | child: Align( 161 | alignment: Alignment.centerLeft, 162 | child: Text( 163 | widget.title ?? "", 164 | style: const TextStyle( 165 | color: Colors.white, fontSize: 12), 166 | ), 167 | ), 168 | color: Colors.black54, 169 | height: 30, 170 | ) 171 | : Container(), 172 | Container( 173 | height: 35, 174 | color: Colors.black54, 175 | padding: const EdgeInsets.symmetric(horizontal: 8), 176 | child: Row( 177 | children: [ 178 | SizedBox( 179 | width: 25, 180 | child: PlayButton( 181 | isPlaying: _isPlaying, 182 | callback: (isPlaying) { 183 | if (isPlaying) { 184 | widget.controller.pause(); 185 | } else { 186 | widget.controller.resume(); 187 | } 188 | }, 189 | ), 190 | ), 191 | Expanded( 192 | child: Slider( 193 | activeColor: Colors.white, 194 | inactiveColor: Colors.grey, 195 | value: progress, 196 | onChanged: (value) { 197 | progress = value; 198 | }, 199 | onChangeEnd: (value) { 200 | widget.controller.seek(value.toInt()); 201 | }, 202 | max: duration, 203 | ), 204 | ), 205 | Padding( 206 | padding: const EdgeInsets.symmetric(horizontal: 8), 207 | child: RateButton( 208 | controller: widget.controller, 209 | callback: (rate) { 210 | widget.controller.setRate(rate); 211 | }, 212 | ), 213 | ), 214 | SizedBox( 215 | child: FullScreenButton( 216 | callback: (isFull) { 217 | _switchScreenOrientation(); 218 | }, 219 | ), 220 | width: 25, 221 | ) 222 | ], 223 | ), 224 | ) 225 | ], 226 | ) 227 | ], 228 | ), 229 | ); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /example/lib/full/rate_button.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/11.
3 | /// 4 | import 'package:flt_video_player/flt_video_player.dart'; 5 | import 'package:flt_video_player_example/define/function_define.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | class RateButton extends StatefulWidget { 9 | const RateButton({Key? key, required this.controller, this.callback}) 10 | : super(key: key); 11 | 12 | final ControlButtonCallback? callback; 13 | 14 | final VodPlayerController controller; 15 | 16 | @override 17 | _RateButtonState createState() => _RateButtonState(); 18 | } 19 | 20 | class _RateButtonState extends State { 21 | final _rates = [1.0, 1.5, 1.75, 2.0]; 22 | var _index = 0; 23 | double _rate = 1.0; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | _rate = widget.controller.rate; 29 | _index = _rates.indexWhere((element) => element == _rate); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return InkWell( 35 | child: Text( 36 | "${_rate}x", 37 | style: const TextStyle(color: Colors.white), 38 | ), 39 | onTap: () { 40 | _index++; 41 | if (_index >= _rates.length) { 42 | _index = 0; 43 | } 44 | _rate = _rates[_index]; 45 | widget.controller.setRate(_rate); 46 | setState(() {}); 47 | widget.callback?.call(_rate); 48 | }, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flt_video_player_example/full/full_player_demo.dart'; 2 | import 'package:flt_video_player_example/platform_view_demo.dart'; 3 | import 'package:flt_video_player_example/simple_demo.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | void main() { 7 | runApp(const MyApp()); 8 | } 9 | 10 | class MyApp extends StatefulWidget { 11 | const MyApp({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _MyAppState(); 15 | } 16 | 17 | class _MyAppState extends State { 18 | @override 19 | Widget build(BuildContext context) { 20 | return MaterialApp( 21 | theme: ThemeData(platform: TargetPlatform.iOS), 22 | home: const HomePage(), 23 | ); 24 | } 25 | } 26 | 27 | class HomePage extends StatefulWidget { 28 | const HomePage({Key? key}) : super(key: key); 29 | 30 | @override 31 | _HomePageState createState() => _HomePageState(); 32 | } 33 | 34 | class _HomePageState extends State { 35 | @override 36 | Widget build(BuildContext context) { 37 | return Scaffold( 38 | appBar: AppBar( 39 | title: const Text('Plugin example app'), 40 | ), 41 | body: Center( 42 | child: Column( 43 | children: [ 44 | ElevatedButton( 45 | onPressed: () { 46 | Navigator.of(context) 47 | .push(MaterialPageRoute(builder: (context) { 48 | return const SimpleDemo(); 49 | })); 50 | }, 51 | child: const Text("简单示例")), 52 | ElevatedButton( 53 | onPressed: () { 54 | Navigator.of(context) 55 | .push(MaterialPageRoute(builder: (context) { 56 | return const FullPlayerDemo(); 57 | })); 58 | }, 59 | child: const Text("完整示例")), 60 | ElevatedButton( 61 | onPressed: () { 62 | Navigator.of(context) 63 | .push(MaterialPageRoute(builder: (context) { 64 | return const PlatformViewDemo(); 65 | })); 66 | }, 67 | child: const Text("Platform View 示例")), 68 | ], 69 | ), 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /example/lib/platform_view_demo.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/17.
3 | /// 4 | import 'package:flutter/material.dart'; 5 | import "package:flutter/widgets.dart"; 6 | 7 | class PlatformViewDemo extends StatefulWidget { 8 | const PlatformViewDemo({Key? key}) : super(key: key); 9 | 10 | @override 11 | _PlatformViewDemoState createState() => _PlatformViewDemoState(); 12 | } 13 | 14 | class _PlatformViewDemoState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar( 19 | title: const Text("Platform View Demo"), 20 | ), 21 | body: Center( 22 | child: Column( 23 | children: const [ 24 | AspectRatio( 25 | aspectRatio: 16 / 9, 26 | child: UiKitView(viewType: "FltVideoView")), 27 | AspectRatio( 28 | aspectRatio: 16 / 9, child: UiKitView(viewType: "FltVideoView")) 29 | ], 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/lib/simple_demo.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/8.
3 | /// 4 | import 'package:flt_video_player/flt_video_player.dart'; 5 | import 'package:flutter/material.dart'; 6 | import "package:flutter/widgets.dart"; 7 | 8 | class SimpleDemo extends StatefulWidget { 9 | const SimpleDemo({Key? key}) : super(key: key); 10 | 11 | @override 12 | _SimpleDemoState createState() => _SimpleDemoState(); 13 | } 14 | 15 | class _SimpleDemoState extends State { 16 | late VodPlayerController controller; 17 | double _aspectRation = 16 / 9; 18 | late GlobalKey _videoViewKey; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | 24 | //用来保证视频 view 不会被刷新 25 | _videoViewKey = GlobalKey(); 26 | 27 | var playerConfig = PlayerConfig(); 28 | playerConfig.headers = {"Referer": "https://videoadmin.chinahrt.com"}; 29 | 30 | controller = VodPlayerController( 31 | config: playerConfig, renderType: RenderType.platformView); 32 | 33 | //监听播放状态 34 | controller.playState.listen((event) { 35 | debugPrint("playerState:$event"); 36 | }); 37 | 38 | controller.onPlayerEvent.listen((event) { 39 | debugPrint("PlayerEvent:$event"); 40 | }); 41 | 42 | controller.onNetEvent.listen((event) { 43 | //获取视频宽度高度 44 | double w = (event["VIDEO_WIDTH"]).toDouble(); 45 | double h = (event["VIDEO_HEIGHT"]).toDouble(); 46 | 47 | //计算比例 48 | if (w > 0 && h > 0) { 49 | _aspectRation = 1.0 * w / h; 50 | setState(() {}); 51 | } 52 | }); 53 | 54 | controller.initialize(); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Scaffold( 60 | appBar: AppBar( 61 | title: const Text('Plugin example app'), 62 | ), 63 | body: Center( 64 | child: Column( 65 | children: [ 66 | AspectRatio( 67 | aspectRatio: _aspectRation, 68 | child: VodPlayer( 69 | key: _videoViewKey, 70 | controller: controller, 71 | ), 72 | ), 73 | ElevatedButton( 74 | onPressed: () { 75 | controller.seek(60); 76 | }, 77 | child: Text("Seek")) 78 | ], 79 | ), 80 | ), 81 | floatingActionButton: FloatingActionButton( 82 | onPressed: () { 83 | //https://hwonline.oss-cn-beijing.aliyuncs.com/course/浅谈医德医风建设.mp4 84 | //https://closs.jumingedu.com/20210930/%E5%B8%B8%E8%A7%81%E5%BF%83%E5%BE%8B%E5%A4%B1%E5%B8%B8%E7%9A%84%E8%AF%8A%E6%B2%BB%E5%8E%9F%E5%88%99/%E5%B8%B8%E8%A7%81%E5%BF%83%E5%BE%8B%E5%A4%B1%E5%B8%B8%E7%9A%84%E8%AF%8A%E6%B2%BB%E5%8E%9F%E5%88%99.m3u8 85 | //https://look.chinahrt.com.cn/courseyun/rxsl2content/transcode/20211/be3b6935-f678-4303-a1f8-b2a006352656/283006-mp4.mp4 86 | //https://video.qiantucdn.com/58pic/00/20/21/09v58PICJQgaWdcC58PICSUbK.mp4?e=1636441061&token=OyzEe_0O8H433pm7zVEjtnSy5dVdfpsIawO2nx3f:eHu6r0m7_zdDEj-L6lTqq_6OYPs= 87 | //https://stream7.iqilu.com/10339/article/202002/18/2fca1c77730e54c7b500573c2437003f.mp4 88 | controller.setStartTime(60); 89 | controller.play( 90 | "https://stream7.iqilu.com/10339/article/202002/18/2fca1c77730e54c7b500573c2437003f.mp4"); 91 | }, 92 | ), 93 | ); 94 | } 95 | 96 | @override 97 | void dispose() { 98 | controller.dispose(); 99 | super.dispose(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.8.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.0.3" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.2.0" 60 | flt_video_player: 61 | dependency: "direct main" 62 | description: 63 | path: ".." 64 | relative: true 65 | source: path 66 | version: "0.0.1" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_lints: 73 | dependency: "direct dev" 74 | description: 75 | name: flutter_lints 76 | url: "https://pub.flutter-io.cn" 77 | source: hosted 78 | version: "1.0.4" 79 | flutter_test: 80 | dependency: "direct dev" 81 | description: flutter 82 | source: sdk 83 | version: "0.0.0" 84 | lints: 85 | dependency: transitive 86 | description: 87 | name: lints 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.0.1" 91 | matcher: 92 | dependency: transitive 93 | description: 94 | name: matcher 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "0.12.10" 98 | meta: 99 | dependency: transitive 100 | description: 101 | name: meta 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.7.0" 105 | orientation: 106 | dependency: "direct main" 107 | description: 108 | name: orientation 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "1.3.0" 112 | path: 113 | dependency: transitive 114 | description: 115 | name: path 116 | url: "https://pub.flutter-io.cn" 117 | source: hosted 118 | version: "1.8.0" 119 | sky_engine: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.99" 124 | source_span: 125 | dependency: transitive 126 | description: 127 | name: source_span 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.8.1" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.10.0" 138 | stream_channel: 139 | dependency: transitive 140 | description: 141 | name: stream_channel 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.0" 145 | string_scanner: 146 | dependency: transitive 147 | description: 148 | name: string_scanner 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.1.0" 152 | term_glyph: 153 | dependency: transitive 154 | description: 155 | name: term_glyph 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "1.2.0" 159 | test_api: 160 | dependency: transitive 161 | description: 162 | name: test_api 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "0.4.2" 166 | typed_data: 167 | dependency: transitive 168 | description: 169 | name: typed_data 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "1.3.0" 173 | vector_math: 174 | dependency: transitive 175 | description: 176 | name: vector_math 177 | url: "https://pub.flutter-io.cn" 178 | source: hosted 179 | version: "2.1.0" 180 | sdks: 181 | dart: ">=2.12.0 <3.0.0" 182 | flutter: ">=2.0.1" 183 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flt_video_player_example 2 | description: Demonstrates how to use the flt_video_player plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | # Dependencies specify other packages that your package needs in order to work. 12 | # To automatically upgrade your package dependencies to the latest versions 13 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 14 | # dependencies can be manually updated by changing the version numbers below to 15 | # the latest version available on pub.dev. To see which dependencies have newer 16 | # versions available, run `flutter pub outdated`. 17 | dependencies: 18 | flutter: 19 | sdk: flutter 20 | 21 | flt_video_player: 22 | # When depending on this package from a real application you should use: 23 | # flt_video_player: ^x.y.z 24 | # See https://dart.dev/tools/pub/dependencies#version-constraints 25 | # The example app is bundled with the plugin so we use a path dependency on 26 | # the parent directory to use the current plugin's version. 27 | path: ../ 28 | 29 | # The following adds the Cupertino Icons font to your application. 30 | # Use with the CupertinoIcons class for iOS style icons. 31 | cupertino_icons: ^1.0.2 32 | # 屏幕方向控制 33 | orientation: ^1.3.0 34 | 35 | dev_dependencies: 36 | flutter_test: 37 | sdk: flutter 38 | 39 | # The "flutter_lints" package below contains a set of recommended lints to 40 | # encourage good coding practices. The lint set provided by the package is 41 | # activated in the `analysis_options.yaml` file located at the root of your 42 | # package. See that file for information about deactivating specific lint 43 | # rules and activating additional ones. 44 | flutter_lints: ^1.0.0 45 | 46 | # For information on the generic Dart part of this file, see the 47 | # following page: https://dart.dev/tools/pub/pubspec 48 | 49 | # The following section is specific to Flutter. 50 | flutter: 51 | 52 | # The following line ensures that the Material Icons font is 53 | # included with your application, so that you can use the icons in 54 | # the material Icons class. 55 | uses-material-design: true 56 | 57 | # To add assets to your application, add an assets section, like this: 58 | assets: 59 | - resources/speaker.circle.png 60 | - resources/speaker.slash.circle.png 61 | - resources/arrow.down.right.and.arrow.up.left.circle.png 62 | - resources/arrow.up.backward.and.arrow.down.forward.circle.png 63 | - resources/pause_circle.png 64 | - resources/play_circle.png 65 | 66 | # An image asset can refer to one or more resolution-specific "variants", see 67 | # https://flutter.dev/assets-and-images/#resolution-aware. 68 | 69 | # For details regarding adding assets from package dependencies, see 70 | # https://flutter.dev/assets-and-images/#from-packages 71 | 72 | # To add custom fonts to your application, add a fonts section here, 73 | # in this "flutter" section. Each entry in this list should have a 74 | # "family" key with the font family name, and a "fonts" key with a 75 | # list giving the asset and other descriptors for the font. For 76 | # example: 77 | # fonts: 78 | # - family: Schyler 79 | # fonts: 80 | # - asset: fonts/Schyler-Regular.ttf 81 | # - asset: fonts/Schyler-Italic.ttf 82 | # style: italic 83 | # - family: Trajan Pro 84 | # fonts: 85 | # - asset: fonts/TrajanPro.ttf 86 | # - asset: fonts/TrajanPro_Bold.ttf 87 | # weight: 700 88 | # 89 | # For details regarding fonts from package dependencies, 90 | # see https://flutter.dev/custom-fonts/#from-packages 91 | -------------------------------------------------------------------------------- /example/resources/arrow.down.right.and.arrow.up.left.circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/resources/arrow.down.right.and.arrow.up.left.circle.png -------------------------------------------------------------------------------- /example/resources/arrow.up.backward.and.arrow.down.forward.circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/resources/arrow.up.backward.and.arrow.down.forward.circle.png -------------------------------------------------------------------------------- /example/resources/pause_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/resources/pause_circle.png -------------------------------------------------------------------------------- /example/resources/play_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/resources/play_circle.png -------------------------------------------------------------------------------- /example/resources/speaker.circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/resources/speaker.circle.png -------------------------------------------------------------------------------- /example/resources/speaker.slash.circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/example/resources/speaker.slash.circle.png -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flt_video_player_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /flt_video_player.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/flt_video_player/fa8d074a73c354362459d2106b68e0f0882088e4/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/EventSinkQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // EventSinkQueue.h 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/5. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface EventSinkQueue : NSObject 14 | 15 | -(void)success:(NSObject*) event; 16 | 17 | -(void)setDelegate:(_Nullable FlutterEventSink)sink; 18 | 19 | -(void)error:(NSString*)code message:(NSString *_Nullable)message details:(id _Nullable)details; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /ios/Classes/EventSinkQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // EventSinkQueue.m 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/5. 6 | // 7 | 8 | #import "EventSinkQueue.h" 9 | 10 | @interface EventSinkQueue() 11 | 12 | @property (nonatomic,strong) NSMutableArray *eventQueue; 13 | @property (nonatomic, copy) FlutterEventSink eventSink; 14 | 15 | @end 16 | 17 | @implementation EventSinkQueue 18 | 19 | - (void)success:(NSObject *)event{ 20 | [self enqueue:event]; 21 | [self flushIfNeed]; 22 | } 23 | 24 | - (void)setDelegate:(FlutterEventSink)sink{ 25 | self.eventSink = sink; 26 | } 27 | 28 | - (void)error:(NSString *)code message:(NSString *)message details:(id)details{ 29 | [self enqueue:[FlutterError errorWithCode:code message:message details:details]]; 30 | [self flushIfNeed]; 31 | } 32 | 33 | -(instancetype)init{ 34 | if (self = [super init]) { 35 | _eventQueue = @[].mutableCopy; 36 | } 37 | return self; 38 | } 39 | 40 | -(void)flushIfNeed{ 41 | if (self.eventSink == nil) { 42 | return; 43 | } 44 | 45 | for (NSObject *obj in self.eventQueue) { 46 | self.eventSink(obj); 47 | } 48 | [self.eventQueue removeAllObjects]; 49 | } 50 | 51 | -(void)enqueue:(NSObject*) event{ 52 | [self.eventQueue addObject:event]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /ios/Classes/FltBasePlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // FltBasePlayer.h 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/4. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import "EventSinkQueue.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface FltBasePlayer : NSObject{ 16 | @protected 17 | TXVodPlayer *_vodPlayer; 18 | 19 | @protected 20 | int64_t _textureId; 21 | 22 | 23 | 24 | //通信通道 25 | @protected 26 | FlutterMethodChannel *_methodChannel; 27 | 28 | //播放事件回调通信通道 29 | @protected 30 | EventSinkQueue *_eventSink; 31 | @protected 32 | FlutterEventChannel *_eventChannel; 33 | 34 | //网络回调通信通道 35 | @protected 36 | EventSinkQueue *_netSink; 37 | @protected 38 | FlutterEventChannel *_netChannel; 39 | } 40 | 41 | @property(atomic, readonly) NSNumber *playerId; 42 | 43 | -(void)destory; 44 | 45 | 46 | /// 47 | ///开始播放 48 | /// 49 | -(int)startPlay:(NSString*)url; 50 | 51 | /// 52 | ///停止播放 53 | /// 54 | -(BOOL)stopPlay; 55 | 56 | /// 57 | ///播放状态 58 | /// 59 | - (BOOL) isPlaying; 60 | 61 | 62 | - (void) pause; 63 | 64 | - (void) resume; 65 | 66 | /** 67 | * 播放跳转到音视频流某个时间 68 | * @param time 流时间,单位为秒 69 | * @return 0 = OK 70 | */ 71 | -(int) seek: (float) time; 72 | 73 | -(float) currentPlaybackTime; 74 | 75 | -(float) duration; 76 | 77 | -(float) playableDuration; 78 | 79 | -(void) setMute:(BOOL)bEnable; 80 | /** 81 | * 设置音量大小 82 | * @param volume 音量大小。范围:0 ~ 100。 83 | */ 84 | -(void) setAudioPlayoutVolume:(int)volume; 85 | 86 | /** 87 | * 设置播放速率 88 | * @param rate 正常速度为1.0;小于为慢速;大于为快速。最大建议不超过2.0 89 | */ 90 | - (void)setRate:(float)rate; 91 | 92 | /** 93 | * 设置画面镜像 94 | */ 95 | - (void)setMirror:(BOOL)isMirror; 96 | 97 | - (void)setRenderRotation: (int)rotaion; 98 | 99 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; 100 | 101 | - (void)createVodPlayer:(TXVodPlayConfig*) config; 102 | 103 | @end 104 | 105 | NS_ASSUME_NONNULL_END 106 | -------------------------------------------------------------------------------- /ios/Classes/FltBasePlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // FltBasePlayer.m 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/4. 6 | // 7 | 8 | #import "FltBasePlayer.h" 9 | #import 10 | #import 11 | 12 | static atomic_int atomicId = 0; 13 | 14 | static const int uninitialized = -1; 15 | 16 | @implementation FltBasePlayer 17 | 18 | -(instancetype) init{ 19 | if (self = [super init]) { 20 | int pid = atomic_fetch_add(&atomicId, 1); 21 | _playerId = @(pid); 22 | } 23 | return self; 24 | } 25 | 26 | 27 | /// 28 | ///开始播放 29 | /// 30 | -(int)startPlay:(NSString*)url{ 31 | if(_vodPlayer != nil){ 32 | return [_vodPlayer startPlay:url]; 33 | } 34 | return uninitialized; 35 | } 36 | 37 | /// 38 | ///停止播放 39 | /// 40 | -(BOOL)stopPlay{ 41 | if (_vodPlayer != nil) { 42 | return [_vodPlayer stopPlay]; 43 | } 44 | return NO; 45 | } 46 | 47 | /// 48 | ///播放状态 49 | /// 50 | - (BOOL) isPlaying{ 51 | if (_vodPlayer != nil) { 52 | return [_vodPlayer isPlaying]; 53 | } 54 | return NO; 55 | } 56 | 57 | 58 | - (void) pause{ 59 | if (_vodPlayer != nil) { 60 | [_vodPlayer pause]; 61 | } 62 | } 63 | 64 | - (void) resume{ 65 | if (_vodPlayer != nil) { 66 | [_vodPlayer resume]; 67 | } 68 | } 69 | 70 | /** 71 | * 播放跳转到音视频流某个时间 72 | * @param time 流时间,单位为秒 73 | * @return 0 = OK 74 | */ 75 | -(int) seek: (float) time{ 76 | if (_vodPlayer != nil) { 77 | return [_vodPlayer seek:time]; 78 | } 79 | return -1; 80 | } 81 | 82 | -(void) setStartTime: (CGFloat) time{ 83 | if (_vodPlayer != nil) { 84 | [_vodPlayer setStartTime:time]; 85 | } 86 | } 87 | 88 | -(float) currentPlaybackTime { 89 | if (_vodPlayer != nil) { 90 | return [_vodPlayer currentPlaybackTime]; 91 | } 92 | return 0; 93 | } 94 | 95 | -(float) duration { 96 | if (_vodPlayer != nil) { 97 | return [_vodPlayer duration]; 98 | } 99 | return 0; 100 | } 101 | 102 | -(float) playableDuration { 103 | if (_vodPlayer != nil) { 104 | return [_vodPlayer playableDuration]; 105 | } 106 | return 0; 107 | } 108 | 109 | -(void) setMute:(BOOL)bEnable { 110 | if (_vodPlayer != nil) { 111 | [_vodPlayer setMute:bEnable]; 112 | } 113 | } 114 | /** 115 | * 设置音量大小 116 | * @param volume 音量大小。范围:0 ~ 100。 117 | */ 118 | -(void) setAudioPlayoutVolume:(int)volume { 119 | if (_vodPlayer != nil) { 120 | [_vodPlayer setAudioPlayoutVolume:volume]; 121 | } 122 | } 123 | 124 | /** 125 | * 设置播放速率 126 | * @param rate 正常速度为1.0;小于为慢速;大于为快速。最大建议不超过2.0 127 | */ 128 | - (void)setRate:(float)rate{ 129 | if (_vodPlayer != nil) { 130 | [_vodPlayer setRate:rate]; 131 | } 132 | } 133 | 134 | /** 135 | * 设置画面镜像 136 | */ 137 | - (void)setMirror:(BOOL)isMirror{ 138 | if (_vodPlayer != nil) { 139 | [_vodPlayer setMirror:isMirror]; 140 | } 141 | } 142 | 143 | -(void)setRenderRotation: (int)rotaion{ 144 | if (_vodPlayer != nil) { 145 | [_vodPlayer setRenderRotation:rotaion]; 146 | } 147 | } 148 | 149 | 150 | -(void)destory{ 151 | //停止播放 152 | [self stopPlay]; 153 | 154 | //移除视频视图 155 | [_vodPlayer removeVideoWidget]; 156 | 157 | _vodPlayer = nil; 158 | 159 | [_methodChannel setMethodCallHandler:nil]; 160 | _methodChannel = nil; 161 | 162 | [_eventSink setDelegate:nil]; 163 | _eventSink = nil; 164 | 165 | [_eventChannel setStreamHandler:nil]; 166 | _eventChannel = nil; 167 | 168 | [_netSink setDelegate:nil]; 169 | _netSink = nil; 170 | 171 | [_netChannel setStreamHandler:nil]; 172 | _netChannel = nil; 173 | } 174 | 175 | 176 | /// 177 | ///创建播放器 178 | /// 179 | - (void)createVodPlayer:(TXVodPlayConfig*) config{ 180 | if (_vodPlayer == nil) { 181 | _vodPlayer = [TXVodPlayer new]; 182 | _vodPlayer.config = config; 183 | _vodPlayer.vodDelegate = self; 184 | _vodPlayer.enableHWAcceleration = YES; 185 | [_vodPlayer setVideoProcessDelegate:self]; 186 | _vodPlayer.enableHWAcceleration = YES; 187 | } 188 | } 189 | 190 | 191 | +(NSDictionary*) getParamsWithEvent:(int)EvtId withParams:(NSDictionary*) params{ 192 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:@(EvtId) forKey:@"event"]; 193 | if (params != nil && params.count > 0 ) { 194 | [dict addEntriesFromDictionary:params]; 195 | } 196 | return dict; 197 | } 198 | 199 | #pragma mark - TXVodPlayListener 200 | 201 | -(void)onPlayEvent:(TXVodPlayer *)player event:(int)EvtID withParam:(NSDictionary *)param{ 202 | [_eventSink success:[FltBasePlayer getParamsWithEvent:EvtID withParams:param]]; 203 | } 204 | 205 | - (void)onNetStatus:(TXVodPlayer *)player withParam:(NSDictionary *)param { 206 | [_netSink success:param]; 207 | } 208 | 209 | 210 | #pragma mark - Flutter Stream Handler 211 | -(FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events{ 212 | if ([arguments isKindOfClass:NSString.class]) { 213 | if ([arguments isEqualToString: @"event"]) { 214 | [_eventSink setDelegate:events]; 215 | } else if ([arguments isEqualToString:@"net"]){ 216 | [_netSink setDelegate:events]; 217 | } 218 | } 219 | return nil; 220 | } 221 | 222 | - (FlutterError *)onCancelWithArguments:(id)arguments{ 223 | if ([arguments isKindOfClass:NSString.class]) { 224 | if ([arguments isEqualToString: @"event"]) { 225 | [_eventSink setDelegate:nil]; 226 | } else if ([arguments isEqualToString:@"net"]){ 227 | [_netSink setDelegate:nil]; 228 | } 229 | } 230 | return nil; 231 | } 232 | 233 | 234 | 235 | 236 | #pragma mark - Flutter Method Channel 237 | -(void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{ 238 | NSLog(@"method: %@",call.method); 239 | NSDictionary *args = call.arguments; //读取参数 240 | if ([@"init" isEqualToString:call.method]) { 241 | // BOOL onlyAudio = [args[@"onlyAudio"] boolValue]; 242 | 243 | //读取 config 244 | 245 | NSDictionary *configMap = args[@"config"]; 246 | 247 | TXVodPlayConfig *playerConfig = [[TXVodPlayConfig alloc]init]; 248 | playerConfig.connectRetryCount = [configMap[@"connectRetryCount"] intValue]; 249 | playerConfig.connectRetryInterval = [configMap[@"connectRetryInterval"] intValue]; 250 | playerConfig.timeout = [configMap[@"timeout"] intValue]; 251 | playerConfig.keepLastFrameWhenStop = [configMap[@"keepLastFrameWhenStop"] boolValue]; 252 | [playerConfig setFirstStartPlayBufferTime: [configMap[@"firstStartPlayBufferTime"] intValue]]; 253 | // playerConfig.firstStartPlayBufferTime = [configMap[@"firstStartPlayBufferTime"] intValue]; 254 | [playerConfig setNextStartPlayBufferTime:[configMap[@"nextStartPlayBufferTime"] intValue]]; 255 | // playerConfig.nextStartPlayBufferTime = [configMap[@"nextStartPlayBufferTime"] intValue]; 256 | 257 | NSString *cacheFolderPath = [configMap[@"cacheFolderPath"] stringValue]; 258 | if (cacheFolderPath != nil && cacheFolderPath.length > 0) { 259 | playerConfig.cacheFolderPath = cacheFolderPath; 260 | } 261 | 262 | playerConfig.maxCacheItems = [configMap[@"maxCacheItems"] intValue]; 263 | playerConfig.headers = configMap[@"headers"]; 264 | 265 | playerConfig.enableAccurateSeek = [configMap[@"enableAccurateSeek"] boolValue]; 266 | playerConfig.progressInterval = [configMap[@"progressInterval"] doubleValue]; 267 | playerConfig.maxBufferSize = [configMap[@"maxBufferSize"] intValue]; 268 | 269 | NSString *overlayKey = [configMap[@"overlayKey"] stringValue]; 270 | 271 | if (overlayKey != nil && overlayKey.length > 0) { 272 | // playerConfig.overlayKey = overlayKey; 273 | [playerConfig setOverlayKey:overlayKey]; 274 | } 275 | 276 | NSString *overlayIv = [configMap[@"overlayIv"] stringValue]; 277 | 278 | if (overlayIv != nil && overlayIv.length > 0) { 279 | // playerConfig.overlayIv = overlayIv; 280 | [playerConfig setOverlayIv:overlayIv]; 281 | } 282 | 283 | [self createVodPlayer:playerConfig]; 284 | NSNumber *textureId = [NSNumber numberWithLongLong:_textureId]; 285 | result(textureId); 286 | } else if ([@"play" isEqualToString:call.method]) { 287 | NSString *url = args[@"url"]; 288 | int r = [self startPlay:url]; 289 | result(@(r)); 290 | } else if ([@"stop" isEqualToString:call.method]){ 291 | BOOL r = [self stopPlay]; 292 | result([NSNumber numberWithBool:r]); 293 | } else if ([@"isPlaying" isEqualToString:call.method]){ 294 | result([NSNumber numberWithBool:[self isPlaying]]); 295 | } else if ([@"pause" isEqualToString:call.method]) { 296 | [self pause]; 297 | result(nil); 298 | } else if ([@"resume" isEqualToString:call.method]){ 299 | [self resume]; 300 | result(nil); 301 | } else if ([@"seek" isEqualToString:call.method]){ 302 | float time = [args[@"time"] intValue]; 303 | BOOL r = [self seek:time]; 304 | result([NSNumber numberWithBool:r]); 305 | } else if ([@"setStartTime" isEqualToString:call.method]){ 306 | float time = [args[@"time"] floatValue]; 307 | [self setStartTime:time]; 308 | result(nil); 309 | } else if([@"currentPlaybackTime" isEqualToString:call.method]){ 310 | result(@([self currentPlaybackTime])); 311 | } else if ([@"duration" isEqualToString:call.method]){ 312 | result(@([self duration])); 313 | } else if ([@"playableDuration" isEqualToString:call.method]){ 314 | result(@([self playableDuration])); 315 | } else if ([@"setMute" isEqualToString:call.method]){ 316 | BOOL enable = [args[@"enable"] boolValue]; 317 | [self setMute:enable]; 318 | result(nil); 319 | } else if([@"setAudioPlayoutVolume" isEqualToString:call.method]){ 320 | int volume = [args[@"volume"] intValue]; 321 | volume = MAX(0, volume); 322 | volume = MIN(100, volume); 323 | [self setAudioPlayoutVolume:volume]; 324 | result(nil); 325 | } else if ([@"setRate" isEqualToString:call.method]){ 326 | int rate = [args[@"rate"] floatValue]; 327 | [self setRate:rate]; 328 | result(nil); 329 | } else if([@"setMirror" isEqualToString:call.method]){ 330 | BOOL mirror = [args[@"mirror"] boolValue]; 331 | [self setMirror:mirror]; 332 | result(nil); 333 | } else if([@"setLoop" isEqualToString:call.method]){ 334 | if (_vodPlayer != nil) { 335 | BOOL loop = [args[@"loop"] boolValue]; 336 | [_vodPlayer setLoop:loop]; 337 | } 338 | result(nil); 339 | } else if([@"setRenderRotation" isEqualToString:call.method]){ 340 | BOOL rotation = [args[@"rotation"] intValue]; 341 | [self setRenderRotation:rotation]; 342 | result(nil); 343 | } 344 | } 345 | 346 | @end 347 | -------------------------------------------------------------------------------- /ios/Classes/FltVideoPlayerPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | @interface FltVideoPlayerPlugin : NSObject 3 | @end 4 | -------------------------------------------------------------------------------- /ios/Classes/FltVideoPlayerPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FltVideoPlayerPlugin.h" 2 | #import "FltVodPlayer.h" 3 | #import "FltVideoViewFactory.h" 4 | 5 | 6 | @interface FltVideoPlayerPlugin() 7 | 8 | @property (nonatomic,strong) NSObject* registrar; //存储 flutter registrar 9 | @property (nonatomic,strong) NSMutableDictionary *players; //存储播放器对象,可能有多个 10 | 11 | @end 12 | 13 | @implementation FltVideoPlayerPlugin 14 | 15 | + (void)registerWithRegistrar:(NSObject*)registrar { 16 | 17 | FlutterMethodChannel* channel = [FlutterMethodChannel 18 | methodChannelWithName:@"plugins.bughub.icu/flt_video_player" 19 | binaryMessenger:[registrar messenger]]; 20 | 21 | FltVideoPlayerPlugin* instance = [[FltVideoPlayerPlugin alloc] initWithRegistrar:registrar]; 22 | 23 | [registrar addMethodCallDelegate:instance channel:channel]; 24 | 25 | [registrar registerViewFactory:[[FltVideoViewFactory alloc]initWithRegistrar:registrar onViewCreate:^(int64_t viewId,FltBasePlayer *player) { 26 | [[instance players] setValue:player forKey:[NSString stringWithFormat:@"%lld",viewId]]; 27 | }] withId:@"FltVideoView"]; 28 | } 29 | 30 | - (instancetype) initWithRegistrar: (NSObject *) registrar{ 31 | self = [super init]; 32 | if (self) { 33 | _registrar = registrar; 34 | _players = @{}.mutableCopy; 35 | } 36 | return self; 37 | } 38 | 39 | 40 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 41 | if ([@"getPlatformVersion" isEqualToString:call.method]) { 42 | result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); 43 | } else if ( [@"createVodPlayer" isEqualToString:call.method] ) { //初始化 vod player 44 | FltVodPlayer *vodPlayer = [[FltVodPlayer alloc]initWithRegistrar:self.registrar]; 45 | NSNumber *playerId = vodPlayer.playerId; 46 | _players[[playerId stringValue]] = vodPlayer; 47 | result(playerId); 48 | } else if ([@"releaseVodPlayer" isEqualToString:call.method] ) { 49 | NSDictionary *args = call.arguments; 50 | NSNumber *playerId = args[@"playerId"]; 51 | FltBasePlayer *player = [_players objectForKey:[playerId stringValue]]; 52 | [player destory]; 53 | if (player != nil) { 54 | [_players removeObjectForKey:playerId]; 55 | } 56 | } 57 | 58 | else { 59 | result(FlutterMethodNotImplemented); 60 | } 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /ios/Classes/FltVideoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FltVideoView.h 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/17. 6 | // 7 | 8 | #import 9 | #import 10 | #import "FltBasePlayer.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface FltVideoView : FltBasePlayer 15 | 16 | -(instancetype) initWithRegistrar:(id)registrar viewId:(int64_t)viewId; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /ios/Classes/FltVideoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FltVideoView.m 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/17. 6 | // 7 | 8 | #import "FltVideoView.h" 9 | #import 10 | 11 | @interface FltVideoView() 12 | 13 | @end 14 | 15 | @implementation FltVideoView{ 16 | UIView *videoView; 17 | } 18 | 19 | -(instancetype)initWithRegistrar:(id)registrar viewId:(int64_t)viewId{ 20 | NSLog(@"initWithRegistrar"); 21 | if (self = [super init]) { 22 | videoView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 1, 1)]; 23 | 24 | _eventSink = [EventSinkQueue new]; 25 | _netSink = [EventSinkQueue new]; 26 | 27 | __weak typeof(self) weakSelf = self; 28 | 29 | _methodChannel = [FlutterMethodChannel methodChannelWithName:[NSString stringWithFormat:@"plugins.bughub.icu/vodplayer/%lld",viewId] binaryMessenger:[registrar messenger]]; 30 | [_methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { 31 | [weakSelf handleMethodCall: call result:result]; 32 | }]; 33 | 34 | _eventChannel = [FlutterEventChannel eventChannelWithName:[@"plugins.bughub.icu/vodplayer/event/" stringByAppendingString:[NSString stringWithFormat:@"%lld",viewId]] binaryMessenger:[registrar messenger]]; 35 | [_eventChannel setStreamHandler:self]; 36 | 37 | _netChannel = [FlutterEventChannel eventChannelWithName:[@"plugins.bughub.icu/vodplayer/net/" stringByAppendingString:[NSString stringWithFormat:@"%lld",viewId]] binaryMessenger:[registrar messenger]]; 38 | [_netChannel setStreamHandler:self]; 39 | 40 | } 41 | return self; 42 | } 43 | 44 | - (void)createVodPlayer:(TXVodPlayConfig *)config{ 45 | [super createVodPlayer:config]; 46 | [_vodPlayer setupVideoWidget:videoView insertIndex:0]; 47 | 48 | } 49 | 50 | - (nonnull UIView *)view { 51 | return videoView; 52 | } 53 | 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /ios/Classes/FltVideoViewFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // FltVideoViewFactory.h 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/17. 6 | // 7 | 8 | #import 9 | #import 10 | #import "FltVideoView.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface FltVideoViewFactory : NSObject 15 | -(instancetype) initWithRegistrar:(id)registrar onViewCreate:(void (^)(int64_t,FltVideoView*))onViewCreate; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /ios/Classes/FltVideoViewFactory.m: -------------------------------------------------------------------------------- 1 | // 2 | // FltVideoViewFactory.m 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/17. 6 | // 7 | 8 | #import "FltVideoViewFactory.h" 9 | 10 | 11 | @implementation FltVideoViewFactory{ 12 | id _registrar; 13 | void (^_onViewCreate)(int64_t,FltVideoView*); 14 | } 15 | 16 | -(instancetype) initWithRegistrar:(id)registrar onViewCreate:(void (^)(int64_t,FltVideoView*))onViewCreate{ 17 | if (self = [super init]) { 18 | _registrar = registrar; 19 | _onViewCreate = onViewCreate; 20 | } 21 | return self; 22 | } 23 | 24 | - (NSObject *)createArgsCodec{ 25 | return FlutterStandardMessageCodec.sharedInstance; 26 | } 27 | 28 | - (nonnull NSObject *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { 29 | FltVideoView *videoView = [[FltVideoView alloc]initWithRegistrar:_registrar viewId:viewId]; 30 | _onViewCreate(viewId,videoView); 31 | return videoView; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ios/Classes/FltVodPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // FltVodPlayer.h 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/4. 6 | // 7 | 8 | 9 | #import 10 | #import "FltBasePlayer.h" 11 | 12 | @protocol FlutterPluginRegistrar; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface FltVodPlayer : FltBasePlayer 17 | 18 | -(instancetype) initWithRegistrar:(id)registrar; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /ios/Classes/FltVodPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // FltVodPlayer.m 3 | // flt_video_player 4 | // 5 | // Created by RandyWei on 2021/11/4. 6 | // 7 | 8 | #import "FltVodPlayer.h" 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import "EventSinkQueue.h" 14 | 15 | 16 | @interface FltVodPlayer() 17 | 18 | @end 19 | 20 | 21 | @implementation FltVodPlayer{ 22 | 23 | //最新一帧 24 | CVPixelBufferRef volatile _latestPixelBuffer; 25 | //旧的一帧 26 | CVPixelBufferRef _lastBuffer; 27 | 28 | id _registrar; 29 | id _textureRegistry; 30 | } 31 | 32 | 33 | -(instancetype)initWithRegistrar:(id)registrar{ 34 | if (self = [self init]) { 35 | _registrar = registrar; 36 | _lastBuffer = nil; 37 | _latestPixelBuffer = nil; 38 | _textureId = -1; 39 | 40 | _eventSink = [EventSinkQueue new]; 41 | _netSink = [EventSinkQueue new]; 42 | 43 | 44 | __weak typeof(self) weakSelf = self; 45 | 46 | _methodChannel = [FlutterMethodChannel methodChannelWithName:[@"plugins.bughub.icu/vodplayer/" stringByAppendingString:[self.playerId stringValue]] binaryMessenger:[registrar messenger]]; 47 | [_methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { 48 | [weakSelf handleMethodCall: call result:result]; 49 | }]; 50 | 51 | _eventChannel = [FlutterEventChannel eventChannelWithName:[@"plugins.bughub.icu/vodplayer/event/" stringByAppendingString:[self.playerId stringValue]] binaryMessenger:[registrar messenger]]; 52 | [_eventChannel setStreamHandler:self]; 53 | 54 | _netChannel = [FlutterEventChannel eventChannelWithName:[@"plugins.bughub.icu/vodplayer/net/" stringByAppendingString:[self.playerId stringValue]] binaryMessenger:[registrar messenger]]; 55 | [_netChannel setStreamHandler:self]; 56 | 57 | } 58 | 59 | return self; 60 | } 61 | 62 | 63 | 64 | -(void)destory{ 65 | [super destory]; 66 | 67 | if (_textureId >= 0) { 68 | [_textureRegistry unregisterTexture:_textureId]; 69 | _textureId = -1; 70 | _textureRegistry = nil; 71 | } 72 | 73 | CVPixelBufferRef old = _latestPixelBuffer; 74 | while (!OSAtomicCompareAndSwapPtrBarrier(old, nil, (void **)&_latestPixelBuffer)) { 75 | old = _latestPixelBuffer; 76 | } 77 | 78 | if (old) { 79 | CFRelease(old); 80 | } 81 | 82 | if (_lastBuffer) { 83 | CVPixelBufferRelease(_lastBuffer); 84 | _lastBuffer = nil; 85 | } 86 | } 87 | 88 | 89 | -(void)createVodPlayer:(TXVodPlayConfig*) config{ 90 | [super createVodPlayer:config]; 91 | [self setupTexture]; 92 | } 93 | /// 94 | ///配置播放器 95 | /// 96 | - (void)setupTexture{ 97 | if (_textureId < 0) { 98 | _textureRegistry = [_registrar textures]; 99 | int64_t tId = [_textureRegistry registerTexture:self]; 100 | _textureId = tId; 101 | } 102 | } 103 | 104 | 105 | #pragma mark - FlutterTexture 106 | - (CVPixelBufferRef _Nullable)copyPixelBuffer{ 107 | CVPixelBufferRef pixelBuffer = _latestPixelBuffer; 108 | while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { 109 | pixelBuffer = _latestPixelBuffer; 110 | } 111 | return pixelBuffer; 112 | } 113 | 114 | #pragma mark - TxVideoCustomProcessDelegate 115 | 116 | - (BOOL)onPlayerPixelBuffer:(CVPixelBufferRef)pixelBuffer{ 117 | if (_lastBuffer == nil) { 118 | _lastBuffer = CVPixelBufferRetain(pixelBuffer); 119 | CFRetain(pixelBuffer); 120 | } else if (_lastBuffer != pixelBuffer) { 121 | CVPixelBufferRelease(_lastBuffer); 122 | _lastBuffer = CVPixelBufferRetain(pixelBuffer); 123 | CFRetain(pixelBuffer); 124 | } 125 | 126 | CVPixelBufferRef newBuffer = pixelBuffer; 127 | 128 | CVPixelBufferRef old = _latestPixelBuffer; 129 | 130 | while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer, (void **)&_latestPixelBuffer)) { 131 | old = _latestPixelBuffer; 132 | } 133 | 134 | if (old && old != pixelBuffer) { 135 | CFRelease(old); 136 | } 137 | 138 | if (_textureId >= 0) { 139 | [_textureRegistry textureFrameAvailable:_textureId]; 140 | } 141 | 142 | return NO; 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /ios/flt_video_player.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint flt_video_player.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'flt_video_player' 7 | s.version = '1.0.0' 8 | s.summary = 'A Video Player Flutter plugin based on TXVodPlayer' 9 | s.description = <<-DESC 10 | A Video Player Flutter plugin based on TXVodPlayer 11 | DESC 12 | s.homepage = 'http://www.bughub.icu:8888' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Randy Wei' => 'smile561607154@163.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.public_header_files = 'Classes/**/*.h' 18 | s.dependency 'Flutter' 19 | s.dependency 'TXLiteAVSDK_Player', '~> 9.2.10637' #集成腾讯原生播放器 20 | s.static_framework = true 21 | s.platform = :ios, '9.0' 22 | 23 | # Flutter.framework does not contain a i386 slice. 24 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 25 | 26 | end 27 | -------------------------------------------------------------------------------- /lib/flt_video_player.dart: -------------------------------------------------------------------------------- 1 | export 'src/plugin.dart'; 2 | export 'src/vod/vod_player.dart'; 3 | export 'src/vod/vodplayer_controller.dart'; 4 | export 'src/vod/player_config.dart'; 5 | export 'src/vod/player_define.dart'; 6 | export 'src/vod/platform_video_view.dart'; 7 | -------------------------------------------------------------------------------- /lib/src/plugin.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/5.
3 | /// 4 | /// 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | 8 | import 'package:flutter/services.dart'; 9 | 10 | class Plugin { 11 | static const methodChannelPrefix = "plugins.bughub.icu"; 12 | 13 | static const MethodChannel _channel = 14 | MethodChannel('$methodChannelPrefix/flt_video_player'); 15 | 16 | static Future get platformVersion async { 17 | final String? version = await _channel.invokeMethod('getPlatformVersion'); 18 | return version; 19 | } 20 | 21 | static Future createVodPlayer(Map configJson) async { 22 | return await _channel 23 | .invokeMethod('createVodPlayer', {"config": configJson}); 24 | } 25 | 26 | static Future releasePlayer(int playerId) async { 27 | return await _channel.invokeMethod("releaseVodPlayer", {"playerId": playerId}); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/vod/platform_video_view.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/22.
3 | /// 4 | import 'dart:io'; 5 | 6 | import 'package:flutter/services.dart'; 7 | import "package:flutter/widgets.dart"; 8 | 9 | class PlatformVideoView extends StatelessWidget { 10 | const PlatformVideoView({Key? key, this.onPlatformViewCreated}) 11 | : super(key: key); 12 | 13 | final Function(int)? onPlatformViewCreated; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Platform.isAndroid 18 | ? AndroidView( 19 | viewType: "FltVideoView", 20 | creationParamsCodec: const StandardMessageCodec(), 21 | onPlatformViewCreated: onPlatformViewCreated, 22 | ) 23 | : UiKitView( 24 | viewType: "FltVideoView", 25 | creationParamsCodec: const StandardMessageCodec(), 26 | onPlatformViewCreated: onPlatformViewCreated, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/vod/player_config.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/5.
3 | /// 4 | class PlayerConfig { 5 | /// 播放器连接重试次数:最小值为1,最大值为10,默认值为 3 6 | int connectRetryCount = 3; 7 | 8 | /// 播放器连接重试间隔:单位秒,最小值为3, 最大值为30,默认值为3 9 | int connectRetryInterval = 3; 10 | 11 | /// 超时时间:单位秒,默认10s 12 | int timeout = 10; 13 | 14 | /// stopPlay 的时候是否保留最后一帧画面,默认值为 NO 15 | bool keepLastFrameWhenStop = false; 16 | 17 | /// 首缓需要加载的数据时长,单位ms, 默认值为100ms 18 | int firstStartPlayBufferTime = 100; 19 | 20 | /// 缓冲时(缓冲数据不够引起的二次缓冲,或者seek引起的拖动缓冲)最少要缓存多长的数据才能结束缓冲,单位ms,默认值为250ms 21 | int nextStartPlayBufferTime = 250; 22 | 23 | /// 注意:缓存目录应该是单独的目录,SDK可能会清掉其中的文件 24 | ///< 视频缓存目录,点播MP4、HLS有效 25 | String cacheFolderPath = ""; 26 | 27 | ///< 最多缓存文件个数 28 | int maxCacheItems = 0; 29 | 30 | ///< 自定义 HTTP Headers 31 | Map headers = {}; 32 | 33 | ///< 是否精确 seek,默认YES。开启精确后seek,seek 的时间平均多出200ms 34 | bool enableAccurateSeek = true; 35 | 36 | /// 设置进度回调间隔时间 37 | /// 若不设置,SDK默认间隔0.5秒回调一次 38 | double progressInterval = 0.5; 39 | 40 | /// 最大预加载大小,单位 MB 41 | /// 此设置会影响playableDuration,设置越大,提前缓存的越多 42 | int maxBufferSize = 0; 43 | 44 | /// HLS EXT-X-KEY 加密key 45 | String overlayKey = ""; 46 | 47 | /// HLS EXT-X-KEY 加密Iv 48 | String overlayIv = ""; 49 | 50 | Map toJson() => { 51 | 'connectRetryCount': connectRetryCount, 52 | 'connectRetryInterval': connectRetryInterval, 53 | 'timeout': timeout, 54 | 'keepLastFrameWhenStop': keepLastFrameWhenStop, 55 | 'firstStartPlayBufferTime': firstStartPlayBufferTime, 56 | 'nextStartPlayBufferTime': nextStartPlayBufferTime, 57 | 'cacheFolderPath': cacheFolderPath, 58 | 'maxCacheItems': maxCacheItems, 59 | 'headers': headers, 60 | 'enableAccurateSeek': enableAccurateSeek, 61 | 'progressInterval': progressInterval, 62 | 'maxBufferSize': maxBufferSize, 63 | 'overlayKey': overlayKey, 64 | 'overlayIv': overlayIv 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/vod/player_define.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/5.
3 | /// 4 | 5 | class PlayerValue { 6 | final PlayerState state; 7 | 8 | double duration = 0; 9 | double progress = 0; 10 | 11 | PlayerValue.uninitialized() : this(state: PlayerState.stopped); 12 | 13 | PlayerValue({required this.state}); 14 | 15 | PlayerValue copyWith({PlayerState? state}) { 16 | return PlayerValue(state: state ?? this.state); 17 | } 18 | } 19 | 20 | enum PlayerState { 21 | paused, // 暂停播放 22 | failed, // 播放失败 23 | buffering, // 缓冲中 24 | playing, // 播放中 25 | stopped, // 停止播放 26 | disposed // 控件释放了 27 | } 28 | 29 | enum RenderRotation { 30 | homeOrientaionRight, //< HOME 键在右边,横屏模式 31 | homeOrientationDown, //< HOME 键在下面,手机直播中最常见的竖屏直播模式 32 | homeOrientationLeft, //< HOME 键在左边,横屏模式 33 | homeOrientationUp, //< HOME 键在上边,竖屏直播(适合小米 MIX2) 34 | } 35 | 36 | /// 37 | /// 渲染糊弄 38 | /// 39 | enum RenderType { 40 | texture, //外接纹理 41 | platformView 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/vod/vod_player.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// Created by wei on 2021/11/5.
3 | /// 4 | /// 5 | import 'package:flt_video_player/src/vod/platform_video_view.dart'; 6 | import 'package:flt_video_player/src/vod/player_define.dart'; 7 | import 'package:flt_video_player/src/vod/vodplayer_controller.dart'; 8 | import 'package:flutter/widgets.dart'; 9 | 10 | class VodPlayer extends StatefulWidget { 11 | final VodPlayerController controller; 12 | 13 | const VodPlayer({Key? key, required this.controller}) : super(key: key); 14 | 15 | @override 16 | _VodPlayerState createState() => _VodPlayerState(); 17 | } 18 | 19 | class _VodPlayerState extends State { 20 | int _textureId = -1; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | 26 | if (widget.controller.renderType == RenderType.texture) { 27 | widget.controller.textureId.then((value) { 28 | setState(() { 29 | _textureId = value; 30 | }); 31 | }); 32 | } 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return widget.controller.renderType == RenderType.texture 38 | ? (_textureId == -1 ? Container() : Texture(textureId: _textureId)) 39 | : PlatformVideoView( 40 | onPlatformViewCreated: widget.controller.onPlatformViewCreated, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/vod/vodplayer_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:core'; 3 | 4 | import 'package:flt_video_player/flt_video_player.dart'; 5 | import 'package:flt_video_player/src/plugin.dart'; 6 | import 'package:flt_video_player/src/vod/player_define.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter/services.dart'; 10 | 11 | /// 12 | /// Created by wei on 2021/11/5.
13 | /// 14 | 15 | class VodPlayerController extends ChangeNotifier 16 | implements ValueListenable { 17 | int _playerId = -1; //播放器 id 18 | 19 | //播放器调用通道 20 | MethodChannel? _channel; 21 | 22 | late final Completer _initPlayer; 23 | late final Completer _createTexture; 24 | 25 | bool _isDisposed = false; 26 | bool _isNeedDisposed = false; 27 | 28 | late PlayerValue _value; 29 | PlayerState? _state; 30 | 31 | //播放事件广播器 32 | final StreamController> _eventStreamController = 33 | StreamController.broadcast(); 34 | 35 | Stream> get onPlayerEvent => 36 | _eventStreamController.stream; 37 | 38 | StreamSubscription? _eventSubScription; 39 | 40 | //网络事件广播 41 | final StreamController> _netStreamController = 42 | StreamController.broadcast(); 43 | 44 | Stream> get onNetEvent => _netStreamController.stream; 45 | 46 | StreamSubscription? _netSubScription; 47 | 48 | //状态广播 49 | final StreamController _stateStreamController = 50 | StreamController.broadcast(); 51 | 52 | Stream get playState => _stateStreamController.stream; 53 | 54 | PlayerConfig? config; 55 | 56 | RenderType? renderType; 57 | 58 | double _rate = 1.0; 59 | 60 | double get rate { 61 | return _rate; 62 | } 63 | 64 | VodPlayerController({this.config, this.renderType = RenderType.texture}) 65 | : _initPlayer = Completer(), 66 | _createTexture = Completer() { 67 | _value = PlayerValue.uninitialized(); 68 | _state = _value.state; 69 | if (renderType == RenderType.texture) { 70 | _create(); 71 | } 72 | } 73 | 74 | Future get textureId async { 75 | return _createTexture.future; 76 | } 77 | 78 | /// 79 | /// 创建播放器 80 | /// 81 | Future _create() async { 82 | var configJson = config?.toJson() ?? {}; 83 | _playerId = await Plugin.createVodPlayer(configJson); 84 | _initChannel(); 85 | } 86 | 87 | void _initChannel() { 88 | //每一个播放器对象创建独立的通信通道 89 | _channel = 90 | MethodChannel("${Plugin.methodChannelPrefix}/vodplayer/$_playerId"); 91 | 92 | _eventSubScription = 93 | EventChannel("${Plugin.methodChannelPrefix}/vodplayer/event/$_playerId") 94 | .receiveBroadcastStream("event") 95 | .listen(_eventHandler); 96 | 97 | _netSubScription = 98 | EventChannel("${Plugin.methodChannelPrefix}/vodplayer/net/$_playerId") 99 | .receiveBroadcastStream("net") 100 | .listen(_netHandler); 101 | if (!_initPlayer.isCompleted) _initPlayer.complete(_playerId); 102 | } 103 | 104 | /// 105 | /// PlatformView 初始化完成回调 106 | /// 107 | void onPlatformViewCreated(int id) { 108 | debugPrint("onPlatformViewCreated:$id"); 109 | _playerId = id; 110 | _initChannel(); 111 | } 112 | 113 | /// 114 | /// 初始化 115 | /// 116 | Future initialize() async { 117 | if (_isNeedDisposed) return; 118 | 119 | await _initPlayer.future; //等待初始化完成 120 | 121 | debugPrint("initialize"); 122 | //通过初始化得到 Texture id,不等同 palyer id 123 | final textureId = await _channel?.invokeMethod("init"); 124 | 125 | _createTexture.complete(textureId); 126 | _updateState(PlayerState.stopped); 127 | } 128 | 129 | /// 130 | /// 播放视频 131 | /// 132 | Future play(String url) async { 133 | await _initPlayer.future; 134 | await _createTexture.future; 135 | 136 | _updateState(PlayerState.buffering); 137 | 138 | final result = await _channel?.invokeMethod("play", {"url": url}); 139 | return result == 0; 140 | } 141 | 142 | /// 143 | /// 停止播放 144 | /// 145 | Future stop() async { 146 | if (_isNeedDisposed) return false; 147 | 148 | await _initPlayer.future; 149 | 150 | final result = await _channel?.invokeMethod("stop"); 151 | _updateState(result == 0 ? PlayerState.stopped : _state!); 152 | return result == 0; 153 | } 154 | 155 | /// 156 | /// 暂停播放 157 | /// 158 | Future pause() async { 159 | if (_isNeedDisposed) return; 160 | 161 | await _initPlayer.future; 162 | 163 | await _channel?.invokeMethod("pause"); 164 | _updateState(PlayerState.paused); 165 | } 166 | 167 | /// 168 | /// 继续播放 169 | /// 170 | Future resume() async { 171 | if (_isNeedDisposed) return; 172 | 173 | await _initPlayer.future; 174 | 175 | await _channel?.invokeMethod("resume"); 176 | _updateState(PlayerState.buffering); 177 | } 178 | 179 | /// 180 | /// 跳转到某个时间点 181 | /// 182 | Future seek(int time) async { 183 | if (_isNeedDisposed) return; 184 | 185 | await _initPlayer.future; 186 | 187 | await _channel?.invokeMethod("seek", {"time": time}); 188 | } 189 | 190 | /// 191 | /// 开始播放前设置时间点 192 | /// 193 | Future setStartTime(int time) async { 194 | if (_isNeedDisposed) return; 195 | 196 | await _initPlayer.future; 197 | 198 | await _channel?.invokeMethod("setStartTime", {"time": time}); 199 | } 200 | 201 | /// 202 | /// 设置静音 203 | /// 204 | Future setMute(bool enable) async { 205 | if (_isNeedDisposed) return; 206 | 207 | await _initPlayer.future; 208 | 209 | await _channel?.invokeMethod("setMute", {"enable": enable}); 210 | } 211 | 212 | /// 设置音量大小 213 | /// @param volume 音量大小。范围:0 ~ 100。 214 | Future setAudioPlaybackVolume(int volume) async { 215 | if (_isNeedDisposed) return; 216 | 217 | await _initPlayer.future; 218 | 219 | await _channel?.invokeMethod("setAudioPlaybackVolume", {"volume": volume}); 220 | } 221 | 222 | /// 设置播放速率 223 | /// @param rate 正常速度为1.0;小于为慢速;大于为快速。最大建议不超过2.0 224 | Future setRate(double rate) async { 225 | if (_isNeedDisposed) return; 226 | 227 | _rate = rate; 228 | 229 | await _initPlayer.future; 230 | 231 | await _channel?.invokeMethod("setRate", {"rate": rate}); 232 | } 233 | 234 | /// 设置画面镜像 235 | /// 236 | Future setMirror(bool mirror) async { 237 | if (_isNeedDisposed) return; 238 | 239 | await _initPlayer.future; 240 | 241 | await _channel?.invokeMethod("setMirror", {"mirror": mirror}); 242 | } 243 | 244 | /// 245 | /// 设置画面旋转 246 | /// homeOrientaionRight, //< HOME 键在右边,横屏模式 247 | /// homeOrientationDown, //< HOME 键在下面,手机直播中最常见的竖屏直播模式 248 | /// homeOrientationLeft, //< HOME 键在左边,横屏模式 249 | /// homeOrientationUp, //< HOME 键在上边,竖屏直播(适合小米 MIX2) 250 | /// 251 | Future setRenderRotation(RenderRotation rotaion) async { 252 | if (_isNeedDisposed) return; 253 | 254 | await _initPlayer.future; 255 | 256 | await _channel 257 | ?.invokeMethod("setRenderRotation", {"rotaion": rotaion.index}); 258 | } 259 | 260 | ///获取是否正在播放 261 | Future get isPlaying async { 262 | if (_isNeedDisposed) return false; 263 | 264 | await _initPlayer.future; 265 | 266 | return await _channel?.invokeMethod("isPlaying"); 267 | } 268 | 269 | ///当前播放进度 270 | Future get currentPlaybackTime async { 271 | if (_isNeedDisposed) return 0; 272 | 273 | await _initPlayer.future; 274 | 275 | return await _channel?.invokeMethod("currentPlaybackTime"); 276 | } 277 | 278 | ///获取视频总进度 279 | Future get duration async { 280 | if (_isNeedDisposed) return 0; 281 | 282 | await _initPlayer.future; 283 | 284 | return await _channel?.invokeMethod("duration"); 285 | } 286 | 287 | ///获取可播放进度 288 | Future get playableDuration async { 289 | if (_isNeedDisposed) return 0; 290 | 291 | await _initPlayer.future; 292 | 293 | return await _channel?.invokeMethod("playableDuration"); 294 | } 295 | 296 | /// 297 | /// 播放器事件 298 | /// event 类型 299 | /// see:https://cloud.tencent.com/document/product/454/7886#.E6.92.AD.E6.94.BE.E4.BA.8B.E4.BB.B6 300 | /// 301 | void _eventHandler(event) { 302 | if (event == null) return; 303 | 304 | final Map map = event; 305 | debugPrint("_eventHandler:${map["event"]}"); 306 | switch (map["event"]) { 307 | case 2002: 308 | break; 309 | case 2003: 310 | if (_isNeedDisposed) return; 311 | if (_state == PlayerState.buffering) _updateState(PlayerState.playing); 312 | break; 313 | case 2004: 314 | if (_isNeedDisposed) return; 315 | if (_state == PlayerState.buffering) _updateState(PlayerState.playing); 316 | break; 317 | case 2005: //播放进度 318 | _value.duration = event["EVT_PLAY_DURATION"] * 1.0; 319 | _value.progress = event["EVT_PLAY_PROGRESS"] * 1.0; 320 | break; 321 | case 2006: 322 | _updateState(PlayerState.stopped); 323 | break; 324 | case 2007: 325 | _updateState(PlayerState.buffering); 326 | break; 327 | case 2009: //下行视频分辨率改变 328 | break; 329 | case 2013: //点播加载完成 330 | break; 331 | case 2014: //loading 结束 332 | break; 333 | case -2301: 334 | _updateState(PlayerState.failed); 335 | break; 336 | case -2303: 337 | _updateState(PlayerState.failed); 338 | break; 339 | case -2305: 340 | _updateState(PlayerState.failed); 341 | break; 342 | case 2103: 343 | break; 344 | case 3001: 345 | break; 346 | case 3002: 347 | break; 348 | case 3003: 349 | break; 350 | 351 | default: 352 | break; 353 | } 354 | 355 | _eventStreamController.add(map); 356 | } 357 | 358 | /// 359 | /// 网络事件 360 | /// 361 | void _netHandler(event) { 362 | if (event == null) return; 363 | 364 | final Map map = event; 365 | 366 | _netStreamController.add(map); 367 | } 368 | 369 | /// 370 | /// 更新播放器状态 371 | /// 372 | void _updateState(PlayerState playerState) { 373 | value = _value.copyWith(state: playerState); 374 | _state = value.state; 375 | debugPrint("_updateState:$_state"); 376 | if (_state == null) return; 377 | _stateStreamController.add(_state!); 378 | } 379 | 380 | /// 381 | /// 释放播放器 382 | /// 383 | Future _release() async { 384 | await _initPlayer.future; 385 | await Plugin.releasePlayer(_playerId); 386 | } 387 | 388 | @override 389 | PlayerValue get value => _value; 390 | 391 | set value(PlayerValue value) { 392 | if (_value == value) return; 393 | _value = value; 394 | notifyListeners(); 395 | } 396 | 397 | @override 398 | void dispose() async { 399 | _isNeedDisposed = true; 400 | 401 | if (!_isDisposed) { 402 | stop(); 403 | 404 | await _eventSubScription?.cancel(); 405 | _eventSubScription = null; 406 | 407 | await _netSubScription?.cancel(); 408 | _netSubScription = null; 409 | 410 | await _release(); 411 | 412 | _updateState(PlayerState.disposed); 413 | 414 | _isDisposed = true; 415 | _stateStreamController.close(); 416 | _eventStreamController.close(); 417 | _netStreamController.close(); 418 | } 419 | 420 | super.dispose(); 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.flutter-io.cn" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.flutter-io.cn" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.flutter-io.cn" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.flutter-io.cn" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" 41 | url: "https://pub.flutter-io.cn" 42 | source: hosted 43 | version: "1.17.1" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.flutter-io.cn" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_lints: 58 | dependency: "direct dev" 59 | description: 60 | name: flutter_lints 61 | sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 62 | url: "https://pub.flutter-io.cn" 63 | source: hosted 64 | version: "1.0.4" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | js: 71 | dependency: transitive 72 | description: 73 | name: js 74 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 75 | url: "https://pub.flutter-io.cn" 76 | source: hosted 77 | version: "0.6.7" 78 | lints: 79 | dependency: transitive 80 | description: 81 | name: lints 82 | sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c 83 | url: "https://pub.flutter-io.cn" 84 | source: hosted 85 | version: "1.0.1" 86 | matcher: 87 | dependency: transitive 88 | description: 89 | name: matcher 90 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" 91 | url: "https://pub.flutter-io.cn" 92 | source: hosted 93 | version: "0.12.15" 94 | material_color_utilities: 95 | dependency: transitive 96 | description: 97 | name: material_color_utilities 98 | sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "0.2.0" 102 | meta: 103 | dependency: transitive 104 | description: 105 | name: meta 106 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.9.1" 110 | path: 111 | dependency: transitive 112 | description: 113 | name: path 114 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 115 | url: "https://pub.flutter-io.cn" 116 | source: hosted 117 | version: "1.8.3" 118 | sky_engine: 119 | dependency: transitive 120 | description: flutter 121 | source: sdk 122 | version: "0.0.99" 123 | source_span: 124 | dependency: transitive 125 | description: 126 | name: source_span 127 | sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.9.1" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 136 | url: "https://pub.flutter-io.cn" 137 | source: hosted 138 | version: "1.11.0" 139 | stream_channel: 140 | dependency: transitive 141 | description: 142 | name: stream_channel 143 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 144 | url: "https://pub.flutter-io.cn" 145 | source: hosted 146 | version: "2.1.1" 147 | string_scanner: 148 | dependency: transitive 149 | description: 150 | name: string_scanner 151 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 152 | url: "https://pub.flutter-io.cn" 153 | source: hosted 154 | version: "1.2.0" 155 | term_glyph: 156 | dependency: transitive 157 | description: 158 | name: term_glyph 159 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 160 | url: "https://pub.flutter-io.cn" 161 | source: hosted 162 | version: "1.2.1" 163 | test_api: 164 | dependency: transitive 165 | description: 166 | name: test_api 167 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb 168 | url: "https://pub.flutter-io.cn" 169 | source: hosted 170 | version: "0.5.1" 171 | vector_math: 172 | dependency: transitive 173 | description: 174 | name: vector_math 175 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 176 | url: "https://pub.flutter-io.cn" 177 | source: hosted 178 | version: "2.1.4" 179 | sdks: 180 | dart: ">=3.0.0-0 <4.0.0" 181 | flutter: ">=1.20.0" 182 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flt_video_player 2 | description: A new Flutter project. 3 | version: 0.0.1 4 | homepage: 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.20.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | flutter_lints: ^1.0.0 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://dart.dev/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | # This section identifies this Flutter project as a plugin project. 25 | # The 'pluginClass' and Android 'package' identifiers should not ordinarily 26 | # be modified. They are used by the tooling to maintain consistency when 27 | # adding or updating assets for this project. 28 | plugin: 29 | platforms: 30 | android: 31 | package: icu.bughub.plugins.video_player.flt_video_player 32 | pluginClass: FltVideoPlayerPlugin 33 | ios: 34 | pluginClass: FltVideoPlayerPlugin 35 | 36 | # To add assets to your plugin package, add an assets section, like this: 37 | # assets: 38 | # - images/a_dot_burr.jpeg 39 | # - images/a_dot_ham.jpeg 40 | # 41 | # For details regarding assets in packages, see 42 | # https://flutter.dev/assets-and-images/#from-packages 43 | # 44 | # An image asset can refer to one or more resolution-specific "variants", see 45 | # https://flutter.dev/assets-and-images/#resolution-aware. 46 | 47 | # To add custom fonts to your plugin package, add a fonts section here, 48 | # in this "flutter" section. Each entry in this list should have a 49 | # "family" key with the font family name, and a "fonts" key with a 50 | # list giving the asset and other descriptors for the font. For 51 | # example: 52 | # fonts: 53 | # - family: Schyler 54 | # fonts: 55 | # - asset: fonts/Schyler-Regular.ttf 56 | # - asset: fonts/Schyler-Italic.ttf 57 | # style: italic 58 | # - family: Trajan Pro 59 | # fonts: 60 | # - asset: fonts/TrajanPro.ttf 61 | # - asset: fonts/TrajanPro_Bold.ttf 62 | # weight: 700 63 | # 64 | # For details regarding fonts in packages, see 65 | # https://flutter.dev/custom-fonts/#from-packages 66 | -------------------------------------------------------------------------------- /test/flt_video_player_test.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:flt_video_player/flt_video_player.dart'; 5 | 6 | void main() { 7 | const MethodChannel channel = MethodChannel('flt_video_player'); 8 | 9 | TestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | setUp(() { 12 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 13 | return '42'; 14 | }); 15 | }); 16 | 17 | tearDown(() { 18 | channel.setMockMethodCallHandler(null); 19 | }); 20 | 21 | test('getPlatformVersion', () async { 22 | expect(await Plugin.platformVersion, '42'); 23 | }); 24 | } 25 | --------------------------------------------------------------------------------