diff --git a/android/build.gradle b/android/build.gradle index 0fea6ef..de0c139 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,10 +2,10 @@ group 'net.ossrs.flutter_live' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.7.10' repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -17,7 +17,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } @@ -25,7 +25,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 29 + compileSdkVersion 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 01a286e..1c5f490 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri May 10 08:31:03 CST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 20ef635..20d793e 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,12 +22,8 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 29 + compileSdkVersion 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -40,7 +37,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "net.ossrs.flutter_live_example" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } @@ -63,5 +60,5 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 886b12a..a4c64c5 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -6,11 +6,12 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + diff --git a/example/android/build.gradle b/example/android/build.gradle index 3100ad2..1e73c4f 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,20 +1,8 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} allprojects { repositories { google() - jcenter() + mavenCentral() } } @@ -26,6 +14,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146..cfe88f6 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..127a17e 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,35 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ':app' -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +//def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +//def properties = new Properties() +// +//assert localPropertiesFile.exists() +//localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } +// +//def flutterSdkPath = properties.getProperty("flutter.sdk") +//assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +//apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf..8c6e561 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 12.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 9411102..10f3c9b 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '10.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9af9c1f..37cd782 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,54 +1,42 @@ PODS: - - BIJKPlayer (0.7.15) - - camera_with_rtmp (0.0.1): - - Flutter - - HaishinKit (~> 1.0.10) - - fijkplayer (0.8.8): - - BIJKPlayer (~> 0.7.10) + - BIJKPlayer (0.7.16) + - fijkplayer (0.11.0): + - BIJKPlayer (~> 0.7.16) - Flutter - Flutter (1.0.0) - flutter_live (0.0.1): - Flutter - - flutter_webrtc (0.2.2): + - flutter_webrtc (0.9.36): - Flutter - - GoogleWebRTC (= 1.1.31999) - - Libyuv (= 1703) - - GoogleWebRTC (1.1.31999) - - HaishinKit (1.0.13): - - Logboard (~> 2.1.3) - - Libyuv (1703) - - Logboard (2.1.3) + - WebRTC-SDK (= 114.5735.10) - package_info (0.0.1): - Flutter - - path_provider (0.0.1): + - path_provider_foundation (0.0.1): - Flutter - - shared_preferences (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): - Flutter - - url_launcher (0.0.1): + - FlutterMacOS + - url_launcher_ios (0.0.1): - Flutter + - WebRTC-SDK (114.5735.10) DEPENDENCIES: - - camera_with_rtmp (from `.symlinks/plugins/camera_with_rtmp/ios`) - fijkplayer (from `.symlinks/plugins/fijkplayer/ios`) - Flutter (from `Flutter`) - flutter_live (from `.symlinks/plugins/flutter_live/ios`) - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - - path_provider (from `.symlinks/plugins/path_provider/ios`) - - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: - BIJKPlayer - - GoogleWebRTC - - HaishinKit - - Libyuv - - Logboard + - WebRTC-SDK EXTERNAL SOURCES: - camera_with_rtmp: - :path: ".symlinks/plugins/camera_with_rtmp/ios" fijkplayer: :path: ".symlinks/plugins/fijkplayer/ios" Flutter: @@ -59,29 +47,25 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_webrtc/ios" package_info: :path: ".symlinks/plugins/package_info/ios" - path_provider: - :path: ".symlinks/plugins/path_provider/ios" - shared_preferences: - :path: ".symlinks/plugins/shared_preferences/ios" - url_launcher: - :path: ".symlinks/plugins/url_launcher/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - BIJKPlayer: 37fd8033360eedfb1afc3658e14fbf5a25ace540 - camera_with_rtmp: 606d566f89a21f1fed15ffda99cb247304beccd4 - fijkplayer: 0d3793a2822d030ef5bba77f904bff1f7a91a115 - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + BIJKPlayer: 4c5d66e5cb99ae5bade6f22a4fcc031722a81c64 + fijkplayer: 8b6d864948dc28e6e07248ed4fce61a7467db4df + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_live: efecc5d284c5a3721d436eebd5ea4322eef31a08 - flutter_webrtc: 39898454258b54ba51996850d5da8d5d53bf1524 - GoogleWebRTC: b39a78c4f5cc6b0323415b9233db03a2faa7b0f0 - HaishinKit: d1786896ace24065d466bde02356c5352ac001b0 - Libyuv: 5f79ced0ee66e60a612ca97de1e6ccacd187a437 - Logboard: e74ec2d9941abd22f4db3a3ddcd0735011df92c7 + flutter_webrtc: b33475c3a57d59ff05bf87b4f5d3feceac63f291 package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586 + WebRTC-SDK: 8c0edd05b880a39648118192c252667ea06dea51 -PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea +PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b -COCOAPODS: 1.10.2 +COCOAPODS: 1.15.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 5a41477..26b8bbb 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -199,10 +199,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -230,6 +232,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -339,7 +342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -355,6 +358,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4X75RZ9C85; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -369,7 +373,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = net.ossrs.flutterLive; + PRODUCT_BUNDLE_IDENTIFIER = net.ossrs.flutterLive2; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -424,7 +428,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -473,7 +477,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -491,6 +495,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4X75RZ9C85; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -505,7 +510,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = net.ossrs.flutterLive; + PRODUCT_BUNDLE_IDENTIFIER = net.ossrs.flutterLive2; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -521,6 +526,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4X75RZ9C85; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -535,7 +541,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = net.ossrs.flutterLive; + PRODUCT_BUNDLE_IDENTIFIER = net.ossrs.flutterLive2; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..5e31d3d 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -22,6 +24,12 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSCameraUsageDescription + $(PRODUCT_NAME) Camera Usage! + NSMicrophoneUsageDescription + $(PRODUCT_NAME) Microphone Usage! + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +49,5 @@ UIViewControllerBasedStatusBarAppearance - NSCameraUsageDescription - $(PRODUCT_NAME) Camera Usage! - NSMicrophoneUsageDescription - $(PRODUCT_NAME) Microphone Usage! diff --git a/example/lib/main.dart b/example/lib/main.dart index 1784065..7273f2b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,14 +6,14 @@ import 'dart:io' show Platform; import 'package:flutter_live/flutter_live.dart' as flutter_live; import 'package:flutter_webrtc/flutter_webrtc.dart' as webrtc; import 'package:fijkplayer/fijkplayer.dart' as fijkplayer; -import 'package:camera_with_rtmp/camera.dart' as camera; +// import 'package:camera_with_rtmp/camera.dart' as camera; import 'package:shared_preferences/shared_preferences.dart'; import 'privacy.dart'; void main() { runApp(MaterialApp( - debugShowCheckedModeBanner: false, - home: Platform.isAndroid? Privacy() : Home(), + debugShowCheckedModeBanner: false, + home: Platform.isAndroid ? Privacy() : Home(), )); } @@ -24,17 +24,18 @@ class Home extends StatefulWidget { class _HomeState extends State { // Platform information. - PackageInfo _info = PackageInfo(version: '0.0.0', buildNumber: '0'); + PackageInfo _info = PackageInfo( + appName: '', packageName: '', version: '0.0.0', buildNumber: '0'); // The url to play or publish. - String _url; // The final url, should equals to controller.text + String _url = ''; // The final url, should equals to controller.text final TextEditingController _urlController = TextEditingController(); // For publisher. bool _isPublish = false; bool _isPublishing = false; // The controller for publisher. - camera.CameraController _cameraController = null; + // camera.CameraController? _cameraController = null; @override Widget build(BuildContext context) { @@ -47,8 +48,9 @@ class _HomeState extends State { body: Container( child: ListView(children: [ UrlInputDisplay(_urlController), - ControlDisplay(isUrlValid(), _onStartPlayOrPublish, _isPublish, _isPublishing, _onSwitchPublish), - CameraDisplay(_isPublish, _cameraController), + ControlDisplay(isUrlValid(), _onStartPlayOrPublish, _isPublish, + _isPublishing, _onSwitchPublish), + // CameraDisplay(_isPublish, _cameraController!), DemoUrlsDisplay(_url, _onUserSelectUrl, _isPublish), PlatformDisplay(_info), ]), @@ -64,7 +66,9 @@ class _HomeState extends State { _urlController.addListener(_onUserEditUrl); PackageInfo.fromPlatform().then((info) { - setState(() { _info = info; }); + setState(() { + _info = info; + }); }).catchError((e) { print('Platform error $e'); }); @@ -87,34 +91,34 @@ class _HomeState extends State { } } - void _onUserSelectUrl(String v) { + void _onUserSelectUrl(String? v) { print('User select $v, url=$_url, text=${_urlController.text}'); if (_url != v) { setState(() { - _urlController.text = _url = v; + _urlController.text = _url = v!; }); } } bool isUrlValid() { - return _url != null && _url.contains('://'); + return _url.contains('://'); } void disposeCamera() async { - if (_cameraController == null) { - return; - } + // if (_cameraController == null) { + // return; + // } _isPublishing = false; - await _cameraController.stopVideoStreaming(); - await _cameraController.dispose(); - _cameraController = null; + // await _cameraController?.stopVideoStreaming(); + // await _cameraController?.dispose(); + // _cameraController = null; print('Camera disposed, publish=$_isPublish, publishing=$_isPublishing'); } void stopPublish() async { - await disposeCamera(); - setState(() { }); - print('Stop publish url=$_url, publishing=$_isPublishing, controller=${_cameraController?.value.isInitialized}'); + disposeCamera(); + setState(() {}); + print('Stop publish url=$_url, publishing=$_isPublishing'); } void _onStartPlayOrPublish(BuildContext context) async { @@ -123,12 +127,15 @@ class _HomeState extends State { return; } - print('${_isPublishing? "Stop":""} ${_isPublish? "Publish":"Play"} url=$_url, publishing=$_isPublishing'); + print( + '${_isPublishing ? "Stop" : ""} ${_isPublish ? "Publish" : "Play"} url=$_url, publishing=$_isPublishing'); // For player. if (!_isPublish) { Navigator.push(context, MaterialPageRoute(builder: (context) { - return _url.startsWith('webrtc://')? WebRTCStreamingPlayer(_url) : LiveStreamingPlayer(_url); + return _url.startsWith('webrtc://') + ? WebRTCStreamingPlayer(_url) + : LiveStreamingPlayer(_url); })); return; } @@ -143,33 +150,38 @@ class _HomeState extends State { if (_url.startsWith('rtmp://')) { stopPublish(); - var cameras = await camera.availableCameras(); - if (cameras.isEmpty) { - print('Error: No cameras'); - return; - } - - camera.CameraDescription desc = cameras[0]; - for (var c in cameras) { - if (c.lensDirection == camera.CameraLensDirection.front) { - desc = c; - break; - } - } - print('Use camera ${desc.name} ${desc.lensDirection}'); - - _cameraController = camera.CameraController(desc, camera.ResolutionPreset.low); - _cameraController.addListener(() { - setState(() { print('got camera event'); }); - }); - - await _cameraController.initialize(); - print('Camera initialized ok'); - - await _cameraController.startVideoStreaming(_url, bitrate: 300 * 1000); + // var cameras = await camera.availableCameras(); + // if (cameras.isEmpty) { + // print('Error: No cameras'); + // return; + // } + + // camera.CameraDescription desc = cameras[0]; + // for (var c in cameras) { + // if (c.lensDirection == camera.CameraLensDirection.front) { + // desc = c; + // break; + // } + // } + // print('Use camera ${desc.name} ${desc.lensDirection}'); + + // _cameraController = + // camera.CameraController(desc, camera.ResolutionPreset.low); + // _cameraController?.addListener(() { + // setState(() { + // print('got camera event'); + // }); + // }); + + // await _cameraController?.initialize(); + // print('Camera initialized ok'); + + // await _cameraController?.startVideoStreaming(_url, bitrate: 300 * 1000); print('Start streaming to $_url'); - setState(() { _isPublishing = true; }); + setState(() { + _isPublishing = true; + }); } } @@ -177,7 +189,9 @@ class _HomeState extends State { if (!v) { stopPublish(); } - setState(() { _isPublish = v; }); + setState(() { + _isPublish = v; + }); } } @@ -189,9 +203,10 @@ class UrlInputDisplay extends StatelessWidget { Widget build(BuildContext context) { return Container( child: TextField( - controller: _controller, autofocus: false, - decoration: InputDecoration(hintText: 'Please select or input url...') - ), + controller: _controller, + autofocus: false, + decoration: + InputDecoration(hintText: 'Please select or input url...')), padding: EdgeInsets.fromLTRB(5, 0, 5, 0), ); } @@ -199,86 +214,159 @@ class UrlInputDisplay extends StatelessWidget { class DemoUrlsDisplay extends StatelessWidget { final String _url; - final ValueChanged _onUserSelectUrl; + final ValueChanged _onUserSelectUrl; final bool _isPublish; DemoUrlsDisplay(this._url, this._onUserSelectUrl, this._isPublish); @override Widget build(BuildContext context) { - return Container(child: - _isPublish? Column(children: [ - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtmp_publish, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtmp_publish, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtmp_publish2, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish2), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtmp_publish2, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ],) : Column(children: [ - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtmp, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtmp, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HLS', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.hls, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hls), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.hls, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HTTP-FLV', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.flv, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flv), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.flv, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('WebRTC', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtc, style: TextStyle(color: Colors.grey[500], fontSize: 15)), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtc), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtc, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HTTPS-FLV', style: TextStyle(fontWeight: FontWeight.bold)), - Container( - child: Text(flutter_live.FlutterLive.flvs, style: TextStyle(color: Colors.grey[500], fontSize: 15)), - padding: EdgeInsets.only(top: 3, bottom:3), - ), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flvs), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.flvs, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HTTPS-HLS', style: TextStyle(fontWeight: FontWeight.bold)), - Container( - child: Text(flutter_live.FlutterLive.hlss, style: TextStyle(color: Colors.grey[500], fontSize: 14)), - padding: EdgeInsets.only(top: 3, bottom: 3), - ), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hlss), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.hlss, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ]), + return Container( + child: _isPublish + ? Column( + children: [ + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('RTMP', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtmp_publish, + style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => + _onUserSelectUrl!(flutter_live.FlutterLive.rtmp_publish), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.rtmp_publish, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('RTMP', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtmp_publish2, + style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => + _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish2), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.rtmp_publish2, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ], + ) + : Column(children: [ + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('RTMP', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtmp, + style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl!(flutter_live.FlutterLive.rtmp), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.rtmp, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('HLS', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.hls, + style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl!(flutter_live.FlutterLive.hls), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.hls, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('HTTP-FLV', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.flv, + style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl!(flutter_live.FlutterLive.flv), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.flv, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('WebRTC', + style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtc, + style: + TextStyle(color: Colors.grey[500], fontSize: 15)), + ]), + onTap: () => _onUserSelectUrl!(flutter_live.FlutterLive.rtc), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.rtc, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('HTTPS-FLV', + style: TextStyle(fontWeight: FontWeight.bold)), + Container( + child: Text(flutter_live.FlutterLive.flvs, + style: TextStyle( + color: Colors.grey[500], fontSize: 15)), + padding: EdgeInsets.only(top: 3, bottom: 3), + ), + ]), + onTap: () => _onUserSelectUrl!(flutter_live.FlutterLive.flvs), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.flvs, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('HTTPS-HLS', + style: TextStyle(fontWeight: FontWeight.bold)), + Container( + child: Text(flutter_live.FlutterLive.hlss, + style: TextStyle( + color: Colors.grey[500], fontSize: 14)), + padding: EdgeInsets.only(top: 3, bottom: 3), + ), + ]), + onTap: () => _onUserSelectUrl!(flutter_live.FlutterLive.hlss), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.hlss, + groupValue: _url, + onChanged: _onUserSelectUrl), + ), + ]), ); } } @@ -289,7 +377,8 @@ class ControlDisplay extends StatelessWidget { final bool _isPubslish; final bool _isPublishing; final ValueChanged _onSwitchPublish; - ControlDisplay(this._urlAvailable, this._onStartPlayOrPublish, this._isPubslish, this._isPublishing, this._onSwitchPublish); + ControlDisplay(this._urlAvailable, this._onStartPlayOrPublish, + this._isPubslish, this._isPublishing, this._onSwitchPublish); @override Widget build(BuildContext context) { @@ -297,7 +386,7 @@ class ControlDisplay extends StatelessWidget { if (_isPublishing) { return 'Stop'; } - return _isPubslish? 'Publish' : 'Play'; + return _isPubslish ? 'Publish' : 'Play'; }; return Row( @@ -311,9 +400,9 @@ class ControlDisplay extends StatelessWidget { ), Container( width: 120, - child:ElevatedButton( + child: ElevatedButton( child: Text(actionText()), - onPressed: _urlAvailable? _onStartPlayOrPublish : null, + onPressed: _urlAvailable ? _onStartPlayOrPublish : null, ), ), ], @@ -321,33 +410,35 @@ class ControlDisplay extends StatelessWidget { } } -class CameraDisplay extends StatelessWidget { - final bool _isPublish; - final camera.CameraController _cameraController; - CameraDisplay(this._isPublish, this._cameraController); - - @override - Widget build(BuildContext context) { - if (!_isPublish) { - return Container(); - } - - if (_cameraController == null) { - return Container(); - } - - if (!_cameraController.value.isInitialized) { - return Container(child: Center(child: Text( - 'Camera not available', style: TextStyle(color: Colors.red[500]), - ))); - } - - return AspectRatio( - aspectRatio: _cameraController.value.aspectRatio, - child: camera.CameraPreview(_cameraController) - ); - } -} +// class CameraDisplay extends StatelessWidget { +// final bool _isPublish; +// final camera.CameraController _cameraController; +// CameraDisplay(this._isPublish, this._cameraController); + +// @override +// Widget build(BuildContext context) { +// if (!_isPublish) { +// return Container(); +// } + +// if (_cameraController == null) { +// return Container(); +// } + +// if (!_cameraController.value.isInitialized) { +// return Container( +// child: Center( +// child: Text( +// 'Camera not available', +// style: TextStyle(color: Colors.red[500]), +// ))); +// } + +// return AspectRatio( +// aspectRatio: _cameraController.value.aspectRatio, +// child: camera.CameraPreview(_cameraController)); +// } +// } class PlatformDisplay extends StatelessWidget { final PackageInfo _info; @@ -361,10 +452,11 @@ class PlatformDisplay extends StatelessWidget { Text.rich( TextSpan( text: 'SRS/v${_info.version}+${_info.buildNumber}', - recognizer: TapGestureRecognizer() ..onTap = () async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setBool("login", false); - }, + recognizer: TapGestureRecognizer() + ..onTap = () async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setBool("login", false); + }, ), ), ], @@ -381,16 +473,17 @@ class LiveStreamingPlayer extends StatefulWidget { } class _LiveStreamingPlayerState extends State { - final flutter_live.RealtimePlayer _player = flutter_live.RealtimePlayer(fijkplayer.FijkPlayer()); + final flutter_live.RealtimePlayer _player = + flutter_live.RealtimePlayer(fijkplayer.FijkPlayer()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('SRS Live Streaming')), body: fijkplayer.FijkView( - player: _player.fijk, panelBuilder: fijkplayer.fijkPanel2Builder(), - fsFit: fijkplayer.FijkFit.fill - ), + player: _player.fijk, + panelBuilder: fijkplayer.fijkPanel2Builder(), + fsFit: fijkplayer.FijkFit.fill), ); } @@ -430,9 +523,11 @@ class _WebRTCStreamingPlayerState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('SRS WebRTC Streaming')), - body: GestureDetector(onTap: _switchLoudspeaker, child: Container( - child: webrtc.RTCVideoView(_video), decoration: BoxDecoration(color: Colors.grey[500]) - )), + body: GestureDetector( + onTap: _switchLoudspeaker, + child: Container( + child: webrtc.RTCVideoView(_video), + decoration: BoxDecoration(color: Colors.grey[500]))), ); } @@ -449,7 +544,9 @@ class _WebRTCStreamingPlayerState extends State { // Render stream when got remote stream. _player.onRemoteStream = (webrtc.MediaStream stream) { // @remark It's very important to use setState to set the srcObject and notify render. - setState(() { _video.srcObject = stream; }); + setState(() { + _video.srcObject = stream; + }); }; // Auto start play WebRTC streaming. @@ -457,7 +554,8 @@ class _WebRTCStreamingPlayerState extends State { } void _switchLoudspeaker() { - print('setSpeakerphoneOn: $_loudspeaker(${_loudspeaker? "Loudspeaker":"Earpiece"})'); + print( + 'setSpeakerphoneOn: $_loudspeaker(${_loudspeaker ? "Loudspeaker" : "Earpiece"})'); flutter_live.FlutterLive.setSpeakerphoneOn(_loudspeaker); _loudspeaker = !_loudspeaker; } @@ -469,4 +567,3 @@ class _WebRTCStreamingPlayerState extends State { _player.dispose(); } } - diff --git a/example/pubspec.yaml b/example/pubspec.yaml index a7eb10d..02ab851 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,10 +4,10 @@ version: 1.0.9+1 # The following line prevents the package from being accidentally published to # pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -28,7 +28,7 @@ dependencies: shared_preferences: ^2.0.6 # To get the version in this file. - package_info: '>=0.4.0 <2.0.0' + package_info: ">=0.4.0 <2.0.0" dev_dependencies: flutter_test: @@ -39,7 +39,6 @@ dev_dependencies: # The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/lib/flutter_live.dart b/lib/flutter_live.dart index 6cd7607..9b313de 100644 --- a/lib/flutter_live.dart +++ b/lib/flutter_live.dart @@ -20,7 +20,8 @@ class FlutterLive { /// Set the speaker phone on. // [enabled] Use Earpiece if false, or Loudspeaker if true. static Future setSpeakerphoneOn(bool enabled) async { - await _channel.invokeMethod('setSpeakerphoneOn', {'enabled': enabled}); + await _channel.invokeMethod( + 'setSpeakerphoneOn', {'enabled': enabled}); } /// RTMP demo stream by https://ossrs.net/ @@ -78,19 +79,29 @@ class RealtimePlayer { Future play(String url) async { print('Start play live streaming $url'); - await _player.setOption(fijkplayer.FijkOption.playerCategory, "mediacodec-all-videos", 1); - await _player.setOption(fijkplayer.FijkOption.hostCategory, "request-screen-on", 1); - await _player.setOption(fijkplayer.FijkOption.hostCategory, "request-audio-focus", 1); + await _player.setOption( + fijkplayer.FijkOption.playerCategory, "mediacodec-all-videos", 1); + await _player.setOption( + fijkplayer.FijkOption.hostCategory, "request-screen-on", 1); + await _player.setOption( + fijkplayer.FijkOption.hostCategory, "request-audio-focus", 1); // Live low-latency: https://www.jianshu.com/p/d6a5d8756eec // For all options, read https://github.com/Bilibili/ijkplayer/blob/master/ijkmedia/ijkplayer/ff_ffplay_options.h - await _player.setOption(fijkplayer.FijkOption.formatCategory, "probesize", 16 * 1024); // in bytes - await _player.setOption(fijkplayer.FijkOption.formatCategory, "analyzeduration", 100 * 1000); // in us - await _player.setOption(fijkplayer.FijkOption.playerCategory, "packet-buffering", 0); // 0, no buffer. - await _player.setOption(fijkplayer.FijkOption.playerCategory, "max_cached_duration", 800); // in ms - await _player.setOption(fijkplayer.FijkOption.playerCategory, "max-buffer-size", 32 * 1024); // in bytes - await _player.setOption(fijkplayer.FijkOption.playerCategory, "infbuf", 1); // 1 for realtime. - await _player.setOption(fijkplayer.FijkOption.playerCategory, "min-frames", 1); // in frames + await _player.setOption(fijkplayer.FijkOption.formatCategory, "probesize", + 16 * 1024); // in bytes + await _player.setOption(fijkplayer.FijkOption.formatCategory, + "analyzeduration", 100 * 1000); // in us + await _player.setOption(fijkplayer.FijkOption.playerCategory, + "packet-buffering", 0); // 0, no buffer. + await _player.setOption(fijkplayer.FijkOption.playerCategory, + "max_cached_duration", 800); // in ms + await _player.setOption(fijkplayer.FijkOption.playerCategory, + "max-buffer-size", 32 * 1024); // in bytes + await _player.setOption( + fijkplayer.FijkOption.playerCategory, "infbuf", 1); // 1 for realtime. + await _player.setOption( + fijkplayer.FijkOption.playerCategory, "min-frames", 1); // in frames await _player.setDataSource(url, autoPlay: true).catchError((e) { print("setDataSource error: $e"); @@ -110,9 +121,10 @@ class RealtimePlayer { /// streamUrl: "webrtc://d.ossrs.net:11985/live/livestream" class WebRTCUri { /// The api server url for WebRTC streaming. - String api; + late String api; + /// The stream url to play or publish. - String streamUrl; + late String streamUrl; /// Parse the url to WebRTC uri. static WebRTCUri parse(String url) { @@ -120,21 +132,23 @@ class WebRTCUri { var schema = 'https'; // For native, default to HTTPS if (uri.queryParameters.containsKey('schema')) { - schema = uri.queryParameters['schema']; + schema = uri.queryParameters['schema']!; } else { schema = 'https'; } - var port = (uri.port > 0)? uri.port : 443; + schema = 'http'; + + var port = (uri.port > 0) ? uri.port : 443; if (schema == 'https') { - port = (uri.port > 0)? uri.port : 443; + port = (uri.port > 0) ? uri.port : 443; } else if (schema == 'http') { - port = (uri.port > 0)? uri.port : 1985; + port = (uri.port > 0) ? uri.port : 1985; } var api = '/rtc/v1/play/'; if (uri.queryParameters.containsKey('play')) { - api = uri.queryParameters['play']; + api = uri.queryParameters['play']!; } var apiParams = []; @@ -157,25 +171,26 @@ class WebRTCUri { } } +typedef AddStreamCallback = void Function(webrtc.MediaStream stream); + /// A WebRTC player, using [flutter_webrtc](https://pub.dev/packages/flutter_webrtc) class WebRTCPlayer { - webrtc.AddStreamCallback _onRemoteStream; - webrtc.RTCPeerConnection _pc; + late AddStreamCallback _onRemoteStream; + webrtc.RTCPeerConnection? _pc = null; /// When got a remote stream. - set onRemoteStream(webrtc.AddStreamCallback v) { + set onRemoteStream(AddStreamCallback v) { _onRemoteStream = v; } /// Initialize the player. - void initState() { - } + void initState() {} /// Start play a url. /// [url] must a path parsed by [WebRTCUri.parse] in https://github.com/rtcdn/rtcdn-draft Future play(String url) async { if (_pc != null) { - await _pc.close(); + await _pc?.close(); } // Create the peer connection. @@ -187,7 +202,7 @@ class WebRTCPlayer { print('WebRTC: createPeerConnection done'); // Setup the peer connection. - _pc.onAddStream = (webrtc.MediaStream stream) { + _pc?.onAddStream = (webrtc.MediaStream stream) { print('WebRTC: got stream ${stream.id}'); if (_onRemoteStream == null) { print('Warning: Stream ${stream.id} is leak'); @@ -196,37 +211,44 @@ class WebRTCPlayer { _onRemoteStream(stream); }; - _pc.addTransceiver( - kind: webrtc.RTCRtpMediaType.RTCRtpMediaTypeAudio, - init: webrtc.RTCRtpTransceiverInit(direction: webrtc.TransceiverDirection.RecvOnly), + _pc?.addTransceiver( + kind: webrtc.RTCRtpMediaType.RTCRtpMediaTypeAudio, + init: webrtc.RTCRtpTransceiverInit( + direction: webrtc.TransceiverDirection.RecvOnly), ); - _pc.addTransceiver( + _pc?.addTransceiver( kind: webrtc.RTCRtpMediaType.RTCRtpMediaTypeVideo, - init: webrtc.RTCRtpTransceiverInit(direction: webrtc.TransceiverDirection.RecvOnly), + init: webrtc.RTCRtpTransceiverInit( + direction: webrtc.TransceiverDirection.RecvOnly), ); print('WebRTC: Setup PC done, A|V RecvOnly'); // Start SDP handshake. - webrtc.RTCSessionDescription offer = await _pc.createOffer({ + webrtc.RTCSessionDescription? offer = await _pc?.createOffer({ 'mandatory': {'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true}, }); - await _pc.setLocalDescription(offer); - print('WebRTC: createOffer, ${offer.type} is ${offer.sdp.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); - webrtc.RTCSessionDescription answer = await _handshake(url, offer.sdp); - print('WebRTC: got ${answer.type} is ${answer.sdp.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); + await _pc?.setLocalDescription(offer!); + print( + 'WebRTC: createOffer, ${offer!.type} is ${offer.sdp?.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); + + webrtc.RTCSessionDescription answer = await _handshake(url, offer.sdp!); + print( + 'WebRTC: got ${answer.type} is ${answer.sdp?.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); - await _pc.setRemoteDescription(answer); + await _pc?.setRemoteDescription(answer); } /// Handshake to exchange SDP, send offer and got answer. - Future _handshake(String url, String offer) async { + Future _handshake( + String url, String offer) async { // Setup the client for HTTP or HTTPS. HttpClient client = HttpClient(); try { // Allow self-sign certificate, see https://api.flutter.dev/flutter/dart-io/HttpClient/badCertificateCallback.html - client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; + client.badCertificateCallback = + (X509Certificate cert, String host, int port) => true; // Parsing the WebRTC uri form url. WebRTCUri uri = WebRTCUri.parse(url); @@ -238,9 +260,11 @@ class WebRTCPlayer { // {api: "xxx", sdp: "offer", streamurl: "webrtc://d.ossrs.net:11985/live/livestream"} // Response: // {code: 0, sdp: "answer", sessionid: "007r51l7:X2Lv"} + print('${uri.api}'); HttpClientRequest req = await client.postUrl(Uri.parse(uri.api)); req.headers.set('Content-Type', 'application/json'); - req.add(utf8.encode(json.encode({'api': uri.api, 'streamurl': uri.streamUrl, 'sdp': offer}))); + req.add(utf8.encode(json + .encode({'api': uri.api, 'streamurl': uri.streamUrl, 'sdp': offer}))); print('WebRTC request: ${uri.api} offer=${offer.length}B'); HttpClientResponse res = await req.close(); @@ -261,8 +285,7 @@ class WebRTCPlayer { /// Dispose the player. void dispose() { if (_pc != null) { - _pc.close(); + _pc?.close(); } } } - diff --git a/pubspec.yaml b/pubspec.yaml index 4d7c961..b1187ff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,15 +4,16 @@ description: Live streaming player, iOS+Android, RTMP/HTTP-FLV/HLS/WebRTC, by Fl homepage: https://github.com/ossrs/flutter_live environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.20.0 <2.0.0" + sdk: ">=3.0.0" + flutter: ">=1.20.0" dependencies: flutter: sdk: flutter - fijkplayer: ^0.8.8 - flutter_webrtc: ^0.6.3 - camera_with_rtmp: ^0.3.2 + fijkplayer: ^0.11.0 + flutter_webrtc: ^0.10.4 + # camera_with_rtmp: + # path: ../flutter_rtmppublisher/ dev_dependencies: flutter_test: @@ -65,4 +66,3 @@ flutter: # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages -