├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Readme_zh.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── settings.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ └── kotlin │ │ └── com │ │ └── swarmcloud │ │ └── flutter_p2p_engine │ │ ├── FlutterP2pEnginePlugin.kt │ │ └── MethodHandler.kt │ └── test │ └── kotlin │ └── com │ └── swarmcloud │ └── flutter_p2p_engine │ └── FlutterP2pEnginePluginTest.kt ├── darwin ├── Classes │ └── FlutterP2pEnginePlugin.swift └── flutter_p2p_engine.podspec ├── example ├── .gitignore ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── swarmcloud │ │ │ │ │ └── flutter_p2p_engine_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── integration_test │ └── plugin_integration_test.dart ├── 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.swift │ │ ├── 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 │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ ├── main.dart │ ├── style │ │ ├── color.dart │ │ ├── physics.dart │ │ ├── size.dart │ │ ├── text.dart │ │ └── theme.dart │ └── views │ │ └── confirm.dart ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Configs │ │ │ ├── AppInfo.xcconfig │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements │ └── RunnerTests │ │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── lib ├── flutter_p2p_engine.dart ├── flutter_p2p_engine_method_channel.dart └── flutter_p2p_engine_platform_interface.dart ├── pubspec.yaml └── test ├── flutter_p2p_engine_method_channel_test.dart └── flutter_p2p_engine_test.dart /.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 and should not be manually edited. 5 | 6 | version: 7 | revision: "12fccda598477eddd19f93040a1dba24f915b9be" 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: 12fccda598477eddd19f93040a1dba24f915b9be 17 | base_revision: 12fccda598477eddd19f93040a1dba24f915b9be 18 | - platform: android 19 | create_revision: 12fccda598477eddd19f93040a1dba24f915b9be 20 | base_revision: 12fccda598477eddd19f93040a1dba24f915b9be 21 | - platform: ios 22 | create_revision: 12fccda598477eddd19f93040a1dba24f915b9be 23 | base_revision: 12fccda598477eddd19f93040a1dba24f915b9be 24 | - platform: macos 25 | create_revision: 12fccda598477eddd19f93040a1dba24f915b9be 26 | base_revision: 12fccda598477eddd19f93040a1dba24f915b9be 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | * Update native sdk to v3. 4 | 5 | ## 1.1.0 6 | 7 | * Update iOS sdk to v3.1.2. 8 | * Update android sdk to v3.6.4. 9 | 10 | ## 1.1.1 11 | 12 | * Update android sdk to v3.6.5. 13 | 14 | ## 1.2.0 15 | * Update android sdk to v3.8.1. 16 | * Update iOS sdk to v3.2.0. 17 | * Change example player to video_player. 18 | 19 | ## 1.2.1 20 | * Update iOS sdk to v3.2.1. 21 | * Use sharedDarwinSource 22 | 23 | ## 1.2.2 24 | * Update android sdk to v3.8.2. 25 | * Optimize example. 26 | 27 | ## 1.2.3 28 | * Update android sdk to v3.8.4. 29 | * Update iOS sdk to v3.2.3. 30 | 31 | ## 1.2.4 32 | * Update android sdk to v3.8.7. 33 | 34 | * ## 1.2.5 35 | * Fixed bug that android compile failed 36 | 37 | * ## 1.2.6 38 | * Update android sdk to v3.8.8 39 | 40 | * ## 1.2.7 41 | * Update android sdk to v3.8.9 42 | * Update iOS sdk to v3.2.5. 43 | * Add new API ***setHttpHeaders*** 44 | 45 | * ## 1.2.8 46 | * Update android sdk to v3.9.7 47 | * Remove config field ***httpLoadTime*** 48 | 49 | ## 1.2.9 50 | * Update android sdk to v3.10.2. 51 | * Update iOS sdk to v3.3.2. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright <2023> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **English | [简体中文](Readme_zh.md)** 2 | 3 |

logo

4 |

Live/VOD P2P Engine for Flutter

5 |

6 | pub 7 |

8 | 9 | ## Features 10 | - Support iOS, macOS and Android platform 11 | - Support live and VOD streams over HLS protocol and MPEG-Dash(Android only) 12 | - Support encrypted HLS stream 13 | - Support cache to avoid repeating the download of TS file 14 | - Very easy to integrate with an existing flutter project 15 | - Support any flutter player 16 | - Highly configurable 17 | - Use IP database to group up peers by ISP and regions 18 | 19 | ## Usage 20 | See [documentation](https://www.swarmcloud.net/flutter) 21 | 22 | ## Request Token 23 | See [here](https://www.swarmcloud.net/guides/getting-started#for-mobile-app-integration) 24 | 25 | ## Dashboard 26 | Register your AppId at [dashboard](https://dash.swarmcloud.net), where you can view p2p-related information. 27 | 28 | ## Issue & Feature Request 29 | - If you found a bug, open an issue. 30 | - If you have a feature request, open an issue. 31 | 32 | ## Related Projects 33 | - [hlsjs-p2p-engine](https://github.com/cdnbye/hlsjs-p2p-engine) - Web Video Delivery Technology with No Plugins. 34 | - [ios-p2p-engine](https://github.com/cdnbye/ios-p2p-engine) - iOS Video P2P Engine for Any Player. 35 | - [android-p2p-engine](https://github.com/cdnbye/android-p2p-engine) - iOS Video P2P Engine for Any Player. 36 | - [react-native-swarmcloud](https://github.com/swarm-cloud/react-native-swarmcloud) - Official React Native bindings for SwarmCloud's P2P SDKs. 37 | 38 | ## FAQ 39 | We have collected some [frequently asked questions](https://www.swarmcloud.net/faq). Before reporting an issue, please search if the FAQ has the answer to your problem. 40 | 41 | ## Contact Us 42 | Email:service@cdnbye.com 43 |
44 | Telegram: @cdnbye 45 |
46 | Skype: live:86755838 47 | -------------------------------------------------------------------------------- /Readme_zh.md: -------------------------------------------------------------------------------- 1 | **[English](README.md) | 简体中文** 2 | 3 |

logo

4 |

Flutter视频/直播APP省流量&加速神器.

5 |

6 | pub 7 |

