├── .github └── FUNDING.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── flutterplayer │ └── fplayer │ ├── FEngine.java │ ├── FPlayer.java │ ├── FVolume.java │ ├── FileMediaDataSource.java │ ├── FplayerPlugin.java │ ├── HostOption.java │ ├── QueuingEventSink.java │ └── RawMediaDataSource.java ├── example ├── .gitignore ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── flutterplayer │ │ │ │ │ └── fplayer_example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── app_bar.dart │ ├── home_page.dart │ ├── main.dart │ ├── media_item.dart │ ├── recent_list.dart │ └── video_page.dart ├── macos │ ├── Flutter │ │ ├── GeneratedPluginRegistrant.swift │ │ └── ephemeral │ │ │ ├── Flutter-Generated.xcconfig │ │ │ └── flutter_export_environment.sh │ └── Podfile ├── pubspec.yaml └── test │ └── widget_test.dart ├── images ├── wx.png └── zfb.png ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FHostOption.h │ ├── FHostOption.m │ ├── FPlayer.h │ ├── FPlayer.m │ ├── FQueuingEventSink.h │ ├── FQueuingEventSink.m │ ├── FplayerPlugin.h │ └── FplayerPlugin.m └── fplayer.podspec ├── lib ├── core │ ├── f_log.dart │ ├── f_option.dart │ ├── f_player.dart │ ├── f_plugin.dart │ ├── f_value.dart │ ├── f_view.dart │ └── f_vol.dart ├── fplayer.dart ├── fplayer_method_channel.dart ├── fplayer_platform_interface.dart └── style │ ├── panel.dart │ ├── panel2.dart │ ├── slider.dart │ └── volume.dart ├── pubspec.yaml └── test ├── fplayer_method_channel_test.dart └── fplayer_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: biless # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /.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. 5 | 6 | version: 7 | revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c 8 | channel: stable 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c 17 | base_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c 18 | - platform: android 19 | create_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c 20 | base_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c 21 | - platform: ios 22 | create_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c 23 | base_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2023] [ndy] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fplayer (Video player plugin for Flutter) Flutter 媒体播放器 2 | 3 | 4 | A Flutter media player plugin for iOS and android based on [fplayer-core](https://github.com/FlutterPlayer/ijkplayer) 5 | 6 | 您的支持是我们开发的动力。 欢迎Star,欢迎PR~。 7 | [Feedback welcome](https://github.com/FlutterPlayer/fplayer/issues) and 8 | [Pull Requests](https://github.com/FlutterPlayer/fplayer/pulls) are most welcome! 9 | 10 | ## Documentation 文档 11 | 12 | * 开发文档 https://fplayer.dev/ 包含首页、入门指南、基础、内核、fplayer 中的概念理解 13 | 14 | ## Installation 安装 15 | 16 | Add `fplayer` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). 17 | 18 | [![pub package](https://img.shields.io/pub/v/fplayer.svg)](https://pub.dartlang.org/packages/fplayer) 19 | 20 | ```yaml 21 | dependencies: 22 | fplayer: ^{{latest version}} 23 | ``` 24 | 25 | Replace `{{latest version}}` with the version number in badge above. 26 | 27 | Use git branch which not published to pub. 28 | ```yaml 29 | dependencies: 30 | fplayer: 31 | git: 32 | url: https://github.com/FlutterPlayer/fplayer.git 33 | ref: develop # can be replaced to branch or tag name 34 | ``` 35 | 36 | ## Example 示例 37 | 38 | ```dart 39 | import 'package:flutter/foundation.dart'; 40 | import 'package:flutter/material.dart'; 41 | import 'package:fplayer/fplayer.dart'; 42 | import 'package:screen_brightness/screen_brightness.dart'; 43 | 44 | import 'app_bar.dart'; 45 | 46 | class VideoScreen extends StatefulWidget { 47 | final String url; 48 | 49 | const VideoScreen({super.key, required this.url}); 50 | 51 | @override 52 | VideoScreenState createState() => VideoScreenState(); 53 | } 54 | 55 | class VideoScreenState extends State { 56 | final FPlayer player = FPlayer(); 57 | 58 | // 视频列表 59 | List videoList = [ 60 | VideoItem( 61 | title: '第一集', 62 | subTitle: '视频1副标题', 63 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 64 | ), 65 | VideoItem( 66 | title: '第二集', 67 | subTitle: '视频2副标题', 68 | url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4', 69 | ), 70 | VideoItem( 71 | title: '第三集', 72 | subTitle: '视频3副标题', 73 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 74 | ), 75 | ]; 76 | 77 | // 倍速列表 78 | Map speedList = { 79 | "2.0": 2.0, 80 | "1.5": 1.5, 81 | "1.0": 1.0, 82 | "0.5": 0.5, 83 | }; 84 | 85 | // 清晰度列表 86 | Map resolutionList = { 87 | "480P": ResolutionItem( 88 | value: 480, 89 | url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4', 90 | ), 91 | "270P": ResolutionItem( 92 | value: 270, 93 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 94 | ), 95 | }; 96 | 97 | // 视频索引,单个视频可不传 98 | int videoIndex = 0; 99 | 100 | // 模拟播放记录视频初始化完需要跳转的进度 101 | int seekTime = 100000; 102 | 103 | VideoScreenState(); 104 | 105 | @override 106 | void initState() { 107 | super.initState(); 108 | startPlay(); 109 | } 110 | 111 | void startPlay() async { 112 | // 视频播放相关配置 113 | await player.setOption(FOption.hostCategory, "enable-snapshot", 1); 114 | await player.setOption(FOption.hostCategory, "request-screen-on", 1); 115 | await player.setOption(FOption.hostCategory, "request-audio-focus", 1); 116 | await player.setOption(FOption.playerCategory, "reconnect", 20); 117 | await player.setOption(FOption.playerCategory, "framedrop", 20); 118 | await player.setOption(FOption.playerCategory, "enable-accurate-seek", 1); 119 | await player.setOption(FOption.playerCategory, "mediacodec", 1); 120 | await player.setOption(FOption.playerCategory, "packet-buffering", 0); 121 | await player.setOption(FOption.playerCategory, "soundtouch", 1); 122 | 123 | // 播放传入的视频 124 | setVideoUrl(widget.url); 125 | 126 | // 播放视频列表的第一个视频 127 | // setVideoUrl(videoList[videoIndex].url); 128 | } 129 | 130 | Future setVideoUrl(String url) async { 131 | try { 132 | await player.setDataSource(url, autoPlay: true, showCover: true); 133 | } catch (error) { 134 | print("播放-异常: $error"); 135 | return; 136 | } 137 | } 138 | 139 | @override 140 | Widget build(BuildContext context) { 141 | MediaQueryData mediaQueryData = MediaQuery.of(context); 142 | Size size = mediaQueryData.size; 143 | double videoHeight = size.width * 9 / 16; 144 | return Scaffold( 145 | appBar: const FAppBar.defaultSetting(title: "Video"), 146 | body: Column( 147 | children: [ 148 | FView( 149 | player: player, 150 | width: double.infinity, 151 | height: videoHeight, 152 | color: Colors.black, 153 | fsFit: FFit.contain, // 全屏模式下的填充 154 | fit: FFit.fill, // 正常模式下的填充 155 | panelBuilder: fPanelBuilder( 156 | // 单视频配置 157 | title: '视频标题', 158 | subTitle: '视频副标题', 159 | // 右下方截屏按钮 160 | isSnapShot: true, 161 | // 右上方按钮组开关 162 | isRightButton: true, 163 | // 右上方按钮组 164 | rightButtonList: [ 165 | InkWell( 166 | onTap: () {}, 167 | child: Container( 168 | padding: const EdgeInsets.all(10), 169 | decoration: BoxDecoration( 170 | color: Theme.of(context).primaryColorLight, 171 | borderRadius: const BorderRadius.vertical( 172 | top: Radius.circular(5), 173 | ), 174 | ), 175 | child: Icon( 176 | Icons.favorite, 177 | color: Theme.of(context).primaryColor, 178 | ), 179 | ), 180 | ), 181 | InkWell( 182 | onTap: () {}, 183 | child: Container( 184 | padding: const EdgeInsets.all(10), 185 | decoration: BoxDecoration( 186 | color: Theme.of(context).primaryColorLight, 187 | borderRadius: const BorderRadius.vertical( 188 | bottom: Radius.circular(5), 189 | ), 190 | ), 191 | child: Icon( 192 | Icons.thumb_up, 193 | color: Theme.of(context).primaryColor, 194 | ), 195 | ), 196 | ) 197 | ], 198 | // 字幕功能:待内核提供api 199 | // caption: true, 200 | // 视频列表开关 201 | isVideos: true, 202 | // 视频列表列表 203 | videoList: videoList, 204 | // 当前视频索引 205 | videoIndex: videoIndex, 206 | // 全屏模式下点击播放下一集视频按钮 207 | playNextVideoFun: () { 208 | setState(() { 209 | videoIndex += 1; 210 | }); 211 | }, 212 | settingFun: () { 213 | print('设置按钮点击事件'); 214 | }, 215 | // 自定义倍速列表 216 | speedList: speedList, 217 | // 清晰度开关 218 | isResolution: true, 219 | // 自定义清晰度列表 220 | resolutionList: resolutionList, 221 | // 视频播放错误点击刷新回调 222 | onError: () async { 223 | await player.reset(); 224 | setVideoUrl(videoList[videoIndex].url); 225 | }, 226 | // 视频播放完成回调 227 | onVideoEnd: () async { 228 | var index = videoIndex + 1; 229 | if (index < videoList.length) { 230 | await player.reset(); 231 | setState(() { 232 | videoIndex = index; 233 | }); 234 | setVideoUrl(videoList[index].url); 235 | } 236 | }, 237 | onVideoTimeChange: () { 238 | // 视频时间变动则触发一次,可以保存视频播放历史 239 | }, 240 | onVideoPrepared: () async { 241 | // 视频初始化完毕,如有历史记录时间段则可以触发快进 242 | try { 243 | if (seekTime >= 1) { 244 | /// seekTo必须在FState.prepared 245 | print('seekTo'); 246 | await player.seekTo(seekTime); 247 | // print("视频快进-$seekTime"); 248 | seekTime = 0; 249 | } 250 | } catch (error) { 251 | print("视频初始化完快进-异常: $error"); 252 | } 253 | }, 254 | ), 255 | ), 256 | // 自定义小屏列表 257 | Container( 258 | width: double.infinity, 259 | height: 30, 260 | margin: const EdgeInsets.all(20), 261 | child: ListView.builder( 262 | scrollDirection: Axis.horizontal, 263 | padding: EdgeInsets.zero, 264 | itemCount: videoList.length, 265 | itemBuilder: (context, index) { 266 | bool isCurrent = videoIndex == index; 267 | Color textColor = Theme.of(context).primaryColor; 268 | Color bgColor = Theme.of(context).primaryColorDark; 269 | Color borderColor = Theme.of(context).primaryColor; 270 | if (isCurrent) { 271 | textColor = Theme.of(context).primaryColorDark; 272 | bgColor = Theme.of(context).primaryColor; 273 | borderColor = Theme.of(context).primaryColor; 274 | } 275 | return GestureDetector( 276 | onTap: () async { 277 | await player.reset(); 278 | setState(() { 279 | videoIndex = index; 280 | }); 281 | setVideoUrl(videoList[index].url); 282 | }, 283 | child: Container( 284 | margin: EdgeInsets.only(left: index == 0 ? 0 : 10), 285 | padding: const EdgeInsets.symmetric(horizontal: 5), 286 | decoration: BoxDecoration( 287 | borderRadius: BorderRadius.circular(5), 288 | color: bgColor, 289 | border: Border.all( 290 | width: 1.5, 291 | color: borderColor, 292 | ), 293 | ), 294 | alignment: Alignment.center, 295 | child: Text( 296 | videoList[index].title, 297 | style: TextStyle( 298 | fontSize: 15, 299 | color: textColor, 300 | ), 301 | ), 302 | ), 303 | ); 304 | }, 305 | ), 306 | ), 307 | ], 308 | ), 309 | ); 310 | } 311 | 312 | @override 313 | void dispose() async { 314 | super.dispose(); 315 | try { 316 | await ScreenBrightness().resetScreenBrightness(); 317 | } catch (e) { 318 | print(e); 319 | throw 'Failed to reset brightness'; 320 | } 321 | player.release(); 322 | } 323 | } 324 | ``` 325 | 326 | ## 鸣谢以下项目 327 | * [fijkplayer](https://github.com/befovy/fijkplayer) 328 | * [ijkplayer](https://github.com/bilibili/ijkplayer) 329 | * [ffmpeg](https://github.com/FFmpeg/FFmpeg) 330 | 331 | ## iOS Warning 警告 332 | 333 | Warning: The fplayer video player plugin is not functional on iOS simulators. An iOS device must be used during development/testing. For more details, please refer to this [issue](https://github.com/flutter/flutter/issues/14647). 334 | 335 | 感谢您的关注!开源不易,需要开发者们的不断努力和付出。如果您觉得我的项目对您有所帮助,希望能够支持我继续改进和维护这个项目,您可以考虑打赏我一杯咖啡的钱。 336 | 您的支持将是我继续前进的动力,让我能够更加专注地投入到开源社区中,让我的项目变得更加完善和有用。如果您决定打赏我,可以通过以下方式: 337 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 |
微信支付宝
微信支付宝
355 | 再次感谢您的支持和慷慨,让我们一起为开源社区贡献一份力量! -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.flutterplayer.fplayer' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:8.3.1' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdk 34 26 | namespace 'com.flutterplayer.fplayer' 27 | 28 | 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | 34 | defaultConfig { 35 | minSdk 19 36 | } 37 | } 38 | 39 | dependencies { 40 | 41 | // implementation(name: 'fijkplayer-full-release', ext: 'aar') 42 | 43 | // fijkplayer-full include the java lib and native shared libs for armv5 armv7 arm64 x86 x86_64 44 | implementation 'io.github.flutterplayer:fplayer-core:1.0.4' 45 | implementation 'androidx.annotation:annotation:1.2.0' 46 | } 47 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'fplayer' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/com/flutterplayer/fplayer/FEngine.java: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019-2020] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | 24 | package com.flutterplayer.fplayer; 25 | 26 | import android.content.Context; 27 | import androidx.annotation.NonNull; 28 | import androidx.annotation.Nullable; 29 | import io.flutter.plugin.common.BinaryMessenger; 30 | import io.flutter.view.TextureRegistry; 31 | 32 | interface FEngine { 33 | 34 | @Nullable 35 | TextureRegistry.SurfaceTextureEntry createSurfaceEntry(); 36 | 37 | @Nullable 38 | BinaryMessenger messenger(); 39 | 40 | @Nullable 41 | Context context(); 42 | 43 | @Nullable 44 | String lookupKeyForAsset(@NonNull String asset, @Nullable String packageName); 45 | 46 | void onPlayingChange(int delta); 47 | 48 | void onPlayableChange(int delta); 49 | 50 | void setScreenOn(boolean on); 51 | 52 | void audioFocus(boolean request); 53 | } 54 | -------------------------------------------------------------------------------- /android/src/main/java/com/flutterplayer/fplayer/FVolume.java: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | package com.flutterplayer.fplayer; 24 | 25 | import android.view.KeyEvent; 26 | 27 | public class FVolume { 28 | 29 | public interface VolumeKeyListener { 30 | boolean onVolumeKeyDown(int keyCode, KeyEvent event); 31 | } 32 | 33 | public interface CanListenVolumeKey { 34 | void setVolumeKeyListener(VolumeKeyListener listener); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /android/src/main/java/com/flutterplayer/fplayer/FileMediaDataSource.java: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | package com.flutterplayer.fplayer; 24 | 25 | 26 | import android.util.Log; 27 | import tv.danmaku.ijk.media.player.misc.IMediaDataSource; 28 | 29 | import java.io.File; 30 | import java.io.IOException; 31 | import java.io.RandomAccessFile; 32 | 33 | class FileMediaDataSource implements IMediaDataSource { 34 | private RandomAccessFile mFile; 35 | private long mFileSize; 36 | 37 | public FileMediaDataSource(File file) { 38 | try { 39 | mFile = new RandomAccessFile(file, "r"); 40 | mFileSize = mFile.length(); 41 | } catch (IOException e) { 42 | mFile = null; 43 | mFileSize = -1; 44 | Log.e("DataSource", "failed to open RandomAccess" + e.getMessage()); 45 | } 46 | } 47 | 48 | @Override 49 | public int readAt(long position, byte[] buffer, int offset, int size) { 50 | if (size == 0) 51 | return 0; 52 | int length = -1; 53 | if (mFile != null) { 54 | try { 55 | if (mFile.getFilePointer() != position) 56 | mFile.seek(position); 57 | length = mFile.read(buffer, 0, size); 58 | } catch (IOException e) { 59 | Log.e("DataSource", "failed to read" + e.getMessage()); 60 | } 61 | } 62 | return length; 63 | } 64 | 65 | @Override 66 | public long getSize() { 67 | return mFileSize; 68 | } 69 | 70 | @Override 71 | public void close() { 72 | if (mFile != null) { 73 | try { 74 | mFile.close(); 75 | mFileSize = 0; 76 | mFile = null; 77 | } catch (IOException e) { 78 | Log.e("DataSource", "failed to close" + e.getMessage()); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /android/src/main/java/com/flutterplayer/fplayer/HostOption.java: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | package com.flutterplayer.fplayer; 24 | 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | final class HostOption { 29 | 30 | final static String REQUEST_AUDIOFOCUS = "request-audio-focus"; 31 | final static String RELEASE_AUDIOFOCUS = "release-audio-focus"; 32 | 33 | final static String REQUEST_SCREENON = "request-screen-on"; 34 | 35 | final static String ENABLE_SNAPSHOT = "enable-snapshot"; 36 | 37 | final private Map mIntOption; 38 | 39 | final private Map mStrOption; 40 | 41 | 42 | HostOption() { 43 | this.mIntOption = new HashMap<>(); 44 | this.mStrOption = new HashMap<>(); 45 | } 46 | 47 | 48 | void addIntOption(String key, Integer value) { 49 | mIntOption.put(key, value); 50 | } 51 | 52 | void addStrOption(String key, String value) { 53 | mStrOption.put(key, value); 54 | } 55 | 56 | @SuppressWarnings("SameParameterValue") 57 | int getIntOption(String key, int defalt) { 58 | int value = defalt; 59 | if (mIntOption.containsKey(key)) { 60 | Integer v = mIntOption.get(key); 61 | if (v != null) 62 | value = v; 63 | } 64 | return value; 65 | } 66 | 67 | public String getStrOption(String key, String defalt) { 68 | String value = defalt; 69 | if (mStrOption.containsKey(key)) { 70 | String v = mStrOption.get(key); 71 | if (v != null) 72 | value = v; 73 | } 74 | return value; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /android/src/main/java/com/flutterplayer/fplayer/QueuingEventSink.java: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | package com.flutterplayer.fplayer; 24 | 25 | import io.flutter.plugin.common.EventChannel; 26 | 27 | import java.util.ArrayList; 28 | 29 | /** 30 | * And implementation of {@link EventChannel.EventSink} which can wrap an underlying sink. 31 | * 32 | *

It delivers messages immediately when downstream is available, but it queues messages before 33 | * the delegate event sink is set with setDelegate. 34 | * 35 | *

This class is not thread-safe. All calls must be done on the same thread or synchronized 36 | * externally. 37 | */ 38 | final class QueuingEventSink implements EventChannel.EventSink { 39 | 40 | private EventChannel.EventSink delegate; 41 | private final ArrayList eventQueue = new ArrayList<>(); 42 | private boolean done = false; 43 | 44 | public void setDelegate(EventChannel.EventSink delegate) { 45 | this.delegate = delegate; 46 | maybeFlush(); 47 | } 48 | 49 | @Override 50 | public void endOfStream() { 51 | enqueue(new EndOfStreamEvent()); 52 | maybeFlush(); 53 | done = true; 54 | } 55 | 56 | @Override 57 | public void error(String code, String message, Object details) { 58 | enqueue(new ErrorEvent(code, message, details)); 59 | maybeFlush(); 60 | } 61 | 62 | @Override 63 | public void success(Object event) { 64 | enqueue(event); 65 | maybeFlush(); 66 | } 67 | 68 | private void enqueue(Object event) { 69 | if (done) { 70 | return; 71 | } 72 | eventQueue.add(event); 73 | } 74 | 75 | private void maybeFlush() { 76 | if (delegate == null) { 77 | return; 78 | } 79 | for (Object event : eventQueue) { 80 | if (event instanceof EndOfStreamEvent) { 81 | delegate.endOfStream(); 82 | } else if (event instanceof ErrorEvent) { 83 | ErrorEvent errorEvent = (ErrorEvent) event; 84 | delegate.error(errorEvent.code, errorEvent.message, errorEvent.details); 85 | } else { 86 | delegate.success(event); 87 | } 88 | } 89 | eventQueue.clear(); 90 | } 91 | 92 | private static class EndOfStreamEvent { 93 | } 94 | 95 | private static class ErrorEvent { 96 | final String code; 97 | final String message; 98 | final Object details; 99 | 100 | ErrorEvent(String code, String message, Object details) { 101 | this.code = code; 102 | this.message = message; 103 | this.details = details; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /android/src/main/java/com/flutterplayer/fplayer/RawMediaDataSource.java: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | package com.flutterplayer.fplayer; 24 | 25 | import android.util.Log; 26 | import tv.danmaku.ijk.media.player.misc.IMediaDataSource; 27 | 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | 31 | class RawMediaDataSource implements IMediaDataSource { 32 | private InputStream mIs; 33 | private long mPosition = 0; 34 | 35 | public RawMediaDataSource(InputStream is) { 36 | mIs = is; 37 | } 38 | 39 | @Override 40 | public int readAt(long position, byte[] buffer, int offset, int size) { 41 | if (size <= 0) 42 | return size; 43 | int length = -1; 44 | try { 45 | if (mPosition != position) { 46 | mIs.reset(); 47 | mPosition = mIs.skip(position); 48 | } 49 | length = mIs.read(buffer, offset, size); 50 | mPosition += length; 51 | } catch (IOException e) { 52 | Log.e("DataSource", "failed to read" + e.getMessage()); 53 | } 54 | return length; 55 | } 56 | 57 | @Override 58 | public long getSize() { 59 | long size = -1; 60 | try { 61 | size = mIs.available(); 62 | } catch (IOException e) { 63 | Log.e("DataSource", "failed to get size" + e.getMessage()); 64 | } 65 | return size; 66 | } 67 | 68 | @Override 69 | public void close() { 70 | if (mIs != null) { 71 | try { 72 | mIs.close(); 73 | mIs = null; 74 | } catch (IOException e) { 75 | Log.e("DataSource", "failed to close" + e.getMessage()); 76 | } 77 | } 78 | } 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # fplayer_example 2 | 3 | Demonstrates how to use the fplayer 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://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterRoot = localProperties.getProperty('flutter.sdk') 16 | if (flutterRoot == null) { 17 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 18 | } 19 | 20 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 21 | if (flutterVersionCode == null) { 22 | flutterVersionCode = '1' 23 | } 24 | 25 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 26 | if (flutterVersionName == null) { 27 | flutterVersionName = '1.0' 28 | } 29 | 30 | android { 31 | compileSdkVersion flutter.compileSdkVersion 32 | ndkVersion "25.1.8937393" 33 | 34 | namespace 'com.flutterplayer.fplayer_example' 35 | 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_8 38 | targetCompatibility JavaVersion.VERSION_1_8 39 | } 40 | 41 | defaultConfig { 42 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 43 | applicationId "com.flutterplayer.fplayer_example" 44 | // You can update the following values to match your application needs. 45 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 46 | minSdkVersion flutter.minSdkVersion 47 | targetSdkVersion flutter.targetSdkVersion 48 | versionCode flutterVersionCode.toInteger() 49 | versionName flutterVersionName 50 | } 51 | 52 | buildTypes { 53 | release { 54 | // TODO: Add your own signing config for the release build. 55 | // Signing with the debug keys for now, so `flutter run --release` works. 56 | signingConfig signingConfigs.debug 57 | } 58 | } 59 | } 60 | 61 | flutter { 62 | source '../..' 63 | } -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 9 | 13 | 21 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/flutterplayer/fplayer_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.flutterplayer.fplayer_example; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.os.Bundle; 6 | import android.view.KeyEvent; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.core.app.ActivityCompat; 10 | import androidx.core.content.ContextCompat; 11 | 12 | import com.flutterplayer.fplayer.FVolume; 13 | 14 | import java.util.ArrayList; 15 | 16 | import io.flutter.embedding.android.FlutterActivity; 17 | 18 | public class MainActivity extends FlutterActivity implements FVolume.CanListenVolumeKey { 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | ArrayList noGranted = new ArrayList<>(); 24 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) 25 | != PackageManager.PERMISSION_GRANTED) { 26 | noGranted.add(Manifest.permission.INTERNET); 27 | } 28 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) 29 | != PackageManager.PERMISSION_GRANTED) { 30 | noGranted.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); 31 | } 32 | if (noGranted.size() > 0) { 33 | ActivityCompat.requestPermissions(this, noGranted.toArray(new String[0]), 1); 34 | } 35 | } 36 | 37 | private FVolume.VolumeKeyListener volumeKeyListener; 38 | 39 | @Override 40 | public boolean onKeyDown(int keyCode, KeyEvent event) { 41 | switch (keyCode) { 42 | case KeyEvent.KEYCODE_VOLUME_DOWN: 43 | case KeyEvent.KEYCODE_VOLUME_UP: 44 | case KeyEvent.KEYCODE_VOLUME_MUTE: 45 | if (volumeKeyListener != null) { 46 | return volumeKeyListener.onVolumeKeyDown(keyCode, event); 47 | } 48 | default: 49 | break; 50 | } 51 | return super.onKeyDown(keyCode, event); 52 | } 53 | 54 | 55 | @Override 56 | public void setVolumeKeyListener(FVolume.VolumeKeyListener listener) { 57 | volumeKeyListener = listener; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | //package example.android 2 | 3 | allprojects { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | } 9 | 10 | rootProject.buildDir = '../build' 11 | subprojects { 12 | project.buildDir = "${rootProject.buildDir}/${project.name}" 13 | } 14 | subprojects { 15 | project.evaluationDependsOn(':app') 16 | } 17 | 18 | tasks.register("clean", Delete) { 19 | delete rootProject.buildDir 20 | } 21 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true -------------------------------------------------------------------------------- /example/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-8.4-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.3.2" apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.23" apply false 23 | } 24 | 25 | include ':app' 26 | 27 | //def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 28 | //def properties = new Properties() 29 | // 30 | //assert localPropertiesFile.exists() 31 | //localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 32 | // 33 | //def flutterSdkPath = properties.getProperty("flutter.sdk") 34 | //assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 35 | //apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 36 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.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, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - battery_plus (1.0.0): 3 | - Flutter 4 | - Flutter (1.0.0) 5 | - fplayer (0.0.1): 6 | - Flutter 7 | - fplayer-core (= 1.0.4) 8 | - fplayer-core (1.0.4) 9 | - screen_brightness_ios (0.1.0): 10 | - Flutter 11 | - shared_preferences_foundation (0.0.1): 12 | - Flutter 13 | - FlutterMacOS 14 | 15 | DEPENDENCIES: 16 | - battery_plus (from `.symlinks/plugins/battery_plus/ios`) 17 | - Flutter (from `Flutter`) 18 | - fplayer (from `.symlinks/plugins/fplayer/ios`) 19 | - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) 20 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) 21 | 22 | SPEC REPOS: 23 | trunk: 24 | - fplayer-core 25 | 26 | EXTERNAL SOURCES: 27 | battery_plus: 28 | :path: ".symlinks/plugins/battery_plus/ios" 29 | Flutter: 30 | :path: Flutter 31 | fplayer: 32 | :path: ".symlinks/plugins/fplayer/ios" 33 | screen_brightness_ios: 34 | :path: ".symlinks/plugins/screen_brightness_ios/ios" 35 | shared_preferences_foundation: 36 | :path: ".symlinks/plugins/shared_preferences_foundation/ios" 37 | 38 | SPEC CHECKSUMS: 39 | battery_plus: 7851ab482c336dd101ae735dc9363f01e1223c7c 40 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 41 | fplayer: 8dd9f9bc871e8e9ef5a94840cd29a62d8f6d5e52 42 | fplayer-core: b7a0256f8e5e034644e92a3234bc557f1613b13f 43 | screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 44 | shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 45 | 46 | PODFILE CHECKSUM: 663715e941f9adb426e33bf9376914006f9ea95b 47 | 48 | COCOAPODS: 1.12.0 49 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/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/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Fplayer 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | fplayer_example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 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/app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SettingMenu extends StatelessWidget { 4 | const SettingMenu({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return IconButton( 9 | // action button 10 | icon: const Icon(Icons.settings), 11 | onPressed: () { 12 | debugPrint("Click Menu Setting"); 13 | }, 14 | ); 15 | } 16 | } 17 | 18 | class FAppBar extends StatelessWidget implements PreferredSizeWidget { 19 | const FAppBar({Key? key, required this.title, this.actions}) 20 | : super(key: key); 21 | 22 | final String title; 23 | final List? actions; 24 | 25 | const FAppBar.defaultSetting({super.key, required this.title}) 26 | : actions = null; 27 | // todo settings page 28 | //: actions=[SettingMenu()]; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return PreferredSize( 33 | preferredSize: preferredSize, 34 | child: AppBar( 35 | title: Text(title), 36 | actions: actions, 37 | ), 38 | ); 39 | } 40 | 41 | @override 42 | Size get preferredSize => const Size.fromHeight(45.0); 43 | } 44 | -------------------------------------------------------------------------------- /example/lib/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fplayer_example/recent_list.dart'; 3 | 4 | import 'app_bar.dart'; 5 | 6 | class HomeItem extends StatelessWidget { 7 | const HomeItem({ 8 | super.key, 9 | required this.onPressed, 10 | required this.text, 11 | }); 12 | 13 | final String text; 14 | final VoidCallback onPressed; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return ButtonTheme( 19 | height: 45, 20 | child: Container( 21 | padding: const EdgeInsets.all(0), 22 | child: TextButton( 23 | key: ValueKey(text), 24 | onPressed: onPressed, 25 | child: Container( 26 | width: double.infinity, 27 | padding: const EdgeInsets.only(left: 15, right: 15), 28 | child: Text(text), 29 | ), 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | class HomeScreen extends StatelessWidget { 37 | final RecentMediaList list = RecentMediaList(); 38 | 39 | HomeScreen({super.key}); 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Scaffold( 44 | appBar: const FAppBar.defaultSetting( 45 | title: "FPlayer", 46 | ), 47 | body: Builder( 48 | builder: (ctx) => Column( 49 | crossAxisAlignment: CrossAxisAlignment.stretch, 50 | children: [ 51 | HomeItem( 52 | onPressed: () { 53 | Navigator.push( 54 | context, 55 | MaterialPageRoute( 56 | builder: (context) => const SamplesScreen(), 57 | ), 58 | ); 59 | }, 60 | text: "Online Samples", 61 | ), 62 | /* 63 | Container( 64 | color: Theme.of(context).primaryColorLight, 65 | padding: EdgeInsets.only(left: 15, top: 3, bottom: 3, right: 15), 66 | child: Text( 67 | "Recent", 68 | style: TextStyle(fontSize: 15), 69 | ), 70 | ), 71 | Expanded( 72 | child: list, 73 | ), 74 | */ 75 | ], 76 | ), 77 | ), 78 | ); 79 | } 80 | } 81 | 82 | void displaySnackBar(BuildContext context) { 83 | ScaffoldMessenger.of(context).showSnackBar(const SnackBar( 84 | duration: Duration(seconds: 1), 85 | content: Text('Not implemented, pull request is welcome 👏👏🍺🍺'), 86 | )); 87 | } 88 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | // import 'package:flutter/rendering.dart'; 3 | 4 | import 'home_page.dart'; 5 | 6 | void main() { 7 | // debugPaintSizeEnabled = true; 8 | runApp(const MyApp()); 9 | } 10 | 11 | class MyApp extends StatefulWidget { 12 | const MyApp({super.key}); 13 | 14 | @override 15 | State createState() => _MyAppState(); 16 | } 17 | 18 | class _MyAppState extends State { 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return MaterialApp( 27 | debugShowCheckedModeBanner: false, 28 | theme: ThemeData( 29 | primaryColor: const Color(0xFF07B9B9), 30 | primaryColorDark: const Color(0xFFFFFFFF), 31 | primaryColorLight: const Color(0x33000000), 32 | textButtonTheme: TextButtonThemeData( 33 | style: ButtonStyle( 34 | tapTargetSize: MaterialTapTargetSize.shrinkWrap, 35 | overlayColor: MaterialStateProperty.all(Colors.transparent), 36 | ), 37 | ), 38 | ), 39 | home: HomeScreen(), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/lib/media_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'video_page.dart'; 4 | 5 | @immutable 6 | class MediaUrl { 7 | const MediaUrl({required this.url, this.title}); 8 | 9 | final String? title; 10 | final String url; 11 | 12 | MediaUrl.fromJson(Map json) 13 | : title = json['title'], 14 | url = json['url']; 15 | 16 | Map toJson() => { 17 | 'title': title, 18 | 'url': url, 19 | }; 20 | 21 | @override 22 | bool operator ==(Object other) => 23 | identical(this, other) || 24 | other is MediaUrl && 25 | runtimeType == other.runtimeType && 26 | title == other.title && 27 | url == other.url; 28 | 29 | @override 30 | int get hashCode => title.hashCode ^ url.hashCode; 31 | } 32 | 33 | class MediaItem extends StatelessWidget { 34 | const MediaItem({ 35 | super.key, 36 | required this.mediaUrl, 37 | }); 38 | 39 | final MediaUrl mediaUrl; 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | List ws = []; 44 | 45 | if (mediaUrl.title != null) { 46 | ws.add(Text( 47 | mediaUrl.title!, 48 | style: const TextStyle(fontSize: 15), 49 | )); 50 | } 51 | ws.add(Text( 52 | mediaUrl.url, 53 | style: const TextStyle(fontSize: 13), 54 | )); 55 | return ButtonTheme( 56 | // height: mediaUrl.title == null ? 50 : 60, 57 | 58 | child: Container( 59 | padding: const EdgeInsets.all(0), 60 | child: TextButton( 61 | key: ValueKey(mediaUrl.url), 62 | onPressed: () { 63 | Navigator.push( 64 | context, 65 | MaterialPageRoute( 66 | builder: (context) => VideoScreen(url: mediaUrl.url), 67 | ), 68 | ); 69 | }, 70 | child: Container( 71 | width: double.infinity, 72 | padding: const EdgeInsets.only( 73 | left: 15, 74 | right: 15, 75 | top: 10, 76 | bottom: 10, 77 | ), 78 | child: Column( 79 | crossAxisAlignment: CrossAxisAlignment.stretch, 80 | children: ws, 81 | ), 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /example/lib/recent_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; 3 | 4 | import 'app_bar.dart'; 5 | import 'media_item.dart'; 6 | 7 | const List samples = [ 8 | MediaUrl( 9 | title: "Aliyun", url: "http://player.alicdn.com/video/aliyunmedia.mp4"), 10 | MediaUrl(title: "http 404", url: "https://fplayer.dev/butterfly.flv"), 11 | MediaUrl(title: "assets file", url: "asset:///assets/butterfly.mp4"), 12 | MediaUrl(title: "assets file", url: "asset:///assets/birthday.mp4"), 13 | MediaUrl(title: "assets file 404", url: "asset:///assets/beebee.mp4"), 14 | MediaUrl( 15 | title: "Protocol not found", url: "noprotocol://assets/butterfly.mp4"), 16 | MediaUrl( 17 | title: "rtsp test", 18 | url: "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"), 19 | MediaUrl( 20 | title: "Sample Video 360 * 240", 21 | url: 22 | "https://sample-videos.com/video123/flv/240/big_buck_bunny_240p_10mb.flv"), 23 | MediaUrl( 24 | title: "bipbop basic master playlist", 25 | url: 26 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"), 27 | MediaUrl( 28 | title: "bipbop basic 400x300 @ 232 kbps", 29 | url: 30 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8"), 31 | MediaUrl( 32 | title: "bipbop basic 640x480 @ 650 kbps", 33 | url: 34 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8"), 35 | MediaUrl( 36 | title: "bipbop basic 640x480 @ 1 Mbps", 37 | url: 38 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear3/prog_index.m3u8"), 39 | MediaUrl( 40 | title: "bipbop basic 960x720 @ 2 Mbps", 41 | url: 42 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear4/prog_index.m3u8"), 43 | MediaUrl( 44 | title: "bipbop basic 22.050Hz stereo @ 40 kbps", 45 | url: 46 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8"), 47 | MediaUrl( 48 | title: "bipbop advanced master playlist", 49 | url: 50 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"), 51 | MediaUrl( 52 | title: "bipbop advanced 416x234 @ 265 kbps", 53 | url: 54 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/gear1/prog_index.m3u8"), 55 | MediaUrl( 56 | title: "bipbop advanced 640x360 @ 580 kbps", 57 | url: 58 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/gear2/prog_index.m3u8"), 59 | MediaUrl( 60 | title: "bipbop advanced 960x540 @ 910 kbps", 61 | url: 62 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/gear3/prog_index.m3u8"), 63 | MediaUrl( 64 | title: "bipbop advanced 1280x720 @ 1 Mbps", 65 | url: 66 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/gear4/prog_index.m3u8"), 67 | MediaUrl( 68 | title: "bipbop advanced 1920x1080 @ 2 Mbps", 69 | url: 70 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/gear5/prog_index.m3u8"), 71 | MediaUrl( 72 | title: "bipbop advanced 22.050Hz stereo @ 40 kbps", 73 | url: 74 | "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/gear0/prog_index.m3u8"), 75 | ]; 76 | 77 | class SamplesScreen extends StatelessWidget { 78 | const SamplesScreen({super.key}); 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | return Scaffold( 83 | appBar: const FAppBar.defaultSetting(title: "Online Samples"), 84 | body: ListView.builder( 85 | itemCount: samples.length, 86 | itemBuilder: (BuildContext context, int index) { 87 | MediaUrl mediaUrl = samples[index]; 88 | return MediaItem(mediaUrl: mediaUrl); 89 | }, 90 | ), 91 | ); 92 | } 93 | } 94 | 95 | class RecentMediaList extends StatefulWidget { 96 | const RecentMediaList({super.key}); 97 | 98 | @override 99 | _RecentMediaListState createState() => _RecentMediaListState(); 100 | } 101 | 102 | class _RecentMediaListState extends State { 103 | int recentCount = 0; 104 | int newestId = 0; 105 | late StreamingSharedPreferences prefs; 106 | final ScrollController _controller = ScrollController(); 107 | 108 | _RecentMediaListState() { 109 | asyncSetup(); 110 | } 111 | 112 | asyncSetup() async { 113 | prefs = await StreamingSharedPreferences.instance; 114 | 115 | Preference counter = prefs.getInt('recent_count', defaultValue: 0); 116 | counter.listen(onHistoryChanged); 117 | } 118 | 119 | onHistoryChanged(int v) { 120 | int count = prefs.getInt("recent_count", defaultValue: 0).getValue(); 121 | int newest = prefs.getInt("recent_newest", defaultValue: 0).getValue(); 122 | _controller.jumpTo(0); 123 | setState(() { 124 | recentCount = count; 125 | newestId = newest; 126 | }); 127 | } 128 | 129 | @override 130 | Widget build(BuildContext context) { 131 | return ListView.builder( 132 | controller: _controller, 133 | itemCount: recentCount > 20 ? 20 : recentCount, 134 | itemBuilder: (BuildContext context, int index) { 135 | index = ((newestId + 20) - index) % 20; 136 | final key = "recentid$index"; 137 | MediaUrl item = prefs 138 | .getCustomValue(key, 139 | defaultValue: const MediaUrl(url: ""), 140 | adapter: JsonAdapter( 141 | deserializer: (value) => 142 | MediaUrl.fromJson(value as Map), 143 | )) 144 | .getValue(); 145 | return MediaItem(mediaUrl: item); 146 | }); 147 | } 148 | } 149 | 150 | Future addToHistory(MediaUrl mediaUrl) async { 151 | StreamingSharedPreferences prefs = await StreamingSharedPreferences.instance; 152 | int newest = prefs.getInt("recent_newest", defaultValue: 0).getValue(); 153 | int count = prefs.getInt("recent_count", defaultValue: 0).getValue(); 154 | 155 | if (count > 0) { 156 | MediaUrl theNewest = prefs 157 | .getCustomValue( 158 | "recentid$newest", 159 | defaultValue: const MediaUrl(url: ""), 160 | adapter: JsonAdapter( 161 | deserializer: (value) => 162 | MediaUrl.fromJson(value as Map), 163 | ), 164 | ) 165 | .getValue(); 166 | 167 | if (theNewest.url != mediaUrl.url) { 168 | newest = (newest + 1) % 20; 169 | count += 1; 170 | } else { 171 | newest = -1; 172 | } 173 | } else { 174 | count += 1; 175 | } 176 | 177 | if (newest >= 0) { 178 | await prefs.setInt("recent_count", count); 179 | await prefs.setInt("recent_newest", newest); 180 | await prefs.setCustomValue( 181 | "recentid$newest", 182 | mediaUrl, 183 | adapter: JsonAdapter( 184 | deserializer: (value) => 185 | MediaUrl.fromJson(value as Map), 186 | ), 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /example/lib/video_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:fplayer/fplayer.dart'; 4 | import 'package:screen_brightness/screen_brightness.dart'; 5 | 6 | import 'app_bar.dart'; 7 | 8 | class VideoScreen extends StatefulWidget { 9 | final String url; 10 | 11 | const VideoScreen({super.key, required this.url}); 12 | 13 | @override 14 | VideoScreenState createState() => VideoScreenState(); 15 | } 16 | 17 | class VideoScreenState extends State { 18 | final FPlayer player = FPlayer(); 19 | 20 | // 视频列表 21 | List videoList = [ 22 | VideoItem( 23 | title: '第一集', 24 | subTitle: '视频1副标题', 25 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 26 | ), 27 | VideoItem( 28 | title: '第二集', 29 | subTitle: '视频2副标题', 30 | url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4', 31 | ), 32 | VideoItem( 33 | title: '第三集', 34 | subTitle: '视频3副标题', 35 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 36 | ), 37 | VideoItem( 38 | title: '第四集', 39 | subTitle: '视频4副标题', 40 | url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4', 41 | ), 42 | VideoItem( 43 | title: '第五集', 44 | subTitle: '视频5副标题', 45 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 46 | ), 47 | VideoItem( 48 | title: '第六集', 49 | subTitle: '视频6副标题', 50 | url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4', 51 | ), 52 | VideoItem( 53 | title: '第七集', 54 | subTitle: '视频7副标题', 55 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 56 | ) 57 | ]; 58 | 59 | // 倍速列表 60 | Map speedList = { 61 | "2.0": 2.0, 62 | "1.5": 1.5, 63 | "1.0": 1.0, 64 | "0.5": 0.5, 65 | }; 66 | 67 | // 清晰度列表 68 | Map resolutionList = { 69 | "480P": ResolutionItem( 70 | value: 480, 71 | url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4', 72 | ), 73 | "270P": ResolutionItem( 74 | value: 270, 75 | url: 'http://player.alicdn.com/video/aliyunmedia.mp4', 76 | ), 77 | }; 78 | 79 | // 视频索引,单个视频可不传 80 | int videoIndex = 0; 81 | 82 | // 模拟播放记录视频初始化完需要跳转的进度 83 | int seekTime = 100000; 84 | 85 | VideoScreenState(); 86 | 87 | @override 88 | void initState() { 89 | super.initState(); 90 | startPlay(); 91 | } 92 | 93 | void startPlay() async { 94 | // 视频播放相关配置 95 | await player.setOption(FOption.hostCategory, "enable-snapshot", 1); 96 | await player.setOption(FOption.hostCategory, "request-screen-on", 1); 97 | await player.setOption(FOption.hostCategory, "request-audio-focus", 1); 98 | await player.setOption(FOption.playerCategory, "reconnect", 20); 99 | await player.setOption(FOption.playerCategory, "framedrop", 20); 100 | await player.setOption(FOption.playerCategory, "enable-accurate-seek", 1); 101 | await player.setOption(FOption.playerCategory, "mediacodec", 1); 102 | await player.setOption(FOption.playerCategory, "packet-buffering", 0); 103 | await player.setOption(FOption.playerCategory, "soundtouch", 1); 104 | 105 | // 播放传入的视频 106 | setVideoUrl(widget.url); 107 | 108 | // 播放视频列表的第一个视频 109 | // setVideoUrl(videoList[videoIndex].url); 110 | } 111 | 112 | Future setVideoUrl(String url) async { 113 | try { 114 | await player.setDataSource(url, autoPlay: true, showCover: true); 115 | } catch (error) { 116 | print("播放-异常: $error"); 117 | return; 118 | } 119 | } 120 | 121 | @override 122 | Widget build(BuildContext context) { 123 | MediaQueryData mediaQueryData = MediaQuery.of(context); 124 | Size size = mediaQueryData.size; 125 | double videoHeight = size.width * 9 / 16; 126 | return Scaffold( 127 | appBar: const FAppBar.defaultSetting(title: "Video"), 128 | body: SingleChildScrollView( 129 | child: Column( 130 | children: [ 131 | FView( 132 | player: player, 133 | width: double.infinity, 134 | height: videoHeight, 135 | color: Colors.black, 136 | fsFit: FFit.contain, // 全屏模式下的填充 137 | fit: FFit.fill, // 正常模式下的填充 138 | panelBuilder: fPanelBuilder( 139 | // 单视频配置 140 | title: '视频标题', 141 | subTitle: '视频副标题', 142 | // 右下方截屏按钮 143 | isSnapShot: true, 144 | // 右上方按钮组开关 145 | isRightButton: true, 146 | // 右上方按钮组 147 | rightButtonList: [ 148 | InkWell( 149 | onTap: () {}, 150 | child: Container( 151 | padding: const EdgeInsets.all(10), 152 | decoration: BoxDecoration( 153 | color: Theme.of(context).primaryColorLight, 154 | borderRadius: const BorderRadius.vertical( 155 | top: Radius.circular(5), 156 | ), 157 | ), 158 | child: Icon( 159 | Icons.favorite, 160 | color: Theme.of(context).primaryColor, 161 | ), 162 | ), 163 | ), 164 | InkWell( 165 | onTap: () {}, 166 | child: Container( 167 | padding: const EdgeInsets.all(10), 168 | decoration: BoxDecoration( 169 | color: Theme.of(context).primaryColorLight, 170 | borderRadius: const BorderRadius.vertical( 171 | bottom: Radius.circular(5), 172 | ), 173 | ), 174 | child: Icon( 175 | Icons.thumb_up, 176 | color: Theme.of(context).primaryColor, 177 | ), 178 | ), 179 | ) 180 | ], 181 | // 字幕功能:待内核提供api 182 | // caption: true, 183 | // 视频列表开关 184 | isVideos: true, 185 | // 视频列表列表 186 | videoList: videoList, 187 | // 当前视频索引 188 | videoIndex: videoIndex, 189 | // 全屏模式下点击播放下一集视频按钮 190 | playNextVideoFun: () { 191 | setState(() { 192 | videoIndex += 1; 193 | }); 194 | }, 195 | settingFun: () { 196 | print('设置按钮点击事件'); 197 | }, 198 | // 自定义倍速列表 199 | speedList: speedList, 200 | // 清晰度开关 201 | isResolution: true, 202 | // 自定义清晰度列表 203 | resolutionList: resolutionList, 204 | // 视频播放错误点击刷新回调 205 | onError: () async { 206 | await player.reset(); 207 | setVideoUrl(videoList[videoIndex].url); 208 | }, 209 | // 视频播放完成回调 210 | onVideoEnd: () async { 211 | var index = videoIndex + 1; 212 | if (index < videoList.length) { 213 | await player.reset(); 214 | setState(() { 215 | videoIndex = index; 216 | }); 217 | setVideoUrl(videoList[index].url); 218 | } 219 | }, 220 | onVideoTimeChange: () { 221 | // 视频时间变动则触发一次,可以保存视频播放历史 222 | }, 223 | onVideoPrepared: () async { 224 | // 视频初始化完毕,如有历史记录时间段则可以触发快进 225 | try { 226 | if (seekTime >= 1) { 227 | /// seekTo必须在FState.prepared 228 | print('seekTo'); 229 | await player.seekTo(seekTime); 230 | // print("视频快进-$seekTime"); 231 | seekTime = 0; 232 | } 233 | } catch (error) { 234 | print("视频初始化完快进-异常: $error"); 235 | } 236 | }, 237 | ), 238 | ), 239 | // 自定义小屏列表 240 | Container( 241 | width: double.infinity, 242 | height: 30, 243 | margin: const EdgeInsets.all(20), 244 | child: ListView.builder( 245 | scrollDirection: Axis.horizontal, 246 | padding: EdgeInsets.zero, 247 | itemCount: videoList.length, 248 | itemBuilder: (context, index) { 249 | bool isCurrent = videoIndex == index; 250 | Color textColor = Theme.of(context).primaryColor; 251 | Color bgColor = Theme.of(context).primaryColorDark; 252 | Color borderColor = Theme.of(context).primaryColor; 253 | if (isCurrent) { 254 | textColor = Theme.of(context).primaryColorDark; 255 | bgColor = Theme.of(context).primaryColor; 256 | borderColor = Theme.of(context).primaryColor; 257 | } 258 | return GestureDetector( 259 | onTap: () async { 260 | await player.reset(); 261 | setState(() { 262 | videoIndex = index; 263 | }); 264 | setVideoUrl(videoList[index].url); 265 | }, 266 | child: Container( 267 | margin: EdgeInsets.only(left: index == 0 ? 0 : 10), 268 | padding: const EdgeInsets.symmetric(horizontal: 5), 269 | decoration: BoxDecoration( 270 | borderRadius: BorderRadius.circular(5), 271 | color: bgColor, 272 | border: Border.all( 273 | width: 1.5, 274 | color: borderColor, 275 | ), 276 | ), 277 | alignment: Alignment.center, 278 | child: Text( 279 | videoList[index].title, 280 | style: TextStyle( 281 | fontSize: 15, 282 | color: textColor, 283 | ), 284 | ), 285 | ), 286 | ); 287 | }, 288 | ), 289 | ), 290 | ], 291 | ), 292 | ), 293 | ); 294 | } 295 | 296 | @override 297 | void dispose() async { 298 | super.dispose(); 299 | try { 300 | await ScreenBrightness().resetScreenBrightness(); 301 | } catch (e) { 302 | print(e); 303 | throw 'Failed to reset brightness'; 304 | } 305 | player.release(); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import battery_plus 9 | import screen_brightness_macos 10 | import shared_preferences_foundation 11 | 12 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 13 | BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) 14 | ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) 15 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 16 | } 17 | -------------------------------------------------------------------------------- /example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig: -------------------------------------------------------------------------------- 1 | // This is a generated file; do not edit or check into version control. 2 | FLUTTER_ROOT=/Users/bileskou/fvm/versions/3.22.3 3 | FLUTTER_APPLICATION_PATH=/Users/bileskou/Documents/GitHub/fplayer/example 4 | COCOAPODS_PARALLEL_CODE_SIGN=true 5 | FLUTTER_BUILD_DIR=build 6 | FLUTTER_BUILD_NAME=1.0.0 7 | FLUTTER_BUILD_NUMBER=1 8 | DART_OBFUSCATION=false 9 | TRACK_WIDGET_CREATION=true 10 | TREE_SHAKE_ICONS=false 11 | PACKAGE_CONFIG=.dart_tool/package_config.json 12 | -------------------------------------------------------------------------------- /example/macos/Flutter/ephemeral/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/bileskou/fvm/versions/3.22.3" 4 | export "FLUTTER_APPLICATION_PATH=/Users/bileskou/Documents/GitHub/fplayer/example" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "FLUTTER_BUILD_NAME=1.0.0" 8 | export "FLUTTER_BUILD_NUMBER=1" 9 | export "DART_OBFUSCATION=false" 10 | export "TRACK_WIDGET_CREATION=true" 11 | export "TREE_SHAKE_ICONS=false" 12 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 13 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fplayer_example 2 | description: Demonstrates how to use the fplayer plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: '>=2.18.0 <3.0.0' 10 | 11 | # Dependencies specify other packages that your package needs in order to work. 12 | # To automatically upgrade your package dependencies to the latest versions 13 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 14 | # dependencies can be manually updated by changing the version numbers below to 15 | # the latest version available on pub.dev. To see which dependencies have newer 16 | # versions available, run `flutter pub outdated`. 17 | dependencies: 18 | flutter: 19 | sdk: flutter 20 | 21 | streaming_shared_preferences: ^2.0.0 22 | 23 | fplayer: 24 | # When depending on this package from a real application you should use: 25 | # fplayer: ^x.y.z 26 | # See https://dart.dev/tools/pub/dependencies#version-constraints 27 | # The example app is bundled with the plugin so we use a path dependency on 28 | # the parent directory to use the current plugin's version. 29 | path: ../ 30 | 31 | # The following adds the Cupertino Icons font to your application. 32 | # Use with the CupertinoIcons class for iOS style icons. 33 | cupertino_icons: ^1.0.2 34 | 35 | dev_dependencies: 36 | flutter_test: 37 | sdk: flutter 38 | 39 | # The "flutter_lints" package below contains a set of recommended lints to 40 | # encourage good coding practices. The lint set provided by the package is 41 | # activated in the `analysis_options.yaml` file located at the root of your 42 | # package. See that file for information about deactivating specific lint 43 | # rules and activating additional ones. 44 | flutter_lints: ^2.0.0 45 | 46 | # For information on the generic Dart part of this file, see the 47 | # following page: https://dart.dev/tools/pub/pubspec 48 | 49 | # The following section is specific to Flutter packages. 50 | flutter: 51 | 52 | # The following line ensures that the Material Icons font is 53 | # included with your application, so that you can use the icons in 54 | # the material Icons class. 55 | uses-material-design: true 56 | 57 | # To add assets to your application, add an assets section, like this: 58 | # assets: 59 | # - images/a_dot_burr.jpeg 60 | # - images/a_dot_ham.jpeg 61 | 62 | # An image asset can refer to one or more resolution-specific "variants", see 63 | # https://flutter.dev/assets-and-images/#resolution-aware 64 | 65 | # For details regarding adding assets from package dependencies, see 66 | # https://flutter.dev/assets-and-images/#from-packages 67 | 68 | # To add custom fonts to your application, add a fonts section here, 69 | # in this "flutter" section. Each entry in this list should have a 70 | # "family" key with the font family name, and a "fonts" key with a 71 | # list giving the asset and other descriptors for the font. For 72 | # example: 73 | # fonts: 74 | # - family: Schyler 75 | # fonts: 76 | # - asset: fonts/Schyler-Regular.ttf 77 | # - asset: fonts/Schyler-Italic.ttf 78 | # style: italic 79 | # - family: Trajan Pro 80 | # fonts: 81 | # - asset: fonts/TrajanPro.ttf 82 | # - asset: fonts/TrajanPro_Bold.ttf 83 | # weight: 700 84 | # 85 | # For details regarding fonts from package dependencies, 86 | # see https://flutter.dev/custom-fonts/#from-packages 87 | -------------------------------------------------------------------------------- /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 in the flutter_test package. 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:fplayer_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /images/wx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/images/wx.png -------------------------------------------------------------------------------- /images/zfb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/images/zfb.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterPlayer/fplayer/62168841e2b5b28f2dc69afd395f5c6b50370bb1/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FHostOption.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) [2019] [Befovy] 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | #define FIJK_HOST_OPTION_REQUEST_SCREENON @"request-screen-on" 28 | #define FIJK_HOST_OPTION_ENABLE_SNAPSHOT @"enable-snapshot" 29 | 30 | 31 | @interface FHostOption : NSObject 32 | 33 | - (void)setIntValue:(NSNumber *)value forKey:(NSString *)key; 34 | 35 | - (void)setStrValue:(NSString *)value forKey:(NSString *)key; 36 | 37 | - (NSNumber *)getIntValue:(NSString *)kay defalt:(NSNumber *)defalt; 38 | 39 | - (NSString *)getStrValue:(NSString *)key defalt:(NSString *)defalt; 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /ios/Classes/FHostOption.m: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) [2019] [Befovy] 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import "FHostOption.h" 24 | 25 | @implementation FHostOption { 26 | NSMutableDictionary *_intOption; 27 | 28 | NSMutableDictionary *_strOption; 29 | } 30 | 31 | - (instancetype)init { 32 | self = [super init]; 33 | if (self) { 34 | _intOption = [[NSMutableDictionary alloc] init]; 35 | _strOption = [[NSMutableDictionary alloc] init]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)setIntValue:(NSNumber *)value forKey:(NSString *)key { 41 | _intOption[key] = value; 42 | } 43 | 44 | - (void)setStrValue:(NSString *)value forKey:(NSString *)key { 45 | _strOption[key] = value; 46 | } 47 | 48 | - (NSNumber *)getIntValue:(NSString *)key defalt:(NSNumber *)defalt { 49 | NSNumber *value = defalt; 50 | if ([_intOption objectForKey:key] != nil) { 51 | value = [_intOption objectForKey:key]; 52 | } 53 | return value; 54 | } 55 | 56 | - (NSString *)getStrValue:(NSString *)key defalt:(NSString *)defalt { 57 | NSString *value = defalt; 58 | if ([_strOption objectForKey:key] != nil) { 59 | value = [_strOption objectForKey:key]; 60 | } 61 | return value; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /ios/Classes/FPlayer.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) [2019] [Befovy] 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | #import 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | @interface FPlayer : NSObject 32 | 33 | @property(atomic, readonly) NSNumber *playerId; 34 | 35 | - (instancetype)initWithRegistrar:(id)registrar; 36 | 37 | - (instancetype)initJustTexture; 38 | 39 | - (void)shutdown; 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /ios/Classes/FQueuingEventSink.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) [2019] [Befovy] 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | @interface FQueuingEventSink : NSObject 29 | 30 | - (void)setDelegate:(FlutterEventSink _Nullable)sink; 31 | 32 | - (void)endOfStream; 33 | 34 | - (void)error:(NSString *)code 35 | message:(NSString *_Nullable)message 36 | details:(id _Nullable)details; 37 | 38 | - (void)success:(NSObject *)event; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /ios/Classes/FQueuingEventSink.m: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) [2019] [Befovy] 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import "FQueuingEventSink.h" 24 | 25 | @implementation FQueuingEventSink { 26 | NSMutableArray *_eventQueue; 27 | BOOL _done; 28 | FlutterEventSink _delegate; 29 | } 30 | 31 | - (instancetype)init { 32 | self = [super init]; 33 | if (self) { 34 | _delegate = nil; 35 | _done = false; 36 | _eventQueue = [[NSMutableArray alloc] init]; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)maybeFlush { 42 | if (_delegate == nil) 43 | return; 44 | 45 | for (NSObject *event in _eventQueue) { 46 | _delegate(event); 47 | } 48 | [_eventQueue removeAllObjects]; 49 | } 50 | 51 | - (void)enqueue:(const NSObject *)event { 52 | if (_done) 53 | return; 54 | [_eventQueue addObject:event]; 55 | } 56 | 57 | - (void)setDelegate:(FlutterEventSink)sink { 58 | _delegate = sink; 59 | [self maybeFlush]; 60 | } 61 | 62 | - (void)endOfStream { 63 | [self enqueue:FlutterEndOfEventStream]; 64 | [self maybeFlush]; 65 | _done = TRUE; 66 | } 67 | 68 | - (void)error:(NSString *)code 69 | message:(NSString *_Nullable)message 70 | details:(id _Nullable)details { 71 | [self enqueue:[FlutterError errorWithCode:code 72 | message:message 73 | details:details]]; 74 | [self maybeFlush]; 75 | } 76 | 77 | - (void)success:(NSObject *)event { 78 | [self enqueue:event]; 79 | [self maybeFlush]; 80 | } 81 | 82 | - (void)dealloc { 83 | if (_eventQueue) { 84 | [_eventQueue removeAllObjects]; 85 | _eventQueue = nil; 86 | } 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /ios/Classes/FplayerPlugin.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) [2019] [Befovy] 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import 24 | 25 | @interface FplayerPlugin : NSObject 26 | 27 | @property int playingCnt; 28 | @property int playableCnt; 29 | 30 | + (FplayerPlugin *)singleInstance; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ios/Classes/FplayerPlugin.m: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) [2019] [Befovy] 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #import "FplayerPlugin.h" 24 | #import "FPlayer.h" 25 | #import "FQueuingEventSink.h" 26 | 27 | #import 28 | #import 29 | #import 30 | #import 31 | 32 | typedef NS_ENUM(int, FijkVoUIMode) { 33 | hideUIWhenPlayable = 0, 34 | hideUIWhenPlaying, 35 | neverShowUI, 36 | alwaysShowUI, 37 | }; 38 | 39 | @implementation FplayerPlugin { 40 | NSObject *_registrar; 41 | NSMutableDictionary *_fPlayers; 42 | 43 | FQueuingEventSink *_eventSink; 44 | FlutterEventChannel *_eventChannel; 45 | 46 | MPVolumeView *_volumeView; 47 | UISlider *_volumeViewSlider; 48 | BOOL _volumeInWindow; 49 | 50 | int _volumeUIMode; 51 | BOOL _eventListening; 52 | float _volStep; 53 | BOOL _showOsUI; 54 | } 55 | 56 | static FplayerPlugin *_instance = nil; 57 | 58 | + (void)registerWithRegistrar:(NSObject *)registrar { 59 | FlutterMethodChannel *channel = 60 | [FlutterMethodChannel methodChannelWithName:@"befovy.com/fijk" 61 | binaryMessenger:[registrar messenger]]; 62 | FplayerPlugin *instance = [[FplayerPlugin alloc] initWithRegistrar:registrar]; 63 | _instance = instance; 64 | [registrar addMethodCallDelegate:instance channel:channel]; 65 | 66 | FPlayer *player = [[FPlayer alloc] initJustTexture]; 67 | int64_t vid = [[registrar textures] registerTexture:player]; 68 | [player shutdown]; 69 | [[registrar textures] unregisterTexture:vid]; 70 | } 71 | 72 | + (FplayerPlugin *)singleInstance { 73 | return _instance; 74 | } 75 | 76 | - (instancetype)initWithRegistrar: 77 | (NSObject *)registrar { 78 | self = [super init]; 79 | if (self) { 80 | _registrar = registrar; 81 | _fPlayers = [[NSMutableDictionary alloc] init]; 82 | _eventListening = FALSE; 83 | _volumeUIMode = alwaysShowUI; 84 | _volStep = 1.0 / 16.0; 85 | _showOsUI = YES; 86 | _eventSink = [[FQueuingEventSink alloc] init]; 87 | 88 | _eventChannel = 89 | [FlutterEventChannel eventChannelWithName:@"befovy.com/fijk/event" 90 | binaryMessenger:[registrar messenger]]; 91 | [_eventChannel setStreamHandler:self]; 92 | 93 | [[NSNotificationCenter defaultCenter] 94 | addObserver:self 95 | selector:@selector(volumeChange:) 96 | name:@"AVSystemController_SystemVolumeDidChangeNotification" 97 | object:nil]; 98 | } 99 | return self; 100 | } 101 | 102 | - (void)handleMethodCall:(FlutterMethodCall *)call 103 | result:(FlutterResult)result { 104 | 105 | NSDictionary *argsMap = call.arguments; 106 | if ([@"getPlatformVersion" isEqualToString:call.method]) { 107 | NSString *osVersion = [[UIDevice currentDevice] systemVersion]; 108 | result([@"iOS " stringByAppendingString:osVersion]); 109 | } else if ([@"init" isEqualToString:call.method]) { 110 | NSLog(@"FLUTTER: %s %@", "call init:", argsMap); 111 | result(NULL); 112 | } else if ([@"createPlayer" isEqualToString:call.method]) { 113 | FPlayer *fplayer = 114 | [[FPlayer alloc] initWithRegistrar:_registrar]; 115 | NSNumber *playerId = fplayer.playerId; 116 | _fPlayers[playerId] = fplayer; 117 | result(playerId); 118 | } else if ([@"releasePlayer" isEqualToString:call.method]) { 119 | NSNumber *pid = argsMap[@"pid"]; 120 | FPlayer *fPlayer = [_fPlayers objectForKey:pid]; 121 | [fPlayer shutdown]; 122 | if (fPlayer != nil) { 123 | [_fPlayers removeObjectForKey:pid]; 124 | } 125 | result(nil); 126 | } else if ([@"logLevel" isEqualToString:call.method]) { 127 | NSNumber *level = argsMap[@"level"]; 128 | int l = [level intValue] / 100; 129 | l = l < 0 ? 0 : l; 130 | l = l > 8 ? 8 : l; 131 | [IJKFFMoviePlayerController setLogLevel:l]; 132 | result(nil); 133 | } else if ([@"setOrientationPortrait" isEqualToString:call.method]) { 134 | UIInterfaceOrientationMask mask = [[UIApplication sharedApplication] 135 | supportedInterfaceOrientationsForWindow:nil]; 136 | UIDeviceOrientation deviceOrientation = 137 | [UIDevice currentDevice].orientation; 138 | BOOL changed = NO; 139 | if (deviceOrientation != UIDeviceOrientationPortrait && 140 | deviceOrientation != UIDeviceOrientationPortraitUpsideDown) { 141 | if (mask & UIInterfaceOrientationMaskPortraitUpsideDown) { 142 | [[UIDevice currentDevice] 143 | setValue:@(UIInterfaceOrientationPortraitUpsideDown) 144 | forKey:@"orientation"]; 145 | changed = YES; 146 | } else if (mask & UIInterfaceOrientationMaskPortrait) { 147 | [[UIDevice currentDevice] 148 | setValue:@(UIInterfaceOrientationPortrait) 149 | forKey:@"orientation"]; 150 | changed = YES; 151 | } 152 | } 153 | [UIViewController attemptRotationToDeviceOrientation]; 154 | result(@(changed)); 155 | } else if ([@"setOrientationLandscape" isEqualToString:call.method]) { 156 | UIInterfaceOrientationMask mask = [[UIApplication sharedApplication] 157 | supportedInterfaceOrientationsForWindow:nil]; 158 | UIDeviceOrientation deviceOrientation = 159 | [UIDevice currentDevice].orientation; 160 | BOOL changed = NO; 161 | if (deviceOrientation != UIDeviceOrientationLandscapeLeft && 162 | deviceOrientation != UIDeviceOrientationLandscapeRight) { 163 | if (mask & UIInterfaceOrientationMaskLandscapeRight) { 164 | [[UIDevice currentDevice] 165 | setValue:@(UIInterfaceOrientationLandscapeRight) 166 | forKey:@"orientation"]; 167 | changed = YES; 168 | } else if (mask & UIInterfaceOrientationMaskLandscapeLeft) { 169 | [[UIDevice currentDevice] 170 | setValue:@(UIInterfaceOrientationLandscapeLeft) 171 | forKey:@"orientation"]; 172 | changed = YES; 173 | } 174 | } 175 | [UIViewController attemptRotationToDeviceOrientation]; 176 | result(@(changed)); 177 | } else if ([@"setOrientationAuto" isEqualToString:call.method]) { 178 | UIInterfaceOrientationMask mask = [[UIApplication sharedApplication] 179 | supportedInterfaceOrientationsForWindow:nil]; 180 | 181 | if (mask & UIInterfaceOrientationMaskPortrait) 182 | [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait) 183 | forKey:@"orientation"]; 184 | [UIViewController attemptRotationToDeviceOrientation]; 185 | result(nil); 186 | } else if ([@"setScreenOn" isEqualToString:call.method]) { 187 | bool screenOn = false; 188 | NSNumber* on = argsMap[@"on"]; 189 | screenOn = on == nil ? false : on.boolValue; 190 | [self setScreenOn:screenOn]; 191 | result(nil); 192 | } else if ([@"isScreenKeptOn" isEqualToString:call.method]) { 193 | bool isIdleTimerDisabled = [[UIApplication sharedApplication] isIdleTimerDisabled]; 194 | result(@(isIdleTimerDisabled)); 195 | } else if ([@"brightness" isEqualToString:call.method]) { 196 | float brightness = [UIScreen mainScreen].brightness; 197 | result(@(brightness)); 198 | } else if ([@"setBrightness" isEqualToString:call.method]) { 199 | float brightness = [UIScreen mainScreen].brightness; 200 | NSNumber* arg = argsMap[@"brightness"]; 201 | brightness = arg == nil ? brightness : arg.floatValue; 202 | [[UIScreen mainScreen] setBrightness:brightness]; 203 | result(nil); 204 | } else if ([@"volumeUp" isEqualToString:call.method]) { 205 | NSNumber *number = argsMap[@"step"]; 206 | float step = number == nil ? _volStep : [number floatValue]; 207 | float vol = [self getSystemVolume]; 208 | vol += step; 209 | vol = [self setSystemVolume:vol]; 210 | result(@(vol)); 211 | } else if ([@"volumeDown" isEqualToString:call.method]) { 212 | NSNumber *number = argsMap[@"step"]; 213 | float step = number == nil ? _volStep : [number floatValue]; 214 | float vol = [self getSystemVolume]; 215 | vol -= step; 216 | vol = [self setSystemVolume:vol]; 217 | result(@(vol)); 218 | } else if ([@"volumeMute" isEqualToString:call.method]) { 219 | float vol = [self setSystemVolume:0.0f]; 220 | result(@(vol)); 221 | } else if ([@"volumeSet" isEqualToString:call.method]) { 222 | NSNumber *number = argsMap[@"vol"]; 223 | float v = number == nil ? [self getSystemVolume] : [number floatValue]; 224 | v = [self setSystemVolume:v]; 225 | result(@(v)); 226 | } else if ([@"systemVolume" isEqualToString:call.method]) { 227 | result(@([self getSystemVolume])); 228 | } else if ([@"volUiMode" isEqualToString:call.method]) { 229 | NSNumber *number = argsMap[@"mode"]; 230 | _volumeUIMode = [number intValue]; 231 | [self updateVolumeVisiablity]; 232 | result(nil); 233 | } else if ([@"onLoad" isEqualToString:call.method]) { 234 | [self initVolumeView]; 235 | _eventListening = YES; 236 | result(nil); 237 | } else if ([@"onUnload" isEqualToString:call.method]) { 238 | _eventListening = NO; 239 | result(nil); 240 | } else { 241 | result(FlutterMethodNotImplemented); 242 | } 243 | } 244 | 245 | - (void)initVolumeView { 246 | if (_volumeView == nil) { 247 | _volumeView = 248 | [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, -100, 10, 10)]; 249 | _volumeView.hidden = NO; 250 | } 251 | if (_volumeViewSlider == nil) { 252 | for (UIView *view in [_volumeView subviews]) { 253 | if ([view.class.description isEqualToString:@"MPVolumeSlider"]) { 254 | _volumeViewSlider = (UISlider *)view; 255 | _volumeViewSlider.value = [AVAudioSession sharedInstance].outputVolume; 256 | break; 257 | } 258 | } 259 | } 260 | if (!_volumeInWindow) { 261 | UIWindow *window = UIApplication.sharedApplication.keyWindow; 262 | if (window != nil) { 263 | [window addSubview:_volumeView]; 264 | _volumeInWindow = YES; 265 | } 266 | } 267 | [self updateVolumeVisiablity]; 268 | } 269 | 270 | - (void)onPlayingChange:(int)delta { 271 | _playingCnt += delta; 272 | [self updateVolumeVisiablity]; 273 | } 274 | 275 | - (void)onPlayableChange:(int)delta { 276 | _playableCnt += delta; 277 | [self updateVolumeVisiablity]; 278 | } 279 | 280 | - (void)setScreenOn:(BOOL)on { 281 | [UIApplication sharedApplication].idleTimerDisabled = on; 282 | } 283 | 284 | - (float)getSystemVolume { 285 | [self initVolumeView]; 286 | if (_volumeViewSlider == nil) { 287 | AVAudioSession *audioSession = [AVAudioSession sharedInstance]; 288 | CGFloat currentVol = audioSession.outputVolume; 289 | return currentVol; 290 | } else { 291 | return _volumeViewSlider.value; 292 | } 293 | } 294 | 295 | - (float)setSystemVolume:(float)vol { 296 | [self initVolumeView]; 297 | if (vol > 1.0) { 298 | vol = 1.0; 299 | } else if (vol < 0) { 300 | vol = 0.0; 301 | } 302 | [_volumeViewSlider setValue:vol animated:FALSE]; 303 | vol = _volumeViewSlider.value; 304 | [self sendVolumeChange:vol]; 305 | return vol; 306 | } 307 | 308 | - (void)updateVolumeVisiablity { 309 | if (_volumeView == nil || _volumeInWindow == FALSE) { 310 | _showOsUI = YES; 311 | return; 312 | } 313 | if (_volumeUIMode == alwaysShowUI) { 314 | _showOsUI = YES; 315 | _volumeView.hidden = YES; 316 | } else if (_volumeUIMode == neverShowUI) { 317 | _showOsUI = NO; 318 | _volumeView.hidden = NO; 319 | } else if (_volumeUIMode == hideUIWhenPlaying) { 320 | _volumeView.hidden = _playingCnt <= 0; 321 | _showOsUI = _volumeView.hidden; 322 | } else if (_volumeUIMode == hideUIWhenPlayable) { 323 | _volumeView.hidden = _playableCnt <= 0; 324 | _showOsUI = _volumeView.hidden; 325 | } 326 | } 327 | 328 | - (void)volumeChange:(NSNotification *)notifi { 329 | NSString *style = [notifi.userInfo 330 | objectForKey:@"AVSystemController_AudioCategoryNotificationParameter"]; 331 | CGFloat value = [[notifi.userInfo 332 | objectForKey:@"AVSystemController_AudioVolumeNotificationParameter"] 333 | doubleValue]; 334 | if ([style isEqualToString:@"Audio/Video"]) { 335 | [self sendVolumeChange:value]; 336 | } 337 | } 338 | 339 | - (void)sendVolumeChange:(float)value { 340 | if (_eventListening) { 341 | [_eventSink success:@{ 342 | @"event" : @"volume", 343 | @"sui" : @(_showOsUI), 344 | @"vol" : @(value) 345 | }]; 346 | } 347 | } 348 | 349 | - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { 350 | [_eventSink setDelegate:nil]; 351 | return nil; 352 | } 353 | 354 | - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments 355 | eventSink: 356 | (nonnull FlutterEventSink)events { 357 | [_eventSink setDelegate:events]; 358 | return nil; 359 | } 360 | 361 | @end 362 | -------------------------------------------------------------------------------- /ios/fplayer.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint fplayer.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'fplayer' 7 | s.version = '1.1.3' 8 | s.summary = 'A new Flutter plugin project.' 9 | s.description = <<-DESC 10 | A new Flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.public_header_files = 'Classes/**/*.h' 18 | s.dependency 'Flutter' 19 | s.platform = :ios, '11.0' 20 | 21 | s.static_framework = true 22 | 23 | # s.preserve_paths = 'Frameworks/*.framework' 24 | # s.vendored_framework = 'Framework/IJKMediaPlayer.framework' 25 | # s.xcconfig = { 'LD_RUNPATH_SEARCH_PATHS' => '"$(PODS_ROOT)/Frameworks/"' } 26 | 27 | s.libraries = "bz2", "z", "stdc++" 28 | 29 | s.dependency 'fplayer-core', '1.0.4' 30 | 31 | s.ios.deployment_target = '11.0' 32 | 33 | # Flutter.framework does not contain a i386 slice. 34 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 35 | end 36 | -------------------------------------------------------------------------------- /lib/core/f_log.dart: -------------------------------------------------------------------------------- 1 | part of fplayer; 2 | 3 | @immutable 4 | class FLogLevel { 5 | final int level; 6 | final String name; 7 | 8 | const FLogLevel._(int l, String n) 9 | : level = l, 10 | name = n; 11 | 12 | /// Priority constant for the [FLog.log] method; 13 | static const FLogLevel All = FLogLevel._(000, 'all'); 14 | 15 | /// Priority constant for the [FLog.log] method; 16 | static const FLogLevel Detail = FLogLevel._(100, 'det'); 17 | 18 | /// Priority constant for the [FLog.log] method; 19 | static const FLogLevel Verbose = FLogLevel._(200, 'veb'); 20 | 21 | /// Priority constant for the [FLog.log] method; use [FLog.d(msg)] 22 | static const FLogLevel Debug = FLogLevel._(300, 'dbg'); 23 | 24 | /// Priority constant for the [FLog.log] method; use [FLog.i(msg)] 25 | static const FLogLevel Info = FLogLevel._(400, 'inf'); 26 | 27 | /// Priority constant for the [FLog.log] method; use [FLog.w(msg)] 28 | static const FLogLevel Warn = FLogLevel._(500, 'war'); 29 | 30 | /// Priority constant for the [FLog.log] method; use [FLog.e(msg)] 31 | static const FLogLevel Error = FLogLevel._(600, 'err'); 32 | static const FLogLevel Fatal = FLogLevel._(700, 'fal'); 33 | static const FLogLevel Silent = FLogLevel._(800, 'sil'); 34 | 35 | @override 36 | String toString() { 37 | return 'FLogLevel{level:$level, name:$name}'; 38 | } 39 | } 40 | 41 | /// API for sending log output 42 | /// 43 | /// Generally, you should use the [FLog.d(msg)], [FLog.i(msg)], 44 | /// [FLog.w(msg)], and [FLog.e(msg)] methods to write logs. 45 | /// You can then view the logs in console/logcat. 46 | /// 47 | /// The order in terms of verbosity, from least to most is ERROR, WARN, INFO, DEBUG, VERBOSE. 48 | /// Verbose should always be skipped in an application except during development. 49 | /// Debug logs are compiled in but stripped at runtime. 50 | /// Error, warning and info logs are always kept. 51 | class FLog { 52 | static FLogLevel _level = FLogLevel.Info; 53 | 54 | /// Make constructor private 55 | const FLog._(); 56 | 57 | /// Set global whole log level 58 | /// 59 | /// Call this method on Android platform will load natvie shared libraries. 60 | /// If you care about app boot performance, 61 | /// you should call this method as late as possiable. Call this method before the first time you consturctor new [FPlayer] 62 | static setLevel(final FLogLevel level) { 63 | _level = level; 64 | log(FLogLevel.Silent, "set log level $level", "fplayer"); 65 | FPlugin._setLogLevel(level.level).then((_) { 66 | log(FLogLevel.Silent, "native log level ${level.level}", "fplayer"); 67 | }); 68 | } 69 | 70 | /// log [msg] with [level] and [tag] to console 71 | static log(FLogLevel level, String msg, String tag) { 72 | if (level.level >= _level.level) { 73 | DateTime now = DateTime.now(); 74 | print("[${level.name}] ${now.toLocal()} [$tag] $msg"); 75 | } 76 | } 77 | 78 | /// log [msg] with [FLogLevel.Verbose] level 79 | static v(String msg, {String tag = 'fplayer'}) { 80 | log(FLogLevel.Verbose, msg, tag); 81 | } 82 | 83 | /// log [msg] with [FLogLevel.Debug] level 84 | static d(String msg, {String tag = 'fplayer'}) { 85 | log(FLogLevel.Debug, msg, tag); 86 | } 87 | 88 | /// log [msg] with [FLogLevel.Info] level 89 | static i(String msg, {String tag = 'fplayer'}) { 90 | log(FLogLevel.Info, msg, tag); 91 | } 92 | 93 | /// log [msg] with [FLogLevel.Warn] level 94 | static w(String msg, {String tag = 'fplayer'}) { 95 | log(FLogLevel.Warn, msg, tag); 96 | } 97 | 98 | /// log [msg] with [FLogLevel.Error] level 99 | static e(String msg, {String tag = 'fplayer'}) { 100 | log(FLogLevel.Error, msg, tag); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/core/f_option.dart: -------------------------------------------------------------------------------- 1 | part of fplayer; 2 | 3 | class FOption { 4 | final Map> _options = HashMap(); 5 | 6 | final Map _hostOption = HashMap(); 7 | final Map _formatOption = HashMap(); 8 | final Map _codecOption = HashMap(); 9 | final Map _swsOption = HashMap(); 10 | final Map _playerOption = HashMap(); 11 | final Map _swrOption = HashMap(); 12 | 13 | static const int hostCategory = 0; 14 | static const int formatCategory = 1; 15 | static const int codecCategory = 2; 16 | static const int swsCategory = 3; 17 | static const int playerCategory = 4; 18 | static const int swrCategory = 5; 19 | 20 | /// return a deep copy of option datas 21 | Map> get data { 22 | final Map> options = HashMap(); 23 | options[0] = Map.from(_hostOption); 24 | options[1] = Map.from(_formatOption); 25 | options[2] = Map.from(_codecOption); 26 | options[3] = Map.from(_swsOption); 27 | options[4] = Map.from(_playerOption); 28 | options[5] = Map.from(_swrOption); 29 | FLog.i("FOption cloned"); 30 | return options; 31 | } 32 | 33 | FOption() { 34 | _options[0] = _hostOption; 35 | _options[1] = _formatOption; 36 | _options[2] = _codecOption; 37 | _options[3] = _swsOption; 38 | _options[4] = _playerOption; 39 | _options[5] = _swrOption; 40 | } 41 | 42 | /// set host option 43 | /// [value] must be int or String 44 | void setHostOption(String key, dynamic value) { 45 | if (value is String || value is int) { 46 | _hostOption[key] = value; 47 | FLog.v("FOption.setHostOption key:$key, value :$value"); 48 | } else { 49 | FLog.e("FOption.setHostOption with invalid value:$value"); 50 | throw ArgumentError.value(value, "value", "Must be int or String"); 51 | } 52 | } 53 | 54 | /// set player option 55 | /// [value] must be int or String 56 | void setPlayerOption(String key, dynamic value) { 57 | if (value is String || value is int) { 58 | _playerOption[key] = value; 59 | FLog.v("FOption.setPlayerOption key:$key, value :$value"); 60 | } else { 61 | FLog.e("FOption.setPlayerOption with invalid value:$value"); 62 | throw ArgumentError.value(value, "value", "Must be int or String"); 63 | } 64 | } 65 | 66 | /// set ffmpeg avformat option 67 | /// [value] must be int or String 68 | void setFormatOption(String key, dynamic value) { 69 | if (value is String || value is int) { 70 | _formatOption[key] = value; 71 | FLog.v("FOption.setFormatOption key:$key, value :$value"); 72 | } else { 73 | FLog.e("FOption.setFormatOption with invalid value:$value"); 74 | throw ArgumentError.value(value, "value", "Must be int or String"); 75 | } 76 | } 77 | 78 | /// set ffmpeg avcodec option 79 | /// [value] must be int or String 80 | void setCodecOption(String key, dynamic value) { 81 | if (value is String || value is int) { 82 | _codecOption[key] = value; 83 | FLog.v("FOption.setCodecOption key:$key, value :$value"); 84 | } else { 85 | FLog.e("FOption.setCodecOption with invalid value:$value"); 86 | throw ArgumentError.value(value, "value", "Must be int or String"); 87 | } 88 | } 89 | 90 | /// set ffmpeg swscale option 91 | /// [value] must be int or String 92 | void setSwsOption(String key, dynamic value) { 93 | if (value is String || value is int) { 94 | _swsOption[key] = value; 95 | FLog.v("FOption.setSwsOption key:$key, value :$value"); 96 | } else { 97 | FLog.e("FOption.setSwsOption with invalid value:$value"); 98 | throw ArgumentError.value(value, "value", "Must be int or String"); 99 | } 100 | } 101 | 102 | /// set ffmpeg swresample option 103 | /// [value] must be int or String 104 | void setSwrOption(String key, dynamic value) { 105 | if (value is String || value is int) { 106 | _swrOption[key] = value; 107 | FLog.v("FOption.setSwrOption key:$key, value :$value"); 108 | } else { 109 | FLog.e("FOption.setSwrOption with invalid value:$value"); 110 | throw ArgumentError.value(value, "value", "Must be int or String"); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/core/f_plugin.dart: -------------------------------------------------------------------------------- 1 | part of fplayer; 2 | 3 | class FPlugin { 4 | /// Make constructor private 5 | const FPlugin._(); 6 | 7 | static const MethodChannel _channel = MethodChannel('befovy.com/fijk'); 8 | 9 | static Future _createPlayer() async { 10 | int? pid = await _channel.invokeMethod("createPlayer"); 11 | if (pid != null) { 12 | return Future.value(pid); 13 | } 14 | FLog.e("failed to create native player"); 15 | return Future.value(-1); 16 | } 17 | 18 | static Future _releasePlayer(int pid) { 19 | return _channel 20 | .invokeMethod("releasePlayer", {'pid': pid}); 21 | } 22 | 23 | static bool isDesktop() { 24 | return Platform.isWindows || 25 | Platform.isMacOS || 26 | Platform.isLinux || 27 | Platform.isFuchsia; 28 | } 29 | 30 | /// Only works on Android and iOS 31 | static Future setOrientationPortrait() async { 32 | if (isDesktop()) return Future.value(true); 33 | // ios crash Supported orientations has no common orientation with the application 34 | bool? changed = await _channel.invokeMethod("setOrientationPortrait"); 35 | SystemChrome.setPreferredOrientations( 36 | [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); 37 | return Future.value(changed); 38 | } 39 | 40 | /// Only works on Android and iOS 41 | /// return false if current orientation is landscape 42 | /// return true if current orientation is portrait and after this API 43 | /// call finished, the orientation becomes landscape. 44 | /// return false if can't change orientation. 45 | static Future setOrientationLandscape() async { 46 | if (isDesktop()) return Future.value(false); 47 | bool? changed = await _channel.invokeMethod("setOrientationLandscape"); 48 | SystemChrome.setPreferredOrientations( 49 | [DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft]); 50 | return Future.value(changed); 51 | } 52 | 53 | static Future setOrientationAuto() { 54 | if (Platform.isAndroid) { 55 | SystemChrome.setPreferredOrientations(DeviceOrientation.values); 56 | } 57 | return _channel.invokeMethod("setOrientationAuto"); 58 | } 59 | 60 | /// Works on Android and iOS 61 | /// Keep screen on or not 62 | static Future keepScreenOn(bool on) { 63 | if (Platform.isAndroid || Platform.isIOS) { 64 | FLog.i("keepScreenOn :$on"); 65 | return _channel.invokeMethod("setScreenOn", {'on': on}); 66 | } 67 | return Future.value(); 68 | } 69 | 70 | /// Check if screen is kept on 71 | static Future isScreenKeptOn() async { 72 | if (Platform.isAndroid || Platform.isIOS) { 73 | var keptOn = await _channel.invokeMethod("isScreenKeptOn"); 74 | if (keptOn != null) { 75 | return Future.value(keptOn); 76 | } 77 | } 78 | return Future.value(false); 79 | } 80 | 81 | /// Set screen brightness. 82 | /// The range of [value] is [0.0, 1.0] 83 | static Future setScreenBrightness(double value) { 84 | if (value < 0.0 || value > 1.0) { 85 | return Future.error(ArgumentError.value( 86 | value, "brightness value must be not null and in range [0.0, 1.0]")); 87 | } else if (Platform.isAndroid || Platform.isIOS) { 88 | return _channel.invokeMethod( 89 | "setBrightness", {'brightness': value}); 90 | } 91 | return Future.value(); 92 | } 93 | 94 | /// Get the screen brightness. 95 | /// The range of returned value is [0.0, 1.0] 96 | static Future screenBrightness() async { 97 | if (Platform.isAndroid || Platform.isIOS) { 98 | var brightness = await _channel.invokeMethod("brightness"); 99 | if (brightness != null) return Future.value(brightness); 100 | } 101 | return Future.value(0); 102 | } 103 | 104 | /// Only works on Android 105 | /// request audio focus for media usage 106 | static Future requestAudioFocus() { 107 | if (Platform.isAndroid) { 108 | return _channel.invokeMethod("requestAudioFocus"); 109 | } 110 | return Future.value(); 111 | } 112 | 113 | /// Only works on Android 114 | /// release audio focus 115 | static Future releaseAudioFocus() { 116 | if (Platform.isAndroid) { 117 | return _channel.invokeMethod("releaseAudioFocus"); 118 | } 119 | return Future.value(); 120 | } 121 | 122 | static Future _setLogLevel(int level) { 123 | return _channel.invokeMethod("logLevel", {'level': level}); 124 | } 125 | 126 | static StreamSubscription? _eventSubs; 127 | 128 | static void _onLoad(String type) { 129 | if (_eventSubs == null) { 130 | FLog.i("_onLoad $type"); 131 | _eventSubs = const EventChannel("befovy.com/fijk/event") 132 | .receiveBroadcastStream() 133 | .listen(FPlugin._eventListener, onError: FPlugin._errorListener); 134 | } 135 | _channel.invokeMethod("onLoad"); 136 | } 137 | 138 | // ignore: unused_element 139 | static void _onUnload() { 140 | FLog.i("_onUnload"); 141 | _channel.invokeMethod("onUnload"); 142 | _eventSubs?.cancel(); 143 | } 144 | 145 | static void _eventListener(dynamic event) { 146 | final Map map = event; 147 | FLog.d("plugin listener: $map"); 148 | switch (map['event']) { 149 | case 'volume': 150 | bool sui = map['sui'] ?? false; 151 | double vol = map['vol'] ?? 0.0; 152 | FVolume._instance._onVolCallback(vol, sui); 153 | break; 154 | default: 155 | break; 156 | } 157 | } 158 | 159 | static void _errorListener(Object obj) { 160 | FLog.e("plugin errorListerner: $obj"); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/core/f_value.dart: -------------------------------------------------------------------------------- 1 | part of fplayer; 2 | 3 | enum FState { 4 | /// The state when a [FPlayer] is just created. 5 | /// Native ijkplayer memory and objects also be alloced or created when a [FPlayer] is created. 6 | /// 7 | /// * setDataSource() -> [initialized] 8 | /// * reset() -> self 9 | /// * release() -> [end] 10 | idle, 11 | 12 | /// After call [FPlayer.setDataSource] on state [idle], the state becomes [initialized]. 13 | /// 14 | /// * prepareAsync() -> [asyncPreparing] 15 | /// * reset() -> [idle] 16 | /// * release() -> [end] 17 | initialized, 18 | 19 | /// There're many tasks to do during prepare, such as detect stream info in datasource, find and open decoder, start decode and refresh thread. 20 | /// So ijkplayer export a async api prepareAsync. 21 | /// When [FPlayer.prepareAsync] is called on state [initialized], ths state changed to [asyncPreparing] immediately. 22 | /// After all task in prepare have finished, the state changed to [prepared]. 23 | /// Additionally, if any error occurs during prepare, the state will change to [error]. 24 | /// 25 | /// * ..... -> [prepared] 26 | /// * ..... -> [error] 27 | /// * ..... -> [stopped] 28 | /// * reset() -> [idle] 29 | /// * release() -> [end] 30 | asyncPreparing, 31 | 32 | /// After finish all the heavy tasks during [FPlayer.prepareAsync], 33 | /// the state becomes [prepared] from [asyncPreparing]. 34 | /// 35 | /// * seekTo() -> self 36 | /// * start() -> [started] 37 | /// * reset() -> [idle] 38 | /// * release() -> [end] 39 | prepared, 40 | 41 | /// * seekTo() -> self 42 | /// * start() -> self 43 | /// * pause() -> [paused] 44 | /// * stop() -> [stopped] 45 | /// * ...... -> [completed] 46 | /// * ...... -> [error] 47 | /// * reset() -> [idle] 48 | /// * release() -> [end] 49 | started, 50 | 51 | /// * seekTo() -> self 52 | /// * start() -> [started] 53 | /// * pause() -> self 54 | /// * stop() -> [stopped] 55 | /// * reset() -> [idle] 56 | /// * release() -> [end] 57 | paused, 58 | 59 | /// * seekTo() -> [paused] 60 | /// * start() -> [started] (from beginning) 61 | /// * pause() -> self 62 | /// * stop() -> [stopped] 63 | /// * reset() -> [idle] 64 | /// * release() -> [end] 65 | completed, 66 | 67 | /// * stop() -> self 68 | /// * prepareAsync() -> [asyncPreparing] 69 | /// * reset() -> [idle] 70 | /// * release() -> [end] 71 | stopped, 72 | 73 | /// * reset() -> [idle] 74 | /// * release() -> [end] 75 | error, 76 | 77 | /// * release() -> self 78 | end 79 | } 80 | 81 | /// FValue include the properties of a [FPlayer] which update not frequently. 82 | /// 83 | /// To get the updated value of other frequently updated properties, 84 | /// add listener of the value stream. 85 | /// See 86 | /// * [FPlayer.onBufferPosUpdate] 87 | /// * [FPlayer.onCurrentPosUpdate] 88 | /// * [FPlayer.onBufferStateUpdate] 89 | @immutable 90 | class FValue { 91 | /// Indicates if the player is ready 92 | final bool prepared; 93 | 94 | /// Indicates if the player is completed 95 | /// 96 | /// If the playback stream is realtime/live, [completed] never be true. 97 | final bool completed; 98 | 99 | /// Indicates if audio is started rendering 100 | /// 101 | /// When first audio frame rendered, this value changes to true from false. 102 | /// After call [FPlayer.reset], this value becomes to false. 103 | final bool audioRenderStart; 104 | 105 | /// Indicates if video is started rendering 106 | /// 107 | /// When first video frame rendered, this value changes to true from false. 108 | /// After call [FPlayer.reset], this value becomes to false. 109 | final bool videoRenderStart; 110 | 111 | /// Current state of the player 112 | final FState state; 113 | 114 | /// The pixel [size] of current video 115 | /// 116 | /// Is null when [prepared] is false. 117 | /// Is negative width and height if playback is audio only. 118 | final Size? size; 119 | 120 | /// The rotation degrees 121 | final int rotate; 122 | 123 | /// The current playback duration 124 | /// 125 | /// Is null when [prepared] is false. 126 | /// Is zero when playback is realtime stream. 127 | final Duration duration; 128 | 129 | /// whether if player should be displayed in full screen mode 130 | final bool fullScreen; 131 | 132 | final FException exception; 133 | 134 | /// A constructor requires all value. 135 | const FValue({ 136 | required this.prepared, 137 | required this.completed, 138 | required this.audioRenderStart, 139 | required this.videoRenderStart, 140 | required this.state, 141 | required this.size, 142 | required this.rotate, 143 | required this.duration, 144 | required this.fullScreen, 145 | required this.exception, 146 | }); 147 | 148 | /// Construct FValue with uninitialized value 149 | const FValue.uninitialized() 150 | : this( 151 | prepared: false, 152 | completed: false, 153 | videoRenderStart: false, 154 | audioRenderStart: false, 155 | state: FState.idle, 156 | size: null, 157 | rotate: 0, 158 | duration: const Duration(), 159 | fullScreen: false, 160 | exception: FException.noException, 161 | ); 162 | 163 | /// Return new FValue which combines the old value and the assigned new value 164 | FValue copyWith({ 165 | bool? prepared, 166 | bool? completed, 167 | bool? videoRenderStart, 168 | bool? audioRenderStart, 169 | FState? state, 170 | Size? size, 171 | int? rotate, 172 | Duration? duration, 173 | bool? fullScreen, 174 | FException? exception, 175 | }) { 176 | return FValue( 177 | prepared: prepared ?? this.prepared, 178 | completed: completed ?? this.completed, 179 | videoRenderStart: videoRenderStart ?? this.videoRenderStart, 180 | audioRenderStart: audioRenderStart ?? this.audioRenderStart, 181 | state: state ?? this.state, 182 | size: size ?? this.size, 183 | rotate: rotate ?? this.rotate, 184 | duration: duration ?? this.duration, 185 | fullScreen: fullScreen ?? this.fullScreen, 186 | exception: exception ?? this.exception, 187 | ); 188 | } 189 | 190 | @override 191 | bool operator ==(Object other) => 192 | identical(this, other) || 193 | other is FValue && 194 | runtimeType == other.runtimeType && 195 | hashCode == other.hashCode; 196 | 197 | @override 198 | int get hashCode => Object.hash( 199 | prepared, 200 | completed, 201 | state, 202 | size, 203 | rotate, 204 | videoRenderStart, 205 | audioRenderStart, 206 | duration, 207 | fullScreen, 208 | exception, 209 | ); 210 | 211 | @override 212 | String toString() { 213 | return "prepared:$prepared, completed:$completed, state:$state, " 214 | "size:$size, rotate:$rotate, duration:$duration, " 215 | "fullScreen:$fullScreen, exception:$exception"; 216 | } 217 | } 218 | 219 | @immutable 220 | class FException implements Exception { 221 | static const int ok = 0; 222 | static const FException noException = FException(ok); 223 | 224 | /// local file or asset not found, 225 | static const int local404 = -875574348; 226 | 227 | /// local io exception 228 | static const int localIOe = -1162824012; 229 | 230 | /// Internal bug 231 | static const int interBug = -558323010; 232 | 233 | /// Buffer too small 234 | static const int smallBuf = -1397118274; 235 | 236 | /// Decoder not found 237 | static const int noDecoder = -1128613112; 238 | 239 | /// Demuxer not found 240 | static const int noDemuxer = -1296385272; 241 | 242 | /// Encoder not found 243 | static const int noEncoder = -1129203192; 244 | 245 | /// End of file 246 | static const int fileEnd = -541478725; 247 | 248 | /// Immediate exit was requested 249 | static const int exitImm = -1414092869; 250 | 251 | /// Generic error in an external library 252 | static const int extErr = -542398533; 253 | 254 | /// Filter not found 255 | static const int noFilter = -1279870712; 256 | 257 | /// Invalid data found when processing input 258 | static const int badData = -1094995529; 259 | 260 | /// Muxer not found 261 | static const int noMuxer = -1481985528; 262 | 263 | /// Option not found 264 | static const int noOption = -1414549496; 265 | 266 | /// Not yet implemented in FFmpeg, patches welcome 267 | static const int noImplemented = -1163346256; 268 | 269 | /// Protocol not found 270 | static const int noProtocol = -1330794744; 271 | 272 | /// Stream not found 273 | static const int noStream = -1381258232; 274 | 275 | /// unknown error 276 | static const int unknown = -1313558101; 277 | 278 | /// Http 400 279 | static const int http400 = -808465656; 280 | 281 | /// Http 401 282 | static const int http401 = -825242872; 283 | 284 | /// Http 403 285 | static const int http403 = -858797304; 286 | 287 | /// Http 404 288 | static const int http404 = -875574520; 289 | 290 | /// Http 4xx 291 | static const int http4xx = -1482175736; 292 | 293 | /// Http 5xx 294 | static const int http5xx = -1482175992; 295 | 296 | /// exception code 297 | final int code; 298 | 299 | /// human readable exception message 300 | final String? message; 301 | 302 | // const FException(code, [this.message]) : code = code; 303 | 304 | const FException(this.code, [this.message]); 305 | 306 | static FException fromPlatformException(PlatformException e) { 307 | int? code = int.tryParse(e.code); 308 | return code != null 309 | ? FException(code, e.message) 310 | : FException(unknown, e.message); 311 | } 312 | 313 | @override 314 | bool operator ==(Object other) => 315 | identical(this, other) || 316 | other is FException && 317 | runtimeType == other.runtimeType && 318 | hashCode == other.hashCode; 319 | 320 | @override 321 | int get hashCode => Object.hash(code, message); 322 | 323 | @override 324 | String toString() { 325 | return "FException($code, $message)"; 326 | } 327 | } 328 | 329 | class FData { 330 | static const String _fViewPanelVolume = "__fview_panel_init_volume"; 331 | static const String _fViewPanelBrightness = "__fview_panel_init_brightness"; 332 | static const String _fViewPanelSeekto = "__fview_panel_sekto_position"; 333 | 334 | final Map _data = HashMap(); 335 | 336 | void setValue(String key, dynamic value) { 337 | _data[key] = value; 338 | } 339 | 340 | void clearValue(String key) { 341 | _data.remove(key); 342 | } 343 | 344 | bool contains(String key) { 345 | return _data.containsKey(key); 346 | } 347 | 348 | dynamic getValue(String key) { 349 | return _data[key]; 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /lib/core/f_vol.dart: -------------------------------------------------------------------------------- 1 | part of fplayer; 2 | 3 | @immutable 4 | class FVolumeEvent { 5 | final double vol; 6 | final bool sui; 7 | final int type; 8 | 9 | const FVolumeEvent({ 10 | required this.vol, 11 | required this.sui, 12 | required this.type, 13 | }); 14 | 15 | @override 16 | bool operator ==(Object other) => 17 | identical(this, other) || 18 | (other is FVolumeEvent && 19 | vol != 0.0 && 20 | vol != 1.0 && 21 | hashCode == other.hashCode); 22 | 23 | @override 24 | int get hashCode => Object.hash(vol, sui, type); 25 | } 26 | 27 | class _VolumeValueNotifier extends ValueNotifier { 28 | _VolumeValueNotifier(FVolumeEvent value) : super(value); 29 | } 30 | 31 | class FVolume { 32 | FVolume._(); 33 | 34 | static const int STREAM_VOICE_CALL = 0; 35 | static const int STREAM_SYSTEM = 1; 36 | static const int STREAM_RING = 2; 37 | static const int STREAM_MUSIC = 3; 38 | static const int STREAM_ALARM = 4; 39 | 40 | /// show system volume changed UI if no playable player. 41 | /// hide system volume changed UI if some players are in playable state. 42 | static const int hideUIWhenPlayable = 0; 43 | 44 | /// show system volume changed UI if no start state player. 45 | /// hide system volume changed UI if some players are in start state. 46 | static const int hideUIWhenPlaying = 1; 47 | 48 | /// never show system volume changed UI. 49 | static const int neverShowUI = 2; 50 | 51 | /// always show system volume changed UI 52 | static const int alwaysShowUI = 3; 53 | 54 | static final FVolume _instance = FVolume._(); 55 | 56 | static final _VolumeValueNotifier _notifier = 57 | _VolumeValueNotifier(const FVolumeEvent(vol: 0, sui: false, type: 0)); 58 | 59 | static const double _defaultStep = 1.0 / 16.0; 60 | 61 | /// Mute system volume. 62 | /// return system volume after mute 63 | static Future mute() async { 64 | var vol = await FPlugin._channel.invokeMethod("volumeMute"); 65 | if (vol != null) return Future.value(vol); 66 | return Future.value(0); 67 | } 68 | 69 | /// set system volume to [vol]. 70 | /// the range of [vol] is [0.0, 1,0]. 71 | /// return the system volume value after set. 72 | static Future setVol(double vol) async { 73 | if (vol < 0.0 || vol > 1.0) { 74 | return Future.error(ArgumentError.value( 75 | vol, "step must be not null and in range [0.0, 1.0]")); 76 | } else { 77 | var afterSet = await FPlugin._channel 78 | .invokeMethod("volumeSet", {'vol': vol}); 79 | if (afterSet != null) return Future.value(afterSet); 80 | return Future.value(0); 81 | } 82 | } 83 | 84 | /// get ths current system volume. 85 | /// the range of returned value is [0.0, 1.0]. 86 | static Future getVol() async { 87 | var vol = await FPlugin._channel.invokeMethod("systemVolume"); 88 | if (vol != null) return Future.value(vol); 89 | return Future.value(0); 90 | } 91 | 92 | /// increase system volume by step, step must be in range [0.0, 1.0]. 93 | /// return the system volume value after increase. 94 | /// the return volume value may be not equals to the current volume + step. 95 | static Future up({double step = _defaultStep}) async { 96 | if (step < 0.0 || step > 1.0) { 97 | return Future.error(ArgumentError.value( 98 | step, "step must be not null and in range [0.0, 1.0]")); 99 | } else { 100 | var vol = await FPlugin._channel 101 | .invokeMethod("volumeUp", {'step': step}); 102 | if (vol != null) return Future.value(vol); 103 | return Future.value(0); 104 | } 105 | } 106 | 107 | /// decrease system volume by step, step must be in range [0.0, 1.0]. 108 | /// return the system volume value after decrease. 109 | /// the return volume value may be not equals to the current volume - step. 110 | static Future down({double step = _defaultStep}) async { 111 | if (step < 0.0 || step > 1.0) { 112 | return Future.error(ArgumentError.value( 113 | step, "step must be not null and in range [0.0, 1.0]")); 114 | } else { 115 | var vol = await FPlugin._channel 116 | .invokeMethod("volumeDown", {'step': step}); 117 | if (vol != null) return Future.value(vol); 118 | return Future.value(0); 119 | } 120 | } 121 | 122 | /// update the ui mode when system volume changing. 123 | /// mode can be one of 124 | /// {[hideUIWhenPlayable], [hideUIWhenPlaying], [neverShowUI], [alwaysShowUI]} 125 | static Future setUIMode(int mode) { 126 | if (mode < hideUIWhenPlayable || hideUIWhenPlayable > alwaysShowUI) { 127 | return Future.error(ArgumentError.notNull("mode")); 128 | } else { 129 | return FPlugin._channel 130 | .invokeMethod("volUiMode", {'mode': mode}); 131 | } 132 | } 133 | 134 | void _onVolCallback(double vol, bool ui) { 135 | _notifier.value = FVolumeEvent(vol: vol, sui: ui, type: STREAM_MUSIC); 136 | } 137 | 138 | /// the [listener] wiil be nitified after system volume changed. 139 | /// the value after change can be obtained through [FVolume.value] 140 | static void addListener(VoidCallback listener) { 141 | FPlugin._onLoad("vol"); 142 | _notifier.addListener(listener); 143 | } 144 | 145 | /// remove the [listener] set using [addListener] 146 | static void removeListener(VoidCallback listener) { 147 | _notifier.removeListener(listener); 148 | } 149 | 150 | /// get the system volume event. 151 | /// a valid value is returned only if [addListener] is called and there's really volume changing 152 | static FVolumeEvent get value => _notifier.value; 153 | } 154 | 155 | /// Volume changed callback func. 156 | /// 157 | /// See [FVolumeEvent] 158 | /// [value] is the value of volume, and has been mapped into range [0.0, 1.0] 159 | /// true value of [ui] indicates that Android/iOS system volume changed UI is shown for this volume change event 160 | /// [streamType] shows track\stream type for this volume change, this value is always [FVolume.STREAM_MUSIC] in this version 161 | typedef FVolumeCallback = void Function(FVolumeEvent value); 162 | 163 | /// stateful widget that watching system volume, no ui widget 164 | /// when system volume changed, [watcher] will be invoked. 165 | class FVolumeWatcher extends StatefulWidget { 166 | /// volume changed callback 167 | final FVolumeCallback watcher; 168 | 169 | /// child widget, must be non-null 170 | final Widget child; 171 | 172 | /// whether show default volume changed toast, default value is false. 173 | /// 174 | /// The default toast ui insert an OverlayEntry to current context's overlay 175 | final bool showToast; 176 | 177 | const FVolumeWatcher({ 178 | super.key, 179 | required this.watcher, 180 | /*required*/ required this.child, 181 | this.showToast = false, 182 | }); 183 | 184 | @override 185 | _FVolumeWatcherState createState() => _FVolumeWatcherState(); 186 | } 187 | 188 | class _FVolumeWatcherState extends State { 189 | static OverlayEntry? _entry; 190 | static Timer? _timer; 191 | late StreamController _volController; 192 | 193 | @override 194 | void initState() { 195 | super.initState(); 196 | _volController = StreamController.broadcast(); 197 | FVolume.addListener(volChanged); 198 | FVolume.setUIMode(FVolume.hideUIWhenPlayable); 199 | } 200 | 201 | void volChanged() { 202 | FVolumeEvent value = FVolume.value; 203 | _volController.add(value.vol); 204 | widget.watcher(value); 205 | if (widget.showToast && !value.sui) { 206 | showVolToast(value.vol); 207 | } 208 | } 209 | 210 | /// reference https://www.kikt.top/posts/flutter/toast/oktoast/ 211 | void showVolToast(double vol) { 212 | bool active = _timer?.isActive ?? false; 213 | _timer?.cancel(); 214 | Widget widget = defaultFVolumeToast(vol, _volController.stream); 215 | if (active == false) { 216 | var entry = OverlayEntry(builder: (_) => widget); 217 | _entry = entry; 218 | var overlay = Overlay.of(context); 219 | if (overlay != null) overlay.insert(entry); 220 | } 221 | _timer = Timer(const Duration(milliseconds: 800), () { 222 | _entry?.remove(); 223 | }); 224 | } 225 | 226 | @override 227 | void dispose() { 228 | super.dispose(); 229 | FVolume.removeListener(volChanged); 230 | _volController.close(); 231 | } 232 | 233 | @override 234 | Widget build(BuildContext context) { 235 | return widget.child; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /lib/fplayer.dart: -------------------------------------------------------------------------------- 1 | library fplayer; 2 | 3 | import 'dart:async'; 4 | import 'dart:collection'; 5 | import 'dart:core'; 6 | import 'dart:io'; 7 | import 'dart:math'; 8 | 9 | import 'package:battery_plus/battery_plus.dart'; 10 | // import 'package:connectivity_plus/connectivity_plus.dart'; 11 | import 'package:flutter/foundation.dart'; 12 | import 'package:flutter/gestures.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter/services.dart'; 15 | 16 | part 'core/f_log.dart'; 17 | part 'core/f_option.dart'; 18 | part 'core/f_player.dart'; 19 | part 'core/f_plugin.dart'; 20 | part 'core/f_value.dart'; 21 | part 'core/f_view.dart'; 22 | part 'core/f_vol.dart'; 23 | part 'style/panel.dart'; 24 | part 'style/panel2.dart'; 25 | part 'style/slider.dart'; 26 | part 'style/volume.dart'; 27 | -------------------------------------------------------------------------------- /lib/fplayer_method_channel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'fplayer_platform_interface.dart'; 5 | 6 | /// An implementation of [FplayerPlatform] that uses method channels. 7 | class MethodChannelFplayer extends FplayerPlatform { 8 | /// The method channel used to interact with the native platform. 9 | @visibleForTesting 10 | final methodChannel = const MethodChannel('fplayer'); 11 | 12 | @override 13 | Future getPlatformVersion() async { 14 | final version = await methodChannel.invokeMethod('getPlatformVersion'); 15 | return version; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/fplayer_platform_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 2 | 3 | import 'fplayer_method_channel.dart'; 4 | 5 | abstract class FplayerPlatform extends PlatformInterface { 6 | /// Constructs a FplayerPlatform. 7 | FplayerPlatform() : super(token: _token); 8 | 9 | static final Object _token = Object(); 10 | 11 | static FplayerPlatform _instance = MethodChannelFplayer(); 12 | 13 | /// The default instance of [FplayerPlatform] to use. 14 | /// 15 | /// Defaults to [MethodChannelFplayer]. 16 | static FplayerPlatform get instance => _instance; 17 | 18 | /// Platform-specific implementations should set this with their own 19 | /// platform-specific class that extends [FplayerPlatform] when 20 | /// they register themselves. 21 | static set instance(FplayerPlatform instance) { 22 | PlatformInterface.verifyToken(instance, _token); 23 | _instance = instance; 24 | } 25 | 26 | Future getPlatformVersion() { 27 | throw UnimplementedError('platformVersion() has not been implemented.'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/style/panel.dart: -------------------------------------------------------------------------------- 1 | part of fplayer; 2 | 3 | /// Default builder generate default [FPanel] UI 4 | Widget defaultFPanelBuilder(FPlayer player, FData data, BuildContext context, 5 | Size viewSize, Rect texturePos) { 6 | return _DefaultFPanel( 7 | player: player, 8 | buildContext: context, 9 | viewSize: viewSize, 10 | texturePos: texturePos); 11 | } 12 | 13 | /// Default Panel Widget 14 | class _DefaultFPanel extends StatefulWidget { 15 | final FPlayer player; 16 | final BuildContext buildContext; 17 | final Size viewSize; 18 | final Rect texturePos; 19 | 20 | const _DefaultFPanel({ 21 | required this.player, 22 | required this.buildContext, 23 | required this.viewSize, 24 | required this.texturePos, 25 | }); 26 | 27 | @override 28 | _DefaultFPanelState createState() => _DefaultFPanelState(); 29 | } 30 | 31 | String _duration2String(Duration duration) { 32 | if (duration.inMilliseconds < 0) return "-: negtive"; 33 | 34 | String twoDigits(int n) { 35 | if (n >= 10) return "$n"; 36 | return "0$n"; 37 | } 38 | 39 | String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); 40 | String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); 41 | int inHours = duration.inHours; 42 | return inHours > 0 43 | ? "$inHours:$twoDigitMinutes:$twoDigitSeconds" 44 | : "$twoDigitMinutes:$twoDigitSeconds"; 45 | } 46 | 47 | class _DefaultFPanelState extends State<_DefaultFPanel> { 48 | FPlayer get player => widget.player; 49 | 50 | Duration _duration = const Duration(); 51 | Duration _currentPos = const Duration(); 52 | Duration _bufferPos = const Duration(); 53 | 54 | bool _playing = false; 55 | bool _prepared = false; 56 | String? _exception; 57 | 58 | // bool _buffering = false; 59 | 60 | double _seekPos = -1.0; 61 | 62 | StreamSubscription? _currentPosSubs; 63 | 64 | StreamSubscription? _bufferPosSubs; 65 | //StreamSubscription _bufferingSubs; 66 | 67 | Timer? _hideTimer; 68 | bool _hideStuff = true; 69 | 70 | double _volume = 1.0; 71 | 72 | final barHeight = 40.0; 73 | 74 | @override 75 | void initState() { 76 | super.initState(); 77 | 78 | _duration = player.value.duration; 79 | _currentPos = player.currentPos; 80 | _bufferPos = player.bufferPos; 81 | _prepared = player.state.index >= FState.prepared.index; 82 | _playing = player.state == FState.started; 83 | _exception = player.value.exception.message; 84 | // _buffering = player.isBuffering; 85 | 86 | player.addListener(_playerValueChanged); 87 | 88 | _currentPosSubs = player.onCurrentPosUpdate.listen((v) { 89 | setState(() { 90 | _currentPos = v; 91 | }); 92 | }); 93 | 94 | _bufferPosSubs = player.onBufferPosUpdate.listen((v) { 95 | setState(() { 96 | _bufferPos = v; 97 | }); 98 | }); 99 | } 100 | 101 | void _playerValueChanged() { 102 | FValue value = player.value; 103 | if (value.duration != _duration) { 104 | setState(() { 105 | _duration = value.duration; 106 | }); 107 | } 108 | 109 | bool playing = (value.state == FState.started); 110 | bool prepared = value.prepared; 111 | String? exception = value.exception.message; 112 | if (playing != _playing || 113 | prepared != _prepared || 114 | exception != _exception) { 115 | setState(() { 116 | _playing = playing; 117 | _prepared = prepared; 118 | _exception = exception; 119 | }); 120 | } 121 | } 122 | 123 | void _playOrPause() { 124 | if (_playing == true) { 125 | player.pause(); 126 | } else { 127 | player.start(); 128 | } 129 | } 130 | 131 | @override 132 | void dispose() { 133 | super.dispose(); 134 | _hideTimer?.cancel(); 135 | 136 | player.removeListener(_playerValueChanged); 137 | _currentPosSubs?.cancel(); 138 | _bufferPosSubs?.cancel(); 139 | } 140 | 141 | void _startHideTimer() { 142 | _hideTimer?.cancel(); 143 | _hideTimer = Timer(const Duration(seconds: 3), () { 144 | setState(() { 145 | _hideStuff = true; 146 | }); 147 | }); 148 | } 149 | 150 | void _cancelAndRestartTimer() { 151 | if (_hideStuff == true) { 152 | _startHideTimer(); 153 | } 154 | setState(() { 155 | _hideStuff = !_hideStuff; 156 | }); 157 | } 158 | 159 | Widget _buildVolumeButton() { 160 | IconData iconData; 161 | if (_volume <= 0) { 162 | iconData = Icons.volume_off; 163 | } else { 164 | iconData = Icons.volume_up; 165 | } 166 | return IconButton( 167 | icon: Icon(iconData), 168 | padding: const EdgeInsets.only(left: 10.0, right: 10.0), 169 | onPressed: () { 170 | setState(() { 171 | _volume = _volume > 0 ? 0.0 : 1.0; 172 | player.setVolume(_volume); 173 | }); 174 | }, 175 | ); 176 | } 177 | 178 | AnimatedOpacity _buildBottomBar(BuildContext context) { 179 | double duration = _duration.inMilliseconds.toDouble(); 180 | double currentValue = 181 | _seekPos > 0 ? _seekPos : _currentPos.inMilliseconds.toDouble(); 182 | currentValue = min(currentValue, duration); 183 | currentValue = max(currentValue, 0); 184 | return AnimatedOpacity( 185 | opacity: _hideStuff ? 0.0 : 0.8, 186 | duration: const Duration(milliseconds: 400), 187 | child: Container( 188 | height: barHeight, 189 | color: Theme.of(context).dialogBackgroundColor, 190 | child: Row( 191 | children: [ 192 | _buildVolumeButton(), 193 | Padding( 194 | padding: const EdgeInsets.only(right: 5.0, left: 5), 195 | child: Text( 196 | _duration2String(_currentPos), 197 | style: const TextStyle(fontSize: 14.0), 198 | ), 199 | ), 200 | 201 | _duration.inMilliseconds == 0 202 | ? const Expanded(child: Center()) 203 | : Expanded( 204 | child: Padding( 205 | padding: const EdgeInsets.only(right: 0, left: 0), 206 | child: FSlider( 207 | value: currentValue, 208 | cacheValue: _bufferPos.inMilliseconds.toDouble(), 209 | min: 0.0, 210 | max: duration, 211 | onChanged: (v) { 212 | _startHideTimer(); 213 | setState(() { 214 | _seekPos = v; 215 | }); 216 | }, 217 | onChangeEnd: (v) { 218 | setState(() { 219 | player.seekTo(v.toInt()); 220 | print("seek to $v"); 221 | _currentPos = 222 | Duration(milliseconds: _seekPos.toInt()); 223 | _seekPos = -1; 224 | }); 225 | }, 226 | ), 227 | ), 228 | ), 229 | 230 | // duration / position 231 | _duration.inMilliseconds == 0 232 | ? const Text("LIVE") 233 | : Padding( 234 | padding: const EdgeInsets.only(right: 5.0, left: 5), 235 | child: Text( 236 | _duration2String(_duration), 237 | style: const TextStyle( 238 | fontSize: 14.0, 239 | ), 240 | ), 241 | ), 242 | 243 | IconButton( 244 | icon: Icon(widget.player.value.fullScreen 245 | ? Icons.fullscreen_exit 246 | : Icons.fullscreen), 247 | padding: const EdgeInsets.only(left: 10.0, right: 10.0), 248 | // color: Colors.transparent, 249 | onPressed: () { 250 | widget.player.value.fullScreen 251 | ? player.exitFullScreen() 252 | : player.enterFullScreen(); 253 | }, 254 | ) 255 | // 256 | ], 257 | ), 258 | ), 259 | ); 260 | } 261 | 262 | @override 263 | Widget build(BuildContext context) { 264 | Rect rect = player.value.fullScreen 265 | ? Rect.fromLTWH(0, 0, widget.viewSize.width, widget.viewSize.height) 266 | : Rect.fromLTRB( 267 | max(0.0, widget.texturePos.left), 268 | max(0.0, widget.texturePos.top), 269 | min(widget.viewSize.width, widget.texturePos.right), 270 | min(widget.viewSize.height, widget.texturePos.bottom)); 271 | return Positioned.fromRect( 272 | rect: rect, 273 | child: GestureDetector( 274 | onTap: _cancelAndRestartTimer, 275 | child: AbsorbPointer( 276 | absorbing: _hideStuff, 277 | child: Column( 278 | children: [ 279 | Container(height: barHeight), 280 | Expanded( 281 | child: GestureDetector( 282 | onTap: () { 283 | _cancelAndRestartTimer(); 284 | }, 285 | child: Container( 286 | color: Colors.transparent, 287 | height: double.infinity, 288 | width: double.infinity, 289 | child: Center( 290 | child: _exception != null 291 | ? Text( 292 | _exception!, 293 | style: const TextStyle( 294 | color: Colors.white, 295 | fontSize: 25, 296 | ), 297 | ) 298 | : (_prepared || player.state == FState.initialized) 299 | ? AnimatedOpacity( 300 | opacity: _hideStuff ? 0.0 : 0.7, 301 | duration: const Duration(milliseconds: 400), 302 | child: IconButton( 303 | iconSize: barHeight * 2, 304 | icon: Icon( 305 | _playing 306 | ? Icons.pause 307 | : Icons.play_arrow, 308 | color: Colors.white), 309 | padding: const EdgeInsets.only( 310 | left: 10.0, right: 10.0), 311 | onPressed: _playOrPause, 312 | ), 313 | ) 314 | : SizedBox( 315 | width: barHeight * 1.5, 316 | height: barHeight * 1.5, 317 | child: const CircularProgressIndicator( 318 | valueColor: AlwaysStoppedAnimation( 319 | Colors.white, 320 | ), 321 | ), 322 | )), 323 | ), 324 | ), 325 | ), 326 | _buildBottomBar(context), 327 | ], 328 | ), 329 | ), 330 | ), 331 | ); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /lib/style/slider.dart: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | part of fplayer; 24 | 25 | /// FSlider is like Slider in Flutter SDK. 26 | /// FSlider support [cacheValue] which can be used 27 | /// to show the player's cached buffer. 28 | /// The [colors] is used to make colorful painter to draw the line and circle. 29 | class FSlider extends StatefulWidget { 30 | final double value; 31 | final double cacheValue; 32 | 33 | final ValueChanged onChanged; 34 | final ValueChanged? onChangeStart; 35 | final ValueChanged? onChangeEnd; 36 | 37 | final double min; 38 | final double max; 39 | 40 | final FSliderColors colors; 41 | 42 | const FSlider({ 43 | Key? key, 44 | required this.value, 45 | required this.onChanged, 46 | this.cacheValue = 0.0, 47 | this.onChangeStart, 48 | this.onChangeEnd, 49 | this.min = 0.0, 50 | this.max = 1.0, 51 | this.colors = const FSliderColors(), 52 | }) : assert(min <= max), 53 | assert(value >= min && value <= max), 54 | super(key: key); 55 | 56 | @override 57 | State createState() { 58 | return _FSliderState(); 59 | } 60 | } 61 | 62 | class _FSliderState extends State { 63 | bool dragging = false; 64 | 65 | double dragValue = 0.0; 66 | 67 | static const double margin = 2.0; 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | double v = widget.value / (widget.max - widget.min); 72 | double cv = widget.cacheValue / (widget.max - widget.min); 73 | 74 | return GestureDetector( 75 | child: Container( 76 | margin: EdgeInsets.only(left: margin, right: margin), 77 | height: double.infinity, 78 | width: double.infinity, 79 | color: Colors.transparent, 80 | child: CustomPaint( 81 | painter: _SliderPainter(v, cv, dragging, colors: widget.colors), 82 | ), 83 | ), 84 | onHorizontalDragStart: (DragStartDetails details) { 85 | setState(() { 86 | dragging = true; 87 | }); 88 | dragValue = widget.value; 89 | widget.onChangeStart?.call(dragValue); 90 | }, 91 | onHorizontalDragUpdate: (DragUpdateDetails details) { 92 | final box = context.findRenderObject() as RenderBox; 93 | final dx = details.localPosition.dx; 94 | dragValue = (dx - margin) / (box.size.width - 2 * margin); 95 | dragValue = max(0, min(1, dragValue)); 96 | dragValue = dragValue * (widget.max - widget.min) + widget.min; 97 | widget.onChanged(dragValue); 98 | }, 99 | onHorizontalDragEnd: (DragEndDetails details) { 100 | setState(() { 101 | dragging = false; 102 | }); 103 | widget.onChangeEnd?.call(dragValue); 104 | }, 105 | ); 106 | } 107 | } 108 | 109 | /// Colors for the FSlider 110 | class FSliderColors { 111 | const FSliderColors({ 112 | this.playedColor = const Color.fromRGBO(255, 0, 0, 0.6), 113 | this.bufferedColor = const Color.fromRGBO(50, 50, 100, 0.4), 114 | this.cursorColor = const Color.fromRGBO(255, 0, 0, 0.8), 115 | this.baselineColor = const Color.fromRGBO(200, 200, 200, 0.5), 116 | }); 117 | 118 | final Color playedColor; 119 | final Color bufferedColor; 120 | final Color cursorColor; 121 | final Color baselineColor; 122 | 123 | @override 124 | bool operator ==(Object other) => 125 | identical(this, other) || 126 | other is FSliderColors && 127 | runtimeType == other.runtimeType && 128 | hashCode == other.hashCode; 129 | 130 | @override 131 | int get hashCode => 132 | Object.hash(playedColor, bufferedColor, cursorColor, baselineColor); 133 | } 134 | 135 | class _SliderPainter extends CustomPainter { 136 | final double v; 137 | final double cv; 138 | 139 | final bool dragging; 140 | final Paint pt = Paint(); 141 | 142 | final FSliderColors colors; 143 | 144 | _SliderPainter(this.v, this.cv, this.dragging, 145 | {this.colors = const FSliderColors()}); 146 | 147 | @override 148 | void paint(Canvas canvas, Size size) { 149 | double lineHeight = min(size.height / 2, 1); 150 | pt.color = colors.baselineColor; 151 | 152 | double radius = min(size.height / 2, 4); 153 | // draw background 154 | canvas.drawRRect( 155 | RRect.fromRectAndRadius( 156 | Rect.fromPoints( 157 | Offset(0, size.height / 2 - lineHeight), 158 | Offset(size.width, size.height / 2 + lineHeight), 159 | ), 160 | Radius.circular(radius), 161 | ), 162 | pt, 163 | ); 164 | 165 | final double value = v * size.width; 166 | 167 | // draw played part 168 | pt.color = colors.playedColor; 169 | canvas.drawRRect( 170 | RRect.fromRectAndRadius( 171 | Rect.fromPoints( 172 | Offset(0, size.height / 2 - lineHeight), 173 | Offset(value, size.height / 2 + lineHeight), 174 | ), 175 | Radius.circular(radius), 176 | ), 177 | pt, 178 | ); 179 | 180 | // draw cached part 181 | final double cacheValue = cv * size.width; 182 | if (cacheValue > value && cacheValue > 0) { 183 | pt.color = colors.bufferedColor; 184 | canvas.drawRRect( 185 | RRect.fromRectAndRadius( 186 | Rect.fromPoints( 187 | Offset(value, size.height / 2 - lineHeight), 188 | Offset(cacheValue, size.height / 2 + lineHeight), 189 | ), 190 | Radius.circular(radius), 191 | ), 192 | pt, 193 | ); 194 | } 195 | 196 | // draw circle cursor 197 | pt.color = colors.cursorColor; 198 | pt.color = pt.color.withAlpha(max(0, pt.color.alpha - 50)); 199 | radius = min(size.height / 2, dragging ? 10 : 5); 200 | canvas.drawCircle(Offset(value, size.height / 2), radius, pt); 201 | pt.color = colors.cursorColor; 202 | radius = min(size.height / 2, dragging ? 6 : 3); 203 | canvas.drawCircle(Offset(value, size.height / 2), radius, pt); 204 | } 205 | 206 | @override 207 | bool operator ==(Object other) => 208 | identical(this, other) || 209 | other is _SliderPainter && hashCode == other.hashCode; 210 | 211 | @override 212 | int get hashCode => Object.hash(v, cv, dragging, colors); 213 | 214 | @override 215 | bool shouldRepaint(_SliderPainter oldDelegate) { 216 | return hashCode != oldDelegate.hashCode; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/style/volume.dart: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) [2019] [Befovy] 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | part of fplayer; 24 | 25 | /// Default builder generate default FVolToast UI 26 | Widget defaultFVolumeToast(double value, Stream emitter) { 27 | return _FSliderToast(value, 0, emitter); 28 | } 29 | 30 | Widget defaultFBrightnessToast(double value, Stream emitter) { 31 | return _FSliderToast(value, 1, emitter); 32 | } 33 | 34 | class _FSliderToast extends StatefulWidget { 35 | final Stream emitter; 36 | final double initial; 37 | 38 | // type 0 volume 39 | // type 1 screen brightness 40 | final int type; 41 | 42 | const _FSliderToast(this.initial, this.type, this.emitter); 43 | 44 | @override 45 | _FSliderToastState createState() => _FSliderToastState(); 46 | } 47 | 48 | class _FSliderToastState extends State<_FSliderToast> { 49 | double value = 0; 50 | StreamSubscription? subs; 51 | 52 | @override 53 | void initState() { 54 | super.initState(); 55 | value = widget.initial; 56 | subs = widget.emitter.listen((v) { 57 | setState(() { 58 | value = v; 59 | }); 60 | }); 61 | } 62 | 63 | @override 64 | void dispose() { 65 | super.dispose(); 66 | subs?.cancel(); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | IconData iconData; 72 | final type = widget.type; 73 | if (value <= 0) { 74 | iconData = type == 0 ? Icons.volume_mute : Icons.brightness_low; 75 | } else if (value < 0.5) { 76 | iconData = type == 0 ? Icons.volume_down : Icons.brightness_medium; 77 | } else { 78 | iconData = type == 0 ? Icons.volume_up : Icons.brightness_high; 79 | } 80 | 81 | final primaryColor = Theme.of(context).primaryColor; 82 | return Align( 83 | alignment: const Alignment(0, -0.4), 84 | child: Card( 85 | color: const Color(0x33000000), 86 | child: Container( 87 | padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), 88 | child: Row( 89 | mainAxisSize: MainAxisSize.min, 90 | children: [ 91 | Icon( 92 | iconData, 93 | color: Colors.white, 94 | ), 95 | Container( 96 | width: 100, 97 | height: 1.5, 98 | margin: const EdgeInsets.only(left: 8), 99 | child: LinearProgressIndicator( 100 | value: value, 101 | backgroundColor: Colors.black, 102 | valueColor: AlwaysStoppedAnimation(primaryColor), 103 | ), 104 | ), 105 | ], 106 | ), 107 | ), 108 | ), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fplayer 2 | description: A Video Player Flutter plugin based on fplayer-core, support most popular protocols and codecs. 3 | version: 1.1.3 4 | homepage: https://github.com/FlutterPlayer/fplayer.git 5 | 6 | environment: 7 | sdk: '>=2.18.0 <3.0.0' 8 | flutter: ">=2.5.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | plugin_platform_interface: ^2.1.8 14 | 15 | # 网络检测 16 | # connectivity_plus: ^3.0.2 17 | 18 | # 电量获取 19 | battery_plus: ^6.2.1 20 | 21 | # 设备亮度 22 | screen_brightness: ^2.1.2 23 | 24 | dev_dependencies: 25 | flutter_test: 26 | sdk: flutter 27 | flutter_lints: ^2.0.0 28 | 29 | # For information on the generic Dart part of this file, see the 30 | # following page: https://dart.dev/tools/pub/pubspec 31 | 32 | # The following section is specific to Flutter packages. 33 | flutter: 34 | # This section identifies this Flutter project as a plugin project. 35 | # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) 36 | # which should be registered in the plugin registry. This is required for 37 | # using method channels. 38 | # The Android 'package' specifies package in which the registered class is. 39 | # This is required for using method channels on Android. 40 | # The 'ffiPlugin' specifies that native code should be built and bundled. 41 | # This is required for using `dart:ffi`. 42 | # All these are used by the tooling to maintain consistency when 43 | # adding or updating assets for this project. 44 | plugin: 45 | platforms: 46 | android: 47 | package: com.flutterplayer.fplayer 48 | pluginClass: FplayerPlugin 49 | ios: 50 | pluginClass: FplayerPlugin 51 | 52 | # To add assets to your plugin package, add an assets section, like this: 53 | # assets: 54 | # - images/a_dot_burr.jpeg 55 | # - images/a_dot_ham.jpeg 56 | # 57 | # For details regarding assets in packages, see 58 | # https://flutter.dev/assets-and-images/#from-packages 59 | # 60 | # An image asset can refer to one or more resolution-specific "variants", see 61 | # https://flutter.dev/assets-and-images/#resolution-aware 62 | 63 | # To add custom fonts to your plugin package, add a fonts section here, 64 | # in this "flutter" section. Each entry in this list should have a 65 | # "family" key with the font family name, and a "fonts" key with a 66 | # list giving the asset and other descriptors for the font. For 67 | # example: 68 | # fonts: 69 | # - family: Schyler 70 | # fonts: 71 | # - asset: fonts/Schyler-Regular.ttf 72 | # - asset: fonts/Schyler-Italic.ttf 73 | # style: italic 74 | # - family: Trajan Pro 75 | # fonts: 76 | # - asset: fonts/TrajanPro.ttf 77 | # - asset: fonts/TrajanPro_Bold.ttf 78 | # weight: 700 79 | # 80 | # For details regarding fonts in packages, see 81 | # https://flutter.dev/custom-fonts/#from-packages 82 | -------------------------------------------------------------------------------- /test/fplayer_method_channel_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:fplayer/fplayer_method_channel.dart'; 4 | 5 | void main() { 6 | MethodChannelFplayer platform = MethodChannelFplayer(); 7 | const MethodChannel channel = MethodChannel('fplayer'); 8 | 9 | TestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | setUp(() { 12 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 13 | return '42'; 14 | }); 15 | }); 16 | 17 | tearDown(() { 18 | channel.setMockMethodCallHandler(null); 19 | }); 20 | 21 | test('getPlatformVersion', () async { 22 | expect(await platform.getPlatformVersion(), '42'); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /test/fplayer_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | --------------------------------------------------------------------------------