diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..e5c0921
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,35 @@
+name: Build
+on: [push]
+
+defaults:
+ run:
+ working-directory: example/
+
+jobs:
+ build_ios:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Flutter
+ id: flutter
+ uses: DanTup/gh-actions/setup-flutter@master
+ with:
+ channel: stable
+ - name: Install dependencies
+ run: flutter pub get
+ - name: Build iOS application
+ run: flutter build ios --no-codesign
+
+ build_android:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Flutter
+ id: flutter
+ uses: DanTup/gh-actions/setup-flutter@master
+ with:
+ channel: 2.5.3
+ - name: Install dependencies
+ run: flutter pub get
+ - name: Build Android application
+ run: flutter build apk
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..f1c8099
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,18 @@
+name: Publish to pub.dev
+on:
+ release:
+ types: [ published ]
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Flutter
+ id: flutter
+ uses: DanTup/gh-actions/setup-flutter@master
+ with:
+ channel: stable
+ - name: Install dependencies
+ run: flutter pub get
+ - name: Publishing
+ run: flutter pub publish
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..27e8946
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,32 @@
+name: Run tests
+on: [push]
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Flutter
+ id: flutter
+ uses: DanTup/gh-actions/setup-flutter@master
+ with:
+ channel: stable
+ - name: Install dependencies
+ run: flutter pub get
+ - name: Analyze project source
+ run: flutter analyze
+ - name: Run tests
+ run: flutter test
+
+ publish_test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Flutter
+ id: flutter
+ uses: DanTup/gh-actions/setup-flutter@master
+ with:
+ channel: stable
+ - name: Install dependencies
+ run: flutter pub get
+ - name: Dry-run publishing
+ run: flutter pub publish --dry-run
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 4a8d516..d193fc1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,38 +1,74 @@
+# Miscellaneous
+*.class
+*.lock
+*.log
+*.pyc
+*.swp
.DS_Store
-.dart_tool/
+.atom/
+.buildlog/
+.history
+.svn/
-.packages
-.pub/
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
-build/
+# Visual Studio Code related
+.classpath
+.project
+.settings/
+.vscode/
+
+# Flutter repo-specific
+/bin/cache/
+/bin/internal/bootstrap.bat
+/bin/internal/bootstrap.sh
+/bin/mingit/
+/dev/benchmarks/mega_gallery/
+/dev/bots/.recipe_deps
+/dev/bots/android_tools/
+/dev/devicelab/ABresults*.json
+/dev/docs/doc/
+/dev/docs/flutter.docs.zip
+/dev/docs/lib/
+/dev/docs/pubspec.yaml
+/dev/integration_tests/**/xcuserdata
+/dev/integration_tests/**/Pods
+/packages/flutter/coverage/
+version
+analysis_benchmark.json
+
+# packages file containing multi-root paths
+.packages.generated
-### Flutter ###
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
-.fvm/
+**/generated_plugin_registrant.dart
.packages
.pub-cache/
.pub/
build/
-coverage/
-lib/generated_plugin_registrant.dart
-# For library packages, don’t commit the pubspec.lock file.
-# Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies.
-# See https://dart.dev/guides/libraries/private-files#pubspeclock
-#pubspec.lock
+flutter_*.png
+linked_*.ds
+unlinked.ds
+unlinked_spec.ds
# Android related
**/android/**/gradle-wrapper.jar
-**/android/.gradle
+.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
-**/android/key.properties
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
+**/android/key.properties
+*.jks
# iOS/XCode related
**/ios/**/*.mode1v3
@@ -56,6 +92,7 @@ lib/generated_plugin_registrant.dart
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
@@ -63,183 +100,19 @@ lib/generated_plugin_registrant.dart
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
+# macOS
+**/macos/Flutter/GeneratedPluginRegistrant.swift
+
+# Coverage
+coverage/
+
+# Symbols
+app.*.symbols
+
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
-
-### Xcode ###
-# Xcode
-#
-# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-
-## User settings
-xcuserdata/
-
-## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
-*.xcscmblueprint
-*.xccheckout
-
-## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
-DerivedData/
-*.moved-aside
-*.pbxuser
-!default.pbxuser
-*.mode1v3
-!default.mode1v3
-*.mode2v3
-!default.mode2v3
-*.perspectivev3
-!default.perspectivev3
-
-## Gcc Patch
-/*.gcno
-
-### Xcode Patch ###
-*.xcodeproj/*
-!*.xcodeproj/project.pbxproj
-!*.xcodeproj/xcshareddata/
-!*.xcworkspace/contents.xcworkspacedata
-**/xcshareddata/WorkspaceSettings.xcsettings
-
-### AndroidStudio ###
-# Covers files to be ignored for android development using Android Studio.
-
-# Built application files
-*.apk
-*.ap_
-*.aab
-
-# Files for the ART/Dalvik VM
-*.dex
-
-# Java class files
-*.class
-
-# Generated files
-bin/
-gen/
-out/
-
-# Gradle files
-.gradle
-.gradle/
-
-# Signing files
-.signing/
-
-# Local configuration file (sdk path, etc)
-local.properties
-
-# Proguard folder generated by Eclipse
-proguard/
-
-# Log Files
-*.log
-
-# Android Studio
-/*/build/
-/*/local.properties
-/*/out
-/*/*/build
-/*/*/production
-captures/
-.navigation/
-*.ipr
-*~
-*.swp
-
-# Keystore files
-*.jks
-*.keystore
-
-# Google Services (e.g. APIs or Firebase)
-# google-services.json
-
-# Android Patch
-gen-external-apklibs
-
-# External native build folder generated in Android Studio 2.2 and later
-.externalNativeBuild
-
-# NDK
-obj/
-
-# IntelliJ IDEA
-*.iml
-*.iws
-/out/
-
-# User-specific configurations
-.idea/caches/
-.idea/libraries/
-.idea/shelf/
-.idea/workspace.xml
-.idea/tasks.xml
-.idea/.name
-.idea/compiler.xml
-.idea/copyright/profiles_settings.xml
-.idea/encodings.xml
-.idea/misc.xml
-.idea/modules.xml
-.idea/scopes/scope_settings.xml
-.idea/dictionaries
-.idea/vcs.xml
-.idea/jsLibraryMappings.xml
-.idea/datasources.xml
-.idea/dataSources.ids
-.idea/sqlDataSources.xml
-.idea/dynamic.xml
-.idea/uiDesigner.xml
-.idea/assetWizardSettings.xml
-.idea/gradle.xml
-.idea/jarRepositories.xml
-.idea/navEditor.xml
-
-# OS-specific files
-.DS_Store
-.DS_Store?
-._*
-.Spotlight-V100
-.Trashes
-ehthumbs.db
-Thumbs.db
-
-# Legacy Eclipse project files
-.classpath
-.project
-.cproject
-.settings/
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.war
-*.ear
-
-# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
-hs_err_pid*
-
-## Plugin-specific files:
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Mongo Explorer plugin
-.idea/mongoSettings.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-### AndroidStudio Patch ###
-
-!/gradle/wrapper/gradle-wrapper.jar
+!/dev/ci/**/Gemfile.lock
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/runConfigurations/example_lib_main_dart.xml b/.idea/runConfigurations/example_lib_main_dart.xml
deleted file mode 100644
index 5fd9159..0000000
--- a/.idea/runConfigurations/example_lib_main_dart.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41cc7d8..6073234 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,3 @@
-## 0.0.1
+## 0.1.0
-* TODO: Describe initial release.
+* Initial release.
diff --git a/LICENSE b/LICENSE
index ba75c69..c0eef12 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1 +1,21 @@
-TODO: Add your license here.
+MIT License
+
+Copyright (c) 2022 api.video
+
+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:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+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.
diff --git a/android/build.gradle b/android/build.gradle
index 42c9e79..f15ccc6 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,15 +1,15 @@
-group 'video.api.eco.flt.livestream.apivideolivestream'
+group 'video.api.flutter.livestream'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.0'
+ classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -17,7 +17,7 @@ buildscript {
rootProject.allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
maven { url 'https://jitpack.io' }
}
}
@@ -39,5 +39,5 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
- implementation 'video.api:android-live-stream:0.1.5'
+ implementation 'video.api:android-live-stream:0.3.1'
}
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 7fcb384..2e3d9f4 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,3 +1,7 @@
+ package="video.api.flutter.livestream">
+
+
+
+
diff --git a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/LiveStreamNativeView.kt b/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/LiveStreamNativeView.kt
deleted file mode 100644
index 5143ef6..0000000
--- a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/LiveStreamNativeView.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-package video.api.eco.flt.livestream.apivideolivestream
-
-import android.content.Context
-import android.graphics.Camera
-import android.util.Log
-import android.view.View
-import com.pedro.encoder.input.video.CameraHelper
-import io.flutter.plugin.common.BinaryMessenger
-import io.flutter.plugin.common.MethodCall
-import io.flutter.plugin.common.MethodChannel
-import io.flutter.plugin.platform.PlatformView
-import net.ossrs.rtmp.ConnectCheckerRtmp
-import video.api.livestream_module.ApiVideoLiveStream
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler
-import java.lang.Exception
-
-class LiveStreamNativeView(context: Context, id: Int, creationParams: Map?, messenger: BinaryMessenger):
- PlatformView, ConnectCheckerRtmp, MethodCallHandler {
-
- //private var channel : MethodChannel = MethodChannel(messenger, "apivideolivestream")
-
- private lateinit var view: LiveStreamView
- private var apiVideo: ApiVideoLiveStream
- private var livestreamKey: String = ""
- private var url : String? = null
- private var methodChannel: MethodChannel? = null
-
- override fun getView(): View {
- return view.findViewById(R.id.opengl_view)
- }
- override fun dispose() {
- try {
- methodChannel?.setMethodCallHandler(null)
- }catch (e: Exception){
- Log.e("MethodCallHandler","Already null")
- }
- }
-
- init {
- //channel.setMethodCallHandler(this)
- view = LiveStreamView(context)
- apiVideo = ApiVideoLiveStream(context, this, null, null)
- initMethodChannel(messenger, id)
- }
-
- private fun initMethodChannel(messenger: BinaryMessenger, viewId: Int){
- methodChannel = MethodChannel(messenger, "apivideolivestream_$viewId")
- methodChannel!!.setMethodCallHandler(this)
- }
-
- override fun onConnectionSuccessRtmp() {
- Log.i("Rtmp Connection", "success")
- }
-
- override fun onConnectionFailedRtmp(reason: String?) {
- Log.e("Rtmp Connection", "failed")
- }
-
- override fun onNewBitrateRtmp(bitrate: Long) {
- Log.i("New rtmp bitrate", "$bitrate")
- }
-
- override fun onDisconnectRtmp() {
- Log.i("Rtmp connetion", "On disconnect")
- }
-
- override fun onAuthErrorRtmp() {
- Log.e("Rtmp Auth", "error")
- }
-
- override fun onAuthSuccessRtmp() {
- Log.i("Rtmp Auth", "success")
- }
-
- private fun setUrl(newUrl: String?){
- url = if(url != null || url != ""){
- newUrl
- }else{
- null
- }
- }
-
- private fun startLive(){
- Log.e("startlive method","called")
- Log.e("startlive key",livestreamKey)
- apiVideo.startStreaming(livestreamKey, url)
- }
- private fun stopLive(){
- Log.e("stop method","called")
- apiVideo.stopStreaming()
- }
- private fun switchCamera(){
- Log.e("camera switch", apiVideo.videoCamera.toString())
- if(apiVideo.videoCamera === CameraHelper.Facing.BACK){
- apiVideo.videoCamera = CameraHelper.Facing.FRONT
- }else{
- apiVideo.videoCamera = CameraHelper.Facing.BACK
- }
- }
-
- override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
- when (call.method){
- "setLivestreamKey" -> livestreamKey = call.arguments.toString()
- "startStreaming" -> startLive()
- "stopStreaming" -> stopLive()
- "switchCamera" -> switchCamera()
- }
- }
-}
\ No newline at end of file
diff --git a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/LiveStreamView.kt b/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/LiveStreamView.kt
deleted file mode 100644
index 9c1c0b8..0000000
--- a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/LiveStreamView.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package video.api.eco.flt.livestream.apivideolivestream
-
-import android.content.Context
-import androidx.constraintlayout.widget.ConstraintLayout
-
-class LiveStreamView(context: Context): ConstraintLayout(context) {
- init {
- inflate(context, R.layout.fluter_livestream, this)
- }
-}
\ No newline at end of file
diff --git a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/NativeViewFactory.kt b/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/NativeViewFactory.kt
deleted file mode 100644
index f4bbf69..0000000
--- a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/NativeViewFactory.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package video.api.eco.flt.livestream.apivideolivestream
-
-import android.content.Context
-import io.flutter.plugin.common.BinaryMessenger
-import io.flutter.plugin.common.MethodChannel
-import io.flutter.plugin.common.StandardMessageCodec
-import io.flutter.plugin.platform.PlatformView
-import io.flutter.plugin.platform.PlatformViewFactory
-
-class NativeViewFactory(private val messenger: BinaryMessenger): PlatformViewFactory(
- StandardMessageCodec.INSTANCE) {
- private lateinit var mess : BinaryMessenger
-
- override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
- val creationParams = args as Map?
- this.mess = messenger
- return LiveStreamNativeView(context, viewId, creationParams, this.mess)
- }
-}
\ No newline at end of file
diff --git a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/ApivideolivestreamPlugin.kt b/android/src/main/kotlin/video/api/flutter/livestream/ApiVideoLiveStreamPlugin.kt
similarity index 54%
rename from android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/ApivideolivestreamPlugin.kt
rename to android/src/main/kotlin/video/api/flutter/livestream/ApiVideoLiveStreamPlugin.kt
index b5852a1..87c0746 100644
--- a/android/src/main/kotlin/video/api/eco/flt/livestream/apivideolivestream/ApivideolivestreamPlugin.kt
+++ b/android/src/main/kotlin/video/api/flutter/livestream/ApiVideoLiveStreamPlugin.kt
@@ -1,34 +1,21 @@
-package video.api.eco.flt.livestream.apivideolivestream
+package video.api.flutter.livestream
-import android.util.Log
import androidx.annotation.NonNull
-
import io.flutter.embedding.engine.plugins.FlutterPlugin
-import io.flutter.plugin.common.MethodCall
-import io.flutter.plugin.common.MethodChannel
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler
-import io.flutter.plugin.common.MethodChannel.Result
-/** ApivideolivestreamPlugin */
-class ApivideolivestreamPlugin: FlutterPlugin{
+/** ApiVideoLiveStreamPlugin */
+class ApiVideoLiveStreamPlugin: FlutterPlugin{
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
- //private lateinit var channel : MethodChannel
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
- //channel = MethodChannel(flutterPluginBinding.binaryMessenger, "apivideolivestream1")
- //channel.setMethodCallHandler(this)
flutterPluginBinding
.platformViewRegistry
.registerViewFactory("", NativeViewFactory(flutterPluginBinding.binaryMessenger))
}
-
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
-/*
- channel.setMethodCallHandler(null)
-*/
}
}
diff --git a/android/src/main/kotlin/video/api/flutter/livestream/ConfigHelper.kt b/android/src/main/kotlin/video/api/flutter/livestream/ConfigHelper.kt
new file mode 100644
index 0000000..eba417a
--- /dev/null
+++ b/android/src/main/kotlin/video/api/flutter/livestream/ConfigHelper.kt
@@ -0,0 +1,17 @@
+package video.api.flutter.livestream
+
+import video.api.livestream.enums.Resolution
+
+object ConfigHelper {
+ fun getResolutionFromResolutionString(resolutionString: String): Resolution {
+ return when (resolutionString) {
+ "240p" -> Resolution.RESOLUTION_240
+ "360p" -> Resolution.RESOLUTION_360
+ "480p" -> Resolution.RESOLUTION_480
+ "720p" -> Resolution.RESOLUTION_720
+ "1080p" -> Resolution.RESOLUTION_1080
+ "2160p" -> Resolution.RESOLUTION_2160
+ else -> Resolution.RESOLUTION_720
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/kotlin/video/api/flutter/livestream/Extensions.kt b/android/src/main/kotlin/video/api/flutter/livestream/Extensions.kt
new file mode 100644
index 0000000..5b48508
--- /dev/null
+++ b/android/src/main/kotlin/video/api/flutter/livestream/Extensions.kt
@@ -0,0 +1,24 @@
+package video.api.flutter.livestream
+
+import video.api.livestream.models.AudioConfig
+import video.api.livestream.models.VideoConfig
+
+fun Map.toVideoConfig(): VideoConfig {
+ return VideoConfig(
+ bitrate = this["bitrate"] as Int,
+ resolution = ConfigHelper.getResolutionFromResolutionString(this["resolution"] as String),
+ fps = this["fps"] as Int
+ )
+}
+
+fun Map.toAudioConfig(): AudioConfig {
+ return AudioConfig(
+ bitrate = this["bitrate"] as Int,
+ sampleRate = this["sampleRate"] as Int,
+ stereo = this["channel"] == "stereo",
+ noiseSuppressor = this["enableNoiseSuppressor"] as Boolean,
+ echoCanceler = this["enableEchoCanceler"] as Boolean
+ )
+}
+
+
diff --git a/android/src/main/kotlin/video/api/flutter/livestream/LiveStreamNativeView.kt b/android/src/main/kotlin/video/api/flutter/livestream/LiveStreamNativeView.kt
new file mode 100644
index 0000000..ac6070d
--- /dev/null
+++ b/android/src/main/kotlin/video/api/flutter/livestream/LiveStreamNativeView.kt
@@ -0,0 +1,139 @@
+package video.api.flutter.livestream
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.platform.PlatformView
+import video.api.livestream.ApiVideoLiveStream
+import video.api.livestream.enums.CameraFacingDirection
+import video.api.livestream.interfaces.IConnectionChecker
+import video.api.livestream.views.ApiVideoView
+
+
+class LiveStreamNativeView(
+ context: Context,
+ id: Int,
+ messenger: BinaryMessenger,
+ creationParams: Map
+) :
+ PlatformView, IConnectionChecker, MethodCallHandler {
+ companion object {
+ const val TAG = "LiveStreamNativeView"
+ }
+
+ private val glView = ApiVideoView(context)
+ private var liveStream =
+ ApiVideoLiveStream(
+ context = context,
+ connectionChecker = this,
+ initialAudioConfig = (creationParams["audioParameters"] as Map).toAudioConfig(),
+ initialVideoConfig = (creationParams["videoParameters"] as Map).toVideoConfig(),
+ apiVideoView = glView
+ )
+ private var methodChannel = MethodChannel(messenger, "video.api.livestream/controller")
+
+ override fun getView() = glView
+
+ override fun dispose() {
+ try {
+ liveStream.stopStreaming()
+ methodChannel.setMethodCallHandler(null)
+ } catch (e: Exception) {
+ Log.e(TAG, "Already null")
+ }
+ }
+
+ init {
+ methodChannel.setMethodCallHandler(this)
+ }
+
+ override fun onConnectionSuccess() {
+ Handler(Looper.getMainLooper()).post {
+ methodChannel.invokeMethod("onConnectionSuccess", null)
+ }
+ }
+
+ override fun onConnectionFailed(reason: String) {
+ Handler(Looper.getMainLooper()).post {
+ methodChannel.invokeMethod("onConnectionError", reason)
+ }
+ }
+
+ override fun onConnectionStarted(url: String) {
+ Log.d(TAG, "onConnectionStarted")
+ }
+
+ override fun onDisconnect() {
+ Handler(Looper.getMainLooper()).post {
+ methodChannel.invokeMethod("onDisconnect", null)
+ }
+ }
+
+ override fun onAuthError() {
+ Log.e(TAG, "Rtmp Auth error")
+ }
+
+ override fun onAuthSuccess() {
+ Log.d(TAG, "Rtmp Auth success")
+ }
+
+ private fun setVideoParameters(map: Map) {
+ liveStream.videoConfig = map.toVideoConfig()
+ }
+
+ private fun setAudioParameters(map: Map) {
+ liveStream.audioConfig = map.toAudioConfig()
+ }
+
+ private fun startStreaming(streamKey: String, url: String) {
+ Log.d(TAG, "startlive method called")
+ liveStream.startStreaming(streamKey, url)
+ }
+
+ private fun stopStreaming() {
+ Log.d(TAG, "stop method called")
+ liveStream.stopStreaming()
+ }
+
+ private fun switchCamera() {
+ Log.d(TAG, "camera switch")
+ if (liveStream.camera == CameraFacingDirection.BACK) {
+ liveStream.camera = CameraFacingDirection.FRONT
+ } else {
+ liveStream.camera = CameraFacingDirection.BACK
+ }
+ }
+
+ private fun toggleMute() {
+ Log.d(TAG, "toggle microphone")
+ liveStream.isMuted = !liveStream.isMuted
+ }
+
+ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
+ when (call.method) {
+ "startStreaming" -> {
+ val streamKey = call.argument("streamKey")
+ val url = call.argument("url")
+ when {
+ streamKey == null -> result.error("missing_stream_key", "Stream key is missing", null)
+ url == null -> result.error("missing_rtmp_url", "RTMP url is missing", null)
+ else -> startStreaming(streamKey, url)
+ }
+ }
+ "stopStreaming" -> stopStreaming()
+ "setVideoParameters" -> {
+ setVideoParameters(call.arguments as Map)
+ }
+ "setAudioParameters" -> {
+ setAudioParameters(call.arguments as Map)
+ }
+ "switchCamera" -> switchCamera()
+ "toggleMute" -> toggleMute()
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/kotlin/video/api/flutter/livestream/NativeViewFactory.kt b/android/src/main/kotlin/video/api/flutter/livestream/NativeViewFactory.kt
new file mode 100644
index 0000000..7d5d621
--- /dev/null
+++ b/android/src/main/kotlin/video/api/flutter/livestream/NativeViewFactory.kt
@@ -0,0 +1,17 @@
+package video.api.flutter.livestream
+
+import android.content.Context
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.StandardMessageCodec
+import io.flutter.plugin.platform.PlatformView
+import io.flutter.plugin.platform.PlatformViewFactory
+
+class NativeViewFactory(private val messenger: BinaryMessenger) : PlatformViewFactory(
+ StandardMessageCodec.INSTANCE
+) {
+ override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
+ val creationParams = args as Map
+ return LiveStreamNativeView(context, viewId, messenger, creationParams)
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/res/layout/fluter_livestream.xml b/android/src/main/res/layout/fluter_livestream.xml
deleted file mode 100644
index 94b9e83..0000000
--- a/android/src/main/res/layout/fluter_livestream.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index fd5c5ad..5a41421 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -34,7 +34,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "video.api.eco.flt.livestream.apivideolivestream_example"
+ applicationId "video.api.flutter.livestream.example"
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
index e8f02aa..6f8e8c8 100644
--- a/example/android/app/src/debug/AndroidManifest.xml
+++ b/example/android/app/src/debug/AndroidManifest.xml
@@ -1,5 +1,5 @@
+ package="video.api.flutter.livestream.example">
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index a3437ad..011c2a1 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,10 +1,10 @@
+ package="video.api.flutter.livestream.example">
+ package="video.api.flutter.livestream.example">
diff --git a/example/android/build.gradle b/example/android/build.gradle
index 9b6ed06..530764e 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,12 +1,12 @@
buildscript {
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.6.0'
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.0'
+ classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -14,7 +14,7 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index bc6a58a..b1fe44c 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Jun 23 08:50:38 CEST 2017
+#Tue Dec 21 16:00:47 CET 2021
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 9411102..ee6a180 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -37,5 +37,16 @@ end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
+ target.build_configurations.each do |config|
+ config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
+ '$(inherited)',
+
+ ## dart: PermissionGroup.camera
+ 'PERMISSION_CAMERA=1',
+
+ ## dart: PermissionGroup.microphone
+ 'PERMISSION_MICROPHONE=1',
+ ]
+ end
end
end
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
deleted file mode 100644
index 44435d3..0000000
--- a/example/ios/Podfile.lock
+++ /dev/null
@@ -1,43 +0,0 @@
-PODS:
- - apivideolivestream (0.0.1):
- - Flutter
- - LiveStreamIos (~> 0.0.2)
- - Flutter (1.0.0)
- - HaishinKit (1.1.5):
- - Logboard (~> 2.2.1)
- - image_picker (0.0.1):
- - Flutter
- - LiveStreamIos (0.0.2):
- - HaishinKit (~> 1.1.0)
- - Logboard (2.2.2)
-
-DEPENDENCIES:
- - apivideolivestream (from `.symlinks/plugins/apivideolivestream/ios`)
- - Flutter (from `Flutter`)
- - image_picker (from `.symlinks/plugins/image_picker/ios`)
-
-SPEC REPOS:
- trunk:
- - HaishinKit
- - LiveStreamIos
- - Logboard
-
-EXTERNAL SOURCES:
- apivideolivestream:
- :path: ".symlinks/plugins/apivideolivestream/ios"
- Flutter:
- :path: Flutter
- image_picker:
- :path: ".symlinks/plugins/image_picker/ios"
-
-SPEC CHECKSUMS:
- apivideolivestream: 8dea154ed1756fca177fe310d81d50b49e8ac58e
- Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
- HaishinKit: 5cf385a823dee6b299404877edf418974422bf9f
- image_picker: e06f7a68f000bd36f552c1847e33cda96ed31f1f
- LiveStreamIos: dffd0e5d6f9fb712f72ac87eb018e0396bc1284e
- Logboard: 0ab6bbd984ed032b3f0b615cef06779a73445c80
-
-PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea
-
-COCOAPODS: 1.10.1
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 2ba3492..32324c5 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -355,14 +355,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = VY3VXRC7P4;
+ DEVELOPMENT_TEAM = GBC36KP98K;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = video.api.eco.flt.livestream.apivideolivestreamExample;
+ PRODUCT_BUNDLE_IDENTIFIER = video.api.flutter.livestream.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -484,14 +484,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = VY3VXRC7P4;
+ DEVELOPMENT_TEAM = GBC36KP98K;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = video.api.eco.flt.livestream.apivideolivestreamExample;
+ PRODUCT_BUNDLE_IDENTIFIER = video.api.flutter.livestream.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -507,14 +507,14 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = VY3VXRC7P4;
+ DEVELOPMENT_TEAM = GBC36KP98K;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = video.api.eco.flt.livestream.apivideolivestreamExample;
+ PRODUCT_BUNDLE_IDENTIFIER = video.api.flutter.livestream.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index cb30c04..833a1b3 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -22,6 +22,12 @@
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
+ NSCameraUsageDescription
+ Your own description of the purpose
+ NSMicrophoneUsageDescription
+ Your own description of the purpose
+ NSPhotoLibraryUsageDescription
+ This app need access to your library
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -41,11 +47,5 @@
UIViewControllerBasedStatusBarAppearance
- NSPhotoLibraryUsageDescription
- This app need access to your library
- NSCameraUsageDescription
- Your own description of the purpose
- NSMicrophoneUsageDescription
- Your own description of the purpose
diff --git a/example/lib/constants.dart b/example/lib/constants.dart
new file mode 100644
index 0000000..825a928
--- /dev/null
+++ b/example/lib/constants.dart
@@ -0,0 +1,7 @@
+class Constants {
+ static const String Settings = "Settings";
+
+ static const List choices = [
+ Settings,
+ ];
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 269c84d..9134231 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,61 +1,101 @@
+import 'package:apivideo_live_stream/apivideo_live_stream.dart';
+import 'package:apivideo_live_stream_example/settings_screen.dart';
+import 'package:apivideo_live_stream_example/types/params.dart';
import 'package:flutter/material.dart';
-import 'package:apivideolivestream/apivideolivestream.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+import 'constants.dart';
+
+const permissions = [Permission.camera, Permission.microphone];
void main() {
runApp(MyApp());
}
-class MyApp extends StatefulWidget {
+class MyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return new MaterialApp(home: LiveViewPage());
+ }
+}
+
+class LiveViewPage extends StatefulWidget {
+ const LiveViewPage({Key? key}) : super(key: key);
+
@override
- _MyAppState createState() => _MyAppState();
+ _LiveViewPageState createState() => new _LiveViewPageState();
}
-class _MyAppState extends State {
- Apivideolivestream? controller;
+class _LiveViewPageState extends State {
+ final ButtonStyle buttonStyle =
+ ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20));
+
bool _isStreaming = false;
- String _title = "Start";
+ String _liveButtonTitle = "Start";
+ String _rtmpStreamKey = '';
+ Params params = Params();
+ final LiveStreamController _controller =
+ LiveStreamController(onConnectionError: (error) {
+ // TODO: change live button state
+ });
@override
void initState() {
+ // TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
- final ButtonStyle style =
- ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20));
-
- return MaterialApp(
- home: Scaffold(
- appBar: AppBar(
- title: const Text('Plugin example app'),
- ),
- body: Center(
- child: Column(
- children: [
- Center(
- child: _cameraPreviewWidget(),
- ),
- Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- ElevatedButton(
- style: style,
- onPressed: () {
- Apivideolivestream.switchCamera();
- },
- child: const Text('switch'),
- ),
- const SizedBox(width: 30),
- ElevatedButton(
- style: style,
- onPressed: _toggleStream,
- child: Text('$_title'),
- ),
- ],
- )
- ],
- ),
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Live Stream Example'),
+ actions: [
+ PopupMenuButton(
+ onSelected: (choice) => _onMenuSelected(choice, context),
+ itemBuilder: (BuildContext context) {
+ return Constants.choices.map((String choice) {
+ return PopupMenuItem(
+ value: choice,
+ child: Text(choice),
+ );
+ }).toList();
+ },
+ )
+ ],
+ ),
+ body: Center(
+ child: Column(
+ children: [
+ Center(
+ child: SizedBox(
+ height: 400,
+ child: CameraContainer(
+ controller: _controller,
+ initialVideoParameters: params.video,
+ initialAudioParameters: params.audio))),
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ElevatedButton(
+ style: buttonStyle,
+ onPressed: () {
+ _controller.switchCamera();
+ },
+ child: const Text('switch'),
+ ),
+ const SizedBox(width: 30),
+ ElevatedButton(
+ style: buttonStyle,
+ onPressed: _toggleStream,
+ child: Text('$_liveButtonTitle'),
+ ),
+ ],
+ ),
+ Center(
+ child: Text('$_rtmpStreamKey'),
+ )
+ ],
),
),
);
@@ -65,28 +105,92 @@ class _MyAppState extends State {
setState(() {
if (_isStreaming) {
print("Stop Stream");
- _title = "Start";
+ _liveButtonTitle = "Start";
_isStreaming = false;
- Apivideolivestream.stopStream();
+ _controller.stopStreaming();
} else {
print("Start Stream");
- _title = "Stop";
+ _liveButtonTitle = "Stop";
_isStreaming = true;
- Apivideolivestream.startStream();
+ _controller.startStreaming(
+ streamKey: params.streamKey, url: params.rtmpUrl);
}
});
}
- Widget _cameraPreviewWidget() {
- final plugin = Apivideolivestream();
+ void _onMenuSelected(String choice, BuildContext context) {
+ if (choice == Constants.Settings) {
+ _awaitResultFromSettingsFinal(context);
+ }
+ }
- return Container(
- color: Colors.lightBlueAccent,
- child: LiveStreamPreview(
- controller: plugin,
- liveStreamKey: 'd08c582e-e251-4f9e-9894-8c8d69755d45',
- videoResolution: '1080p',
- ),
- );
+ void _awaitResultFromSettingsFinal(BuildContext context) async {
+ await Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => SettingsScreen(params: params)));
+ setState(() {
+ _rtmpStreamKey = params.streamKey;
+ });
+ _controller.setVideoParameters(params.video);
+ _controller.setAudioParameters(params.audio);
+ }
+}
+
+class CameraContainer extends StatelessWidget {
+ final LiveStreamController controller;
+ final VideoParameters initialVideoParameters;
+ final AudioParameters initialAudioParameters;
+
+ CameraContainer(
+ {required this.controller,
+ required this.initialVideoParameters,
+ required this.initialAudioParameters});
+
+ @override
+ Widget build(BuildContext context) {
+ return FutureBuilder(
+ future: _requestPermission(permissions),
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
+ if (!snapshot.hasData) {
+ // while data is loading:
+ return Center(
+ child: CircularProgressIndicator(),
+ );
+ } else {
+ final hasPermissionsAccepted = snapshot.data!;
+ if (hasPermissionsAccepted) {
+ return CameraPreview(
+ controller: controller,
+ initialVideoParameters: initialVideoParameters,
+ initialAudioParameters: initialAudioParameters);
+ } else {
+ return Center(
+ child: Text(
+ "Permissions for Camera and Microphone are required"));
+ }
+ }
+ });
+ }
+
+ Future _requestPermission(List permissions) async {
+ final statuses = await permissions.request();
+
+ var numOfPermissionsGranted = 0;
+ statuses.forEach((permission, status) {
+ if (status == PermissionStatus.granted) {
+ print('$permission permission Granted');
+ numOfPermissionsGranted++;
+ } else if (status == PermissionStatus.denied) {
+ print('$permission permission denied');
+ } else if (status == PermissionStatus.permanentlyDenied) {
+ print('$permission permission Permanently Denied');
+ }
+ });
+ if (numOfPermissionsGranted >= permissions.length) {
+ return true;
+ } else {
+ return false;
+ }
}
}
diff --git a/example/lib/settings_screen.dart b/example/lib/settings_screen.dart
new file mode 100644
index 0000000..86b2f80
--- /dev/null
+++ b/example/lib/settings_screen.dart
@@ -0,0 +1,315 @@
+import 'package:apivideo_live_stream_example/types/channel.dart';
+import 'package:flutter/material.dart';
+import 'package:settings_ui/settings_ui.dart';
+
+import 'types/params.dart';
+import 'types/resolution.dart';
+import 'types/sample_rate.dart';
+
+class SettingsScreen extends StatefulWidget {
+ const SettingsScreen({Key? key, required this.params}) : super(key: key);
+ final Params params;
+
+ @override
+ _SettingsScreenState createState() => _SettingsScreenState();
+}
+
+class _SettingsScreenState extends State {
+ int resultAlert = -1;
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Settings'),
+ leading: IconButton(
+ icon: Icon(Icons.arrow_back),
+ onPressed: () {
+ Navigator.pop(
+ context,
+ );
+ }),
+ ),
+ body: Container(
+ width: MediaQuery.of(context).size.width,
+ child: SettingsList(
+ sections: [
+ SettingsSection(
+ title: Text('Video'),
+ tiles: [
+ SettingsTile(
+ title: Text('Resolution'),
+ value: Text(widget.params.getResolutionToString()),
+ onPressed: (BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (context) {
+ return PickerScreen(
+ title: "Pick a resolution",
+ initialValue: widget.params.video.resolution,
+ values: getResolutionsMap());
+ }).then((value) {
+ if (value != null) {
+ setState(() {
+ widget.params.video.resolution = value;
+ });
+ }
+ });
+ },
+ ),
+ SettingsTile(
+ title: Text('Framerate'),
+ value: Text(widget.params.video.fps.toString()),
+ onPressed: (BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (context) {
+ return PickerScreen(
+ title: "Pick a frame rate",
+ initialValue:
+ widget.params.video.fps.toString(),
+ values: fpsList.toMap());
+ }).then((value) {
+ if (value != null) {
+ setState(() {
+ widget.params.video.fps = value;
+ });
+ }
+ });
+ },
+ ),
+ CustomSettingsTile(
+ child: Container(
+ child: Column(
+ children: [
+ SettingsTile(
+ title: Text('Bitrate'),
+ ),
+ Row(
+ children: [
+ Slider(
+ value: (widget.params.video.bitrate / 1024)
+ .toDouble(),
+ onChanged: (newValue) {
+ setState(() {
+ widget.params.video.bitrate =
+ (newValue.roundToDouble() * 1024)
+ .toInt();
+ });
+ },
+ min: 500,
+ max: 10000,
+ ),
+ Text('${widget.params.video.bitrate}')
+ ],
+ )
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ SettingsSection(
+ title: Text('Audio'),
+ tiles: [
+ SettingsTile(
+ title: Text("Number of channels"),
+ value: Text(widget.params.getChannelToString()),
+ onPressed: (BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (context) {
+ return PickerScreen(
+ title: "Pick the number of channels",
+ initialValue:
+ widget.params.getChannelToString(),
+ values: getChannelsMap());
+ }).then((value) {
+ if (value != null) {
+ setState(() {
+ widget.params.audio.channel = value;
+ });
+ }
+ });
+ },
+ ),
+ SettingsTile(
+ title: Text('Bitrate'),
+ value: Text(widget.params.getBitrateToString()),
+ onPressed: (BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (context) {
+ return PickerScreen(
+ title: "Pick a bitrate",
+ initialValue:
+ widget.params.getChannelToString(),
+ values: audioBitrateList.toMap(
+ valueTransformation: (int e) =>
+ bitrateToPrettyString(e)));
+ }).then((value) {
+ if (value != null) {
+ setState(() {
+ widget.params.audio.bitrate = value;
+ });
+ }
+ });
+ },
+ ),
+ SettingsTile(
+ title: Text('Sample rate'),
+ value: Text(widget.params.getSampleRateToString()),
+ onPressed: (BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (context) {
+ return PickerScreen(
+ title: "Pick a sample rate",
+ initialValue:
+ widget.params.getSampleRateToString(),
+ values: getSampleRatesMap());
+ }).then((value) {
+ if (value != null) {
+ setState(() {
+ widget.params.audio.sampleRate = value;
+ });
+ }
+ });
+ },
+ ),
+ SettingsTile.switchTile(
+ title: Text('Enable echo canceler'),
+ initialValue: widget.params.audio.enableEchoCanceler,
+ onToggle: (bool value) {
+ setState(() {
+ widget.params.audio.enableEchoCanceler = value;
+ });
+ },
+ ),
+ SettingsTile.switchTile(
+ title: Text('Enable noise suppressor'),
+ initialValue: widget.params.audio.enableNoiseSuppressor,
+ onToggle: (bool value) {
+ setState(() {
+ widget.params.audio.enableNoiseSuppressor = value;
+ });
+ },
+ ),
+ ],
+ ),
+ SettingsSection(
+ title: Text('Endpoint'),
+ tiles: [
+ SettingsTile(
+ title: Text('RTMP endpoint'),
+ value: Text(widget.params.rtmpUrl),
+ onPressed: (BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (context) {
+ return EditTextScreen(
+ title: "Enter RTMP endpoint URL",
+ initialValue: widget.params.rtmpUrl,
+ onChanged: (value) {
+ setState(() {
+ widget.params.rtmpUrl = value;
+ });
+ });
+ });
+ }),
+ SettingsTile(
+ title: Text('Stream key'),
+ value: Text(widget.params.streamKey),
+ onPressed: (BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (context) {
+ return EditTextScreen(
+ title: "Enter stream key",
+ initialValue: widget.params.streamKey,
+ onChanged: (value) {
+ setState(() {
+ widget.params.streamKey = value;
+ });
+ });
+ });
+ }),
+ ],
+ )
+ ],
+ )),
+ );
+ }
+}
+
+class PickerScreen extends StatelessWidget {
+ const PickerScreen({
+ Key? key,
+ required this.title,
+ required this.initialValue,
+ required this.values,
+ }) : super(key: key);
+
+ final String title;
+ final dynamic initialValue;
+ final Map values;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: Text('Settings')),
+ body: SettingsList(
+ sections: [
+ SettingsSection(
+ title: Text(title),
+ tiles: values.keys.map((e) {
+ final value = values[e];
+
+ return SettingsTile(
+ title: Text(value!),
+ onPressed: (_) {
+ Navigator.of(context).pop(e);
+ },
+ );
+ }).toList(),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class EditTextScreen extends StatelessWidget {
+ const EditTextScreen(
+ {Key? key,
+ required this.title,
+ required this.initialValue,
+ required this.onChanged})
+ : super(key: key);
+
+ final String title;
+ final String initialValue;
+ final ValueChanged? onChanged;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: Text('Settings')),
+ body: SettingsList(
+ sections: [
+ SettingsSection(title: Text(title), tiles: [
+ CustomSettingsTile(
+ child: TextField(
+ controller: TextEditingController(text: initialValue),
+ onChanged: onChanged),
+ ),
+ ]),
+ ],
+ ),
+ );
+ }
+}
diff --git a/example/lib/types/channel.dart b/example/lib/types/channel.dart
new file mode 100644
index 0000000..9bb787e
--- /dev/null
+++ b/example/lib/types/channel.dart
@@ -0,0 +1,27 @@
+import 'package:apivideo_live_stream/apivideo_live_stream.dart';
+
+Map getChannelsMap() {
+ Map map = {};
+ for (final res in Channel.values) {
+ map[res] = res.toPrettyString();
+ }
+ return map;
+}
+
+extension ChannelExtension on Channel {
+ String toPrettyString() {
+ var result = "";
+ switch (this) {
+ case Channel.mono:
+ result = "mono";
+ break;
+ case Channel.stereo:
+ result = "stereo";
+ break;
+ default:
+ result = "stereo";
+ break;
+ }
+ return result;
+ }
+}
diff --git a/example/lib/types/params.dart b/example/lib/types/params.dart
new file mode 100644
index 0000000..0519aba
--- /dev/null
+++ b/example/lib/types/params.dart
@@ -0,0 +1,57 @@
+import 'dart:core';
+
+import 'package:apivideo_live_stream/apivideo_live_stream.dart';
+import 'package:apivideo_live_stream_example/types/sample_rate.dart';
+
+import 'channel.dart';
+import 'resolution.dart';
+
+List fpsList = [24, 30];
+List audioBitrateList = [32000, 64000, 128000, 192000];
+
+String defaultValueTransformation(int e) {
+ return "$e";
+}
+
+extension ListExtension on List {
+ Map toMap(
+ {Function(int e) valueTransformation = defaultValueTransformation}) {
+ var map =
+ Map.fromIterable(this, key: (e) => e, value: (e) => valueTransformation(e));
+ return map;
+ }
+}
+
+String bitrateToPrettyString(int bitrate) {
+ return "${bitrate / 1000} Kbps";
+}
+
+class Params {
+ final VideoParameters video = VideoParameters(
+ bitrate: 2 * 1024 * 1024,
+ resolution: Resolution.RESOLUTION_720,
+ fps: 30,
+ );
+ final AudioParameters audio = AudioParameters(
+ bitrate: 128 * 1000,
+ channel: Channel.stereo,
+ sampleRate: SampleRate.kHz_48);
+ String rtmpUrl = "rtmp://broadcast.api.video/s/";
+ String streamKey = "";
+
+ String getResolutionToString() {
+ return video.resolution.toPrettyString();
+ }
+
+ String getChannelToString() {
+ return audio.channel.toPrettyString();
+ }
+
+ String getBitrateToString() {
+ return bitrateToPrettyString(audio.bitrate);
+ }
+
+ String getSampleRateToString() {
+ return audio.sampleRate.toPrettyString();
+ }
+}
diff --git a/example/lib/types/resolution.dart b/example/lib/types/resolution.dart
new file mode 100644
index 0000000..f6e035d
--- /dev/null
+++ b/example/lib/types/resolution.dart
@@ -0,0 +1,39 @@
+import 'package:apivideo_live_stream/apivideo_live_stream.dart';
+
+Map getResolutionsMap() {
+ Map map = {};
+ for (final res in Resolution.values) {
+ map[res] = res.toPrettyString();
+ }
+ return map;
+}
+
+extension ResolutionExtension on Resolution {
+ String toPrettyString() {
+ var result = "";
+ switch (this) {
+ case Resolution.RESOLUTION_240:
+ result = "352x240";
+ break;
+ case Resolution.RESOLUTION_360:
+ result = "640x360";
+ break;
+ case Resolution.RESOLUTION_480:
+ result = "858x480";
+ break;
+ case Resolution.RESOLUTION_720:
+ result = "1280x720";
+ break;
+ case Resolution.RESOLUTION_1080:
+ result = "1920x1080";
+ break;
+ case Resolution.RESOLUTION_2160:
+ result = "3860x2160";
+ break;
+ default:
+ result = "1280x720";
+ break;
+ }
+ return result;
+ }
+}
diff --git a/example/lib/types/sample_rate.dart b/example/lib/types/sample_rate.dart
new file mode 100644
index 0000000..04a8411
--- /dev/null
+++ b/example/lib/types/sample_rate.dart
@@ -0,0 +1,36 @@
+import 'package:apivideo_live_stream/apivideo_live_stream.dart';
+
+Map getSampleRatesMap() {
+ Map map = {};
+ for (final res in SampleRate.values) {
+ map[res] = res.toPrettyString();
+ }
+ return map;
+}
+
+extension SampleRateExtension on SampleRate {
+ String toPrettyString() {
+ var result = "";
+ switch (this) {
+ case SampleRate.kHz_8:
+ result = "8 kHz";
+ break;
+ case SampleRate.kHz_16:
+ result = "16 kHz";
+ break;
+ case SampleRate.kHz_32:
+ result = "32 kHz";
+ break;
+ case SampleRate.kHz_44_1:
+ result = "44.1 kHz";
+ break;
+ case SampleRate.kHz_48:
+ result = "48 kHz";
+ break;
+ default:
+ result = "32 kHz";
+ break;
+ }
+ return result;
+ }
+}
diff --git a/example/pubspec.lock b/example/pubspec.lock
deleted file mode 100644
index b5475ce..0000000
--- a/example/pubspec.lock
+++ /dev/null
@@ -1,236 +0,0 @@
-# Generated by pub
-# See https://dart.dev/tools/pub/glossary#lockfile
-packages:
- apivideolivestream:
- dependency: "direct main"
- description:
- path: ".."
- relative: true
- source: path
- version: "0.0.1"
- async:
- dependency: transitive
- description:
- name: async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.8.1"
- boolean_selector:
- dependency: transitive
- description:
- name: boolean_selector
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- characters:
- dependency: transitive
- description:
- name: characters
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- charcode:
- dependency: transitive
- description:
- name: charcode
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.1"
- clock:
- dependency: transitive
- description:
- name: clock
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- collection:
- dependency: transitive
- description:
- name: collection
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.15.0"
- cross_file:
- dependency: transitive
- description:
- name: cross_file
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.3.1+5"
- cupertino_icons:
- dependency: "direct main"
- description:
- name: cupertino_icons
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.3"
- fake_async:
- dependency: transitive
- description:
- name: fake_async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.0"
- flutter:
- dependency: "direct main"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_plugin_android_lifecycle:
- dependency: transitive
- description:
- name: flutter_plugin_android_lifecycle
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.3"
- flutter_test:
- dependency: "direct dev"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_web_plugins:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.0"
- http:
- dependency: transitive
- description:
- name: http
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.13.3"
- http_parser:
- dependency: transitive
- description:
- name: http_parser
- url: "https://pub.dartlang.org"
- source: hosted
- version: "4.0.0"
- image_picker:
- dependency: "direct main"
- description:
- name: image_picker
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.8.4+1"
- image_picker_for_web:
- dependency: transitive
- description:
- name: image_picker_for_web
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.3"
- image_picker_platform_interface:
- dependency: transitive
- description:
- name: image_picker_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.4.1"
- js:
- dependency: transitive
- description:
- name: js
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.6.3"
- matcher:
- dependency: transitive
- description:
- name: matcher
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.12.10"
- meta:
- dependency: transitive
- description:
- name: meta
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.7.0"
- path:
- dependency: transitive
- description:
- name: path
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.0"
- pedantic:
- dependency: transitive
- description:
- name: pedantic
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.11.1"
- plugin_platform_interface:
- dependency: transitive
- description:
- name: plugin_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.1"
- sky_engine:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.99"
- source_span:
- dependency: transitive
- description:
- name: source_span
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.1"
- stack_trace:
- dependency: transitive
- description:
- name: stack_trace
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.10.0"
- stream_channel:
- dependency: transitive
- description:
- name: stream_channel
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- string_scanner:
- dependency: transitive
- description:
- name: string_scanner
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- term_glyph:
- dependency: transitive
- description:
- name: term_glyph
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.0"
- test_api:
- dependency: transitive
- description:
- name: test_api
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.4.2"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.0"
- vector_math:
- dependency: transitive
- description:
- name: vector_math
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
-sdks:
- dart: ">=2.12.0 <3.0.0"
- flutter: ">=2.0.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 1e49b54..5b7d22f 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,5 +1,5 @@
-name: apivideolivestream_example
-description: Demonstrates how to use the apivideolivestream plugin.
+name: apivideo_live_stream_example
+description: Demonstrates how to use the apivideo_live_stream plugin.
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
@@ -13,10 +13,11 @@ dependencies:
sdk: flutter
image_picker: ^0.8.3+2
+ settings_ui: ^2.0.1
- apivideolivestream:
+ apivideo_live_stream:
# When depending on this package from a real application you should use:
- # apivideolivestream: ^x.y.z
+ # apivideo_live_stream: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
@@ -25,6 +26,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
+ permission_handler: ^8.3.0
dev_dependencies:
flutter_test:
diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart
deleted file mode 100644
index 462ae06..0000000
--- a/example/test/widget_test.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-// This is a basic Flutter widget test.
-//
-// To perform an interaction with a widget in your test, use the WidgetTester
-// utility that Flutter provides. For example, you can send tap and scroll
-// gestures. You can also use WidgetTester to find child widgets in the widget
-// tree, read text, and verify that the values of widget properties are correct.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:apivideolivestream_example/main.dart';
-
-void main() {
- testWidgets('Verify Platform version', (WidgetTester tester) async {
- // Build our app and trigger a frame.
- await tester.pumpWidget(MyApp());
-
- // Verify that platform version is retrieved.
- expect(
- find.byWidgetPredicate(
- (Widget widget) => widget is Text &&
- widget.data!.startsWith('Running on:'),
- ),
- findsOneWidget,
- );
- });
-}
diff --git a/ios/Classes/ApiVideoLiveStreamPlugin.h b/ios/Classes/ApiVideoLiveStreamPlugin.h
new file mode 100644
index 0000000..3051cde
--- /dev/null
+++ b/ios/Classes/ApiVideoLiveStreamPlugin.h
@@ -0,0 +1,4 @@
+#import
+
+@interface ApiVideoLiveStreamPlugin : NSObject
+@end
diff --git a/ios/Classes/ApivideolivestreamPlugin.m b/ios/Classes/ApiVideoLiveStreamPlugin.m
similarity index 51%
rename from ios/Classes/ApivideolivestreamPlugin.m
rename to ios/Classes/ApiVideoLiveStreamPlugin.m
index a9fb4fd..c0b0b9c 100644
--- a/ios/Classes/ApivideolivestreamPlugin.m
+++ b/ios/Classes/ApiVideoLiveStreamPlugin.m
@@ -1,15 +1,15 @@
-#import "ApivideolivestreamPlugin.h"
-#if __has_include()
-#import
+#import "ApiVideoLiveStreamPlugin.h"
+#if __has_include()
+#import
#else
// Support project import fallback if the generated compatibility header
// is not copied when this plugin is created as a library.
// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
-#import "apivideolivestream-Swift.h"
+#import "apivideo_live_stream-Swift.h"
#endif
-@implementation ApivideolivestreamPlugin
+@implementation ApiVideoLiveStreamPlugin
+ (void)registerWithRegistrar:(NSObject*)registrar {
- [SwiftApivideolivestreamPlugin registerWithRegistrar:registrar];
+ [SwiftApiVideoLiveStreamPlugin registerWithRegistrar:registrar];
}
@end
diff --git a/ios/Classes/ApivideolivestreamPlugin.h b/ios/Classes/ApivideolivestreamPlugin.h
deleted file mode 100644
index f48d578..0000000
--- a/ios/Classes/ApivideolivestreamPlugin.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#import
-
-@interface ApivideolivestreamPlugin : NSObject
-@end
diff --git a/ios/Classes/SwiftApiVideoLiveStreamPlugin.swift b/ios/Classes/SwiftApiVideoLiveStreamPlugin.swift
new file mode 100644
index 0000000..aacc342
--- /dev/null
+++ b/ios/Classes/SwiftApiVideoLiveStreamPlugin.swift
@@ -0,0 +1,242 @@
+import Flutter
+import UIKit
+import LiveStreamIos
+import AVFoundation
+import Network
+
+public class SwiftApiVideoLiveStreamPlugin: NSObject, FlutterPlugin {
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let channel = FlutterMethodChannel(name: "video.api.livestream/controller", binaryMessenger: registrar.messenger())
+
+ let factory = LiveStreamViewFactory(messenger: registrar.messenger(), channel: channel)
+ registrar.register(factory, withId: "")
+
+ let instance = SwiftApiVideoLiveStreamPlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ result("iOS " + UIDevice.current.systemVersion)
+ }
+}
+
+class LiveStreamViewFactory: NSObject, FlutterPlatformViewFactory {
+ private let messenger: FlutterBinaryMessenger
+ private let channel: FlutterMethodChannel
+
+ init(messenger: FlutterBinaryMessenger, channel: FlutterMethodChannel) {
+ self.messenger = messenger
+ self.channel = channel
+ super.init()
+ }
+
+ func create(
+ withFrame frame: CGRect,
+ viewIdentifier viewId: Int64,
+ arguments args: Any?
+ ) -> FlutterPlatformView {
+ return LiveStreamNativeView(
+ frame: frame,
+ viewIdentifier: viewId,
+ arguments: args,
+ binaryMessenger: messenger,
+ channel: channel
+ )
+ }
+}
+
+class LiveStreamNativeView: NSObject, FlutterPlatformView {
+ private let liveStreamView: LiveStreamView
+ private let channel: FlutterMethodChannel
+
+ init(
+ frame: CGRect,
+ viewIdentifier viewId: Int64,
+ arguments args: Any?,
+ binaryMessenger messenger: FlutterBinaryMessenger?,
+ channel: FlutterMethodChannel
+ ) {
+ liveStreamView = LiveStreamView(frame: frame, channel: channel)
+ self.channel = channel
+ super.init()
+ }
+
+ func view() -> UIView {
+ return liveStreamView
+ }
+
+ func handlerMethodCall(_ call: FlutterMethodCall, _ result: FlutterResult) {
+ switch call.method {
+ case "startStreaming":
+ if let args = call.arguments as? Dictionary,
+ let streamKey = args["streamKey"] as? String,
+ let url = args["url"] as? String {
+ liveStreamView.startStreaming(streamKey: streamKey, url: url)
+ }
+ break
+ case "stopStreaming":
+ liveStreamView.stopStreaming()
+ break
+ case "switchCamera":
+ if(liveStreamView.videoCamera == "back"){
+ liveStreamView.videoCamera = "front"
+ }else{
+ liveStreamView.videoCamera = "back"
+ }
+ break
+ case "setVideoParameters":
+ if let args = call.arguments as? Dictionary,
+ let bitrate = args["bitrate"] as? Double,
+ let resolution = args["resolution"] as? String,
+ let fps = args["fps"] as? Double {
+ liveStreamView.videoBitrate = bitrate
+ liveStreamView.videoResolution = resolution
+ liveStreamView.videoFps = fps
+ }
+ break
+ case "setAudioParameters":
+ if let args = call.arguments as? Dictionary,
+ let bitrate = args["bitrate"] as? Int {
+ liveStreamView.audioBitrate = bitrate
+ }
+ break
+
+ case "toggleMute":
+ liveStreamView.audioMuted = !liveStreamView.audioMuted
+ break
+ default:
+ break
+ }
+ }
+}
+
+extension String {
+ func toResolution() -> ApiVideoLiveStream.Resolutions{
+ switch self {
+ case "240p":
+ return ApiVideoLiveStream.Resolutions.RESOLUTION_240
+ case "360p":
+ return ApiVideoLiveStream.Resolutions.RESOLUTION_360
+ case "480p":
+ return ApiVideoLiveStream.Resolutions.RESOLUTION_480
+ case "720p":
+ return ApiVideoLiveStream.Resolutions.RESOLUTION_720
+ case "1080p":
+ return ApiVideoLiveStream.Resolutions.RESOLUTION_1080
+ case "2160p":
+ return ApiVideoLiveStream.Resolutions.RESOLUTION_2160
+ default:
+ return ApiVideoLiveStream.Resolutions.RESOLUTION_720
+ }
+ }
+
+}
+
+class LiveStreamView: UIView{
+ private var liveStream: ApiVideoLiveStream?
+ private let channel: FlutterMethodChannel
+ public init(frame: CGRect, channel: FlutterMethodChannel) {
+ self.channel = channel
+ super.init(frame: frame)
+ self.liveStream = ApiVideoLiveStream(view: self)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ @objc var videoFps: Double = 30 {
+ didSet {
+ if(videoFps == Double(liveStream!.videoFps)){
+ return
+ }
+ liveStream?.videoFps = videoFps
+ }
+ }
+
+ @objc var videoResolution: String = "720p" {
+ didSet {
+ let newResolution = videoResolution.toResolution()
+ if(newResolution == liveStream!.videoResolution){
+ return
+ }
+ liveStream?.videoResolution = newResolution
+ }
+ }
+
+ @objc var videoBitrate: Double = -1 {
+ didSet {
+ }
+ }
+
+ @objc var videoCamera: String = "back" {
+ didSet {
+ var value : AVCaptureDevice.Position
+ switch videoCamera {
+ case "back":
+ value = AVCaptureDevice.Position.back
+ case "front":
+ value = AVCaptureDevice.Position.front
+ default:
+ value = AVCaptureDevice.Position.back
+ }
+ if(value == liveStream!.videoCamera){
+ return
+ }
+ liveStream?.videoCamera = value
+ }
+ }
+
+ @objc var videoOrientation: String = "landscape" {
+ didSet {
+ var value : ApiVideoLiveStream.Orientation
+ switch videoOrientation {
+ case "landscape":
+ value = ApiVideoLiveStream.Orientation.landscape
+ case "portrait":
+ value = ApiVideoLiveStream.Orientation.portrait
+ default:
+ value = ApiVideoLiveStream.Orientation.landscape
+ }
+ if(value == liveStream!.videoOrientation){
+ return
+ }
+ liveStream?.videoOrientation = value
+ }
+ }
+
+ @objc var audioMuted: Bool = false {
+ didSet {
+ if(audioMuted == liveStream!.audioMuted){
+ return
+ }
+ liveStream?.audioMuted = audioMuted
+ }
+ }
+
+ @objc var audioBitrate: Int = -1 {
+ didSet {
+ if(audioBitrate == liveStream!.audioBitrate){
+ return
+ }
+ liveStream?.audioBitrate = audioBitrate
+ }
+ }
+
+ @objc func startStreaming(streamKey: String, url: String) {
+ liveStream?.onConnectionSuccess = {() in
+ self.channel.invokeMethod("onConnectionSuccess", arguments: nil)
+ }
+ liveStream?.onConnectionFailed = {(code) in
+ self.channel.invokeMethod("onConnectionFailed", arguments: code)
+ }
+ liveStream?.onDisconnect = {() in
+ self.channel.invokeMethod("onDisconnect", arguments: nil)
+ }
+ liveStream?.startLiveStreamFlux(liveStreamKey: streamKey, rtmpServerUrl: url)
+ }
+
+ @objc func stopStreaming() {
+ liveStream?.stopLiveStreamFlux()
+ }
+}
diff --git a/ios/Classes/SwiftApivideolivestreamPlugin.swift b/ios/Classes/SwiftApivideolivestreamPlugin.swift
deleted file mode 100644
index a944395..0000000
--- a/ios/Classes/SwiftApivideolivestreamPlugin.swift
+++ /dev/null
@@ -1,285 +0,0 @@
-import Flutter
-import UIKit
-import LiveStreamIos
-import AVFoundation
-
-public class SwiftApivideolivestreamPlugin: NSObject, FlutterPlugin {
- public static func register(with registrar: FlutterPluginRegistrar) {
- let channel = FlutterMethodChannel(name: "apivideolivestream", binaryMessenger: registrar.messenger())
-
- let factory = LiveStreamViewFactory(messenger: registrar.messenger())
- registrar.register(factory, withId: "")
-
- let instance = SwiftApivideolivestreamPlugin()
- registrar.addMethodCallDelegate(instance, channel: channel)
- }
-
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- //print("call method : \(call.method)")
- result("iOS " + UIDevice.current.systemVersion)
- }
-}
-
-class LiveStreamViewFactory: NSObject, FlutterPlatformViewFactory {
- private var messenger: FlutterBinaryMessenger
-
- init(messenger: FlutterBinaryMessenger) {
- self.messenger = messenger
- super.init()
- }
-
- func create(
- withFrame frame: CGRect,
- viewIdentifier viewId: Int64,
- arguments args: Any?
- ) -> FlutterPlatformView {
- return LiveStreamNativeView(
- frame: frame,
- viewIdentifier: viewId,
- arguments: args,
- binaryMessenger: messenger
- )
- }
-}
-
-class LiveStreamNativeView: NSObject, FlutterPlatformView {
- private var _view: LiveStreamView
-
- init(
- frame: CGRect,
- viewIdentifier viewId: Int64,
- arguments args: Any?,
- binaryMessenger messenger: FlutterBinaryMessenger?
- ) {
- _view = LiveStreamView()
- super.init()
-
- let channelFirstConnection = FlutterMethodChannel(name: "apivideolivestream_\(viewId)", binaryMessenger: messenger!)
- channelFirstConnection.setMethodCallHandler { [weak self] (call, result) -> Void in
- self?.handlerMethodCall(call, result)
- }
-
- // iOS views can be created here
- //createNativeView(view: _view)
- }
-
- func view() -> UIView {
- return _view
- }
-
- func handlerMethodCall(_ call: FlutterMethodCall, _ result: FlutterResult) {
- switch call.method {
- case "getPlatformVersion":
- print("getPlatformVersion")
- result("iOS " + UIDevice.current.systemVersion)
- break
- case "startStreaming":
- print("start STREAMING")
- _view.startStreaming()
- break
- case "stopStreaming":
- print("stop STREAMING")
- _view.stopStreaming()
- break
- case "switchCamera":
- if(_view.videoCamera == "back"){
- _view.videoCamera = "front"
- }else{
- _view.videoCamera = "back"
- }
- break
- case "setLivestreamKey":
- let key = call.arguments as! String
- print("key: \(key)")
- _view.liveStreamKey = key
- print("view key: \(_view.liveStreamKey)")
- case "setParam":
- let str = call.arguments as! String
- print("Data: \(str)")
- let data = str.data(using: .utf8)
- do {
- let param = try JSONDecoder().decode(Parameters.self, from: data!)
- _view.liveStreamKey = param.liveStreamKey
- _view.rtmpServerUrl = param.rtmpServerUrl
- _view.videoFps = param.videoFps
- _view.videoResolution = param.videoResolution
- _view.videoBitrate = param.videoBitrate
- _view.videoCamera = param.videoCamera
- _view.videoOrientation = param.videoOrientation
- _view.audioMuted = param.audioMuted
- _view.audioBitrate = param.audioBitrate
- print(param)
- print(param.rtmpServerUrl)
- } catch let error as NSError{
- print(error)
- }
-
-
- default:
- break
- }
- }
-
- func createNativeView(view _view: UIView){
- _view.backgroundColor = UIColor.blue
- let nativeLabel = UILabel()
- nativeLabel.text = "Native text from iOS"
- nativeLabel.textColor = UIColor.white
- nativeLabel.textAlignment = .center
- nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
- _view.addSubview(nativeLabel)
- }
-}
-
-class LiveStreamView: UIView{
- private var apiVideo: ApiVideoLiveStream?
- public override init(frame: CGRect) {
- super.init(frame: frame)
- apiVideo = ApiVideoLiveStream(view: self)
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- private func getResolutionFromString(resolutionString: String) -> ApiVideoLiveStream.Resolutions{
- switch resolutionString {
- case "240p":
- return ApiVideoLiveStream.Resolutions.RESOLUTION_240
- case "360p":
- return ApiVideoLiveStream.Resolutions.RESOLUTION_360
- case "480p":
- return ApiVideoLiveStream.Resolutions.RESOLUTION_480
- case "720p":
- return ApiVideoLiveStream.Resolutions.RESOLUTION_720
- case "1080p":
- return ApiVideoLiveStream.Resolutions.RESOLUTION_1080
- case "2160p":
- return ApiVideoLiveStream.Resolutions.RESOLUTION_2160
- default:
- return ApiVideoLiveStream.Resolutions.RESOLUTION_720
- }
- }
-
- @objc override func didMoveToWindow() {
- super.didMoveToWindow()
- }
-
- @objc var liveStreamKey: String = "" {
- didSet {
- }
- }
-
- @objc var rtmpServerUrl: String? {
- didSet {
- }
- }
-
- @objc var videoFps: Double = 30 {
- didSet {
- if(videoFps == Double(apiVideo!.videoFps)){
- return
- }
- apiVideo?.videoFps = videoFps
- }
- }
-
- @objc var videoResolution: String = "720p" {
- didSet {
- let newResolution = getResolutionFromString(resolutionString: videoResolution)
- if(newResolution == apiVideo!.videoResolution){
- return
- }
- apiVideo?.videoResolution = newResolution
- }
- }
-
- @objc var videoBitrate: Double = -1 {
- didSet {
- }
- }
-
- @objc var videoCamera: String = "back" {
- didSet {
- var value : AVCaptureDevice.Position
- switch videoCamera {
- case "back":
- value = AVCaptureDevice.Position.back
- case "front":
- value = AVCaptureDevice.Position.front
- default:
- value = AVCaptureDevice.Position.back
- }
- if(value == apiVideo?.videoCamera){
- return
- }
- apiVideo?.videoCamera = value
-
- }
- }
-
- @objc var videoOrientation: String = "landscape" {
- didSet {
- var value : ApiVideoLiveStream.Orientation
- switch videoOrientation {
- case "landscape":
- value = ApiVideoLiveStream.Orientation.landscape
- case "portrait":
- value = ApiVideoLiveStream.Orientation.portrait
- default:
- value = ApiVideoLiveStream.Orientation.landscape
- }
- if(value == apiVideo?.videoOrientation){
- return
- }
- apiVideo?.videoOrientation = value
-
- }
- }
-
- @objc var audioMuted: Bool = false {
- didSet {
- if(audioMuted == apiVideo!.audioMuted){
- return
- }
- apiVideo?.audioMuted = audioMuted
- }
- }
-
- @objc var audioBitrate: Double = -1 {
- didSet {
- }
- }
-
- @objc func startStreaming() {
- apiVideo!.startLiveStreamFlux(liveStreamKey: self.liveStreamKey, rtmpServerUrl: self.rtmpServerUrl)
- }
-
- @objc func stopStreaming() {
- apiVideo!.stopLiveStreamFlux()
- }
-}
-
-struct Parameters: Codable {
- let liveStreamKey: String
- let rtmpServerUrl: String
- let videoFps: Double
- let videoResolution: String
- let videoBitrate: Double
- let videoCamera: String
- let videoOrientation: String
- let audioMuted: Bool
- let audioBitrate: Double
-
- private enum CodingKeys: String, CodingKey {
- case liveStreamKey = "liveStreamKey"
- case videoFps = "videoFps"
- case videoBitrate = "videoBitrate"
- case videoOrientation = "videoOrientation"
- case audioBitrate = "audioBitrate"
- case rtmpServerUrl = "rtmpServerUrl"
- case videoResolution = "videoResolution"
- case videoCamera = "videoCamera"
- case audioMuted = "audioMuted"
- }
-}
diff --git a/ios/apivideolivestream.podspec b/ios/apivideo_live_stream.podspec
similarity index 81%
rename from ios/apivideolivestream.podspec
rename to ios/apivideo_live_stream.podspec
index cc98144..198b4c1 100644
--- a/ios/apivideolivestream.podspec
+++ b/ios/apivideo_live_stream.podspec
@@ -1,9 +1,9 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
-# Run `pod lib lint apivideolivestream.podspec` to validate before publishing.
+# Run `pod lib lint apivideo_live_stream.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
- s.name = 'apivideolivestream'
+ s.name = 'apivideo_live_stream'
s.version = '0.0.1'
s.summary = 'A new flutter plugin project.'
s.description = <<-DESC
@@ -15,7 +15,7 @@ A new flutter plugin project.
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
- s.dependency 'LiveStreamIos', '~> 0.0.2'
+ s.dependency 'LiveStreamIos', '~> 0.0.4'
s.platform = :ios, '8.0'
# Flutter.framework does not contain a i386 slice.
diff --git a/lib/apivideo_live_stream.dart b/lib/apivideo_live_stream.dart
new file mode 100644
index 0000000..a2eb225
--- /dev/null
+++ b/lib/apivideo_live_stream.dart
@@ -0,0 +1,111 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+import 'src/types/audio_parameters.dart';
+import 'src/types/video_parameters.dart';
+
+export 'src/types.dart';
+
+class LiveStreamController {
+ static const MethodChannel _channel =
+ const MethodChannel('video.api.livestream/controller');
+ final Function()? onConnectionSuccess;
+ final Function(String)? onConnectionError;
+ final Function()? onDisconnection;
+
+ LiveStreamController({
+ this.onConnectionSuccess,
+ this.onConnectionError,
+ this.onDisconnection,
+ }) {
+ _channel.setMethodCallHandler(_methodCallHandler);
+ }
+
+ void setVideoParameters(VideoParameters videoParameters) {
+ _channel.invokeMethod('setVideoParameters', videoParameters.toJson());
+ }
+
+ void setAudioParameters(AudioParameters audioParameters) {
+ _channel.invokeMethod('setAudioParameters', audioParameters.toJson());
+ }
+
+ Future startStreaming({required String streamKey,
+ String url = "rtmp://broadcast.api.video/s/"}) {
+ return _channel.invokeMethod('startStreaming', {
+ 'streamKey': streamKey,
+ 'url': url,
+ });
+ }
+
+ void stopStreaming() {
+ _channel.invokeMethod('stopStreaming');
+ }
+
+ void switchCamera() {
+ _channel.invokeMethod('switchCamera');
+ }
+
+ void toggleMute() {
+ _channel.invokeMethod('toggleMute');
+ }
+
+ Future _methodCallHandler(MethodCall call) async {
+ switch (call.method) {
+ case "onConnectionSuccess":
+ if (onConnectionSuccess != null) {
+ onConnectionSuccess!();
+ }
+ break;
+ case "onConnectionFailed":
+ if (onConnectionError != null) {
+ String error = call.arguments;
+ onConnectionError!("$error");
+ }
+ break;
+ case "onDisconnect":
+ if (onDisconnection != null) {
+ onDisconnection!();
+ }
+ break;
+ }
+ }
+}
+
+class CameraPreview extends StatelessWidget {
+ final LiveStreamController controller;
+ final VideoParameters initialVideoParameters;
+ final AudioParameters initialAudioParameters;
+
+ const CameraPreview({required this.controller,
+ required this.initialAudioParameters,
+ required this.initialVideoParameters});
+
+ @override
+ Widget build(BuildContext context) {
+ final String viewType = '';
+ final Map creationParams = {
+ "audioParameters": initialAudioParameters.toJson(),
+ "videoParameters": initialVideoParameters.toJson()
+ };
+
+ switch (defaultTargetPlatform) {
+ case TargetPlatform.android:
+ return AndroidView(
+ viewType: viewType,
+ creationParamsCodec: const StandardMessageCodec(),
+ creationParams: creationParams);
+
+ case TargetPlatform.iOS:
+ return UiKitView(
+ viewType: viewType,
+ layoutDirection: TextDirection.ltr,
+ creationParamsCodec: const StandardMessageCodec(),
+ creationParams: creationParams);
+ default:
+ throw UnsupportedError("Unsupported platform view");
+ }
+ }
+}
diff --git a/lib/apivideolivestream.dart b/lib/apivideolivestream.dart
deleted file mode 100644
index 2b36f41..0000000
--- a/lib/apivideolivestream.dart
+++ /dev/null
@@ -1,151 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/rendering.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-
-class Apivideolivestream {
- static const MethodChannel _channel =
- const MethodChannel('apivideolivestream_0');
-
- static Future get platformVersion async {
- final String? version = await _channel.invokeMethod('getPlatformVersion');
- return version;
- }
-
- static Future startStream() async {
- print("start stream called");
- await _channel.invokeMethod('startStreaming');
- }
-
- static Future stopStream() async {
- print("stop stream called");
- await _channel.invokeMethod('stopStreaming');
- }
-
- static void switchCamera() async {
- await _channel.invokeMethod('switchCamera');
- }
-
- static void changeMute() async {
- await _channel.invokeMethod('changeMute');
- }
-}
-
-class LiveStreamPreview extends StatefulWidget {
- final Apivideolivestream controller;
- final String liveStreamKey;
- final String? rtmpServerUrl;
- final double? videoFps;
- final String? videoResolution;
- final double? videoBitrate;
- final String? videoCamera;
- final String? videoOrientation;
- final bool? audioMuted;
- final double? audioBitrate;
-
- const LiveStreamPreview({
- required this.controller,
- required this.liveStreamKey,
- this.rtmpServerUrl,
- this.videoFps,
- this.videoResolution,
- this.videoBitrate,
- this.videoCamera,
- this.videoOrientation,
- this.audioMuted,
- this.audioBitrate,
- });
-
- @override
- _LiveStreamPreviewState createState() => _LiveStreamPreviewState();
-}
-
-class _LiveStreamPreviewState extends State {
- late MethodChannel _channel;
- late Apivideolivestream _controller;
- Set _updateMap = {};
-
- createParams() {
- var param = {};
- param["liveStreamKey"] = widget.liveStreamKey;
- param["rtmpServerUrl"] = widget.rtmpServerUrl ?? 'rtmp://broadcast.api.video/s';
- param["videoFps"] = widget.videoFps ?? 30;
- param["videoResolution"] = widget.videoResolution ?? '720p';
- param["videoBitrate"] = widget.videoBitrate ?? -1;
- param["videoCamera"] = widget.videoCamera ?? "back";
- param["videoOrientation"] = widget.videoOrientation ?? 'landscape';
- param["audioMuted"] = widget.audioMuted ?? false;
- param["audioBitrate"] = widget.audioBitrate ?? -1;
- return param;
- }
-
- @override
- void initState() {
- _controller = widget.controller;
- if (widget.liveStreamKey.isNotEmpty) {
- Future.delayed(const Duration(milliseconds: 300)).then((value) {
- _channel.invokeMethod('setLivestreamKey', widget.liveStreamKey);
- });
- }
- Future.delayed(const Duration(milliseconds: 300)).then((value) {
- _channel.invokeMethod('setParam', json.encode(createParams()));
- });
- super.initState();
- }
-
- @override
- Widget build(BuildContext context) {
- final String viewType = '';
- // Pass parameters to the platform side.
-
- switch (defaultTargetPlatform) {
- case TargetPlatform.android:
- return SizedBox(
- height: 400,
- child: PlatformViewLink(
- viewType: viewType,
- surfaceFactory: (BuildContext context, dynamic controller) {
- return AndroidViewSurface(
- controller: controller,
- gestureRecognizers: const <
- Factory>{},
- hitTestBehavior: PlatformViewHitTestBehavior.opaque,
- );
- },
- onCreatePlatformView: (PlatformViewCreationParams params) {
- return PlatformViewsService.initSurfaceAndroidView(
- id: params.id,
- viewType: viewType,
- layoutDirection: TextDirection.ltr,
- creationParams: createParams(),
- creationParamsCodec: StandardMessageCodec(),
- )
- ..addOnPlatformViewCreatedListener((id) {
- _channel = MethodChannel('apivideolivestream_$id');
- //_channel.setMethodCallHandler(_handlerCall);
- })
- ..create();
- },
- ));
-
- case TargetPlatform.iOS:
- return SizedBox(
- height: 400,
- child: UiKitView(
- viewType: viewType,
- layoutDirection: TextDirection.ltr,
- creationParams: createParams(),
- onPlatformViewCreated: (viewId) {
- _channel = MethodChannel('apivideolivestream_$viewId');
- },
- creationParamsCodec: const StandardMessageCodec(),
- ),
- );
- default:
- throw UnsupportedError("Unsupported platform view");
- }
- }
-}
diff --git a/lib/src/types.dart b/lib/src/types.dart
new file mode 100644
index 0000000..4910699
--- /dev/null
+++ b/lib/src/types.dart
@@ -0,0 +1,5 @@
+export 'types/audio_parameters.dart';
+export 'types/channel.dart';
+export 'types/resolution.dart';
+export 'types/sample_rate.dart';
+export 'types/video_parameters.dart';
\ No newline at end of file
diff --git a/lib/src/types/audio_parameters.dart b/lib/src/types/audio_parameters.dart
new file mode 100644
index 0000000..27371b4
--- /dev/null
+++ b/lib/src/types/audio_parameters.dart
@@ -0,0 +1,27 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'channel.dart';
+import 'sample_rate.dart';
+
+part 'audio_parameters.g.dart';
+
+@JsonSerializable()
+class AudioParameters {
+ int bitrate;
+ Channel channel;
+ SampleRate sampleRate;
+ bool enableEchoCanceler;
+ bool enableNoiseSuppressor;
+
+ AudioParameters(
+ {required this.bitrate,
+ required this.channel,
+ required this.sampleRate,
+ this.enableEchoCanceler = true,
+ this.enableNoiseSuppressor = true});
+
+ factory AudioParameters.fromJson(Map json) =>
+ _$AudioParametersFromJson(json);
+
+ Map toJson() => _$AudioParametersToJson(this);
+}
diff --git a/lib/src/types/audio_parameters.g.dart b/lib/src/types/audio_parameters.g.dart
new file mode 100644
index 0000000..4ada126
--- /dev/null
+++ b/lib/src/types/audio_parameters.g.dart
@@ -0,0 +1,38 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'audio_parameters.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+AudioParameters _$AudioParametersFromJson(Map json) =>
+ AudioParameters(
+ bitrate: json['bitrate'] as int,
+ channel: $enumDecode(_$ChannelEnumMap, json['channel']),
+ sampleRate: $enumDecode(_$SampleRateEnumMap, json['sampleRate']),
+ enableEchoCanceler: json['enableEchoCanceler'] as bool? ?? true,
+ enableNoiseSuppressor: json['enableNoiseSuppressor'] as bool? ?? true,
+ );
+
+Map _$AudioParametersToJson(AudioParameters instance) =>
+ {
+ 'bitrate': instance.bitrate,
+ 'channel': _$ChannelEnumMap[instance.channel],
+ 'sampleRate': _$SampleRateEnumMap[instance.sampleRate],
+ 'enableEchoCanceler': instance.enableEchoCanceler,
+ 'enableNoiseSuppressor': instance.enableNoiseSuppressor,
+ };
+
+const _$ChannelEnumMap = {
+ Channel.stereo: 'stereo',
+ Channel.mono: 'mono',
+};
+
+const _$SampleRateEnumMap = {
+ SampleRate.kHz_8: 8000,
+ SampleRate.kHz_16: 16000,
+ SampleRate.kHz_32: 32000,
+ SampleRate.kHz_44_1: 44100,
+ SampleRate.kHz_48: 48000,
+};
diff --git a/lib/src/types/channel.dart b/lib/src/types/channel.dart
new file mode 100644
index 0000000..a2da8d3
--- /dev/null
+++ b/lib/src/types/channel.dart
@@ -0,0 +1,8 @@
+import 'package:json_annotation/json_annotation.dart';
+
+enum Channel {
+ @JsonValue("stereo")
+ stereo,
+ @JsonValue("mono")
+ mono,
+}
diff --git a/lib/src/types/resolution.dart b/lib/src/types/resolution.dart
new file mode 100644
index 0000000..4eab113
--- /dev/null
+++ b/lib/src/types/resolution.dart
@@ -0,0 +1,16 @@
+import 'package:json_annotation/json_annotation.dart';
+
+enum Resolution {
+ @JsonValue("240p")
+ RESOLUTION_240,
+ @JsonValue("360p")
+ RESOLUTION_360,
+ @JsonValue("480p")
+ RESOLUTION_480,
+ @JsonValue("720p")
+ RESOLUTION_720,
+ @JsonValue("1080")
+ RESOLUTION_1080,
+ @JsonValue("2160p")
+ RESOLUTION_2160,
+}
diff --git a/lib/src/types/sample_rate.dart b/lib/src/types/sample_rate.dart
new file mode 100644
index 0000000..f36c801
--- /dev/null
+++ b/lib/src/types/sample_rate.dart
@@ -0,0 +1,14 @@
+import 'package:json_annotation/json_annotation.dart';
+
+enum SampleRate {
+ @JsonValue(8000)
+ kHz_8,
+ @JsonValue(16000)
+ kHz_16,
+ @JsonValue(32000)
+ kHz_32,
+ @JsonValue(44100)
+ kHz_44_1,
+ @JsonValue(48000)
+ kHz_48,
+}
diff --git a/lib/src/types/video_parameters.dart b/lib/src/types/video_parameters.dart
new file mode 100644
index 0000000..4e07752
--- /dev/null
+++ b/lib/src/types/video_parameters.dart
@@ -0,0 +1,20 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import 'resolution.dart';
+
+part 'video_parameters.g.dart';
+
+@JsonSerializable()
+class VideoParameters {
+ int bitrate;
+ Resolution resolution;
+ int fps;
+
+ VideoParameters(
+ {required this.bitrate, required this.resolution, required this.fps});
+
+ factory VideoParameters.fromJson(Map json) =>
+ _$VideoParametersFromJson(json);
+
+ Map toJson() => _$VideoParametersToJson(this);
+}
diff --git a/lib/src/types/video_parameters.g.dart b/lib/src/types/video_parameters.g.dart
new file mode 100644
index 0000000..4b1593b
--- /dev/null
+++ b/lib/src/types/video_parameters.g.dart
@@ -0,0 +1,30 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'video_parameters.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+VideoParameters _$VideoParametersFromJson(Map json) =>
+ VideoParameters(
+ bitrate: json['bitrate'] as int,
+ resolution: $enumDecode(_$ResolutionEnumMap, json['resolution']),
+ fps: json['fps'] as int,
+ );
+
+Map _$VideoParametersToJson(VideoParameters instance) =>
+ {
+ 'bitrate': instance.bitrate,
+ 'resolution': _$ResolutionEnumMap[instance.resolution],
+ 'fps': instance.fps,
+ };
+
+const _$ResolutionEnumMap = {
+ Resolution.RESOLUTION_240: '240p',
+ Resolution.RESOLUTION_360: '360p',
+ Resolution.RESOLUTION_480: '480p',
+ Resolution.RESOLUTION_720: '720p',
+ Resolution.RESOLUTION_1080: '1080',
+ Resolution.RESOLUTION_2160: '2160p',
+};
diff --git a/pubspec.lock b/pubspec.lock
deleted file mode 100644
index 4935c14..0000000
--- a/pubspec.lock
+++ /dev/null
@@ -1,147 +0,0 @@
-# Generated by pub
-# See https://dart.dev/tools/pub/glossary#lockfile
-packages:
- async:
- dependency: transitive
- description:
- name: async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.8.1"
- boolean_selector:
- dependency: transitive
- description:
- name: boolean_selector
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- characters:
- dependency: transitive
- description:
- name: characters
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- charcode:
- dependency: transitive
- description:
- name: charcode
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.1"
- clock:
- dependency: transitive
- description:
- name: clock
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- collection:
- dependency: transitive
- description:
- name: collection
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.15.0"
- fake_async:
- dependency: transitive
- description:
- name: fake_async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.0"
- flutter:
- dependency: "direct main"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_test:
- dependency: "direct dev"
- description: flutter
- source: sdk
- version: "0.0.0"
- matcher:
- dependency: transitive
- description:
- name: matcher
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.12.10"
- meta:
- dependency: transitive
- description:
- name: meta
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.7.0"
- path:
- dependency: transitive
- description:
- name: path
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.0"
- sky_engine:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.99"
- source_span:
- dependency: transitive
- description:
- name: source_span
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.1"
- stack_trace:
- dependency: transitive
- description:
- name: stack_trace
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.10.0"
- stream_channel:
- dependency: transitive
- description:
- name: stream_channel
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- string_scanner:
- dependency: transitive
- description:
- name: string_scanner
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- term_glyph:
- dependency: transitive
- description:
- name: term_glyph
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.0"
- test_api:
- dependency: transitive
- description:
- name: test_api
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.4.2"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.0"
- vector_math:
- dependency: transitive
- description:
- name: vector_math
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
-sdks:
- dart: ">=2.12.0 <3.0.0"
- flutter: ">=1.20.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index bf536ff..63ad48c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,10 @@
-name: apivideolivestream
-description: A new flutter plugin project.
-version: 0.0.1
-homepage:
+name: apivideo_live_stream
+description: An easy way to broadcast livestream on api.video platform as well as other RTMP platforms.
+version: 0.1.0
+repository: https://github.com/apivideo/api.video-flutter-live-stream
+issue_tracker: https://github.com/apivideo/api.video-flutter-live-stream/issues
+homepage: https://api.video
+documentation: https://docs.api.video
environment:
sdk: ">=2.12.0 <3.0.0"
@@ -10,10 +13,14 @@ environment:
dependencies:
flutter:
sdk: flutter
+ json_annotation: ^4.4.0
+
dev_dependencies:
flutter_test:
sdk: flutter
+ build_runner: ^2.1.7
+ json_serializable: ^6.1.3
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
@@ -27,10 +34,10 @@ flutter:
plugin:
platforms:
android:
- package: video.api.eco.flt.livestream.apivideolivestream
- pluginClass: ApivideolivestreamPlugin
+ package: video.api.flutter.livestream
+ pluginClass: ApiVideoLiveStreamPlugin
ios:
- pluginClass: ApivideolivestreamPlugin
+ pluginClass: ApiVideoLiveStreamPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
diff --git a/test/apivideolivestream_test.dart b/test/apivideo_live_stream_test.dart
similarity index 70%
rename from test/apivideolivestream_test.dart
rename to test/apivideo_live_stream_test.dart
index 73988a6..1604b5d 100644
--- a/test/apivideolivestream_test.dart
+++ b/test/apivideo_live_stream_test.dart
@@ -1,6 +1,5 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:apivideolivestream/apivideolivestream.dart';
void main() {
const MethodChannel channel = MethodChannel('apivideolivestream');
@@ -16,8 +15,4 @@ void main() {
tearDown(() {
channel.setMockMethodCallHandler(null);
});
-
- test('getPlatformVersion', () async {
- expect(await Apivideolivestream.platformVersion, '42');
- });
}