├── .gitignore ├── .idea ├── libraries │ ├── Dart_SDK.xml │ ├── Flutter_Plugins.xml │ └── Flutter_for_Android.xml ├── misc.xml ├── modules.xml └── runConfigurations │ └── example_lib_main_dart.xml ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── libs │ └── LiteAVSDK_Player_6.8.7969.aar ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── plus │ └── tencentplayer │ └── wilson │ └── flutter │ └── flutter_tencentplayer_plus │ ├── FlutterTencentplayerPlusPlugin.java │ ├── TencentQueuingEventSink.java │ └── Util.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── plus │ │ │ │ │ └── tencentplayer │ │ │ │ │ └── wilson │ │ │ │ │ └── flutter │ │ │ │ │ └── flutter_tencentplayer_plus_example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── 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 │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── 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 │ ├── home_page.dart │ ├── main.dart │ ├── video_play_page.dart │ └── video_play_page2.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── flutter_tencentplayer_plus.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FLTDownLoadManager.h │ ├── FLTDownLoadManager.m │ ├── FLTFrameUpdater.h │ ├── FLTFrameUpdater.m │ ├── FLTVideoPlayer.h │ ├── FLTVideoPlayer.m │ ├── FlutterTencentplayerPlusPlugin.h │ └── FlutterTencentplayerPlusPlugin.m └── flutter_tencentplayer_plus.podspec ├── lib ├── controller │ ├── download_controller.dart │ └── tencent_player_controller.dart ├── flutter_tencentplayer_plus.dart ├── model │ ├── download_value.dart │ ├── player_config.dart │ └── tentcent_player_value.dart └── view │ └── tencent_player.dart ├── pubspec.lock ├── pubspec.yaml ├── readme └── android.gif └── test └── flutter_tencentplayer_plus_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | *.iml 10 | .gradle 11 | /local.properties 12 | /.idea/workspace.xml 13 | /.idea/libraries 14 | .DS_Store 15 | /build 16 | /captures 17 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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: 68587a0916366e9512a78df22c44163d041dd5f3 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 2 | 3 | * 格式化代码 4 | 5 | 6 | ## 0.0.1 7 | 8 | * 初始化项目 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 简介 2 | 3 | flutter_tencentplayer_plus 是基于腾讯云点播封装的flutter版的播放器插件 提供video_player 相似的api, 是flutter_tencentplayer的优化版本 ,flutter_tencentplayer 开源项目是我和[大帅](https://github.com/qq326646683/flutter_tencentplayer)一起合作的一个开源项目, 4 | 5 | 6 | ### 支持功能 7 | 1. 支持直播源 8 | 2. 视频跳转 9 | 3. 切换视频源 10 | 4. 边下边播放 11 | 5. 清晰度切换 12 | 6. 设置播放速度 13 | 14 | 15 | 16 | ### 效果图 17 | ![](readme/android.gif) 18 | 19 | 20 | 21 | ### 1.Setup 22 | 23 | 24 | 25 | flutter_tencentplayer_plus: ${last_version} 26 | 27 | or 28 | 29 | flutter_tencentplayer_plus: 30 | git: 31 | url: https://github.com/yxwandroid/flutter_tencentplayer_plus.git 32 | 33 | > For Android 34 | 35 | 1. project/android/build.gradle 添加依赖的aar: 36 | 37 | ``` 38 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 39 | def plugins = new Properties() 40 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 41 | if (pluginsFile.exists()) { 42 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 43 | } 44 | 45 | allprojects { 46 | repositories { 47 | google() 48 | jcenter() 49 | flatDir { 50 | dirs "${plugins.get("flutter_tencentplayer_plus")}android/libs" 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | 2. AndroidManifest.xml 声明权限: 57 | 58 | ``` 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ``` 77 | 78 | > For Ios 79 | 80 | 81 | 82 | ``` 83 | 84 | //项目的info.plist文件上添加如下权限 85 | NSAppTransportSecurity 86 | 87 | NSAllowsArbitraryLoads 88 | 89 | 90 | ``` 91 | 92 | ### 2.Usage(TencentPlayer) 93 | 94 | 95 | #### 1.初始化播放器 96 | 97 | 提供 asset、network、filePath、fileId四种方式播放 98 | 99 | 100 | ```dart 101 | TencentPlayerController _controller; 102 | 103 | _MyAppState() { 104 | listener = () { 105 | if (!mounted) { 106 | return; 107 | } 108 | setState(() {}); 109 | }; 110 | } 111 | 112 | initState() { 113 | _controller = TencentPlayerController.network('http://file.jinxianyun.com/testhaha.mp4', playerConfig: PlayerConfig()) 114 | //_controller = TencentPlayerController.asset('static/tencent1.mp4') 115 | //_controller = TencentPlayerController.file('/storage/emulated/0/test.mp4') 116 | //_controller = TencentPlayerController.network(null, playerConfig: {auth: {"appId": 1252463788, "fileId": '4564972819220421305'}}) 117 | ..initialize().then((_) { 118 | setState(() {}); 119 | }); 120 | _controller.addListener(listener); 121 | } 122 | ``` 123 | 124 | #### 2.PlayerConfig (播放器配置参数 ) 125 | 126 | ```dart 127 | _controller = TencentPlayerController.network(url, playerConfig: PlayerConfig()) 128 | 129 | ``` 130 | 131 | Prop | Type | Default | Note 132 | ---|---|---|--- 133 | autoPlay | bool | true | 是否自动播放 134 | loop | bool | false | 是否循环播放 135 | headers | Map | | 请求头 136 | cachePath | String | | 缓存路径(边播放边下载) 137 | progressInterval | int | 200 | 播放进度回调频率(毫秒) 138 | startTime | int | 0 | 哪里开始播放(秒) 139 | auth | Map | | 云点播视频源appId&fileId 140 | 141 | 142 | 143 | #### 3.TencentPlayerValue (播放器回调) 144 | ```dart 145 | Text("总时长:" + _controller.value.duration.toString()) 146 | ``` 147 | Prop | Type | Note 148 | ---|---|--- 149 | initialized | bool | 是否初始化完成从而显示播放器 150 | aspectRatio | double | 用来控制播放器宽高比 151 | duration | Duration | 时长 152 | position | Duration | 播放进度 153 | playable | Duration | 缓冲进度 154 | isPlaying | bool | 是否在播放 155 | size | Size | 视频宽高 156 | isLoading | bool | 是否在加载 157 | netSpeed | int | 视频播放网速 158 | rate | double | 播放速度 159 | bitrateIndex | int | 视频清晰度 160 | 161 | #### 4.Event (播放器事件) 162 | 163 | a.跳转进度 164 | 165 | ```dart 166 | _controller.seekTo(Duration(seconds: 5)); 167 | 168 | ``` 169 | 170 | b.设置播放速度 171 | 172 | ```dart 173 | _controller.setRate(1.5); // 1.0 ~ 2.0 174 | 175 | ``` 176 | 177 | c.切换播放源 178 | 179 | ```dart 180 | controller?.removeListener(listener); 181 | controller?.pause(); 182 | controller = TencentPlayerController.network(url, playerConfig: PlayerConfig(startTime: startTime ?? controller.value.position.inSeconds)); 183 | controller?.initialize().then((_) { 184 | if (mounted) setState(() {}); 185 | }); 186 | controller?.addListener(listener); 187 | ``` 188 | 189 | d.切换清晰度(实质就是切换播放源) 190 | 191 | 192 | # 3.Usage(Download) 193 | 194 | > 离线下载, 支持断点续传(这里只支持m3u8视频、fileId), 支持多文件同时下载 195 | 196 | #### 1.初始化下载器 197 | 198 | ```dart 199 | DownloadController _downloadController; 200 | 201 | _MyAppState() { 202 | downloadListener = () { 203 | if (!mounted) { 204 | return; 205 | } 206 | setState(() {}); 207 | }; 208 | } 209 | 210 | initState() { 211 | _downloadController = DownloadController('/storage/emulated/0/tencentdownload', appId: 1252463788); 212 | _downloadController.addListener(downloadListener); 213 | } 214 | ``` 215 | #### 2.Event (下载事件) 216 | 217 | a. 下载 218 | 219 | ```dart 220 | _downloadController.dowload("4564972819220421305", quanlity: 2); 221 | // _downloadController.dowload("http://1253131631.vod2.myqcloud.com/26f327f9vodgzp1253131631/f4bdff799031868222924043041/playlist.m3u8"); 222 | ``` 223 | 224 | b. 暂停下载 225 | 226 | ```dart 227 | _downloadController.pauseDownload("4564972819220421305"); 228 | // _downloadController.stopDownload("http://1253131631.vod2.myqcloud.com/26f327f9vodgzp1253131631/f4bdff799031868222924043041/playlist.m3u8"); 229 | 230 | ``` 231 | 232 | b. 取消下载 233 | 234 | ```dart 235 | _downloadController.cancelDownload("4564972819220421305"); 236 | // _downloadController.cancelDownload("http://1253131631.vod2.myqcloud.com/26f327f9vodgzp1253131631/f4bdff799031868222924043041/playlist.m3u8"); 237 | 238 | ``` 239 | 240 | 241 | #### 3.DownloadValue (下载信息回调) 242 | >因为支持多文件同时下载,回调以Map返回,key为url/fileId 243 | 244 | Prop | Type | Note 245 | ---|---|--- 246 | downloadStatus | String | "start"、"progress"、"stop"、"complete"、"error" 247 | quanlity | int | 1: "FLU"、2: "SD"、3: "HD"、4: "FHD"、5: "2K"、6: "4K" 248 | duration | int | 249 | size | int | 文件大小 250 | downloadSize | int | 已下载大小 251 | progress | int | 已下载大小 252 | playPath | String | 下载文件的绝对路径 253 | isStop | bool | 是否暂停下载 254 | url | String | 下载的视频链接 255 | fileId | String | 下载的视频FileId 256 | error | String | 下载的错误信息 257 | 258 | 259 | 260 | ### 4.[Example](https://github.com/qq326646683/flutter_tencentplayer/blob/master/example/lib/main.dart) 261 | 262 | 263 | 264 | ### 5.更新内容 265 | 266 | 1,升级android,IOS LiteAVSDK 6.8.7969 267 | 268 | ### 6.参考 269 | 270 | [腾讯云点播android版本](https://github.com/tencentyun/SuperPlayer_Android/wiki) 271 | [腾讯云点播ios版本](https://github.com/tencentyun/SuperPlayer_iOS) 272 | [flutter_tencentplayer](https://github.com/qq326646683/flutter_tencentplayer 273 | ) 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /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 'plus.tencentplayer.wilson.flutter.flutter_tencentplayer_plus' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.2.1' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 28 26 | 27 | defaultConfig { 28 | minSdkVersion 16 29 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | } 35 | dependencies { 36 | implementation fileTree(include: ['*.jar'], dir: 'libs') 37 | implementation(name: 'LiteAVSDK_Player_6.8.7969', ext: 'aar') 38 | // implementation(name: 'lib_tcsuperplayer', ext: 'aar') 39 | // 超级播放器弹幕集成的第三方库 40 | // implementation 'com.github.ctiao:DanmakuFlameMaster:0.5.3' 41 | 42 | } 43 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | -------------------------------------------------------------------------------- /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-4.10.2-all.zip 6 | -------------------------------------------------------------------------------- /android/libs/LiteAVSDK_Player_6.8.7969.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/android/libs/LiteAVSDK_Player_6.8.7969.aar -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_tencentplayer_plus' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/plus/tencentplayer/wilson/flutter/flutter_tencentplayer_plus/FlutterTencentplayerPlusPlugin.java: -------------------------------------------------------------------------------- 1 | package plus.tencentplayer.wilson.flutter.flutter_tencentplayer_plus; 2 | 3 | import android.content.res.AssetManager; 4 | import android.os.Bundle; 5 | import android.util.Base64; 6 | import android.util.LongSparseArray; 7 | import android.view.Surface; 8 | 9 | 10 | import com.tencent.rtmp.ITXVodPlayListener; 11 | import com.tencent.rtmp.TXLiveConstants; 12 | import com.tencent.rtmp.TXPlayerAuthBuilder; 13 | import com.tencent.rtmp.TXVodPlayConfig; 14 | import com.tencent.rtmp.TXVodPlayer; 15 | import com.tencent.rtmp.downloader.ITXVodDownloadListener; 16 | import com.tencent.rtmp.downloader.TXVodDownloadDataSource; 17 | import com.tencent.rtmp.downloader.TXVodDownloadManager; 18 | import com.tencent.rtmp.downloader.TXVodDownloadMediaInfo; 19 | 20 | import java.io.File; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | import io.flutter.plugin.common.EventChannel; 28 | import io.flutter.plugin.common.MethodCall; 29 | import io.flutter.plugin.common.MethodChannel; 30 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 31 | import io.flutter.plugin.common.MethodChannel.Result; 32 | import io.flutter.plugin.common.PluginRegistry; 33 | import io.flutter.plugin.common.PluginRegistry.Registrar; 34 | import io.flutter.view.FlutterNativeView; 35 | import io.flutter.view.TextureRegistry; 36 | 37 | 38 | /** 39 | * FlutterTencentplayerPlugin 40 | */ 41 | public class FlutterTencentplayerPlusPlugin implements MethodCallHandler { 42 | 43 | ///////////////////// TencentPlayer 开始//////////////////// 44 | 45 | private static class TencentPlayer implements ITXVodPlayListener { 46 | private TXVodPlayer mVodPlayer; 47 | TXVodPlayConfig mPlayConfig; 48 | private Surface surface; 49 | TXPlayerAuthBuilder authBuilder; 50 | 51 | private final TextureRegistry.SurfaceTextureEntry textureEntry; 52 | 53 | private TencentQueuingEventSink eventSink = new TencentQueuingEventSink(); 54 | 55 | private final EventChannel eventChannel; 56 | 57 | private final Registrar mRegistrar; 58 | 59 | 60 | 61 | TencentPlayer( 62 | Registrar mRegistrar, 63 | EventChannel eventChannel, 64 | TextureRegistry.SurfaceTextureEntry textureEntry, 65 | MethodCall call, 66 | Result result) { 67 | this.eventChannel = eventChannel; 68 | this.textureEntry = textureEntry; 69 | this.mRegistrar = mRegistrar; 70 | 71 | 72 | mVodPlayer = new TXVodPlayer(mRegistrar.context()); 73 | 74 | setPlayConfig(call); 75 | 76 | setTencentPlayer(call); 77 | 78 | setFlutterBridge(eventChannel, textureEntry, result); 79 | 80 | setPlaySource(call); 81 | } 82 | 83 | 84 | private void setPlayConfig(MethodCall call) { 85 | mPlayConfig = new TXVodPlayConfig(); 86 | if (call.argument("cachePath") != null) { 87 | mPlayConfig.setCacheFolderPath(call.argument("cachePath").toString());// mPlayConfig.setCacheFolderPath(Environment.getExternalStorageDirectory().getPath() + "/nellcache"); 88 | mPlayConfig.setMaxCacheItems(1); 89 | } else { 90 | mPlayConfig.setCacheFolderPath(null); 91 | mPlayConfig.setMaxCacheItems(0); 92 | } 93 | if (call.argument("headers") != null) { 94 | mPlayConfig.setHeaders((Map) call.argument("headers")); 95 | } 96 | 97 | mPlayConfig.setProgressInterval(((Number) call.argument("progressInterval")).intValue()); 98 | mVodPlayer.setConfig(this.mPlayConfig); 99 | } 100 | 101 | private void setTencentPlayer(MethodCall call) { 102 | mVodPlayer.setVodListener(this); 103 | // mVodPlayer.enableHardwareDecode(true); 104 | mVodPlayer.setLoop((boolean) call.argument("loop")); 105 | if (call.argument("startTime") != null) { 106 | mVodPlayer.setStartTime(((Number)call.argument("startTime")).floatValue()); 107 | } 108 | mVodPlayer.setAutoPlay((boolean) call.argument("autoPlay")); 109 | 110 | } 111 | 112 | private void setFlutterBridge(EventChannel eventChannel, TextureRegistry.SurfaceTextureEntry textureEntry, Result result) { 113 | // 注册android向flutter发事件 114 | eventChannel.setStreamHandler( 115 | new EventChannel.StreamHandler() { 116 | @Override 117 | public void onListen(Object o, EventChannel.EventSink sink) { 118 | eventSink.setDelegate(sink); 119 | } 120 | 121 | @Override 122 | public void onCancel(Object o) { 123 | eventSink.setDelegate(null); 124 | } 125 | } 126 | ); 127 | 128 | surface = new Surface(textureEntry.surfaceTexture()); 129 | mVodPlayer.setSurface(surface); 130 | 131 | 132 | Map reply = new HashMap<>(); 133 | reply.put("textureId", textureEntry.id()); 134 | result.success(reply); 135 | } 136 | 137 | private void setPlaySource(MethodCall call) { 138 | // network FileId播放 139 | if (call.argument("auth") != null) { 140 | authBuilder = new TXPlayerAuthBuilder(); 141 | Map authMap = (Map)call.argument("auth"); 142 | authBuilder.setAppId(((Number)authMap.get("appId")).intValue()); 143 | authBuilder.setFileId(authMap.get("fileId").toString()); 144 | mVodPlayer.startPlay(authBuilder); 145 | } else { 146 | // asset播放 147 | if (call.argument("asset") != null) { 148 | String assetLookupKey = mRegistrar.lookupKeyForAsset(call.argument("asset").toString()); 149 | AssetManager assetManager = mRegistrar.context().getAssets(); 150 | try { 151 | InputStream inputStream = assetManager.open(assetLookupKey); 152 | String cacheDir = mRegistrar.context().getCacheDir().getAbsoluteFile().getPath(); 153 | String fileName = Base64.encodeToString(assetLookupKey.getBytes(), Base64.DEFAULT); 154 | File file = new File(cacheDir, fileName + ".mp4"); 155 | FileOutputStream fileOutputStream = new FileOutputStream(file); 156 | if(!file.exists()){ 157 | file.createNewFile(); 158 | } 159 | int ch = 0; 160 | while((ch=inputStream.read()) != -1) { 161 | fileOutputStream.write(ch); 162 | } 163 | inputStream.close(); 164 | fileOutputStream.close(); 165 | 166 | mVodPlayer.startPlay(file.getPath()); 167 | } catch (IOException e) { 168 | e.printStackTrace(); 169 | } 170 | } else { 171 | // file、 network播放 172 | mVodPlayer.startPlay(call.argument("uri").toString()); 173 | } 174 | } 175 | } 176 | 177 | // 播放器监听1 178 | @Override 179 | public void onPlayEvent(TXVodPlayer player, int event, Bundle param) { 180 | switch (event) { 181 | //准备阶段 182 | case TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED: 183 | Map preparedMap = new HashMap<>(); 184 | preparedMap.put("event", "initialized"); 185 | preparedMap.put("duration", (int) player.getDuration()); 186 | preparedMap.put("width", player.getWidth()); 187 | preparedMap.put("height", player.getHeight()); 188 | eventSink.success(preparedMap); 189 | break; 190 | case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS: 191 | Map progressMap = new HashMap<>(); 192 | progressMap.put("event", "progress"); 193 | progressMap.put("progress", param.getInt(TXLiveConstants.EVT_PLAY_PROGRESS_MS)); 194 | progressMap.put("duration", param.getInt(TXLiveConstants.EVT_PLAY_DURATION_MS)); 195 | progressMap.put("playable", param.getInt(TXLiveConstants.EVT_PLAYABLE_DURATION_MS)); 196 | eventSink.success(progressMap); 197 | break; 198 | case TXLiveConstants.PLAY_EVT_PLAY_LOADING: 199 | Map loadingMap = new HashMap<>(); 200 | loadingMap.put("event", "loading"); 201 | eventSink.success(loadingMap); 202 | break; 203 | case TXLiveConstants.PLAY_EVT_VOD_LOADING_END: 204 | Map loadingendMap = new HashMap<>(); 205 | loadingendMap.put("event", "loadingend"); 206 | eventSink.success(loadingendMap); 207 | break; 208 | case TXLiveConstants.PLAY_EVT_PLAY_END: 209 | Map playendMap = new HashMap<>(); 210 | playendMap.put("event", "playend"); 211 | eventSink.success(playendMap); 212 | break; 213 | case TXLiveConstants.PLAY_ERR_NET_DISCONNECT: 214 | Map disconnectMap = new HashMap<>(); 215 | disconnectMap.put("event", "disconnect"); 216 | if (mVodPlayer != null) { 217 | mVodPlayer.setVodListener(null); 218 | mVodPlayer.stopPlay(true); 219 | } 220 | eventSink.success(disconnectMap); 221 | break; 222 | } 223 | if (event < 0) { 224 | Map errorMap = new HashMap<>(); 225 | errorMap.put("event", "error"); 226 | errorMap.put("errorInfo", param.getString(TXLiveConstants.EVT_DESCRIPTION)); 227 | eventSink.success(errorMap); 228 | } 229 | } 230 | 231 | // 播放器监听2 232 | @Override 233 | public void onNetStatus(TXVodPlayer txVodPlayer, Bundle param) { 234 | Map netStatusMap = new HashMap<>(); 235 | netStatusMap.put("event", "netStatus"); 236 | netStatusMap.put("netSpeed", param.getInt(TXLiveConstants.NET_STATUS_NET_SPEED)); 237 | netStatusMap.put("cacheSize", param.getInt(TXLiveConstants.NET_STATUS_V_SUM_CACHE_SIZE)); 238 | eventSink.success(netStatusMap); 239 | } 240 | 241 | void play() { 242 | if (!mVodPlayer.isPlaying()) { 243 | mVodPlayer.resume(); 244 | } 245 | } 246 | 247 | void pause() { 248 | mVodPlayer.pause(); 249 | } 250 | 251 | void seekTo(int location) { 252 | mVodPlayer.seek(location); 253 | } 254 | 255 | void setRate(float rate) { 256 | mVodPlayer.setRate(rate); 257 | } 258 | 259 | void setBitrateIndex(int index) { 260 | mVodPlayer.setBitrateIndex(index); 261 | } 262 | 263 | void dispose() { 264 | if (mVodPlayer != null) { 265 | mVodPlayer.setVodListener(null); 266 | mVodPlayer.stopPlay(true); 267 | } 268 | textureEntry.release(); 269 | eventChannel.setStreamHandler(null); 270 | if (surface != null) { 271 | surface.release(); 272 | } 273 | } 274 | } 275 | ///////////////////// TencentPlayer 结束//////////////////// 276 | 277 | //////////////////// TencentDownload 开始///////////////// 278 | class TencentDownload implements ITXVodDownloadListener { 279 | private TencentQueuingEventSink eventSink = new TencentQueuingEventSink(); 280 | 281 | private final EventChannel eventChannel; 282 | 283 | private final Registrar mRegistrar; 284 | 285 | private String fileId; 286 | 287 | private TXVodDownloadManager downloader; 288 | 289 | private TXVodDownloadMediaInfo txVodDownloadMediaInfo; 290 | 291 | 292 | void stopDownload() { 293 | if (downloader != null && txVodDownloadMediaInfo != null) { 294 | downloader.stopDownload(txVodDownloadMediaInfo); 295 | } 296 | } 297 | 298 | 299 | TencentDownload( 300 | Registrar mRegistrar, 301 | EventChannel eventChannel, 302 | MethodCall call, 303 | Result result) { 304 | this.eventChannel = eventChannel; 305 | this.mRegistrar = mRegistrar; 306 | 307 | 308 | downloader = TXVodDownloadManager.getInstance(); 309 | downloader.setListener(this); 310 | downloader.setDownloadPath(call.argument("savePath").toString()); 311 | String urlOrFileId = call.argument("urlOrFileId").toString(); 312 | 313 | if (urlOrFileId.startsWith("http")) { 314 | txVodDownloadMediaInfo = downloader.startDownloadUrl(urlOrFileId); 315 | } else { 316 | TXPlayerAuthBuilder auth = new TXPlayerAuthBuilder(); 317 | auth.setAppId(((Number)call.argument("appId")).intValue()); 318 | auth.setFileId(urlOrFileId); 319 | int quanlity = ((Number)call.argument("quanlity")).intValue(); 320 | String templateName = "HLS-标清-SD"; 321 | if (quanlity == 2) { 322 | templateName = "HLS-标清-SD"; 323 | } else if (quanlity == 3) { 324 | templateName = "HLS-高清-HD"; 325 | } else if (quanlity == 4) { 326 | templateName = "HLS-全高清-FHD"; 327 | } 328 | TXVodDownloadDataSource source = new TXVodDownloadDataSource(auth, templateName); 329 | txVodDownloadMediaInfo = downloader.startDownload(source); 330 | } 331 | 332 | eventChannel.setStreamHandler( 333 | new EventChannel.StreamHandler() { 334 | @Override 335 | public void onListen(Object o, EventChannel.EventSink sink) { 336 | eventSink.setDelegate(sink); 337 | } 338 | 339 | @Override 340 | public void onCancel(Object o) { 341 | eventSink.setDelegate(null); 342 | } 343 | } 344 | ); 345 | result.success(null); 346 | } 347 | 348 | @Override 349 | public void onDownloadStart(TXVodDownloadMediaInfo txVodDownloadMediaInfo) { 350 | dealCallToFlutterData("start", txVodDownloadMediaInfo); 351 | 352 | } 353 | 354 | @Override 355 | public void onDownloadProgress(TXVodDownloadMediaInfo txVodDownloadMediaInfo) { 356 | dealCallToFlutterData("progress", txVodDownloadMediaInfo); 357 | 358 | } 359 | 360 | @Override 361 | public void onDownloadStop(TXVodDownloadMediaInfo txVodDownloadMediaInfo) { 362 | dealCallToFlutterData("stop", txVodDownloadMediaInfo); 363 | } 364 | 365 | @Override 366 | public void onDownloadFinish(TXVodDownloadMediaInfo txVodDownloadMediaInfo) { 367 | dealCallToFlutterData("complete", txVodDownloadMediaInfo); 368 | } 369 | 370 | @Override 371 | public void onDownloadError(TXVodDownloadMediaInfo txVodDownloadMediaInfo, int i, String s) { 372 | HashMap targetMap = Util.convertToMap(txVodDownloadMediaInfo); 373 | targetMap.put("downloadStatus", "error"); 374 | targetMap.put("error", "code:" + i + " msg:" + s); 375 | if (txVodDownloadMediaInfo.getDataSource() != null) { 376 | targetMap.put("quanlity", txVodDownloadMediaInfo.getDataSource().getQuality()); 377 | targetMap.putAll(Util.convertToMap(txVodDownloadMediaInfo.getDataSource().getAuthBuilder())); 378 | } 379 | eventSink.success(targetMap); 380 | } 381 | 382 | @Override 383 | public int hlsKeyVerify(TXVodDownloadMediaInfo txVodDownloadMediaInfo, String s, byte[] bytes) { 384 | return 0; 385 | } 386 | 387 | private void dealCallToFlutterData(String type, TXVodDownloadMediaInfo txVodDownloadMediaInfo) { 388 | HashMap targetMap = Util.convertToMap(txVodDownloadMediaInfo); 389 | targetMap.put("downloadStatus", type); 390 | if (txVodDownloadMediaInfo.getDataSource() != null) { 391 | targetMap.put("quanlity", txVodDownloadMediaInfo.getDataSource().getQuality()); 392 | targetMap.putAll(Util.convertToMap(txVodDownloadMediaInfo.getDataSource().getAuthBuilder())); 393 | } 394 | eventSink.success(targetMap); 395 | } 396 | 397 | 398 | } 399 | //////////////////// TencentDownload 结束///////////////// 400 | private final Registrar registrar; 401 | private final LongSparseArray videoPlayers; 402 | private final HashMap downloadManagerMap; 403 | 404 | private FlutterTencentplayerPlusPlugin(Registrar registrar) { 405 | this.registrar = registrar; 406 | this.videoPlayers = new LongSparseArray<>(); 407 | this.downloadManagerMap = new HashMap<>(); 408 | 409 | 410 | 411 | } 412 | 413 | 414 | /** 415 | * Plugin registration. 416 | */ 417 | public static void registerWith(Registrar registrar) { 418 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_tencentplayer"); 419 | final FlutterTencentplayerPlusPlugin plugin = new FlutterTencentplayerPlusPlugin(registrar); 420 | 421 | channel.setMethodCallHandler(plugin); 422 | 423 | registrar.addViewDestroyListener( 424 | new PluginRegistry.ViewDestroyListener() { 425 | @Override 426 | public boolean onViewDestroy(FlutterNativeView flutterNativeView) { 427 | plugin.onDestroy(); 428 | return false; 429 | } 430 | } 431 | ); 432 | } 433 | 434 | @Override 435 | public void onMethodCall(MethodCall call, Result result) { 436 | TextureRegistry textures = registrar.textures(); 437 | if (call.method.equals("getPlatformVersion")) { 438 | result.success("Android " + android.os.Build.VERSION.RELEASE); 439 | } 440 | 441 | switch (call.method) { 442 | case "init": 443 | disposeAllPlayers(); 444 | break; 445 | case "create": 446 | TextureRegistry.SurfaceTextureEntry handle = textures.createSurfaceTexture(); 447 | 448 | EventChannel eventChannel = new EventChannel(registrar.messenger(), "flutter_tencentplayer/videoEvents" + handle.id()); 449 | 450 | 451 | TencentPlayer player = new TencentPlayer(registrar, eventChannel, handle, call, result); 452 | videoPlayers.put(handle.id(), player); 453 | break; 454 | case "download": 455 | String urlOrFileId = call.argument("urlOrFileId").toString(); 456 | EventChannel downloadEventChannel = new EventChannel(registrar.messenger(), "flutter_tencentplayer/downloadEvents" + urlOrFileId); 457 | TencentDownload tencentDownload = new TencentDownload(registrar, downloadEventChannel, call, result); 458 | 459 | downloadManagerMap.put(urlOrFileId, tencentDownload); 460 | break; 461 | case "stopDownload": 462 | downloadManagerMap.get(call.argument("urlOrFileId").toString()).stopDownload(); 463 | result.success(null); 464 | break; 465 | default: 466 | long textureId = ((Number) call.argument("textureId")).longValue(); 467 | TencentPlayer tencentPlayer = videoPlayers.get(textureId); 468 | if (tencentPlayer == null) { 469 | result.error( 470 | "Unknown textureId", 471 | "No video player associated with texture id " + textureId, 472 | null); 473 | return; 474 | } 475 | onMethodCall(call, result, textureId, tencentPlayer); 476 | break; 477 | 478 | } 479 | } 480 | 481 | // flutter 发往android的命令 482 | private void onMethodCall(MethodCall call, Result result, long textureId, TencentPlayer player) { 483 | switch (call.method) { 484 | case "play": 485 | player.play(); 486 | result.success(null); 487 | break; 488 | case "pause": 489 | player.pause(); 490 | result.success(null); 491 | break; 492 | case "seekTo": 493 | int location = ((Number) call.argument("location")).intValue(); 494 | player.seekTo(location); 495 | result.success(null); 496 | break; 497 | case "setRate": 498 | float rate = ((Number) call.argument("rate")).floatValue(); 499 | player.setRate(rate); 500 | result.success(null); 501 | break; 502 | case "setBitrateIndex": 503 | int bitrateIndex = ((Number) call.argument("index")).intValue(); 504 | player.setBitrateIndex(bitrateIndex); 505 | result.success(null); 506 | break; 507 | case "dispose": 508 | player.dispose(); 509 | videoPlayers.remove(textureId); 510 | result.success(null); 511 | break; 512 | default: 513 | result.notImplemented(); 514 | break; 515 | } 516 | 517 | } 518 | 519 | 520 | private void disposeAllPlayers() { 521 | for (int i = 0; i < videoPlayers.size(); i++) { 522 | videoPlayers.valueAt(i).dispose(); 523 | } 524 | videoPlayers.clear(); 525 | } 526 | 527 | private void onDestroy() { 528 | disposeAllPlayers(); 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /android/src/main/java/plus/tencentplayer/wilson/flutter/flutter_tencentplayer_plus/TencentQueuingEventSink.java: -------------------------------------------------------------------------------- 1 | package plus.tencentplayer.wilson.flutter.flutter_tencentplayer_plus; 2 | 3 | import java.util.ArrayList; 4 | 5 | import io.flutter.plugin.common.EventChannel; 6 | 7 | /** 8 | * And implementation of {@link EventChannel.EventSink} which can wrap an underlying sink. 9 | * 10 | *

