Repository: Isvisoft/flutter_screen_recording Branch: master Commit: 358745e7a174 Files: 88 Total size: 120.3 KB Directory structure: gitextract_jxm2crhe/ ├── .gitignore ├── README.md ├── analysis_options.yaml ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── GeneratedPluginRegistrant.java │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── isvisoft/ │ │ │ │ │ ├── example/ │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ └── flutter_screen_recording_example/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ └── values/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ └── settings.gradle │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── ephemeral/ │ │ │ ├── flutter_lldb_helper.py │ │ │ └── flutter_lldbinit │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── lib/ │ │ ├── generated_plugin_registrant.dart │ │ └── main.dart │ ├── pubspec.yaml │ ├── test/ │ │ └── widget_test.dart │ └── web/ │ ├── index.html │ └── manifest.json ├── flutter_screen_recording/ │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── io/ │ │ │ └── flutter/ │ │ │ └── plugins/ │ │ │ └── GeneratedPluginRegistrant.java │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── settings.gradle │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── kotlin/ │ │ └── com/ │ │ └── isvisoft/ │ │ └── flutter_screen_recording/ │ │ ├── FlutterScreenRecordingPlugin.kt │ │ └── ForegroundService.kt │ ├── ios/ │ │ ├── .gitignore │ │ ├── Assets/ │ │ │ └── .gitkeep │ │ ├── Classes/ │ │ │ ├── FlutterScreenRecordingPlugin.h │ │ │ ├── FlutterScreenRecordingPlugin.m │ │ │ └── SwiftFlutterScreenRecordingPlugin.swift │ │ └── flutter_screen_recording.podspec │ ├── lib/ │ │ └── flutter_screen_recording.dart │ └── pubspec.yaml ├── flutter_screen_recording_platform_interface/ │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── lib/ │ │ ├── flutter_screen_recording_platform_interface.dart │ │ └── method_channel_flutter_screen_recording.dart │ └── pubspec.yaml └── flutter_screen_recording_web/ ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib/ │ ├── flutter_screen_recording_web.dart │ └── interop/ │ └── get_display_media.dart ├── pubspec.yaml └── test/ └── flutter_screen_recording_web_test.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ .vscode # 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/ .dart_tool/ .flutter-plugins .packages .pub-cache/ .pub/ /build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/local.properties # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages .flutter-plugins .flutter-plugins-dependencies example/.flutter-plugins-dependencies ================================================ FILE: README.md ================================================ # flutter_screen_recording Flutter plugin to record the screen on Android, iOS, and web. Current platform support in this repository: - Android: `minSdkVersion 23` - iOS: `iOS 11.0+` - Web: supported through the federated web implementation ## Getting Started Import the package: ```dart import 'package:flutter_screen_recording/flutter_screen_recording.dart'; ``` Start screen recording: ```dart final bool started = await FlutterScreenRecording.startRecordScreen( 'my_recording', titleNotification: 'Screen recording', messageNotification: 'Recording in progress', ); ``` Start screen recording with microphone audio: ```dart final bool started = await FlutterScreenRecording.startRecordScreenAndAudio( 'my_recording', titleNotification: 'Screen recording', messageNotification: 'Recording in progress', ); ``` Stop recording and get the output path or file name: ```dart final String path = await FlutterScreenRecording.stopRecordScreen; ``` ## Android The Android implementation uses `MediaProjection`, `MediaRecorder`, and a foreground service. - The plugin currently builds with `compileSdkVersion 35` - The plugin manifest already includes its service declaration and required foreground-service permissions - If you record audio, request microphone permission at runtime in your app - On modern Android versions, you may also need notification permission for the foreground service notification The example app requests permissions with `permission_handler` before starting recording. ## iOS The iOS implementation uses `ReplayKit` and requires `iOS 11.0+`. Add the usage description for microphone access if you record audio: ```xml NSMicrophoneUsageDescription Save audio in video ``` The plugin returns the local output file path. If your app later saves the file to the Photos library, also add the appropriate Photos usage description to your app. ## Web The web implementation uses `getDisplayMedia` and `MediaRecorder`. - Best experience is on modern desktop browsers - Browser support depends on screen-capture and codec support - The web implementation downloads the recorded file in the browser when recording stops ## Notes - This package exposes asynchronous APIs; use `await` when starting and stopping recordings - Notification title and message parameters are used by the Android implementation - Returned output differs by platform: native platforms return a local path, while web triggers a browser download ================================================ FILE: analysis_options.yaml ================================================ formatter: page_width: 135 ================================================ FILE: example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # 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 .packages .pub-cache/ .pub/ build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: example/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f channel: beta project_type: app ================================================ FILE: example/README.md ================================================ # flutter_screen_recording_example Demonstrates how to use the flutter_screen_recording plugin. ## Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) For help getting started with Flutter, view our [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. ================================================ FILE: example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties ================================================ FILE: example/android/app/build.gradle ================================================ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'dev.flutter.flutter-gradle-plugin' } def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } android { namespace 'com.isvisoft.flutter_screen_recording_example' compileSdkVersion 36 sourceSets { main.java.srcDirs += 'src/main/kotlin' } lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.isvisoft.flutter_screen_recording_example" minSdkVersion flutter.minSdkVersion targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" } 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 '../..' } dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' implementation 'androidx.appcompat:appcompat:1.0.0' } ================================================ FILE: example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java ================================================ package io.flutter.plugins; import androidx.annotation.Keep; import androidx.annotation.NonNull; import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; /** * Generated file. Do not edit. * This file is generated by the Flutter tool based on the * plugins that support the Android platform. */ @Keep public final class GeneratedPluginRegistrant { private static final String TAG = "GeneratedPluginRegistrant"; public static void registerWith(@NonNull FlutterEngine flutterEngine) { try { flutterEngine.getPlugins().add(new com.pravera.flutter_foreground_task.FlutterForegroundTaskPlugin()); } catch (Exception e) { Log.e(TAG, "Error registering plugin flutter_foreground_task, com.pravera.flutter_foreground_task.FlutterForegroundTaskPlugin", e); } try { flutterEngine.getPlugins().add(new com.isvisoft.flutter_screen_recording.FlutterScreenRecordingPlugin()); } catch (Exception e) { Log.e(TAG, "Error registering plugin flutter_screen_recording, com.isvisoft.flutter_screen_recording.FlutterScreenRecordingPlugin", e); } try { flutterEngine.getPlugins().add(new com.crazecoder.openfile.OpenFilePlugin()); } catch (Exception e) { Log.e(TAG, "Error registering plugin open_file_android, com.crazecoder.openfile.OpenFilePlugin", e); } try { flutterEngine.getPlugins().add(new com.baseflow.permissionhandler.PermissionHandlerPlugin()); } catch (Exception e) { Log.e(TAG, "Error registering plugin permission_handler_android, com.baseflow.permissionhandler.PermissionHandlerPlugin", e); } try { flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin()); } catch (Exception e) { Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e); } } } ================================================ FILE: example/android/app/src/main/kotlin/com/isvisoft/example/MainActivity.kt ================================================ package com.isvisoft.example import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ================================================ FILE: example/android/app/src/main/kotlin/com/isvisoft/flutter_screen_recording_example/MainActivity.kt ================================================ package com.isvisoft.flutter_screen_recording_example import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine; class MainActivity: FlutterActivity() { } ================================================ FILE: example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: example/android/build.gradle ================================================ allprojects { repositories { google() mavenCentral() maven { url 'https://jitpack.io' } } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } tasks.register("clean", Delete) { delete rootProject.buildDir } ================================================ FILE: example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Sep 17 21:08:02 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip ================================================ FILE: example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx2048M android.enableR8=true android.useAndroidX=true android.enableJetifier=false ================================================ FILE: example/android/settings.gradle ================================================ pluginManagement { def flutterSdkPath = { def properties = new Properties() file("local.properties").withInputStream { properties.load(it) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath }() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.11.1" apply false id "org.jetbrains.kotlin.android" version "2.2.20" apply false } include(":app") ================================================ FILE: example/ios/.gitignore ================================================ *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 9.0 ================================================ FILE: example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Flutter/ephemeral/flutter_lldb_helper.py ================================================ # # Generated file, do not edit. # import lldb def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" base = frame.register["x0"].GetValueAsAddress() page_len = frame.register["x1"].GetValueAsUnsigned() # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the # first page to see if handled it correctly. This makes diagnosing # misconfiguration (e.g. missing breakpoint) easier. data = bytearray(page_len) data[0:8] = b'IHELPED!' error = lldb.SBError() frame.GetThread().GetProcess().WriteMemory(base, data, error) if not error.Success(): print(f'Failed to write into {base}[+{page_len}]', error) return def __lldb_init_module(debugger: lldb.SBDebugger, _): target = debugger.GetDummyTarget() # Caveat: must use BreakpointCreateByRegEx here and not # BreakpointCreateByName. For some reasons callback function does not # get carried over from dummy target for the later. bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) bp.SetAutoContinue(True) print("-- LLDB integration loaded --") ================================================ FILE: example/ios/Flutter/ephemeral/flutter_lldbinit ================================================ # # Generated file, do not edit. # command script import --relative-to-command-file flutter_lldb_helper.py ================================================ FILE: example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: example/ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. ================================================ FILE: example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName flutter_screen_recording_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS NSMicrophoneUsageDescription Save audio in video NSPhotoLibraryUsageDescription Save video in gallery UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: example/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 48C732CB3C69385D54A034BD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 185C9162DA468CC4BE0965D1 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 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 */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* 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 = ""; }; 185C9162DA468CC4BE0965D1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 41805422E2BD3E2C89559507 /* 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 = ""; }; 6BBA514A532944C87EC9D773 /* 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 = ""; }; 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 = ""; }; 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; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 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 = ""; }; B6939BF1A171A396BBFFF539 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 48C732CB3C69385D54A034BD /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0857EB6ACFF648BF2EB910EA /* Frameworks */ = { isa = PBXGroup; children = ( 185C9162DA468CC4BE0965D1 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, A914DAAD988B38B3D7097D97 /* Pods */, 0857EB6ACFF648BF2EB910EA /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( ); name = "Supporting Files"; sourceTree = ""; }; A914DAAD988B38B3D7097D97 /* Pods */ = { isa = PBXGroup; children = ( B6939BF1A171A396BBFFF539 /* Pods-Runner.debug.xcconfig */, 6BBA514A532944C87EC9D773 /* Pods-Runner.release.xcconfig */, 41805422E2BD3E2C89559507 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 78DFD84CE180EFB1601D168A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 6C1CA423670BEB4A6D273175 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1020; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = Q5Y7GV2TU6; LastSwiftMigration = 0910; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 6C1CA423670BEB4A6D273175 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${BUILT_PRODUCTS_DIR}/flutter_foreground_plugin/flutter_foreground_plugin.framework", "${BUILT_PRODUCTS_DIR}/flutter_screen_recording/flutter_screen_recording.framework", "${BUILT_PRODUCTS_DIR}/open_file/open_file.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_foreground_plugin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_screen_recording.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_file.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 78DFD84CE180EFB1601D168A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); 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; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = Q5Y7GV2TU6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.isvisoft.flutterScreenRecordingExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = Q5Y7GV2TU6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.isvisoft.flutterScreenRecordingExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = Q5Y7GV2TU6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.isvisoft.flutterScreenRecordingExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/lib/generated_plugin_registrant.dart ================================================ // // Generated file. Do not edit. // // ignore_for_file: directives_ordering // ignore_for_file: lines_longer_than_80_chars // ignore_for_file: depend_on_referenced_packages import 'package:flutter_screen_recording_web/flutter_screen_recording_web.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; // ignore: public_member_api_docs void registerPlugins(Registrar registrar) { WebFlutterScreenRecording.registerWith(registrar); SharedPreferencesPlugin.registerWith(registrar); registrar.registerMessageHandler(); } ================================================ FILE: example/lib/main.dart ================================================ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screen_recording/flutter_screen_recording.dart'; import 'package:quiver/async.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:open_file/open_file.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { bool recording = false; int _time = 0; requestPermissions() async { if (!kIsWeb) { if (await Permission.notification.isDenied) { await Permission.notification.request(); } if (await Permission.microphone.request().isDenied) { await Permission.microphone.request(); } } } @override void initState() { super.initState(); requestPermissions(); startTimer(); } void startTimer() { CountdownTimer countDownTimer = new CountdownTimer( new Duration(seconds: 1000), new Duration(seconds: 1), ); var sub = countDownTimer.listen(null); sub.onData((duration) { setState(() => _time++); }); sub.onDone(() { print("Done"); sub.cancel(); }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Flutter Screen Recording'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Time: $_time\n'), !recording ? Center( child: ElevatedButton( child: Text("Record Screen"), onPressed: () => startScreenRecord(false), ), ) : Container(), !recording ? Center( child: ElevatedButton( child: Text("Record Screen & audio"), onPressed: () => startScreenRecord(true), ), ) : Center( child: ElevatedButton( child: Text("Stop Record"), onPressed: () => stopScreenRecord(), ), ) ], ), ), ); } startScreenRecord(bool audio) async { bool start = false; if (audio) { start = await FlutterScreenRecording.startRecordScreenAndAudio( "Title", titleNotification: "titleNotification", messageNotification: "messageNotification", ); } else { start = await FlutterScreenRecording.startRecordScreen( "Title", titleNotification: "titleNotification", messageNotification: "messageNotification", ); } if (start) { setState(() => recording = !recording); } return start; } stopScreenRecord() async { String path = await FlutterScreenRecording.stopRecordScreen; setState(() { recording = !recording; }); print("Opening video"); print(path); OpenFile.open(path); } } ================================================ FILE: example/pubspec.yaml ================================================ name: flutter_screen_recording_example description: Demonstrates how to use the flutter_screen_recording plugin. publish_to: "none" environment: sdk: ">=2.12.0 <4.0.0" dependencies: flutter: sdk: flutter quiver: ^3.0.0 flutter_screen_recording: path: ../flutter_screen_recording/ # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter flutter_foreground_task: ^9.1.0 permission_handler: ^11.3.1 open_file: ^3.5.10 dependency_overrides: flutter_screen_recording_web: path: ../flutter_screen_recording_web # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: example/test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility that Flutter provides. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_screen_recording_example/main.dart'; void main() { testWidgets('Verify Platform version', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(MyApp()); // Verify that platform version is retrieved. expect( find.byWidgetPredicate( (Widget widget) => widget is Text && widget.data!.startsWith('Running on:'), ), findsOneWidget, ); }); } ================================================ FILE: example/web/index.html ================================================ example ================================================ FILE: example/web/manifest.json ================================================ { "name": "example", "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: flutter_screen_recording/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # 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 .packages .vscode .pub-cache/ .pub/ build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: flutter_screen_recording/.metadata ================================================ # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f channel: beta project_type: plugin ================================================ FILE: flutter_screen_recording/CHANGELOG.md ================================================ ## 2.0.24 - Refactor ## 2.0.23 - Update flutter_foreground_task ## 2.0.22 - Bug Fixed ## 2.0.21 - Bug Fixed ## 2.0.20 - Update Android ## 2.0.19 - Fix Audio record in iOS ## 2.0.18 - Fix Android 34 notification ## 2.0.17 - Update web lib ## 2.0.16 - Update audio codecs android ## 2.0.15 - try/catch control ## 2.0.14 - Bug fixed ios ## 2.0.13 - Bug fixed ## 2.0.12 - Bug fixed ## 2.0.11 - Android v2 embedding ## 2.0.10 - Update kotlin and gradle versions ## 2.0.9 - Update flutter ## 2.0.8 - Implemented null-safety ## 2.0.7 - Bugs Android 12 fixed ## 2.0.6 - Support Android 12 ## 2.0.5 - foreground android ## 2.0.4 - update android ## 2.0.3 - ios Bug ## 2.0.2 - Change devDependencies ## 2.0.1 - Change dependencies ## 2.0.0 - Federated Plugin - Web support ## 1.0.10 - Update docs ## 1.0.9 - iOS bug fixed ## 1.0.8 - Update Doc ## 1.0.7 - Add fun startRecordScreenAndAudio in Android ## 1.0.6 - Some Android bug fixed ## 1.0.5 - Android devices compatibility ## 1.0.4 - Included permission controller - Bug fixed ## 1.0.2 - ImagePicker conflict fixed - Remove HBRecorder. ================================================ FILE: flutter_screen_recording/LICENSE ================================================ MIT License Copyright (c) 2019 Isvisoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: flutter_screen_recording/README.md ================================================ # flutter_screen_recording Flutter plugin to record the screen on Android, iOS, and web. Current platform support in this repository: - Android: `minSdkVersion 23` - iOS: `iOS 11.0+` - Web: supported through the federated web implementation ## Getting Started Import the package: ```dart import 'package:flutter_screen_recording/flutter_screen_recording.dart'; ``` Start screen recording: ```dart final bool started = await FlutterScreenRecording.startRecordScreen( 'my_recording', titleNotification: 'Screen recording', messageNotification: 'Recording in progress', ); ``` Start screen recording with microphone audio: ```dart final bool started = await FlutterScreenRecording.startRecordScreenAndAudio( 'my_recording', titleNotification: 'Screen recording', messageNotification: 'Recording in progress', ); ``` Stop recording and get the output path or file name: ```dart final String path = await FlutterScreenRecording.stopRecordScreen; ``` ## Android The Android implementation uses `MediaProjection`, `MediaRecorder`, and a foreground service. - The plugin currently builds with `compileSdkVersion 35` - The plugin manifest already includes its service declaration and required foreground-service permissions - If you record audio, request microphone permission at runtime in your app - On modern Android versions, you may also need notification permission for the foreground service notification The example app requests permissions with `permission_handler` before starting recording. ## iOS The iOS implementation uses `ReplayKit` and requires `iOS 11.0+`. Add the usage description for microphone access if you record audio: ```xml NSMicrophoneUsageDescription Save audio in video ``` The plugin returns the local output file path. If your app later saves the file to the Photos library, also add the appropriate Photos usage description to your app. ## Web The web implementation uses `getDisplayMedia` and `MediaRecorder`. - Best experience is on modern desktop browsers - Browser support depends on screen-capture and codec support - The web implementation downloads the recorded file in the browser when recording stops ## Notes - This package exposes asynchronous APIs; use `await` when starting and stopping recordings - Notification title and message parameters are used by the Android implementation - Returned output differs by platform: native platforms return a local path, while web triggers a browser download ================================================ FILE: flutter_screen_recording/android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: flutter_screen_recording/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java ================================================ package io.flutter.plugins; import io.flutter.plugin.common.PluginRegistry; import com.isvisoft.flutter_screen_recording.FlutterScreenRecordingPlugin; /** * Generated file. Do not edit. */ public final class GeneratedPluginRegistrant { public static void registerWith(PluginRegistry registry) { if (alreadyRegisteredWith(registry)) { return; } FlutterScreenRecordingPlugin.registerWith(registry.registrarFor("com.isvisoft.flutter_screen_recording.FlutterScreenRecordingPlugin")); } private static boolean alreadyRegisteredWith(PluginRegistry registry) { final String key = GeneratedPluginRegistrant.class.getCanonicalName(); if (registry.hasPlugin(key)) { return true; } registry.registrarFor(key); return false; } } ================================================ FILE: flutter_screen_recording/android/build.gradle ================================================ group 'com.isvisoft.flutter_screen_recording' version '1.0-SNAPSHOT' buildscript { ext.kotlin_version = '2.1.0' repositories { google() jcenter() maven { url 'https://jitpack.io' } } dependencies { classpath 'com.android.tools.build:gradle:8.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } rootProject.allprojects { repositories { google() jcenter() maven { url 'https://jitpack.io' } maven { url "https://artifact.bytedance.com/repository/Volcengine/" } } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { compileSdkVersion 35 namespace 'com.isvisoft.flutter_screen_recording' sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { minSdkVersion 23 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'InvalidPackage' } compileOptions { targetCompatibility JavaVersion.VERSION_17 } android { kotlinOptions { jvmTarget = '17' } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.6.0' implementation 'com.github.HBiSoft:HBRecorder:2.0.5' // Flutter dependencies //compileOnly("io.flutter:flutter_embedding_debug:1.0.0-dbec018f4d83ae4b7b97eb8c5a066c61832e12df") // For debug //compileOnly("io.flutter:flutter_embedding_release:1.0.0-dbec018f4d83ae4b7b97eb8c5a066c61832e12df") // For release } ================================================ FILE: flutter_screen_recording/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Sep 24 17:44:55 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip ================================================ FILE: flutter_screen_recording/android/gradle.properties ================================================ android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M ================================================ FILE: flutter_screen_recording/android/settings.gradle ================================================ rootProject.name = 'flutter_screen_recording' ================================================ FILE: flutter_screen_recording/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: flutter_screen_recording/android/src/main/kotlin/com/isvisoft/flutter_screen_recording/FlutterScreenRecordingPlugin.kt ================================================ package com.isvisoft.flutter_screen_recording import android.annotation.SuppressLint import android.app.Activity import android.content.ComponentName import android.content.Intent import android.content.ServiceConnection import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.media.MediaRecorder import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.Build import android.os.Environment import android.os.IBinder import android.util.DisplayMetrics import android.util.Log import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.PluginRegistry import java.io.IOException import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding class FlutterScreenRecordingPlugin : MethodCallHandler, PluginRegistry.ActivityResultListener, FlutterPlugin, ActivityAware { private var mScreenDensity: Int = 0 var mMediaRecorder: MediaRecorder? = null val mProjectionManager: MediaProjectionManager by lazy { ContextCompat.getSystemService( pluginBinding!!.applicationContext, MediaProjectionManager::class.java ) ?: throw Exception("MediaProjectionManager not found") } var mMediaProjection: MediaProjection? = null var mMediaProjectionCallback: MediaProjectionCallback? = null var mVirtualDisplay: VirtualDisplay? = null private var mDisplayWidth: Int = 1280 private var mDisplayHeight: Int = 800 private var videoName: String? = "" private var mFileName: String? = "" private var mTitle = "Your screen is being recorded" private var mMessage = "Your screen is being recorded" private var recordAudio: Boolean? = false; private val SCREEN_RECORD_REQUEST_CODE = 333 private var pendingResult: Result? = null private var pluginBinding: FlutterPlugin.FlutterPluginBinding? = null private var activityBinding: ActivityPluginBinding? = null private var serviceConnection: ServiceConnection? = null private fun completePendingResult(value: Boolean) { pendingResult?.success(value) pendingResult = null } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { val context = pluginBinding!!.applicationContext if (requestCode == SCREEN_RECORD_REQUEST_CODE) { if (pendingResult == null) { Log.w("ScreenRecordingPlugin", "Ignoring activity result with no pending callback") if (resultCode != Activity.RESULT_OK) { ForegroundService.stopService(context) } return true } if (resultCode == Activity.RESULT_OK) { ForegroundService.startService(context, mTitle, mMessage) val intentConnection = Intent(context, ForegroundService::class.java) serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { try { startRecordScreen() mMediaProjectionCallback = MediaProjectionCallback() mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data!!) mMediaProjection?.registerCallback(mMediaProjectionCallback!!, null) mVirtualDisplay = createVirtualDisplay() completePendingResult(true) } catch (e: Throwable) { e.message?.let { Log.e("ScreenRecordingPlugin", it) } completePendingResult(false) } } override fun onServiceDisconnected(name: ComponentName?) { } } val isBound = context.bindService(intentConnection, serviceConnection!!, Activity.BIND_AUTO_CREATE) if (!isBound) { ForegroundService.stopService(context) completePendingResult(false) } } else { ForegroundService.stopService(context) completePendingResult(false) } return true } return false } override fun onMethodCall(call: MethodCall, result: Result) { val appContext = pluginBinding!!.applicationContext when (call.method) { "startRecordScreen" -> { if (pendingResult != null) { result.error( "already_pending", "A screen recording request is already pending.", null ) return } try { pendingResult = result val title = call.argument("title") val message = call.argument("message") if (!title.isNullOrEmpty()) { mTitle = title } if (!message.isNullOrEmpty()) { mMessage = message } val metrics = DisplayMetrics() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val display = activityBinding!!.activity.display display?.getRealMetrics(metrics) } else { @SuppressLint("NewApi") val defaultDisplay = appContext.display defaultDisplay?.getMetrics(metrics) } mScreenDensity = metrics.densityDpi calculateResolution(metrics) videoName = call.argument("name") recordAudio = call.argument("audio") val permissionIntent = mProjectionManager.createScreenCaptureIntent() ActivityCompat.startActivityForResult( activityBinding!!.activity, permissionIntent, SCREEN_RECORD_REQUEST_CODE, null ) } catch (e: Exception) { println("Error onMethodCall startRecordScreen") println(e.message) pendingResult = null result.success(false) } } "stopRecordScreen" -> { try { serviceConnection?.let { appContext.unbindService(it) } ForegroundService.stopService(pluginBinding!!.applicationContext) if (mMediaRecorder != null) { stopRecordScreen() result.success(mFileName) } else { result.success("") } } catch (e: Exception) { result.success("") } } else -> { result.notImplemented() } } } private fun calculateResolution(metrics: DisplayMetrics) { mDisplayHeight = metrics.heightPixels mDisplayWidth = metrics.widthPixels var maxRes = 1280.0; if (metrics.scaledDensity >= 3.0f) { maxRes = 1920.0; } if (metrics.widthPixels > metrics.heightPixels) { var rate = metrics.widthPixels / maxRes if (rate > 1.5) { rate = 1.5 } mDisplayWidth = maxRes.toInt() mDisplayHeight = (metrics.heightPixels / rate).toInt() println("Rate : $rate") } else { var rate = metrics.heightPixels / maxRes if (rate > 1.5) { rate = 1.5 } mDisplayHeight = maxRes.toInt() mDisplayWidth = (metrics.widthPixels / rate).toInt() println("Rate : $rate") } println("Scaled Density") println(metrics.scaledDensity) println("Original Resolution ") println(metrics.widthPixels.toString() + " x " + metrics.heightPixels) println("Calcule Resolution ") println("$mDisplayWidth x $mDisplayHeight") } private fun startRecordScreen() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { mMediaRecorder = MediaRecorder(pluginBinding!!.applicationContext) } else { @Suppress("DEPRECATION") mMediaRecorder = MediaRecorder() } try { mFileName = if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { pluginBinding!!.applicationContext.externalCacheDir?.absolutePath } else { pluginBinding!!.applicationContext.cacheDir?.absolutePath } mFileName += "/$videoName.mp4" } catch (e: IOException) { println("Error creating name") return } mMediaRecorder?.setVideoSource(MediaRecorder.VideoSource.SURFACE) if (recordAudio!!) { mMediaRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); } else { mMediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) } mMediaRecorder?.setOutputFile(mFileName) mMediaRecorder?.setVideoSize(mDisplayWidth, mDisplayHeight) mMediaRecorder?.setVideoEncoder(MediaRecorder.VideoEncoder.H264) mMediaRecorder?.setVideoEncodingBitRate(5 * mDisplayWidth * mDisplayHeight) mMediaRecorder?.setVideoFrameRate(30) mMediaRecorder?.prepare() mMediaRecorder?.start() } catch (e: Exception) { Log.d("--INIT-RECORDER", e.message + "") println("Error startRecordScreen") println(e.message) } } private fun stopRecordScreen() { try { println("stopRecordScreen") mMediaRecorder?.stop() mMediaRecorder?.reset() println("stopRecordScreen success") } catch (e: Exception) { Log.d("--INIT-RECORDER", e.message + "") println("stopRecordScreen error") println(e.message) } finally { stopScreenSharing() } } private fun createVirtualDisplay(): VirtualDisplay? { try { return mMediaProjection?.createVirtualDisplay( "MainActivity", mDisplayWidth, mDisplayHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mMediaRecorder?.surface, null, null ) } catch (e: Exception) { println("createVirtualDisplay err") println(e.message) return null } } private fun stopScreenSharing() { if (mVirtualDisplay != null) { mVirtualDisplay?.release() if (mMediaProjection != null && mMediaProjectionCallback != null) { mMediaProjection?.unregisterCallback(mMediaProjectionCallback!!) mMediaProjection?.stop() mMediaProjection = null } Log.d("TAG", "MediaProjection Stopped") } } override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { pluginBinding = binding; } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {} override fun onAttachedToActivity(binding: ActivityPluginBinding) { activityBinding = binding; val channel = MethodChannel(pluginBinding!!.binaryMessenger, "flutter_screen_recording") channel.setMethodCallHandler(this) activityBinding!!.addActivityResultListener(this) } override fun onDetachedFromActivityForConfigChanges() {} override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { activityBinding = binding; } override fun onDetachedFromActivity() {} inner class MediaProjectionCallback : MediaProjection.Callback() { override fun onStop() { mMediaRecorder?.reset() mMediaProjection = null stopScreenSharing() } } } ================================================ FILE: flutter_screen_recording/android/src/main/kotlin/com/isvisoft/flutter_screen_recording/ForegroundService.kt ================================================ package com.isvisoft.flutter_screen_recording import android.Manifest import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.os.IBinder import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import android.app.Activity import android.os.Binder class ForegroundService : Service() { private val CHANNEL_ID = "ForegroundService Kotlin" private val REQUEST_CODE_MEDIA_PROJECTION = 1001 companion object { fun startService(context: Context, title: String, message: String) { println("-------------------------- startService"); try { val startIntent = Intent(context, ForegroundService::class.java) startIntent.putExtra("messageExtra", message) startIntent.putExtra("titleExtra", title) println("-------------------------- startService2"); ContextCompat.startForegroundService(context, startIntent) println("-------------------------- startService3"); } catch (err: Exception) { println("startService err"); println(err); } } fun stopService(context: Context) { val stopIntent = Intent(context, ForegroundService::class.java) .setAction(ACTION_STOP) context.startService(stopIntent) } const val ACTION_STOP = "com.foregroundservice.ACTION_STOP" } @Suppress("DEPRECATION") override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent?.action == ACTION_STOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE) } else { stopForeground(true) } stopSelf() return START_NOT_STICKY } else { try { println("-------------------------- onStartCommand") // Verificar permisos en Android 14 (SDK 34) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (ContextCompat.checkSelfPermission( this, Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION ) == PackageManager.PERMISSION_DENIED ) { println("MediaProjection permission not granted, requesting permission") // Solicitar el permiso si no ha sido concedido ActivityCompat.requestPermissions( this as Activity, arrayOf(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION), REQUEST_CODE_MEDIA_PROJECTION ) } else { // Si ya está concedido, continuar normalmente startForegroundServiceWithNotification(intent) } } else { // Si no es Android 14, continuar normalmente startForegroundServiceWithNotification(intent) } return START_STICKY } catch (err: Exception) { println("onStartCommand err") println(err) } return START_STICKY } } private fun startForegroundServiceWithNotification(intent: Intent?) { var title = intent?.getStringExtra("titleExtra") ?: "Flutter Screen Recording" var message = intent?.getStringExtra("messageExtra") ?: "" createNotificationChannel() val notificationIntent = Intent(this, FlutterScreenRecordingPlugin::class.java) val pendingIntent = PendingIntent.getActivity( this, 0, notificationIntent, PendingIntent.FLAG_MUTABLE ) val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(title) .setContentText(message) .setSmallIcon(android.R.drawable.presence_video_online) .setContentIntent(pendingIntent) .build() startForeground(1, notification) println("-------------------------- startForegroundServiceWithNotification") } override fun onBind(intent: Intent): IBinder { return Binder() } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceChannel = NotificationChannel( CHANNEL_ID, "Foreground Service Channel", NotificationManager.IMPORTANCE_DEFAULT ) val manager = getSystemService(NotificationManager::class.java) manager!!.createNotificationChannel(serviceChannel) } } } ================================================ FILE: flutter_screen_recording/ios/.gitignore ================================================ .idea/ .vagrant/ .sconsign.dblite .svn/ .DS_Store *.swp profile DerivedData/ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* /Flutter/Generated.xcconfig /Flutter/flutter_export_environment.sh ================================================ FILE: flutter_screen_recording/ios/Assets/.gitkeep ================================================ ================================================ FILE: flutter_screen_recording/ios/Classes/FlutterScreenRecordingPlugin.h ================================================ #import @interface FlutterScreenRecordingPlugin : NSObject @end ================================================ FILE: flutter_screen_recording/ios/Classes/FlutterScreenRecordingPlugin.m ================================================ #import "FlutterScreenRecordingPlugin.h" #import @implementation FlutterScreenRecordingPlugin + (void)registerWithRegistrar:(NSObject*)registrar { [SwiftFlutterScreenRecordingPlugin registerWithRegistrar:registrar]; } @end ================================================ FILE: flutter_screen_recording/ios/Classes/SwiftFlutterScreenRecordingPlugin.swift ================================================ import Flutter import UIKit import ReplayKit import AVFoundation public class SwiftFlutterScreenRecordingPlugin: NSObject, FlutterPlugin { let recorder = RPScreenRecorder.shared() var videoWriter: AVAssetWriter? var videoWriterInput: AVAssetWriterInput? var audioWriterInput: AVAssetWriterInput? var videoOutputURL: URL? var isRecording = false var firstTimestamp: CMTime? let screenSize = UIScreen.main.bounds public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "flutter_screen_recording", binaryMessenger: registrar.messenger()) let instance = SwiftFlutterScreenRecordingPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "startRecordScreen": guard let args = call.arguments as? [String: Any], let name = args["name"] as? String, let includeAudio = args["audio"] as? Bool else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing arguments", details: nil)) return } startRecording(videoName: name, recordAudio: includeAudio, result: result) case "stopRecordScreen": stopRecording(result: result) default: result(FlutterMethodNotImplemented) } } func startRecording(videoName: String, recordAudio: Bool, result: @escaping FlutterResult) { guard !isRecording else { result(FlutterError(code: "ALREADY_RECORDING", message: "Recording is already in progress", details: nil)) return } isRecording = true // Configurar la ruta del archivo de video let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] videoOutputURL = URL(fileURLWithPath: documentsPath).appendingPathComponent("\(videoName).mp4") // Eliminar el archivo si ya existe if FileManager.default.fileExists(atPath: videoOutputURL!.path) { try? FileManager.default.removeItem(at: videoOutputURL!) } if #available(iOS 11.0, *) { // Crear el AVAssetWriter do { videoWriter = try AVAssetWriter(outputURL: videoOutputURL!, fileType: .mp4) } catch { result(FlutterError(code: "FILE_ERROR", message: "Unable to create video file", details: error.localizedDescription)) return } // Configurar la entrada de video let videoSettings: [String: Any] = [ AVVideoCodecKey: AVVideoCodecType.h264, AVVideoWidthKey: screenSize.width, AVVideoHeightKey: screenSize.height ] videoWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings) videoWriterInput?.expectsMediaDataInRealTime = true videoWriter?.add(videoWriterInput!) // Configurar la entrada de audio si es necesario if recordAudio { let audioSettings: [String: Any] = [ AVFormatIDKey: kAudioFormatMPEG4AAC, AVSampleRateKey: 44100, AVNumberOfChannelsKey: 2 ] audioWriterInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings) audioWriterInput?.expectsMediaDataInRealTime = true videoWriter?.add(audioWriterInput!) } // Iniciar la captura con ReplayKit recorder.isMicrophoneEnabled = recordAudio recorder.startCapture(handler: { [weak self] sampleBuffer, sampleBufferType, error in guard let self = self, self.isRecording, error == nil else { return } switch sampleBufferType { case .video: self.handleVideoBuffer(sampleBuffer) case .audioMic: if recordAudio { self.handleAudioBuffer(sampleBuffer) } default: break } }) { error in if let error = error { result(FlutterError(code: "CAPTURE_ERROR", message: "Failed to start screen recording", details: error.localizedDescription)) } else { result(true) } } } else { result(FlutterError(code: "IOS_VERSION_ERROR", message: "This feature is only available on iOS 11 or later", details: nil)) } } func handleVideoBuffer(_ sampleBuffer: CMSampleBuffer) { // Añadir el video al archivo guard let writer = videoWriter, let input = videoWriterInput else { return } if writer.status == .unknown { firstTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) writer.startWriting() writer.startSession(atSourceTime: firstTimestamp!) } if writer.status == .writing && input.isReadyForMoreMediaData { input.append(sampleBuffer) } } func handleAudioBuffer(_ sampleBuffer: CMSampleBuffer) { // Añadir el audio al video guard let writer = videoWriter, let input = audioWriterInput else { return } if writer.status == .writing && input.isReadyForMoreMediaData { input.append(sampleBuffer) } } func stopRecording(result: @escaping FlutterResult) { // Detener la captura con ReplayKit guard isRecording else { result(FlutterError(code: "NOT_RECORDING", message: "No recording in progress", details: nil)) return } isRecording = false if #available(iOS 11.0, *) { recorder.stopCapture { [weak self] error in guard let self = self else { return } self.videoWriterInput?.markAsFinished() self.audioWriterInput?.markAsFinished() self.videoWriter?.finishWriting { if let error = error { result(FlutterError(code: "STOP_ERROR", message: "Failed to stop recording", details: error.localizedDescription)) } else { let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .alert) let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(defaultAction) result(self.videoOutputURL?.path) } } } } else { result(FlutterError(code: "IOS_VERSION_ERROR", message: "This feature is only available on iOS 11 or later", details: nil)) } } } ================================================ FILE: flutter_screen_recording/ios/flutter_screen_recording.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'flutter_screen_recording' s.version = '0.0.1' s.summary = 'A new Flutter plugin for record the screen.' s.description = <<-DESC A new Flutter plugin for record the screen. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' s.ios.deployment_target = '10.0' end ================================================ FILE: flutter_screen_recording/lib/flutter_screen_recording.dart ================================================ //import 'file:D:/Workspace/flutter_screen_recording/flutter_screen_recording_platform_interface/lib/flutter_screen_recording_platform_interface.dart'; import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_screen_recording_platform_interface/flutter_screen_recording_platform_interface.dart'; class FlutterScreenRecording { static Future startRecordScreen(String name, {String? titleNotification, String? messageNotification}) async { try { if (titleNotification == null) { titleNotification = ""; } if (messageNotification == null) { messageNotification = ""; } if (!kIsWeb) { _maybeStartFGS(titleNotification, messageNotification); } final bool start = await FlutterScreenRecordingPlatform.instance.startRecordScreen( name, notificationTitle: titleNotification, notificationMessage: messageNotification, ); return start; } catch (err) { print("startRecordScreen err"); print(err); } return false; } static Future startRecordScreenAndAudio(String name, {String? titleNotification, String? messageNotification}) async { try { if (titleNotification == null) { titleNotification = ""; } if (messageNotification == null) { messageNotification = ""; } if (!kIsWeb) { _maybeStartFGS(titleNotification, messageNotification); } final bool start = await FlutterScreenRecordingPlatform.instance.startRecordScreenAndAudio( name, notificationTitle: titleNotification, notificationMessage: messageNotification, ); return start; } catch (err) { print("startRecordScreenAndAudio err"); print(err); } return false; } static Future get stopRecordScreen async { try { final String path = await FlutterScreenRecordingPlatform.instance.stopRecordScreen; if (!kIsWeb && Platform.isAndroid) { FlutterForegroundTask.stopService(); } return path; } catch (err) { print("stopRecordScreen err"); print(err); } return ""; } static _maybeStartFGS(String titleNotification, String messageNotification) { try { if (!kIsWeb && Platform.isAndroid) { FlutterForegroundTask.init( androidNotificationOptions: AndroidNotificationOptions( channelId: 'notification_channel_id', channelName: titleNotification, channelDescription: messageNotification, channelImportance: NotificationChannelImportance.LOW, priority: NotificationPriority.LOW, // iconData: const NotificationIconData( // resType: ResourceType.mipmap, // resPrefix: ResourcePrefix.ic, // name: 'launcher', // ), ), iosNotificationOptions: const IOSNotificationOptions( showNotification: true, playSound: false, ), foregroundTaskOptions: ForegroundTaskOptions( eventAction: ForegroundTaskEventAction.repeat(5000), autoRunOnBoot: true, autoRunOnMyPackageReplaced: true, allowWakeLock: true, allowWifiLock: true, ), ); } } catch (err) { print("_maybeStartFGS err"); print(err); } } } ================================================ FILE: flutter_screen_recording/pubspec.yaml ================================================ name: flutter_screen_recording description: A new Flutter plugin for record the screen. This plugin can be used for record the screen on android, iOS, and web devices. version: 2.0.24 homepage: "https://github.com/Isvisoft/flutter_screen_recording" environment: sdk: ">=2.12.0 <4.0.0" flutter: ">=1.17.0" dependencies: flutter: sdk: flutter flutter_foreground_task: ^9.1.0 flutter_screen_recording_platform_interface: ^1.0.3 flutter_screen_recording_web: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter flutter: plugin: platforms: android: package: com.isvisoft.flutter_screen_recording pluginClass: FlutterScreenRecordingPlugin ios: pluginClass: FlutterScreenRecordingPlugin web: default_package: flutter_screen_recording_web ================================================ FILE: flutter_screen_recording_platform_interface/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # 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/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/local.properties # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: flutter_screen_recording_platform_interface/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: a2bde82fbd52e09057a4146f46889f4e10342d32 channel: beta project_type: package ================================================ FILE: flutter_screen_recording_platform_interface/CHANGELOG.md ================================================ ## 1.0.3 \*Bug fixed. ## 1.0.2 \*Implemented null-safety. ## 1.0.1 \*Update plugin_platform_interface ## 1.0.0 \*Initial release. ================================================ FILE: flutter_screen_recording_platform_interface/LICENSE ================================================ MIT License Copyright (c) 2019 Isvisoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: flutter_screen_recording_platform_interface/README.md ================================================ # flutter_screen_recording_platform_interface A common platform interface for the [`flutter_screen_recording`][1] plugin. This interface allows platform-specific implementations of the `flutter_screen_recording` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `flutter_screen_recording`, extend [`FlutterScreenRecordingPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `FlutterScreenRecordingPlatform` by calling `FlutterScreenRecordingPlatform.instance = MyPlatformFlutterScreenRecording()`. [1]: ../flutter_screen_recording [2]: lib/flutter_screen_recording_platform_interface.dart ================================================ FILE: flutter_screen_recording_platform_interface/lib/flutter_screen_recording_platform_interface.dart ================================================ library flutter_screen_recording_platform_interface; import 'dart:async'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'method_channel_flutter_screen_recording.dart'; abstract class FlutterScreenRecordingPlatform extends PlatformInterface { /// Constructs a UrlLauncherPlatform. FlutterScreenRecordingPlatform() : super(token: _token); static final Object _token = Object(); static FlutterScreenRecordingPlatform _instance = MethodChannelFlutterScreenRecording(); /// The default instance of [FlutterScreenRecordingPlatform] to use. /// /// Defaults to [MethodChannelUrlLauncher]. static FlutterScreenRecordingPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [UrlLauncherPlatform] when they register themselves. // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 static set instance(FlutterScreenRecordingPlatform instance) { PlatformInterface.verifyToken(instance, _token); _instance = instance; } Future startRecordScreen( String name, { String notificationTitle = "", String notificationMessage = "", }) { throw UnimplementedError(); } Future startRecordScreenAndAudio( String name, { String notificationTitle = "", String notificationMessage = "", }) { throw UnimplementedError(); } Future get stopRecordScreen { throw UnimplementedError(); } } ================================================ FILE: flutter_screen_recording_platform_interface/lib/method_channel_flutter_screen_recording.dart ================================================ import 'dart:async'; import 'package:flutter/services.dart'; import 'flutter_screen_recording_platform_interface.dart'; class MethodChannelFlutterScreenRecording extends FlutterScreenRecordingPlatform { static const MethodChannel _channel = const MethodChannel('flutter_screen_recording'); Future startRecordScreen( String name, { String notificationTitle = "", String notificationMessage = "", }) async { final bool start = await _channel.invokeMethod('startRecordScreen', { "name": name, "audio": false, "title": notificationTitle, "message": notificationMessage, }); return start; } Future startRecordScreenAndAudio( String name, { String notificationTitle = "", String notificationMessage = "", }) async { final bool start = await _channel.invokeMethod('startRecordScreen', { "name": name, "audio": true, "title": notificationTitle, "message": notificationMessage, }); return start; } Future get stopRecordScreen async { final String path = await _channel.invokeMethod('stopRecordScreen'); return path; } } ================================================ FILE: flutter_screen_recording_platform_interface/pubspec.yaml ================================================ name: flutter_screen_recording_platform_interface description: A common platform interface for the flutter_screen_recording plugin version: 1.0.3 homepage: "https://github.com/Isvisoft/flutter_screen_recording/tree/web/flutter_screen_recording_platform_interface" environment: sdk: ">=2.12.0 <4.0.0" flutter: ">=1.17.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.0.2 meta: ^1.5.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: flutter_screen_recording_web/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # 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/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/local.properties # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: flutter_screen_recording_web/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: a2bde82fbd52e09057a4146f46889f4e10342d32 channel: beta project_type: package ================================================ FILE: flutter_screen_recording_web/CHANGELOG.md ================================================ ## 1.0.8 \*Update dependencies ## 1.0.7 \*Bug fixed ## 1.0.6 \*Bug fixed ## 1.0.5 \*Update interface ## 1.0.4 \*Bug fixed ## 1.0.3 \*Null safety ## 1.0.2 \*Reorder codecs ## 1.0.1 \*Change dependencies ## 1.0.0 \*Initial release. ================================================ FILE: flutter_screen_recording_web/LICENSE ================================================ MIT License Copyright (c) 2019 Isvisoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: flutter_screen_recording_web/README.md ================================================ # flutter_screen_recording The web implementation of [`flutter_screen_recording`][1]. ## Usage This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), which means you can simply use `flutter_screen_recording` normally. This package will be automatically included in your app when you do. [1]: ../flutter_screen_recording ================================================ FILE: flutter_screen_recording_web/lib/flutter_screen_recording_web.dart ================================================ library flutter_screen_recording_web; import 'dart:async'; import 'dart:html'; import 'dart:js'; import 'interop/get_display_media.dart'; import 'package:flutter_screen_recording_platform_interface/flutter_screen_recording_platform_interface.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; class WebFlutterScreenRecording extends FlutterScreenRecordingPlatform { MediaStream? stream; String? name; MediaRecorder? mediaRecorder; Blob? recordedChunks; String? mimeType; static registerWith(Registrar registrar) { FlutterScreenRecordingPlatform.instance = WebFlutterScreenRecording(); } @override Future startRecordScreen( String name, { String notificationTitle = "", String notificationMessage = "", }) async { return _record(name, true, false); } @override Future startRecordScreenAndAudio( String name, { String notificationTitle = "", String notificationMessage = "", }) async { return _record(name, true, true); } Future _record(String name, bool recordVideo, bool recordAudio) async { try { var audioStream; final captureOptions = [ { "video": { "displaySurface": 'browser', }, "preferCurrentTab": true, "selfBrowserSurface": 'include', "surfaceSwitching": 'include', }, { "video": { "displaySurface": 'browser', }, "preferCurrentTab": true, "selfBrowserSurface": 'include', }, ]; for (var i = 0; i < captureOptions.length; i++) { try { stream = await navigator.getDisplayMedia(captureOptions[i]); break; } catch (e) { if (i == captureOptions.length - 1) { rethrow; } } } if (recordAudio) { audioStream = await navigator.getUserMedia({"audio": true}); if (audioStream.getAudioTracks().isNotEmpty) { stream!.addTrack(audioStream.getAudioTracks()[0]); } } this.name = name; if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) { print('video/webm;codecs=vp9'); mimeType = 'video/webm;codecs=vp9,opus'; } else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8.0')) { print('video/webm;codecs=vp8.0'); mimeType = 'video/webm;codecs=vp8.0,opus'; } else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8')) { print('video/webm;codecs=vp8'); mimeType = 'video/webm;codecs=vp8,opus'; } else if (MediaRecorder.isTypeSupported('video/mp4;codecs=h265')) { mimeType = 'video/mp4;codecs=h265,opus'; print("video/mp4;codecs=h265"); } else if (MediaRecorder.isTypeSupported('video/mp4;codecs=h264')) { print("video/mp4;codecs=h264"); mimeType = 'video/mp4;codecs=h264,opus'; } else if (MediaRecorder.isTypeSupported('video/webm;codecs=h265')) { print("video/webm;codecs=h265"); mimeType = 'video/webm;codecs=h265,opus'; } else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264')) { print("video/webm;codecs=h264"); mimeType = 'video/webm;codecs=h264,opus'; } else { mimeType = 'video/webm'; } this.mediaRecorder = new MediaRecorder(stream!, {'mimeType': mimeType}); this.mediaRecorder!.addEventListener('dataavailable', (Event event) { print("datavailable ${event.runtimeType}"); recordedChunks = JsObject.fromBrowserObject(event)['data']; this.mimeType = mimeType; print("blob size: ${recordedChunks?.size ?? 'empty'}"); }); this.stream!.getVideoTracks()[0].addEventListener('ended', (Event event) { //If user stop sharing screen, stop record stopRecordScreen; }); this.mediaRecorder!.start(); return true; } catch (e) { print("--->$e"); return false; } } @override Future get stopRecordScreen { final c = new Completer(); this.mediaRecorder!.addEventListener("stop", (event) { mediaRecorder = null; this.stream!.getTracks().forEach((element) => element.stop()); this.stream = null; final a = document.createElement("a") as AnchorElement; final url = Url.createObjectUrl(new Blob(List.from([recordedChunks]), mimeType)); document.body!.append(a); a.style.display = "none"; a.href = url; a.download = this.name; a.click(); Url.revokeObjectUrl(url); c.complete(this.name); }); mediaRecorder!.stop(); return c.future; } } ================================================ FILE: flutter_screen_recording_web/lib/interop/get_display_media.dart ================================================ import 'dart:async'; import 'dart:html'; import 'dart:js_util' as JSUtils; import 'dart:html' as HTML; class navigator { static Future getUserMedia(Map mediaConstraints) async { try { final nav = HTML.window.navigator; if (mediaConstraints['video'] is Map) { if (mediaConstraints['video']['facingMode'] != null) { mediaConstraints['video'].remove('facingMode'); } } final jsStream = await nav.getUserMedia(audio: mediaConstraints['audio'] ?? false, video: mediaConstraints['video'] ?? false); return MediaStream(jsStream); } catch (e) { throw 'Unable to getUserMedia: ${e.toString()}'; } } static Future getDisplayMedia(Map mediaConstraints) async { try { final mediaDevices = HTML.window.navigator.mediaDevices; final dynamic arg = JSUtils.jsify(mediaConstraints); final HTML.MediaStream jsStream = await JSUtils.promiseToFuture(JSUtils.callMethod(mediaDevices!, 'getDisplayMedia', [arg])); return MediaStream(jsStream); } catch (e) { throw 'Unable to getDisplayMedia: ${e.toString()}'; } } static Future> getSources() async { final devices = await HTML.window.navigator.mediaDevices!.enumerateDevices(); final result = []; for (final device in devices) { result.add({'deviceId': device.deviceId, 'groupId': device.groupId, 'kind': device.kind, 'label': device.label}); } return result; } } ================================================ FILE: flutter_screen_recording_web/pubspec.yaml ================================================ name: flutter_screen_recording_web description: Web platform implementation of flutter_screen_recording version: 1.0.8 homepage: "https://github.com/Isvisoft/flutter_screen_recording" environment: sdk: ">=2.12.0 <4.0.0" flutter: ">=1.20.0" dependencies: flutter: sdk: flutter flutter_screen_recording_platform_interface: ^1.0.3 flutter_web_plugins: sdk: flutter js: ^0.7.1 dev_dependencies: flutter_test: sdk: flutter flutter: plugin: platforms: web: pluginClass: WebFlutterScreenRecording fileName: flutter_screen_recording_web.dart ================================================ FILE: flutter_screen_recording_web/test/flutter_screen_recording_web_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_screen_recording_web/flutter_screen_recording_web.dart'; void main() {}