8 | 9 | 该插件的优势如下: 10 | - 支持iOS和安卓平台,可与[Web端插件](https://gitee.com/cdnbye/hlsjs-p2p-engine)P2P互通 11 | - 支持基于HLS流媒体协议(m3u8)的直播和点播场景 12 | - 支持加密HLS传输 13 | - 支持ts文件缓存从而避免重复下载 14 | - 几行代码即可在现有Flutter项目中快速集成 15 | - 支持任何Flutter播放器 16 | - 通过预加载形式实现P2P加速,完全不影响用户的播放体验 17 | - 高可配置化,用户可以根据特定的使用环境调整各个参数 18 | - 通过有效的调度策略来保证用户的播放体验以及p2p分享率 19 | - Tracker服务器根据访问IP的ISP、地域等进行智能调度 20 | 21 | ## 使用方法 22 | 参考 [文档](https://www.cdnbye.com/cn/flutter/usage.html) 23 | 24 | ## 获取Token 25 | 参考[如何获取token](https://www.cdnbye.com/cn/bindings.html#%E7%BB%91%E5%AE%9A-app-id-%E5%B9%B6%E8%8E%B7%E5%8F%96token) 26 | 27 | ## 控制台 28 | 登录 [dashboard](https://dash.swarmcloud.net) 并绑定 APPId, 即可查看P2P效果、在线人数等信息。 29 | 30 | ## 反馈及意见 31 | 当你遇到任何问题时,可以通过在 GitHub 的 repo 提交 issues 来反馈问题,请尽可能的描述清楚遇到的问题,如果有错误信息也一同附带,并且在 Labels 中指明类型为 bug 或者其他。 32 | 33 | ## 相关项目 34 | - [hlsjs-p2p-engine](https://gitee.com/cdnbye/hlsjs-p2p-engine) - 目前最好的Web端P2P流媒体方案。 35 | - [android-p2p-engine](https://gitee.com/cdnbye/android-p2p-engine) - 安卓端P2P流媒体加速引擎。 36 | - [ios-p2p-engine](https://gitee.com/cdnbye/ios-p2p-engine) - iOS端P2P流媒体加速引擎。 37 | 38 | ## FAQ 39 | 我们收集了一些[常见问题](https://www.cdnbye.com/faq.html)。在报告issue之前请先查看一下。 40 | 41 | ## 联系我们 42 | 邮箱:service@cdnbye.com 43 | -------------------------------------------------------------------------------- /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.swarmcloud.flutter_p2p_engine' 2 | version '1.0-SNAPSHOT' 3 | 4 | String localMavenPath = project.mkdir("build").absolutePath 5 | rootProject.allprojects { 6 | repositories { 7 | maven { url "file://$localMavenPath" } 8 | google() 9 | mavenCentral() 10 | maven{ 11 | url 'https://maven.swarmcloud.net/repository/maven-releases/' 12 | } 13 | } 14 | } 15 | 16 | apply plugin: 'com.android.library' 17 | apply plugin: 'kotlin-android' 18 | 19 | android { 20 | if (project.android.hasProperty("namespace")) { 21 | namespace 'com.swarmcloud.flutter_p2p_engine' 22 | } 23 | 24 | compileSdkVersion 33 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | 35 | sourceSets { 36 | main.java.srcDirs += 'src/main/kotlin' 37 | test.java.srcDirs += 'src/test/kotlin' 38 | } 39 | 40 | defaultConfig { 41 | minSdkVersion 17 42 | } 43 | 44 | dependencies { 45 | testImplementation 'org.jetbrains.kotlin:kotlin-test' 46 | testImplementation 'org.mockito:mockito-core:5.0.0' 47 | } 48 | 49 | testOptions { 50 | unitTests.all { 51 | useJUnitPlatform() 52 | 53 | testLogging { 54 | events "passed", "skipped", "failed", "standardOut", "standardError" 55 | outputs.upToDateWhen {false} 56 | showStandardStreams = true 57 | } 58 | } 59 | } 60 | } 61 | 62 | dependencies { 63 | implementation("com.squareup.okhttp3:okhttp:3.12.13") 64 | implementation("com.orhanobut:logger:2.2.0") 65 | implementation("com.google.code.gson:gson:2.9.0") 66 | implementation("com.cdnbye:datachannel_native:1.0.211") 67 | implementation("com.cdnbye:p2p_engine:latest.release") 68 | } 69 | -------------------------------------------------------------------------------- /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 "7.3.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/swarmcloud/flutter_p2p_engine/FlutterP2pEnginePlugin.kt: -------------------------------------------------------------------------------- 1 | package com.swarmcloud.flutter_p2p_engine 2 | 3 | import io.flutter.embedding.engine.plugins.FlutterPlugin 4 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 5 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 6 | import io.flutter.plugin.common.EventChannel 7 | import io.flutter.plugin.common.MethodChannel 8 | 9 | /** FlutterP2pEnginePlugin */ 10 | class FlutterP2pEnginePlugin: FlutterPlugin, ActivityAware { 11 | /// The MethodChannel that will the communication between Flutter and native Android 12 | /// 13 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 14 | /// when the Flutter Engine is detached from the Activity 15 | private lateinit var methodChannel : MethodChannel 16 | private lateinit var eventChannel : EventChannel 17 | 18 | private val handler = MethodHandler() 19 | 20 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 21 | methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "p2p_engine") 22 | eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "p2p_engine_stats") 23 | println("set eventChannel") 24 | handler.setChannel(methodChannel, eventChannel) 25 | methodChannel.setMethodCallHandler(handler) 26 | } 27 | 28 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 29 | methodChannel.setMethodCallHandler(null) 30 | } 31 | 32 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 33 | handler.setActivity(binding.activity) 34 | } 35 | 36 | override fun onDetachedFromActivityForConfigChanges() { 37 | 38 | } 39 | 40 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 41 | handler.setActivity(binding.activity) 42 | } 43 | 44 | override fun onDetachedFromActivity() { 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/swarmcloud/flutter_p2p_engine/MethodHandler.kt: -------------------------------------------------------------------------------- 1 | package com.swarmcloud.flutter_p2p_engine 2 | 3 | import android.app.Activity 4 | import android.util.Log 5 | import com.p2pengine.core.p2p.P2pConfig 6 | import com.p2pengine.core.p2p.P2pStatisticsListener 7 | import com.p2pengine.core.p2p.PlayerInteractor 8 | import com.p2pengine.core.tracking.TrackerZone 9 | import com.p2pengine.core.utils.LogLevel 10 | import com.p2pengine.sdk.P2pEngine 11 | import io.flutter.plugin.common.MethodCall 12 | import io.flutter.plugin.common.MethodChannel 13 | import java.util.concurrent.CountDownLatch 14 | import java.util.concurrent.TimeUnit 15 | import io.flutter.plugin.common.EventChannel 16 | 17 | class MethodHandler : MethodChannel.MethodCallHandler { 18 | 19 | private var activity: Activity? = null 20 | 21 | private var methodChannel: MethodChannel? = null 22 | 23 | private var eventSink: EventChannel.EventSink? = null 24 | 25 | fun setActivity(activity: Activity?) { 26 | this.activity = activity 27 | } 28 | 29 | fun setChannel(methodChannel1: MethodChannel?, eventChannel: EventChannel?) { 30 | this.methodChannel = methodChannel1 31 | eventChannel?.setStreamHandler( 32 | object : EventChannel.StreamHandler { 33 | override fun onListen(arguments: Any?, events: EventChannel.EventSink) { 34 | eventSink = events 35 | Log.d("Android", "EventChannel onListen called") 36 | } 37 | override fun onCancel(arguments: Any?) { 38 | Log.w("Android", "EventChannel onCancel called") 39 | eventSink = null 40 | } 41 | }) 42 | } 43 | 44 | // companion object { 45 | // private var singleton: MethodHandler? = null 46 | // val instance: MethodHandler 47 | // get() { 48 | // if (singleton == null) { 49 | // singleton = MethodHandler() 50 | // } 51 | // return singleton!! 52 | // } 53 | // } 54 | 55 | 56 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { 57 | if (call.method == "getSDKVersion") { 58 | // result.success("Android " + android.os.Build.VERSION.RELEASE); 59 | result.success(P2pEngine.version) 60 | } else if (call.method == "init") { 61 | val arguments = call.arguments as? Map 62 | if (arguments == null) { 63 | result.error("argument_error", "arguments must be map", null) 64 | return 65 | } 66 | val token = arguments["token"] as? String 67 | if (token == null) { 68 | result.error("token_error", "token not found", null) 69 | return 70 | } 71 | val configMap = arguments["config"] as? Map<*, *> 72 | if (configMap == null) { 73 | result.error("config_error", "config must be map", null) 74 | return 75 | } 76 | var logEnabled = if (configMap["logEnabled"] is Boolean) configMap["logEnabled"] as Boolean else false 77 | val level: LogLevel = when (configMap["logLevel"] as? Int) { 78 | 0 -> { 79 | logEnabled = false 80 | LogLevel.ERROR 81 | } 82 | 1 -> LogLevel.DEBUG 83 | 2 -> LogLevel.INFO 84 | 3 -> LogLevel.WARN 85 | 4 -> LogLevel.ERROR 86 | else -> LogLevel.ERROR 87 | } 88 | 89 | 90 | var builder = P2pConfig.Builder() 91 | .logEnabled(logEnabled).logLevel(level) 92 | 93 | val zone: TrackerZone = when (configMap["trackerZone"] as? Int) { 94 | 0 -> TrackerZone.Europe 95 | 1 -> TrackerZone.HongKong 96 | 2 -> TrackerZone.USA 97 | 3 -> TrackerZone.China 98 | else -> TrackerZone.Europe 99 | } 100 | builder = builder.trackerZone(zone) 101 | 102 | configMap["p2pEnabled"]?.let { 103 | builder = builder.p2pEnabled(it as Boolean) 104 | } 105 | 106 | configMap["isSetTopBox"]?.let { 107 | builder = builder.isSetTopBox(it as Boolean) 108 | } 109 | 110 | configMap["wifiOnly"]?.let { 111 | builder = builder.wifiOnly(it as Boolean) 112 | } 113 | 114 | configMap["prefetchOnly"]?.let { 115 | builder = builder.prefetchOnly(it as Boolean) 116 | } 117 | 118 | configMap["downloadOnly"]?.let { 119 | builder = builder.downloadOnly(it as Boolean) 120 | } 121 | 122 | configMap["useHttpRange"]?.let { 123 | builder = builder.useHttpRange(it as Boolean) 124 | } 125 | 126 | configMap["localPortHls"]?.let { 127 | builder = builder.localPortHls(it as Int) 128 | } 129 | 130 | configMap["localPortDash"]?.let { 131 | builder = builder.localPortDash(it as Int) 132 | } 133 | 134 | configMap["maxPeerConnections"]?.let { 135 | builder = builder.maxPeerConnections(it as Int) 136 | } 137 | 138 | configMap["downloadTimeout"]?.let { 139 | builder = builder.downloadTimeout(it as Int, TimeUnit.SECONDS) 140 | } 141 | 142 | configMap["dcDownloadTimeout"]?.let { 143 | builder = builder.dcDownloadTimeout(it as Int, TimeUnit.SECONDS) 144 | } 145 | 146 | configMap["diskCacheLimit"]?.let { 147 | builder = builder.diskCacheLimit(it as Long) 148 | } 149 | 150 | configMap["memoryCacheCountLimit"]?.let { 151 | builder = builder.memoryCacheCountLimit(it as Int) 152 | } 153 | 154 | configMap["startFromSegmentOffset"]?.let { 155 | builder = builder.startFromSegmentOffset(it as Int) 156 | } 157 | 158 | configMap["signalConfig"]?.let { 159 | builder = builder.signalConfig(it as String, null) 160 | } 161 | 162 | configMap["announce"]?.let { 163 | builder = builder.announce(it as String) 164 | } 165 | 166 | configMap["tag"]?.let { 167 | builder = builder.withTag(it as String) 168 | } 169 | 170 | configMap["httpHeadersForHls"]?.let { 171 | builder = builder.httpHeadersForHls(it as Map) 172 | } 173 | 174 | configMap["httpHeadersForDash"]?.let { 175 | builder = builder.httpHeadersForDash(it as Map) 176 | } 177 | 178 | // configMap["httpLoadTime"]?.let { 179 | // builder = builder.httpLoadTime((it as Long)) 180 | // } 181 | 182 | configMap["logPersistent"]?.let { 183 | builder = builder.logPersistent((it as Boolean)) 184 | } 185 | 186 | configMap["sharePlaylist"]?.let { 187 | builder = builder.sharePlaylist((it as Boolean)) 188 | } 189 | 190 | configMap["dashMediaFiles"]?.let { 191 | builder = builder.dashMediaFiles((it as ArrayList)) 192 | } 193 | 194 | configMap["playlistTimeOffset"]?.let { 195 | builder = builder.insertTimeOffsetTag(it as Double) 196 | } 197 | 198 | configMap["maxMediaFilesInPlaylist"]?.let { 199 | builder = builder.maxMediaFilesInPlaylist(it as Int) 200 | } 201 | 202 | configMap["fastStartup"]?.let { 203 | builder = builder.fastStartup(it as Boolean) 204 | } 205 | 206 | configMap["geoIpPreflight"]?.let { 207 | builder = builder.geoIpPreflight(it as Boolean) 208 | } 209 | 210 | configMap["useStrictHlsSegmentId"]?.let { 211 | builder = builder.useStrictHlsSegmentId(it as Boolean) 212 | } 213 | 214 | val config: P2pConfig = builder.build() 215 | P2pEngine.init(activity!!.application.applicationContext, token, config) 216 | 217 | if (arguments["enableBufferedDurationGenerator"] as? Boolean == true) { 218 | P2pEngine.instance 219 | ?.setPlayerInteractor(object : PlayerInteractor() { 220 | override fun onBufferedDuration(): Long { 221 | var bufferedDuration: Long = -1 222 | val latch = CountDownLatch(1) 223 | methodChannel!!.invokeMethod( 224 | "bufferedDuration", 225 | null, 226 | object : MethodChannel.Result { 227 | override fun success(result: Any?) { 228 | if (result != null) { 229 | val map: Map<*, *> = 230 | result as Map 231 | val value = map["result"] 232 | bufferedDuration = if (value is Int) 233 | value.toLong() * 1000 else -1 234 | } 235 | latch.countDown() 236 | } 237 | 238 | override fun error( 239 | errorCode: String, 240 | errorMessage: String?, 241 | errorDetails: Any? 242 | ) { 243 | latch.countDown() 244 | } 245 | 246 | override fun notImplemented() { 247 | latch.countDown() 248 | } 249 | }) 250 | try { 251 | latch.await(100, TimeUnit.MILLISECONDS) 252 | } catch (e: InterruptedException) { 253 | e.printStackTrace() 254 | } 255 | return bufferedDuration 256 | } 257 | }) 258 | } 259 | 260 | P2pEngine.instance!!.addP2pStatisticsListener(object : P2pStatisticsListener { 261 | override fun onHttpDownloaded(value: Int) { 262 | val info = HashMap() 263 | info["httpDownloaded"] = value 264 | eventSink?.success(info) 265 | } 266 | 267 | override fun onP2pDownloaded(value: Int, speed: Int) { 268 | val info = HashMap() 269 | info["p2pDownloaded"] = value 270 | info["p2pDownloadSpeed"] = speed 271 | eventSink?.success(info) 272 | } 273 | 274 | override fun onP2pUploaded(value: Int, speed: Int) { 275 | val info = HashMap() 276 | info["p2pUploaded"] = value 277 | eventSink?.success(info) 278 | } 279 | 280 | override fun onPeers(peers: List) { 281 | val info = HashMap>() 282 | info["peers"] = peers 283 | eventSink?.success(info) 284 | } 285 | 286 | override fun onServerConnected(connected: Boolean) { 287 | val info = HashMap() 288 | info["serverConnected"] = connected 289 | eventSink?.success(info) 290 | } 291 | }) 292 | result.success(1) 293 | 294 | } else if (call.method == "parseStreamURL") { 295 | val arguments = call.arguments as? Map<*, *> 296 | if (arguments == null) { 297 | result.error("argument_error", "arguments must be map", null) 298 | return 299 | } 300 | val url = arguments["url"] as? String 301 | if (url == null) { 302 | result.error("url_error", "url not found", null) 303 | return 304 | } 305 | val videoId = arguments["videoId"] as? String? 306 | val parsedUrl = if (videoId == null) { 307 | P2pEngine.instance!!.parseStreamUrl(url) 308 | } else { 309 | P2pEngine.instance!!.parseStreamUrl(url, videoId) 310 | } 311 | result.success(parsedUrl) 312 | } else if (call.method == "notifyPlaybackStalled") { 313 | P2pEngine.instance?.notifyPlaybackStalled() 314 | result.success(1) 315 | } else if (call.method == "isConnected") { 316 | result.success(P2pEngine.instance?.isConnected ?: false) 317 | } else if (call.method == "restartP2p") { 318 | P2pEngine.instance?.restartP2p(null) 319 | result.success(1) 320 | } else if (call.method == "disableP2p") { 321 | P2pEngine.instance?.disableP2p() 322 | result.success(1) 323 | } else if (call.method == "stopP2p") { 324 | P2pEngine.instance?.stopP2p() 325 | result.success(1) 326 | } else if (call.method == "enableP2p") { 327 | P2pEngine.instance?.enableP2p() 328 | result.success(1) 329 | } else if (call.method == "shutdown") { 330 | P2pEngine.instance?.shutdown() 331 | result.success(1) 332 | } else if (call.method == "getPeerId") { 333 | result.success(P2pEngine.instance?.peerId) 334 | } else if (call.method == "setHttpHeadersForHls") { 335 | val arguments = call.arguments as? Map<*, *> 336 | if (arguments != null) { 337 | val headers = arguments["headers"] as? Map 338 | P2pEngine.instance?.setHttpHeadersForHls(headers) 339 | } 340 | result.success(1) 341 | } else if (call.method == "setHttpHeadersForDash") { 342 | val arguments = call.arguments as? Map<*, *> 343 | if (arguments != null) { 344 | val headers = arguments["headers"] as? Map 345 | P2pEngine.instance?.setHttpHeadersForDash(headers) 346 | } 347 | result.success(1) 348 | } else { 349 | println("notImplemented") 350 | result.notImplemented() 351 | } 352 | } 353 | 354 | } 355 | -------------------------------------------------------------------------------- /android/src/test/kotlin/com/swarmcloud/flutter_p2p_engine/FlutterP2pEnginePluginTest.kt: -------------------------------------------------------------------------------- 1 | package com.swarmcloud.flutter_p2p_engine 2 | 3 | import io.flutter.plugin.common.MethodCall 4 | import io.flutter.plugin.common.MethodChannel 5 | import kotlin.test.Test 6 | import org.mockito.Mockito 7 | 8 | /* 9 | * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. 10 | * 11 | * Once you have built the plugin's example app, you can run these tests from the command 12 | * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or 13 | * you can run them directly from IDEs that support JUnit such as Android Studio. 14 | */ 15 | 16 | internal class FlutterP2pEnginePluginTest { 17 | @Test 18 | fun onMethodCall_getPlatformVersion_returnsExpectedValue() { 19 | val plugin = FlutterP2pEnginePlugin() 20 | 21 | val call = MethodCall("getPlatformVersion", null) 22 | val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) 23 | plugin.onMethodCall(call, mockResult) 24 | 25 | Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /darwin/Classes/FlutterP2pEnginePlugin.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Flutter 3 | import UIKit 4 | #elseif os(macOS) 5 | import Cocoa 6 | import FlutterMacOS 7 | #endif 8 | import SwarmCloudKit 9 | 10 | public class FlutterP2pEnginePlugin: NSObject, FlutterPlugin { 11 | 12 | public init(_ channel: FlutterMethodChannel) { 13 | super.init() 14 | self.channel = channel 15 | } 16 | 17 | var sink: FlutterEventSink? 18 | var channel: FlutterMethodChannel? 19 | 20 | public static func register(with registrar: FlutterPluginRegistrar) { 21 | #if os(iOS) 22 | let channel = FlutterMethodChannel(name: "p2p_engine", binaryMessenger: registrar.messenger()) 23 | let instance = FlutterP2pEnginePlugin(channel) 24 | FlutterEventChannel(name: "p2p_engine_stats", binaryMessenger: registrar.messenger()).setStreamHandler(instance) 25 | registrar.addMethodCallDelegate(instance, channel: channel) 26 | #elseif os(macOS) 27 | let channel = FlutterMethodChannel(name: "p2p_engine", binaryMessenger: registrar.messenger) 28 | let instance = FlutterP2pEnginePlugin(channel) 29 | registrar.addMethodCallDelegate(instance, channel: channel) 30 | FlutterEventChannel(name: "p2p_engine_stats", binaryMessenger: registrar.messenger).setStreamHandler(instance) 31 | #endif 32 | } 33 | 34 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 35 | // print(call.method) 36 | switch call.method { 37 | case "getSDKVersion": 38 | result(P2pEngine.VERSION) 39 | case "init": do { 40 | guard let arguments = call.arguments as? Dictionary else { 41 | result(FlutterError(code: "argument_error", 42 | message: "arguments must be dictionary", 43 | details: nil)) 44 | return 45 | } 46 | guard let token = arguments["token"] as? String else { 47 | result(FlutterError(code: "token_error", 48 | message: "token not found", 49 | details: nil)) 50 | return 51 | } 52 | guard let configMap = arguments["config"] as? Dictionary else { 53 | result(FlutterError(code: "config_error", 54 | message: "config must be dictionary", 55 | details: nil)) 56 | return 57 | } 58 | var logEnabled = configMap["logEnabled"] as? Bool ?? false 59 | var level: LogLevel 60 | switch configMap["logLevel"] as? Int { 61 | case 0: do { 62 | logEnabled = false 63 | level = .ERROR 64 | } 65 | case 1: 66 | level = .DEBUG 67 | case 2: 68 | level = .INFO 69 | case 3: 70 | level = .WARN 71 | case 4: 72 | level = .ERROR 73 | default: 74 | level = .ERROR 75 | } 76 | var zone: TrackerZone = .Europe 77 | switch configMap["trackerZone"] as? Int { 78 | case 1: 79 | zone = .HongKong 80 | case 2: 81 | zone = .USA 82 | case 3: 83 | zone = .China 84 | default: 85 | zone = .Europe 86 | } 87 | let config = P2pConfig( 88 | trackerZone: zone 89 | ) 90 | if let p2pEnabled = configMap["p2pEnabled"] as? Bool { 91 | config.p2pEnabled = p2pEnabled 92 | } 93 | config.debug = logEnabled 94 | config.logLevel = level 95 | if let wifiOnly = configMap["wifiOnly"] as? Bool { 96 | config.wifiOnly = wifiOnly 97 | } 98 | if let prefetchOnly = configMap["prefetchOnly"] as? Bool { 99 | config.prefetchOnly = prefetchOnly 100 | } 101 | if let downloadOnly = configMap["downloadOnly"] as? Bool { 102 | config.downloadOnly = downloadOnly 103 | } 104 | if let useHttpRange = configMap["useHttpRange"] as? Bool { 105 | config.useHttpRange = useHttpRange 106 | } 107 | if let localPortHls = configMap["localPortHls"] as? UInt { 108 | config.localPortHls = localPortHls 109 | } 110 | if let maxPeerConnections = configMap["maxPeerConnections"] as? Int { 111 | config.maxPeerConnections = maxPeerConnections 112 | } 113 | if let dcDownloadTimeout = configMap["dcDownloadTimeout"] as? TimeInterval { 114 | config.dcDownloadTimeout = dcDownloadTimeout 115 | } 116 | if let diskCacheLimit = configMap["diskCacheLimit"] as? UInt { 117 | config.diskCacheLimit = diskCacheLimit 118 | } 119 | if let memoryCacheCountLimit = configMap["memoryCacheCountLimit"] as? UInt { 120 | config.memoryCacheCountLimit = memoryCacheCountLimit 121 | } 122 | if let startFromSegmentOffset = configMap["startFromSegmentOffset"] as? UInt { 123 | config.startFromSegmentOffset = startFromSegmentOffset 124 | } 125 | if let signalConfig = configMap["signalConfig"] as? String { 126 | config.signalConfig = SignalConfig(mainAddr: signalConfig) 127 | } 128 | if let announce = configMap["announce"] as? String { 129 | config.announce = announce 130 | } 131 | if let tag = configMap["tag"] as? String { 132 | config.customLabel = tag 133 | } 134 | if let httpHeadersForHls = configMap["httpHeadersForHls"] as? Dictionary { 135 | config.httpHeadersHls = httpHeadersForHls 136 | } 137 | // if let httpLoadTime = configMap["httpLoadTime"] as? TimeInterval { 138 | // config.httpLoadTime = httpLoadTime 139 | // } 140 | if let logPersistent = configMap["logPersistent"] as? Bool { 141 | config.logPersistent = logPersistent 142 | } 143 | if let sharePlaylist = configMap["sharePlaylist"] as? Bool { 144 | config.sharePlaylist = sharePlaylist 145 | } 146 | if let maxMediaFilesInPlaylist = configMap["maxMediaFilesInPlaylist"] as? Int { 147 | config.maxMediaFilesInPlaylist = maxMediaFilesInPlaylist 148 | } 149 | if let fastStartup = configMap["fastStartup"] as? Bool { 150 | config.fastStartup = fastStartup 151 | } 152 | if let geoIpPreflight = configMap["geoIpPreflight"] as? Bool { 153 | config.geoIpPreflight = geoIpPreflight 154 | } 155 | if let useStrictHlsSegmentId = configMap["useStrictHlsSegmentId"] as? Bool { 156 | config.useStrictHlsSegmentId = useStrictHlsSegmentId 157 | } 158 | 159 | P2pEngine.setup(token: token, config: config) 160 | 161 | if arguments["enableBufferedDurationGenerator"] is Bool { 162 | P2pEngine.shared.playerInteractor = self 163 | } 164 | 165 | startMonitoring() 166 | 167 | result(1) 168 | } 169 | case "parseStreamURL": do { 170 | guard let arguments = call.arguments as? Dictionary else { 171 | result(FlutterError(code: "argument_error", 172 | message: "arguments must be dictionary", 173 | details: nil)) 174 | return 175 | } 176 | guard let url = arguments["url"] as? String else { 177 | result(FlutterError(code: "url_error", 178 | message: "url not found", 179 | details: nil)) 180 | return 181 | } 182 | let videoId = arguments["videoId"] as? String 183 | var parsedUrl: String 184 | if videoId == nil { 185 | parsedUrl = P2pEngine.shared.parseStreamUrl(url) 186 | } else { 187 | parsedUrl = P2pEngine.shared.parseStreamUrl(url, videoId: videoId) 188 | } 189 | result(parsedUrl) 190 | } 191 | case "notifyPlaybackStalled": do { 192 | P2pEngine.shared.notifyPlaybackStalled() 193 | result(1) 194 | } 195 | case "isConnected": do { 196 | result(P2pEngine.shared.isConnected) 197 | } 198 | case "getPeerId": do { 199 | result(P2pEngine.shared.peerId) 200 | } 201 | case "restartP2p": do { 202 | P2pEngine.shared.restartP2p() 203 | result(1) 204 | } 205 | case "disableP2p": do { 206 | P2pEngine.shared.disableP2p() 207 | result(1) 208 | } 209 | case "stopP2p": do { 210 | P2pEngine.shared.stopP2p() 211 | result(1) 212 | } 213 | case "enableP2p": do { 214 | P2pEngine.shared.enableP2p() 215 | result(1) 216 | } 217 | case "shutdown": do { 218 | P2pEngine.shared.shutdown() 219 | result(1) 220 | } 221 | case "setHttpHeadersForHls": do { 222 | if let arguments = call.arguments as? Dictionary { 223 | if let headers = arguments["headers"] as? Dictionary { 224 | P2pEngine.shared.setHttpHeadersForHls(headers: headers) 225 | } 226 | } 227 | result(1) 228 | } 229 | default: 230 | result(FlutterMethodNotImplemented) 231 | } 232 | } 233 | 234 | func startMonitoring() { 235 | let monitor = P2pStatisticsMonitor() 236 | monitor.onPeers = { peers in 237 | let info = ["peers": peers] 238 | self.sink?(info) 239 | } 240 | monitor.onP2pUploaded = { value in 241 | let info = ["p2pUploaded": value] 242 | self.sink?(info) 243 | } 244 | monitor.onP2pDownloaded = { value, speed in 245 | let info = ["p2pDownloaded": value, "p2pDownloadSpeed": speed] 246 | self.sink?(info) 247 | } 248 | monitor.onHttpDownloaded = { value in 249 | let info = ["httpDownloaded": value] 250 | self.sink?(info) 251 | } 252 | monitor.onServerConnected = { connected in 253 | let info = ["serverConnected": connected] 254 | self.sink?(info) 255 | } 256 | P2pEngine.shared.p2pStatisticsMonitor = monitor 257 | } 258 | 259 | } 260 | 261 | extension FlutterP2pEnginePlugin : FlutterStreamHandler { 262 | public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 263 | self.sink = events 264 | return nil 265 | } 266 | 267 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 268 | self.sink = nil 269 | return nil 270 | } 271 | } 272 | 273 | extension FlutterP2pEnginePlugin : PlayerInteractor { 274 | public func onBufferedDuration() -> TimeInterval { 275 | let sema = DispatchSemaphore(value: 0) 276 | var bufferedDuration: TimeInterval = -1 277 | DispatchQueue.main.async { 278 | self.channel?.invokeMethod("bufferedDuration", arguments: nil, result: { arg in 279 | guard let map = arg as? Dictionary else { 280 | return 281 | } 282 | bufferedDuration = map["result"] as? TimeInterval ?? -1 283 | sema.signal() 284 | }) 285 | } 286 | _ = sema.wait(timeout: DispatchTime.now() + 0.1) 287 | return bufferedDuration; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /darwin/flutter_p2p_engine.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint flutter_p2p_engine.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'flutter_p2p_engine' 7 | s.version = '3.2.5' 8 | s.summary = 'SwarmCloud p2p engine for flutter' 9 | s.description = <<-DESC 10 | SwarmCloud p2p engine for flutter 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'SwarmCloud' => 'cdnbye@gmail.com' } 15 | 16 | s.source = { :path => '.' } 17 | s.source_files = 'Classes/**/*' 18 | s.ios.dependency 'Flutter' 19 | s.osx.dependency 'FlutterMacOS' 20 | s.dependency 'SwarmCloudKit', '~> 3.0' 21 | 22 | s.ios.deployment_target = '13.0' 23 | s.osx.deployment_target = '10.15' 24 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 25 | s.swift_version = '5.0' 26 | end 27 | -------------------------------------------------------------------------------- /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 | # flutter_p2p_engine_example 2 | 3 | Demonstrates how to use the flutter_p2p_engine 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 https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /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 flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.swarmcloud.flutter_p2p_engine_example" 27 | compileSdkVersion flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.swarmcloud.flutter_p2p_engine_example" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies {} 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 24 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/swarmcloud/flutter_p2p_engine_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.swarmcloud.flutter_p2p_engine_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | 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-7.5-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 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | plugins { 14 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 15 | } 16 | } 17 | 18 | include ":app" 19 | 20 | apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" 21 | -------------------------------------------------------------------------------- /example/integration_test/plugin_integration_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter integration test. 2 | // 3 | // Since integration tests run in a full Flutter application, they can interact 4 | // with the host side of a plugin implementation, unlike Dart unit tests. 5 | // 6 | // For more information about Flutter integration tests, please see 7 | // https://docs.flutter.dev/cookbook/testing/integration/introduction 8 | 9 | 10 | import 'package:flutter_test/flutter_test.dart'; 11 | import 'package:integration_test/integration_test.dart'; 12 | 13 | import 'package:flutter_p2p_engine/flutter_p2p_engine.dart'; 14 | 15 | void main() { 16 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 17 | 18 | testWidgets('getPlatformVersion test', (WidgetTester tester) async { 19 | final FlutterP2pEngine plugin = FlutterP2pEngine(); 20 | final String? version = await FlutterP2pEngine.getSDKVersion(); 21 | // The version string depends on the host platform running the test, so 22 | // just assert that some non-empty string is returned. 23 | expect(version?.isNotEmpty, true); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /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 | 12.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, '13.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 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_p2p_engine (3.2.5): 4 | - Flutter 5 | - FlutterMacOS 6 | - SwarmCloudKit (~> 3.3.2) 7 | - integration_test (0.0.1): 8 | - Flutter 9 | - path_provider_foundation (0.0.1): 10 | - Flutter 11 | - FlutterMacOS 12 | - SwarmCloudKit (3.3.2) 13 | - video_player_avfoundation (0.0.1): 14 | - Flutter 15 | - FlutterMacOS 16 | 17 | DEPENDENCIES: 18 | - Flutter (from `Flutter`) 19 | - flutter_p2p_engine (from `.symlinks/plugins/flutter_p2p_engine/darwin`) 20 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 21 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 22 | - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) 23 | 24 | SPEC REPOS: 25 | trunk: 26 | - SwarmCloudKit 27 | 28 | EXTERNAL SOURCES: 29 | Flutter: 30 | :path: Flutter 31 | flutter_p2p_engine: 32 | :path: ".symlinks/plugins/flutter_p2p_engine/darwin" 33 | integration_test: 34 | :path: ".symlinks/plugins/integration_test/ios" 35 | path_provider_foundation: 36 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 37 | video_player_avfoundation: 38 | :path: ".symlinks/plugins/video_player_avfoundation/darwin" 39 | 40 | SPEC CHECKSUMS: 41 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 42 | flutter_p2p_engine: 160d338e20fab76d3f305fa07cc73c1219d6c8de 43 | integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 44 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 45 | SwarmCloudKit: 310f8d1c81c131540aa75228cf2a82bf4b91afb0 46 | video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 47 | 48 | PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 49 | 50 | COCOAPODS: 1.15.2 51 | -------------------------------------------------------------------------------- /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 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /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.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/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 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleDisplayName 13 | Flutter P2p Engine 14 | CFBundleExecutable 15 | $(EXECUTABLE_NAME) 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | flutter_p2p_engine_example 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | $(FLUTTER_BUILD_NAME) 26 | CFBundleSignature 27 | ???? 28 | CFBundleVersion 29 | $(FLUTTER_BUILD_NUMBER) 30 | LSRequiresIPhoneOS 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | CADisableMinimumFrameDurationOnPhone 50 | 51 | UIApplicationSupportsIndirectInputEvents 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | @testable import flutter_p2p_engine 6 | 7 | // This demonstrates a simple unit test of the Swift portion of this plugin's implementation. 8 | // 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | 11 | class RunnerTests: XCTestCase { 12 | 13 | func testGetPlatformVersion() { 14 | let plugin = FlutterP2pEnginePlugin() 15 | 16 | let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) 17 | 18 | let resultExpectation = expectation(description: "result block must be called.") 19 | plugin.handle(call) { result in 20 | XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) 21 | resultExpectation.fulfill() 22 | } 23 | waitForExpectations(timeout: 1) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:safemap/safemap.dart'; 4 | import 'package:flutter_p2p_engine/flutter_p2p_engine.dart'; 5 | import 'package:flutter_p2p_engine_example/style/color.dart'; 6 | import 'package:flutter_p2p_engine_example/views/confirm.dart'; 7 | import 'package:tapped/tapped.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:video_player/video_player.dart'; 10 | 11 | const token = 'ZMuO5qHZg'; // replace with your own token 12 | 13 | void main() { 14 | runApp(const VideoApp()); 15 | 16 | SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle( 17 | statusBarColor: Colors.transparent, 18 | ); 19 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 20 | } 21 | 22 | class VideoApp extends StatelessWidget { 23 | const VideoApp({Key? key}) : super(key: key); 24 | @override 25 | Widget build(BuildContext context) { 26 | return const MaterialApp( 27 | title: 'Video Demo', 28 | home: HomePage(), 29 | ); 30 | } 31 | } 32 | 33 | class HomePage extends StatefulWidget { 34 | const HomePage({Key? key}) : super(key: key); 35 | 36 | @override 37 | State createState() => _HomePageState(); 38 | } 39 | 40 | class _HomePageState extends State { 41 | var playerReady = false; 42 | VideoPlayerController? _controller; 43 | @override 44 | void initState() { 45 | super.initState(); 46 | init(); 47 | } 48 | 49 | // var url = 'https://test-streams.mux.dev/x36xhzz/url_0/193039199_mp4_h264_aac_hd_7.m3u8'; 50 | var url = 'https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/level_0.m3u8'; 51 | 52 | var totalHTTPDn = 0; 53 | var totalP2PDn = 0; 54 | var totalP2PUp = 0; 55 | var connected = false; 56 | var sdkVersion = ''; 57 | 58 | init() async { 59 | try { 60 | // Do not init FlutterP2pEngine more than once! 请不要多次init FlutterP2pEngine! 61 | await FlutterP2pEngine.init( 62 | token, 63 | // bufferedDurationGeneratorEnable: true, 64 | config: P2pConfig( 65 | trackerZone: TrackerZone.Europe, 66 | logEnabled: true, 67 | logLevel: P2pLogLevel.debug, 68 | // useStrictHlsSegmentId: true, 69 | // playlistTimeOffset: 0.0, 70 | ), 71 | infoListener: (info) { 72 | // print('p2p listen: $info'); 73 | if (SafeMap(info)["serverConnected"].hasValue) { 74 | setState(() { 75 | connected = SafeMap(info)["serverConnected"].boolean; 76 | }); 77 | } else { 78 | setState(() { 79 | totalHTTPDn += SafeMap(info)["httpDownloaded"].intOrZero; 80 | totalP2PDn += SafeMap(info)["p2pDownloaded"].intOrZero; 81 | totalP2PUp += SafeMap(info)["p2pUploaded"].intOrZero; 82 | }); 83 | } 84 | }, 85 | ); 86 | 87 | setState(() {}); 88 | setUpVideo(url); 89 | 90 | sdkVersion = await FlutterP2pEngine.getSDKVersion(); 91 | } catch (e) { 92 | // print('Init Error $e'); 93 | } 94 | } 95 | 96 | bool showDetail = false; 97 | 98 | setUpVideo(String url) async { 99 | _controller?.dispose(); 100 | var res = await FlutterP2pEngine.parseStreamURL(url, 101 | // bufferedDurationGenerator: () { 102 | // return _controller!.value.buffered.last.end - _controller!.value.position; 103 | // } 104 | ); 105 | print('urlResult $res'); 106 | _controller = VideoPlayerController.networkUrl(Uri.parse(res ?? url))..initialize().then((_) { 107 | // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. 108 | setState(() { 109 | playerReady = true; 110 | }); 111 | _controller?.play(); 112 | }); 113 | 114 | } 115 | 116 | @override 117 | void dispose() { 118 | // player.dispose(); 119 | _controller?.dispose(); 120 | super.dispose(); 121 | } 122 | 123 | @override 124 | Widget build(BuildContext context) { 125 | return Scaffold( 126 | backgroundColor: ColorPlate.lightGray, 127 | body: Center( 128 | child: Stack( 129 | alignment: Alignment.bottomLeft, 130 | children: [ 131 | Center( 132 | child: SizedBox( 133 | width: MediaQuery.of(context).size.width, 134 | height: MediaQuery.of(context).size.width * 9.0 / 16.0, 135 | // Use [Video] widget to display video output. 136 | child: playerReady 137 | ? AspectRatio( 138 | aspectRatio: _controller!.value.aspectRatio, 139 | child: Stack( 140 | alignment: Alignment.bottomCenter, 141 | children: [ 142 | VideoPlayer(_controller!), 143 | VideoProgressIndicator(_controller!, allowScrubbing: true), 144 | GestureDetector( 145 | onTap: () { 146 | _controller!.value.isPlaying ? _controller!.pause() : _controller!.play(); 147 | }, 148 | ), 149 | ], 150 | ), 151 | ) 152 | : Container(), 153 | ), 154 | ), 155 | Positioned( 156 | left: 12, 157 | bottom: 20, 158 | child: Container( 159 | padding: const EdgeInsets.symmetric( 160 | horizontal: 8, 161 | vertical: 2, 162 | ), 163 | decoration: BoxDecoration( 164 | color: Colors.blue.withOpacity(0.8), 165 | borderRadius: BorderRadius.circular(6), 166 | ), 167 | constraints: const BoxConstraints( 168 | maxWidth: 300, 169 | ), 170 | child: DefaultTextStyle( 171 | style: const TextStyle( 172 | color: Colors.white, 173 | fontWeight: FontWeight.normal, 174 | fontSize: 10, 175 | ), 176 | child: Row( 177 | mainAxisSize: MainAxisSize.min, 178 | children: [ 179 | if (showDetail) 180 | Flexible( 181 | child: Container( 182 | padding: const EdgeInsets.symmetric( 183 | vertical: 4, 184 | ), 185 | child: Column( 186 | mainAxisSize: MainAxisSize.min, 187 | crossAxisAlignment: CrossAxisAlignment.start, 188 | children: [ 189 | Container( 190 | padding: const EdgeInsets.symmetric( 191 | vertical: 2, 192 | ), 193 | child: Text('Token:$token'), 194 | ), 195 | Container( 196 | padding: const EdgeInsets.symmetric( 197 | vertical: 2, 198 | ), 199 | child: Text('Url:$url'), 200 | ), 201 | Container( 202 | padding: const EdgeInsets.symmetric( 203 | vertical: 2, 204 | ), 205 | child: Text( 206 | [ 207 | 'SDK Version: $sdkVersion', 208 | 'Connected: $connected', 209 | 'TotalHTTPDownloaded: $totalHTTPDn', 210 | 'TotalP2PDownloaded: $totalP2PDn', 211 | 'TotalP2PUploaded: $totalP2PUp', 212 | ].join('\n'), 213 | ), 214 | ), 215 | ], 216 | ), 217 | ), 218 | ), 219 | Column( 220 | children: [ 221 | Tapped( 222 | onTap: () { 223 | setState(() { 224 | showDetail = !showDetail; 225 | }); 226 | }, 227 | child: Container( 228 | padding: const EdgeInsets.symmetric( 229 | horizontal: 6, 230 | vertical: 12, 231 | ), 232 | child: Icon( 233 | showDetail ? Icons.clear : Icons.info_outline, 234 | color: Colors.white, 235 | size: 20, 236 | ), 237 | ), 238 | ), 239 | if (showDetail) 240 | Tapped( 241 | onTap: () async { 242 | var res = await editUrl( 243 | context, 244 | url: url, 245 | ); 246 | var _url = res?.asMap()[0]; 247 | if (_url != null) { 248 | setState(() {}); 249 | setUpVideo(_url); 250 | } 251 | }, 252 | child: Container( 253 | padding: const EdgeInsets.symmetric( 254 | horizontal: 6, 255 | vertical: 12, 256 | ), 257 | child: const Icon( 258 | Icons.edit, 259 | color: Colors.white, 260 | size: 20, 261 | ), 262 | ), 263 | ), 264 | ], 265 | ), 266 | ], 267 | ), 268 | ), 269 | ), 270 | ) 271 | ], 272 | ), 273 | ), 274 | // floatingActionButton: FloatingActionButton( 275 | // onPressed: () { 276 | // setState(() { 277 | // if (playerReady) { 278 | // _controller!.value.isPlaying ? _controller!.pause(): _controller!.play(); 279 | // } 280 | // }); 281 | // }, 282 | // child: Icon( 283 | // playerReady && _controller!.value.isPlaying ? Icons.pause : Icons.play_arrow, 284 | // ), 285 | // ), 286 | ); 287 | } 288 | } 289 | 290 | /// 输入文本,可以通过onWillConfirm方法检查 291 | Future?> editUrl( 292 | BuildContext context, { 293 | ConfirmType? type, 294 | String? url, 295 | }) async { 296 | InputHelper urlInput = InputHelper(defaultText: url); 297 | var res = await confirm( 298 | context, 299 | type: type, 300 | title: 'Edit', 301 | ok: 'Save', 302 | cancel: 'Cancel', 303 | onWillConfirm: () async => true, 304 | contentBuilder: (ctx) => Column( 305 | children: [ 306 | Container( 307 | margin: const EdgeInsets.symmetric( 308 | horizontal: 12, 309 | vertical: 6, 310 | ), 311 | decoration: BoxDecoration( 312 | color: ColorPlate.lightGray, 313 | borderRadius: BorderRadius.circular(6), 314 | ), 315 | child: StTextField( 316 | autofocus: true, 317 | margin: EdgeInsets.zero, 318 | helper: urlInput, 319 | hintText: 'Input Url', 320 | ), 321 | ), 322 | ], 323 | ), 324 | ); 325 | if (res == true) { 326 | return [ 327 | urlInput.text, 328 | ]; 329 | } 330 | return null; 331 | } 332 | -------------------------------------------------------------------------------- /example/lib/style/color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColorPlate { 4 | // 层次灰色 5 | static const Color black = Colors.black; 6 | static const Color darkGray = Color(0xff111D32); 7 | static const Color gray = Color(0xff9B9B9B); 8 | static const Color halfGray = Color(0xffdbdbdb); 9 | static const Color lightGray = Color(0xfff5f5f4); 10 | static const Color white = Colors.white; 11 | static const Color clear = Color(0); 12 | // 13 | // 专属色 14 | // 15 | static const Color mainBlue = Color(0xFF276AF6); 16 | static const Color highLightBlue = Color(0xFF276AF6); 17 | static const Color darkFontBlue = Color(0xff1A2E50); 18 | 19 | static const Color orange = Color(0xffE88001); 20 | static const Color yellow = Color(0xFFF7B500); 21 | static const Color wheat = Color(0xFFFFF2D7); 22 | static const Color darkOrange = Color(0xffF8684D); 23 | static const Color green = Color(0xff44CD8D); 24 | static const Color blueGreen = Color(0xff43CED2); 25 | static const Color red = Color(0xffF24C61); 26 | } 27 | -------------------------------------------------------------------------------- /example/lib/style/physics.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const alwaysBouncePhysics = AlwaysScrollableScrollPhysics( 4 | parent: BouncingScrollPhysics(), 5 | ); 6 | -------------------------------------------------------------------------------- /example/lib/style/size.dart: -------------------------------------------------------------------------------- 1 | class SysSize { 2 | static const double huge = 25; 3 | static const double big = 18; 4 | static const double normal = 16; 5 | static const double small = 14; 6 | static const double tiny = 12; 7 | } 8 | -------------------------------------------------------------------------------- /example/lib/style/text.dart: -------------------------------------------------------------------------------- 1 | import 'color.dart'; 2 | import 'size.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | double get oneLineH => 1.3; 6 | 7 | class StandardTextStyle { 8 | static const TextStyle big = TextStyle( 9 | fontWeight: FontWeight.w600, 10 | fontSize: SysSize.big, 11 | color: ColorPlate.darkGray, 12 | inherit: true, 13 | height: 1.4, 14 | ); 15 | static const TextStyle normalW = TextStyle( 16 | fontWeight: FontWeight.w600, 17 | fontSize: SysSize.normal, 18 | color: ColorPlate.darkGray, 19 | inherit: true, 20 | height: 1.4, 21 | ); 22 | static const TextStyle normal = TextStyle( 23 | fontWeight: FontWeight.normal, 24 | fontSize: SysSize.normal, 25 | color: ColorPlate.darkGray, 26 | inherit: true, 27 | height: 1.4, 28 | ); 29 | static const TextStyle small = TextStyle( 30 | fontWeight: FontWeight.normal, 31 | fontSize: SysSize.small, 32 | color: ColorPlate.gray, 33 | inherit: true, 34 | height: 1.4, 35 | ); 36 | } 37 | 38 | class StText extends StatelessWidget { 39 | final String? text; 40 | final TextStyle? style; 41 | final TextStyle defaultStyle; 42 | final TextAlign? align; 43 | final int? maxLines; 44 | 45 | const StText({ 46 | Key? key, 47 | this.text, 48 | this.style, 49 | required this.defaultStyle, 50 | this.maxLines, 51 | this.align, 52 | }) : super(key: key); 53 | 54 | const StText.small( 55 | String? text, { 56 | Key? key, 57 | TextStyle? style, 58 | TextAlign? align, 59 | int? maxLines, 60 | }) : this( 61 | key: key, 62 | text: text, 63 | style: style, 64 | defaultStyle: StandardTextStyle.small, 65 | maxLines: maxLines, 66 | align: align, 67 | ); 68 | 69 | const StText.normal( 70 | String? text, { 71 | Key? key, 72 | TextStyle? style, 73 | TextAlign? align, 74 | int? maxLines, 75 | }) : this( 76 | key: key, 77 | text: text, 78 | style: style, 79 | defaultStyle: StandardTextStyle.normal, 80 | maxLines: maxLines, 81 | align: align, 82 | ); 83 | 84 | const StText.medium( 85 | String? text, { 86 | Key? key, 87 | TextStyle? style, 88 | int? maxLines, 89 | }) : this( 90 | key: key, 91 | text: text, 92 | style: style, 93 | defaultStyle: StandardTextStyle.normalW, 94 | maxLines: maxLines, 95 | ); 96 | 97 | const StText.big( 98 | String? text, { 99 | Key? key, 100 | TextStyle? style, 101 | TextAlign? align, 102 | int? maxLines, 103 | }) : this( 104 | key: key, 105 | text: text, 106 | style: style, 107 | defaultStyle: StandardTextStyle.big, 108 | maxLines: maxLines, 109 | align: align, 110 | ); 111 | 112 | @override 113 | Widget build(BuildContext context) { 114 | return DefaultTextStyle( 115 | style: defaultStyle, 116 | child: Text( 117 | text ?? '', 118 | maxLines: maxLines ?? 100, 119 | textAlign: align, 120 | overflow: TextOverflow.ellipsis, 121 | style: style, 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /example/lib/style/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | import 'color.dart'; 4 | import 'size.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class MyTheme { 8 | static ThemeData standard = ThemeData( 9 | brightness: Brightness.light, 10 | hintColor: ColorPlate.mainBlue, 11 | textTheme: TextTheme( 12 | //设置Material的默认字体样式 13 | bodyMedium: TextStyle( 14 | color: ColorPlate.darkGray, 15 | fontSize: SysSize.normal, 16 | ), 17 | ), 18 | appBarTheme: AppBarTheme( 19 | elevation: 0, 20 | color: ColorPlate.white, 21 | systemOverlayStyle: SystemUiOverlayStyle.dark, 22 | iconTheme: IconThemeData( 23 | color: ColorPlate.darkGray, 24 | ), 25 | ), 26 | primaryColor: ColorPlate.mainBlue, 27 | // primaryColorBrightness: Brightness.dark, 28 | scaffoldBackgroundColor: ColorPlate.white, 29 | textSelectionTheme: TextSelectionThemeData( 30 | cursorColor: ColorPlate.mainBlue, 31 | selectionColor: ColorPlate.highLightBlue.withOpacity(0.5), 32 | selectionHandleColor: ColorPlate.highLightBlue, 33 | ) 34 | // inputDecorationTheme: InputDecorationTheme( 35 | // ), 36 | // highlightColor: Colors.transparent, 37 | // splashFactory: const NoSplashFactory(), 38 | ); 39 | } 40 | 41 | class NoSplashFactory extends InteractiveInkFeatureFactory { 42 | const NoSplashFactory(); 43 | 44 | InteractiveInkFeature create({ 45 | required MaterialInkController controller, 46 | required RenderBox referenceBox, 47 | required Offset position, 48 | required Color color, 49 | TextDirection? textDirection, 50 | bool containedInkWell = false, 51 | RectCallback? rectCallback, 52 | BorderRadius? borderRadius, 53 | ShapeBorder? customBorder, 54 | double? radius, 55 | VoidCallback? onRemoved, 56 | }) { 57 | return NoSplash( 58 | controller: controller, 59 | referenceBox: referenceBox, 60 | color: color, 61 | onRemoved: onRemoved, 62 | ); 63 | } 64 | } 65 | 66 | class NoSplash extends InteractiveInkFeature { 67 | NoSplash({ 68 | required MaterialInkController controller, 69 | required RenderBox referenceBox, 70 | required Color color, 71 | VoidCallback? onRemoved, 72 | }) : super( 73 | controller: controller, 74 | referenceBox: referenceBox, 75 | onRemoved: onRemoved, 76 | color: color, 77 | ) { 78 | controller.addInkFeature(this); 79 | } 80 | @override 81 | void paintFeature(Canvas canvas, Matrix4 transform) {} 82 | } 83 | -------------------------------------------------------------------------------- /example/lib/views/confirm.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_p2p_engine_example/style/color.dart'; 4 | import 'package:flutter_p2p_engine_example/style/size.dart'; 5 | import 'package:flutter_p2p_engine_example/style/text.dart'; 6 | import 'package:tapped/tapped.dart'; 7 | 8 | const String titleText = '结果'; 9 | const String cancelText = '取消'; 10 | const String closeText = '关闭'; 11 | const String okText = '确认'; 12 | 13 | enum ConfirmType { 14 | info, 15 | success, 16 | warning, 17 | danger, 18 | } 19 | 20 | /// 封装了取值,焦点,控制器方法 21 | class InputHelper { 22 | final String? defaultText; 23 | 24 | InputHelper({this.defaultText}) 25 | : controller = TextEditingController(text: defaultText); 26 | 27 | final TextEditingController controller; 28 | 29 | String get text => controller.value.text; 30 | 31 | set text(String? t) { 32 | controller.value = TextEditingValue(text: t ?? ''); 33 | } 34 | 35 | final FocusNode focusNode = FocusNode(); 36 | } 37 | 38 | textOfConfirmType(ConfirmType type) => [ 39 | '提醒', 40 | '操作成功', 41 | '警告', 42 | '警告', 43 | ][type.index]; 44 | 45 | /// 输入多行文本,可以通过onWillConfirm方法检查 46 | Future inputMutiLineText( 47 | BuildContext context, { 48 | ConfirmType? type, 49 | String? text, 50 | String? title, 51 | String? hintText, 52 | String? ok, 53 | String? cancel, 54 | Future Function(String)? onWillConfirm, 55 | }) async { 56 | InputHelper temp = InputHelper(defaultText: text); 57 | var res = await confirm( 58 | context, 59 | type: type ?? ConfirmType.warning, 60 | title: title, 61 | ok: ok, 62 | cancel: cancel, 63 | onWillConfirm: () async => (await onWillConfirm?.call(temp.text)) ?? true, 64 | contentBuilder: (ctx) => StTextField( 65 | autofocus: true, 66 | margin: const EdgeInsets.symmetric( 67 | horizontal: 12, 68 | vertical: 14, 69 | ), 70 | helper: temp, 71 | hintText: hintText, 72 | ), 73 | ); 74 | if (res == true) { 75 | return temp.text; 76 | } 77 | return null; 78 | } 79 | 80 | /// 输入文本,可以通过onWillConfirm方法检查 81 | Future inputText( 82 | BuildContext context, { 83 | ConfirmType? type, 84 | String? text, 85 | int? maxLength, 86 | String? title, 87 | String? hintText, 88 | String? ok, 89 | String? cancel, 90 | TextInputType? textInputType, 91 | Future Function(String)? onWillConfirm, 92 | }) async { 93 | InputHelper temp = InputHelper(defaultText: text); 94 | var res = await confirm( 95 | context, 96 | type: type, 97 | title: title, 98 | ok: ok, 99 | cancel: cancel, 100 | onWillConfirm: () async => (await onWillConfirm?.call(temp.text)) ?? true, 101 | contentBuilder: (ctx) => Container( 102 | margin: const EdgeInsets.symmetric( 103 | horizontal: 12, 104 | vertical: 16, 105 | ), 106 | decoration: BoxDecoration( 107 | color: ColorPlate.lightGray, 108 | borderRadius: BorderRadius.circular(6), 109 | ), 110 | child: StInput.helper( 111 | autofocus: true, 112 | contentPadding: const EdgeInsets.symmetric( 113 | horizontal: 12, 114 | vertical: 14, 115 | ), 116 | inputType: textInputType, 117 | maxLength: maxLength ?? 20, 118 | clearable: true, 119 | helper: temp, 120 | hintText: hintText, 121 | ), 122 | ), 123 | ); 124 | if (res == true) { 125 | return temp.text; 126 | } 127 | return null; 128 | } 129 | 130 | /// 显示对话窗口 131 | Future confirm( 132 | BuildContext context, { 133 | String? title, 134 | String? content, 135 | Widget Function(BuildContext)? contentBuilder, 136 | Future Function()? onWillConfirm, 137 | String? ok, 138 | String? cancel, 139 | ConfirmType? type, 140 | double? width, 141 | bool showHeader = true, 142 | bool onlyCloseButton = false, 143 | bool onlyContent = false, 144 | bool barrierDismissible = true, 145 | }) { 146 | return showDialog( 147 | context: context, 148 | barrierDismissible: barrierDismissible, 149 | builder: (context) => _ConfirmDialog( 150 | title: title, 151 | content: content, 152 | contentWidgetBuilder: contentBuilder, 153 | onWillConfirm: onWillConfirm, 154 | ok: ok, 155 | cancel: cancel, 156 | type: type ?? ConfirmType.info, 157 | width: width ?? 300, 158 | onlyCloseButton: onlyCloseButton, 159 | onlyContent: onlyContent, 160 | showHeader: showHeader, 161 | ), 162 | ); 163 | } 164 | 165 | class ResMsg { 166 | final bool success; 167 | final String msg; 168 | final T? data; 169 | 170 | ResMsg(this.success, this.msg, this.data); 171 | ResMsg.success(String msg, [data]) : this(true, msg, data); 172 | ResMsg.fail(String msg, [data]) : this(false, msg, data); 173 | 174 | @override 175 | String toString() { 176 | return 'ResMsg:$success $msg $data'; 177 | } 178 | } 179 | 180 | /// 使用ResMsg显示对话窗口 181 | Future confirmResMsg(context, ResMsg msg, [String? title]) { 182 | return confirm( 183 | context, 184 | title: title, 185 | content: msg.msg, 186 | type: msg.success ? ConfirmType.success : ConfirmType.danger, 187 | ); 188 | } 189 | 190 | class _ConfirmDialog extends StatelessWidget { 191 | final ConfirmType type; 192 | final String? title; 193 | final String? content; 194 | final Widget Function(BuildContext)? contentWidgetBuilder; 195 | final double width; 196 | final String? ok; 197 | final String? cancel; 198 | final bool onlyCloseButton; 199 | final bool onlyContent; 200 | final bool showHeader; 201 | final Future Function()? onWillConfirm; 202 | 203 | const _ConfirmDialog({ 204 | Key? key, 205 | this.title, 206 | this.content, 207 | this.ok, 208 | this.cancel, 209 | this.type = ConfirmType.info, 210 | this.width = 300, 211 | this.onlyCloseButton = false, 212 | this.contentWidgetBuilder, 213 | this.onWillConfirm, 214 | this.onlyContent = false, 215 | this.showHeader = true, 216 | }) : super(key: key); 217 | 218 | @override 219 | Widget build(BuildContext context) { 220 | IconData? iconData; 221 | Color? color; 222 | switch (type) { 223 | case ConfirmType.danger: 224 | iconData = Icons.info; 225 | color = ColorPlate.red; 226 | break; 227 | case ConfirmType.info: 228 | iconData = Icons.info; 229 | color = ColorPlate.mainBlue; 230 | break; 231 | case ConfirmType.success: 232 | iconData = Icons.info_outline; 233 | color = ColorPlate.green; 234 | break; 235 | case ConfirmType.warning: 236 | iconData = Icons.warning; 237 | color = Colors.orange; 238 | break; 239 | } 240 | 241 | Widget icon = Container( 242 | padding: const EdgeInsets.only(right: 8), 243 | child: Icon( 244 | iconData, 245 | color: color, 246 | size: 30, 247 | ), 248 | ); 249 | 250 | return SimpleDialog( 251 | shape: RoundedRectangleBorder( 252 | borderRadius: BorderRadius.circular(10), 253 | ), 254 | titlePadding: const EdgeInsets.fromLTRB(20, 16, 20, 0), 255 | contentPadding: const EdgeInsets.fromLTRB(2, 0, 2, 8), 256 | title: showHeader 257 | ? Row( 258 | children: [ 259 | Expanded( 260 | child: StText.medium( 261 | title ?? textOfConfirmType(type), 262 | style: const TextStyle( 263 | fontSize: 20, 264 | ), 265 | ), 266 | ), 267 | icon, 268 | ], 269 | ) 270 | : null, 271 | children: [ 272 | contentWidgetBuilder?.call(context) ?? 273 | Container( 274 | padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 20), 275 | width: width, 276 | constraints: const BoxConstraints(minHeight: 120), 277 | child: StText.normal(content ?? ''), 278 | ), 279 | if (!onlyContent) 280 | Container( 281 | width: width, 282 | margin: const EdgeInsets.symmetric(horizontal: 6), 283 | child: Row( 284 | children: [ 285 | Expanded( 286 | child: _DialogButton( 287 | title: onlyCloseButton ? closeText : (cancel ?? cancelText), 288 | pimary: false, 289 | onTap: () { 290 | Navigator.of(context).pop(onlyCloseButton ? null : false); 291 | }, 292 | ), 293 | ), 294 | onlyCloseButton 295 | ? Container() 296 | : Expanded( 297 | child: _DialogButton( 298 | title: ok ?? okText, 299 | color: color, 300 | onTap: () async { 301 | var refuse = await onWillConfirm?.call(); 302 | if (refuse == false) { 303 | return; 304 | } 305 | Navigator.of(context).pop(true); 306 | }, 307 | ), 308 | ), 309 | ], 310 | ), 311 | ) 312 | ], 313 | ); 314 | } 315 | } 316 | 317 | class _DialogButton extends StatelessWidget { 318 | final Function? onTap; 319 | final String? title; 320 | final Color? color; 321 | final bool pimary; 322 | final double? height; 323 | final double? width; 324 | 325 | const _DialogButton({ 326 | Key? key, 327 | this.onTap, 328 | this.title, 329 | this.pimary = true, 330 | this.color, 331 | this.height, 332 | this.width, 333 | }) : super(key: key); 334 | 335 | @override 336 | Widget build(BuildContext context) { 337 | BoxDecoration d; 338 | if (pimary) { 339 | d = BoxDecoration( 340 | color: color, 341 | borderRadius: BorderRadius.circular(6), 342 | ); 343 | } else { 344 | d = BoxDecoration( 345 | color: ColorPlate.lightGray, 346 | borderRadius: BorderRadius.circular(6), 347 | ); 348 | } 349 | return Tapped( 350 | onTap: onTap, 351 | child: Container( 352 | height: height ?? 36, 353 | width: width ?? 368, 354 | margin: const EdgeInsets.fromLTRB(6, 8, 6, 8), 355 | alignment: Alignment.center, 356 | decoration: d, 357 | child: StText.medium( 358 | title, 359 | style: TextStyle( 360 | height: oneLineH, 361 | color: pimary ? ColorPlate.white : null, 362 | ), 363 | ), 364 | ), 365 | ); 366 | } 367 | } 368 | 369 | class StInput extends StatefulWidget { 370 | final TextEditingController? controller; 371 | final String? hintText; 372 | final bool enabled; 373 | final bool isPassword; 374 | final bool onlyNumber; 375 | final int maxLength; 376 | final TextInputAction? textInputAction; 377 | final ValueChanged? onSubmitted; 378 | final TextInputType? inputType; 379 | final FocusNode? focusNode; 380 | final TextAlign textAlign; 381 | final EdgeInsets? contentPadding; 382 | final bool? clearable; 383 | final bool? autofocus; 384 | 385 | const StInput({ 386 | Key? key, 387 | this.controller, 388 | this.hintText, 389 | this.enabled = true, 390 | this.isPassword = false, 391 | this.textInputAction, 392 | this.onSubmitted, 393 | this.focusNode, 394 | this.inputType, 395 | this.maxLength = 20, 396 | this.textAlign = TextAlign.start, 397 | this.onlyNumber = false, 398 | this.contentPadding, 399 | this.clearable, 400 | this.autofocus, 401 | }) : super(key: key); 402 | 403 | StInput.helper({ 404 | Key? key, 405 | InputHelper? helper, 406 | this.hintText, 407 | this.enabled = true, 408 | this.isPassword = false, 409 | this.textInputAction, 410 | this.onSubmitted, 411 | this.inputType, 412 | this.maxLength = 20, 413 | this.textAlign = TextAlign.start, 414 | this.onlyNumber = false, 415 | this.contentPadding, 416 | this.clearable, 417 | this.autofocus, 418 | }) : controller = helper?.controller, 419 | focusNode = helper?.focusNode, 420 | super(key: key); 421 | 422 | @override 423 | _StInputState createState() => _StInputState(); 424 | } 425 | 426 | class _StInputState extends State { 427 | bool get hasClearBtn => 428 | widget.controller?.text.isNotEmpty == true && 429 | widget.focusNode!.hasFocus && 430 | (widget.clearable ?? false); 431 | 432 | @override 433 | void initState() { 434 | super.initState(); 435 | widget.controller?.addListener(update); 436 | } 437 | 438 | void update() => setState(() {}); 439 | 440 | @override 441 | void dispose() { 442 | widget.controller?.removeListener(update); 443 | super.dispose(); 444 | } 445 | 446 | @override 447 | Widget build(BuildContext context) { 448 | return TextField( 449 | focusNode: widget.focusNode, 450 | controller: widget.controller, 451 | enabled: widget.enabled, 452 | obscureText: widget.isPassword, 453 | keyboardType: widget.inputType, 454 | autofocus: widget.autofocus ?? false, 455 | keyboardAppearance: Brightness.light, 456 | textInputAction: widget.textInputAction, 457 | textAlign: widget.textAlign, 458 | inputFormatters: [ 459 | widget.maxLength == 0 460 | ? LengthLimitingTextInputFormatter(999) 461 | : LengthLimitingTextInputFormatter(widget.maxLength), //限制长度 462 | ] + 463 | (widget.onlyNumber 464 | ? [ 465 | FilteringTextInputFormatter.allow(RegExp("[0-9]")), 466 | ] 467 | : []), 468 | onSubmitted: widget.onSubmitted, 469 | decoration: InputDecoration( 470 | isDense: true, 471 | hintText: widget.hintText ?? '##Hint Text##', 472 | contentPadding: widget.contentPadding ?? 473 | const EdgeInsets.symmetric( 474 | horizontal: 6, 475 | vertical: 8, 476 | ), 477 | // border: enabled == false ? InputBorder.none : null, 478 | suffixIconConstraints: const BoxConstraints( 479 | minHeight: 26, 480 | ), 481 | suffixIcon: hasClearBtn == true 482 | ? Tapped( 483 | onTap: () { 484 | if (widget.controller != null) { 485 | widget.controller!.text = ''; 486 | } 487 | }, 488 | child: Container( 489 | margin: const EdgeInsets.only(right: 2), 490 | color: ColorPlate.clear, 491 | padding: const EdgeInsets.symmetric( 492 | horizontal: 8, 493 | ), 494 | child: const Icon( 495 | Icons.cancel, 496 | color: ColorPlate.gray, 497 | size: 20, 498 | ), 499 | ), 500 | ) 501 | : null, 502 | border: InputBorder.none, 503 | 504 | hintStyle: TextStyle( 505 | height: oneLineH, 506 | fontSize: SysSize.small, 507 | color: ColorPlate.gray, 508 | ), 509 | ), 510 | ); 511 | } 512 | } 513 | 514 | class StPwInput extends StatefulWidget { 515 | final TextEditingController? controller; 516 | final String? hintText; 517 | final bool enabled; 518 | final int maxLength; 519 | final bool onlyNumAndEn; 520 | final TextInputAction? textInputAction; 521 | final ValueChanged? onSubmitted; 522 | final FocusNode? focusNode; 523 | final TextInputType? inputType; 524 | 525 | const StPwInput({ 526 | Key? key, 527 | this.controller, 528 | this.hintText, 529 | this.enabled = true, 530 | this.textInputAction, 531 | this.onSubmitted, 532 | this.focusNode, 533 | this.inputType, 534 | this.onlyNumAndEn = false, 535 | this.maxLength = 20, 536 | }) : super(key: key); 537 | 538 | @override 539 | _StPwInputState createState() => _StPwInputState(); 540 | } 541 | 542 | class _StPwInputState extends State { 543 | bool showPassword = false; 544 | 545 | @override 546 | Widget build(BuildContext context) { 547 | return Stack( 548 | alignment: Alignment.centerRight, 549 | children: [ 550 | TextField( 551 | focusNode: widget.focusNode, 552 | controller: widget.controller, 553 | enabled: widget.enabled, 554 | obscureText: !showPassword, 555 | keyboardAppearance: Brightness.light, 556 | keyboardType: widget.inputType, 557 | textInputAction: widget.textInputAction, 558 | onSubmitted: widget.onSubmitted, 559 | inputFormatters: [ 560 | // WhitelistingTextInputFormatter.digitsOnly, //只输入数字 561 | widget.maxLength == 0 562 | ? LengthLimitingTextInputFormatter(999) 563 | : LengthLimitingTextInputFormatter(widget.maxLength), //限制长度 564 | ] + 565 | (widget.onlyNumAndEn 566 | ? [ 567 | FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]")), 568 | ] 569 | : []), 570 | decoration: InputDecoration( 571 | isDense: true, 572 | hintText: widget.hintText ?? '##Hint Text##', 573 | contentPadding: const EdgeInsets.symmetric( 574 | horizontal: 6, 575 | ), 576 | border: widget.enabled == false ? InputBorder.none : null, 577 | hintStyle: const TextStyle( 578 | fontWeight: FontWeight.w600, 579 | fontSize: 16, 580 | color: ColorPlate.gray, 581 | ), 582 | ), 583 | ), 584 | Tapped( 585 | onTap: () { 586 | setState(() => showPassword = !showPassword); 587 | }, 588 | child: Container( 589 | height: 40, 590 | width: 40, 591 | color: ColorPlate.clear, 592 | child: Center( 593 | child: Icon( 594 | Icons.remove_red_eye, 595 | color: showPassword 596 | ? ColorPlate.mainBlue 597 | : ColorPlate.gray.withOpacity(0.5), 598 | ), 599 | ), 600 | ), 601 | ) 602 | ], 603 | ); 604 | } 605 | } 606 | 607 | class StTextField extends StatelessWidget { 608 | final InputHelper? helper; 609 | final String? hintText; 610 | final bool? autofocus; 611 | final int? max; 612 | final int? minLines; 613 | final int? maxLines; 614 | final EdgeInsets? margin; 615 | final Widget? Function( 616 | BuildContext context, { 617 | required int currentLength, 618 | required int? maxLength, 619 | required bool isFocused, 620 | })? buildCounter; 621 | const StTextField({ 622 | Key? key, 623 | this.helper, 624 | this.hintText, 625 | this.margin, 626 | this.autofocus, 627 | this.max = 900, 628 | this.minLines, 629 | this.maxLines, 630 | this.buildCounter, 631 | }) : super(key: key); 632 | 633 | @override 634 | Widget build(BuildContext context) { 635 | return Container( 636 | margin: margin ?? const EdgeInsets.fromLTRB(12, 10, 12, 8), 637 | child: ClipRRect( 638 | borderRadius: BorderRadius.circular(6), 639 | child: Container( 640 | color: ColorPlate.lightGray, 641 | child: TextField( 642 | focusNode: helper?.focusNode, 643 | controller: helper?.controller, 644 | autofocus: autofocus == true, 645 | keyboardAppearance: Brightness.light, 646 | minLines: minLines ?? 3, 647 | maxLines: maxLines ?? 20, 648 | maxLength: max, 649 | keyboardType: TextInputType.multiline, 650 | inputFormatters: [ 651 | LengthLimitingTextInputFormatter(max), 652 | ], 653 | decoration: InputDecoration( 654 | isDense: true, 655 | hintText: hintText ?? '请填写内容', 656 | contentPadding: const EdgeInsets.symmetric( 657 | horizontal: 12, 658 | vertical: 8, 659 | ), 660 | border: InputBorder.none, 661 | hintStyle: TextStyle( 662 | height: oneLineH, 663 | fontSize: SysSize.normal, 664 | color: ColorPlate.gray, 665 | ), 666 | ), 667 | buildCounter: buildCounter, 668 | ), 669 | ), 670 | ), 671 | ); 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import flutter_p2p_engine 9 | import path_provider_foundation 10 | import video_player_avfoundation 11 | 12 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 13 | FlutterP2pEnginePlugin.register(with: registry.registrar(forPlugin: "FlutterP2pEnginePlugin")) 14 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 15 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 16 | } 17 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 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/macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - flutter_p2p_engine (3.2.5): 3 | - Flutter 4 | - FlutterMacOS 5 | - SwarmCloudKit (= 3.2.5) 6 | - FlutterMacOS (1.0.0) 7 | - path_provider_foundation (0.0.1): 8 | - Flutter 9 | - FlutterMacOS 10 | - SwarmCloudKit (3.2.5) 11 | - video_player_avfoundation (0.0.1): 12 | - Flutter 13 | - FlutterMacOS 14 | 15 | DEPENDENCIES: 16 | - flutter_p2p_engine (from `Flutter/ephemeral/.symlinks/plugins/flutter_p2p_engine/darwin`) 17 | - FlutterMacOS (from `Flutter/ephemeral`) 18 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) 19 | - video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) 20 | 21 | SPEC REPOS: 22 | trunk: 23 | - SwarmCloudKit 24 | 25 | EXTERNAL SOURCES: 26 | flutter_p2p_engine: 27 | :path: Flutter/ephemeral/.symlinks/plugins/flutter_p2p_engine/darwin 28 | FlutterMacOS: 29 | :path: Flutter/ephemeral 30 | path_provider_foundation: 31 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin 32 | video_player_avfoundation: 33 | :path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin 34 | 35 | SPEC CHECKSUMS: 36 | flutter_p2p_engine: 1276c2ca91cc75c3b4cf9da6787498c43126ac59 37 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 38 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 39 | SwarmCloudKit: e73eb9e0de2cd02ac464d9cacc681400ab1f3752 40 | video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 41 | 42 | PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 43 | 44 | COCOAPODS: 1.14.3 45 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdnbye/flutter-p2p-engine/2a59f08cd402e300dacf54baae3d95af34886b25/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = flutter_p2p_engine_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.swarmcloud.flutterP2pEngineExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.swarmcloud. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.network.client 6 | 7 | com.apple.security.app-sandbox 8 | 9 | com.apple.security.cs.allow-jit 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIconFile 15 | 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | $(PRODUCT_NAME) 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | $(FLUTTER_BUILD_NAME) 26 | CFBundleVersion 27 | $(FLUTTER_BUILD_NUMBER) 28 | LSMinimumSystemVersion 29 | $(MACOSX_DEPLOYMENT_TARGET) 30 | NSHumanReadableCopyright 31 | $(PRODUCT_COPYRIGHT) 32 | NSMainNibFile 33 | MainMenu 34 | NSPrincipalClass 35 | NSApplication 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.network.client 6 | 7 | com.apple.security.app-sandbox 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | @testable import flutter_p2p_engine 6 | 7 | // This demonstrates a simple unit test of the Swift portion of this plugin's implementation. 8 | // 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | 11 | class RunnerTests: XCTestCase { 12 | 13 | func testGetPlatformVersion() { 14 | let plugin = FlutterP2pEnginePlugin() 15 | 16 | let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) 17 | 18 | let resultExpectation = expectation(description: "result block must be called.") 19 | plugin.handle(call) { result in 20 | XCTAssertEqual(result as! String, 21 | "macOS " + ProcessInfo.processInfo.operatingSystemVersionString) 22 | resultExpectation.fulfill() 23 | } 24 | waitForExpectations(timeout: 1) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.flutter-io.cn" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.flutter-io.cn" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.flutter-io.cn" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.flutter-io.cn" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.flutter-io.cn" 42 | source: hosted 43 | version: "1.18.0" 44 | csslib: 45 | dependency: transitive 46 | description: 47 | name: csslib 48 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 49 | url: "https://pub.flutter-io.cn" 50 | source: hosted 51 | version: "1.0.0" 52 | cupertino_icons: 53 | dependency: "direct main" 54 | description: 55 | name: cupertino_icons 56 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.0.8" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 65 | url: "https://pub.flutter-io.cn" 66 | source: hosted 67 | version: "1.3.1" 68 | ffi: 69 | dependency: transitive 70 | description: 71 | name: ffi 72 | sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" 73 | url: "https://pub.flutter-io.cn" 74 | source: hosted 75 | version: "2.1.2" 76 | file: 77 | dependency: transitive 78 | description: 79 | name: file 80 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "7.0.0" 84 | flutter: 85 | dependency: "direct main" 86 | description: flutter 87 | source: sdk 88 | version: "0.0.0" 89 | flutter_driver: 90 | dependency: transitive 91 | description: flutter 92 | source: sdk 93 | version: "0.0.0" 94 | flutter_lints: 95 | dependency: "direct dev" 96 | description: 97 | name: flutter_lints 98 | sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "2.0.3" 102 | flutter_p2p_engine: 103 | dependency: "direct main" 104 | description: 105 | path: ".." 106 | relative: true 107 | source: path 108 | version: "1.2.7" 109 | flutter_test: 110 | dependency: "direct dev" 111 | description: flutter 112 | source: sdk 113 | version: "0.0.0" 114 | flutter_web_plugins: 115 | dependency: transitive 116 | description: flutter 117 | source: sdk 118 | version: "0.0.0" 119 | fuchsia_remote_debug_protocol: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.0" 124 | html: 125 | dependency: transitive 126 | description: 127 | name: html 128 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 129 | url: "https://pub.flutter-io.cn" 130 | source: hosted 131 | version: "0.15.4" 132 | integration_test: 133 | dependency: "direct dev" 134 | description: flutter 135 | source: sdk 136 | version: "0.0.0" 137 | leak_tracker: 138 | dependency: transitive 139 | description: 140 | name: leak_tracker 141 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "10.0.5" 145 | leak_tracker_flutter_testing: 146 | dependency: transitive 147 | description: 148 | name: leak_tracker_flutter_testing 149 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 150 | url: "https://pub.flutter-io.cn" 151 | source: hosted 152 | version: "3.0.5" 153 | leak_tracker_testing: 154 | dependency: transitive 155 | description: 156 | name: leak_tracker_testing 157 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 158 | url: "https://pub.flutter-io.cn" 159 | source: hosted 160 | version: "3.0.1" 161 | lints: 162 | dependency: transitive 163 | description: 164 | name: lints 165 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 166 | url: "https://pub.flutter-io.cn" 167 | source: hosted 168 | version: "2.1.1" 169 | matcher: 170 | dependency: transitive 171 | description: 172 | name: matcher 173 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 174 | url: "https://pub.flutter-io.cn" 175 | source: hosted 176 | version: "0.12.16+1" 177 | material_color_utilities: 178 | dependency: transitive 179 | description: 180 | name: material_color_utilities 181 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 182 | url: "https://pub.flutter-io.cn" 183 | source: hosted 184 | version: "0.11.1" 185 | meta: 186 | dependency: transitive 187 | description: 188 | name: meta 189 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 190 | url: "https://pub.flutter-io.cn" 191 | source: hosted 192 | version: "1.15.0" 193 | path: 194 | dependency: transitive 195 | description: 196 | name: path 197 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 198 | url: "https://pub.flutter-io.cn" 199 | source: hosted 200 | version: "1.9.0" 201 | path_provider: 202 | dependency: "direct main" 203 | description: 204 | name: path_provider 205 | sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 206 | url: "https://pub.flutter-io.cn" 207 | source: hosted 208 | version: "2.1.3" 209 | path_provider_android: 210 | dependency: transitive 211 | description: 212 | name: path_provider_android 213 | sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a 214 | url: "https://pub.flutter-io.cn" 215 | source: hosted 216 | version: "2.2.6" 217 | path_provider_foundation: 218 | dependency: transitive 219 | description: 220 | name: path_provider_foundation 221 | sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 222 | url: "https://pub.flutter-io.cn" 223 | source: hosted 224 | version: "2.4.0" 225 | path_provider_linux: 226 | dependency: transitive 227 | description: 228 | name: path_provider_linux 229 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 230 | url: "https://pub.flutter-io.cn" 231 | source: hosted 232 | version: "2.2.1" 233 | path_provider_platform_interface: 234 | dependency: transitive 235 | description: 236 | name: path_provider_platform_interface 237 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 238 | url: "https://pub.flutter-io.cn" 239 | source: hosted 240 | version: "2.1.2" 241 | path_provider_windows: 242 | dependency: transitive 243 | description: 244 | name: path_provider_windows 245 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" 246 | url: "https://pub.flutter-io.cn" 247 | source: hosted 248 | version: "2.2.1" 249 | platform: 250 | dependency: transitive 251 | description: 252 | name: platform 253 | sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" 254 | url: "https://pub.flutter-io.cn" 255 | source: hosted 256 | version: "3.1.5" 257 | plugin_platform_interface: 258 | dependency: transitive 259 | description: 260 | name: plugin_platform_interface 261 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 262 | url: "https://pub.flutter-io.cn" 263 | source: hosted 264 | version: "2.1.8" 265 | process: 266 | dependency: transitive 267 | description: 268 | name: process 269 | sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" 270 | url: "https://pub.flutter-io.cn" 271 | source: hosted 272 | version: "5.0.2" 273 | safemap: 274 | dependency: "direct main" 275 | description: 276 | name: safemap 277 | sha256: "6525179e412bfd664e59f57023b9cf3a83583e53aa5a8c3039b094ea41ee6b60" 278 | url: "https://pub.flutter-io.cn" 279 | source: hosted 280 | version: "2.1.0" 281 | sky_engine: 282 | dependency: transitive 283 | description: flutter 284 | source: sdk 285 | version: "0.0.99" 286 | source_span: 287 | dependency: transitive 288 | description: 289 | name: source_span 290 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 291 | url: "https://pub.flutter-io.cn" 292 | source: hosted 293 | version: "1.10.0" 294 | stack_trace: 295 | dependency: transitive 296 | description: 297 | name: stack_trace 298 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 299 | url: "https://pub.flutter-io.cn" 300 | source: hosted 301 | version: "1.11.1" 302 | stream_channel: 303 | dependency: transitive 304 | description: 305 | name: stream_channel 306 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 307 | url: "https://pub.flutter-io.cn" 308 | source: hosted 309 | version: "2.1.2" 310 | string_scanner: 311 | dependency: transitive 312 | description: 313 | name: string_scanner 314 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 315 | url: "https://pub.flutter-io.cn" 316 | source: hosted 317 | version: "1.2.0" 318 | sync_http: 319 | dependency: transitive 320 | description: 321 | name: sync_http 322 | sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" 323 | url: "https://pub.flutter-io.cn" 324 | source: hosted 325 | version: "0.3.1" 326 | tapped: 327 | dependency: "direct main" 328 | description: 329 | name: tapped 330 | sha256: "27a01d5c80d0e33100f62514dfcca43d933984822d852c84a7664943b3cf1b30" 331 | url: "https://pub.flutter-io.cn" 332 | source: hosted 333 | version: "2.0.0" 334 | term_glyph: 335 | dependency: transitive 336 | description: 337 | name: term_glyph 338 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 339 | url: "https://pub.flutter-io.cn" 340 | source: hosted 341 | version: "1.2.1" 342 | test_api: 343 | dependency: transitive 344 | description: 345 | name: test_api 346 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 347 | url: "https://pub.flutter-io.cn" 348 | source: hosted 349 | version: "0.7.2" 350 | vector_math: 351 | dependency: transitive 352 | description: 353 | name: vector_math 354 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 355 | url: "https://pub.flutter-io.cn" 356 | source: hosted 357 | version: "2.1.4" 358 | video_player: 359 | dependency: "direct main" 360 | description: 361 | name: video_player 362 | sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d 363 | url: "https://pub.flutter-io.cn" 364 | source: hosted 365 | version: "2.9.1" 366 | video_player_android: 367 | dependency: transitive 368 | description: 369 | name: video_player_android 370 | sha256: fdc0331ce9f808cc2714014cb8126bd6369943affefd54f8fdab0ea0bb617b7f 371 | url: "https://pub.flutter-io.cn" 372 | source: hosted 373 | version: "2.5.2" 374 | video_player_avfoundation: 375 | dependency: transitive 376 | description: 377 | name: video_player_avfoundation 378 | sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c 379 | url: "https://pub.flutter-io.cn" 380 | source: hosted 381 | version: "2.6.1" 382 | video_player_platform_interface: 383 | dependency: transitive 384 | description: 385 | name: video_player_platform_interface 386 | sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" 387 | url: "https://pub.flutter-io.cn" 388 | source: hosted 389 | version: "6.2.2" 390 | video_player_web: 391 | dependency: transitive 392 | description: 393 | name: video_player_web 394 | sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2 395 | url: "https://pub.flutter-io.cn" 396 | source: hosted 397 | version: "2.3.1" 398 | vm_service: 399 | dependency: transitive 400 | description: 401 | name: vm_service 402 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" 403 | url: "https://pub.flutter-io.cn" 404 | source: hosted 405 | version: "14.2.5" 406 | web: 407 | dependency: transitive 408 | description: 409 | name: web 410 | sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" 411 | url: "https://pub.flutter-io.cn" 412 | source: hosted 413 | version: "0.5.1" 414 | webdriver: 415 | dependency: transitive 416 | description: 417 | name: webdriver 418 | sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" 419 | url: "https://pub.flutter-io.cn" 420 | source: hosted 421 | version: "3.0.3" 422 | win32: 423 | dependency: transitive 424 | description: 425 | name: win32 426 | sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 427 | url: "https://pub.flutter-io.cn" 428 | source: hosted 429 | version: "5.5.1" 430 | xdg_directories: 431 | dependency: transitive 432 | description: 433 | name: xdg_directories 434 | sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d 435 | url: "https://pub.flutter-io.cn" 436 | source: hosted 437 | version: "1.0.4" 438 | sdks: 439 | dart: ">=3.4.0 <4.0.0" 440 | flutter: ">=3.22.0" 441 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_p2p_engine_example 2 | description: Demonstrates how to use the flutter_p2p_engine plugin. 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | 6 | environment: 7 | sdk: '>=3.1.2 <4.0.0' 8 | 9 | # Dependencies specify other packages that your package needs in order to work. 10 | # To automatically upgrade your package dependencies to the latest versions 11 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 12 | # dependencies can be manually updated by changing the version numbers below to 13 | # the latest version available on pub.dev. To see which dependencies have newer 14 | # versions available, run `flutter pub outdated`. 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | flutter_p2p_engine: 20 | # When depending on this package from a real application you should use: 21 | # flutter_p2p_engine: ^x.y.z 22 | # See https://dart.dev/tools/pub/dependencies#version-constraints 23 | # The example app is bundled with the plugin so we use a path dependency on 24 | # the parent directory to use the current plugin's version. 25 | path: ../ 26 | 27 | # The following adds the Cupertino Icons font to your application. 28 | # Use with the CupertinoIcons class for iOS style icons. 29 | cupertino_icons: ^1.0.2 30 | tapped: ^2.0.0 31 | safemap: ^2.0.0 32 | path_provider: ^2.0.1 33 | video_player: ^2.8.5 34 | 35 | dev_dependencies: 36 | integration_test: 37 | sdk: flutter 38 | flutter_test: 39 | sdk: flutter 40 | 41 | # The "flutter_lints" package below contains a set of recommended lints to 42 | # encourage good coding practices. The lint set provided by the package is 43 | # activated in the `analysis_options.yaml` file located at the root of your 44 | # package. See that file for information about deactivating specific lint 45 | # rules and activating additional ones. 46 | flutter_lints: ^2.0.0 47 | 48 | # For information on the generic Dart part of this file, see the 49 | # following page: https://dart.dev/tools/pub/pubspec 50 | 51 | # The following section is specific to Flutter packages. 52 | flutter: 53 | 54 | # The following line ensures that the Material Icons font is 55 | # included with your application, so that you can use the icons in 56 | # the material Icons class. 57 | uses-material-design: true 58 | 59 | # To add assets to your application, add an assets section, like this: 60 | # assets: 61 | # - images/a_dot_burr.jpeg 62 | # - images/a_dot_ham.jpeg 63 | 64 | # An image asset can refer to one or more resolution-specific "variants", see 65 | # https://flutter.dev/assets-and-images/#resolution-aware 66 | 67 | # For details regarding adding assets from package dependencies, see 68 | # https://flutter.dev/assets-and-images/#from-packages 69 | 70 | # To add custom fonts to your application, add a fonts section here, 71 | # in this "flutter" section. Each entry in this list should have a 72 | # "family" key with the font family name, and a "fonts" key with a 73 | # list giving the asset and other descriptors for the font. For 74 | # example: 75 | # fonts: 76 | # - family: Schyler 77 | # fonts: 78 | # - asset: fonts/Schyler-Regular.ttf 79 | # - asset: fonts/Schyler-Italic.ttf 80 | # style: italic 81 | # - family: Trajan Pro 82 | # fonts: 83 | # - asset: fonts/TrajanPro.ttf 84 | # - asset: fonts/TrajanPro_Bold.ttf 85 | # weight: 700 86 | # 87 | # For details regarding fonts from package dependencies, 88 | # see https://flutter.dev/custom-fonts/#from-packages 89 | -------------------------------------------------------------------------------- /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:flutter_p2p_engine_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 VideoApp()); 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 | -------------------------------------------------------------------------------- /lib/flutter_p2p_engine.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'flutter_p2p_engine_platform_interface.dart'; 3 | 4 | const String version = "1.2.9"; 5 | 6 | class FlutterP2pEngine { 7 | static FlutterP2pEnginePlatform get _platform => FlutterP2pEnginePlatform.instance; 8 | 9 | /// Create a new instance with token and the specified config. 10 | static Future init( 11 | token, { 12 | required P2pConfig config, 13 | void Function(Map)? infoListener, 14 | bufferedDurationGeneratorEnable = false, 15 | }) => 16 | _platform.init( 17 | token, 18 | config: config, 19 | infoListener: infoListener, 20 | bufferedDurationGeneratorEnable: bufferedDurationGeneratorEnable 21 | ); 22 | 23 | /// Get parsed local stream url by passing the original stream url(m3u8) to CBP2pEngine instance. 24 | static Future parseStreamURL( 25 | String sourceUrl, { 26 | String? videoId, 27 | Duration Function()? bufferedDurationGenerator, 28 | }) => 29 | _platform.parseStreamURL( 30 | sourceUrl, 31 | videoId: videoId, 32 | bufferedDurationGenerator: bufferedDurationGenerator, 33 | ); 34 | 35 | /// Get the connection state of p2p engine. 获取P2P Engine的连接状态 36 | static Future isConnected() => _platform.isConnected(); 37 | 38 | /// Restart p2p engine. 39 | static Future restartP2p() => _platform.restartP2p(); 40 | 41 | /// Stop p2p and free used resources. 42 | static Future stopP2p() => _platform.stopP2p(); 43 | 44 | /// Get the peer ID of p2p engine. 获取P2P Engine的peer ID 45 | static Future getPeerId() => _platform.getPeerId(); 46 | 47 | static Future getSDKVersion() => _platform.getSDKVersion(); 48 | 49 | static Future notifyPlaybackStalled() => _platform.notifyPlaybackStalled(); 50 | 51 | static Future setHttpHeadersForHls(Map? headers) => _platform.setHttpHeadersForHls(headers); 52 | 53 | static Future setHttpHeadersForDash(Map? headers) => _platform.setHttpHeadersForDash(headers); 54 | 55 | static Future shutdown() => _platform.shutdown(); 56 | } 57 | 58 | /// Print log level. 59 | enum P2pLogLevel { 60 | none, 61 | debug, 62 | info, 63 | warn, 64 | error, 65 | } 66 | 67 | /// tracker服务器地址所在国家的枚举 68 | enum TrackerZone { 69 | Europe, 70 | HongKong, 71 | USA, 72 | China, 73 | } 74 | 75 | /// The configuration of p2p engine. 76 | class P2pConfig { 77 | final P2pLogLevel logLevel; 78 | final bool logEnabled; 79 | final String? signalConfig; 80 | final String? announce; 81 | final int? diskCacheLimit; 82 | final int? memoryCacheCountLimit; 83 | final bool p2pEnabled; 84 | final Duration? downloadTimeout; 85 | final Duration? dcDownloadTimeout; 86 | final String? tag; 87 | final int localPortHls; 88 | final int localPortDash; 89 | final int? maxPeerConnections; 90 | final bool useHttpRange; 91 | final bool wifiOnly; 92 | final bool prefetchOnly; 93 | final bool downloadOnly; 94 | final Map? httpHeadersForHls; 95 | final Map? httpHeadersForDash; 96 | final bool isSetTopBox; 97 | final bool sharePlaylist; 98 | final bool logPersistent; 99 | final bool? fastStartup; 100 | final bool? geoIpPreflight; 101 | final bool? useStrictHlsSegmentId; 102 | final List? dashMediaFiles; 103 | final double? playlistTimeOffset; 104 | final int? maxMediaFilesInPlaylist; 105 | final TrackerZone? trackerZone; 106 | 107 | P2pConfig({ 108 | this.logLevel = P2pLogLevel.warn, 109 | this.signalConfig, //: 'wss://signal.cdnbye.com', 110 | this.announce, //: 'https://tracker.cdnbye.com/v1', 111 | this.diskCacheLimit, 112 | this.memoryCacheCountLimit, 113 | this.p2pEnabled = true, 114 | this.downloadTimeout, 115 | this.dcDownloadTimeout, 116 | this.tag = "flutter-$version", 117 | this.localPortHls = 0, 118 | this.localPortDash = 0, 119 | this.maxPeerConnections, 120 | this.useHttpRange = true, 121 | this.wifiOnly = false, 122 | this.prefetchOnly = false, 123 | this.downloadOnly = false, 124 | this.httpHeadersForHls, 125 | this.httpHeadersForDash, 126 | this.isSetTopBox = false, 127 | this.logEnabled = false, 128 | this.logPersistent = false, 129 | this.sharePlaylist = false, 130 | this.dashMediaFiles, 131 | this.trackerZone, 132 | this.playlistTimeOffset, 133 | this.maxMediaFilesInPlaylist, 134 | this.fastStartup, 135 | this.geoIpPreflight, 136 | this.useStrictHlsSegmentId, 137 | }); 138 | 139 | P2pConfig.byDefault() : this(); 140 | 141 | Map get toMap => { 142 | 'logEnabled': logEnabled, 143 | 'logLevel': logLevel.index, 144 | 'signalConfig': signalConfig, 145 | 'announce': announce, 146 | 'diskCacheLimit': diskCacheLimit, 147 | 'memoryCacheCountLimit': memoryCacheCountLimit, 148 | 'p2pEnabled': p2pEnabled, 149 | 'downloadTimeout': downloadTimeout?.inSeconds, 150 | 'dcDownloadTimeout': dcDownloadTimeout?.inSeconds, 151 | 'tag': tag, 152 | 'localPortHls': localPortHls, 153 | 'localPortDash': localPortDash, 154 | 'maxPeerConnections': maxPeerConnections, 155 | 'useHttpRange': useHttpRange, 156 | 'wifiOnly': wifiOnly, 157 | 'prefetchOnly': prefetchOnly, 158 | 'downloadOnly': downloadOnly, 159 | 'httpHeadersForHls': httpHeadersForHls ?? {}, 160 | 'httpHeadersForDash': httpHeadersForDash ?? {}, 161 | 'isSetTopBox': isSetTopBox, 162 | 'sharePlaylist': sharePlaylist, 163 | 'logPersistent': logPersistent, 164 | 'dashMediaFiles': dashMediaFiles, 165 | 'trackerZone': trackerZone?.index, 166 | 'playlistTimeOffset': playlistTimeOffset, 167 | 'maxMediaFilesInPlaylist': maxMediaFilesInPlaylist, 168 | 'fastStartup': fastStartup, 169 | 'geoIpPreflight': geoIpPreflight, 170 | 'useStrictHlsSegmentId': useStrictHlsSegmentId, 171 | }; 172 | } -------------------------------------------------------------------------------- /lib/flutter_p2p_engine_method_channel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'flutter_p2p_engine.dart'; 3 | import 'flutter_p2p_engine_platform_interface.dart'; 4 | 5 | /// An implementation of [FlutterP2pEnginePlatform] that uses method channels. 6 | class MethodChannelFlutterP2pEngine extends FlutterP2pEnginePlatform { 7 | /// The method channel used to interact with the native platform. 8 | final _channel = const MethodChannel('p2p_engine'); 9 | 10 | static Duration Function()? _bufferedDurationGenerator; 11 | 12 | static bool _bufferedDurationGeneratorEnable = false; 13 | 14 | static const EventChannel _eventChannel = EventChannel('p2p_engine_stats'); 15 | 16 | /// Create a new instance with token and the specified config. 17 | @override 18 | Future init( 19 | token, { 20 | required P2pConfig config, 21 | void Function(Map)? infoListener, 22 | bool bufferedDurationGeneratorEnable = false, // 是否可以给SDK提供缓冲前沿到当前播放时间的差值 23 | }) async { 24 | _bufferedDurationGeneratorEnable = bufferedDurationGeneratorEnable; 25 | final int? success = await _channel.invokeMethod('init', { 26 | 'token': token, 27 | 'config': config.toMap, 28 | 'enableBufferedDurationGenerator': bufferedDurationGeneratorEnable, 29 | }); 30 | _channel.setMethodCallHandler((call) async { 31 | if (call.method == 'bufferedDuration') { 32 | var duration = _bufferedDurationGenerator?.call(); 33 | return {'result': duration?.inSeconds ?? -1}; 34 | } 35 | return {"success": true}; 36 | }); 37 | 38 | if (infoListener != null) { 39 | _eventChannel.receiveBroadcastStream().listen( 40 | (dynamic event) { 41 | var map = Map.from(event); 42 | infoListener.call(map); 43 | }, 44 | onError: (dynamic error) { 45 | // print('Received error: ${error.message}'); 46 | }, 47 | cancelOnError: true); 48 | } 49 | 50 | if (success == null) { 51 | throw 'Not Avaliable Result: $success. Init fail.'; 52 | } 53 | return success; 54 | } 55 | 56 | /// Get parsed local stream url by passing the original stream url(m3u8) to CBP2pEngine instance. 57 | @override 58 | Future parseStreamURL( 59 | String sourceUrl, { 60 | String? videoId, 61 | Duration Function()? bufferedDurationGenerator, 62 | }) async { 63 | if (_bufferedDurationGeneratorEnable && bufferedDurationGenerator == null) { 64 | throw 'Must provide bufferedDurationGenerator if bufferedDurationGeneratorEnable was set true'; 65 | } 66 | if (!_bufferedDurationGeneratorEnable && 67 | bufferedDurationGenerator != null) { 68 | throw 'Must set bufferedDurationGeneratorEnable true before set bufferedDurationGenerator'; 69 | } 70 | _bufferedDurationGenerator = bufferedDurationGenerator; 71 | final String url = await _channel.invokeMethod('parseStreamURL', { 72 | 'url': sourceUrl, 73 | 'videoId': videoId ?? sourceUrl, 74 | }); 75 | // print('mobile parse R:$url S:$sourceUrl '); 76 | return url; 77 | } 78 | 79 | /// Get the connection state of p2p engine. 获取P2P Engine的连接状态 80 | @override 81 | Future isConnected() async => 82 | (await _channel.invokeMethod('isConnected')) == true; 83 | 84 | /// Restart p2p engine. 85 | @override 86 | Future restartP2p() => _channel.invokeMethod('restartP2p'); 87 | 88 | @override 89 | Future disableP2p() => _channel.invokeMethod('disableP2p'); 90 | 91 | /// Stop p2p and free used resources. 92 | @override 93 | Future stopP2p() => _channel.invokeMethod('stopP2p'); 94 | 95 | @override 96 | Future enableP2p() => _channel.invokeMethod('enableP2p'); 97 | 98 | @override 99 | Future shutdown() => _channel.invokeMethod('shutdown'); 100 | 101 | @override 102 | Future notifyPlaybackStalled() => _channel.invokeMethod('notifyPlaybackStalled'); 103 | 104 | /// Get the peer ID of p2p engine. 获取P2P Engine的peer ID 105 | @override 106 | Future getPeerId() async => 107 | (await _channel.invokeMethod('getPeerId')) ?? ''; 108 | 109 | @override 110 | Future getSDKVersion() async => 111 | (await _channel.invokeMethod('getSDKVersion')) ?? ''; 112 | 113 | @override 114 | Future setHttpHeadersForHls(Map? headers) => 115 | _channel.invokeMethod('setHttpHeadersForHls', { 116 | 'headers': headers, 117 | }); 118 | 119 | @override 120 | Future setHttpHeadersForDash(Map? headers) => 121 | _channel.invokeMethod('setHttpHeadersForDash', { 122 | 'headers': headers, 123 | }); 124 | } 125 | -------------------------------------------------------------------------------- /lib/flutter_p2p_engine_platform_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 2 | 3 | import 'flutter_p2p_engine.dart'; 4 | import 'flutter_p2p_engine_method_channel.dart'; 5 | 6 | abstract class FlutterP2pEnginePlatform extends PlatformInterface { 7 | /// Constructs a P2pEnginePlatform. 8 | FlutterP2pEnginePlatform() : super(token: _token); 9 | 10 | static final Object _token = Object(); 11 | 12 | static FlutterP2pEnginePlatform _instance = MethodChannelFlutterP2pEngine(); 13 | 14 | /// The default instance of [P2pEnginePlatform] to use. 15 | /// 16 | /// Defaults to [MethodChannelP2pEngine]. 17 | static FlutterP2pEnginePlatform get instance => _instance; 18 | 19 | /// Platform-specific implementations should set this with their own 20 | /// platform-specific class that extends [P2pEnginePlatform] when 21 | /// they register themselves. 22 | static set instance(FlutterP2pEnginePlatform instance) { 23 | PlatformInterface.verifyToken(instance, _token); 24 | _instance = instance; 25 | } 26 | 27 | /// Create a new instance with token and the specified config. 28 | Future init( 29 | token, { 30 | required P2pConfig config, 31 | void Function(Map)? infoListener, 32 | bool bufferedDurationGeneratorEnable = false, // 是否可以给SDK提供缓冲前沿到当前播放时间的差值 33 | }) => 34 | _instance.init( 35 | token, 36 | config: config, 37 | infoListener: infoListener, 38 | bufferedDurationGeneratorEnable: bufferedDurationGeneratorEnable, 39 | ); 40 | 41 | /// Get parsed local stream url by passing the original stream url(m3u8) to CBP2pEngine instance. 42 | Future parseStreamURL( 43 | String sourceUrl, { 44 | String? videoId, 45 | Duration Function()? bufferedDurationGenerator, 46 | }) => 47 | _instance.parseStreamURL( 48 | sourceUrl, 49 | videoId: videoId, 50 | bufferedDurationGenerator: bufferedDurationGenerator, 51 | ); 52 | 53 | Future getSDKVersion() => _instance.getSDKVersion(); 54 | 55 | /// Get the connection state of p2p engine. 56 | Future isConnected() => _instance.isConnected(); 57 | 58 | /// Restart p2p engine. 59 | Future restartP2p() => _instance.restartP2p(); 60 | 61 | /// Stop p2p and free used resources. 62 | Future stopP2p() => _instance.stopP2p(); 63 | 64 | Future shutdown() => _instance.shutdown(); 65 | 66 | Future disableP2p() => _instance.disableP2p(); 67 | 68 | Future enableP2p() => _instance.enableP2p(); 69 | 70 | Future notifyPlaybackStalled() => _instance.notifyPlaybackStalled(); 71 | 72 | Future setHttpHeadersForHls(Map? headers) => _instance.setHttpHeadersForHls(headers); 73 | 74 | Future setHttpHeadersForDash(Map? headers) => _instance.setHttpHeadersForDash(headers); 75 | 76 | /// Get the peer ID of p2p engine 77 | Future getPeerId() => _instance.getPeerId(); 78 | } 79 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_p2p_engine 2 | description: SwarmCloud p2p engine for flutter 3 | version: 1.2.9 4 | homepage: https://www.cdnbye.com/en/ 5 | 6 | environment: 7 | sdk: '>=3.1.2 <4.0.0' 8 | flutter: '>=3.7.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | plugin_platform_interface: ^2.0.2 14 | video_player: ^2.8.5 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | flutter_lints: ^2.0.0 20 | 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://dart.dev/tools/pub/pubspec 23 | 24 | # The following section is specific to Flutter packages. 25 | flutter: 26 | # This section identifies this Flutter project as a plugin project. 27 | # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) 28 | # which should be registered in the plugin registry. This is required for 29 | # using method channels. 30 | # The Android 'package' specifies package in which the registered class is. 31 | # This is required for using method channels on Android. 32 | # The 'ffiPlugin' specifies that native code should be built and bundled. 33 | # This is required for using `dart:ffi`. 34 | # All these are used by the tooling to maintain consistency when 35 | # adding or updating assets for this project. 36 | plugin: 37 | platforms: 38 | android: 39 | package: com.swarmcloud.flutter_p2p_engine 40 | pluginClass: FlutterP2pEnginePlugin 41 | ios: 42 | pluginClass: FlutterP2pEnginePlugin 43 | sharedDarwinSource: true 44 | macos: 45 | pluginClass: FlutterP2pEnginePlugin 46 | sharedDarwinSource: true 47 | 48 | # To add assets to your plugin package, add an assets section, like this: 49 | # assets: 50 | # - images/a_dot_burr.jpeg 51 | # - images/a_dot_ham.jpeg 52 | # 53 | # For details regarding assets in packages, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | # 56 | # An image asset can refer to one or more resolution-specific "variants", see 57 | # https://flutter.dev/assets-and-images/#resolution-aware 58 | 59 | # To add custom fonts to your plugin package, add a fonts section here, 60 | # in this "flutter" section. Each entry in this list should have a 61 | # "family" key with the font family name, and a "fonts" key with a 62 | # list giving the asset and other descriptors for the font. For 63 | # example: 64 | # fonts: 65 | # - family: Schyler 66 | # fonts: 67 | # - asset: fonts/Schyler-Regular.ttf 68 | # - asset: fonts/Schyler-Italic.ttf 69 | # style: italic 70 | # - family: Trajan Pro 71 | # fonts: 72 | # - asset: fonts/TrajanPro.ttf 73 | # - asset: fonts/TrajanPro_Bold.ttf 74 | # weight: 700 75 | # 76 | # For details regarding fonts in packages, see 77 | # https://flutter.dev/custom-fonts/#from-packages 78 | -------------------------------------------------------------------------------- /test/flutter_p2p_engine_method_channel_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:flutter_p2p_engine/flutter_p2p_engine_method_channel.dart'; 4 | 5 | void main() { 6 | TestWidgetsFlutterBinding.ensureInitialized(); 7 | 8 | MethodChannelFlutterP2pEngine platform = MethodChannelFlutterP2pEngine(); 9 | const MethodChannel channel = MethodChannel('flutter_p2p_engine'); 10 | 11 | setUp(() { 12 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( 13 | channel, 14 | (MethodCall methodCall) async { 15 | return '42'; 16 | }, 17 | ); 18 | }); 19 | 20 | tearDown(() { 21 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); 22 | }); 23 | 24 | test('getPlatformVersion', () async { 25 | expect(await platform.getSDKVersion(), '3.1.0'); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/flutter_p2p_engine_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:flutter_p2p_engine/flutter_p2p_engine.dart'; 3 | import 'package:flutter_p2p_engine/flutter_p2p_engine_platform_interface.dart'; 4 | import 'package:flutter_p2p_engine/flutter_p2p_engine_method_channel.dart'; 5 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 6 | 7 | class MockFlutterP2pEnginePlatform 8 | with MockPlatformInterfaceMixin 9 | implements FlutterP2pEnginePlatform { 10 | 11 | @override 12 | Future disableP2p() { 13 | // TODO: implement disableP2p 14 | throw UnimplementedError(); 15 | } 16 | 17 | @override 18 | Future enableP2p() { 19 | // TODO: implement enableP2p 20 | throw UnimplementedError(); 21 | } 22 | 23 | @override 24 | Future getPeerId() { 25 | // TODO: implement getPeerId 26 | throw UnimplementedError(); 27 | } 28 | 29 | @override 30 | Future getSDKVersion() { 31 | // TODO: implement getSDKVersion 32 | throw UnimplementedError(); 33 | } 34 | 35 | @override 36 | Future init(token, {required P2pConfig config, void Function(Map p1)? infoListener, bool bufferedDurationGeneratorEnable = false}) { 37 | // TODO: implement init 38 | throw UnimplementedError(); 39 | } 40 | 41 | @override 42 | Future isConnected() { 43 | // TODO: implement isConnected 44 | throw UnimplementedError(); 45 | } 46 | 47 | @override 48 | Future notifyPlaybackStalled() { 49 | // TODO: implement notifyPlaybackStalled 50 | throw UnimplementedError(); 51 | } 52 | 53 | @override 54 | Future parseStreamURL(String sourceUrl, {String? videoId, Duration Function()? bufferedDurationGenerator}) { 55 | // TODO: implement parseStreamURL 56 | throw UnimplementedError(); 57 | } 58 | 59 | @override 60 | Future restartP2p() { 61 | // TODO: implement restartP2p 62 | throw UnimplementedError(); 63 | } 64 | 65 | @override 66 | Future shutdown() { 67 | // TODO: implement shutdown 68 | throw UnimplementedError(); 69 | } 70 | 71 | @override 72 | Future stopP2p() { 73 | // TODO: implement stopP2p 74 | throw UnimplementedError(); 75 | } 76 | } 77 | 78 | void main() { 79 | final FlutterP2pEnginePlatform initialPlatform = FlutterP2pEnginePlatform.instance; 80 | 81 | test('$MethodChannelFlutterP2pEngine is the default instance', () { 82 | expect(initialPlatform, isInstanceOf()); 83 | }); 84 | 85 | test('getPlatformVersion', () async { 86 | MockFlutterP2pEnginePlatform fakePlatform = MockFlutterP2pEnginePlatform(); 87 | FlutterP2pEnginePlatform.instance = fakePlatform; 88 | 89 | expect(await FlutterP2pEngine.getSDKVersion(), '3.1.0'); 90 | }); 91 | } 92 | --------------------------------------------------------------------------------