From 3434e46289a7c430e880a8f6d192b54b7cd8baaa Mon Sep 17 00:00:00 2001 From: Khanh Nguyen Date: Wed, 23 Apr 2025 16:46:14 -0700 Subject: [PATCH 1/5] add soloud for sound output, but record is failing on iOS --- .../example/android/.gitignore | 3 +- .../example/android/app/build.gradle | 58 ------ .../example/android/app/build.gradle.kts | 47 +++++ .../android/app/src/debug/AndroidManifest.xml | 1 + .../android/app/src/main/AndroidManifest.xml | 8 +- .../com/example/example/MainActivity.kt | 5 - .../example/vertex_ai_example/MainActivity.kt | 5 + .../example/android/build.gradle | 32 ---- .../example/android/build.gradle.kts | 21 +++ .../example/android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../example/android/settings.gradle | 26 --- .../example/android/settings.gradle.kts | 28 +++ .../firebase_vertexai/example/ios/Podfile | 3 +- .../ios/Runner.xcodeproj/project.pbxproj | 170 ++++++++---------- .../xcshareddata/xcschemes/Runner.xcscheme | 24 --- .../example/ios/Runner/AppDelegate.swift | 2 +- .../example/ios/Runner/Info.plist | 6 +- .../example/ios/RunnerTests/RunnerTests.swift | 12 ++ .../example/ios/firebase_app_id_file.json | 7 - .../firebase_vertexai/example/lib/main.dart | 10 +- .../example/lib/pages/bidi_page.dart | 61 ++++--- .../example/lib/utils/audio_output.dart | 57 ++++++ .../example/linux/.gitignore | 1 + .../example/linux/CMakeLists.txt | 128 +++++++++++++ .../example/linux/flutter/CMakeLists.txt | 88 +++++++++ .../example/linux/runner/CMakeLists.txt | 26 +++ .../example/linux/runner/main.cc | 6 + .../example/linux/runner/my_application.cc | 130 ++++++++++++++ .../example/linux/runner/my_application.h | 18 ++ .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../macos/Runner/DebugProfile.entitlements | 2 + .../example/macos/Runner/Release.entitlements | 2 + .../macos/RunnerTests/RunnerTests.swift | 12 ++ .../firebase_vertexai/example/pubspec.yaml | 1 + .../example/windows/.gitignore | 17 ++ 36 files changed, 733 insertions(+), 289 deletions(-) delete mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle.kts delete mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/vertex_ai_example/MainActivity.kt delete mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle.kts delete mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle.kts create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/ios/RunnerTests/RunnerTests.swift delete mode 100644 packages/firebase_vertexai/firebase_vertexai/example/ios/firebase_app_id_file.json create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/linux/.gitignore create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/linux/CMakeLists.txt create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/linux/flutter/CMakeLists.txt create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/linux/runner/CMakeLists.txt create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/linux/runner/main.cc create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.cc create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.h create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/macos/RunnerTests/RunnerTests.swift create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/windows/.gitignore diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/.gitignore b/packages/firebase_vertexai/firebase_vertexai/example/android/.gitignore index 6f568019d3c6..be3943c96d8e 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/.gitignore +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle b/packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle deleted file mode 100644 index 9736e189b0d9..000000000000 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -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' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -// START: FlutterFire Configuration -apply plugin: 'com.google.gms.google-services' -// END: FlutterFire Configuration -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - namespace "com.example.example" - - compileSdk 35 - - defaultConfig { - applicationId "com.example.example" - minSdk 21 - targetSdk 33 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildFeatures { - buildConfig true - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle.kts b/packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle.kts new file mode 100644 index 000000000000..3415989fde7e --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/app/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + id("com.android.application") + // START: FlutterFire Configuration + id("com.google.gms.google-services") + // END: FlutterFire Configuration + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.vertex_ai_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = "27.0.12077973" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.vertex_ai_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 23 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/debug/AndroidManifest.xml b/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/debug/AndroidManifest.xml index 399f6981d5d3..d30de11e3410 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/debug/AndroidManifest.xml @@ -4,4 +4,5 @@ to allow setting breakpoints, to provide hot reload, etc. --> + diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/AndroidManifest.xml index 8b7413a6a397..f8755c8957d1 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/AndroidManifest.xml @@ -1,12 +1,13 @@ @@ -41,7 +42,4 @@ - - - diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt deleted file mode 100644 index 70f8f08f2479..000000000000 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/vertex_ai_example/MainActivity.kt b/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/vertex_ai_example/MainActivity.kt new file mode 100644 index 000000000000..a09c414f7bc6 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/app/src/main/kotlin/com/example/vertex_ai_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.vertex_ai_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle b/packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle deleted file mode 100644 index 97c6de922a3d..000000000000 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.1.2' - // START: FlutterFire Configuration - classpath 'com.google.gms:google-services:4.4.0' - // END: FlutterFire Configuration - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle.kts b/packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle.kts new file mode 100644 index 000000000000..89176ef44e8c --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/gradle.properties b/packages/firebase_vertexai/firebase_vertexai/example/android/gradle.properties index 598d13fee446..f018a61817f5 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/gradle.properties +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx4G +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_vertexai/firebase_vertexai/example/android/gradle/wrapper/gradle-wrapper.properties index e1ca574ef017..afa1e8eb0a83 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle b/packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle deleted file mode 100644 index 985a6e2ea6af..000000000000 --- a/packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle +++ /dev/null @@ -1,26 +0,0 @@ -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 - } - settings.ext.flutterSdkPath = flutterSdkPath() - - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -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.9.22" apply false -} - -include ":app" diff --git a/packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle.kts b/packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle.kts new file mode 100644 index 000000000000..9e2d35ccf5e0 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/android/settings.gradle.kts @@ -0,0 +1,28 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + // START: FlutterFire Configuration + id("com.google.gms.google-services") version("4.3.15") apply false + // END: FlutterFire Configuration + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/firebase_vertexai/firebase_vertexai/example/ios/Podfile b/packages/firebase_vertexai/firebase_vertexai/example/ios/Podfile index e51a31d9ca9d..2dbf7d728d81 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/ios/Podfile +++ b/packages/firebase_vertexai/firebase_vertexai/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -29,7 +29,6 @@ flutter_ios_podfile_setup target 'Runner' do use_frameworks! - use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do diff --git a/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj index 98cd0c1ded4a..f8eab6146d5f 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,17 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 05854BCEC3E60F2E8A74A88A /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2D7799BF98A33AAEBFECC25 /* Pods_RunnerTests.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3414F5B6C6F086F6373F1948 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5F1FA05866A2D0FCA3287B20 /* GoogleService-Info.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 40BF4CFF96751F0BC6E6FBB9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC516E37F284FF540735909 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 901FEC83A38129064032C578 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94CE5BFCDF90764354BB6740 /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - B7B3CA2D70F15615E1B8E5D8 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 154D9627A1C14A5ACE0B7B0D /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -46,18 +44,15 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 154D9627A1C14A5ACE0B7B0D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 232D95ECCEC6F04B9CEC8925 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1C100933A67087550C624271 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 560CA017EC76D8AAE2E21549 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 5F1FA05866A2D0FCA3287B20 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; + 3BC516E37F284FF540735909 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5063542D2F93285118BE28D1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8ACDC47C7E9AF1A1B9595598 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 94CE5BFCDF90764354BB6740 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -65,17 +60,19 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A85D07EF8959748E1D3E564B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - B0B22A9E291076BD22BA9F10 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - E1D0571EA0792087F8F27457 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 989054C088FE0B94A456191D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B314B71C7FD4E44CC2C161B2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C25345F7E48C093742CA0AA8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + C895CC408FCA01D6B4A18674 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + E2D7799BF98A33AAEBFECC25 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 0F5F3CD1ED7DB09B81C92173 /* Frameworks */ = { + 18267702F8AD5AFFDC88177F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B7B3CA2D70F15615E1B8E5D8 /* Pods_RunnerTests.framework in Frameworks */, + 05854BCEC3E60F2E8A74A88A /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -83,8 +80,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - 901FEC83A38129064032C578 /* Pods_Runner.framework in Frameworks */, + 40BF4CFF96751F0BC6E6FBB9 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,15 +95,15 @@ path = RunnerTests; sourceTree = ""; }; - 3C3B3E8596675CC144D1BD5B /* Pods */ = { + 80F070E2C8B2159FF3476A84 /* Pods */ = { isa = PBXGroup; children = ( - E1D0571EA0792087F8F27457 /* Pods-Runner.debug.xcconfig */, - 232D95ECCEC6F04B9CEC8925 /* Pods-Runner.release.xcconfig */, - 560CA017EC76D8AAE2E21549 /* Pods-Runner.profile.xcconfig */, - A85D07EF8959748E1D3E564B /* Pods-RunnerTests.debug.xcconfig */, - 8ACDC47C7E9AF1A1B9595598 /* Pods-RunnerTests.release.xcconfig */, - B0B22A9E291076BD22BA9F10 /* Pods-RunnerTests.profile.xcconfig */, + 5063542D2F93285118BE28D1 /* Pods-Runner.debug.xcconfig */, + B314B71C7FD4E44CC2C161B2 /* Pods-Runner.release.xcconfig */, + 989054C088FE0B94A456191D /* Pods-Runner.profile.xcconfig */, + C895CC408FCA01D6B4A18674 /* Pods-RunnerTests.debug.xcconfig */, + C25345F7E48C093742CA0AA8 /* Pods-RunnerTests.release.xcconfig */, + 1C100933A67087550C624271 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -130,9 +126,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, - 5F1FA05866A2D0FCA3287B20 /* GoogleService-Info.plist */, - 3C3B3E8596675CC144D1BD5B /* Pods */, - A50BECFB61A452F592070BAA /* Frameworks */, + 80F070E2C8B2159FF3476A84 /* Pods */, + E449472909CC17700399D86A /* Frameworks */, ); sourceTree = ""; }; @@ -160,11 +155,11 @@ path = Runner; sourceTree = ""; }; - A50BECFB61A452F592070BAA /* Frameworks */ = { + E449472909CC17700399D86A /* Frameworks */ = { isa = PBXGroup; children = ( - 94CE5BFCDF90764354BB6740 /* Pods_Runner.framework */, - 154D9627A1C14A5ACE0B7B0D /* Pods_RunnerTests.framework */, + 3BC516E37F284FF540735909 /* Pods_Runner.framework */, + E2D7799BF98A33AAEBFECC25 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -176,10 +171,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - F5C7CFE0E232B64D613F0623 /* [CP] Check Pods Manifest.lock */, + 8F81C131722F1C6926C1A8F8 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, - 0F5F3CD1ED7DB09B81C92173 /* Frameworks */, + 18267702F8AD5AFFDC88177F /* Frameworks */, ); buildRules = ( ); @@ -195,23 +190,20 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - F51794D56D63ACA383D5C2E4 /* [CP] Check Pods Manifest.lock */, + D665CAED4148D9261926D46D /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 34F21DFC67109DEAFD936E80 /* [CP] Embed Pods Frameworks */, + 41FF5C09A5C01274499A8841 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -245,9 +237,6 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, - ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -274,30 +263,12 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - 3414F5B6C6F086F6373F1948 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 34F21DFC67109DEAFD936E80 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -314,22 +285,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 41FF5C09A5C01274499A8841 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Run Script"; - outputPaths = ( + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - F51794D56D63ACA383D5C2E4 /* [CP] Check Pods Manifest.lock */ = { + 8F81C131722F1C6926C1A8F8 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -344,14 +317,29 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F5C7CFE0E232B64D613F0623 /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + D665CAED4148D9261926D46D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -366,7 +354,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -466,7 +454,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -477,19 +465,19 @@ }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = 989054C088FE0B94A456191D /* Pods-Runner.profile.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = YYX2P3XVJ7; + DEVELOPMENT_TEAM = S8QB4VV633; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.vertexAiExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -499,14 +487,14 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A85D07EF8959748E1D3E564B /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = C895CC408FCA01D6B4A18674 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.vertexAiExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -517,14 +505,14 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8ACDC47C7E9AF1A1B9595598 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = C25345F7E48C093742CA0AA8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.vertexAiExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -533,14 +521,14 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B0B22A9E291076BD22BA9F10 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 1C100933A67087550C624271 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.vertexAiExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -596,7 +584,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -647,7 +635,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -665,14 +653,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = YYX2P3XVJ7; + DEVELOPMENT_TEAM = S8QB4VV633; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.vertexAiExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -688,14 +676,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = YYX2P3XVJ7; + DEVELOPMENT_TEAM = S8QB4VV633; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.vertexAiExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -737,20 +725,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8178cd1c619c..15cada4838e2 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,24 +5,6 @@ - - - - - - - - - - - - - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Example + Vertex Ai Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - example + vertex_ai_example CFBundlePackageType APPL CFBundleShortVersionString @@ -46,6 +46,6 @@ UIApplicationSupportsIndirectInputEvents NSMicrophoneUsageDescription - We need access to the microphone to record audio. + Need microphone to talk with Gemini diff --git a/packages/firebase_vertexai/firebase_vertexai/example/ios/RunnerTests/RunnerTests.swift b/packages/firebase_vertexai/firebase_vertexai/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000000..86a7c3b1b611 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/ios/firebase_app_id_file.json b/packages/firebase_vertexai/firebase_vertexai/example/ios/firebase_app_id_file.json deleted file mode 100644 index 59a23a1a01cc..000000000000 --- a/packages/firebase_vertexai/firebase_vertexai/example/ios/firebase_app_id_file.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "file_generated_by": "FlutterFire CLI", - "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", - "GOOGLE_APP_ID": "1:651313571784:ios:2f1472905da3e8e9b1c2fd", - "FIREBASE_PROJECT_ID": "vertex-ai-example-ef5a2", - "GCM_SENDER_ID": "651313571784" -} \ No newline at end of file diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/main.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/main.dart index e4e2a8c56e6d..e09b59b4a8a2 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/main.dart +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/main.dart @@ -27,17 +27,17 @@ import 'pages/imagen_page.dart'; import 'pages/document.dart'; import 'pages/video_page.dart'; import 'pages/bidi_page.dart'; +import 'firebase_options.dart'; // REQUIRED if you want to run on Web -const FirebaseOptions? options = null; +FirebaseOptions options = DefaultFirebaseOptions.currentPlatform; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); - await FirebaseAuth.instance.signInAnonymously(); + await Firebase.initializeApp(options: options); + //await FirebaseAuth.instance.signInAnonymously(); - var vertexInstance = - FirebaseVertexAI.instanceFor(auth: FirebaseAuth.instance); + var vertexInstance = FirebaseVertexAI.instanceFor(); final model = vertexInstance.generativeModel(model: 'gemini-1.5-flash'); runApp(GenerativeAISample(model: model)); diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart index 0192c02367d6..637cda2790e1 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart @@ -13,6 +13,7 @@ // limitations under the License. import 'dart:typed_data'; import 'dart:async'; +import 'dart:math'; // For min function import 'dart:developer'; import 'package:flutter/material.dart'; @@ -20,6 +21,8 @@ import 'package:firebase_vertexai/firebase_vertexai.dart'; import '../widgets/message_widget.dart'; import '../utils/audio_player.dart'; import '../utils/audio_recorder.dart'; +import '../utils/audio_output.dart'; +import 'package:flutter_soloud/flutter_soloud.dart'; class BidiPage extends StatefulWidget { const BidiPage({super.key, required this.title, required this.model}); @@ -48,11 +51,12 @@ class _BidiPageState extends State { bool _recording = false; late LiveGenerativeModel _liveModel; late LiveSession _session; - final _audioManager = AudioStreamManager(); + //final _audioManager = AudioStreamManager(); final _audioRecorder = InMemoryAudioRecorder(); var _chunkBuilder = BytesBuilder(); var _audioIndex = 0; StreamController _stopController = StreamController(); + final AudioOutput audioOutput = AudioOutput(); @override void initState() { @@ -72,6 +76,11 @@ class _BidiPageState extends State { Tool.functionDeclarations([lightControlTool]), ], ); + initAudioOutput(); + } + + Future initAudioOutput() async { + await audioOutput.init(); } void _scrollDown() { @@ -89,8 +98,8 @@ class _BidiPageState extends State { @override void dispose() { if (_sessionOpening) { - _audioManager.stopAudioPlayer(); - _audioManager.disposeAudioPlayer(); + //_audioManager.stopAudioPlayer(); + //_audioManager.disposeAudioPlayer(); _audioRecorder.stopRecording(); @@ -243,8 +252,8 @@ class _BidiPageState extends State { await _stopController.close(); await _session.close(); - await _audioManager.stopAudioPlayer(); - await _audioManager.disposeAudioPlayer(); + //await _audioManager.stopAudioPlayer(); + //await _audioManager.disposeAudioPlayer(); _sessionOpening = false; } @@ -258,14 +267,21 @@ class _BidiPageState extends State { _recording = true; }); try { + print('check permission'); await _audioRecorder.checkPermission(); + print('start recording stream'); final audioRecordStream = _audioRecorder.startRecordingStream(); + print('play stream'); + await audioOutput.playStream(); // Map the Uint8List stream to InlineDataPart stream - final mediaChunkStream = audioRecordStream.map((data) { + Stream inlineDataStream = audioRecordStream.map((data) { + print('recording data!'); return InlineDataPart('audio/pcm', data); }); - await _session.sendMediaStream(mediaChunkStream); + + await _session.sendMediaStream(inlineDataStream); } catch (e) { + print(e); _showError(e.toString()); } } @@ -333,16 +349,16 @@ class _BidiPageState extends State { await _handleLiveServerContent(response); } - if (response is LiveServerContent && + /*if (response is LiveServerContent && response.turnComplete != null && response.turnComplete!) { await _handleTurnComplete(); - } + }*/ if (response is LiveServerContent && response.interrupted != null && response.interrupted!) { - log('Interrupted: $response'); + // log('Interrupted: $response'); } if (response is LiveServerToolCall && response.functionCalls != null) { @@ -359,7 +375,7 @@ class _BidiPageState extends State { } else if (part is InlineDataPart) { await _handleInlineDataPart(part); } else { - log('receive part with type ${part.runtimeType}'); + // log('receive part with type ${part.runtimeType}'); } } } @@ -379,28 +395,33 @@ class _BidiPageState extends State { } Future _handleInlineDataPart(InlineDataPart part) async { + print('audio part'); if (part.mimeType.startsWith('audio')) { - _chunkBuilder.add(part.bytes); - _audioIndex++; + //var resampledBytes = resamplePcm16Le(part.bytes, 24000, 16000); + SoLoud.instance.addAudioDataStream(audioOutput.stream!, part.bytes); + //_chunkBuilder.add(part.bytes); + /*_audioIndex++; if (_audioIndex == 15) { - Uint8List chunk = await audioChunkWithHeader( + /*Uint8List chunk = await audioChunkWithHeader( _chunkBuilder.toBytes(), 24000, - ); - _audioManager.addAudio(chunk); + );*/ + //_audioManager.addAudio(chunk); _chunkBuilder.clear(); _audioIndex = 0; - } + }*/ } } Future _handleTurnComplete() async { if (_chunkBuilder.isNotEmpty) { - Uint8List chunk = await audioChunkWithHeader( + print('turn complete with chunkbuilder not empty'); + /*Uint8List chunk = await audioChunkWithHeader( _chunkBuilder.toBytes(), 24000, - ); - _audioManager.addAudio(chunk); + );*/ + //SoLoud.instance.addAudioDataStream(audioOutput.stream!, chunk); + //_audioManager.addAudio(chunk); _audioIndex = 0; _chunkBuilder.clear(); } diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart new file mode 100644 index 000000000000..2d6d81c3a7bc --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart @@ -0,0 +1,57 @@ +import 'package:flutter_soloud/flutter_soloud.dart'; + +class AudioOutput { + AudioSource? stream; // Start playback + SoundHandle? handle; + + Future init() async { + /// Initialize the player. + await SoLoud.instance.init(sampleRate: 24000, channels: Channels.mono); + await setupNewStream(); + } + + Future setupNewStream() async { + if (SoLoud.instance.isInitialized) { + // Stop and clear any previous playback handle if it's still valid + await stopStream(); // Ensure previous sound is stopped + + stream = SoLoud.instance.setBufferStream( + maxBufferSizeBytes: + 1024 * 1024 * 10, // 10MB of max buffer (not allocated) + bufferingType: BufferingType.released, + bufferingTimeNeeds: 0, + sampleRate: 24000, // <<< MATCH SoLoud init and input + channels: Channels.mono, // <<< MATCH input (likely mono) + format: BufferType.s16le, // Should match pcm16bits + onBuffering: (isBuffering, handle, time) { + // When isBuffering==true, the stream is set to paused automatically till + // it reaches bufferingTimeNeeds of audio data or until setDataIsEnded is called + // or maxBufferSizeBytes is reached. When isBuffering==false, the playback stream + // is resumed. + print('Buffering: $isBuffering, Time: $time'); + }, + ); + print("New audio output stream buffer created."); + // Reset handle to null until the stream is played again + handle = null; + } + } + + Future playStream() async { + print('playing'); + handle = await SoLoud.instance.play(stream!); + return stream; + } + + Future stopStream() async { + if (stream != null && + handle != null && + SoLoud.instance.getIsValidVoiceHandle(handle!)) { + SoLoud.instance.setDataIsEnded(stream!); + await SoLoud.instance.stop(handle!); + + // Clear old stream, set up new session for next time. + setupNewStream(); + } + } +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/linux/.gitignore b/packages/firebase_vertexai/firebase_vertexai/example/linux/.gitignore new file mode 100644 index 000000000000..d3896c98444f --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/firebase_vertexai/firebase_vertexai/example/linux/CMakeLists.txt b/packages/firebase_vertexai/firebase_vertexai/example/linux/CMakeLists.txt new file mode 100644 index 000000000000..c95d5195668c --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "vertex_ai_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.vertex_ai_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/firebase_vertexai/firebase_vertexai/example/linux/flutter/CMakeLists.txt b/packages/firebase_vertexai/firebase_vertexai/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000000..d5bd01648a96 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/CMakeLists.txt b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/CMakeLists.txt new file mode 100644 index 000000000000..e97dabc7028e --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/main.cc b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/main.cc new file mode 100644 index 000000000000..e7c5c5437037 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.cc b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.cc new file mode 100644 index 000000000000..d38cf89dffa9 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "vertex_ai_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "vertex_ai_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.h b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.h new file mode 100644 index 000000000000..72271d5e4170 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index b2775746f883..b0a82f087ad1 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/DebugProfile.entitlements b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/DebugProfile.entitlements index b4bd9ee174a1..d3eb4e3f2a11 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/DebugProfile.entitlements +++ b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/DebugProfile.entitlements @@ -14,5 +14,7 @@ com.apple.security.device.audio-input + com.apple.security.network.client + diff --git a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/Release.entitlements b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/Release.entitlements index 2f9659c917fb..f18debee72ff 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/Release.entitlements +++ b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner/Release.entitlements @@ -6,5 +6,7 @@ com.apple.security.files.downloads.read-write + com.apple.security.network.client + diff --git a/packages/firebase_vertexai/firebase_vertexai/example/macos/RunnerTests/RunnerTests.swift b/packages/firebase_vertexai/firebase_vertexai/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000000..61f3bd1fc504 --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml b/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml index 9723d9cdb990..f6e42351b945 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml +++ b/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: flutter: sdk: flutter flutter_markdown: ^0.6.20 + flutter_soloud: ^3.1.6 just_audio: ^0.9.43 path_provider: ^2.1.5 record: ^5.2.1 diff --git a/packages/firebase_vertexai/firebase_vertexai/example/windows/.gitignore b/packages/firebase_vertexai/firebase_vertexai/example/windows/.gitignore new file mode 100644 index 000000000000..d492d0d98c8f --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ From 958d97b8d96725fcff91dab1557a6a559b08562c Mon Sep 17 00:00:00 2001 From: Khanh Nguyen Date: Wed, 23 Apr 2025 17:15:48 -0700 Subject: [PATCH 2/5] restarting my simulator fixed it... but incorporating my AudioInput class! --- .../firebase_vertexai/example/.metadata | 25 ++- .../ios/Runner.xcodeproj/project.pbxproj | 145 +++++++++--------- .../example/lib/pages/bidi_page.dart | 39 +++-- .../example/lib/utils/audio_input.dart | 87 +++++++++++ .../macos/Runner.xcodeproj/project.pbxproj | 4 +- 5 files changed, 207 insertions(+), 93 deletions(-) create mode 100644 packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart diff --git a/packages/firebase_vertexai/firebase_vertexai/example/.metadata b/packages/firebase_vertexai/firebase_vertexai/example/.metadata index 784ce1298249..e8f7bf911cbc 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/.metadata +++ b/packages/firebase_vertexai/firebase_vertexai/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" + revision: "ea121f8859e4b13e47a8f845e4586164519588bc" channel: "stable" project_type: app @@ -13,11 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: android + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: ios + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: linux + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: macos + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc - platform: web - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: windows + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc # User provided section diff --git a/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj index f8eab6146d5f..37f29d6208ac 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_vertexai/firebase_vertexai/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,12 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - 05854BCEC3E60F2E8A74A88A /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2D7799BF98A33AAEBFECC25 /* Pods_RunnerTests.framework */; }; + 12DD27C70B6F1A3A29F606CA /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EC8278BABD88B76D174C9B3 /* Pods_RunnerTests.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 40BF4CFF96751F0BC6E6FBB9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC516E37F284FF540735909 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7B483211B8F8447551559CD8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A7451CAF0BF9B58B1FC94AC /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -44,15 +44,18 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 1C100933A67087550C624271 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 1B003FC08370C067F6112BA3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 1E4EFC92E26DC42959308596 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 2DF9D5C450661BB71EE1CA4A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3BC516E37F284FF540735909 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5063542D2F93285118BE28D1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5A7451CAF0BF9B58B1FC94AC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EC8278BABD88B76D174C9B3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8E74AA7A780E6AD093F2280C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -60,27 +63,24 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 989054C088FE0B94A456191D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - B314B71C7FD4E44CC2C161B2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - C25345F7E48C093742CA0AA8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - C895CC408FCA01D6B4A18674 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - E2D7799BF98A33AAEBFECC25 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B2BD865801978D1293EC9548 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B5411DA55636D994211B15CD /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 18267702F8AD5AFFDC88177F /* Frameworks */ = { + 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 05854BCEC3E60F2E8A74A88A /* Pods_RunnerTests.framework in Frameworks */, + 7B483211B8F8447551559CD8 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 97C146EB1CF9000F007C117D /* Frameworks */ = { + E0117E231D8F6E331F0AF95D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 40BF4CFF96751F0BC6E6FBB9 /* Pods_Runner.framework in Frameworks */, + 12DD27C70B6F1A3A29F606CA /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -95,19 +95,29 @@ path = RunnerTests; sourceTree = ""; }; - 80F070E2C8B2159FF3476A84 /* Pods */ = { + 51AC52FF58548C49E2FD13CA /* Pods */ = { isa = PBXGroup; children = ( - 5063542D2F93285118BE28D1 /* Pods-Runner.debug.xcconfig */, - B314B71C7FD4E44CC2C161B2 /* Pods-Runner.release.xcconfig */, - 989054C088FE0B94A456191D /* Pods-Runner.profile.xcconfig */, - C895CC408FCA01D6B4A18674 /* Pods-RunnerTests.debug.xcconfig */, - C25345F7E48C093742CA0AA8 /* Pods-RunnerTests.release.xcconfig */, - 1C100933A67087550C624271 /* Pods-RunnerTests.profile.xcconfig */, - ); + 2DF9D5C450661BB71EE1CA4A /* Pods-Runner.debug.xcconfig */, + B2BD865801978D1293EC9548 /* Pods-Runner.release.xcconfig */, + 1E4EFC92E26DC42959308596 /* Pods-Runner.profile.xcconfig */, + 1B003FC08370C067F6112BA3 /* Pods-RunnerTests.debug.xcconfig */, + 8E74AA7A780E6AD093F2280C /* Pods-RunnerTests.release.xcconfig */, + B5411DA55636D994211B15CD /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; path = Pods; sourceTree = ""; }; + 67A1388587063912C673254D /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5A7451CAF0BF9B58B1FC94AC /* Pods_Runner.framework */, + 5EC8278BABD88B76D174C9B3 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -126,8 +136,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, - 80F070E2C8B2159FF3476A84 /* Pods */, - E449472909CC17700399D86A /* Frameworks */, + 51AC52FF58548C49E2FD13CA /* Pods */, + 67A1388587063912C673254D /* Frameworks */, ); sourceTree = ""; }; @@ -155,15 +165,6 @@ path = Runner; sourceTree = ""; }; - E449472909CC17700399D86A /* Frameworks */ = { - isa = PBXGroup; - children = ( - 3BC516E37F284FF540735909 /* Pods_Runner.framework */, - E2D7799BF98A33AAEBFECC25 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -171,10 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 8F81C131722F1C6926C1A8F8 /* [CP] Check Pods Manifest.lock */, + 8580F0CE7F25830B9BDEF0A0 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, - 18267702F8AD5AFFDC88177F /* Frameworks */, + E0117E231D8F6E331F0AF95D /* Frameworks */, ); buildRules = ( ); @@ -190,14 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - D665CAED4148D9261926D46D /* [CP] Check Pods Manifest.lock */, + 0C5779354B72E692610FCBAC /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 41FF5C09A5C01274499A8841 /* [CP] Embed Pods Frameworks */, + 8F2729CA72CB997339394B4E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -269,40 +270,45 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 0C5779354B72E692610FCBAC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Thin Binary"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 41FF5C09A5C01274499A8841 /* [CP] Embed Pods Frameworks */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + name = "Thin Binary"; + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 8F81C131722F1C6926C1A8F8 /* [CP] Check Pods Manifest.lock */ = { + 8580F0CE7F25830B9BDEF0A0 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -324,42 +330,37 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 8F2729CA72CB997339394B4E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Run Script"; - outputPaths = ( + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - D665CAED4148D9261926D46D /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( ); + name = "Run Script"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ @@ -465,7 +466,7 @@ }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 989054C088FE0B94A456191D /* Pods-Runner.profile.xcconfig */; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -487,7 +488,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C895CC408FCA01D6B4A18674 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 1B003FC08370C067F6112BA3 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -505,7 +506,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C25345F7E48C093742CA0AA8 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 8E74AA7A780E6AD093F2280C /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -521,7 +522,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1C100933A67087550C624271 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = B5411DA55636D994211B15CD /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart index 637cda2790e1..af11e8e42881 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart @@ -18,6 +18,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:firebase_vertexai/firebase_vertexai.dart'; +import 'package:vertex_ai_example/utils/audio_input.dart'; import '../widgets/message_widget.dart'; import '../utils/audio_player.dart'; import '../utils/audio_recorder.dart'; @@ -52,11 +53,12 @@ class _BidiPageState extends State { late LiveGenerativeModel _liveModel; late LiveSession _session; //final _audioManager = AudioStreamManager(); - final _audioRecorder = InMemoryAudioRecorder(); - var _chunkBuilder = BytesBuilder(); - var _audioIndex = 0; + //final _audioRecorder = InMemoryAudioRecorder(); + //var _chunkBuilder = BytesBuilder(); + //var _audioIndex = 0; StreamController _stopController = StreamController(); final AudioOutput audioOutput = AudioOutput(); + final AudioInput audioInput = AudioInput(); @override void initState() { @@ -81,6 +83,7 @@ class _BidiPageState extends State { Future initAudioOutput() async { await audioOutput.init(); + await audioInput.init(); } void _scrollDown() { @@ -101,7 +104,7 @@ class _BidiPageState extends State { //_audioManager.stopAudioPlayer(); //_audioManager.disposeAudioPlayer(); - _audioRecorder.stopRecording(); + //_audioRecorder.stopRecording(); _stopController.close(); @@ -267,19 +270,23 @@ class _BidiPageState extends State { _recording = true; }); try { - print('check permission'); - await _audioRecorder.checkPermission(); + //print('check permission'); + + //await _audioRecorder.checkPermission(); print('start recording stream'); - final audioRecordStream = _audioRecorder.startRecordingStream(); + //final audioRecordStream = _audioRecorder.startRecordingStream(); + var inputStream = await audioInput.startRecordingStream(); print('play stream'); await audioOutput.playStream(); // Map the Uint8List stream to InlineDataPart stream - Stream inlineDataStream = audioRecordStream.map((data) { - print('recording data!'); - return InlineDataPart('audio/pcm', data); - }); + if (inputStream != null) { + Stream inlineDataStream = inputStream.map((data) { + print('recording data!'); + return InlineDataPart('audio/pcm', data); + }); - await _session.sendMediaStream(inlineDataStream); + await _session.sendMediaStream(inlineDataStream); + } } catch (e) { print(e); _showError(e.toString()); @@ -288,7 +295,9 @@ class _BidiPageState extends State { Future _stopRecording() async { try { - await _audioRecorder.stopRecording(); + //await _audioRecorder.stopRecording(); + + await audioInput.stopRecording(); } catch (e) { _showError(e.toString()); } @@ -413,7 +422,7 @@ class _BidiPageState extends State { } } - Future _handleTurnComplete() async { + /*Future _handleTurnComplete() async { if (_chunkBuilder.isNotEmpty) { print('turn complete with chunkbuilder not empty'); /*Uint8List chunk = await audioChunkWithHeader( @@ -425,7 +434,7 @@ class _BidiPageState extends State { _audioIndex = 0; _chunkBuilder.clear(); } - } + }*/ Future _handleLiveServerToolCall(LiveServerToolCall response) async { final functionCalls = response.functionCalls!.toList(); diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart new file mode 100644 index 000000000000..68e5579d7c3d --- /dev/null +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:record/record.dart'; +import 'dart:typed_data'; + +class AudioInput extends ChangeNotifier { + final _recorder = AudioRecorder(); + final AudioEncoder _encoder = AudioEncoder.pcm16bits; + bool isRecording = false; + bool isPaused = false; + Stream? audioStream; + + init() { + checkPermission(); + } + + @override + void dispose() { + _recorder.dispose(); + super.dispose(); + } + + Future checkPermission() async { + final hasPermission = await _recorder.hasPermission(); + if (!hasPermission) { + throw MicrophonePermissionDeniedException( + 'App does not have mic permissions', + ); + } + } + + Future?> startRecordingStream() async { + var recordConfig = RecordConfig( + encoder: _encoder, + sampleRate: 24000, + numChannels: 1, + //sampleRate: 16000, + //numChannels: 2, + ); + final devices = await _recorder.listInputDevices(); + print(devices.toString()); + audioStream = await _recorder.startStream(recordConfig); + isRecording = true; + print("${isRecording ? "Is" : "Not"} Recording"); + notifyListeners(); + return audioStream; + /*await for (final data in stream) { + yield data; + }*/ + } + + Future stopRecording() async { + await _recorder.stop(); + isRecording = false; + print("${isRecording ? "Is" : "Not"} Recording"); + notifyListeners(); + } + + Future togglePause() async { + if (isPaused) { + await _recorder.resume(); + isPaused = false; + } else { + await _recorder.pause(); + isPaused = true; + } + print("${isPaused ? "Is" : "Not"} Paused"); + notifyListeners(); + return; + } +} + +/// An exception thrown when microphone permission is denied or not granted. +class MicrophonePermissionDeniedException implements Exception { + /// The optional message associated with the permission denial. + final String? message; + + /// Creates a new [MicrophonePermissionDeniedException] with an optional [message]. + MicrophonePermissionDeniedException([this.message]); + + @override + String toString() { + if (message == null) { + return 'MicrophonePermissionDeniedException'; + } + return 'MicrophonePermissionDeniedException: $message'; + } +} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/project.pbxproj b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/project.pbxproj index 4bc66a519ca5..47f1397e22f9 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_vertexai/firebase_vertexai/example/macos/Runner.xcodeproj/project.pbxproj @@ -198,7 +198,6 @@ 587C61AFC0E2B0BF5340F8E8 /* Pods-RunnerTests.release.xcconfig */, 5C2B5E4F1CE100E1FA5D9DC5 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -584,6 +583,7 @@ "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; + STRIP_STYLE = "non-global"; SWIFT_VERSION = 5.0; }; name = Profile; @@ -716,6 +716,7 @@ "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; + STRIP_STYLE = "non-global"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -736,6 +737,7 @@ "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; + STRIP_STYLE = "non-global"; SWIFT_VERSION = 5.0; }; name = Release; From 618843093e6c43e443edee406fd60dc415b01d19 Mon Sep 17 00:00:00 2001 From: Khanh Nguyen Date: Thu, 24 Apr 2025 10:55:27 -0700 Subject: [PATCH 3/5] add JS scripts for flutter_soloud on web --- .../firebase_vertexai/example/web/index.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/firebase_vertexai/firebase_vertexai/example/web/index.html b/packages/firebase_vertexai/firebase_vertexai/example/web/index.html index adc47a626031..dcd929827260 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/web/index.html +++ b/packages/firebase_vertexai/firebase_vertexai/example/web/index.html @@ -1,5 +1,6 @@ + - + flutterfire_vertexai + + + - + + \ No newline at end of file From 3ba57a2b0c15a28ea280cdb45c1dbe436b5fcfe2 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 20 May 2025 22:08:35 -0700 Subject: [PATCH 4/5] add gitignore to example --- .../firebase_ai/example/.gitignore | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/firebase_ai/firebase_ai/example/.gitignore diff --git a/packages/firebase_ai/firebase_ai/example/.gitignore b/packages/firebase_ai/firebase_ai/example/.gitignore new file mode 100644 index 000000000000..53bed76d8faa --- /dev/null +++ b/packages/firebase_ai/firebase_ai/example/.gitignore @@ -0,0 +1,51 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +#firebase +firebase_options.dart +google-services.json +GoogleService-Info.plist +firebase.json From 340db1f92d4faef76eda9e827f46fd7f33e555ca Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Fri, 30 May 2025 16:01:40 -0700 Subject: [PATCH 5/5] Clean up before review --- .../example/lib/pages/bidi_page.dart | 78 ++---- .../lib/pages/function_calling_page.dart | 32 ++- .../example/lib/utils/audio_input.dart | 95 +++++++ .../example/lib/utils/audio_output.dart | 66 +++++ .../example/lib/utils/audio_player.dart | 143 ---------- .../example/lib/utils/audio_recorder.dart | 76 +----- .../firebase_ai/example/pubspec.yaml | 2 +- .../example/lib/pages/bidi_page.dart | 69 +---- .../example/lib/utils/audio_input.dart | 38 +-- .../example/lib/utils/audio_output.dart | 35 ++- .../example/lib/utils/audio_player.dart | 143 ---------- .../example/lib/utils/audio_recorder.dart | 245 ------------------ .../firebase_vertexai/example/pubspec.yaml | 1 - 13 files changed, 268 insertions(+), 755 deletions(-) create mode 100644 packages/firebase_ai/firebase_ai/example/lib/utils/audio_input.dart create mode 100644 packages/firebase_ai/firebase_ai/example/lib/utils/audio_output.dart delete mode 100644 packages/firebase_ai/firebase_ai/example/lib/utils/audio_player.dart delete mode 100644 packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_player.dart delete mode 100644 packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_recorder.dart diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/bidi_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/bidi_page.dart index acd936a7e827..313b3da20193 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/bidi_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/bidi_page.dart @@ -11,15 +11,15 @@ // 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. -import 'dart:typed_data'; import 'dart:async'; -import 'dart:developer'; +import 'dart:developer' as developer; import 'package:flutter/material.dart'; import 'package:firebase_ai/firebase_ai.dart'; + +import '../utils/audio_input.dart'; +import '../utils/audio_output.dart'; import '../widgets/message_widget.dart'; -import '../utils/audio_player.dart'; -import '../utils/audio_recorder.dart'; class BidiPage extends StatefulWidget { const BidiPage({super.key, required this.title, required this.model}); @@ -48,11 +48,9 @@ class _BidiPageState extends State { bool _recording = false; late LiveGenerativeModel _liveModel; late LiveSession _session; - final _audioManager = AudioStreamManager(); - final _audioRecorder = InMemoryAudioRecorder(); - var _chunkBuilder = BytesBuilder(); - var _audioIndex = 0; StreamController _stopController = StreamController(); + final AudioOutput audioOutput = AudioOutput(); + final AudioInput audioInput = AudioInput(); @override void initState() { @@ -65,6 +63,7 @@ class _BidiPageState extends State { ], ); + // ignore: deprecated_member_use _liveModel = FirebaseAI.vertexAI().liveGenerativeModel( model: 'gemini-2.0-flash-exp', liveGenerationConfig: config, @@ -72,6 +71,12 @@ class _BidiPageState extends State { Tool.functionDeclarations([lightControlTool]), ], ); + initAudioOutput(); + } + + Future initAudioOutput() async { + await audioOutput.init(); + await audioInput.init(); } void _scrollDown() { @@ -89,13 +94,7 @@ class _BidiPageState extends State { @override void dispose() { if (_sessionOpening) { - _audioManager.stopAudioPlayer(); - _audioManager.disposeAudioPlayer(); - - _audioRecorder.stopRecording(); - _stopController.close(); - _sessionOpening = false; _session.close(); } @@ -243,8 +242,6 @@ class _BidiPageState extends State { await _stopController.close(); await _session.close(); - await _audioManager.stopAudioPlayer(); - await _audioManager.disposeAudioPlayer(); _sessionOpening = false; } @@ -258,21 +255,25 @@ class _BidiPageState extends State { _recording = true; }); try { - await _audioRecorder.checkPermission(); - final audioRecordStream = _audioRecorder.startRecordingStream(); + var inputStream = await audioInput.startRecordingStream(); + await audioOutput.playStream(); // Map the Uint8List stream to InlineDataPart stream - final mediaChunkStream = audioRecordStream.map((data) { - return InlineDataPart('audio/pcm', data); - }); - await _session.sendMediaStream(mediaChunkStream); + if (inputStream != null) { + Stream inlineDataStream = inputStream.map((data) { + return InlineDataPart('audio/pcm', data); + }); + + await _session.sendMediaStream(inlineDataStream); + } } catch (e) { + print(e); _showError(e.toString()); } } Future _stopRecording() async { try { - await _audioRecorder.stopRecording(); + await audioInput.stopRecording(); } catch (e) { _showError(e.toString()); } @@ -335,11 +336,8 @@ class _BidiPageState extends State { if (message.modelTurn != null) { await _handleLiveServerContent(message); } - if (message.turnComplete != null && message.turnComplete!) { - await _handleTurnComplete(); - } if (message.interrupted != null && message.interrupted!) { - log('Interrupted: $response'); + developer.log('Interrupted: $response'); } } else if (message is LiveServerToolCall && message.functionCalls != null) { await _handleLiveServerToolCall(message); @@ -355,7 +353,7 @@ class _BidiPageState extends State { } else if (part is InlineDataPart) { await _handleInlineDataPart(part); } else { - log('receive part with type ${part.runtimeType}'); + developer.log('receive part with type ${part.runtimeType}'); } } } @@ -376,29 +374,7 @@ class _BidiPageState extends State { Future _handleInlineDataPart(InlineDataPart part) async { if (part.mimeType.startsWith('audio')) { - _chunkBuilder.add(part.bytes); - _audioIndex++; - if (_audioIndex == 15) { - Uint8List chunk = await audioChunkWithHeader( - _chunkBuilder.toBytes(), - 24000, - ); - _audioManager.addAudio(chunk); - _chunkBuilder.clear(); - _audioIndex = 0; - } - } - } - - Future _handleTurnComplete() async { - if (_chunkBuilder.isNotEmpty) { - Uint8List chunk = await audioChunkWithHeader( - _chunkBuilder.toBytes(), - 24000, - ); - _audioManager.addAudio(chunk); - _audioIndex = 0; - _chunkBuilder.clear(); + audioOutput.addAudioStream(part.bytes); } } diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart index cf79b61a7104..8f2b7e067388 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart @@ -170,23 +170,21 @@ class _FunctionCallingPageState extends State { final functionCalls = response.functionCalls.toList(); // When the model response with a function call, invoke the function. if (functionCalls.isNotEmpty) { - final functionCall = functionCalls.first; - if (functionCall.name == 'fetchWeather') { - Map location = - functionCall.args['location']! as Map; - var date = functionCall.args['date']! as String; - var city = location['city'] as String; - var state = location['state'] as String; - final functionResult = await fetchWeather(Location(city, state), date); - // Send the response to the model so that it can use the result to - // generate text for the user. - response = await functionCallChat.sendMessage( - Content.functionResponse(functionCall.name, functionResult), - ); - } else { - throw UnimplementedError( - 'Function not declared to the model: ${functionCall.name}', - ); + for (final functionCall in functionCalls) { + if (functionCall.name == 'fetchWeather') { + Map location = + functionCall.args['location']! as Map; + var date = functionCall.args['date']! as String; + var city = location['city'] as String; + var state = location['state'] as String; + final functionResult = + await fetchWeather(Location(city, state), date); + // Send the response to the model so that it can use the result to + // generate text for the user. + response = await functionCallChat.sendMessage( + Content.functionResponse(functionCall.name, functionResult), + ); + } } } // When the model responds with non-null text content, print it. diff --git a/packages/firebase_ai/firebase_ai/example/lib/utils/audio_input.dart b/packages/firebase_ai/firebase_ai/example/lib/utils/audio_input.dart new file mode 100644 index 000000000000..b32b592029a4 --- /dev/null +++ b/packages/firebase_ai/firebase_ai/example/lib/utils/audio_input.dart @@ -0,0 +1,95 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +import 'package:flutter/material.dart'; +import 'package:record/record.dart'; +import 'dart:typed_data'; + +class AudioInput extends ChangeNotifier { + final _recorder = AudioRecorder(); + final AudioEncoder _encoder = AudioEncoder.pcm16bits; + bool isRecording = false; + bool isPaused = false; + Stream? audioStream; + + Future init() async { + await checkPermission(); + } + + @override + void dispose() { + _recorder.dispose(); + super.dispose(); + } + + Future checkPermission() async { + final hasPermission = await _recorder.hasPermission(); + if (!hasPermission) { + throw MicrophonePermissionDeniedException( + 'App does not have mic permissions', + ); + } + } + + Future?> startRecordingStream() async { + var recordConfig = RecordConfig( + encoder: _encoder, + sampleRate: 24000, + numChannels: 1, + echoCancel: true, + noiseSuppress: true, + androidConfig: const AndroidRecordConfig( + audioSource: AndroidAudioSource.voiceCommunication, + ), + iosConfig: const IosRecordConfig(categoryOptions: []), + ); + await _recorder.listInputDevices(); + audioStream = await _recorder.startStream(recordConfig); + isRecording = true; + notifyListeners(); + return audioStream; + } + + Future stopRecording() async { + await _recorder.stop(); + isRecording = false; + notifyListeners(); + } + + Future togglePause() async { + if (isPaused) { + await _recorder.resume(); + isPaused = false; + } else { + await _recorder.pause(); + isPaused = true; + } + notifyListeners(); + return; + } +} + +/// An exception thrown when microphone permission is denied or not granted. +class MicrophonePermissionDeniedException implements Exception { + /// The optional message associated with the permission denial. + final String? message; + + /// Creates a new [MicrophonePermissionDeniedException] with an optional [message]. + MicrophonePermissionDeniedException([this.message]); + + @override + String toString() { + return 'MicrophonePermissionDeniedException: $message'; + } +} diff --git a/packages/firebase_ai/firebase_ai/example/lib/utils/audio_output.dart b/packages/firebase_ai/firebase_ai/example/lib/utils/audio_output.dart new file mode 100644 index 000000000000..2f13d3fd2349 --- /dev/null +++ b/packages/firebase_ai/firebase_ai/example/lib/utils/audio_output.dart @@ -0,0 +1,66 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +import 'dart:typed_data'; + +import 'package:flutter_soloud/flutter_soloud.dart'; + +class AudioOutput { + AudioSource? stream; // Start playback + SoundHandle? handle; + + Future init() async { + /// Initialize the player. + await SoLoud.instance.init(sampleRate: 24000, channels: Channels.mono); + await setupNewStream(); + } + + Future setupNewStream() async { + if (SoLoud.instance.isInitialized) { + // Stop and clear any previous playback handle if it's still valid + await stopStream(); // Ensure previous sound is stopped + + stream = SoLoud.instance.setBufferStream( + maxBufferSizeBytes: + 1024 * 1024 * 10, // 10MB of max buffer (not allocated) + bufferingType: BufferingType.released, + bufferingTimeNeeds: 0, + onBuffering: (isBuffering, handle, time) {}, + ); + // Reset handle to null until the stream is played again + handle = null; + } + } + + Future playStream() async { + handle = await SoLoud.instance.play(stream!); + return stream; + } + + Future stopStream() async { + if (stream != null && + handle != null && + SoLoud.instance.getIsValidVoiceHandle(handle!)) { + SoLoud.instance.setDataIsEnded(stream!); + await SoLoud.instance.stop(handle!); + + // Clear old stream, set up new session for next time. + await setupNewStream(); + } + } + + void addAudioStream(Uint8List audioChunk) { + SoLoud.instance.addAudioDataStream(stream!, audioChunk); + } +} diff --git a/packages/firebase_ai/firebase_ai/example/lib/utils/audio_player.dart b/packages/firebase_ai/firebase_ai/example/lib/utils/audio_player.dart deleted file mode 100644 index 3c5559481ed7..000000000000 --- a/packages/firebase_ai/firebase_ai/example/lib/utils/audio_player.dart +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2025 Google LLC -// -// 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 -// -// http://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. - -import 'dart:typed_data'; -import 'dart:async'; - -import 'package:just_audio/just_audio.dart'; - -/// Creates a WAV audio chunk with a properly formatted header. -Future audioChunkWithHeader( - List data, - int sampleRate, -) async { - var channels = 1; - - int byteRate = ((16 * sampleRate * channels) / 8).round(); - - var size = data.length; - var fileSize = size + 36; - - Uint8List header = Uint8List.fromList([ - // "RIFF" - 82, 73, 70, 70, - fileSize & 0xff, - (fileSize >> 8) & 0xff, - (fileSize >> 16) & 0xff, - (fileSize >> 24) & 0xff, - // WAVE - 87, 65, 86, 69, - // fmt - 102, 109, 116, 32, - // fmt chunk size 16 - 16, 0, 0, 0, - // Type of format - 1, 0, - // One channel - channels, 0, - // Sample rate - sampleRate & 0xff, - (sampleRate >> 8) & 0xff, - (sampleRate >> 16) & 0xff, - (sampleRate >> 24) & 0xff, - // Byte rate - byteRate & 0xff, - (byteRate >> 8) & 0xff, - (byteRate >> 16) & 0xff, - (byteRate >> 24) & 0xff, - // Uhm - ((16 * channels) / 8).round(), 0, - // bitsize - 16, 0, - // "data" - 100, 97, 116, 97, - size & 0xff, - (size >> 8) & 0xff, - (size >> 16) & 0xff, - (size >> 24) & 0xff, - // incoming data - ...data, - ]); - return header; -} - -class ByteStreamAudioSource extends StreamAudioSource { - ByteStreamAudioSource(this.bytes) : super(tag: 'Byte Stream Audio'); - - final Uint8List bytes; - - @override - Future request([int? start, int? end]) async { - start ??= 0; - end ??= bytes.length; - return StreamAudioResponse( - sourceLength: bytes.length, - contentLength: end - start, - offset: start, - stream: Stream.value(bytes.sublist(start, end)), - contentType: 'audio/wav', // Or the appropriate content type - ); - } -} - -class AudioStreamManager { - final _audioPlayer = AudioPlayer(); - final _audioChunkController = StreamController(); - var _audioSource = ConcatenatingAudioSource( - children: [], - ); - - AudioStreamManager() { - _initAudioPlayer(); - } - - Future _initAudioPlayer() async { - // 1. Create a ConcatenatingAudioSource to handle the stream - await _audioPlayer.setAudioSource(_audioSource); - - // 2. Listen to the stream of audio chunks - _audioChunkController.stream.listen(_addAudioChunk); - - await _audioPlayer.play(); // Start playing (even if initially empty) - - _audioPlayer.processingStateStream.listen((state) async { - if (state == ProcessingState.completed) { - await _audioPlayer - .pause(); // Or player.stop() if you want to release resources - await _audioPlayer.seek(Duration.zero, index: 0); - await _audioSource.clear(); - await _audioPlayer.play(); - } - }); - } - - Future _addAudioChunk(Uint8List chunk) async { - var buffer = ByteStreamAudioSource(chunk); - - await _audioSource.add(buffer); - } - - void addAudio(Uint8List chunk) { - _audioChunkController.add(chunk); - } - - Future stopAudioPlayer() async { - await _audioPlayer.stop(); - } - - Future disposeAudioPlayer() async { - await _audioPlayer.dispose(); - await _audioChunkController.close(); - } -} diff --git a/packages/firebase_ai/firebase_ai/example/lib/utils/audio_recorder.dart b/packages/firebase_ai/firebase_ai/example/lib/utils/audio_recorder.dart index 1f3710cd0c8f..4bd5d8e5d7f3 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/utils/audio_recorder.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/utils/audio_recorder.dart @@ -129,7 +129,7 @@ class InMemoryAudioRecorder { return isSupported; } - Future startRecording({bool fromFile = false}) async { + Future startRecording() async { if (!await _isEncoderSupported(_encoder)) { return; } @@ -137,35 +137,20 @@ class InMemoryAudioRecorder { encoder: _encoder, sampleRate: 16000, numChannels: 1, + echoCancel: true, + noiseSuppress: true, androidConfig: const AndroidRecordConfig( muteAudio: true, - audioSource: AndroidAudioSource.mic, + audioSource: AndroidAudioSource.voiceCommunication, ), + iosConfig: const IosRecordConfig(categoryOptions: []), ); final devs = await _recorder.listInputDevices(); debugPrint(devs.toString()); _lastAudioPath = await _getPath(); - if (fromFile) { - await _recorder.start(recordConfig, path: _lastAudioPath!); - } else { - final stream = await _recorder.startStream(recordConfig); - _recordSubscription = stream.listen(_audioChunks.add); - } - } - Future startRecordingFile() async { - if (!await _isEncoderSupported(_encoder)) { - return; - } - var recordConfig = RecordConfig( - encoder: _encoder, - sampleRate: 16000, - numChannels: 1, - ); - final devs = await _recorder.listInputDevices(); - debugPrint(devs.toString()); - _lastAudioPath = await _getPath(); - await _recorder.start(recordConfig, path: _lastAudioPath!); + final stream = await _recorder.startStream(recordConfig); + _recordSubscription = stream.listen(_audioChunks.add); } Stream startRecordingStream() async* { @@ -193,53 +178,16 @@ class InMemoryAudioRecorder { await _recorder.stop(); } - Future fetchAudioBytes({ - bool fromFile = false, - bool removeHeader = false, - }) async { + Future fetchAudioBytes() async { Uint8List resultBytes; - if (fromFile) { - resultBytes = await _getAudioBytesFromFile(_lastAudioPath!); - } else { - final builder = BytesBuilder(); - _audioChunks.forEach(builder.add); - resultBytes = builder.toBytes(); - } + + final builder = BytesBuilder(); + _audioChunks.forEach(builder.add); + resultBytes = builder.toBytes(); // resample resultBytes = Resampler.resampleLinear16(44100, 16000, resultBytes); - final dir = await getDownloadsDirectory(); - final path = '${dir!.path}/audio_resampled.pcm'; - final file = File(path); - final sink = file.openWrite(); - - sink.add(resultBytes); - await sink.close(); return resultBytes; } - - Future _removeWavHeader(Uint8List audio) async { - // Assuming a standard WAV header size of 44 bytes - const wavHeaderSize = 44; - final audioData = audio.sublist(wavHeaderSize); - return audioData; - } - - Future _getAudioBytesFromFile( - String filePath, { - bool removeHeader = false, - }) async { - final file = File(_lastAudioPath!); - - if (!file.existsSync()) { - throw Exception('Audio file not found: ${file.path}'); - } - - var pcmBytes = await file.readAsBytes(); - if (removeHeader) { - pcmBytes = await _removeWavHeader(pcmBytes); - } - return pcmBytes; - } } diff --git a/packages/firebase_ai/firebase_ai/example/pubspec.yaml b/packages/firebase_ai/firebase_ai/example/pubspec.yaml index 21b1fa04272d..4868f106d648 100644 --- a/packages/firebase_ai/firebase_ai/example/pubspec.yaml +++ b/packages/firebase_ai/firebase_ai/example/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: flutter: sdk: flutter flutter_markdown: ^0.6.20 - just_audio: ^0.9.43 + flutter_soloud: ^3.1.6 path_provider: ^2.1.5 record: ^5.2.1 diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart index 9e1198e190a2..f52b7a3f0be9 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/pages/bidi_page.dart @@ -11,19 +11,15 @@ // 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. -import 'dart:typed_data'; import 'dart:async'; -import 'dart:math'; // For min function -import 'dart:developer'; +import 'dart:developer' as developer; import 'package:flutter/material.dart'; import 'package:firebase_vertexai/firebase_vertexai.dart'; -import 'package:vertex_ai_example/utils/audio_input.dart'; -import '../widgets/message_widget.dart'; -import '../utils/audio_player.dart'; -import '../utils/audio_recorder.dart'; + +import '../utils/audio_input.dart'; import '../utils/audio_output.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; +import '../widgets/message_widget.dart'; class BidiPage extends StatefulWidget { const BidiPage({super.key, required this.title, required this.model}); @@ -52,10 +48,6 @@ class _BidiPageState extends State { bool _recording = false; late LiveGenerativeModel _liveModel; late LiveSession _session; - //final _audioManager = AudioStreamManager(); - //final _audioRecorder = InMemoryAudioRecorder(); - //var _chunkBuilder = BytesBuilder(); - //var _audioIndex = 0; StreamController _stopController = StreamController(); final AudioOutput audioOutput = AudioOutput(); final AudioInput audioInput = AudioInput(); @@ -102,13 +94,7 @@ class _BidiPageState extends State { @override void dispose() { if (_sessionOpening) { - //_audioManager.stopAudioPlayer(); - //_audioManager.disposeAudioPlayer(); - - //_audioRecorder.stopRecording(); - _stopController.close(); - _sessionOpening = false; _session.close(); } @@ -256,8 +242,6 @@ class _BidiPageState extends State { await _stopController.close(); await _session.close(); - //await _audioManager.stopAudioPlayer(); - //await _audioManager.disposeAudioPlayer(); _sessionOpening = false; } @@ -271,18 +255,11 @@ class _BidiPageState extends State { _recording = true; }); try { - //print('check permission'); - - //await _audioRecorder.checkPermission(); - print('start recording stream'); - //final audioRecordStream = _audioRecorder.startRecordingStream(); var inputStream = await audioInput.startRecordingStream(); - print('play stream'); await audioOutput.playStream(); // Map the Uint8List stream to InlineDataPart stream if (inputStream != null) { Stream inlineDataStream = inputStream.map((data) { - print('recording data!'); return InlineDataPart('audio/pcm', data); }); @@ -296,8 +273,6 @@ class _BidiPageState extends State { Future _stopRecording() async { try { - //await _audioRecorder.stopRecording(); - await audioInput.stopRecording(); } catch (e) { _showError(e.toString()); @@ -361,11 +336,8 @@ class _BidiPageState extends State { if (message.modelTurn != null) { await _handleLiveServerContent(message); } - if (message.turnComplete != null && message.turnComplete!) { - await _handleTurnComplete(); - } if (message.interrupted != null && message.interrupted!) { - log('Interrupted: $response'); + developer.log('Interrupted: $response'); } } else if (message is LiveServerToolCall && message.functionCalls != null) { await _handleLiveServerToolCall(message); @@ -381,7 +353,7 @@ class _BidiPageState extends State { } else if (part is InlineDataPart) { await _handleInlineDataPart(part); } else { - // log('receive part with type ${part.runtimeType}'); + developer.log('receive part with type ${part.runtimeType}'); } } } @@ -401,38 +373,11 @@ class _BidiPageState extends State { } Future _handleInlineDataPart(InlineDataPart part) async { - print('audio part'); if (part.mimeType.startsWith('audio')) { - //var resampledBytes = resamplePcm16Le(part.bytes, 24000, 16000); - SoLoud.instance.addAudioDataStream(audioOutput.stream!, part.bytes); - //_chunkBuilder.add(part.bytes); - /*_audioIndex++; - if (_audioIndex == 15) { - /*Uint8List chunk = await audioChunkWithHeader( - _chunkBuilder.toBytes(), - 24000, - );*/ - //_audioManager.addAudio(chunk); - _chunkBuilder.clear(); - _audioIndex = 0; - }*/ + audioOutput.addAudioStream(part.bytes); } } - /*Future _handleTurnComplete() async { - if (_chunkBuilder.isNotEmpty) { - print('turn complete with chunkbuilder not empty'); - /*Uint8List chunk = await audioChunkWithHeader( - _chunkBuilder.toBytes(), - 24000, - );*/ - //SoLoud.instance.addAudioDataStream(audioOutput.stream!, chunk); - //_audioManager.addAudio(chunk); - _audioIndex = 0; - _chunkBuilder.clear(); - } - }*/ - Future _handleLiveServerToolCall(LiveServerToolCall response) async { final functionCalls = response.functionCalls!.toList(); if (functionCalls.isNotEmpty) { diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart index 68e5579d7c3d..b32b592029a4 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_input.dart @@ -1,3 +1,17 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + import 'package:flutter/material.dart'; import 'package:record/record.dart'; import 'dart:typed_data'; @@ -9,8 +23,8 @@ class AudioInput extends ChangeNotifier { bool isPaused = false; Stream? audioStream; - init() { - checkPermission(); + Future init() async { + await checkPermission(); } @override @@ -33,25 +47,23 @@ class AudioInput extends ChangeNotifier { encoder: _encoder, sampleRate: 24000, numChannels: 1, - //sampleRate: 16000, - //numChannels: 2, + echoCancel: true, + noiseSuppress: true, + androidConfig: const AndroidRecordConfig( + audioSource: AndroidAudioSource.voiceCommunication, + ), + iosConfig: const IosRecordConfig(categoryOptions: []), ); - final devices = await _recorder.listInputDevices(); - print(devices.toString()); + await _recorder.listInputDevices(); audioStream = await _recorder.startStream(recordConfig); isRecording = true; - print("${isRecording ? "Is" : "Not"} Recording"); notifyListeners(); return audioStream; - /*await for (final data in stream) { - yield data; - }*/ } Future stopRecording() async { await _recorder.stop(); isRecording = false; - print("${isRecording ? "Is" : "Not"} Recording"); notifyListeners(); } @@ -63,7 +75,6 @@ class AudioInput extends ChangeNotifier { await _recorder.pause(); isPaused = true; } - print("${isPaused ? "Is" : "Not"} Paused"); notifyListeners(); return; } @@ -79,9 +90,6 @@ class MicrophonePermissionDeniedException implements Exception { @override String toString() { - if (message == null) { - return 'MicrophonePermissionDeniedException'; - } return 'MicrophonePermissionDeniedException: $message'; } } diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart index 2d6d81c3a7bc..2f13d3fd2349 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart +++ b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_output.dart @@ -1,3 +1,19 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +import 'dart:typed_data'; + import 'package:flutter_soloud/flutter_soloud.dart'; class AudioOutput { @@ -20,25 +36,14 @@ class AudioOutput { 1024 * 1024 * 10, // 10MB of max buffer (not allocated) bufferingType: BufferingType.released, bufferingTimeNeeds: 0, - sampleRate: 24000, // <<< MATCH SoLoud init and input - channels: Channels.mono, // <<< MATCH input (likely mono) - format: BufferType.s16le, // Should match pcm16bits - onBuffering: (isBuffering, handle, time) { - // When isBuffering==true, the stream is set to paused automatically till - // it reaches bufferingTimeNeeds of audio data or until setDataIsEnded is called - // or maxBufferSizeBytes is reached. When isBuffering==false, the playback stream - // is resumed. - print('Buffering: $isBuffering, Time: $time'); - }, + onBuffering: (isBuffering, handle, time) {}, ); - print("New audio output stream buffer created."); // Reset handle to null until the stream is played again handle = null; } } Future playStream() async { - print('playing'); handle = await SoLoud.instance.play(stream!); return stream; } @@ -51,7 +56,11 @@ class AudioOutput { await SoLoud.instance.stop(handle!); // Clear old stream, set up new session for next time. - setupNewStream(); + await setupNewStream(); } } + + void addAudioStream(Uint8List audioChunk) { + SoLoud.instance.addAudioDataStream(stream!, audioChunk); + } } diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_player.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_player.dart deleted file mode 100644 index 3c5559481ed7..000000000000 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_player.dart +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2025 Google LLC -// -// 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 -// -// http://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. - -import 'dart:typed_data'; -import 'dart:async'; - -import 'package:just_audio/just_audio.dart'; - -/// Creates a WAV audio chunk with a properly formatted header. -Future audioChunkWithHeader( - List data, - int sampleRate, -) async { - var channels = 1; - - int byteRate = ((16 * sampleRate * channels) / 8).round(); - - var size = data.length; - var fileSize = size + 36; - - Uint8List header = Uint8List.fromList([ - // "RIFF" - 82, 73, 70, 70, - fileSize & 0xff, - (fileSize >> 8) & 0xff, - (fileSize >> 16) & 0xff, - (fileSize >> 24) & 0xff, - // WAVE - 87, 65, 86, 69, - // fmt - 102, 109, 116, 32, - // fmt chunk size 16 - 16, 0, 0, 0, - // Type of format - 1, 0, - // One channel - channels, 0, - // Sample rate - sampleRate & 0xff, - (sampleRate >> 8) & 0xff, - (sampleRate >> 16) & 0xff, - (sampleRate >> 24) & 0xff, - // Byte rate - byteRate & 0xff, - (byteRate >> 8) & 0xff, - (byteRate >> 16) & 0xff, - (byteRate >> 24) & 0xff, - // Uhm - ((16 * channels) / 8).round(), 0, - // bitsize - 16, 0, - // "data" - 100, 97, 116, 97, - size & 0xff, - (size >> 8) & 0xff, - (size >> 16) & 0xff, - (size >> 24) & 0xff, - // incoming data - ...data, - ]); - return header; -} - -class ByteStreamAudioSource extends StreamAudioSource { - ByteStreamAudioSource(this.bytes) : super(tag: 'Byte Stream Audio'); - - final Uint8List bytes; - - @override - Future request([int? start, int? end]) async { - start ??= 0; - end ??= bytes.length; - return StreamAudioResponse( - sourceLength: bytes.length, - contentLength: end - start, - offset: start, - stream: Stream.value(bytes.sublist(start, end)), - contentType: 'audio/wav', // Or the appropriate content type - ); - } -} - -class AudioStreamManager { - final _audioPlayer = AudioPlayer(); - final _audioChunkController = StreamController(); - var _audioSource = ConcatenatingAudioSource( - children: [], - ); - - AudioStreamManager() { - _initAudioPlayer(); - } - - Future _initAudioPlayer() async { - // 1. Create a ConcatenatingAudioSource to handle the stream - await _audioPlayer.setAudioSource(_audioSource); - - // 2. Listen to the stream of audio chunks - _audioChunkController.stream.listen(_addAudioChunk); - - await _audioPlayer.play(); // Start playing (even if initially empty) - - _audioPlayer.processingStateStream.listen((state) async { - if (state == ProcessingState.completed) { - await _audioPlayer - .pause(); // Or player.stop() if you want to release resources - await _audioPlayer.seek(Duration.zero, index: 0); - await _audioSource.clear(); - await _audioPlayer.play(); - } - }); - } - - Future _addAudioChunk(Uint8List chunk) async { - var buffer = ByteStreamAudioSource(chunk); - - await _audioSource.add(buffer); - } - - void addAudio(Uint8List chunk) { - _audioChunkController.add(chunk); - } - - Future stopAudioPlayer() async { - await _audioPlayer.stop(); - } - - Future disposeAudioPlayer() async { - await _audioPlayer.dispose(); - await _audioChunkController.close(); - } -} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_recorder.dart b/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_recorder.dart deleted file mode 100644 index 1f3710cd0c8f..000000000000 --- a/packages/firebase_vertexai/firebase_vertexai/example/lib/utils/audio_recorder.dart +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright 2025 Google LLC -// -// 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 -// -// http://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. - -import 'dart:async'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:record/record.dart'; - -/// An exception thrown when microphone permission is denied or not granted. -class MicrophonePermissionDeniedException implements Exception { - /// The optional message associated with the permission denial. - final String? message; - - /// Creates a new [MicrophonePermissionDeniedException] with an optional [message]. - MicrophonePermissionDeniedException([this.message]); - - @override - String toString() { - if (message == null) { - return 'MicrophonePermissionDeniedException'; - } - return 'MicrophonePermissionDeniedException: $message'; - } -} - -class Resampler { - /// Resamples 16-bit integer PCM audio data from a source sample rate to a - /// target sample rate using linear interpolation. - /// - /// [sourceRate]: The sample rate of the input audio data. - /// [targetRate]: The desired sample rate of the output audio data. - /// [input]: The input audio data as a Uint8List containing 16-bit PCM samples. - /// - /// Returns a new Uint8List containing 16-bit PCM samples resampled to the - /// target rate. - static Uint8List resampleLinear16( - int sourceRate, - int targetRate, - Uint8List input, - ) { - if (sourceRate == targetRate) return input; // No resampling needed - - final outputLength = (input.length * targetRate / sourceRate).round(); - final output = Uint8List(outputLength); - final inputData = Int16List.view(input.buffer); - final outputData = Int16List.view(output.buffer); - - for (int i = 0; i < outputLength ~/ 2; i++) { - final sourcePosition = i * sourceRate / targetRate; - final index1 = sourcePosition.floor(); - final index2 = index1 + 1; - final weight2 = sourcePosition - index1; - final weight1 = 1.0 - weight2; - - // Ensure indices are within the valid range - final sample1 = inputData[index1.clamp(0, inputData.length - 1)]; - final sample2 = inputData[index2.clamp(0, inputData.length - 1)]; - - // Interpolate and convert back to 16-bit integer - final interpolatedSample = - (sample1 * weight1 + sample2 * weight2).toInt(); - - outputData[i] = interpolatedSample; - } - - return output; - } -} - -class InMemoryAudioRecorder { - final _audioChunks = []; - final _recorder = AudioRecorder(); - StreamSubscription? _recordSubscription; - late String? _lastAudioPath; - AudioEncoder _encoder = AudioEncoder.pcm16bits; - - Future _getPath() async { - String suffix; - if (_encoder == AudioEncoder.pcm16bits) { - suffix = 'pcm'; - } else if (_encoder == AudioEncoder.aacLc) { - suffix = 'm4a'; - } else { - suffix = 'wav'; - } - final dir = await getDownloadsDirectory(); - final path = - '${dir!.path}/audio_${DateTime.now().millisecondsSinceEpoch}.$suffix'; - return path; - } - - Future checkPermission() async { - final hasPermission = await _recorder.hasPermission(); - if (!hasPermission) { - throw MicrophonePermissionDeniedException('Not having mic permission'); - } - } - - Future _isEncoderSupported(AudioEncoder encoder) async { - final isSupported = await _recorder.isEncoderSupported( - encoder, - ); - - if (!isSupported) { - debugPrint('${encoder.name} is not supported on this platform.'); - debugPrint('Supported encoders are:'); - - for (final e in AudioEncoder.values) { - if (await _recorder.isEncoderSupported(e)) { - debugPrint('- ${e.name}'); - } - } - } - - return isSupported; - } - - Future startRecording({bool fromFile = false}) async { - if (!await _isEncoderSupported(_encoder)) { - return; - } - var recordConfig = RecordConfig( - encoder: _encoder, - sampleRate: 16000, - numChannels: 1, - androidConfig: const AndroidRecordConfig( - muteAudio: true, - audioSource: AndroidAudioSource.mic, - ), - ); - final devs = await _recorder.listInputDevices(); - debugPrint(devs.toString()); - _lastAudioPath = await _getPath(); - if (fromFile) { - await _recorder.start(recordConfig, path: _lastAudioPath!); - } else { - final stream = await _recorder.startStream(recordConfig); - _recordSubscription = stream.listen(_audioChunks.add); - } - } - - Future startRecordingFile() async { - if (!await _isEncoderSupported(_encoder)) { - return; - } - var recordConfig = RecordConfig( - encoder: _encoder, - sampleRate: 16000, - numChannels: 1, - ); - final devs = await _recorder.listInputDevices(); - debugPrint(devs.toString()); - _lastAudioPath = await _getPath(); - await _recorder.start(recordConfig, path: _lastAudioPath!); - } - - Stream startRecordingStream() async* { - if (!await _isEncoderSupported(_encoder)) { - return; - } - var recordConfig = RecordConfig( - encoder: _encoder, - sampleRate: 16000, - numChannels: 1, - ); - final devices = await _recorder.listInputDevices(); - debugPrint(devices.toString()); - final stream = await _recorder.startStream(recordConfig); - - await for (final data in stream) { - yield data; - } - } - - Future stopRecording() async { - await _recordSubscription?.cancel(); - _recordSubscription = null; - - await _recorder.stop(); - } - - Future fetchAudioBytes({ - bool fromFile = false, - bool removeHeader = false, - }) async { - Uint8List resultBytes; - if (fromFile) { - resultBytes = await _getAudioBytesFromFile(_lastAudioPath!); - } else { - final builder = BytesBuilder(); - _audioChunks.forEach(builder.add); - resultBytes = builder.toBytes(); - } - - // resample - resultBytes = Resampler.resampleLinear16(44100, 16000, resultBytes); - final dir = await getDownloadsDirectory(); - final path = '${dir!.path}/audio_resampled.pcm'; - final file = File(path); - final sink = file.openWrite(); - - sink.add(resultBytes); - - await sink.close(); - return resultBytes; - } - - Future _removeWavHeader(Uint8List audio) async { - // Assuming a standard WAV header size of 44 bytes - const wavHeaderSize = 44; - final audioData = audio.sublist(wavHeaderSize); - return audioData; - } - - Future _getAudioBytesFromFile( - String filePath, { - bool removeHeader = false, - }) async { - final file = File(_lastAudioPath!); - - if (!file.existsSync()) { - throw Exception('Audio file not found: ${file.path}'); - } - - var pcmBytes = await file.readAsBytes(); - if (removeHeader) { - pcmBytes = await _removeWavHeader(pcmBytes); - } - return pcmBytes; - } -} diff --git a/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml b/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml index 13f7f9f60b4d..7f62a9dd4a3b 100644 --- a/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml +++ b/packages/firebase_vertexai/firebase_vertexai/example/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: sdk: flutter flutter_markdown: ^0.6.20 flutter_soloud: ^3.1.6 - just_audio: ^0.9.43 path_provider: ^2.1.5 record: ^5.2.1