It delivers messages immediately when downstream is available, but it queues messages before 11 | * the delegate event sink is set with setDelegate. 12 | * 13 | *

This class is not thread-safe. All calls must be done on the same thread or synchronized 14 | * externally. 15 | */ 16 | final class TencentQueuingEventSink implements EventChannel.EventSink { 17 | private EventChannel.EventSink delegate; 18 | private ArrayList eventQueue = new ArrayList<>(); 19 | private boolean done = false; 20 | 21 | public void setDelegate(EventChannel.EventSink delegate) { 22 | this.delegate = delegate; 23 | maybeFlush(); 24 | } 25 | 26 | @Override 27 | public void endOfStream() { 28 | enqueue(new EndOfStreamEvent()); 29 | maybeFlush(); 30 | done = true; 31 | } 32 | 33 | @Override 34 | public void error(String code, String message, Object details) { 35 | enqueue(new ErrorEvent(code, message, details)); 36 | maybeFlush(); 37 | } 38 | 39 | @Override 40 | public void success(Object event) { 41 | enqueue(event); 42 | maybeFlush(); 43 | } 44 | 45 | private void enqueue(Object event) { 46 | if (done) { 47 | return; 48 | } 49 | eventQueue.add(event); 50 | } 51 | 52 | private void maybeFlush() { 53 | if (delegate == null) { 54 | return; 55 | } 56 | for (Object event : eventQueue) { 57 | if (event instanceof EndOfStreamEvent) { 58 | delegate.endOfStream(); 59 | } else if (event instanceof ErrorEvent) { 60 | ErrorEvent errorEvent = (ErrorEvent) event; 61 | delegate.error(errorEvent.code, errorEvent.message, errorEvent.details); 62 | } else { 63 | delegate.success(event); 64 | } 65 | } 66 | eventQueue.clear(); 67 | } 68 | 69 | private static class EndOfStreamEvent {} 70 | 71 | private static class ErrorEvent { 72 | String code; 73 | String message; 74 | Object details; 75 | 76 | ErrorEvent(String code, String message, Object details) { 77 | this.code = code; 78 | this.message = message; 79 | this.details = details; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /android/src/main/java/plus/tencentplayer/wilson/flutter/flutter_tencentplayer_plus/Util.java: -------------------------------------------------------------------------------- 1 | package plus.tencentplayer.wilson.flutter.flutter_tencentplayer_plus; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.HashMap; 5 | 6 | public class Util { 7 | public static HashMap convertToMap(Object obj) { 8 | 9 | HashMap map = new HashMap(); 10 | Field[] fields = obj.getClass().getDeclaredFields(); 11 | for (int i = 0, len = fields.length; i < len; i++) { 12 | String varName = fields[i].getName(); 13 | boolean accessFlag = fields[i].isAccessible(); 14 | fields[i].setAccessible(true); 15 | 16 | Object o = null; 17 | try { 18 | o = fields[i].get(obj); 19 | } catch (IllegalAccessException e) { 20 | e.printStackTrace(); 21 | } 22 | if (o != null) 23 | map.put(varName, o.toString()); 24 | 25 | fields[i].setAccessible(accessFlag); 26 | } 27 | 28 | return map; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /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: 68587a0916366e9512a78df22c44163d041dd5f3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_tencentplayer_plus_example 2 | 3 | Demonstrates how to use the flutter_tencentplayer_plus 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/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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "plus.tencentplayer.wilson.flutter.flutter_tencentplayer_plus_example" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 44 | 48 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/plus/tencentplayer/wilson/flutter/flutter_tencentplayer_plus_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package plus.tencentplayer.wilson.flutter.flutter_tencentplayer_plus_example; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | 13 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 14 | def plugins = new Properties() 15 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 16 | if (pluginsFile.exists()) { 17 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 18 | } 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | flatDir { 24 | dirs "${plugins.get("flutter_tencentplayer_plus")}android/libs" 25 | } 26 | } 27 | } 28 | 29 | 30 | rootProject.buildDir = '../build' 31 | subprojects { 32 | project.buildDir = "${rootProject.buildDir}/${project.name}" 33 | } 34 | subprojects { 35 | project.evaluationDependsOn(':app') 36 | } 37 | 38 | task clean(type: Delete) { 39 | delete rootProject.buildDir 40 | } 41 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | -------------------------------------------------------------------------------- /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-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 8.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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 64 | install! 'cocoapods', :disable_input_output_paths => true 65 | 66 | post_install do |installer| 67 | installer.pods_project.targets.each do |target| 68 | target.build_configurations.each do |config| 69 | config.build_settings['ENABLE_BITCODE'] = 'NO' 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/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 | 6 | NSAppTransportSecurity 7 | 8 | NSAllowsArbitraryLoads 9 | 10 | 11 | CFBundleDevelopmentRegion 12 | $(DEVELOPMENT_LANGUAGE) 13 | CFBundleExecutable 14 | $(EXECUTABLE_NAME) 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | flutter_tencentplayer_plus_example 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | $(FLUTTER_BUILD_NAME) 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | $(FLUTTER_BUILD_NUMBER) 29 | LSRequiresIPhoneOS 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /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/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_tencentplayer_plus_example/video_play_page.dart'; 3 | 4 | var launch = MaterialApp( 5 | title: "App", 6 | home: HomePage(), 7 | ); 8 | 9 | class HomePage extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | centerTitle: true, 15 | title: Text('主界面'), 16 | ), 17 | body: Center( 18 | child: Padding( 19 | padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), 20 | child: RaisedButton( 21 | onPressed: () { 22 | Navigator.push(context, 23 | MaterialPageRoute(builder: (context) => VideoPlayPage())); 24 | }, 25 | child: const Text('进入播放器')), 26 | )), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import 'package:flutter_tencentplayer_plus/flutter_tencentplayer_plus.dart'; 4 | import 'package:flutter_tencentplayer_plus_example/home_page.dart'; 5 | 6 | //void main() => runApp(MyApp()); 7 | void main() => runApp(launch); 8 | 9 | class MyApp extends StatefulWidget { 10 | @override 11 | _MyAppState createState() => _MyAppState(); 12 | } 13 | 14 | class _MyAppState extends State { 15 | TencentPlayerController _controller; 16 | VoidCallback listener; 17 | 18 | DownloadController _downloadController; 19 | VoidCallback downloadListener; 20 | 21 | String videoUrl = 22 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4.flv'; 23 | String videoUrlB = 24 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4_550.flv'; 25 | String videoUrlG = 26 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4_900.flv'; 27 | String videoUrlAAA = 'http://file.jinxianyun.com/2018-06-12_16_58_22.mp4'; 28 | String videoUrlBBB = 'http://file.jinxianyun.com/testhaha.mp4'; 29 | String mu = 30 | 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8'; 31 | String spe1 = 32 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f10.mp4'; 33 | String spe2 = 34 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f20.mp4'; 35 | String spe3 = 36 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f30.mp4'; 37 | 38 | String testDownload = 39 | 'http://1253131631.vod2.myqcloud.com/26f327f9vodgzp1253131631/f4bdff799031868222924043041/playlist.m3u8'; 40 | String downloadRes = 41 | '/storage/emulated/0/tencentdownload/txdownload/2c58873a5b9916f9fef5103c74f0ce5e.m3u8.sqlite'; 42 | String downloadRes2 = 43 | '/storage/emulated/0/tencentdownload/txdownload/cf3e281653e562303c8c2b14729ba7f5.m3u8.sqlite'; 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | 49 | addListener(); 50 | initPlatformState(); 51 | } 52 | 53 | addListener() { 54 | // listener = () { 55 | // if (!mounted) { 56 | // return; 57 | // } 58 | // setState(() {}); 59 | // }; 60 | downloadListener = () { 61 | if (!mounted) { 62 | return; 63 | } 64 | setState(() {}); 65 | }; 66 | } 67 | 68 | Future initPlatformState() async { 69 | //点播 70 | // _controller = TencentPlayerController.network(null, playerConfig: PlayerConfig( 71 | // auth: {"appId": , "fileId": ''} 72 | // )) 73 | _controller = TencentPlayerController.network(spe3, 74 | playerConfig: PlayerConfig(autoPlay: false)) 75 | 76 | // _controller = TencentPlayerController.asset('static/tencent1.mp4') 77 | // _controller = TencentPlayerController.file('/storage/emulated/0/test.mp4') 78 | ..initialize().then((_) { 79 | setState(() {}); 80 | }); 81 | 82 | // _controller.addListener(listener); 83 | /// 下载目录 ios 的和android的不一致 注意在ios项目运行的时候传递ios的目录 84 | _downloadController = DownloadController( 85 | '/storage/emulated/0/tencentdownload', 86 | appId: 1252463788); 87 | _downloadController.addListener(downloadListener); 88 | } 89 | 90 | @override 91 | void dispose() { 92 | super.dispose(); 93 | // _controller.removeListener(listener); 94 | _downloadController.removeListener(downloadListener); 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | return MaterialApp( 100 | title: 'Video Demo', 101 | home: ValueListenableBuilder( 102 | valueListenable: _controller, 103 | builder: 104 | (BuildContext context, TencentPlayerValue value, Widget child) { 105 | return Scaffold( 106 | body: Container( 107 | child: Column( 108 | children: [ 109 | Container( 110 | child: Stack( 111 | alignment: AlignmentDirectional.center, 112 | children: [ 113 | // _controller.value.initialized ? 114 | AspectRatio( 115 | aspectRatio: _controller.value.aspectRatio, 116 | child: TencentPlayer(_controller), 117 | ), 118 | Center( 119 | child: _controller.value.isLoading 120 | ? CircularProgressIndicator() 121 | : SizedBox(), 122 | ), 123 | ], 124 | ), 125 | ), 126 | Expanded( 127 | child: ListView( 128 | children: [ 129 | Text( 130 | "播放网速:" + _controller.value.netSpeed.toString(), 131 | style: TextStyle(color: Colors.pink), 132 | ), 133 | Text( 134 | "错误:" + _controller.value.errorDescription.toString(), 135 | style: TextStyle(color: Colors.pink), 136 | ), 137 | Text( 138 | "播放进度:" + _controller.value.position.toString(), 139 | style: TextStyle(color: Colors.pink), 140 | ), 141 | Text( 142 | "缓冲进度:" + _controller.value.playable.toString(), 143 | style: TextStyle(color: Colors.pink), 144 | ), 145 | Text( 146 | "总时长:" + _controller.value.duration.toString(), 147 | style: TextStyle(color: Colors.pink), 148 | ), 149 | FlatButton( 150 | onPressed: () { 151 | _controller.seekTo(Duration(seconds: 5)); 152 | }, 153 | child: Text( 154 | 'seekTo 00:00:05', 155 | style: TextStyle(color: Colors.blue), 156 | )), 157 | Row( 158 | children: [ 159 | FlatButton( 160 | onPressed: () { 161 | _controller.setRate(1.0); 162 | }, 163 | child: Text( 164 | 'setRate 1.0', 165 | style: TextStyle( 166 | color: _controller.value.rate == 1.0 167 | ? Colors.red 168 | : Colors.blue), 169 | )), 170 | FlatButton( 171 | onPressed: () { 172 | _controller.setRate(1.5); 173 | }, 174 | child: Text( 175 | 'setRate 1.5', 176 | style: TextStyle( 177 | color: _controller.value.rate == 1.5 178 | ? Colors.red 179 | : Colors.blue), 180 | )), 181 | FlatButton( 182 | onPressed: () { 183 | _controller.setRate(2.0); 184 | }, 185 | child: Text( 186 | 'setRate 2.0', 187 | style: TextStyle( 188 | color: _controller.value.rate == 2.0 189 | ? Colors.red 190 | : Colors.blue), 191 | )), 192 | ], 193 | ), 194 | Row( 195 | children: [ 196 | FlatButton( 197 | onPressed: () { 198 | _controller = 199 | TencentPlayerController.network(mu); 200 | _controller.initialize().then((_) { 201 | setState(() {}); 202 | }); 203 | // _controller.addListener(listener); 204 | }, 205 | child: Text( 206 | 'm3u8点播', 207 | style: TextStyle( 208 | color: 209 | _controller.dataSource == videoUrlAAA 210 | ? Colors.red 211 | : Colors.blue), 212 | )), 213 | FlatButton( 214 | onPressed: () { 215 | _controller = 216 | TencentPlayerController.network(spe1); 217 | _controller.initialize().then((_) { 218 | setState(() {}); 219 | }); 220 | // _controller.addListener(listener); 221 | }, 222 | child: Text( 223 | '普通点播', 224 | style: TextStyle( 225 | color: _controller.dataSource == videoUrlBBB 226 | ? Colors.red 227 | : Colors.blue), 228 | ), 229 | ), 230 | ], 231 | ), 232 | Row( 233 | children: [ 234 | Container( 235 | margin: EdgeInsets.only(left: 15), 236 | child: Text( 237 | 'm3u8点播 : ', 238 | style: TextStyle(color: Colors.orange), 239 | ), 240 | ), 241 | FlatButton( 242 | child: Text( 243 | '标', 244 | style: TextStyle( 245 | color: _controller.value.bitrateIndex == 0 246 | ? Colors.yellow 247 | : Colors.green), 248 | ), 249 | onPressed: () { 250 | _controller.setBitrateIndex(0); 251 | }, 252 | ), 253 | FlatButton( 254 | child: Text( 255 | '高', 256 | style: TextStyle( 257 | color: _controller.value.bitrateIndex == 1 258 | ? Colors.yellow 259 | : Colors.green), 260 | ), 261 | onPressed: () { 262 | _controller.setBitrateIndex(1); 263 | }, 264 | ), 265 | FlatButton( 266 | child: Text( 267 | '超', 268 | style: TextStyle( 269 | color: _controller.value.bitrateIndex == 2 270 | ? Colors.yellow 271 | : Colors.green), 272 | ), 273 | onPressed: () { 274 | _controller.setBitrateIndex(2); 275 | }, 276 | ), 277 | ], 278 | ), 279 | Row( 280 | children: [ 281 | Container( 282 | margin: EdgeInsets.only(left: 15), 283 | child: Text( 284 | '普通点播 : ', 285 | style: TextStyle(color: Colors.orange), 286 | ), 287 | ), 288 | FlatButton( 289 | onPressed: () { 290 | _controller = TencentPlayerController.network( 291 | spe1, 292 | playerConfig: PlayerConfig( 293 | startTime: _controller 294 | .value.position.inSeconds)); 295 | _controller.initialize().then((_) { 296 | setState(() {}); 297 | }); 298 | // _controller.addListener(listener); 299 | }, 300 | child: Text( 301 | '标', 302 | style: TextStyle( 303 | color: _controller.dataSource == videoUrlB 304 | ? Colors.red 305 | : Colors.blue), 306 | )), 307 | FlatButton( 308 | onPressed: () { 309 | _controller = TencentPlayerController.network( 310 | spe2, 311 | playerConfig: PlayerConfig( 312 | startTime: _controller 313 | .value.position.inSeconds)); 314 | _controller.initialize().then((_) { 315 | setState(() {}); 316 | }); 317 | // _controller.addListener(listener); 318 | }, 319 | child: Text( 320 | '高', 321 | style: TextStyle( 322 | color: _controller.dataSource == videoUrlG 323 | ? Colors.red 324 | : Colors.blue), 325 | )), 326 | FlatButton( 327 | onPressed: () { 328 | _controller = TencentPlayerController.network( 329 | spe3, 330 | playerConfig: PlayerConfig( 331 | startTime: _controller 332 | .value.position.inSeconds)); 333 | _controller.initialize().then((_) { 334 | setState(() {}); 335 | }); 336 | // _controller.addListener(listener); 337 | }, 338 | child: Text( 339 | '超', 340 | style: TextStyle( 341 | color: _controller.dataSource == videoUrl 342 | ? Colors.red 343 | : Colors.blue), 344 | ), 345 | ), 346 | ], 347 | ), 348 | Row( 349 | children: [ 350 | FlatButton( 351 | onPressed: () { 352 | _downloadController.dowload( 353 | "4564972819220421305", 354 | quanlity: 2); 355 | }, 356 | child: Text( 357 | 'download1', 358 | style: TextStyle(color: Colors.blue), 359 | ), 360 | ), 361 | FlatButton( 362 | onPressed: () { 363 | _downloadController 364 | .pauseDownload("4564972819220421305"); 365 | }, 366 | child: Text( 367 | 'download1 - stop', 368 | style: TextStyle(color: Colors.blue), 369 | ), 370 | ), 371 | ], 372 | ), 373 | Row( 374 | children: [ 375 | FlatButton( 376 | onPressed: () { 377 | _downloadController.dowload(testDownload); 378 | }, 379 | child: Text( 380 | 'download2', 381 | style: TextStyle(color: Colors.blue), 382 | ), 383 | ), 384 | FlatButton( 385 | onPressed: () { 386 | _downloadController.pauseDownload(testDownload); 387 | }, 388 | child: Text( 389 | 'download2 - stop', 390 | style: TextStyle(color: Colors.blue), 391 | ), 392 | ), 393 | ], 394 | ), 395 | Text(_downloadController.value != null 396 | ? _downloadController.value.toString() 397 | : '') 398 | ], 399 | ), 400 | ) 401 | ], 402 | ), 403 | ), 404 | floatingActionButton: FloatingActionButton( 405 | onPressed: () { 406 | setState(() { 407 | _controller.value.isPlaying 408 | ? _controller.pause() 409 | : _controller.play(); 410 | }); 411 | }, 412 | child: Icon( 413 | _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, 414 | ), 415 | ), 416 | ); 417 | }, 418 | ), 419 | ); 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /example/lib/video_play_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import 'package:flutter_tencentplayer_plus/flutter_tencentplayer_plus.dart'; 4 | import 'package:flutter_tencentplayer_plus_example/video_play_page2.dart'; 5 | 6 | class VideoPlayPage extends StatefulWidget { 7 | @override 8 | _VideoPlayPageState createState() => _VideoPlayPageState(); 9 | } 10 | 11 | class _VideoPlayPageState extends State { 12 | TencentPlayerController _controller; 13 | VoidCallback listener; 14 | 15 | DownloadController _downloadController; 16 | VoidCallback downloadListener; 17 | 18 | String videoUrl = 19 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4.flv'; 20 | String videoUrlB = 21 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4_550.flv'; 22 | String videoUrlG = 23 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4_900.flv'; 24 | String videoUrlAAA = 'http://file.jinxianyun.com/2018-06-12_16_58_22.mp4'; 25 | String videoUrlBBB = 'http://file.jinxianyun.com/testhaha.mp4'; 26 | String mu = 27 | 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8'; 28 | String spe1 = 29 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f10.mp4'; 30 | String spe2 = 31 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f20.mp4'; 32 | String spe3 = 33 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f30.mp4'; 34 | 35 | String testDownload = 36 | 'http://1253131631.vod2.myqcloud.com/26f327f9vodgzp1253131631/f4bdff799031868222924043041/playlist.m3u8'; 37 | String downloadRes = 38 | '/storage/emulated/0/tencentdownload/txdownload/2c58873a5b9916f9fef5103c74f0ce5e.m3u8.sqlite'; 39 | String downloadRes2 = 40 | '/storage/emulated/0/tencentdownload/txdownload/cf3e281653e562303c8c2b14729ba7f5.m3u8.sqlite'; 41 | 42 | 43 | 44 | @override 45 | void initState() { 46 | super.initState(); 47 | addListener(); 48 | initPlatformState(); 49 | 50 | } 51 | 52 | addListener(){ 53 | listener = () { 54 | if (!mounted) { 55 | return; 56 | } 57 | setState(() {}); 58 | }; 59 | downloadListener = () { 60 | if (!mounted) { 61 | return; 62 | } 63 | setState(() {}); 64 | }; 65 | } 66 | 67 | Future initPlatformState() async { 68 | //点播 69 | // _controller = TencentPlayerController.network(null, playerConfig: PlayerConfig( 70 | // auth: {"appId": , "fileId": ''} 71 | // )) 72 | _controller = TencentPlayerController.network(spe3, 73 | playerConfig: PlayerConfig(autoPlay: false)) 74 | 75 | // _controller = TencentPlayerController.asset('static/tencent1.mp4') 76 | // _controller = TencentPlayerController.file('/storage/emulated/0/test.mp4') 77 | ..initialize().then((_) { 78 | setState(() {}); 79 | }); 80 | 81 | _controller.addListener(listener); 82 | _downloadController = DownloadController( 83 | '/storage/emulated/0/tencentdownload', 84 | appId: 1252463788); 85 | _downloadController.addListener(downloadListener); 86 | } 87 | 88 | @override 89 | void dispose() { 90 | super.dispose(); 91 | _controller.removeListener(listener); 92 | _downloadController.removeListener(downloadListener); 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | return MaterialApp( 98 | title: 'Video Demo', 99 | home: Scaffold( 100 | body: Container( 101 | child: Column( 102 | children: [ 103 | Container( 104 | child: Stack( 105 | alignment: AlignmentDirectional.center, 106 | children: [ 107 | _controller.value.initialized 108 | ? AspectRatio( 109 | aspectRatio: _controller.value.aspectRatio, 110 | child: TencentPlayer(_controller), 111 | ) 112 | : Container(), 113 | Center( 114 | child: _controller.value.isLoading 115 | ? CircularProgressIndicator() 116 | : SizedBox(), 117 | ), 118 | ], 119 | ), 120 | ), 121 | Expanded( 122 | child: ListView( 123 | children: [ 124 | Text( 125 | "播放网速:" + _controller.value.netSpeed.toString(), 126 | style: TextStyle(color: Colors.pink), 127 | ), 128 | Text( 129 | "错误:" + _controller.value.errorDescription.toString(), 130 | style: TextStyle(color: Colors.pink), 131 | ), 132 | Text( 133 | "播放进度:" + _controller.value.position.toString(), 134 | style: TextStyle(color: Colors.pink), 135 | ), 136 | Text( 137 | "缓冲进度:" + _controller.value.playable.toString(), 138 | style: TextStyle(color: Colors.pink), 139 | ), 140 | Text( 141 | "总时长:" + _controller.value.duration.toString(), 142 | style: TextStyle(color: Colors.pink), 143 | ), 144 | FlatButton( 145 | onPressed: () { 146 | _controller.seekTo(Duration(seconds: 5)); 147 | }, 148 | child: Text( 149 | 'seekTo 00:00:05', 150 | style: TextStyle(color: Colors.blue), 151 | )), 152 | Row( 153 | children: [ 154 | FlatButton( 155 | onPressed: () { 156 | _controller.setRate(1.0); 157 | }, 158 | child: Text( 159 | 'setRate 1.0', 160 | style: TextStyle( 161 | color: _controller.value.rate == 1.0 162 | ? Colors.red 163 | : Colors.blue), 164 | )), 165 | FlatButton( 166 | onPressed: () { 167 | _controller.setRate(1.5); 168 | }, 169 | child: Text( 170 | 'setRate 1.5', 171 | style: TextStyle( 172 | color: _controller.value.rate == 1.5 173 | ? Colors.red 174 | : Colors.blue), 175 | )), 176 | FlatButton( 177 | onPressed: () { 178 | _controller.setRate(2.0); 179 | }, 180 | child: Text( 181 | 'setRate 2.0', 182 | style: TextStyle( 183 | color: _controller.value.rate == 2.0 184 | ? Colors.red 185 | : Colors.blue), 186 | )), 187 | ], 188 | ), 189 | Row( 190 | children: [ 191 | FlatButton( 192 | onPressed: () { 193 | _controller = TencentPlayerController.network(mu); 194 | _controller.initialize().then((_) { 195 | setState(() {}); 196 | }); 197 | _controller.addListener(listener); 198 | }, 199 | child: Text( 200 | 'm3u8点播', 201 | style: TextStyle( 202 | color: _controller.dataSource == videoUrlAAA 203 | ? Colors.red 204 | : Colors.blue), 205 | )), 206 | FlatButton( 207 | onPressed: () { 208 | _controller = TencentPlayerController.network(spe1); 209 | _controller.initialize().then((_) { 210 | setState(() {}); 211 | }); 212 | _controller.addListener(listener); 213 | }, 214 | child: Text( 215 | '普通点播', 216 | style: TextStyle( 217 | color: _controller.dataSource == videoUrlBBB 218 | ? Colors.red 219 | : Colors.blue), 220 | ), 221 | ), 222 | ], 223 | ), 224 | Row( 225 | children: [ 226 | Container( 227 | margin: EdgeInsets.only(left: 15), 228 | child: Text( 229 | 'm3u8点播 : ', 230 | style: TextStyle(color: Colors.orange), 231 | ), 232 | ), 233 | FlatButton( 234 | child: Text( 235 | '标', 236 | style: TextStyle( 237 | color: _controller.value.bitrateIndex == 0 238 | ? Colors.yellow 239 | : Colors.green), 240 | ), 241 | onPressed: () { 242 | _controller.setBitrateIndex(0); 243 | }, 244 | ), 245 | FlatButton( 246 | child: Text( 247 | '高', 248 | style: TextStyle( 249 | color: _controller.value.bitrateIndex == 1 250 | ? Colors.yellow 251 | : Colors.green), 252 | ), 253 | onPressed: () { 254 | _controller.setBitrateIndex(1); 255 | }, 256 | ), 257 | FlatButton( 258 | child: Text( 259 | '超', 260 | style: TextStyle( 261 | color: _controller.value.bitrateIndex == 2 262 | ? Colors.yellow 263 | : Colors.green), 264 | ), 265 | onPressed: () { 266 | _controller.setBitrateIndex(2); 267 | }, 268 | ), 269 | ], 270 | ), 271 | Row( 272 | children: [ 273 | Container( 274 | margin: EdgeInsets.only(left: 15), 275 | child: Text( 276 | '普通点播 : ', 277 | style: TextStyle(color: Colors.orange), 278 | ), 279 | ), 280 | FlatButton( 281 | onPressed: () { 282 | _controller = TencentPlayerController.network( 283 | spe1, 284 | playerConfig: PlayerConfig( 285 | startTime: _controller 286 | .value.position.inSeconds)); 287 | _controller.initialize().then((_) { 288 | setState(() {}); 289 | }); 290 | _controller.addListener(listener); 291 | }, 292 | child: Text( 293 | '标', 294 | style: TextStyle( 295 | color: _controller.dataSource == videoUrlB 296 | ? Colors.red 297 | : Colors.blue), 298 | )), 299 | FlatButton( 300 | onPressed: () { 301 | _controller = TencentPlayerController.network( 302 | spe2, 303 | playerConfig: PlayerConfig( 304 | startTime: _controller 305 | .value.position.inSeconds)); 306 | _controller.initialize().then((_) { 307 | setState(() {}); 308 | }); 309 | _controller.addListener(listener); 310 | }, 311 | child: Text( 312 | '高', 313 | style: TextStyle( 314 | color: _controller.dataSource == videoUrlG 315 | ? Colors.red 316 | : Colors.blue), 317 | )), 318 | FlatButton( 319 | onPressed: () { 320 | _controller = TencentPlayerController.network(spe3, 321 | playerConfig: PlayerConfig( 322 | startTime: 323 | _controller.value.position.inSeconds)); 324 | _controller.initialize().then((_) { 325 | setState(() {}); 326 | }); 327 | _controller.addListener(listener); 328 | }, 329 | child: Text( 330 | '超', 331 | style: TextStyle( 332 | color: _controller.dataSource == videoUrl 333 | ? Colors.red 334 | : Colors.blue), 335 | ), 336 | ), 337 | ], 338 | ), 339 | Row( 340 | children: [ 341 | FlatButton( 342 | onPressed: () { 343 | _downloadController.dowload("4564972819220421305", 344 | quanlity: 2); 345 | }, 346 | child: Text( 347 | 'download1', 348 | style: TextStyle(color: Colors.blue), 349 | ), 350 | ), 351 | FlatButton( 352 | onPressed: () { 353 | _downloadController 354 | .pauseDownload("4564972819220421305"); 355 | }, 356 | child: Text( 357 | 'download1 - stop', 358 | style: TextStyle(color: Colors.blue), 359 | ), 360 | ), 361 | ], 362 | ), 363 | Row( 364 | children: [ 365 | FlatButton( 366 | onPressed: () { 367 | _downloadController.dowload(testDownload); 368 | }, 369 | child: Text( 370 | 'download2', 371 | style: TextStyle(color: Colors.blue), 372 | ), 373 | ), 374 | FlatButton( 375 | onPressed: () { 376 | _downloadController.pauseDownload(testDownload); 377 | }, 378 | child: Text( 379 | 'download2 - stop', 380 | style: TextStyle(color: Colors.blue), 381 | ), 382 | ), 383 | ], 384 | ), 385 | Row( 386 | children: [ 387 | FlatButton( 388 | onPressed: () { 389 | Navigator.push( 390 | context, 391 | MaterialPageRoute( 392 | builder: (context) => VideoPlayPage2())); 393 | }, 394 | child: Text( 395 | '进入下一个界面播放', 396 | style: TextStyle(color: Colors.blue), 397 | ), 398 | ), 399 | ], 400 | ), 401 | Text(_downloadController.value != null 402 | ? _downloadController.value.toString() 403 | : '') 404 | ], 405 | ), 406 | ) 407 | ], 408 | ), 409 | ), 410 | floatingActionButton: FloatingActionButton( 411 | onPressed: () { 412 | setState(() { 413 | _controller.value.isPlaying 414 | ? _controller.pause() 415 | : _controller.play(); 416 | }); 417 | }, 418 | child: Icon( 419 | _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, 420 | ), 421 | ), 422 | ), 423 | ); 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /example/lib/video_play_page2.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import 'package:flutter_tencentplayer_plus/flutter_tencentplayer_plus.dart'; 4 | 5 | class VideoPlayPage2 extends StatefulWidget { 6 | @override 7 | _VideoPlayPage2State createState() => _VideoPlayPage2State(); 8 | } 9 | 10 | class _VideoPlayPage2State extends State { 11 | TencentPlayerController _controller; 12 | VoidCallback listener; 13 | 14 | DownloadController _downloadController; 15 | VoidCallback downloadListener; 16 | 17 | String videoUrl = 18 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4.flv'; 19 | String videoUrlB = 20 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4_550.flv'; 21 | String videoUrlG = 22 | 'http://5815.liveplay.myqcloud.com/live/5815_89aad37e06ff11e892905cb9018cf0d4_900.flv'; 23 | String videoUrlAAA = 'http://file.jinxianyun.com/2018-06-12_16_58_22.mp4'; 24 | String videoUrlBBB = 'http://file.jinxianyun.com/testhaha.mp4'; 25 | String mu = 26 | 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8'; 27 | String spe1 = 28 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f10.mp4'; 29 | String spe2 = 30 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f20.mp4'; 31 | String spe3 = 32 | 'http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/v.f30.mp4'; 33 | 34 | String testDownload = 35 | 'http://1253131631.vod2.myqcloud.com/26f327f9vodgzp1253131631/f4bdff799031868222924043041/playlist.m3u8'; 36 | String downloadRes = 37 | '/storage/emulated/0/tencentdownload/txdownload/2c58873a5b9916f9fef5103c74f0ce5e.m3u8.sqlite'; 38 | String downloadRes2 = 39 | '/storage/emulated/0/tencentdownload/txdownload/cf3e281653e562303c8c2b14729ba7f5.m3u8.sqlite'; 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | addListener(); 45 | initPlatformState(); 46 | 47 | } 48 | 49 | 50 | addListener() { 51 | listener = () { 52 | if (!mounted) { 53 | return; 54 | } 55 | setState(() {}); 56 | }; 57 | downloadListener = () { 58 | if (!mounted) { 59 | return; 60 | } 61 | setState(() {}); 62 | }; 63 | } 64 | Future initPlatformState() async { 65 | //点播 66 | // _controller = TencentPlayerController.network(null, playerConfig: PlayerConfig( 67 | // auth: {"appId": , "fileId": ''} 68 | // )) 69 | _controller = TencentPlayerController.network(spe3, 70 | playerConfig: PlayerConfig(autoPlay: true)) 71 | 72 | // _controller = TencentPlayerController.asset('static/tencent1.mp4') 73 | // _controller = TencentPlayerController.file('/storage/emulated/0/test.mp4') 74 | ..initialize().then((_) { 75 | setState(() {}); 76 | }); 77 | 78 | _controller.addListener(listener); 79 | _downloadController = DownloadController( 80 | '/storage/emulated/0/tencentdownload', 81 | appId: 1252463788); 82 | _downloadController.addListener(downloadListener); 83 | } 84 | 85 | @override 86 | void dispose() { 87 | super.dispose(); 88 | _controller.removeListener(listener); 89 | _downloadController.removeListener(downloadListener); 90 | } 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | return MaterialApp( 95 | title: 'Video Demo', 96 | home: Scaffold( 97 | body: Container( 98 | child: Column( 99 | children: [ 100 | Container( 101 | child: Stack( 102 | alignment: AlignmentDirectional.center, 103 | children: [ 104 | _controller.value.initialized 105 | ? AspectRatio( 106 | aspectRatio: _controller.value.aspectRatio, 107 | child: TencentPlayer(_controller), 108 | ) 109 | : Container(), 110 | Center( 111 | child: _controller.value.isLoading 112 | ? CircularProgressIndicator() 113 | : SizedBox(), 114 | ), 115 | ], 116 | ), 117 | ), 118 | Expanded( 119 | child: ListView( 120 | children: [ 121 | Text( 122 | "播放网速:" + _controller.value.netSpeed.toString(), 123 | style: TextStyle(color: Colors.pink), 124 | ), 125 | Text( 126 | "错误:" + _controller.value.errorDescription.toString(), 127 | style: TextStyle(color: Colors.pink), 128 | ), 129 | Text( 130 | "播放进度:" + _controller.value.position.toString(), 131 | style: TextStyle(color: Colors.pink), 132 | ), 133 | Text( 134 | "缓冲进度:" + _controller.value.playable.toString(), 135 | style: TextStyle(color: Colors.pink), 136 | ), 137 | Text( 138 | "总时长:" + _controller.value.duration.toString(), 139 | style: TextStyle(color: Colors.pink), 140 | ), 141 | FlatButton( 142 | onPressed: () { 143 | _controller.seekTo(Duration(seconds: 5)); 144 | }, 145 | child: Text( 146 | 'seekTo 00:00:05', 147 | style: TextStyle(color: Colors.blue), 148 | )), 149 | Row( 150 | children: [ 151 | FlatButton( 152 | onPressed: () { 153 | _controller.setRate(1.0); 154 | }, 155 | child: Text( 156 | 'setRate 1.0', 157 | style: TextStyle( 158 | color: _controller.value.rate == 1.0 159 | ? Colors.red 160 | : Colors.blue), 161 | )), 162 | FlatButton( 163 | onPressed: () { 164 | _controller.setRate(1.5); 165 | }, 166 | child: Text( 167 | 'setRate 1.5', 168 | style: TextStyle( 169 | color: _controller.value.rate == 1.5 170 | ? Colors.red 171 | : Colors.blue), 172 | )), 173 | FlatButton( 174 | onPressed: () { 175 | _controller.setRate(2.0); 176 | }, 177 | child: Text( 178 | 'setRate 2.0', 179 | style: TextStyle( 180 | color: _controller.value.rate == 2.0 181 | ? Colors.red 182 | : Colors.blue), 183 | )), 184 | ], 185 | ), 186 | Row( 187 | children: [ 188 | FlatButton( 189 | onPressed: () { 190 | _controller = TencentPlayerController.network(mu); 191 | _controller.initialize().then((_) { 192 | setState(() {}); 193 | }); 194 | _controller.addListener(listener); 195 | }, 196 | child: Text( 197 | 'm3u8点播', 198 | style: TextStyle( 199 | color: _controller.dataSource == videoUrlAAA 200 | ? Colors.red 201 | : Colors.blue), 202 | )), 203 | FlatButton( 204 | onPressed: () { 205 | _controller = TencentPlayerController.network(spe1); 206 | _controller.initialize().then((_) { 207 | setState(() {}); 208 | }); 209 | _controller.addListener(listener); 210 | }, 211 | child: Text( 212 | '普通点播', 213 | style: TextStyle( 214 | color: _controller.dataSource == videoUrlBBB 215 | ? Colors.red 216 | : Colors.blue), 217 | ), 218 | ), 219 | ], 220 | ), 221 | Row( 222 | children: [ 223 | Container( 224 | margin: EdgeInsets.only(left: 15), 225 | child: Text( 226 | 'm3u8点播 : ', 227 | style: TextStyle(color: Colors.orange), 228 | ), 229 | ), 230 | FlatButton( 231 | child: Text( 232 | '标', 233 | style: TextStyle( 234 | color: _controller.value.bitrateIndex == 0 235 | ? Colors.yellow 236 | : Colors.green), 237 | ), 238 | onPressed: () { 239 | _controller.setBitrateIndex(0); 240 | }, 241 | ), 242 | FlatButton( 243 | child: Text( 244 | '高', 245 | style: TextStyle( 246 | color: _controller.value.bitrateIndex == 1 247 | ? Colors.yellow 248 | : Colors.green), 249 | ), 250 | onPressed: () { 251 | _controller.setBitrateIndex(1); 252 | }, 253 | ), 254 | FlatButton( 255 | child: Text( 256 | '超', 257 | style: TextStyle( 258 | color: _controller.value.bitrateIndex == 2 259 | ? Colors.yellow 260 | : Colors.green), 261 | ), 262 | onPressed: () { 263 | _controller.setBitrateIndex(2); 264 | }, 265 | ), 266 | ], 267 | ), 268 | Row( 269 | children: [ 270 | Container( 271 | margin: EdgeInsets.only(left: 15), 272 | child: Text( 273 | '普通点播 : ', 274 | style: TextStyle(color: Colors.orange), 275 | ), 276 | ), 277 | FlatButton( 278 | onPressed: () { 279 | _controller = TencentPlayerController.network( 280 | spe1, 281 | playerConfig: PlayerConfig( 282 | startTime: _controller 283 | .value.position.inSeconds)); 284 | _controller.initialize().then((_) { 285 | setState(() {}); 286 | }); 287 | _controller.addListener(listener); 288 | }, 289 | child: Text( 290 | '标', 291 | style: TextStyle( 292 | color: _controller.dataSource == videoUrlB 293 | ? Colors.red 294 | : Colors.blue), 295 | )), 296 | FlatButton( 297 | onPressed: () { 298 | _controller = TencentPlayerController.network( 299 | spe2, 300 | playerConfig: PlayerConfig( 301 | startTime: _controller 302 | .value.position.inSeconds)); 303 | _controller.initialize().then((_) { 304 | setState(() {}); 305 | }); 306 | _controller.addListener(listener); 307 | }, 308 | child: Text( 309 | '高', 310 | style: TextStyle( 311 | color: _controller.dataSource == videoUrlG 312 | ? Colors.red 313 | : Colors.blue), 314 | )), 315 | FlatButton( 316 | onPressed: () { 317 | _controller = TencentPlayerController.network(spe3, 318 | playerConfig: PlayerConfig( 319 | startTime: 320 | _controller.value.position.inSeconds)); 321 | _controller.initialize().then((_) { 322 | setState(() {}); 323 | }); 324 | _controller.addListener(listener); 325 | }, 326 | child: Text( 327 | '超', 328 | style: TextStyle( 329 | color: _controller.dataSource == videoUrl 330 | ? Colors.red 331 | : Colors.blue), 332 | ), 333 | ), 334 | ], 335 | ), 336 | Row( 337 | children: [ 338 | FlatButton( 339 | onPressed: () { 340 | _downloadController.dowload("4564972819220421305", 341 | quanlity: 2); 342 | }, 343 | child: Text( 344 | 'download1', 345 | style: TextStyle(color: Colors.blue), 346 | ), 347 | ), 348 | FlatButton( 349 | onPressed: () { 350 | _downloadController 351 | .pauseDownload("4564972819220421305"); 352 | }, 353 | child: Text( 354 | 'download1 - stop', 355 | style: TextStyle(color: Colors.blue), 356 | ), 357 | ), 358 | ], 359 | ), 360 | Row( 361 | children: [ 362 | FlatButton( 363 | onPressed: () { 364 | _downloadController.dowload(testDownload); 365 | }, 366 | child: Text( 367 | 'download2', 368 | style: TextStyle(color: Colors.blue), 369 | ), 370 | ), 371 | FlatButton( 372 | onPressed: () { 373 | _downloadController.pauseDownload(testDownload); 374 | }, 375 | child: Text( 376 | 'download2 - stop', 377 | style: TextStyle(color: Colors.blue), 378 | ), 379 | ), 380 | ], 381 | ), 382 | Text(_downloadController.value != null 383 | ? _downloadController.value.toString() 384 | : '') 385 | ], 386 | ), 387 | ) 388 | ], 389 | ), 390 | ), 391 | floatingActionButton: FloatingActionButton( 392 | onPressed: () { 393 | setState(() { 394 | _controller.value.isPlaying 395 | ? _controller.pause() 396 | : _controller.play(); 397 | }); 398 | }, 399 | child: Icon( 400 | _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, 401 | ), 402 | ), 403 | ), 404 | ); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /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.3.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.0.5" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.14.11" 32 | cupertino_icons: 33 | dependency: "direct main" 34 | description: 35 | name: cupertino_icons 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "0.1.3" 39 | flutter: 40 | dependency: "direct main" 41 | description: flutter 42 | source: sdk 43 | version: "0.0.0" 44 | flutter_tencentplayer_plus: 45 | dependency: "direct dev" 46 | description: 47 | path: ".." 48 | relative: true 49 | source: path 50 | version: "0.0.1" 51 | flutter_test: 52 | dependency: "direct dev" 53 | description: flutter 54 | source: sdk 55 | version: "0.0.0" 56 | matcher: 57 | dependency: transitive 58 | description: 59 | name: matcher 60 | url: "https://pub.flutter-io.cn" 61 | source: hosted 62 | version: "0.12.5" 63 | meta: 64 | dependency: transitive 65 | description: 66 | name: meta 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "1.1.7" 70 | path: 71 | dependency: transitive 72 | description: 73 | name: path 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "1.6.4" 77 | pedantic: 78 | dependency: transitive 79 | description: 80 | name: pedantic 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.8.0+1" 84 | quiver: 85 | dependency: transitive 86 | description: 87 | name: quiver 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "2.0.5" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.5.5" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.9.3" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "2.0.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.0.5" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.1.0" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "0.2.5" 138 | typed_data: 139 | dependency: transitive 140 | description: 141 | name: typed_data 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.1.6" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "2.0.8" 152 | sdks: 153 | dart: ">=2.2.2 <3.0.0" 154 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_tencentplayer_plus_example 2 | description: Demonstrates how to use the flutter_tencentplayer_plus plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter_tencentplayer_plus: 21 | path: ../ 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://dart.dev/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | 29 | # The following line ensures that the Material Icons font is 30 | # included with your application, so that you can use the icons in 31 | # the material Icons class. 32 | uses-material-design: true 33 | 34 | # To add assets to your application, add an assets section, like this: 35 | # assets: 36 | # - images/a_dot_burr.jpeg 37 | # - images/a_dot_ham.jpeg 38 | 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # For details regarding adding assets from package dependencies, see 43 | # https://flutter.dev/assets-and-images/#from-packages 44 | 45 | # To add custom fonts to your application, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts from package dependencies, 63 | # see https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /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:flutter_tencentplayer_plus_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(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 | -------------------------------------------------------------------------------- /flutter_tencentplayer_plus.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /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/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FLTDownLoadManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLTDownLoadManager.h 3 | // flutter_tencentplayer 4 | // 5 | // Created by wilson on 2019/8/16. 6 | // 7 | 8 | #import 9 | #import 10 | #import "TXLiteAVSDK.h" 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FLTDownLoadManager : NSObject 14 | 15 | 16 | @property(nonatomic) FlutterEventSink eventSink; 17 | @property(nonatomic) FlutterEventChannel* eventChannel; 18 | @property(nonatomic) FlutterResult result; 19 | @property(nonatomic) FlutterMethodCall* call; 20 | @property(nonatomic) NSString* path; 21 | @property(nonatomic) NSString* urlOrFileId; 22 | @property(nonatomic) TXVodDownloadManager* tXVodDownloadManager; 23 | @property(nonatomic) TXVodDownloadMediaInfo* tempMedia; 24 | 25 | - (instancetype)initWithMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; 26 | 27 | //下载的方法 28 | - (void)downLoad; 29 | //停止下载的方法 30 | - (void)stopDownLoad; 31 | 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /ios/Classes/FLTDownLoadManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLTDownLoadManager.m 3 | // flutter_tencentplayer 4 | // 5 | // Created by wilson on 2019/8/16. 6 | // 7 | 8 | #import "FLTDownLoadManager.h" 9 | 10 | @implementation FLTDownLoadManager 11 | 12 | 13 | - (instancetype)initWithMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{ 14 | _call = call; 15 | _result = result; 16 | 17 | // [_eventChannel setStreamHandler:self]; 18 | NSDictionary* argsMap = _call.arguments; 19 | _path = argsMap[@"savePath"]; 20 | 21 | NSLog(@"下载地址 %@", _path); 22 | _urlOrFileId = argsMap[@"urlOrFileId"]; 23 | if (_tXVodDownloadManager == nil) { 24 | _tXVodDownloadManager = [TXVodDownloadManager shareInstance]; 25 | // NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 26 | // NSString *docPath = [paths lastObject]; 27 | // NSString *downloadPath = [docPath stringByAppendingString:@"/downloader" ]; 28 | //// NSLog(downloadPath); 29 | [_tXVodDownloadManager setDownloadPath:_path]; 30 | } 31 | _tXVodDownloadManager.delegate = self; 32 | return self; 33 | } 34 | 35 | //开始下载 36 | - (void)downLoad{ 37 | //设置下载对象 38 | NSLog(@"开始下载"); 39 | if([_urlOrFileId hasPrefix: @"http"]){ 40 | _tempMedia = [_tXVodDownloadManager startDownloadUrl:_urlOrFileId]; 41 | }else{ 42 | NSDictionary* argsMap = _call.arguments; 43 | int appId = [argsMap[@"appId"] intValue]; 44 | int quanlity = [argsMap[@"quanlity"] intValue]; 45 | _urlOrFileId = argsMap[@"urlOrFileId"]; 46 | TXPlayerAuthParams *auth = [TXPlayerAuthParams new]; 47 | auth.appId =appId; 48 | auth.fileId = _urlOrFileId; 49 | TXVodDownloadDataSource *dataSource = [TXVodDownloadDataSource new]; 50 | dataSource.auth = auth; 51 | dataSource.templateName = @"HLS-标清-SD"; 52 | if (quanlity == 2) { 53 | dataSource.templateName = @"HLS-标清-SD"; 54 | } else if (quanlity == 3) { 55 | dataSource.templateName = @"HLS-高清-HD"; 56 | } else if (quanlity == 4) { 57 | dataSource.templateName = @"HLS-全高清-FHD"; 58 | } 59 | _tempMedia = [_tXVodDownloadManager startDownload:dataSource]; 60 | } 61 | 62 | } 63 | 64 | 65 | //停止下载 66 | - (void)stopDownLoad{ 67 | NSLog(@"停止下载"); 68 | [_tXVodDownloadManager stopDownload:_tempMedia]; 69 | } 70 | 71 | // ---------------通信相关 72 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 73 | _eventSink = nil; 74 | 75 | NSLog(@"FLTDownLoadManager停止通信"); 76 | return nil; 77 | } 78 | 79 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 80 | _eventSink = events; 81 | NSLog(@"FLTDownLoadManager设置全局通信"); 82 | return nil; 83 | } 84 | 85 | 86 | 87 | //----------------下载回调相关 88 | 89 | - (void)onDownloadStart:(TXVodDownloadMediaInfo *)mediaInfo { 90 | [self dealCallToFlutterData:@"start" mediaInfo:mediaInfo ]; 91 | } 92 | 93 | - (void)onDownloadProgress:(TXVodDownloadMediaInfo *)mediaInfo { 94 | 95 | [self dealCallToFlutterData:@"progress" mediaInfo:mediaInfo ]; 96 | } 97 | 98 | - (void)onDownloadStop:(TXVodDownloadMediaInfo *)mediaInfo { 99 | 100 | [self dealCallToFlutterData:@"stop" mediaInfo:mediaInfo ]; 101 | 102 | } 103 | - (void)onDownloadFinish:(TXVodDownloadMediaInfo *)mediaInfo { 104 | 105 | [self dealCallToFlutterData:@"complete" mediaInfo:mediaInfo ]; 106 | } 107 | 108 | - (void)onDownloadError:(TXVodDownloadMediaInfo *)mediaInfo errorCode:(TXDownloadError)code errorMsg:(NSString *)msg { 109 | 110 | NSLog(@"onDownloadError"); 111 | 112 | NSString *quality = [NSString stringWithFormat:@"%ld",(long)mediaInfo.dataSource.quality]; 113 | NSString *duration = [NSString stringWithFormat:@"%d",mediaInfo.duration]; 114 | NSString *size = [NSString stringWithFormat:@"%d",mediaInfo.size]; 115 | NSString *downloadSize = [NSString stringWithFormat:@"%d",mediaInfo.downloadSize]; 116 | NSString *progress = [NSString stringWithFormat:@"%f",mediaInfo.progress]; 117 | if (mediaInfo.dataSource!=nil) { 118 | if(self->_eventSink!=nil){ 119 | NSMutableDictionary* paramDic = [NSMutableDictionary dictionary]; 120 | [paramDic setValue:@"error" forKey:@"downloadStatus"]; 121 | [paramDic setValue:quality forKey:@"quanlity"]; 122 | [paramDic setValue:duration forKey:@"duration"]; 123 | [paramDic setValue:size forKey:@"size"]; 124 | [paramDic setValue:downloadSize forKey:@"downloadSize"]; 125 | [paramDic setValue:progress forKey:@"progress"]; 126 | [paramDic setValue:mediaInfo.playPath forKey:@"playPath"]; 127 | [paramDic setValue:@(true) forKey:@"isStop"]; 128 | [paramDic setValue:mediaInfo.url forKey:@"url"]; 129 | [paramDic setValue:mediaInfo.dataSource.auth.fileId forKey:@"fileId"]; 130 | [paramDic setValue:msg forKey:@"error"]; 131 | 132 | self->_eventSink(paramDic); 133 | } 134 | } 135 | } 136 | 137 | - (int)hlsKeyVerify:(TXVodDownloadMediaInfo *)mediaInfo url:(NSString *)url data:(NSData *)data { 138 | NSLog(@"停止下载"); 139 | return 0; 140 | } 141 | 142 | - (void)dealCallToFlutterData:(NSString*)type mediaInfo:(TXVodDownloadMediaInfo *)mediaInfo { 143 | NSLog(@"下载类型"); 144 | 145 | NSString *quality = [NSString stringWithFormat:@"%ld",(long)mediaInfo.dataSource.quality]; 146 | NSString *duration = [NSString stringWithFormat:@"%d",mediaInfo.duration]; 147 | NSString *size = [NSString stringWithFormat:@"%d",mediaInfo.size]; 148 | NSString *downloadSize = [NSString stringWithFormat:@"%d",mediaInfo.downloadSize]; 149 | NSString *progress = [NSString stringWithFormat:@"%f",mediaInfo.progress]; 150 | 151 | if (mediaInfo.dataSource!=nil) { 152 | // [mediaInfo.dataSource auth]; 153 | if(self->_eventSink!=nil){ 154 | self->_eventSink(@{ 155 | @"downloadStatus":type, 156 | @"quanlity":quality , 157 | @"duration":duration , 158 | @"size":size , 159 | @"downloadSize":downloadSize , 160 | @"progress":progress , 161 | @"playPath":mediaInfo.playPath , 162 | @"isStop":@(true) , 163 | @"url":mediaInfo.url , 164 | @"fileId":mediaInfo.dataSource.auth.fileId, 165 | @"error":@"error" , 166 | 167 | }); 168 | } 169 | } 170 | 171 | } 172 | 173 | 174 | 175 | 176 | 177 | @end 178 | -------------------------------------------------------------------------------- /ios/Classes/FLTFrameUpdater.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLTFrameUpdater.h 3 | // flutter_plugin_demo3 4 | // 5 | // Created by Wei on 2019/5/15. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FLTFrameUpdater : NSObject 14 | @property(nonatomic) int64_t textureId; 15 | @property(nonatomic, readonly) NSObject* registry; 16 | 17 | -(void)refreshDisplay; 18 | - (FLTFrameUpdater*)initWithRegistry:(NSObject*)registry; 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /ios/Classes/FLTFrameUpdater.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLTFrameUpdater.m 3 | // flutter_plugin_demo3 4 | // 5 | // Created by Wei on 2019/5/15. 6 | // 7 | 8 | #import "FLTFrameUpdater.h" 9 | 10 | @implementation FLTFrameUpdater 11 | - (FLTFrameUpdater*)initWithRegistry:(NSObject*)registry { 12 | NSAssert(self, @"super init cannot be nil"); 13 | if (self == nil) return nil; 14 | _registry = registry; 15 | return self; 16 | } 17 | 18 | -(void)refreshDisplay{ 19 | [_registry textureFrameAvailable:self.textureId]; 20 | } 21 | @end 22 | -------------------------------------------------------------------------------- /ios/Classes/FLTVideoPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLTVideoPlayer.h 3 | // flutter_plugin_demo3 4 | // 5 | // Created by Wei on 2019/5/15. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import "FLTFrameUpdater.h" 12 | #import "TXLiteAVSDK.h" 13 | #import 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface FLTVideoPlayer : NSObject 17 | @property(readonly,nonatomic) TXVodPlayer* txPlayer; 18 | @property(nonatomic) FlutterEventChannel* eventChannel; 19 | 20 | //ios主动和flutter通信 21 | @property(nonatomic) FlutterEventSink eventSink; 22 | @property(nonatomic, readonly) bool disposed; 23 | @property(nonatomic, readonly) int64_t textureId; 24 | 25 | /** 26 | * 是否循环播放 27 | */ 28 | @property (nonatomic, assign) BOOL loop; 29 | @property(nonatomic)FLTFrameUpdater* frameUpdater; 30 | 31 | - (instancetype)initWithCall:(FlutterMethodCall*)call 32 | frameUpdater:(FLTFrameUpdater*)frameUpdater 33 | registry:(NSObject*)registry 34 | messenger:(NSObject*)messenger; 35 | - (void)dispose; 36 | -(void)resume; 37 | -(void)pause; 38 | -(int64_t)position; 39 | -(int64_t)duration; 40 | -(void)seekTo:(int)position; 41 | /** 42 | * 设置播放开始时间 43 | * 在startPlay前设置,修改开始播放的起始位置 44 | */ 45 | - (void)setStartTime:(CGFloat)startTime; 46 | 47 | /** 48 | * 停止播放音视频流 49 | * @return 0 = OK 50 | */ 51 | - (int)stopPlay; 52 | /** 53 | * 可播放时长 54 | */ 55 | - (float)playableDuration; 56 | /** 57 | * 视频宽度 58 | */ 59 | - (int)width; 60 | 61 | /** 62 | * 视频高度 63 | */ 64 | - (int)height; 65 | /** 66 | * 设置画面的方向 67 | * @param rotation 方向 68 | * @see TX_Enum_Type_HomeOrientation 69 | */ 70 | - (void)setRenderRotation:(TX_Enum_Type_HomeOrientation)rotation; 71 | /** 72 | * 设置画面的裁剪模式 73 | * @param renderMode 裁剪 74 | * @see TX_Enum_Type_RenderMode 75 | */ 76 | - (void)setRenderMode:(TX_Enum_Type_RenderMode)renderMode; 77 | /** 78 | * 设置静音 79 | */ 80 | - (void)setMute:(BOOL)bEnable; 81 | 82 | /* 83 | * 截屏 84 | * @param snapshotCompletionBlock 通过回调返回当前图像 85 | */ 86 | - (void)snapshot:(void (^)(UIImage *))snapshotCompletionBlock; 87 | /** 88 | * 设置播放速率 89 | * @param rate 正常速度为1.0;小于为慢速;大于为快速。最大建议不超过2.0 90 | */ 91 | - (void)setRate:(float)rate; 92 | // 设置播放清晰度 93 | - (void)setBitrateIndex:(int)index; 94 | /** 95 | * 设置画面镜像 96 | */ 97 | - (void)setMirror:(BOOL)isMirror; 98 | 99 | @end 100 | 101 | NS_ASSUME_NONNULL_END 102 | -------------------------------------------------------------------------------- /ios/Classes/FLTVideoPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLTVideoPlayer.m 3 | // flutter_plugin_demo3 4 | // 5 | // Created by Wei on 2019/5/15. 6 | // 7 | 8 | #import "FLTVideoPlayer.h" 9 | #import 10 | 11 | 12 | 13 | @implementation FLTVideoPlayer{ 14 | // CVPixelBufferRef finalPiexelBuffer; 15 | // CVPixelBufferRef pixelBufferNowRef; 16 | CVPixelBufferRef volatile _latestPixelBuffer; 17 | CVPixelBufferRef _lastBuffer; 18 | } 19 | 20 | - (instancetype)initWithCall:(FlutterMethodCall *)call frameUpdater:(FLTFrameUpdater *)frameUpdater registry:(NSObject *)registry messenger:(NSObject*)messenger{ 21 | self = [super init]; 22 | _latestPixelBuffer = nil; 23 | _lastBuffer = nil; 24 | // NSLog(@"FLTVideo 初始化播放器"); 25 | _textureId = [registry registerTexture:self]; 26 | // NSLog(@"FLTVideo _textureId %lld",_textureId); 27 | 28 | FlutterEventChannel* eventChannel = [FlutterEventChannel 29 | eventChannelWithName:[NSString stringWithFormat:@"flutter_tencentplayer/videoEvents%lld",_textureId] 30 | binaryMessenger:messenger]; 31 | 32 | 33 | 34 | _eventChannel = eventChannel; 35 | [_eventChannel setStreamHandler:self]; 36 | NSDictionary* argsMap = call.arguments; 37 | TXVodPlayConfig* playConfig = [[TXVodPlayConfig alloc]init]; 38 | playConfig.connectRetryCount= 3 ; 39 | playConfig.connectRetryInterval = 3; 40 | playConfig.timeout = 10 ; 41 | 42 | 43 | 44 | id headers = argsMap[@"headers"]; 45 | if (headers!=nil&&headers!=NULL&&![@"" isEqualToString:headers]&&headers!=[NSNull null]) { 46 | NSDictionary* headers = argsMap[@"headers"]; 47 | playConfig.headers = headers; 48 | } 49 | 50 | id cacheFolderPath = argsMap[@"cachePath"]; 51 | if (cacheFolderPath!=nil&&cacheFolderPath!=NULL&&![@"" isEqualToString:cacheFolderPath]&&cacheFolderPath!=[NSNull null]) { 52 | playConfig.cacheFolderPath = cacheFolderPath; 53 | playConfig.maxCacheItems = 20; 54 | }else{ 55 | // 设置缓存路径 56 | playConfig.cacheFolderPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 57 | playConfig.maxCacheItems = 10; 58 | } 59 | 60 | playConfig.progressInterval = 1; 61 | playConfig.maxBufferSize=4; 62 | //[argsMap[@"progressInterval"] intValue] ; 63 | BOOL autoPlayArg = [argsMap[@"autoPlay"] boolValue]; 64 | float startPosition=0; 65 | 66 | id startTime = argsMap[@"startTime"]; 67 | if(startTime!=nil&&startTime!=NULL&&![@"" isEqualToString:startTime]&&startTime!=[NSNull null]){ 68 | startPosition =[argsMap[@"startTime"] floatValue]; 69 | } 70 | 71 | frameUpdater.textureId = _textureId; 72 | _frameUpdater = frameUpdater; 73 | 74 | _txPlayer = [[TXVodPlayer alloc]init]; 75 | [playConfig setPlayerPixelFormatType:kCVPixelFormatType_32BGRA]; 76 | [_txPlayer setConfig:playConfig]; 77 | [_txPlayer setIsAutoPlay:autoPlayArg]; 78 | _txPlayer.enableHWAcceleration = YES; 79 | [_txPlayer setVodDelegate:self]; 80 | [_txPlayer setVideoProcessDelegate:self]; 81 | [_txPlayer setStartTime:startPosition]; 82 | 83 | BOOL loop = [argsMap[@"loop"] boolValue]; 84 | [_txPlayer setLoop: loop]; 85 | 86 | id pathArg = argsMap[@"uri"]; 87 | if(pathArg!=nil&&pathArg!=NULL&&![@"" isEqualToString:pathArg]&&pathArg!=[NSNull null]){ 88 | NSLog(@"播放器启动方式1 play"); 89 | [_txPlayer startPlay:pathArg]; 90 | }else{ 91 | NSLog(@"播放器启动方式2 fileid"); 92 | id auth = argsMap[@"auth"]; 93 | if(auth!=nil&&auth!=NULL&&![@"" isEqualToString:auth]&&auth!=[NSNull null]){ 94 | NSDictionary* authMap = argsMap[@"auth"]; 95 | int appId= [authMap[@"appId"] intValue]; 96 | NSString *fileId= authMap[@"fileId"]; 97 | TXPlayerAuthParams *p = [TXPlayerAuthParams new]; 98 | p.appId = appId; 99 | p.fileId = fileId; 100 | [_txPlayer startPlayWithParams:p]; 101 | } 102 | } 103 | NSLog(@"播放器初始化结束"); 104 | 105 | 106 | return self; 107 | 108 | } 109 | 110 | 111 | #pragma FlutterTexture 112 | - (CVPixelBufferRef)copyPixelBuffer { 113 | CVPixelBufferRef pixelBuffer = _latestPixelBuffer; 114 | while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, 115 | (void **)&_latestPixelBuffer)) { 116 | pixelBuffer = _latestPixelBuffer; 117 | } 118 | return pixelBuffer; 119 | } 120 | 121 | #pragma 腾讯播放器代理回调方法 122 | - (BOOL)onPlayerPixelBuffer:(CVPixelBufferRef)pixelBuffer{ 123 | 124 | if (_lastBuffer == nil) { 125 | _lastBuffer = CVPixelBufferRetain(pixelBuffer); 126 | CFRetain(pixelBuffer); 127 | } else if (_lastBuffer != pixelBuffer) { 128 | CVPixelBufferRelease(_lastBuffer); 129 | _lastBuffer = CVPixelBufferRetain(pixelBuffer); 130 | CFRetain(pixelBuffer); 131 | } 132 | 133 | CVPixelBufferRef newBuffer = pixelBuffer; 134 | 135 | CVPixelBufferRef old = _latestPixelBuffer; 136 | while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer, 137 | (void **)&_latestPixelBuffer)) { 138 | old = _latestPixelBuffer; 139 | } 140 | 141 | if (old && old != pixelBuffer) { 142 | CFRelease(old); 143 | } 144 | [self.frameUpdater refreshDisplay]; 145 | return NO; 146 | } 147 | 148 | /** 149 | * 点播事件通知 150 | * 151 | * @param player 点播对象 152 | * @param EvtID 参见TXLiveSDKEventDef.h 153 | * @param param 参见TXLiveSDKTypeDef.h 154 | * @see TXVodPlayer 155 | */ 156 | -(void)onPlayEvent:(TXVodPlayer *)player event:(int)EvtID withParam:(NSDictionary *)param{ 157 | 158 | dispatch_async(dispatch_get_main_queue(), ^{ 159 | 160 | if(EvtID==PLAY_EVT_VOD_PLAY_PREPARED){ 161 | int64_t duration = [player duration]; 162 | NSString *durationStr = [NSString stringWithFormat: @"%ld", (long)duration]; 163 | NSInteger durationInt = [durationStr intValue]; 164 | if(self->_eventSink!=nil){ 165 | self->_eventSink(@{ 166 | @"event":@"initialized", 167 | @"duration":@(durationInt), 168 | @"width":@([player width]), 169 | @"height":@([player height]) 170 | }); 171 | } 172 | 173 | 174 | }else if(EvtID==PLAY_EVT_PLAY_PROGRESS){ 175 | int64_t progress = [player currentPlaybackTime]; 176 | int64_t duration = [player duration]; 177 | int64_t playableDuration = [player playableDuration]; 178 | // NSString *progressStr = [NSString stringWithFormat: @"%ld", (long)progress]; 179 | // NSString *durationStr = [NSString stringWithFormat: @"%ld", (long)duration]; 180 | // NSString *playableDurationStr = [NSString stringWithFormat: @"%ld", (long)playableDuration]; 181 | // long progressInt = [progress intValue]*1000; 182 | // long durationint = [duration intValue]*1000; 183 | // long playableDurationInt = [playableDuration intValue]*1000; 184 | // NSLog(@"单精度浮点数: %d",progressInt); 185 | // NSLog(@"单精度浮点数: %d",durationint); 186 | if(self->_eventSink!=nil){ 187 | self->_eventSink(@{ 188 | @"event":@"progress", 189 | @"progress":@(progress*1000), 190 | @"duration":@(duration*1000), 191 | @"playable":@(playableDuration*1000) 192 | }); 193 | } 194 | 195 | }else if(EvtID==PLAY_EVT_PLAY_LOADING){ 196 | if(self->_eventSink!=nil){ 197 | self->_eventSink(@{ 198 | @"event":@"loading", 199 | }); 200 | } 201 | 202 | }else if(EvtID==PLAY_EVT_VOD_LOADING_END){ 203 | if(self->_eventSink!=nil){ 204 | self->_eventSink(@{ 205 | @"event":@"loadingend", 206 | }); 207 | } 208 | 209 | }else if(EvtID==PLAY_EVT_PLAY_END){ 210 | if(self->_eventSink!=nil){ 211 | self->_eventSink(@{ 212 | @"event":@"playend", 213 | }); 214 | } 215 | 216 | }else if(EvtID==PLAY_ERR_NET_DISCONNECT){ 217 | if(self->_eventSink!=nil){ 218 | self->_eventSink(@{ 219 | @"event":@"error", 220 | @"errorInfo":param[@"EVT_MSG"], 221 | }); 222 | 223 | self->_eventSink(@{ 224 | @"event":@"disconnect", 225 | }); 226 | 227 | } 228 | 229 | }else if(EvtID==ERR_PLAY_LIVE_STREAM_NET_DISCONNECT){ 230 | if(self->_eventSink!=nil){ 231 | self->_eventSink(@{ 232 | @"event":@"error", 233 | @"errorInfo":param[@"EVT_MSG"], 234 | }); 235 | } 236 | }else if(EvtID==WARNING_LIVE_STREAM_SERVER_RECONNECT){ 237 | if(self->_eventSink!=nil){ 238 | self->_eventSink(@{ 239 | @"event":@"error", 240 | @"errorInfo":param[@"EVT_MSG"], 241 | }); 242 | } 243 | }else { 244 | if(EvtID<0){ 245 | if(self->_eventSink!=nil){ 246 | self->_eventSink(@{ 247 | @"event":@"error", 248 | @"errorInfo":param[@"EVT_MSG"], 249 | }); 250 | } 251 | } 252 | } 253 | 254 | }); 255 | } 256 | 257 | - (void)onNetStatus:(TXVodPlayer *)player withParam:(NSDictionary *)param { 258 | if(self->_eventSink!=nil){ 259 | self->_eventSink(@{ 260 | @"event":@"netStatus", 261 | @"netSpeed": param[NET_STATUS_NET_SPEED], 262 | @"cacheSize": param[NET_STATUS_V_SUM_CACHE_SIZE], 263 | }); 264 | } 265 | } 266 | 267 | #pragma FlutterStreamHandler 268 | - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { 269 | _eventSink = nil; 270 | NSLog(@"FLTVideo 停止通信"); 271 | return nil; 272 | } 273 | 274 | - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments 275 | eventSink:(nonnull FlutterEventSink)events { 276 | _eventSink = events; 277 | 278 | NSLog(@"FLTVideo 开启通信"); 279 | //[self sendInitialized]; 280 | return nil; 281 | } 282 | 283 | - (void)dispose { 284 | _disposed = true; 285 | [self stopPlay]; 286 | _txPlayer = nil; 287 | _frameUpdater = nil; 288 | NSLog(@"FLTVideo dispose"); 289 | CVPixelBufferRef old = _latestPixelBuffer; 290 | while (!OSAtomicCompareAndSwapPtrBarrier(old, nil, 291 | (void **)&_latestPixelBuffer)) { 292 | old = _latestPixelBuffer; 293 | } 294 | if (old) { 295 | CFRelease(old); 296 | } 297 | 298 | if (_lastBuffer) { 299 | CVPixelBufferRelease(_lastBuffer); 300 | _lastBuffer = nil; 301 | } 302 | 303 | // if(_eventChannel){ 304 | // [_eventChannel setStreamHandler:nil]; 305 | // _eventChannel =nil; 306 | // } 307 | 308 | } 309 | 310 | -(void)setLoop:(BOOL)loop{ 311 | [_txPlayer setLoop:loop]; 312 | _loop = loop; 313 | } 314 | 315 | - (void)resume{ 316 | [_txPlayer resume]; 317 | } 318 | -(void)pause{ 319 | [_txPlayer pause]; 320 | } 321 | - (int64_t)position{ 322 | return [_txPlayer currentPlaybackTime]; 323 | } 324 | 325 | - (int64_t)duration{ 326 | return [_txPlayer duration]; 327 | } 328 | 329 | - (void)seekTo:(int)position{ 330 | [_txPlayer seek:position]; 331 | } 332 | 333 | - (void)setStartTime:(CGFloat)startTime{ 334 | [_txPlayer setStartTime:startTime]; 335 | } 336 | 337 | - (int)stopPlay{ 338 | return [_txPlayer stopPlay]; 339 | } 340 | 341 | - (float)playableDuration{ 342 | return [_txPlayer playableDuration]; 343 | } 344 | 345 | - (int)width{ 346 | return [_txPlayer width]; 347 | } 348 | 349 | - (int)height{ 350 | return [_txPlayer height]; 351 | } 352 | 353 | - (void)setRenderMode:(TX_Enum_Type_RenderMode)renderMode{ 354 | [_txPlayer setRenderMode:renderMode]; 355 | } 356 | 357 | - (void)setRenderRotation:(TX_Enum_Type_HomeOrientation)rotation{ 358 | 359 | [_txPlayer setRenderRotation:rotation]; 360 | } 361 | 362 | - (void)setMute:(BOOL)bEnable{ 363 | [_txPlayer setMute:bEnable]; 364 | } 365 | 366 | 367 | 368 | 369 | - (void)setRate:(float)rate{ 370 | [_txPlayer setRate:rate]; 371 | } 372 | 373 | - (void)setBitrateIndex:(int)index{ 374 | [_txPlayer setBitrateIndex:index]; 375 | } 376 | 377 | - (void)setMirror:(BOOL)isMirror{ 378 | [_txPlayer setMirror:isMirror]; 379 | } 380 | 381 | -(void)snapshot:(void (^)(UIImage * _Nonnull))snapshotCompletionBlock{ 382 | 383 | } 384 | 385 | @end 386 | -------------------------------------------------------------------------------- /ios/Classes/FlutterTencentplayerPlusPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterTencentplayerPlusPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/FlutterTencentplayerPlusPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterTencentplayerPlusPlugin.h" 2 | 3 | #import "FLTVideoPlayer.h" 4 | #import "FLTFrameUpdater.h" 5 | #import "FLTDownLoadManager.h" 6 | 7 | @interface FlutterTencentplayerPlusPlugin () 8 | 9 | @property(readonly, nonatomic) NSObject* registry; 10 | @property(readonly, nonatomic) NSObject* messenger; 11 | @property(readonly, nonatomic) NSMutableDictionary* players; 12 | @property(readonly, nonatomic) NSMutableDictionary* downLoads; 13 | @property(readonly, nonatomic) NSObject* registrar; 14 | 15 | 16 | 17 | 18 | @end 19 | 20 | 21 | @implementation FlutterTencentplayerPlusPlugin 22 | 23 | NSObject* mRegistrar; 24 | //FLTVideoPlayer* player ; 25 | 26 | 27 | //第一次进来先执行的方法 注册插件 28 | - (instancetype)initWithRegistrar:(NSObject*)registrar { 29 | self = [super init]; 30 | NSAssert(self, @"super init cannot be nil"); 31 | _registry = [registrar textures]; 32 | _messenger = [registrar messenger]; 33 | _registrar = registrar; 34 | _players = [NSMutableDictionary dictionary]; 35 | _downLoads = [NSMutableDictionary dictionaryWithCapacity:1]; 36 | NSLog(@"FLTVideo initWithRegistrar"); 37 | return self; 38 | } 39 | 40 | + (void)registerWithRegistrar:(NSObject*)registrar { 41 | FlutterMethodChannel* channel = [FlutterMethodChannel 42 | methodChannelWithName:@"flutter_tencentplayer" 43 | binaryMessenger:[registrar messenger]]; 44 | // FlutterTencentplayerPlugin* instance = [[FlutterTencentplayerPlugin alloc] init]; 45 | FlutterTencentplayerPlusPlugin* instance = [[FlutterTencentplayerPlusPlugin alloc] initWithRegistrar:registrar]; 46 | NSLog(@"FLTVideo registerWithRegistrar"); 47 | [registrar addMethodCallDelegate:instance channel:channel]; 48 | 49 | 50 | } 51 | 52 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 53 | NSLog(@"FLTVideo call name %@",call.method); 54 | if ([@"init" isEqualToString:call.method]) { 55 | [self disposeAllPlayers]; 56 | result(nil); 57 | }else if([@"create" isEqualToString:call.method]){ 58 | // [self disposeAllPlayers]; 59 | FLTFrameUpdater* frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; 60 | FLTVideoPlayer* player= [[FLTVideoPlayer alloc] initWithCall:call frameUpdater:frameUpdater registry:_registry messenger:_messenger]; 61 | 62 | if (player) { 63 | [self onPlayerSetup:player frameUpdater:frameUpdater result:result]; 64 | // NSString *textureIdStr = [NSString stringWithFormat: @"%lld",[player textureId]]; 65 | //[_players setObject:player forKey:textureIdStr]; 66 | } 67 | result(nil); 68 | }else if([@"download" isEqualToString:call.method]){ 69 | 70 | NSDictionary* argsMap = call.arguments; 71 | NSString* urlOrFileId = argsMap[@"urlOrFileId"]; 72 | NSLog(@"下载相关 startdownload %@", urlOrFileId); 73 | 74 | NSString* channelUrl =[NSString stringWithFormat:@"flutter_tencentplayer/downloadEvents%@",urlOrFileId]; 75 | NSLog(@"%@", channelUrl); 76 | FlutterEventChannel* eventChannel = [FlutterEventChannel 77 | eventChannelWithName:channelUrl 78 | binaryMessenger:_messenger]; 79 | FLTDownLoadManager* downLoadManager = [[FLTDownLoadManager alloc] initWithMethodCall:call result:result]; 80 | [eventChannel setStreamHandler:downLoadManager]; 81 | downLoadManager.eventChannel =eventChannel; 82 | [downLoadManager downLoad]; 83 | 84 | _downLoads[urlOrFileId] = downLoadManager; 85 | NSLog(@"下载相关 start 数组大小 %lu", (unsigned long)_downLoads.count); 86 | 87 | 88 | result(nil); 89 | }else if([@"stopDownload" isEqualToString:call.method]){ 90 | NSDictionary* argsMap = call.arguments; 91 | NSString* urlOrFileId = argsMap[@"urlOrFileId"]; 92 | NSLog(@"下载相关 stopDownload %@", urlOrFileId); 93 | FLTDownLoadManager* downLoadManager = _downLoads[urlOrFileId]; 94 | if(downLoadManager!=nil){ 95 | [downLoadManager stopDownLoad]; 96 | }else{ 97 | NSLog(@"下载相关 对象为空 %lu", (unsigned long)_downLoads.count); 98 | } 99 | 100 | 101 | 102 | result(nil); 103 | }else { 104 | [self onMethodCall:call result:result]; 105 | } 106 | } 107 | 108 | -(void) onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{ 109 | 110 | NSDictionary* argsMap = call.arguments; 111 | int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue; 112 | if([NSNull null]==argsMap[@"textureId"]) { 113 | return; 114 | } 115 | // int64_t textureId = ((NSNumber*)argsMap[@"textureId"]).unsignedIntegerValue; 116 | //NSString *textureIdStr = [NSString stringWithFormat: @"%lld",textureId]; 117 | //FLTVideoPlayer* player = _players[textureIdStr]; 118 | FLTVideoPlayer* player = _players[@(textureId)]; 119 | 120 | if([@"play" isEqualToString:call.method]){ 121 | [player resume]; 122 | result(nil); 123 | }else if([@"pause" isEqualToString:call.method]){ 124 | [player pause]; 125 | result(nil); 126 | }else if([@"seekTo" isEqualToString:call.method]){ 127 | NSLog(@"跳转到指定位置----------"); 128 | [player seekTo:[[argsMap objectForKey:@"location"] intValue]]; 129 | result(nil); 130 | }else if([@"setRate" isEqualToString:call.method]){ //播放速率 131 | NSLog(@"修改播放速率----------"); 132 | float rate = [[argsMap objectForKey:@"rate"] floatValue]; 133 | if (rate<0||rate>2) { 134 | result(nil); 135 | return; 136 | } 137 | [player setRate:rate]; 138 | result(nil); 139 | 140 | }else if([@"setBitrateIndex" isEqualToString:call.method]){ 141 | NSLog(@"修改播放清晰度----------"); 142 | int index = [[argsMap objectForKey:@"index"] intValue]; 143 | [player setBitrateIndex:index]; 144 | result(nil); 145 | }else if([@"dispose" isEqualToString:call.method]){ 146 | [_registry unregisterTexture:textureId]; 147 | [player dispose]; 148 | player= nil; 149 | [_players removeObjectForKey:@(textureId)]; 150 | result(nil); 151 | }else{ 152 | result(FlutterMethodNotImplemented); 153 | } 154 | 155 | } 156 | 157 | - (void)onPlayerSetup:(FLTVideoPlayer*)player 158 | frameUpdater:(FLTFrameUpdater*)frameUpdater 159 | result:(FlutterResult)result { 160 | _players[@(player.textureId)] = player; 161 | result(@{@"textureId" : @(player.textureId)}); 162 | 163 | } 164 | // 165 | //-(void) disposeAllPlayers{ 166 | // NSLog(@"FLTVideo 初始化播放器状态----------"); 167 | // // Allow audio playback when the Ring/Silent switch is set to silent 168 | // [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; 169 | // if(player){ 170 | // [player dispose]; 171 | // player = nil; 172 | // } 173 | //} 174 | 175 | 176 | -(void) disposeAllPlayers{ 177 | NSLog(@"初始化状态----------"); 178 | // Allow audio playback when the Ring/Silent switch is set to silent 179 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; 180 | 181 | for (NSNumber* textureId in _players) { 182 | [_registry unregisterTexture:[textureId unsignedIntegerValue]]; 183 | [[_players objectForKey:textureId] dispose]; 184 | } 185 | [_players removeAllObjects]; 186 | } 187 | 188 | @end 189 | 190 | -------------------------------------------------------------------------------- /ios/flutter_tencentplayer_plus.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'flutter_tencentplayer_plus' 6 | s.version = '0.0.1' 7 | s.summary = 'flutter_plugin_flutter_tencentplayer_plus' 8 | s.description = <<-DESC 9 | flutter_plugin_flutter_tencentplayer_plus 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | s.dependency 'TXLiteAVSDK_Player', '= 6.8.7969' 19 | s.user_target_xcconfig = { 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' } 20 | s.ios.deployment_target = '8.0' 21 | end 22 | 23 | -------------------------------------------------------------------------------- /lib/controller/download_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_tencentplayer_plus/flutter_tencentplayer_plus.dart'; 5 | 6 | class DownloadController extends ValueNotifier> { 7 | final String savePath; 8 | final int appId; 9 | StreamSubscription _eventSubscription; 10 | MethodChannel channel = TencentPlayer.channel; 11 | bool _isDisposed = false; 12 | 13 | DownloadController(this.savePath, {this.appId}) 14 | : super(Map()); 15 | 16 | void dowload(String urlOrFileId, {int quanlity}) async { 17 | Map downloadInfoMap = { 18 | "savePath": savePath, 19 | "urlOrFileId": urlOrFileId, 20 | "appId": appId, 21 | "quanlity": quanlity, 22 | }; 23 | 24 | await channel.invokeMethod( 25 | 'download', 26 | downloadInfoMap, 27 | ); 28 | _eventSubscription = _eventChannelFor(urlOrFileId) 29 | .receiveBroadcastStream() 30 | .listen(eventListener); 31 | } 32 | 33 | EventChannel _eventChannelFor(String urlOrFileId) { 34 | return EventChannel('flutter_tencentplayer/downloadEvents$urlOrFileId'); 35 | } 36 | 37 | void eventListener(dynamic event) { 38 | if (_isDisposed) { 39 | return; 40 | } 41 | final Map map = event; 42 | debugPrint("native to flutter"); 43 | debugPrint(map.toString()); 44 | DownloadValue downloadValue = DownloadValue.fromJson(map); 45 | if (downloadValue.fileId != null) { 46 | value[downloadValue.fileId] = downloadValue; 47 | } else { 48 | value[downloadValue.url] = downloadValue; 49 | } 50 | notifyListeners(); 51 | } 52 | 53 | @override 54 | Future dispose() async { 55 | _isDisposed = true; 56 | _eventSubscription.cancel(); 57 | super.dispose(); 58 | } 59 | 60 | Future pauseDownload(String urlOrFileId) async { 61 | if (_isDisposed) { 62 | return; 63 | } 64 | await channel.invokeMethod( 65 | 'stopDownload', 66 | { 67 | "urlOrFileId": urlOrFileId, 68 | }, 69 | ); 70 | } 71 | 72 | Future cancelDownload(String urlOrFileId) async { 73 | if (_isDisposed) { 74 | return; 75 | } 76 | await channel.invokeMethod( 77 | 'stopDownload', 78 | { 79 | "urlOrFileId": urlOrFileId, 80 | }, 81 | ); 82 | 83 | if (value.containsKey(urlOrFileId)) { 84 | Future.delayed(Duration(milliseconds: 2500), () { 85 | value.remove(urlOrFileId); 86 | }); 87 | } 88 | 89 | notifyListeners(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/controller/tencent_player_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_tencentplayer_plus/flutter_tencentplayer_plus.dart'; 5 | 6 | class TencentPlayerController extends ValueNotifier { 7 | int _textureId; 8 | final String dataSource; 9 | final DataSourceType dataSourceType; 10 | final PlayerConfig playerConfig; 11 | MethodChannel channel = TencentPlayer.channel; 12 | 13 | TencentPlayerController.asset(this.dataSource, 14 | {this.playerConfig = const PlayerConfig()}) 15 | : dataSourceType = DataSourceType.asset, 16 | super(TencentPlayerValue()); 17 | 18 | TencentPlayerController.network(this.dataSource, 19 | {this.playerConfig = const PlayerConfig()}) 20 | : dataSourceType = DataSourceType.network, 21 | super(TencentPlayerValue()); 22 | 23 | TencentPlayerController.file(String filePath, 24 | {this.playerConfig = const PlayerConfig()}) 25 | : dataSource = filePath, 26 | dataSourceType = DataSourceType.file, 27 | super(TencentPlayerValue()); 28 | 29 | bool _isDisposed = false; 30 | StreamSubscription _eventSubscription; 31 | _VideoAppLifeCycleObserver _lifeCycleObserver; 32 | 33 | // @visibleForTesting 34 | int get textureId => _textureId; 35 | 36 | ///初始化播放器的方法 37 | Future initialize() async { 38 | _lifeCycleObserver = _VideoAppLifeCycleObserver(this); 39 | _lifeCycleObserver.initialize(); 40 | Map dataSourceDescription; 41 | switch (dataSourceType) { 42 | case DataSourceType.asset: 43 | dataSourceDescription = {'asset': dataSource}; 44 | break; 45 | case DataSourceType.network: 46 | case DataSourceType.file: 47 | dataSourceDescription = {'uri': dataSource}; 48 | break; 49 | } 50 | 51 | value = value.copyWith(isPlaying: playerConfig.autoPlay); 52 | dataSourceDescription.addAll(playerConfig.toJson()); 53 | 54 | final Map response = 55 | await channel.invokeMapMethod( 56 | 'create', 57 | dataSourceDescription, 58 | ); 59 | 60 | _textureId = response['textureId']; 61 | 62 | ///设置监听naive 返回的的数据 63 | _eventSubscription = _eventChannelFor(_textureId) 64 | .receiveBroadcastStream() 65 | .listen(eventListener); 66 | } 67 | 68 | ///注册监听native的方法 69 | EventChannel _eventChannelFor(int textureId) { 70 | return EventChannel('flutter_tencentplayer/videoEvents$textureId'); 71 | } 72 | 73 | ///native 传递到flutter 进行数据处理 74 | void eventListener(dynamic event) { 75 | if (_isDisposed) { 76 | return; 77 | } 78 | final Map map = event; 79 | switch (map['event']) { 80 | case 'initialized': 81 | value = value.copyWith( 82 | duration: Duration(milliseconds: map['duration']), 83 | size: Size(map['width']?.toDouble() ?? 0.0, 84 | map['height']?.toDouble() ?? 0.0), 85 | ); 86 | break; 87 | case 'progress': 88 | value = value.copyWith( 89 | position: Duration(milliseconds: map['progress']), 90 | duration: Duration(milliseconds: map['duration']), 91 | playable: Duration(milliseconds: map['playable']), 92 | ); 93 | break; 94 | case 'loading': 95 | value = value.copyWith(isLoading: true); 96 | break; 97 | case 'loadingend': 98 | value = value.copyWith(isLoading: false); 99 | break; 100 | case 'playend': 101 | value = value.copyWith(isPlaying: false, position: value.duration); 102 | break; 103 | case 'netStatus': 104 | value = value.copyWith(netSpeed: map['netSpeed']); 105 | break; 106 | case 'error': 107 | value = value.copyWith(errorDescription: map['errorInfo']); 108 | break; 109 | } 110 | } 111 | 112 | @override 113 | Future dispose() async { 114 | if (!_isDisposed) { 115 | _isDisposed = true; 116 | await _eventSubscription?.cancel(); 117 | await channel.invokeListMethod( 118 | 'dispose', {'textureId': _textureId}); 119 | _lifeCycleObserver.dispose(); 120 | } 121 | super.dispose(); 122 | } 123 | 124 | Future play() async { 125 | value = value.copyWith(isPlaying: true); 126 | await _applyPlayPause(); 127 | } 128 | 129 | Future pause() async { 130 | value = value.copyWith(isPlaying: false); 131 | await _applyPlayPause(); 132 | } 133 | 134 | Future _applyPlayPause() async { 135 | if (!value.initialized || _isDisposed) { 136 | return; 137 | } 138 | if (value.isPlaying) { 139 | await channel 140 | .invokeMethod('play', {'textureId': _textureId}); 141 | } else { 142 | await channel 143 | .invokeMethod('pause', {'textureId': _textureId}); 144 | } 145 | } 146 | 147 | Future seekTo(Duration moment) async { 148 | if (_isDisposed) { 149 | return; 150 | } 151 | if (moment == null) { 152 | return; 153 | } 154 | if (moment > value.duration) { 155 | moment = value.duration; 156 | } else if (moment < const Duration()) { 157 | moment = const Duration(); 158 | } 159 | await channel.invokeMethod('seekTo', { 160 | 'textureId': _textureId, 161 | 'location': moment.inSeconds, 162 | }); 163 | value = value.copyWith(position: moment); 164 | } 165 | 166 | ///点播为m3u8子流,会自动无缝seek 167 | Future setBitrateIndex(int index) async { 168 | if (_isDisposed) { 169 | return; 170 | } 171 | await channel.invokeMethod('setBitrateIndex', { 172 | 'textureId': _textureId, 173 | 'index': index, 174 | }); 175 | value = value.copyWith(bitrateIndex: index); 176 | } 177 | 178 | Future setRate(double rate) async { 179 | if (_isDisposed) { 180 | return; 181 | } 182 | if (rate > 2.0) { 183 | rate = 2.0; 184 | } else if (rate < 1.0) { 185 | rate = 1.0; 186 | } 187 | await channel.invokeMethod('setRate', { 188 | 'textureId': _textureId, 189 | 'rate': rate, 190 | }); 191 | value = value.copyWith(rate: rate); 192 | } 193 | } 194 | 195 | ///视频组件生命周期监听 196 | class _VideoAppLifeCycleObserver with WidgetsBindingObserver { 197 | bool _wasPlayingBeforePause = false; 198 | final TencentPlayerController _controller; 199 | 200 | _VideoAppLifeCycleObserver(this._controller); 201 | 202 | void initialize() { 203 | WidgetsBinding.instance.addObserver(this); 204 | } 205 | 206 | @override 207 | void didChangeAppLifecycleState(AppLifecycleState state) { 208 | switch (state) { 209 | 210 | ///组件进入暂停状态 211 | case AppLifecycleState.paused: 212 | _wasPlayingBeforePause = _controller.value.isPlaying; 213 | _controller.pause(); 214 | break; 215 | 216 | ///组件进入活跃状态 217 | case AppLifecycleState.resumed: 218 | if (_wasPlayingBeforePause) { 219 | _controller.play(); 220 | } 221 | break; 222 | default: 223 | } 224 | } 225 | 226 | void dispose() { 227 | WidgetsBinding.instance.removeObserver(this); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /lib/flutter_tencentplayer_plus.dart: -------------------------------------------------------------------------------- 1 | export 'model/download_value.dart'; 2 | export 'model/player_config.dart'; 3 | export 'model/tentcent_player_value.dart'; 4 | 5 | export 'controller/download_controller.dart'; 6 | export 'controller/tencent_player_controller.dart'; 7 | 8 | export 'package:flutter_tencentplayer_plus/view/tencent_player.dart'; 9 | -------------------------------------------------------------------------------- /lib/model/download_value.dart: -------------------------------------------------------------------------------- 1 | class DownloadValue { 2 | final String downloadStatus; 3 | final int quanlity; 4 | final int duration; 5 | final int size; 6 | final int downloadSize; 7 | final double progress; 8 | final String playPath; 9 | final bool isStop; 10 | final String url; 11 | final String fileId; 12 | final String error; 13 | 14 | DownloadValue({ 15 | this.downloadStatus, 16 | this.quanlity, 17 | this.duration, 18 | this.size, 19 | this.downloadSize, 20 | this.progress, 21 | this.playPath, 22 | this.isStop, 23 | this.url, 24 | this.fileId, 25 | this.error, 26 | }); 27 | 28 | DownloadValue copyWith({ 29 | String downloadStatus, 30 | int quanlity, 31 | int duration, 32 | int size, 33 | int downloadSize, 34 | double progress, 35 | String playPath, 36 | bool isStop, 37 | String url, 38 | String fileId, 39 | String error, 40 | }) { 41 | return DownloadValue( 42 | downloadStatus: downloadStatus ?? this.downloadStatus, 43 | quanlity: quanlity ?? this.quanlity, 44 | duration: duration ?? this.duration, 45 | size: size ?? this.size, 46 | downloadSize: downloadSize ?? this.downloadSize, 47 | progress: progress ?? this.progress, 48 | playPath: playPath ?? this.playPath, 49 | isStop: isStop ?? this.isStop, 50 | url: url ?? this.url, 51 | fileId: fileId ?? this.fileId, 52 | error: error ?? this.error, 53 | ); 54 | } 55 | 56 | @override 57 | String toString() { 58 | return toJson().toString(); 59 | } 60 | 61 | Map toJson() => { 62 | 'downloadStatus': this.downloadStatus, 63 | 'quanlity': this.quanlity, 64 | 'duration': this.duration, 65 | 'size': this.size, 66 | 'downloadSize': this.downloadSize, 67 | 'progress': this.progress, 68 | 'playPath': this.playPath, 69 | 'isStop': this.isStop, 70 | 'url': this.url, 71 | 'fileId': this.fileId, 72 | 'error': this.error, 73 | }; 74 | 75 | factory DownloadValue.fromJson(Map json) { 76 | return DownloadValue( 77 | downloadStatus: json['downloadStatus'] as String, 78 | quanlity: int.parse(json['quanlity'].toString()), 79 | duration: int.parse(json['duration']), 80 | size: int.parse(json['size']), 81 | downloadSize: int.parse(json['downloadSize']), 82 | progress: double.parse(json['progress']), 83 | playPath: json['playPath'] as String, 84 | isStop: json['isStop'] == "true", 85 | url: json['url'] as String, 86 | fileId: json['fileId'] as String, 87 | error: json['error'] as String, 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/model/player_config.dart: -------------------------------------------------------------------------------- 1 | class PlayerConfig { 2 | final bool autoPlay; 3 | final bool loop; 4 | final Map headers; 5 | final String cachePath; 6 | final int progressInterval; 7 | 8 | // 单位:秒 9 | final int startTime; 10 | final Map auth; 11 | 12 | const PlayerConfig({ 13 | this.autoPlay = true, 14 | this.loop = false, 15 | this.headers, 16 | this.cachePath, 17 | this.progressInterval = 200, 18 | this.startTime, 19 | this.auth, 20 | }); 21 | 22 | Map toJson() => { 23 | 'autoPlay': this.autoPlay, 24 | 'loop': this.loop, 25 | 'headers': this.headers, 26 | 'cachePath': this.cachePath, 27 | 'progressInterval': this.progressInterval, 28 | 'startTime': this.startTime, 29 | 'auth': this.auth, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /lib/model/tentcent_player_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TencentPlayerValue { 4 | final Duration duration; 5 | final Duration position; 6 | final Duration playable; 7 | final bool isPlaying; 8 | final String errorDescription; 9 | final Size size; 10 | final bool isLoading; 11 | final int netSpeed; 12 | final double rate; 13 | final int bitrateIndex; 14 | 15 | bool get initialized => duration.inMilliseconds != 0; 16 | 17 | bool get hasError => errorDescription != null; 18 | 19 | double get aspectRatio => size != null 20 | ? size.width / size.height > 0.0 ? size.width / size.height : 1.0 21 | : 1.0; 22 | 23 | TencentPlayerValue({ 24 | this.duration = const Duration(), 25 | this.position = const Duration(), 26 | this.playable = const Duration(), 27 | this.isPlaying = false, 28 | this.errorDescription, 29 | this.size, 30 | this.isLoading = false, 31 | this.netSpeed, 32 | this.rate = 1.0, 33 | this.bitrateIndex = 0, //TODO 默认清晰度 34 | }); 35 | 36 | TencentPlayerValue copyWith({ 37 | Duration duration, 38 | Duration position, 39 | Duration playable, 40 | bool isPlaying, 41 | String errorDescription, 42 | Size size, 43 | bool isLoading, 44 | int netSpeed, 45 | double rate, 46 | int bitrateIndex, 47 | }) { 48 | return TencentPlayerValue( 49 | duration: duration ?? this.duration, 50 | position: position ?? this.position, 51 | playable: playable ?? this.playable, 52 | isPlaying: isPlaying ?? this.isPlaying, 53 | errorDescription: errorDescription ?? this.errorDescription, 54 | size: size ?? this.size, 55 | isLoading: isLoading ?? this.isLoading, 56 | netSpeed: netSpeed ?? this.netSpeed, 57 | rate: rate ?? this.rate, 58 | bitrateIndex: bitrateIndex ?? this.bitrateIndex, 59 | ); 60 | } 61 | 62 | @override 63 | String toString() { 64 | return '$runtimeType(' 65 | 'duration: $duration, ' 66 | 'position: $position, ' 67 | 'playable: $playable, ' 68 | 'isPlaying: $isPlaying, ' 69 | 'errorDescription: $errorDescription),' 70 | 'isLoading: $isLoading),' 71 | 'netSpeed: $netSpeed),' 72 | 'rate: $rate),' 73 | 'bitrateIndex: $bitrateIndex),' 74 | 'size: $size)'; 75 | } 76 | } 77 | 78 | enum DataSourceType { asset, network, file } 79 | -------------------------------------------------------------------------------- /lib/view/tencent_player.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_tencentplayer_plus/flutter_tencentplayer_plus.dart'; 4 | 5 | class TencentPlayer extends StatefulWidget { 6 | static MethodChannel channel = const MethodChannel('flutter_tencentplayer') 7 | 8 | /// init方法是TencentPlayer 第一次加载的时候执行 并且只是执行一次 9 | ..invokeMethod('init'); 10 | 11 | final TencentPlayerController controller; 12 | 13 | TencentPlayer(this.controller); 14 | 15 | @override 16 | _TencentPlayerState createState() => new _TencentPlayerState(); 17 | } 18 | 19 | class _TencentPlayerState extends State { 20 | // VoidCallback _listener; 21 | // int _textureId; 22 | // 23 | // _TencentPlayerState() { 24 | // _listener = () { 25 | // final int newTextureId = widget.controller.textureId; 26 | // if (newTextureId != _textureId) { 27 | // setState(() { 28 | // _textureId = newTextureId; 29 | // }); 30 | // } 31 | // }; 32 | // } 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | // _textureId = widget.controller.textureId; 38 | // widget.controller.addListener(_listener); 39 | 40 | print("TencentPlayer initState"); 41 | } 42 | 43 | @override 44 | void didUpdateWidget(TencentPlayer oldWidget) { 45 | //print("TencentPlayer didUpdateWidget"); 46 | super.didUpdateWidget(oldWidget); 47 | // oldWidget.controller.removeListener(_listener); 48 | // _textureId = widget.controller.textureId; 49 | // widget.controller.addListener(_listener); 50 | } 51 | 52 | @override 53 | void deactivate() { 54 | print("TencentPlayer deactivate"); 55 | super.deactivate(); 56 | // widget.controller.removeListener(_listener); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return ValueListenableBuilder( 62 | valueListenable: widget.controller, 63 | builder: (BuildContext context, TencentPlayerValue value, Widget child) { 64 | // return Texture(textureId: widget.controller.textureId); 65 | var _textureId = widget.controller.textureId; 66 | return _textureId == null 67 | ? Container() 68 | : Texture(textureId: _textureId); 69 | }, 70 | ); 71 | // ChangeNotifierProvider 72 | // 73 | // return _textureId == null ? Container() : Texture(textureId: _textureId); 74 | } 75 | 76 | @override 77 | void dispose() { 78 | print("TencentPlayer dispose"); 79 | widget.controller.dispose(); 80 | super.dispose(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /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.3.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.0.5" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.14.11" 32 | flutter: 33 | dependency: "direct main" 34 | description: flutter 35 | source: sdk 36 | version: "0.0.0" 37 | flutter_test: 38 | dependency: "direct dev" 39 | description: flutter 40 | source: sdk 41 | version: "0.0.0" 42 | matcher: 43 | dependency: transitive 44 | description: 45 | name: matcher 46 | url: "https://pub.flutter-io.cn" 47 | source: hosted 48 | version: "0.12.5" 49 | meta: 50 | dependency: transitive 51 | description: 52 | name: meta 53 | url: "https://pub.flutter-io.cn" 54 | source: hosted 55 | version: "1.1.7" 56 | path: 57 | dependency: transitive 58 | description: 59 | name: path 60 | url: "https://pub.flutter-io.cn" 61 | source: hosted 62 | version: "1.6.4" 63 | pedantic: 64 | dependency: transitive 65 | description: 66 | name: pedantic 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "1.8.0+1" 70 | quiver: 71 | dependency: transitive 72 | description: 73 | name: quiver 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "2.0.5" 77 | sky_engine: 78 | dependency: transitive 79 | description: flutter 80 | source: sdk 81 | version: "0.0.99" 82 | source_span: 83 | dependency: transitive 84 | description: 85 | name: source_span 86 | url: "https://pub.flutter-io.cn" 87 | source: hosted 88 | version: "1.5.5" 89 | stack_trace: 90 | dependency: transitive 91 | description: 92 | name: stack_trace 93 | url: "https://pub.flutter-io.cn" 94 | source: hosted 95 | version: "1.9.3" 96 | stream_channel: 97 | dependency: transitive 98 | description: 99 | name: stream_channel 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "2.0.0" 103 | string_scanner: 104 | dependency: transitive 105 | description: 106 | name: string_scanner 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.0.5" 110 | term_glyph: 111 | dependency: transitive 112 | description: 113 | name: term_glyph 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.1.0" 117 | test_api: 118 | dependency: transitive 119 | description: 120 | name: test_api 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "0.2.5" 124 | typed_data: 125 | dependency: transitive 126 | description: 127 | name: typed_data 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.1.6" 131 | vector_math: 132 | dependency: transitive 133 | description: 134 | name: vector_math 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "2.0.8" 138 | sdks: 139 | dart: ">=2.2.2 <3.0.0" 140 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_tencentplayer_plus 2 | description: The flutter plugin supports live local video playback and supports both android and ios 3 | version: 0.1.0 4 | homepage: https://github.com/yxwandroid/flutter_tencentplayer_plus.git 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | 17 | # For information on the generic Dart part of this file, see the 18 | # following page: https://dart.dev/tools/pub/pubspec 19 | 20 | # The following section is specific to Flutter. 21 | flutter: 22 | # This section identifies this Flutter project as a plugin project. 23 | # The androidPackage and pluginClass identifiers should not ordinarily 24 | # be modified. They are used by the tooling to maintain consistency when 25 | # adding or updating assets for this project. 26 | plugin: 27 | androidPackage: plus.tencentplayer.wilson.flutter.flutter_tencentplayer_plus 28 | pluginClass: FlutterTencentplayerPlusPlugin 29 | 30 | # To add assets to your plugin package, add an assets section, like this: 31 | # assets: 32 | # - images/a_dot_burr.jpeg 33 | # - images/a_dot_ham.jpeg 34 | # 35 | # For details regarding assets in packages, see 36 | # https://flutter.dev/assets-and-images/#from-packages 37 | # 38 | # An image asset can refer to one or more resolution-specific "variants", see 39 | # https://flutter.dev/assets-and-images/#resolution-aware. 40 | 41 | # To add custom fonts to your plugin package, add a fonts section here, 42 | # in this "flutter" section. Each entry in this list should have a 43 | # "family" key with the font family name, and a "fonts" key with a 44 | # list giving the asset and other descriptors for the font. For 45 | # example: 46 | # fonts: 47 | # - family: Schyler 48 | # fonts: 49 | # - asset: fonts/Schyler-Regular.ttf 50 | # - asset: fonts/Schyler-Italic.ttf 51 | # style: italic 52 | # - family: Trajan Pro 53 | # fonts: 54 | # - asset: fonts/TrajanPro.ttf 55 | # - asset: fonts/TrajanPro_Bold.ttf 56 | # weight: 700 57 | # 58 | # For details regarding fonts in packages, see 59 | # https://flutter.dev/custom-fonts/#from-packages 60 | -------------------------------------------------------------------------------- /readme/android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yxwandroid/flutter_tencentplayer_plus/35e729c9910abbea17555099b3fdf322dc3d0b12/readme/android.gif -------------------------------------------------------------------------------- /test/flutter_tencentplayer_plus_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | const MethodChannel channel = MethodChannel('flutter_tencentplayer_plus'); 6 | 7 | setUp(() { 8 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 9 | return '42'; 10 | }); 11 | }); 12 | 13 | tearDown(() { 14 | channel.setMockMethodCallHandler(null); 15 | }); 16 | 17 | // test('getPlatformVersion', () async { 18 | // expect(await FlutterTencentplayerPlus.platformVersion, '42'); 19 | // }); 20 | } 21 | --------------------------------------------------------------------------------