├── .github ├── semantic.yml └── workflows │ └── flutter-analyze.yml ├── linux ├── .gitignore ├── main.cc ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugin_registrant.cc │ └── generated_plugins.cmake └── my_application.h ├── fastlane └── metadata │ └── android │ └── en-US │ ├── title.txt │ ├── short_description.txt │ ├── images │ ├── icon.png │ ├── featuredGraphics.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ └── 6.png │ └── changelogs │ └── 1.txt ├── android ├── Gemfile ├── app │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-hdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ ├── android12splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ ├── android12splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ ├── android12splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-v21 │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ ├── android12splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ ├── splash.png │ │ │ │ │ ├── android12splash.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable-night │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable-night-v21 │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── drawable-night-hdpi │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-mdpi │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-xhdpi │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-xxhdpi │ │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-xxxhdpi │ │ │ │ │ └── android12splash.png │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ └── ic_launcher.xml │ │ │ │ ├── values-night │ │ │ │ │ └── styles.xml │ │ │ │ ├── values-v31 │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night-v31 │ │ │ │ │ └── styles.xml │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ ├── example │ │ │ │ └── obs_blade │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── kounex │ │ │ │ └── obs_blade │ │ │ │ └── MainActivity.kt │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── .classpath │ └── .project ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle ├── .settings │ └── org.eclipse.buildship.core.prefs ├── settings.gradle └── .project ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchBackground.imageset │ │ │ ├── background.png │ │ │ ├── darkbackground.png │ │ │ └── Contents.json │ └── AppDelegate.swift ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcworkspace │ ├── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist │ └── contents.xcworkspacedata ├── .gitignore └── Podfile ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── splash │ ├── img │ │ ├── dark-1x.png │ │ ├── dark-2x.png │ │ ├── dark-3x.png │ │ ├── dark-4x.png │ │ ├── light-1x.png │ │ ├── light-2x.png │ │ ├── light-3x.png │ │ └── light-4x.png │ ├── splash.js │ └── style.css └── manifest.json ├── .vscode └── settings.json ├── assets ├── fonts │ ├── JamIcons.ttf │ └── CustomFlutterIcons.ttf ├── icons │ └── app │ │ ├── splash.png │ │ ├── app_logo.png │ │ ├── app_logo_old.png │ │ ├── splash_old.png │ │ └── app_logo_adaptive.png └── images │ ├── base_logo.png │ ├── base_logo_dark.png │ ├── base_logo_old.png │ ├── flutter_logo_render.png │ ├── intro │ ├── intro_obs_websocket_page.png │ ├── intro_obs_websocket_download.png │ └── intro_obs_websocket_settings.png │ └── kounex_logo_ai_no_background.png ├── .gitmodules ├── macos ├── .gitignore ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_64.png │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_256.png │ │ │ └── app_icon_512.png │ ├── AppDelegate.swift │ ├── Release.entitlements │ ├── DebugProfile.entitlements │ ├── MainFlutterWindow.swift │ └── Info.plist ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Podfile ├── windows ├── runner │ ├── resources │ │ └── app_icon.ico │ ├── resource.h │ ├── CMakeLists.txt │ ├── utils.h │ ├── runner.exe.manifest │ ├── flutter_window.h │ └── main.cpp ├── .gitignore └── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ └── generated_plugin_registrant.cc ├── lib ├── types │ ├── enums │ │ ├── web_socket_codes │ │ │ └── base.dart │ │ ├── scene_item_icon.dart │ │ ├── order.dart │ │ └── hive_keys.dart │ ├── exceptions │ │ └── network.dart │ ├── classes │ │ ├── default_filter.dart │ │ ├── stream │ │ │ ├── responses │ │ │ │ ├── get_input_mute.dart │ │ │ │ ├── get_hotkey_list.dart │ │ │ │ ├── get_virtual_cam_status.dart │ │ │ │ ├── get_studio_mode_enabled.dart │ │ │ │ ├── get_record_directory.dart │ │ │ │ ├── get_replay_buffer_status.dart │ │ │ │ ├── get_input_default_settings.dart │ │ │ │ ├── get_input_list.dart │ │ │ │ ├── get_input_volume.dart │ │ │ │ ├── get_input_audio_sync_offset.dart │ │ │ │ ├── get_source_filter_default_settings.dart │ │ │ │ ├── get_profile_list.dart │ │ │ │ ├── get_source_filter_list.dart │ │ │ │ ├── get_scene_item_list.dart │ │ │ │ ├── get_scene_collection_list.dart │ │ │ │ ├── get_group_scene_item_list.dart │ │ │ │ ├── get_source_screenshot.dart │ │ │ │ ├── save_source_screenshot.dart │ │ │ │ ├── get_scene_list.dart │ │ │ │ ├── get_record_status.dart │ │ │ │ ├── get_special_inputs.dart │ │ │ │ ├── get_scene_transition_list.dart │ │ │ │ ├── get_current_scene_transition.dart │ │ │ │ ├── get_stream_status.dart │ │ │ │ └── get_version.dart │ │ │ ├── events │ │ │ │ ├── profile_list_changed.dart │ │ │ │ ├── switch_transition.dart │ │ │ │ ├── studio_mode_switched.dart │ │ │ │ ├── current_preview_scene_changed.dart │ │ │ │ ├── current_program_scene_changed.dart │ │ │ │ ├── current_profile_changed.dart │ │ │ │ ├── scene_collection_list_changed.dart │ │ │ │ ├── transition_duration_changed.dart │ │ │ │ ├── input_name_changed.dart │ │ │ │ ├── current_scene_transition_changed.dart │ │ │ │ ├── input_mute_state_changed.dart │ │ │ │ ├── stream_state_changed.dart │ │ │ │ ├── transition_list_changed.dart │ │ │ │ ├── virtual_cam_state_changed.dart │ │ │ │ ├── replay_buffer_state_changed.dart │ │ │ │ ├── input_audio_sync_offset_changed.dart │ │ │ │ ├── input_volume_changed.dart │ │ │ │ ├── current_scene_collection_changed.dart │ │ │ │ ├── source_filter_enable_state_changed.dart │ │ │ │ ├── record_state_changed.dart │ │ │ │ ├── scene_item_enable_state_changed.dart │ │ │ │ ├── scene_item_list_reindexed.dart │ │ │ │ ├── scene_item_removed.dart │ │ │ │ ├── scene_item_created.dart │ │ │ │ ├── current_scene_collection_changing.dart │ │ │ │ ├── transition_begin.dart │ │ │ │ ├── base.dart │ │ │ │ └── input_volume_meters.dart │ │ │ └── batch_responses │ │ │ │ ├── filter_list.dart │ │ │ │ ├── filter_default_settings.dart │ │ │ │ ├── screenshot.dart │ │ │ │ ├── stats.dart │ │ │ │ ├── inputs.dart │ │ │ │ └── base.dart │ │ ├── session.dart │ │ └── api │ │ │ ├── input_channel.dart │ │ │ ├── scene.dart │ │ │ ├── filter.dart │ │ │ ├── input.dart │ │ │ ├── scene.g.dart │ │ │ ├── input_channel.g.dart │ │ │ ├── filter.g.dart │ │ │ ├── transition.dart │ │ │ ├── scene_item_transform.dart │ │ │ ├── transition.g.dart │ │ │ ├── record_stats.dart │ │ │ ├── scene_item.dart │ │ │ └── input.g.dart │ ├── extensions │ │ ├── string.dart │ │ ├── list.dart │ │ └── color.dart │ └── interfaces │ │ ├── local_persistance.dart │ │ └── message.dart ├── models │ ├── hotkey.dart │ ├── enums │ │ ├── scene_item_type.dart │ │ ├── chat_type.dart │ │ ├── log_level.dart │ │ ├── scene_item_type.g.dart │ │ ├── dashboard_element.dart │ │ ├── log_level.g.dart │ │ └── chat_type.g.dart │ ├── purchased_tip.dart │ ├── type_ids.dart │ ├── app_log.dart │ ├── connection.dart │ ├── hotkey.g.dart │ ├── hidden_scene.dart │ └── hidden_scene.g.dart ├── utils │ ├── icons │ │ └── custom_flutter_icons.dart │ ├── general_helper.dart │ └── authentication_helper.dart ├── shared │ ├── general │ │ ├── base │ │ │ ├── divider.dart │ │ │ ├── constrained_box.dart │ │ │ ├── icon_button.dart │ │ │ └── adaptive_dialog │ │ │ │ └── adaptive_dialog_action.dart │ │ ├── themed │ │ │ ├── cupertino_scaffold.dart │ │ │ ├── rich_text.dart │ │ │ ├── cupertino_sliver_navigation_bar.dart │ │ │ └── cupertino_button.dart │ │ ├── hive_builder.dart │ │ ├── custom_sliver_list.dart │ │ ├── responsive_widget_wrapper.dart │ │ ├── keyboard_number_header.dart │ │ ├── formatted_text.dart │ │ ├── question_mark_tooltip.dart │ │ └── clean_list_tile.dart │ └── dialogs │ │ └── info.dart ├── views │ ├── dashboard │ │ └── widgets │ │ │ ├── dashboard_content │ │ │ ├── scene_content │ │ │ │ ├── placeholder_scene_item.dart │ │ │ │ └── media_inputs │ │ │ │ │ └── media_inputs.dart │ │ │ ├── exposed_controls │ │ │ │ └── hotkeys_control │ │ │ │ │ └── section_header.dart │ │ │ ├── scene_preview │ │ │ │ └── preview_warning_dialog.dart │ │ │ └── dashboard_content_streaming.dart │ │ │ └── obs_widgets │ │ │ └── obs_widgets.dart │ ├── statistics │ │ └── widgets │ │ │ ├── stats_entry_placeholder.dart │ │ │ ├── card_header │ │ │ └── sort_filter_panel │ │ │ │ ├── filter_status.dart │ │ │ │ ├── statistics_date_range.dart │ │ │ │ ├── stat_type_control.dart │ │ │ │ ├── exclude_unnamed_checkbox.dart │ │ │ │ └── filter_name.dart │ │ │ └── stats_entry │ │ │ └── entry_meta_chip.dart │ ├── settings │ │ ├── custom_theme │ │ │ └── widgets │ │ │ │ ├── color_picker │ │ │ │ ├── color_label.dart │ │ │ │ └── color_bubble.dart │ │ │ │ └── custom_theme_list │ │ │ │ └── theme_colors_row.dart │ │ ├── data_management │ │ │ └── widgets │ │ │ │ └── data_block.dart │ │ ├── logs │ │ │ └── logs.dart │ │ ├── faq │ │ │ └── widgets │ │ │ │ └── faq_block.dart │ │ └── dashboard_customisation │ │ │ └── order │ │ │ └── widgets │ │ │ ├── profiles_preview.dart │ │ │ ├── controls_preview.dart │ │ │ └── scene_buttons_preview.dart │ ├── home │ │ └── widgets │ │ │ ├── connect_box │ │ │ └── auto_discovery │ │ │ │ ├── result_entry.dart │ │ │ │ └── session_tile.dart │ │ │ └── saved_connections │ │ │ └── placeholder_connection.dart │ └── intro │ │ └── widgets │ │ └── back_so_selection_wrapper.dart └── stores │ ├── views │ ├── intro.dart │ └── logs.dart │ └── shared │ └── tabs.dart ├── flutterw ├── .gitignore └── test └── widget_test.dart /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | titleOnly: true -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | OBS Blade -------------------------------------------------------------------------------- /android/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Unofficial OBS controller! -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/favicon.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "disabled" 3 | } 4 | -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/fonts/JamIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/fonts/JamIcons.ttf -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule ".flutter"] 2 | path = .flutter 3 | url = https://github.com/flutter/flutter 4 | -------------------------------------------------------------------------------- /assets/icons/app/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/icons/app/splash.png -------------------------------------------------------------------------------- /assets/images/base_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/base_logo.png -------------------------------------------------------------------------------- /web/splash/img/dark-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/dark-1x.png -------------------------------------------------------------------------------- /web/splash/img/dark-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/dark-2x.png -------------------------------------------------------------------------------- /web/splash/img/dark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/dark-3x.png -------------------------------------------------------------------------------- /web/splash/img/dark-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/dark-4x.png -------------------------------------------------------------------------------- /web/splash/img/light-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/light-1x.png -------------------------------------------------------------------------------- /web/splash/img/light-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/light-2x.png -------------------------------------------------------------------------------- /web/splash/img/light-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/light-3x.png -------------------------------------------------------------------------------- /web/splash/img/light-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/splash/img/light-4x.png -------------------------------------------------------------------------------- /assets/icons/app/app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/icons/app/app_logo.png -------------------------------------------------------------------------------- /android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /assets/icons/app/app_logo_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/icons/app/app_logo_old.png -------------------------------------------------------------------------------- /assets/icons/app/splash_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/icons/app/splash_old.png -------------------------------------------------------------------------------- /assets/images/base_logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/base_logo_dark.png -------------------------------------------------------------------------------- /assets/images/base_logo_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/base_logo_old.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /assets/fonts/CustomFlutterIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/fonts/CustomFlutterIcons.ttf -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /assets/icons/app/app_logo_adaptive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/icons/app/app_logo_adaptive.png -------------------------------------------------------------------------------- /assets/images/flutter_logo_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/flutter_logo_render.png -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /lib/types/enums/web_socket_codes/base.dart: -------------------------------------------------------------------------------- 1 | abstract class BaseWebSocketCode { 2 | int get identifier; 3 | String get message; 4 | } 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /assets/images/intro/intro_obs_websocket_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/intro/intro_obs_websocket_page.png -------------------------------------------------------------------------------- /assets/images/kounex_logo_ai_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/kounex_logo_ai_no_background.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /lib/types/exceptions/network.dart: -------------------------------------------------------------------------------- 1 | class NotInWLANException implements Exception {} 2 | 3 | class NoNetworkException implements Exception {} 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/images/intro/intro_obs_websocket_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/intro/intro_obs_websocket_download.png -------------------------------------------------------------------------------- /assets/images/intro/intro_obs_websocket_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/assets/images/intro/intro_obs_websocket_settings.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /lib/types/enums/scene_item_icon.dart: -------------------------------------------------------------------------------- 1 | enum SceneItemIcon { AudioInput, AudioOutput, DisplayCapture } 2 | 3 | extension SceneItemIconFunctions on SceneItemIcon {} 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featuredGraphics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/featuredGraphics.png -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #141C24 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /flutterw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | export PATH="$DIR/vendor/flutter/bin:$PATH" 7 | exec flutter $@ -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kounex/obs_blade/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /lib/types/classes/default_filter.dart: -------------------------------------------------------------------------------- 1 | class DefaultFilter { 2 | String filterKind; 3 | Map filterSettings; 4 | 5 | DefaultFilter(this.filterKind, this.filterSettings); 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/obs_blade/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.kounex.obsBlade 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/kounex/obs_blade/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.kounex.obs_blade 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /web/splash/splash.js: -------------------------------------------------------------------------------- 1 | function removeSplashFromWeb() { 2 | document.getElementById("splash")?.remove(); 3 | document.getElementById("splash-branding")?.remove(); 4 | document.body.style.background = "transparent"; 5 | } 6 | -------------------------------------------------------------------------------- /lib/types/enums/order.dart: -------------------------------------------------------------------------------- 1 | enum Order { 2 | Ascending, 3 | Descending, 4 | } 5 | 6 | extension FilterOrderFunctions on Order { 7 | String get text => const { 8 | Order.Ascending: 'Asc.', 9 | Order.Descending: 'Desc.', 10 | }[this]!; 11 | } 12 | -------------------------------------------------------------------------------- /lib/models/hotkey.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | import 'type_ids.dart'; 4 | 5 | part 'hotkey.g.dart'; 6 | 7 | @HiveType(typeId: TypeIDs.Hotkey) 8 | class Hotkey extends HiveObject { 9 | @HiveField(0) 10 | String name; 11 | 12 | Hotkey(this.name); 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 7 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 4 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_input_mute.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the audio mute state of an input. 4 | class GetInputMuteResponse extends BaseResponse { 5 | GetInputMuteResponse(super.json); 6 | 7 | /// Whether the input is muted 8 | bool get inputMuted => json['inputMuted']; 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/types/classes/session.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:web_socket_channel/io.dart'; 4 | 5 | import '../../models/connection.dart'; 6 | 7 | class Session { 8 | IOWebSocketChannel socket; 9 | Stream? socketStream; 10 | Connection connection; 11 | 12 | Session(this.socket, this.connection); 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/profile_list_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The profile list has changed. 4 | class ProfileListChangedEvent extends BaseEvent { 5 | ProfileListChangedEvent(super.json); 6 | 7 | /// Updated list of profiles 8 | List get profiles => List.from(this.json['profiles']); 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_hotkey_list.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets an array of all hotkey names in OBS 4 | class GetHotkeyListResponse extends BaseResponse { 5 | GetHotkeyListResponse(super.json); 6 | 7 | /// Array of hotkey names 8 | List get hotkeys => List.from(this.json['hotkeys']); 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/switch_transition.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The active transition has been changed 4 | class SwitchTransitionEvent extends BaseEvent { 5 | SwitchTransitionEvent(super.json); 6 | 7 | /// The name of the new active transition 8 | String get transitionName => this.json['transition-name']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_virtual_cam_status.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the status of the virtualcam output. 4 | class GetVirtualCamStatusResponse extends BaseResponse { 5 | GetVirtualCamStatusResponse(super.json); 6 | 7 | /// Whether the output is active 8 | bool get outputActive => this.json['outputActive']; 9 | } 10 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1.txt: -------------------------------------------------------------------------------- 1 | Hello again! 2023 starting off with new features requested by the community: 2 | 3 | - Volume meters 4 | - Fullscreen preview feature 5 | - Fixed twitch chat 6 | - Performance improvements 7 | 8 | As always: feel free to drop some feature or bug requests via the GitHub page of OBS Blade or contact me via email! :) 9 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_studio_mode_enabled.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets whether studio is enabled. 4 | class GetStudioModeEnabledResponse extends BaseResponse { 5 | GetStudioModeEnabledResponse(super.json); 6 | 7 | /// Whether studio mode is enabled 8 | bool get studioModeEnabled => this.json['studioModeEnabled']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/studio_mode_switched.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Studio mode has been enabled or disabled. 4 | class StudioModeStateChangedEvent extends BaseEvent { 5 | StudioModeStateChangedEvent(super.json); 6 | 7 | /// True == Enabled, False == Disabled 8 | bool get studioModeEnabled => this.json['studioModeEnabled']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_record_directory.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the current directory that the record output is set to. 4 | class GetRecordDirectoryResponse extends BaseResponse { 5 | GetRecordDirectoryResponse(super.json); 6 | 7 | /// Output directory 8 | String get recordDirectory => this.json['recordDirectory']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/current_preview_scene_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The current preview scene has changed. 4 | class CurrentPreviewSceneChangedEvent extends BaseEvent { 5 | CurrentPreviewSceneChangedEvent(super.json); 6 | 7 | /// Name of the scene that was switched to 8 | String get sceneName => this.json['sceneName']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/current_program_scene_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The current program scene has changed. 4 | class CurrentProgramSceneChangedEvent extends BaseEvent { 5 | CurrentProgramSceneChangedEvent(super.json); 6 | 7 | /// Name of the scene that was switched to 8 | String get sceneName => this.json['sceneName']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_replay_buffer_status.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Get the status of the OBS replay buffer 4 | class GetReplayBufferStatusResponse extends BaseResponse { 5 | GetReplayBufferStatusResponse(super.json); 6 | 7 | /// Current recording status 8 | bool? get isReplayBufferActive => this.json['isReplayBufferActive']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/current_profile_changed.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/events/base.dart'; 2 | 3 | /// The current profile has changed. 4 | class CurrentProfileChangedEvent extends BaseEvent { 5 | CurrentProfileChangedEvent(super.json); 6 | 7 | /// Name of the new profile 8 | String get profileName => this.json['profileName']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/extensions/string.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | extension StringStuff on String { 4 | Color hexToColor() { 5 | final buffer = StringBuffer(); 6 | if (this.length == 6 || this.length == 7) buffer.write('ff'); 7 | buffer.write(this.replaceFirst('#', '')); 8 | return Color(int.parse(buffer.toString(), radix: 16)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /lib/utils/icons/custom_flutter_icons.dart: -------------------------------------------------------------------------------- 1 | /// Custom icons made on fluttericon.com 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class CustomFlutterIcons { 5 | static const _kFontFam = 'CustomFlutterIcons'; 6 | static const String? _kFontPkg = null; 7 | 8 | static const IconData owncast_logo = 9 | IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); 10 | } 11 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | project.evaluationDependsOn(':app') 12 | } 13 | 14 | tasks.register("clean", Delete) { 15 | delete rootProject.buildDir 16 | } 17 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/scene_collection_list_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The scene collection list has changed. 4 | class SceneCollectionListChangedEvent extends BaseEvent { 5 | SceneCollectionListChangedEvent(super.json); 6 | 7 | /// Updated list of scene collections 8 | List get sceneCollections => List.from(this.json['sceneCollections']); 9 | } 10 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_input_default_settings.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the default settings for an input kind. 4 | class GetInputDefaultSettingsResponse extends BaseResponse { 5 | GetInputDefaultSettingsResponse(super.json); 6 | 7 | /// Object of default settings for the input kind 8 | dynamic get defaultInputSettings => this.json['defaultInputSettings']; 9 | } 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/transition_duration_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The current scene transition duration has changed. 4 | class CurrentSceneTransitionDurationChangedEvent extends BaseEvent { 5 | CurrentSceneTransitionDurationChangedEvent(super.json); 6 | 7 | /// Transition duration in milliseconds 8 | int get transitionDuration => this.json['transitionDuration']; 9 | } 10 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/input_name_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The name of an input has changed. 4 | class InputNameChangedEvent extends BaseEvent { 5 | InputNameChangedEvent(super.json); 6 | 7 | /// Old name of the input 8 | String get oldInputName => this.json['oldInputName']; 9 | 10 | /// New name of the input 11 | String get inputName => this.json['inputName']; 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/current_scene_transition_changed.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/events/base.dart'; 2 | 3 | /// The current scene transition has changed. 4 | class CurrentSceneTransitionChangedEvent extends BaseEvent { 5 | CurrentSceneTransitionChangedEvent(super.json); 6 | 7 | /// Name of the new transition 8 | String get transitionName => this.json['transitionName']; 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/input_mute_state_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// An input's mute state has changed. 4 | class InputMuteStateChangedEvent extends BaseEvent { 5 | InputMuteStateChangedEvent(super.json); 6 | 7 | /// Name of the input 8 | String get inputName => this.json['inputName']; 9 | 10 | /// Whether the input is muted 11 | bool get inputMuted => this.json['inputMuted']; 12 | } 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_input_list.dart: -------------------------------------------------------------------------------- 1 | import '../../api/input.dart'; 2 | import 'base.dart'; 3 | 4 | /// Gets an array of all inputs in OBS. 5 | class GetInputListResponse extends BaseResponse { 6 | GetInputListResponse(super.json); 7 | 8 | /// Array of inputs 9 | List get inputs => (this.json['inputs'] as List) 10 | .map((input) => Input.fromJson(input)) 11 | .toList(); 12 | } 13 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_input_volume.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the current volume setting of an input. 4 | class GetInputVolumeResponse extends BaseResponse { 5 | GetInputVolumeResponse(super.json); 6 | 7 | /// Volume setting in mul 8 | double get inputVolumeMul => this.json['inputVolumeMul']; 9 | 10 | /// Volume setting in dB 11 | double get inputVolumeDb => this.json['inputVolumeDb']; 12 | } 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/stream_state_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The state of the stream output has changed. 4 | class StreamStateChangedEvent extends BaseEvent { 5 | StreamStateChangedEvent(super.json); 6 | 7 | /// Whether the output is active 8 | bool get outputActive => this.json['outputActive']; 9 | 10 | /// The specific state of the output 11 | String get outputState => this.json['outputState']; 12 | } 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_input_audio_sync_offset.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the audio sync offset of an input. 4 | /// 5 | /// Note: The audio sync offset can be negative too! 6 | class GetInputAudioSyncOffsetResponse extends BaseResponse { 7 | GetInputAudioSyncOffsetResponse(super.json); 8 | 9 | /// Audio sync offset in milliseconds 10 | int get inputAudioSyncOffset => this.json['inputAudioSyncOffset']; 11 | } 12 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_source_filter_default_settings.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the default settings for a filter kind. 4 | class GetSourceFilterDefaultSettingsResponse extends BaseResponse { 5 | GetSourceFilterDefaultSettingsResponse(super.json); 6 | 7 | /// Object of default settings for the filter kind 8 | Map get defaultFilterSettings => 9 | this.json['defaultFilterSettings']; 10 | } 11 | -------------------------------------------------------------------------------- /lib/shared/general/base/divider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BaseDivider extends StatelessWidget { 4 | final double? height; 5 | 6 | const BaseDivider({super.key, this.height}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Divider( 11 | color: Theme.of(context).dividerColor.withOpacity(0.4), 12 | height: this.height ?? 1.0, 13 | thickness: 0.0, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/transition_list_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The list of available transitions has been modified. Transitions have been added, removed, or renamed 4 | class TransitionListChangedEvent extends BaseEvent { 5 | TransitionListChangedEvent(super.json); 6 | 7 | /// Transitions list 8 | List get transitions => List.from( 9 | this.json['transitions'].map((transition) => transition['name'])); 10 | } 11 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_profile_list.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets an array of all profiles 4 | class GetProfileListResponse extends BaseResponse { 5 | GetProfileListResponse(super.json); 6 | 7 | /// The name of the current profile 8 | String get currentProfileName => this.json['currentProfileName']; 9 | 10 | /// Array of all available profiles 11 | List get profiles => List.from(this.json['profiles']); 12 | } 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/virtual_cam_state_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The state of the virtualcam output has changed. 4 | class VirtualCamStateChangedEvent extends BaseEvent { 5 | VirtualCamStateChangedEvent(super.json); 6 | 7 | /// Whether the output is active 8 | bool get outputActive => this.json['outputActive']; 9 | 10 | /// The specific state of the output 11 | String get outputState => this.json['outputState']; 12 | } 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/replay_buffer_state_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The state of the replay buffer output has changed. 4 | class ReplayBufferStateChangedEvent extends BaseEvent { 5 | ReplayBufferStateChangedEvent(super.json); 6 | 7 | /// Whether the output is active 8 | bool get outputActive => this.json['outputActive']; 9 | 10 | /// The specific state of the output 11 | String get outputState => this.json['outputState']; 12 | } 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_source_filter_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/api/filter.dart'; 2 | 3 | import 'base.dart'; 4 | 5 | /// Gets an array of all of a source's filters. 6 | class GetSourceFilterListResponse extends BaseResponse { 7 | GetSourceFilterListResponse(super.json); 8 | 9 | /// Array of filters 10 | List get filters => 11 | List.from(this.json['filters'].map((filter) => Filter.fromJson(filter))); 12 | } 13 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /lib/utils/general_helper.dart: -------------------------------------------------------------------------------- 1 | import '../models/enums/log_level.dart'; 2 | 3 | class GeneralHelper { 4 | static void advLog( 5 | Object? obj, { 6 | LogLevel level = LogLevel.Info, 7 | bool includeInLogs = false, 8 | }) { 9 | String inLog = includeInLogs ? '[ON]' : '[OFF]'; 10 | // ignore: avoid_print 11 | print(obj == null 12 | ? '${LogLevel.Warning.prefix}$inLog ${obj.runtimeType} is null!' 13 | : '${level.prefix}$inLog $obj'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/types/classes/stream/batch_responses/filter_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/batch_responses/base.dart'; 2 | import 'package:obs_blade/types/classes/stream/responses/get_source_filter_list.dart'; 3 | 4 | class FilterListBatchResponse extends BaseBatchResponse { 5 | FilterListBatchResponse(super.json); 6 | 7 | Iterable get filterLists => this 8 | .responses 9 | .map((response) => GetSourceFilterListResponse(response.jsonRAW)); 10 | } 11 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/input_audio_sync_offset_changed.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/events/base.dart'; 2 | 3 | /// The sync offset of an input has changed. 4 | class InputAudioSyncOffsetChangedEvent extends BaseEvent { 5 | InputAudioSyncOffsetChangedEvent(super.json); 6 | 7 | /// Name of the input 8 | String get inputName => this.json['inputName']; 9 | 10 | /// New sync offset in milliseconds 11 | int get inputAudioSyncOffset => this.json['inputAudioSyncOffset']; 12 | } 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /lib/types/classes/api/input_channel.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'input_channel.freezed.dart'; 4 | part 'input_channel.g.dart'; 5 | 6 | @freezed 7 | class InputChannel with _$InputChannel { 8 | const factory InputChannel({ 9 | required double? current, 10 | required double? average, 11 | required double? potential, 12 | }) = _InputChannel; 13 | 14 | factory InputChannel.fromJson(Map json) => 15 | _$InputChannelFromJson(json); 16 | } 17 | -------------------------------------------------------------------------------- /lib/types/classes/api/scene.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'scene.freezed.dart'; 4 | part 'scene.g.dart'; 5 | 6 | @freezed 7 | class Scene with _$Scene { 8 | const factory Scene({ 9 | /// Name of the currently active scene 10 | required String sceneName, 11 | 12 | /// Ordered list of the current scene's source items 13 | required int sceneIndex, 14 | }) = _Scene; 15 | 16 | factory Scene.fromJson(Map json) => _$SceneFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/input_volume_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// An input's volume level has changed. 4 | class InputVolumeChangedEvent extends BaseEvent { 5 | InputVolumeChangedEvent(super.json); 6 | 7 | /// Name of the input 8 | String get inputName => this.json['inputName']; 9 | 10 | /// New volume level in multimap 11 | double get inputVolumeMul => this.json['inputVolumeMul']; 12 | 13 | /// New volume level in dB 14 | double get inputVolumeDb => this.json['inputVolumeDb']; 15 | } 16 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_scene_item_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/api/scene_item.dart'; 2 | 3 | import 'base.dart'; 4 | 5 | /// Gets a list of all scene items in a scene. 6 | class GetSceneItemListResponse extends BaseResponse { 7 | GetSceneItemListResponse(super.json); 8 | 9 | /// Array of scene items in the scene 10 | Iterable get sceneItems => 11 | (this.json['sceneItems'] as List) 12 | .map((sceneItem) => SceneItem.fromJson(sceneItem)); 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/enums/scene_item_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import '../type_ids.dart'; 3 | 4 | part 'scene_item_type.g.dart'; 5 | 6 | @HiveType(typeId: TypeIDs.SceneItemType) 7 | enum SceneItemType { 8 | /// Source related to items in the "Sources" list of OBS. 9 | /// Basically the "actual" scene items 10 | @HiveField(0) 11 | Source, 12 | 13 | /// Items which result from entries in the "Sources" list but 14 | /// have a designated entry in the "Audio Mixer" list of OBS 15 | @HiveField(1) 16 | Audio, 17 | } 18 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/types/classes/api/filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'filter.freezed.dart'; 4 | part 'filter.g.dart'; 5 | 6 | @freezed 7 | class Filter with _$Filter { 8 | const factory Filter({ 9 | required bool filterEnabled, 10 | required int filterIndex, 11 | required String filterKind, 12 | required String filterName, 13 | required Map filterSettings, 14 | }) = _Filter; 15 | 16 | factory Filter.fromJson(Map json) => _$FilterFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_scene_collection_list.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets an array of all scene collections 4 | class GetSceneCollectionListResponse extends BaseResponse { 5 | GetSceneCollectionListResponse(super.json); 6 | 7 | /// The name of the current scene collection 8 | String get currentSceneCollectionName => 9 | this.json['currentSceneCollectionName']; 10 | 11 | /// Array of all available scene collections 12 | List get sceneCollections => List.from(this.json['sceneCollections']); 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/flutter-analyze.yml: -------------------------------------------------------------------------------- 1 | name: flutter analyze 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | analyze: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | - name: Set up Flutter 19 | uses: subosito/flutter-action@v1 20 | - run: flutter pub get 21 | - name: Run flutter analyze 22 | uses: invertase/github-action-dart-analyzer@v1 -------------------------------------------------------------------------------- /lib/views/dashboard/widgets/dashboard_content/scene_content/placeholder_scene_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PlaceholderSceneItem extends StatelessWidget { 4 | final String text; 5 | 6 | const PlaceholderSceneItem({ 7 | super.key, 8 | required this.text, 9 | }); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Padding( 14 | padding: const EdgeInsets.only(top: 12.0), 15 | child: Center( 16 | child: Text(this.text), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/types/interfaces/local_persistance.dart: -------------------------------------------------------------------------------- 1 | /// Interface which serves as an abstraction layer for persistance tasks. 2 | /// Every class which implements this has to provide a [load] and [save] function 3 | /// which is what we actually need and do (for now) for this task. The actual 4 | /// implementation can then be handeled individually (right now hive) 5 | abstract class Persistance { 6 | /// Persist [item], should return a bool to indicate if the task was successful 7 | bool save(T item); 8 | 9 | /// Load items as List 10 | List load(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/models/purchased_tip.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'type_ids.dart'; 3 | 4 | part 'purchased_tip.g.dart'; 5 | 6 | @HiveType(typeId: TypeIDs.PurchasedTip) 7 | class PurchasedTip extends HiveObject { 8 | @HiveField(0) 9 | int timestampMS; 10 | 11 | @HiveField(1) 12 | String id; 13 | 14 | @HiveField(2) 15 | String name; 16 | 17 | @HiveField(3) 18 | String price; 19 | 20 | @HiveField(4) 21 | String currencySymbol; 22 | 23 | PurchasedTip( 24 | this.timestampMS, this.id, this.name, this.price, this.currencySymbol); 25 | } 26 | -------------------------------------------------------------------------------- /lib/models/type_ids.dart: -------------------------------------------------------------------------------- 1 | class TypeIDs { 2 | static const int Connection = 0; 3 | static const int PastStreamData = 1; 4 | static const int CustomTheme = 2; 5 | static const int HiddenSceneItem = 3; 6 | static const int ChatType = 4; 7 | static const int SceneItemType = 5; 8 | static const int HiddenScene = 6; 9 | static const int AppLog = 7; 10 | static const int LogLevel = 8; 11 | static const int PurchasedTip = 9; 12 | static const int PastRecordData = 10; 13 | static const int Hotkey = 11; 14 | static const int DashboardElement = 12; 15 | } 16 | -------------------------------------------------------------------------------- /lib/types/classes/stream/batch_responses/filter_default_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/batch_responses/base.dart'; 2 | import 'package:obs_blade/types/classes/stream/responses/get_source_filter_default_settings.dart'; 3 | 4 | class FilterDefaultSettingsResponse extends BaseBatchResponse { 5 | FilterDefaultSettingsResponse(super.json); 6 | 7 | Iterable get defaultSettings => 8 | this.responses.map((response) => 9 | GetSourceFilterDefaultSettingsResponse(response.jsonRAW)); 10 | } 11 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/current_scene_collection_changed.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/events/base.dart'; 2 | 3 | /// The current scene collection has changed. 4 | /// 5 | /// Note: If polling has been paused during CurrentSceneCollectionChanging, 6 | /// this is the que to restart polling. 7 | class CurrentSceneCollectionChangedEvent extends BaseEvent { 8 | CurrentSceneCollectionChangedEvent(super.json); 9 | 10 | /// Name of the current scene collection 11 | String get sceneCollectionName => this.json['sceneCollectionName']; 12 | } 13 | -------------------------------------------------------------------------------- /lib/views/statistics/widgets/stats_entry_placeholder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StatsEntryPlaceholder extends StatelessWidget { 4 | final String text; 5 | 6 | const StatsEntryPlaceholder({super.key, required this.text}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Center( 11 | child: Padding( 12 | padding: const EdgeInsets.all(24.0), 13 | child: Text( 14 | this.text, 15 | textAlign: TextAlign.center, 16 | ), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/source_filter_enable_state_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// A source filter's enable state has changed. 4 | class SourceFilterEnableStateChangedEvent extends BaseEvent { 5 | SourceFilterEnableStateChangedEvent(super.json); 6 | 7 | /// Name of the source the filter is on 8 | String get sourceName => this.json['sourceName']; 9 | 10 | /// Name of the filter 11 | String get filterName => this.json['filterName']; 12 | 13 | /// Whether the filter is enabled 14 | bool get filterEnabled => this.json['filterEnabled']; 15 | } 16 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/record_state_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// The state of the record output has changed. 4 | class RecordStateChangedEvent extends BaseEvent { 5 | RecordStateChangedEvent(super.json); 6 | 7 | /// Whether the output is active 8 | bool get outputActive => this.json['outputActive']; 9 | 10 | /// The specific state of the output 11 | String get outputState => this.json['outputState']; 12 | 13 | /// File name for the saved recording, if record stopped. null otherwise 14 | String? get outputPath => this.json['outputPath']; 15 | } 16 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/scene_item_enable_state_changed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// A scene item's enable state has changed. 4 | class SceneItemEnableStateChangedEvent extends BaseEvent { 5 | SceneItemEnableStateChangedEvent(super.json); 6 | 7 | /// Name of the scene the item is in 8 | String get sceneName => this.json['sceneName']; 9 | 10 | /// Numeric ID of the scene item 11 | int get sceneItemId => this.json['sceneItemId']; 12 | 13 | /// Whether the scene item is enabled (visible) 14 | bool get sceneItemEnabled => this.json['sceneItemEnabled']; 15 | } 16 | -------------------------------------------------------------------------------- /lib/models/app_log.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'type_ids.dart'; 3 | 4 | import 'enums/log_level.dart'; 5 | 6 | part 'app_log.g.dart'; 7 | 8 | @HiveType(typeId: TypeIDs.AppLog) 9 | class AppLog extends HiveObject { 10 | @HiveField(0) 11 | int timestampMS; 12 | 13 | @HiveField(1) 14 | LogLevel level; 15 | 16 | @HiveField(2) 17 | String entry; 18 | 19 | @HiveField(3) 20 | String? stackTrace; 21 | 22 | @HiveField(4) 23 | bool manually; 24 | 25 | AppLog(this.timestampMS, this.level, this.entry, 26 | [this.stackTrace, this.manually = false]); 27 | } 28 | -------------------------------------------------------------------------------- /lib/types/classes/stream/batch_responses/screenshot.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/batch_responses/base.dart'; 2 | import 'package:obs_blade/types/classes/stream/responses/get_source_screenshot.dart'; 3 | import 'package:obs_blade/types/enums/request_type.dart'; 4 | 5 | class ScreenshotBatchResponse extends BaseBatchResponse { 6 | ScreenshotBatchResponse(super.json); 7 | 8 | GetSourceScreenshotResponse get getSourceScreenshotResponse => this.response( 9 | RequestType.GetSourceScreenshot, 10 | (jsonRAW) => GetSourceScreenshotResponse(jsonRAW), 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/scene_item_list_reindexed.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/api/scene_item.dart'; 2 | 3 | import 'base.dart'; 4 | 5 | /// A scene's item list has been reindexed. 6 | class SceneItemListReindexedEvent extends BaseEvent { 7 | SceneItemListReindexedEvent(super.json); 8 | 9 | /// Name of the scene 10 | String get sceneName => this.json['sceneName']; 11 | 12 | /// Array of scene item objects 13 | List get sceneItems => (this.json['sceneItems'] as List) 14 | .map((sceneItem) => SceneItem.fromJson(sceneItem)) 15 | .toList(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/scene_item_removed.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// A scene item has been removed. 4 | /// 5 | /// This event is not emitted when the scene the item is in is removed. 6 | class SceneItemRemovedEvent extends BaseEvent { 7 | SceneItemRemovedEvent(super.json); 8 | 9 | /// Name of the scene the item was removed from 10 | String get sceneName => this.json['sceneName']; 11 | 12 | /// Name of the underlying source (input/scene) 13 | String get sourceName => this.json['sourceName']; 14 | 15 | /// Numeric ID of the scene item 16 | int get sceneItemId => this.json['sceneItemId']; 17 | } 18 | -------------------------------------------------------------------------------- /lib/types/extensions/list.dart: -------------------------------------------------------------------------------- 1 | /// utils should be used for generic stuff which can be useful from everywhere in the 2 | /// app. This example is a extension method for the List type so every List object can 3 | /// use this new method. Another examples would be static functions doing stuff you would 4 | /// do on several places, so summarize them in a util class! 5 | extension ListStuff on Iterable { 6 | Iterable mapIndexed(T Function(E element, int index) f) sync* { 7 | var index = 0; 8 | 9 | for (final item in this) { 10 | yield f(item, index); 11 | index = index + 1; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/scene_item_created.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// A scene item has been created. 4 | class SceneItemAddedEvent extends BaseEvent { 5 | SceneItemAddedEvent(super.json); 6 | 7 | /// Name of the scene the item was added to 8 | String get sceneName => this.json['sceneName']; 9 | 10 | /// Name of the underlying source (input/scene) 11 | String get sourceName => this.json['sourceName']; 12 | 13 | /// Numeric ID of the scene item 14 | int get sceneItemId => this.json['sceneItemId']; 15 | 16 | /// Index position of the item 17 | int get sceneItemIndex => this.json['sceneItemIndex']; 18 | } 19 | -------------------------------------------------------------------------------- /lib/views/settings/custom_theme/widgets/color_picker/color_label.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColorLabel extends StatelessWidget { 4 | final String label; 5 | final double width; 6 | 7 | const ColorLabel({ 8 | super.key, 9 | required this.label, 10 | this.width = 24.0, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SizedBox( 16 | width: this.width, 17 | child: Text( 18 | this.label, 19 | textAlign: TextAlign.center, 20 | style: Theme.of(context).textTheme.titleMedium, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/models/connection.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | import 'type_ids.dart'; 4 | 5 | part 'connection.g.dart'; 6 | 7 | @HiveType(typeId: TypeIDs.Connection) 8 | class Connection extends HiveObject { 9 | @HiveField(0) 10 | String? name; 11 | 12 | @HiveField(1) 13 | String host; 14 | 15 | @HiveField(2) 16 | String? ssid; 17 | 18 | @HiveField(3) 19 | int? port; 20 | 21 | @HiveField(4) 22 | String? pw; 23 | 24 | @HiveField(5) 25 | bool? isDomain; 26 | 27 | String? challenge; 28 | String? salt; 29 | bool? reachable; 30 | 31 | Connection(this.host, this.port, [this.pw, this.isDomain]); 32 | } 33 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_group_scene_item_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/api/scene_item.dart'; 2 | 3 | import 'base.dart'; 4 | 5 | /// Basically GetSceneItemList, but for groups. 6 | /// 7 | /// Using groups at all in OBS is discouraged, as they are very broken under the hood. 8 | class GetGroupSceneItemListResponse extends BaseResponse { 9 | GetGroupSceneItemListResponse(super.json); 10 | 11 | /// Array of scene items in the group 12 | List get sceneItems => (this.json['sceneItems'] as List) 13 | .map((sceneItem) => SceneItem.fromJson(sceneItem)) 14 | .toList(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_source_screenshot.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets a Base64-encoded screenshot of a source. 4 | /// 5 | /// The imageWidth and imageHeight parameters are treated as "scale to inner", meaning the smallest 6 | /// ratio will be used and the aspect ratio of the original resolution is kept. If imageWidth and imageHeight 7 | /// are not specified, the compressed image will use the full resolution of the source. 8 | class GetSourceScreenshotResponse extends BaseResponse { 9 | GetSourceScreenshotResponse(super.json); 10 | 11 | /// Base64-encoded screenshot 12 | String get imageData => this.json['imageData']; 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "utils.cpp" 8 | "win32_window.cpp" 9 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 10 | "Runner.rc" 11 | "runner.exe.manifest" 12 | ) 13 | apply_standard_settings(${BINARY_NAME}) 14 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 15 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 16 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 17 | add_dependencies(${BINARY_NAME} flutter_assemble) 18 | -------------------------------------------------------------------------------- /lib/shared/general/themed/cupertino_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; 3 | 4 | class ThemedCupertinoScaffold extends StatelessWidget { 5 | final Widget body; 6 | 7 | const ThemedCupertinoScaffold({ 8 | super.key, 9 | required this.body, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return CupertinoScaffold( 15 | transitionBackgroundColor: Theme.of(context).scaffoldBackgroundColor, 16 | body: IconTheme( 17 | data: Theme.of(context).iconTheme, 18 | child: this.body, 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/save_source_screenshot.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Saves a screenshot of a source to the filesystem. 4 | /// The imageWidth and imageHeight parameters are treated as "scale to inner", 5 | /// meaning the smallest ratio will be used and the aspect ratio of the 6 | /// original resolution is kept. If imageWidth and imageHeight are not 7 | /// specified, the compressed image will use the full resolution of the source. 8 | class SaveSourceScreenshotResponse extends BaseResponse { 9 | SaveSourceScreenshotResponse(super.json); 10 | 11 | /// Base64-encoded screenshot 12 | String get imageData => this.json['imageData']; 13 | } 14 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/current_scene_collection_changing.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/events/base.dart'; 2 | 3 | /// The current scene collection has begun changing. 4 | /// 5 | /// Note: We recommend using this event to trigger a pause of all polling requests, 6 | /// as performing any requests during a scene collection change is considered undefined 7 | /// behavior and can cause crashes! 8 | class CurrentSceneCollectionChangingEvent extends BaseEvent { 9 | CurrentSceneCollectionChangingEvent(super.json); 10 | 11 | /// Name of the current scene collection 12 | String get sceneCollectionName => this.json['sceneCollectionName']; 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = obs_blade 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.kounex.obsBlade 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.kounex. All rights reserved. 15 | -------------------------------------------------------------------------------- /lib/types/interfaces/message.dart: -------------------------------------------------------------------------------- 1 | /// The Message interface is used as the abstraction layer for the 2 | /// messages which we receive from the OBS connection (currently via 3 | /// the WebSocket). Makes it easier to handle those messages with this 4 | /// abstraction since it allows me to name this instance instead of 5 | /// writing duplicate code (right now for Response and Event) 6 | abstract class Message { 7 | /// The RAW JSON object received through the WebSocket 8 | late Map jsonRAW; 9 | 10 | /// Will hold the 'responseData' / 'eventData' map directly 11 | /// from the [jsonRAW] - will make it easier to access later 12 | late Map json; 13 | } 14 | -------------------------------------------------------------------------------- /lib/types/classes/api/input.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:obs_blade/types/classes/api/input_channel.dart'; 3 | 4 | part 'input.freezed.dart'; 5 | part 'input.g.dart'; 6 | 7 | @freezed 8 | class Input with _$Input { 9 | const factory Input({ 10 | required String? inputKind, 11 | required String? inputName, 12 | required String? unversionedInputKind, 13 | double? inputVolumeMul, 14 | double? inputVolumeDb, 15 | List? inputLevelsMul, 16 | int? syncOffset, 17 | @Default(false) bool inputMuted, 18 | }) = _Input; 19 | 20 | factory Input.fromJson(Map json) => _$InputFromJson(json); 21 | } 22 | -------------------------------------------------------------------------------- /lib/shared/general/themed/rich_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemedRichText extends StatelessWidget { 4 | final List textSpans; 5 | final TextAlign? textAlign; 6 | final TextStyle? textStyle; 7 | 8 | const ThemedRichText({ 9 | super.key, 10 | required this.textSpans, 11 | this.textAlign, 12 | this.textStyle, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return RichText( 18 | textAlign: this.textAlign ?? TextAlign.start, 19 | text: TextSpan( 20 | style: this.textStyle ?? DefaultTextStyle.of(context).style, 21 | children: this.textSpans, 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/types/classes/api/scene.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'scene.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$SceneImpl _$$SceneImplFromJson(Map json) => _$SceneImpl( 10 | sceneName: json['sceneName'] as String, 11 | sceneIndex: (json['sceneIndex'] as num).toInt(), 12 | ); 13 | 14 | Map _$$SceneImplToJson(_$SceneImpl instance) => 15 | { 16 | 'sceneName': instance.sceneName, 17 | 'sceneIndex': instance.sceneIndex, 18 | }; 19 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments=--init-script /var/folders/9b/j0bnrfwd6r36z9l64wrjpxpw0000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/9b/j0bnrfwd6r36z9l64wrjpxpw0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/opt/homebrew/Cellar/openjdk/23/libexec/openjdk.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_scene_list.dart: -------------------------------------------------------------------------------- 1 | import '../../api/scene.dart'; 2 | import 'base.dart'; 3 | 4 | /// Gets an array of all scenes in OBS. 5 | class GetSceneListResponse extends BaseResponse { 6 | GetSceneListResponse(super.json); 7 | 8 | /// Current program scene 9 | String get currentProgramSceneName => this.json['currentProgramSceneName']; 10 | 11 | /// Current preview scene. null if not in studio mode 12 | String? get currentPreviewSceneName => this.json['currentPreviewSceneName']; 13 | 14 | /// Ordered list of the current profile's scenes (See GetCurrentScene for more information) 15 | Iterable get scenes => (this.json['scenes'] as List) 16 | .map((scene) => Scene.fromJson(scene)); 17 | } 18 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_record_status.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the status of the record output. 4 | class GetRecordStatusResponse extends BaseResponse { 5 | GetRecordStatusResponse(super.json); 6 | 7 | /// Whether the output is active 8 | bool get outputActive => this.json['outputActive']; 9 | 10 | /// Whether the output is paused 11 | bool? get outputPaused => this.json['outputPaused']; 12 | 13 | /// Current formatted timecode string for the output 14 | String get outputTimecode => this.json['outputTimecode']; 15 | 16 | /// Current duration in milliseconds for the output 17 | int get outputDuration => this.json['outputDuration']; 18 | 19 | /// Number of bytes sent by the output 20 | int get outputBytes => this.json['outputBytes']; 21 | } 22 | -------------------------------------------------------------------------------- /lib/views/settings/data_management/widgets/data_block.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../../shared/general/base/card.dart'; 5 | import '../../../../shared/general/column_separated.dart'; 6 | import 'data_entry.dart'; 7 | 8 | class DataBlock extends StatelessWidget { 9 | final List dataEntries; 10 | 11 | const DataBlock({ 12 | super.key, 13 | required this.dataEntries, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return BaseCard( 19 | bottomPadding: 12.0, 20 | child: ColumnSeparated( 21 | paddingSeparator: const EdgeInsets.symmetric(vertical: 16.0), 22 | children: this.dataEntries, 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/shared/general/themed/cupertino_sliver_navigation_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:obs_blade/utils/styling_helper.dart'; 4 | 5 | class ThemedCupertinoSliverNavigationBar extends StatelessWidget { 6 | final Widget largeTitle; 7 | 8 | const ThemedCupertinoSliverNavigationBar({ 9 | super.key, 10 | required this.largeTitle, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CupertinoSliverNavigationBar( 16 | backgroundColor: StylingHelper.isApple(context) 17 | ? Theme.of(context).appBarTheme.backgroundColor 18 | : Theme.of(context).appBarTheme.backgroundColor!.withOpacity(1.0), 19 | largeTitle: this.largeTitle, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/transition_begin.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// A transition (other than "cut") has begun 4 | class TransitionBeginEvent extends BaseEvent { 5 | TransitionBeginEvent(super.json); 6 | 7 | /// Transition name 8 | String get name => this.json['name']; 9 | 10 | /// Transition type 11 | String get type => this.json['type']; 12 | 13 | /// transition duration (in milliseconds). Will be -1 for any transition 14 | /// with a fixed duration, such as a Stinger, due to limitations 15 | /// of the OBS API 16 | int get duration => this.json['duration']; 17 | 18 | /// Source scene of the transition 19 | String get fromScene => this.json['from-scene']; 20 | 21 | /// Destination scene of the transition 22 | String get toScene => this.json['to-scene']; 23 | } 24 | -------------------------------------------------------------------------------- /lib/views/settings/custom_theme/widgets/color_picker/color_bubble.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../utils/styling_helper.dart'; 4 | 5 | class ColorBubble extends StatelessWidget { 6 | final Color color; 7 | final double size; 8 | 9 | const ColorBubble({super.key, required this.color, this.size = 24.0}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | height: this.size, 15 | width: this.size, 16 | decoration: BoxDecoration( 17 | color: this.color, 18 | borderRadius: BorderRadius.circular(this.size / 2), 19 | border: Border.all( 20 | color: StylingHelper.surroundingAwareAccent(context: context), 21 | width: 0.0, 22 | ), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 15 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_special_inputs.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the names of all special inputs. 4 | class GetSpecialInputsResponse extends BaseResponse { 5 | GetSpecialInputsResponse(super.json); 6 | 7 | /// Name of the Desktop Audio input 8 | String? get desktop1 => this.json['desktop1']; 9 | 10 | /// Name of the Desktop Audio 2 input 11 | String? get desktop2 => this.json['desktop2']; 12 | 13 | /// Name of the Mic/Auxiliary Audio input 14 | String? get mic1 => this.json['mic1']; 15 | 16 | /// Name of the Mic/Auxiliary Audio 2 input 17 | String? get mic2 => this.json['mic2']; 18 | 19 | /// Name of the Mic/Auxiliary Audio 3 input 20 | String? get mic3 => this.json['mic3']; 21 | 22 | /// Name of the Mic/Auxiliary Audio 3 input 23 | String? get mic4 => this.json['mic4']; 24 | } 25 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.2.1" apply false 22 | id "org.jetbrains.kotlin.android" version "2.0.20" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /lib/views/dashboard/widgets/dashboard_content/exposed_controls/hotkeys_control/section_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../../shared/general/base/divider.dart'; 4 | 5 | class SectionHeader extends StatelessWidget { 6 | final String title; 7 | 8 | const SectionHeader({ 9 | super.key, 10 | required this.title, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Row( 16 | children: [ 17 | const Expanded( 18 | child: BaseDivider(), 19 | ), 20 | const SizedBox(width: 24.0), 21 | Text(this.title, style: Theme.of(context).textTheme.labelSmall!), 22 | const SizedBox(width: 24.0), 23 | const Expanded( 24 | child: BaseDivider(), 25 | ), 26 | ], 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_scene_transition_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/api/transition.dart'; 2 | 3 | import 'base.dart'; 4 | 5 | /// List of all transitions available in the frontend's dropdown menu 6 | class GetSceneTransitionListResponse extends BaseResponse { 7 | GetSceneTransitionListResponse(super.json); 8 | 9 | /// Name of the current scene transition. Can be null 10 | String? get currentSceneTransitionName => 11 | this.json['currentSceneTransitionName']; 12 | 13 | /// Kind of the current scene transition. Can be null 14 | String? get currentSceneTransitionKind => 15 | this.json['currentSceneTransitionKind']; 16 | 17 | /// List of transitions 18 | List get transitions => List.from( 19 | json['transitions'].map((transition) => Transition.fromJson(transition))); 20 | } 21 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1667568563408 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/types/classes/api/input_channel.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'input_channel.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$InputChannelImpl _$$InputChannelImplFromJson(Map json) => 10 | _$InputChannelImpl( 11 | current: (json['current'] as num?)?.toDouble(), 12 | average: (json['average'] as num?)?.toDouble(), 13 | potential: (json['potential'] as num?)?.toDouble(), 14 | ); 15 | 16 | Map _$$InputChannelImplToJson(_$InputChannelImpl instance) => 17 | { 18 | 'current': instance.current, 19 | 'average': instance.average, 20 | 'potential': instance.potential, 21 | }; 22 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /lib/models/enums/chat_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:hive/hive.dart'; 3 | import 'package:obs_blade/utils/icons/custom_flutter_icons.dart'; 4 | import '../type_ids.dart'; 5 | import '../../utils/icons/jam_icons.dart'; 6 | 7 | part 'chat_type.g.dart'; 8 | 9 | @HiveType(typeId: TypeIDs.ChatType) 10 | enum ChatType { 11 | @HiveField(0) 12 | Twitch, 13 | 14 | @HiveField(1) 15 | YouTube, 16 | 17 | @HiveField(2) 18 | Owncast, 19 | } 20 | 21 | extension ChatTypeFunctions on ChatType { 22 | String get text => const { 23 | ChatType.Twitch: 'Twitch', 24 | ChatType.YouTube: 'YouTube', 25 | ChatType.Owncast: 'Owncast', 26 | }[this]!; 27 | 28 | IconData get icon => const { 29 | ChatType.Twitch: JamIcons.twitch, 30 | ChatType.YouTube: JamIcons.youtube, 31 | ChatType.Owncast: CustomFlutterIcons.owncast_logo, 32 | }[this]!; 33 | } 34 | -------------------------------------------------------------------------------- /lib/shared/general/base/constrained_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const double kBaseConstrainedMaxWidth = 640.0; 4 | 5 | class BaseConstrainedBox extends StatelessWidget { 6 | final Widget? child; 7 | 8 | final double maxWidth; 9 | 10 | final bool hasBasePadding; 11 | 12 | final EdgeInsetsGeometry? padding; 13 | 14 | const BaseConstrainedBox({ 15 | super.key, 16 | required this.child, 17 | this.maxWidth = kBaseConstrainedMaxWidth, 18 | this.hasBasePadding = false, 19 | this.padding, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Padding( 25 | padding: this.padding ?? 26 | EdgeInsets.symmetric(horizontal: this.hasBasePadding ? 24.0 : 0), 27 | child: ConstrainedBox( 28 | constraints: BoxConstraints(maxWidth: this.maxWidth), 29 | child: this.child, 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/models/enums/log_level.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hive/hive.dart'; 3 | import '../type_ids.dart'; 4 | 5 | part 'log_level.g.dart'; 6 | 7 | @HiveType(typeId: TypeIDs.LogLevel) 8 | enum LogLevel { 9 | @HiveField(0) 10 | Info, 11 | 12 | @HiveField(1) 13 | Warning, 14 | 15 | @HiveField(2) 16 | Error, 17 | } 18 | 19 | extension LogLevelFunctions on LogLevel { 20 | String get name => { 21 | LogLevel.Info: 'Info', 22 | LogLevel.Warning: 'Warning', 23 | LogLevel.Error: 'Error', 24 | }[this]!; 25 | 26 | String get prefix => { 27 | LogLevel.Info: '[INFO]', 28 | LogLevel.Warning: '[WARNING]', 29 | LogLevel.Error: '[ERROR]', 30 | }[this]!; 31 | 32 | Color get color => { 33 | LogLevel.Info: Colors.lightBlueAccent, 34 | LogLevel.Warning: Colors.orangeAccent, 35 | LogLevel.Error: Colors.redAccent, 36 | }[this]!; 37 | } 38 | -------------------------------------------------------------------------------- /lib/views/settings/logs/logs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get_it/get_it.dart'; 3 | 4 | import '../../../shared/general/transculent_cupertino_navbar_wrapper.dart'; 5 | import '../../../stores/views/logs.dart'; 6 | import 'widgets/log_explanation.dart'; 7 | import 'widgets/log_filter.dart'; 8 | import 'widgets/log_grid/log_list.dart'; 9 | 10 | class LogsView extends StatelessWidget { 11 | const LogsView({ 12 | super.key, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | GetIt.instance.resetLazySingleton(); 18 | 19 | return Scaffold( 20 | body: TransculentCupertinoNavBarWrapper( 21 | previousTitle: 'Settings', 22 | title: 'Logs', 23 | showScrollBar: true, 24 | listViewChildren: const [ 25 | LogExplanation(), 26 | LogFilter(), 27 | LogList(), 28 | ], 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/views/statistics/widgets/card_header/sort_filter_panel/filter_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mobx/flutter_mobx.dart'; 3 | import 'package:get_it/get_it.dart'; 4 | import 'package:obs_blade/shared/general/tag_box.dart'; 5 | import 'package:obs_blade/stores/views/statistics.dart'; 6 | 7 | class FilterStatus extends StatelessWidget { 8 | const FilterStatus({ 9 | super.key, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | StatisticsStore statisticsStore = GetIt.instance(); 15 | 16 | return Observer( 17 | builder: (context) => TagBox( 18 | color: statisticsStore.isFilterSortActive 19 | ? Theme.of(context).colorScheme.secondary 20 | : Theme.of(context).scaffoldBackgroundColor, 21 | label: statisticsStore.isFilterSortActive ? 'ON' : 'OFF', 22 | width: 32.0, 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | connectivity_plus 7 | file_selector_windows 8 | share_plus 9 | url_launcher_windows 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/views/settings/faq/widgets/faq_block.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FAQBlock extends StatelessWidget { 4 | final String heading; 5 | final String? text; 6 | final Widget? customBody; 7 | 8 | const FAQBlock({ 9 | super.key, 10 | required this.heading, 11 | this.text, 12 | this.customBody, 13 | }) : assert(text != null || customBody != null), 14 | super(); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Column( 19 | crossAxisAlignment: CrossAxisAlignment.start, 20 | children: [ 21 | Text( 22 | this.heading, 23 | style: TextStyle( 24 | fontSize: Theme.of(context).textTheme.titleLarge!.fontSize, 25 | fontWeight: FontWeight.bold, 26 | ), 27 | ), 28 | const SizedBox(height: 8.0), 29 | this.text != null ? Text(this.text!) : this.customBody!, 30 | ], 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | ConnectivityPlusWindowsPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); 17 | FileSelectorWindowsRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 19 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 21 | UrlLauncherWindowsRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 23 | } 24 | -------------------------------------------------------------------------------- /lib/shared/general/hive_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hive_flutter/hive_flutter.dart'; 3 | 4 | import '../../types/enums/hive_keys.dart'; 5 | import '../../types/enums/settings_keys.dart'; 6 | 7 | class HiveBuilder extends StatelessWidget { 8 | final HiveKeys hiveKey; 9 | final List? rebuildKeys; 10 | final Widget? child; 11 | final Widget Function(BuildContext context, Box box, Widget? child) 12 | builder; 13 | 14 | const HiveBuilder({ 15 | super.key, 16 | required this.hiveKey, 17 | this.child, 18 | required this.builder, 19 | this.rebuildKeys, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return ValueListenableBuilder( 25 | valueListenable: Hive.box(this.hiveKey.name).listenable( 26 | keys: this.rebuildKeys?.map((key) => key.name).toList(), 27 | ), 28 | builder: this.builder, 29 | child: this.child, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/views/home/widgets/connect_box/auto_discovery/result_entry.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../shared/animator/fader.dart'; 4 | import '../../../../../shared/general/themed/rich_text.dart'; 5 | 6 | class ResultEntry extends StatelessWidget { 7 | final String result; 8 | 9 | const ResultEntry({super.key, required this.result}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | padding: const EdgeInsets.all(24.0), 15 | alignment: Alignment.center, 16 | child: Fader( 17 | child: ThemedRichText( 18 | textAlign: TextAlign.center, 19 | textSpans: [ 20 | TextSpan(text: this.result), 21 | const TextSpan( 22 | text: '\n\nPull down to try again!', 23 | style: TextStyle( 24 | fontWeight: FontWeight.bold, 25 | ), 26 | ), 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/shared/general/custom_sliver_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSliverList extends StatelessWidget { 4 | final List children; 5 | 6 | final double? customTopPadding; 7 | final double? customBottomPadding; 8 | 9 | const CustomSliverList({ 10 | super.key, 11 | required this.children, 12 | this.customTopPadding, 13 | this.customBottomPadding, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return SliverPadding( 19 | padding: EdgeInsets.only( 20 | top: this.customTopPadding ?? 0.0, 21 | right: MediaQuery.paddingOf(context).right, 22 | bottom: this.customBottomPadding ?? 23 | (2 * kBottomNavigationBarHeight + 24 | MediaQuery.paddingOf(context).bottom / 2), 25 | left: MediaQuery.paddingOf(context).left, 26 | ), 27 | sliver: SliverList( 28 | delegate: SliverChildListDelegate(this.children), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/stores/views/intro.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobx/mobx.dart'; 2 | 3 | part 'intro.g.dart'; 4 | 5 | enum IntroStage { 6 | GettingStarted, 7 | VersionSelection, 8 | TwentyEightParty, 9 | InstallationSlides, 10 | } 11 | 12 | class IntroStore = _IntroStore with _$IntroStore; 13 | 14 | const int kSecondsToLockSlide = 5; 15 | 16 | abstract class _IntroStore with Store { 17 | @observable 18 | IntroStage stage = IntroStage.GettingStarted; 19 | 20 | @observable 21 | int currentPage = 0; 22 | 23 | @observable 24 | bool lockedOnSlide = false; 25 | 26 | int slideLockSeconds = kSecondsToLockSlide; 27 | 28 | @action 29 | void setStage(IntroStage stage) => this.stage = stage; 30 | 31 | @action 32 | void setCurrentPage(int currentPage) => this.currentPage = currentPage; 33 | 34 | @action 35 | void setLockedOnSlide(bool lockedOnSlide, [int? secondsToLockSlide]) { 36 | this.slideLockSeconds = secondsToLockSlide ?? kSecondsToLockSlide; 37 | this.lockedOnSlide = lockedOnSlide; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/types/classes/stream/batch_responses/stats.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/batch_responses/base.dart'; 2 | import 'package:obs_blade/types/classes/stream/responses/get_stats.dart'; 3 | import 'package:obs_blade/types/classes/stream/responses/get_stream_status.dart'; 4 | import 'package:obs_blade/types/enums/request_type.dart'; 5 | 6 | import '../responses/get_record_status.dart'; 7 | 8 | class StatsBatchResponse extends BaseBatchResponse { 9 | StatsBatchResponse(super.json); 10 | 11 | GetStreamStatusResponse get streamStatus => this.response( 12 | RequestType.GetStreamStatus, 13 | (jsonRAW) => GetStreamStatusResponse(jsonRAW), 14 | ); 15 | 16 | GetRecordStatusResponse get recordStatus => this.response( 17 | RequestType.GetRecordStatus, 18 | (jsonRAW) => GetRecordStatusResponse(jsonRAW), 19 | ); 20 | 21 | GetStatsResponse get stats => this.response( 22 | RequestType.GetStats, 23 | (jsonRAW) => GetStatsResponse(jsonRAW), 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /lib/views/dashboard/widgets/dashboard_content/scene_preview/preview_warning_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../shared/dialogs/confirmation.dart'; 4 | 5 | class PreviewWarningDialog extends StatelessWidget { 6 | final void Function(bool) onOk; 7 | 8 | const PreviewWarningDialog({ 9 | super.key, 10 | required this.onOk, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return ConfirmationDialog( 16 | title: 'Warning on scene preview', 17 | body: 18 | 'OBS WebSocket is not able to retrieve a video stream of the current scene. This implementation is a workaround. It does not reflect your actual OBS performance.\n\nBeware that this might cause higher battery usage and / or OBS itself (your pc) might suffer performance issues.\n\nUse with caution!', 19 | onOk: (checked) => this.onOk(checked), 20 | enableDontShowAgainOption: true, 21 | okText: 'Ok', 22 | noText: 'Cancel', 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1667568563458 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/types/classes/api/filter.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'filter.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$FilterImpl _$$FilterImplFromJson(Map json) => _$FilterImpl( 10 | filterEnabled: json['filterEnabled'] as bool, 11 | filterIndex: (json['filterIndex'] as num).toInt(), 12 | filterKind: json['filterKind'] as String, 13 | filterName: json['filterName'] as String, 14 | filterSettings: json['filterSettings'] as Map, 15 | ); 16 | 17 | Map _$$FilterImplToJson(_$FilterImpl instance) => 18 | { 19 | 'filterEnabled': instance.filterEnabled, 20 | 'filterIndex': instance.filterIndex, 21 | 'filterKind': instance.filterKind, 22 | 'filterName': instance.filterName, 23 | 'filterSettings': instance.filterSettings, 24 | }; 25 | -------------------------------------------------------------------------------- /lib/types/classes/api/transition.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'transition.freezed.dart'; 4 | part 'transition.g.dart'; 5 | 6 | @freezed 7 | class Transition with _$Transition { 8 | const factory Transition({ 9 | /// Name of the transition 10 | required String transitionName, 11 | 12 | /// Kind of the transition 13 | required String transitionKind, 14 | 15 | /// Whether the transition uses a fixed (unconfigurable) duration 16 | required bool transitionFixed, 17 | 18 | /// Configured transition duration in milliseconds. null if transition is fixed 19 | required int? transitionDuration, 20 | 21 | /// Whether the transition supports being configured 22 | required bool transitionConfigurable, 23 | 24 | /// Object of settings for the transition. null if transition is not configurable 25 | required dynamic transitionSettings, 26 | }) = _Transition; 27 | 28 | factory Transition.fromJson(Map json) => 29 | _$TransitionFromJson(json); 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | # CUSTOM 49 | # Android key file 50 | /android/key.properties 51 | 52 | # Flutter Version Manager (FVM) 53 | .fvm/ -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obs_blade", 3 | "short_name": "obs_blade", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | void main() { 9 | // testWidgets('Counter increments smoke test', (WidgetTester tester) async { 10 | // // Build our app and trigger a frame. 11 | // await tester.pumpWidget(const MyApp()); 12 | 13 | // // Verify that our counter starts at 0. 14 | // expect(find.text('0'), findsOneWidget); 15 | // expect(find.text('1'), findsNothing); 16 | 17 | // // Tap the '+' icon and trigger a frame. 18 | // await tester.tap(find.byIcon(Icons.add)); 19 | // await tester.pump(); 20 | 21 | // // Verify that our counter has incremented. 22 | // expect(find.text('0'), findsNothing); 23 | // expect(find.text('1'), findsOneWidget); 24 | // }); 25 | } 26 | -------------------------------------------------------------------------------- /lib/views/statistics/widgets/card_header/sort_filter_panel/statistics_date_range.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mobx/flutter_mobx.dart'; 3 | import 'package:get_it/get_it.dart'; 4 | 5 | import '../../../../../shared/general/date_range/date_range.dart'; 6 | import '../../../../../stores/views/statistics.dart'; 7 | 8 | class StatisticsDateRange extends StatelessWidget { 9 | const StatisticsDateRange({ 10 | super.key, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | StatisticsStore statisticsStore = GetIt.instance(); 16 | 17 | return Observer( 18 | builder: (context) => DateRange( 19 | selectedFromDate: statisticsStore.fromDate, 20 | updateFromDate: (date) => statisticsStore.setFromDate(date), 21 | selectedToDate: statisticsStore.toDate, 22 | updateToDate: (date) => statisticsStore.setToDate(date 23 | ?.add(const Duration(days: 1)) 24 | .subtract(const Duration(milliseconds: 1))), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_current_scene_transition.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets information about the current scene transition. 4 | class GetCurrentSceneTransitionResponse extends BaseResponse { 5 | GetCurrentSceneTransitionResponse(super.json); 6 | 7 | /// Name of the transition 8 | String get transitionName => this.json['transitionName']; 9 | 10 | /// Kind of the transition 11 | String get transitionKind => this.json['transitionKind']; 12 | 13 | /// Whether the transition uses a fixed (unconfigurable) duration 14 | bool get transitionFixed => this.json['transitionFixed']; 15 | 16 | /// Configured transition duration in milliseconds. null if transition is fixed 17 | int? get transitionDuration => this.json['transitionDuration']; 18 | 19 | /// Whether the transition supports being configured 20 | bool get transitionConfigurable => this.json['transitionConfigurable']; 21 | 22 | /// Object of settings for the transition. null if transition is not configurable 23 | dynamic get transitionSettings => this.json['transitionSettings']; 24 | } 25 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /lib/shared/general/responsive_widget_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../types/enums/hive_keys.dart'; 4 | import '../../types/enums/settings_keys.dart'; 5 | import '../../utils/styling_helper.dart'; 6 | import 'hive_builder.dart'; 7 | 8 | class ResponsiveWidgetWrapper extends StatelessWidget { 9 | final Widget mobileWidget; 10 | final Widget tabletWidget; 11 | 12 | const ResponsiveWidgetWrapper({ 13 | super.key, 14 | required this.mobileWidget, 15 | required this.tabletWidget, 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return HiveBuilder( 21 | hiveKey: HiveKeys.Settings, 22 | rebuildKeys: const [SettingsKeys.EnforceTabletMode], 23 | builder: (context, settingsBox, child) => 24 | MediaQuery.sizeOf(context).width > StylingHelper.max_width_mobile || 25 | settingsBox.get(SettingsKeys.EnforceTabletMode.name, 26 | defaultValue: false) 27 | ? this.tabletWidget 28 | : this.mobileWidget, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/types/classes/api/scene_item_transform.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'scene_item_transform.freezed.dart'; 4 | part 'scene_item_transform.g.dart'; 5 | 6 | @freezed 7 | class SceneItemTransform with _$SceneItemTransform { 8 | const factory SceneItemTransform({ 9 | required int? alignment, 10 | required int? boundsAlignment, 11 | required double? boundsHeight, 12 | required String? boundsType, 13 | required double? boundsWidth, 14 | required int? cropBottom, 15 | required int? cropLeft, 16 | required int? cropRight, 17 | required int? cropTop, 18 | required double? height, 19 | required double? positionX, 20 | required double? positionY, 21 | required double? rotation, 22 | required double? scaleX, 23 | required double? scaleY, 24 | required double? sourceHeight, 25 | required double? sourceWidth, 26 | required double? width, 27 | }) = _SceneItemTransform; 28 | 29 | factory SceneItemTransform.fromJson(Map json) => 30 | _$SceneItemTransformFromJson(json); 31 | } 32 | -------------------------------------------------------------------------------- /lib/views/settings/dashboard_customisation/order/widgets/profiles_preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../shared/general/base/dropdown.dart'; 4 | 5 | class ProfilesPreview extends StatelessWidget { 6 | const ProfilesPreview({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Row( 11 | children: [ 12 | Expanded( 13 | child: BaseDropdown( 14 | value: '', 15 | items: [ 16 | BaseDropdownItem( 17 | value: '', 18 | text: '', 19 | ), 20 | ], 21 | label: 'Profile', 22 | ), 23 | ), 24 | Expanded( 25 | child: BaseDropdown( 26 | value: '', 27 | items: [ 28 | BaseDropdownItem( 29 | value: '', 30 | text: '', 31 | ), 32 | ], 33 | label: 'Scene Collection', 34 | ), 35 | ), 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/shared/dialogs/info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:obs_blade/shared/general/base/adaptive_dialog/adaptive_dialog.dart'; 4 | 5 | class InfoDialog extends StatelessWidget { 6 | final String? title; 7 | final String body; 8 | 9 | final bool enableDontShowAgainOption; 10 | 11 | final Function(bool isDontShowAgainChecked)? onPressed; 12 | 13 | const InfoDialog({ 14 | super.key, 15 | required this.body, 16 | this.title, 17 | this.enableDontShowAgainOption = false, 18 | this.onPressed, 19 | }); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return BaseAdaptiveDialog( 24 | title: this.title, 25 | body: this.body, 26 | enableDontShowAgainOption: this.enableDontShowAgainOption, 27 | actions: [ 28 | DialogActionConfig( 29 | isDefaultAction: false, 30 | onPressed: (isDontShowAgainChecked) => 31 | this.onPressed?.call(isDontShowAgainChecked), 32 | child: const Text('OK'), 33 | ), 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/splash/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100% 3 | } 4 | 5 | body { 6 | margin: 0; 7 | min-height: 100%; 8 | background-color: #101823; 9 | background-size: 100% 100%; 10 | } 11 | 12 | .center { 13 | margin: 0; 14 | position: absolute; 15 | top: 50%; 16 | left: 50%; 17 | -ms-transform: translate(-50%, -50%); 18 | transform: translate(-50%, -50%); 19 | } 20 | 21 | .contain { 22 | display:block; 23 | width:100%; height:100%; 24 | object-fit: contain; 25 | } 26 | 27 | .stretch { 28 | display:block; 29 | width:100%; height:100%; 30 | } 31 | 32 | .cover { 33 | display:block; 34 | width:100%; height:100%; 35 | object-fit: cover; 36 | } 37 | 38 | .bottom { 39 | position: absolute; 40 | bottom: 0; 41 | left: 50%; 42 | -ms-transform: translate(-50%, 0); 43 | transform: translate(-50%, 0); 44 | } 45 | 46 | .bottomLeft { 47 | position: absolute; 48 | bottom: 0; 49 | left: 0; 50 | } 51 | 52 | .bottomRight { 53 | position: absolute; 54 | bottom: 0; 55 | right: 0; 56 | } 57 | 58 | @media (prefers-color-scheme: dark) { 59 | body { 60 | background-color: #101823; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/views/settings/dashboard_customisation/order/widgets/controls_preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:obs_blade/shared/general/base/button.dart'; 3 | 4 | import '../../../../../shared/general/described_box.dart'; 5 | 6 | class ControlsPreview extends StatelessWidget { 7 | const ControlsPreview({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return DescribedBox( 12 | label: 'Various Controls', 13 | labelBackgroundColor: Theme.of(context).scaffoldBackgroundColor, 14 | borderColor: Theme.of(context).dividerColor, 15 | child: Column( 16 | children: [ 17 | SizedBox( 18 | width: double.infinity, 19 | child: BaseButton( 20 | text: '', 21 | onPressed: () {}, 22 | ), 23 | ), 24 | SizedBox( 25 | width: double.infinity, 26 | child: BaseButton( 27 | text: '', 28 | onPressed: () {}, 29 | ), 30 | ), 31 | ], 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/shared/general/keyboard_number_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:keyboard_actions/keyboard_actions.dart'; 3 | 4 | class KeyboardNumberHeader extends StatelessWidget { 5 | final Widget child; 6 | final FocusNode focusNode; 7 | 8 | final void Function()? onDone; 9 | 10 | const KeyboardNumberHeader({ 11 | super.key, 12 | required this.child, 13 | required this.focusNode, 14 | this.onDone, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return KeyboardActions( 20 | config: KeyboardActionsConfig( 21 | keyboardActionsPlatform: KeyboardActionsPlatform.IOS, 22 | keyboardBarColor: Theme.of(context).brightness == Brightness.dark 23 | ? const Color.fromRGBO(45, 45, 45, 1.0) 24 | : const Color.fromRGBO(245, 245, 245, 1.0), 25 | nextFocus: false, 26 | actions: [ 27 | KeyboardActionsItem( 28 | focusNode: this.focusNode, 29 | onTapAction: this.onDone, 30 | ), 31 | ], 32 | ), 33 | disableScroll: true, 34 | child: this.child, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/base.dart: -------------------------------------------------------------------------------- 1 | import '../../../enums/event_type.dart'; 2 | import '../../../interfaces/message.dart'; 3 | 4 | /// Initial Wrapper object for an event which is received from the OBS WebSocket 5 | class BaseEvent implements Message { 6 | @override 7 | Map jsonRAW; 8 | 9 | @override 10 | Map json; 11 | 12 | BaseEvent(Map json) 13 | : jsonRAW = json, 14 | json = json['d']['eventData'] ?? {}; 15 | 16 | EventType? get eventType { 17 | try { 18 | return EventType.values.firstWhere( 19 | (eventType) => eventType.name == this.jsonRAW['d']['eventType'], 20 | ); 21 | } catch (e) { 22 | return null; 23 | } 24 | } 25 | 26 | /// (Optional): time elapsed between now and stream start (only present if OBS Studio is streaming) 27 | /// 28 | /// OLD, < 4.X 29 | String? get streamTimecodeOld => this.json['stream-timecode']; 30 | 31 | /// (Optional): time elapsed between now and recording start (only present if OBS Studio is recording) 32 | /// 33 | /// OLD, < 4.X 34 | String? get recTimecodeOld => this.json['rec-timecode']; 35 | } 36 | -------------------------------------------------------------------------------- /lib/models/hotkey.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'hotkey.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class HotkeyAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 11; 12 | 13 | @override 14 | Hotkey read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return Hotkey( 20 | fields[0] as String, 21 | ); 22 | } 23 | 24 | @override 25 | void write(BinaryWriter writer, Hotkey obj) { 26 | writer 27 | ..writeByte(1) 28 | ..writeByte(0) 29 | ..write(obj.name); 30 | } 31 | 32 | @override 33 | int get hashCode => typeId.hashCode; 34 | 35 | @override 36 | bool operator ==(Object other) => 37 | identical(this, other) || 38 | other is HotkeyAdapter && 39 | runtimeType == other.runtimeType && 40 | typeId == other.typeId; 41 | } 42 | -------------------------------------------------------------------------------- /lib/shared/general/formatted_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FormattedText extends StatelessWidget { 4 | final String label; 5 | final String? text; 6 | final double width; 7 | final String? unit; 8 | 9 | const FormattedText({ 10 | super.key, 11 | required this.label, 12 | this.text, 13 | this.width = 50.0, 14 | this.unit, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return SizedBox( 20 | width: this.width, 21 | child: TextField( 22 | controller: TextEditingController( 23 | text: 24 | (this.text ?? '-') + (this.text != null ? (this.unit ?? '') : ''), 25 | ), 26 | style: Theme.of(context).textTheme.bodyMedium!.copyWith( 27 | fontFeatures: [ 28 | const FontFeature.tabularFigures(), 29 | ], 30 | ), 31 | decoration: InputDecoration( 32 | isDense: true, 33 | enabled: false, 34 | labelText: this.label, 35 | labelStyle: 36 | Theme.of(context).textTheme.bodyMedium!.copyWith(height: 0.75), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/views/statistics/widgets/card_header/sort_filter_panel/stat_type_control.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_mobx/flutter_mobx.dart'; 3 | import 'package:get_it/get_it.dart'; 4 | 5 | import '../../../../../stores/views/statistics.dart'; 6 | 7 | class StatTypeControl extends StatelessWidget { 8 | const StatTypeControl({ 9 | super.key, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | StatisticsStore statisticsStore = GetIt.instance(); 15 | 16 | return Observer( 17 | builder: (context) => SizedBox( 18 | width: double.infinity, 19 | child: CupertinoSlidingSegmentedControl( 20 | groupValue: statisticsStore.statType, 21 | padding: const EdgeInsets.all(0), 22 | children: Map.fromEntries( 23 | StatType.values.map( 24 | (statType) => MapEntry( 25 | statType, 26 | Text(statType.name), 27 | ), 28 | ), 29 | ), 30 | onValueChanged: (statType) => statisticsStore.setStatType(statType!), 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_stream_status.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets the status of the stream output. 4 | class GetStreamStatusResponse extends BaseResponse { 5 | GetStreamStatusResponse(super.json); 6 | 7 | /// Whether the output is active 8 | bool get outputActive => this.json['outputActive']; 9 | 10 | /// Whether the output is currently reconnecting 11 | bool get outputReconnecting => this.json['outputReconnecting']; 12 | 13 | /// Current formatted timecode string for the output 14 | String get outputTimecode => this.json['outputTimecode']; 15 | 16 | /// Current duration in milliseconds for the output 17 | int get outputDuration => this.json['outputDuration']; 18 | 19 | /// Congestion of the output 20 | double get outputCongestion => this.json['outputCongestion']; 21 | 22 | /// Number of bytes sent by the output 23 | int get outputBytes => this.json['outputBytes']; 24 | 25 | /// Number of frames skipped by the output's process 26 | int get outputSkippedFrames => this.json['outputSkippedFrames']; 27 | 28 | /// Total number of frames delivered by the output's process 29 | int get outputTotalFrames => this.json['outputTotalFrames']; 30 | } 31 | -------------------------------------------------------------------------------- /lib/views/dashboard/widgets/dashboard_content/scene_content/media_inputs/media_inputs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../../shared/general/nested_list_manager.dart'; 4 | 5 | class MediaInputs extends StatefulWidget { 6 | const MediaInputs({ 7 | super.key, 8 | }); 9 | 10 | @override 11 | State createState() => _MediaInputsState(); 12 | } 13 | 14 | class _MediaInputsState extends State 15 | with AutomaticKeepAliveClientMixin { 16 | final ScrollController _controller = ScrollController(); 17 | 18 | @override 19 | bool get wantKeepAlive => true; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | super.build(context); 24 | return NestedScrollManager( 25 | parentScrollController: 26 | ModalRoute.of(context)!.settings.arguments as ScrollController, 27 | child: Scrollbar( 28 | controller: _controller, 29 | thumbVisibility: true, 30 | child: ListView( 31 | controller: _controller, 32 | physics: const ClampingScrollPhysics(), 33 | padding: const EdgeInsets.all(0.0), 34 | children: const [], 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/views/statistics/widgets/card_header/sort_filter_panel/exclude_unnamed_checkbox.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_mobx/flutter_mobx.dart'; 4 | import 'package:get_it/get_it.dart'; 5 | 6 | import '../../../../../shared/general/base/checkbox.dart'; 7 | import '../../../../../stores/views/statistics.dart'; 8 | 9 | class ExcludeUnnamedCheckbox extends StatelessWidget { 10 | const ExcludeUnnamedCheckbox({ 11 | super.key, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | StatisticsStore statisticsStore = GetIt.instance(); 17 | 18 | return Transform.translate( 19 | offset: const Offset(-14.0, 0.0), 20 | child: Observer( 21 | builder: (context) => BaseCheckbox( 22 | value: statisticsStore.excludeUnnamedStats, 23 | text: 'Exclude unnamed entries', 24 | smallText: true, 25 | tristate: true, 26 | onChanged: (excludeUnnamedStats) { 27 | HapticFeedback.lightImpact(); 28 | 29 | statisticsStore.setExcludeUnnamedStats(excludeUnnamedStats); 30 | }, 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/shared/general/question_mark_tooltip.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import '../../utils/modal_handler.dart'; 4 | import '../dialogs/info.dart'; 5 | 6 | class QuestionMarkTooltip extends StatelessWidget { 7 | final String message; 8 | 9 | const QuestionMarkTooltip({ 10 | super.key, 11 | required this.message, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GestureDetector( 17 | onTap: () => ModalHandler.showBaseDialog( 18 | context: context, 19 | barrierDismissible: true, 20 | dialogWidget: InfoDialog( 21 | body: this.message, 22 | ), 23 | ), 24 | child: const Icon( 25 | CupertinoIcons.question_circle_fill, 26 | )); 27 | // return CustomTooltip( 28 | // message: this.message, 29 | // padding: EdgeInsets.all(12.0), 30 | // margin: EdgeInsets.all(32.0), 31 | // preferBelow: false, 32 | // verticalOffset: 2.0, 33 | // waitDuration: Duration(milliseconds: 0), 34 | // showDuration: Duration(seconds: 5), 35 | // immediately: true, 36 | // child: Icon(StylingHelper.question), 37 | // ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/types/classes/stream/responses/get_version.dart: -------------------------------------------------------------------------------- 1 | import 'base.dart'; 2 | 3 | /// Gets data about the current plugin and RPC version. 4 | class GetVersionResponse extends BaseResponse { 5 | GetVersionResponse(super.json); 6 | 7 | /// Current OBS Studio version 8 | String get obsVersion => this.json['obsVersion']; 9 | 10 | /// Current obs-websocket version 11 | String get obsWebSocketVersion => this.json['obsWebSocketVersion']; 12 | 13 | /// Current latest obs-websocket RPC version 14 | int get rpcVersion => this.json['rpcVersion']; 15 | 16 | /// Array of available RPC requests for the currently negotiated RPC version 17 | List get availableRequests => 18 | List.from(this.json['availableRequests']); 19 | 20 | /// Image formats available in GetSourceScreenshot and SaveSourceScreenshot requests. 21 | List get supportedImageFormats => 22 | List.from(this.json['supportedImageFormats']); 23 | 24 | /// Name of the platform. Usually windows, macos, or ubuntu (linux flavor). 25 | /// Not guaranteed to be any of those 26 | String get platform => this.json['platform']; 27 | 28 | /// Description of the platform, like Windows 10 (10.0) 29 | String get platformDescription => this.json['platformDescription']; 30 | } 31 | -------------------------------------------------------------------------------- /lib/views/home/widgets/connect_box/auto_discovery/session_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import '../../../../../models/connection.dart'; 4 | import '../../../../../shared/general/base/divider.dart'; 5 | import '../../../../../shared/general/custom_expansion_tile.dart'; 6 | import '../connect_form/connect_form.dart'; 7 | 8 | class SessionTile extends StatelessWidget { 9 | final Connection connection; 10 | 11 | const SessionTile({super.key, required this.connection}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CustomExpansionTile( 16 | leadingIcon: CupertinoIcons.desktopcomputer, 17 | headerText: this.connection.host, 18 | headerPadding: const EdgeInsets.symmetric( 19 | horizontal: 24.0, 20 | vertical: 18.0, 21 | ), 22 | expandedBody: Column( 23 | children: [ 24 | const BaseDivider(), 25 | Padding( 26 | padding: const EdgeInsets.only( 27 | top: 12.0, 28 | left: 24.0, 29 | right: 24, 30 | bottom: 12.0, 31 | ), 32 | child: ConnectForm(connection: this.connection), 33 | ), 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/views/intro/widgets/back_so_selection_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get_it/get_it.dart'; 4 | import 'package:obs_blade/stores/views/intro.dart'; 5 | 6 | import '../../../shared/general/themed/cupertino_button.dart'; 7 | 8 | class BackToSelectionWrapper extends StatelessWidget { 9 | final Widget? child; 10 | 11 | const BackToSelectionWrapper({ 12 | super.key, 13 | this.child, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Column( 19 | children: [ 20 | SizedBox(height: MediaQuery.paddingOf(context).top), 21 | ThemedCupertinoButton( 22 | child: Row( 23 | children: [ 24 | Icon( 25 | CupertinoIcons.chevron_left, 26 | color: Theme.of(context).cupertinoOverrideTheme!.primaryColor, 27 | ), 28 | const Text('Version Selection') 29 | ], 30 | ), 31 | onPressed: () => GetIt.instance() 32 | .setStage(IntroStage.VersionSelection), 33 | ), 34 | Expanded( 35 | child: this.child ?? const SizedBox(), 36 | ), 37 | ], 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/types/classes/stream/events/input_volume_meters.dart: -------------------------------------------------------------------------------- 1 | import '../../api/input_channel.dart'; 2 | import 'base.dart'; 3 | 4 | /// A high-volume event providing volume levels of all active inputs every 50 milliseconds. 5 | class InputVolumeMetersEvent extends BaseEvent { 6 | InputVolumeMetersEvent(super.json); 7 | 8 | /// Array of active inputs with their associated volume levels 9 | List get inputs => 10 | List.from(this.json['inputs'].map((input) => InputLevel.fromJSON(input))); 11 | } 12 | 13 | class InputLevel { 14 | String? inputName; 15 | List? inputLevelsMul; 16 | 17 | InputLevel({ 18 | required this.inputName, 19 | required this.inputLevelsMul, 20 | }); 21 | 22 | static List _mapToChannel(List channels) => 23 | List.from(channels.map( 24 | (channel) => InputChannel( 25 | current: channel[1], 26 | average: channel[0], 27 | potential: channel[2], 28 | ), 29 | )); 30 | 31 | static InputLevel fromJSON(Map json) => InputLevel( 32 | inputName: json['inputName'], 33 | inputLevelsMul: json['inputLevelsMul'].isNotEmpty 34 | ? _mapToChannel(json['inputLevelsMul']) 35 | : [], 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /lib/types/classes/api/transition.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'transition.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$TransitionImpl _$$TransitionImplFromJson(Map json) => 10 | _$TransitionImpl( 11 | transitionName: json['transitionName'] as String, 12 | transitionKind: json['transitionKind'] as String, 13 | transitionFixed: json['transitionFixed'] as bool, 14 | transitionDuration: (json['transitionDuration'] as num?)?.toInt(), 15 | transitionConfigurable: json['transitionConfigurable'] as bool, 16 | transitionSettings: json['transitionSettings'], 17 | ); 18 | 19 | Map _$$TransitionImplToJson(_$TransitionImpl instance) => 20 | { 21 | 'transitionName': instance.transitionName, 22 | 'transitionKind': instance.transitionKind, 23 | 'transitionFixed': instance.transitionFixed, 24 | 'transitionDuration': instance.transitionDuration, 25 | 'transitionConfigurable': instance.transitionConfigurable, 26 | 'transitionSettings': instance.transitionSettings, 27 | }; 28 | -------------------------------------------------------------------------------- /lib/shared/general/clean_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CleanListTile extends StatelessWidget { 4 | final String title; 5 | final String description; 6 | 7 | final Widget? trailing; 8 | 9 | const CleanListTile({ 10 | super.key, 11 | required this.title, 12 | required this.description, 13 | this.trailing, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Row( 19 | children: [ 20 | Expanded( 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Text( 25 | this.title, 26 | style: Theme.of(context) 27 | .textTheme 28 | .labelLarge! 29 | .copyWith(fontSize: 16), 30 | ), 31 | const SizedBox(height: 4.0), 32 | Text( 33 | this.description, 34 | style: Theme.of(context).textTheme.bodySmall, 35 | ), 36 | ], 37 | ), 38 | ), 39 | if (this.trailing != null) ...[ 40 | const SizedBox(width: 32.0), 41 | this.trailing!, 42 | ], 43 | ], 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /lib/views/dashboard/widgets/obs_widgets/obs_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../shared/general/base/card.dart'; 4 | import 'stats/stats.dart'; 5 | import 'stream_chat/stream_chat.dart'; 6 | 7 | class OBSWidgets extends StatelessWidget { 8 | const OBSWidgets({ 9 | super.key, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return const Row( 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 17 | children: [ 18 | Flexible( 19 | child: BaseCard( 20 | title: 'Chat', 21 | rightPadding: 12.0, 22 | paddingChild: EdgeInsets.all(0), 23 | child: SizedBox( 24 | height: 750.0, 25 | child: StreamChat( 26 | usernameRowPadding: true, 27 | ), 28 | ), 29 | ), 30 | ), 31 | Flexible( 32 | child: BaseCard( 33 | title: 'Stats', 34 | leftPadding: 12.0, 35 | paddingChild: EdgeInsets.all(0), 36 | child: SizedBox( 37 | height: 650.0, 38 | child: Stats(), 39 | ), 40 | ), 41 | ), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/types/classes/stream/batch_responses/inputs.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/batch_responses/base.dart'; 2 | import 'package:obs_blade/types/classes/stream/responses/get_input_audio_sync_offset.dart'; 3 | import 'package:obs_blade/types/classes/stream/responses/get_input_mute.dart'; 4 | import 'package:obs_blade/types/classes/stream/responses/get_input_volume.dart'; 5 | import 'package:obs_blade/types/enums/request_type.dart'; 6 | 7 | class InputsBatchResponse extends BaseBatchResponse { 8 | InputsBatchResponse(super.json); 9 | 10 | Iterable get inputsVolume => this 11 | .responses 12 | .where((response) => response.requestType == RequestType.GetInputVolume) 13 | .map((response) => GetInputVolumeResponse(response.jsonRAW)); 14 | 15 | Iterable get inputsMute => this 16 | .responses 17 | .where((response) => response.requestType == RequestType.GetInputMute) 18 | .map((response) => GetInputMuteResponse(response.jsonRAW)); 19 | 20 | Iterable get inputsAudioSyncOffset => this 21 | .responses 22 | .where((response) => 23 | response.requestType == RequestType.GetInputAudioSyncOffset) 24 | .map((response) => GetInputAudioSyncOffsetResponse(response.jsonRAW)); 25 | } 26 | -------------------------------------------------------------------------------- /lib/shared/general/themed/cupertino_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ThemedCupertinoButton extends StatelessWidget { 5 | final String? text; 6 | final Widget? child; 7 | final EdgeInsetsGeometry? padding; 8 | final bool isDestructive; 9 | final double? minSize; 10 | 11 | final void Function()? onPressed; 12 | 13 | const ThemedCupertinoButton({ 14 | super.key, 15 | this.text, 16 | this.child, 17 | this.padding, 18 | this.isDestructive = false, 19 | this.minSize, 20 | this.onPressed, 21 | }); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return CupertinoButton( 26 | minSize: this.minSize ?? kMinInteractiveDimensionCupertino, 27 | padding: this.padding, 28 | onPressed: this.onPressed, 29 | child: DefaultTextStyle.merge( 30 | style: TextStyle( 31 | color: this.onPressed != null 32 | ? this.isDestructive 33 | ? CupertinoColors.destructiveRed 34 | : Theme.of(context).cupertinoOverrideTheme!.primaryColor 35 | : null, 36 | ), 37 | child: this.child ?? 38 | Text( 39 | this.text ?? '', 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /lib/models/enums/scene_item_type.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'scene_item_type.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class SceneItemTypeAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 5; 12 | 13 | @override 14 | SceneItemType read(BinaryReader reader) { 15 | switch (reader.readByte()) { 16 | case 0: 17 | return SceneItemType.Source; 18 | case 1: 19 | return SceneItemType.Audio; 20 | default: 21 | return SceneItemType.Source; 22 | } 23 | } 24 | 25 | @override 26 | void write(BinaryWriter writer, SceneItemType obj) { 27 | switch (obj) { 28 | case SceneItemType.Source: 29 | writer.writeByte(0); 30 | break; 31 | case SceneItemType.Audio: 32 | writer.writeByte(1); 33 | break; 34 | } 35 | } 36 | 37 | @override 38 | int get hashCode => typeId.hashCode; 39 | 40 | @override 41 | bool operator ==(Object other) => 42 | identical(this, other) || 43 | other is SceneItemTypeAdapter && 44 | runtimeType == other.runtimeType && 45 | typeId == other.typeId; 46 | } 47 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /lib/views/statistics/widgets/stats_entry/entry_meta_chip.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EntryMetaChip extends StatelessWidget { 4 | final String title; 5 | final String content; 6 | 7 | final double? width; 8 | 9 | const EntryMetaChip({ 10 | super.key, 11 | required this.title, 12 | required this.content, 13 | this.width, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return SizedBox( 19 | width: this.width ?? double.infinity, 20 | child: Padding( 21 | padding: const EdgeInsets.all(4.0), 22 | child: Chip( 23 | padding: const EdgeInsets.all(4.0), 24 | label: SizedBox( 25 | width: double.infinity, 26 | child: Column( 27 | children: [ 28 | Padding( 29 | padding: const EdgeInsets.only(bottom: 4.0), 30 | child: Text( 31 | this.title, 32 | style: Theme.of(context).textTheme.titleSmall!.copyWith( 33 | decoration: TextDecoration.underline, 34 | ), 35 | ), 36 | ), 37 | Text(this.content), 38 | ], 39 | ), 40 | ), 41 | ), 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/shared/general/base/icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BaseIconButton extends StatelessWidget { 4 | final IconData? icon; 5 | final double? iconSize; 6 | final double? buttonSize; 7 | final Color? backgroundColor; 8 | final Color? foregroundColor; 9 | 10 | final void Function()? onTap; 11 | 12 | final Widget? child; 13 | 14 | const BaseIconButton({ 15 | super.key, 16 | this.icon, 17 | this.iconSize, 18 | this.buttonSize, 19 | this.backgroundColor, 20 | this.foregroundColor, 21 | this.onTap, 22 | this.child, 23 | }) : assert(icon != null || child != null); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return GestureDetector( 28 | onTap: this.onTap, 29 | child: Container( 30 | height: this.buttonSize, 31 | width: this.buttonSize, 32 | padding: const EdgeInsets.all(4.0), 33 | decoration: BoxDecoration( 34 | shape: BoxShape.circle, 35 | color: this.backgroundColor ?? 36 | Theme.of(context).buttonTheme.colorScheme!.secondary, 37 | ), 38 | child: this.child ?? 39 | Icon( 40 | this.icon, 41 | color: this.foregroundColor, 42 | size: this.iconSize, 43 | ), 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/models/hidden_scene.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'type_ids.dart'; 3 | 4 | part 'hidden_scene.g.dart'; 5 | 6 | @HiveType(typeId: TypeIDs.HiddenScene) 7 | class HiddenScene extends HiveObject { 8 | /// Name of the scene (unfortunately OBS WebSocket does not expose something 9 | /// like an ID for scene, just the name) which should be hidden 10 | @HiveField(0) 11 | String sceneName; 12 | 13 | /// Since the scene name itself is not a good enough indicator (different OBS 14 | /// instances could share the same scene name like "Main"), a good indicator 15 | /// (if present since it's not mandatory) would be the connection name because 16 | /// it has to be unique 17 | @HiveField(1) 18 | String? connectionName; 19 | 20 | /// Used as a backup if no connection name is present because it's better than 21 | /// only using the name but might cause false behaviour when the host 22 | /// changes for whatever reasons 23 | @HiveField(2) 24 | String host; 25 | 26 | HiddenScene(this.sceneName, this.connectionName, this.host); 27 | 28 | bool isScene(String sceneName, String? connectionName, String? host) => 29 | this.sceneName == sceneName && 30 | (this.connectionName == connectionName || 31 | (this.connectionName == null && 32 | connectionName == null && 33 | this.host == host)); 34 | } 35 | -------------------------------------------------------------------------------- /lib/types/classes/stream/batch_responses/base.dart: -------------------------------------------------------------------------------- 1 | import 'package:obs_blade/types/classes/stream/responses/base.dart'; 2 | import 'package:obs_blade/types/enums/request_batch_type.dart'; 3 | import 'package:obs_blade/types/interfaces/message.dart'; 4 | 5 | import '../../../enums/request_type.dart'; 6 | 7 | class BaseBatchResponse implements Message { 8 | @override 9 | Map json; 10 | 11 | @override 12 | Map jsonRAW; 13 | 14 | List responses; 15 | 16 | BaseBatchResponse(this.json) 17 | : jsonRAW = json, 18 | responses = List.from(json['d']['results']) 19 | .map((response) => BaseResponse.d(response)) 20 | .toList(); 21 | 22 | String get uuid => this.jsonRAW['d']['requestId']; 23 | 24 | RequestBatchType get batchRequestType => RequestBatchType.values.firstWhere( 25 | (type) => type.requestTypes.every( 26 | (requestType) => this.responses.any( 27 | (response) => requestType == response.requestType, 28 | ), 29 | ), 30 | ); 31 | 32 | T response( 33 | RequestType requestType, T Function(Map) creation) => 34 | creation(this 35 | .responses 36 | .firstWhere((response) => response.requestType == requestType) 37 | .jsonRAW); 38 | } 39 | -------------------------------------------------------------------------------- /lib/models/enums/dashboard_element.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | import '../type_ids.dart'; 4 | 5 | part 'dashboard_element.g.dart'; 6 | 7 | @HiveType(typeId: TypeIDs.DashboardElement) 8 | enum DashboardElement { 9 | @HiveField(0) 10 | ExposedProfile, 11 | 12 | @HiveField(1) 13 | ExposedControls, 14 | 15 | @HiveField(2) 16 | SceneButtons, 17 | 18 | @HiveField(3) 19 | StudioModeTransition, 20 | 21 | @HiveField(4) 22 | StudioModeConfig, 23 | 24 | @HiveField(5) 25 | ScenePreview, 26 | 27 | @HiveField(6) 28 | SceneItems, 29 | 30 | @HiveField(7) 31 | SceneItemsAudio, 32 | 33 | @HiveField(8) 34 | StreamChat, 35 | 36 | @HiveField(9) 37 | OBSStats; 38 | 39 | String get name => switch (this) { 40 | DashboardElement.ExposedProfile => 'Profiles', 41 | DashboardElement.ExposedControls => 'Controls', 42 | DashboardElement.SceneButtons => 'Scene Buttons', 43 | DashboardElement.StudioModeTransition => 'Studio Mode Transition', 44 | DashboardElement.StudioModeConfig => 'Studio Mode Config', 45 | DashboardElement.ScenePreview => 'Scene Preview', 46 | DashboardElement.SceneItems => 'Scene Items', 47 | DashboardElement.SceneItemsAudio => 'Scene Audio', 48 | DashboardElement.StreamChat => 'Chat', 49 | DashboardElement.OBSStats => 'Stats', 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /lib/types/classes/api/record_stats.dart: -------------------------------------------------------------------------------- 1 | class RecordStats { 2 | /// Total time (in seconds) since the record started 3 | int totalTime; 4 | 5 | /// Current framerate 6 | double fps; 7 | 8 | /// Amount of data per second (in kilobits) transmitted by the output 9 | int kbitsPerSec; 10 | 11 | /// Number of frames rendered 12 | int renderTotalFrames; 13 | 14 | /// Number of frames skipped due to rendering lag 15 | int renderSkippedFrames; 16 | 17 | /// Number of frames outputted 18 | int outputTotalFrames; 19 | 20 | /// Number of frames skipped due to encoding lag 21 | int outputSkippedFrames; 22 | 23 | /// Average frame time (in milliseconds) 24 | double averageFrameTime; 25 | 26 | /// Current CPU usage (percentage) 27 | double cpuUsage; 28 | 29 | /// Current RAM usage (in megabytes) 30 | double memoryUsage; 31 | 32 | /// Free recording disk space (in megabytes) 33 | double freeDiskSpace; 34 | 35 | RecordStats({ 36 | required this.kbitsPerSec, 37 | required this.totalTime, 38 | required this.fps, 39 | required this.renderTotalFrames, 40 | required this.renderSkippedFrames, 41 | required this.outputTotalFrames, 42 | required this.outputSkippedFrames, 43 | required this.averageFrameTime, 44 | required this.cpuUsage, 45 | required this.memoryUsage, 46 | required this.freeDiskSpace, 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /lib/views/statistics/widgets/card_header/sort_filter_panel/filter_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get_it/get_it.dart'; 3 | import 'package:mobx/mobx.dart'; 4 | 5 | import '../../../../../stores/views/statistics.dart'; 6 | 7 | class FilterName extends StatefulWidget { 8 | const FilterName({ 9 | super.key, 10 | }); 11 | 12 | @override 13 | State createState() => _FilterNameState(); 14 | } 15 | 16 | class _FilterNameState extends State { 17 | final List _d = []; 18 | final TextEditingController _controller = TextEditingController(); 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | 24 | _d.add(reaction( 25 | (_) => GetIt.instance().triggeredDefault, 26 | (__) => _controller.clear(), 27 | )); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | for (final d in _d) { 33 | d(); 34 | } 35 | 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return CupertinoTextField( 42 | controller: _controller, 43 | placeholder: 'Filter by name...', 44 | clearButtonMode: OverlayVisibilityMode.always, 45 | onChanged: (name) => GetIt.instance().setFilterName( 46 | name.trim().toLowerCase(), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/types/classes/api/scene_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:obs_blade/types/classes/api/filter.dart'; 3 | 4 | import 'scene_item_transform.dart'; 5 | 6 | part 'scene_item.freezed.dart'; 7 | part 'scene_item.g.dart'; 8 | 9 | @freezed 10 | class SceneItem with _$SceneItem { 11 | const factory SceneItem({ 12 | required String? inputKind, 13 | required bool? isGroup, 14 | required String? sceneItemBlendMode, 15 | required bool? sceneItemEnabled, 16 | required int? sceneItemId, 17 | required int? sceneItemIndex, 18 | required bool? sceneItemLocked, 19 | required SceneItemTransform? sceneItemTransform, 20 | required String? sourceName, 21 | required String? sourceType, 22 | @Default([]) List filters, 23 | 24 | /// OPTIONAL - Name of the item's parent (if this item belongs to a group) 25 | String? parentGroupName, 26 | 27 | /// OPTIONAL - List of children (if this item is a group) 28 | List? groupChildren, 29 | 30 | /// CUSTOM - added myself to handle stuff internally 31 | 32 | /// Indicate whether we want to display the children of this group 33 | /// (if this [SceneItem] is a group) 34 | @Default(false) bool displayGroup, 35 | }) = _SceneItem; 36 | 37 | factory SceneItem.fromJson(Map json) => 38 | _$SceneItemFromJson(json); 39 | } 40 | -------------------------------------------------------------------------------- /lib/models/hidden_scene.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'hidden_scene.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class HiddenSceneAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 6; 12 | 13 | @override 14 | HiddenScene read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return HiddenScene( 20 | fields[0] as String, 21 | fields[1] as String?, 22 | fields[2] as String, 23 | ); 24 | } 25 | 26 | @override 27 | void write(BinaryWriter writer, HiddenScene obj) { 28 | writer 29 | ..writeByte(3) 30 | ..writeByte(0) 31 | ..write(obj.sceneName) 32 | ..writeByte(1) 33 | ..write(obj.connectionName) 34 | ..writeByte(2) 35 | ..write(obj.host); 36 | } 37 | 38 | @override 39 | int get hashCode => typeId.hashCode; 40 | 41 | @override 42 | bool operator ==(Object other) => 43 | identical(this, other) || 44 | other is HiddenSceneAdapter && 45 | runtimeType == other.runtimeType && 46 | typeId == other.typeId; 47 | } 48 | -------------------------------------------------------------------------------- /lib/models/enums/log_level.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'log_level.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class LogLevelAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 8; 12 | 13 | @override 14 | LogLevel read(BinaryReader reader) { 15 | switch (reader.readByte()) { 16 | case 0: 17 | return LogLevel.Info; 18 | case 1: 19 | return LogLevel.Warning; 20 | case 2: 21 | return LogLevel.Error; 22 | default: 23 | return LogLevel.Info; 24 | } 25 | } 26 | 27 | @override 28 | void write(BinaryWriter writer, LogLevel obj) { 29 | switch (obj) { 30 | case LogLevel.Info: 31 | writer.writeByte(0); 32 | break; 33 | case LogLevel.Warning: 34 | writer.writeByte(1); 35 | break; 36 | case LogLevel.Error: 37 | writer.writeByte(2); 38 | break; 39 | } 40 | } 41 | 42 | @override 43 | int get hashCode => typeId.hashCode; 44 | 45 | @override 46 | bool operator ==(Object other) => 47 | identical(this, other) || 48 | other is LogLevelAdapter && 49 | runtimeType == other.runtimeType && 50 | typeId == other.typeId; 51 | } 52 | -------------------------------------------------------------------------------- /lib/types/enums/hive_keys.dart: -------------------------------------------------------------------------------- 1 | enum HiveKeys { 2 | /// Returns a List of [Connection] 3 | SavedConnections, 4 | 5 | /// Returns a List of [PastStreamData] 6 | PastStreamData, 7 | 8 | /// Returns a List of [PastRecordData] 9 | PastRecordData, 10 | 11 | /// Returns a List of [CustomTheme] 12 | CustomTheme, 13 | 14 | /// Returns a List of [HiddenSceneItem] 15 | HiddenSceneItem, 16 | 17 | /// Returns a List of [HiddenScene] 18 | HiddenScene, 19 | 20 | /// Returns a List of [AppLog] 21 | AppLog, 22 | 23 | /// Returns a List of [PurchasedTip] 24 | PurchasedTip, 25 | 26 | /// Returns a List of [Hotkey] 27 | Hotkey, 28 | 29 | /// Returns the box containing app settings - refer to [SettingsKeys] 30 | /// to see which key-value pairs are available 31 | Settings, 32 | } 33 | 34 | extension HiveKeysFunctions on HiveKeys { 35 | String get name => const { 36 | HiveKeys.SavedConnections: 'saved-connections', 37 | HiveKeys.PastStreamData: 'past-stream-data', 38 | HiveKeys.PastRecordData: 'past-record-data', 39 | HiveKeys.CustomTheme: 'custom-theme', 40 | HiveKeys.HiddenSceneItem: 'hidden-scene-item', 41 | HiveKeys.HiddenScene: 'hidden-scene', 42 | HiveKeys.AppLog: 'app-log', 43 | HiveKeys.PurchasedTip: 'purchased-tip', 44 | HiveKeys.Hotkey: 'hotkey', 45 | HiveKeys.Settings: 'settings', 46 | }[this]!; 47 | } 48 | -------------------------------------------------------------------------------- /lib/models/enums/chat_type.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'chat_type.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class ChatTypeAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 4; 12 | 13 | @override 14 | ChatType read(BinaryReader reader) { 15 | switch (reader.readByte()) { 16 | case 0: 17 | return ChatType.Twitch; 18 | case 1: 19 | return ChatType.YouTube; 20 | case 2: 21 | return ChatType.Owncast; 22 | default: 23 | return ChatType.Twitch; 24 | } 25 | } 26 | 27 | @override 28 | void write(BinaryWriter writer, ChatType obj) { 29 | switch (obj) { 30 | case ChatType.Twitch: 31 | writer.writeByte(0); 32 | break; 33 | case ChatType.YouTube: 34 | writer.writeByte(1); 35 | break; 36 | case ChatType.Owncast: 37 | writer.writeByte(2); 38 | break; 39 | } 40 | } 41 | 42 | @override 43 | int get hashCode => typeId.hashCode; 44 | 45 | @override 46 | bool operator ==(Object other) => 47 | identical(this, other) || 48 | other is ChatTypeAdapter && 49 | runtimeType == other.runtimeType && 50 | typeId == other.typeId; 51 | } 52 | -------------------------------------------------------------------------------- /lib/views/settings/custom_theme/widgets/custom_theme_list/theme_colors_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../models/custom_theme.dart'; 4 | import '../../../../../types/extensions/string.dart'; 5 | import '../../../../../utils/styling_helper.dart'; 6 | import '../color_picker/color_bubble.dart'; 7 | 8 | class ThemeColorsRow extends StatelessWidget { 9 | final CustomTheme customTheme; 10 | 11 | const ThemeColorsRow({super.key, required this.customTheme}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Wrap( 16 | spacing: 8.0, 17 | runSpacing: 8.0, 18 | children: [ 19 | ColorBubble(color: customTheme.cardColorHex.hexToColor()), 20 | ColorBubble( 21 | color: customTheme.cardBorderColorHex?.hexToColor() ?? 22 | Colors.transparent), 23 | ColorBubble( 24 | color: customTheme.dividerColorHex?.hexToColor() ?? 25 | StylingHelper.light_divider_color), 26 | ColorBubble(color: customTheme.tabBarColorHex.hexToColor()), 27 | ColorBubble(color: customTheme.highlightColorHex.hexToColor()), 28 | ColorBubble(color: customTheme.accentColorHex.hexToColor()), 29 | ColorBubble(color: customTheme.backgroundColorHex.hexToColor()), 30 | // ColorBubble(color: customTheme.textColorHex.hexToColor()), 31 | ], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | NSLocalNetworkUsageDescription 32 | In order to find running OBS instances automatically. 33 | NSPhotoLibraryUsageDescription 34 | In order to select your custom logo. 35 | 36 | 37 | -------------------------------------------------------------------------------- /lib/views/home/widgets/saved_connections/placeholder_connection.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../../shared/general/base/card.dart'; 5 | import '../../../../shared/overlay/base_result.dart'; 6 | 7 | class PlaceholderConnection extends StatelessWidget { 8 | final double height; 9 | final double width; 10 | 11 | const PlaceholderConnection( 12 | {super.key, required this.height, required this.width}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Center( 17 | child: SizedBox( 18 | width: this.width, 19 | child: BaseCard( 20 | topPadding: 0.0, 21 | rightPadding: 0.0, 22 | bottomPadding: 0.0, 23 | leftPadding: 0.0, 24 | paddingChild: const EdgeInsets.all(0), 25 | child: Padding( 26 | padding: const EdgeInsets.only(left: 24.0, right: 24.0), 27 | child: SizedBox( 28 | height: this.height, 29 | child: const BaseResult( 30 | icon: BaseResultIcon.Missing, 31 | iconSize: 42.0, 32 | text: 33 | 'No saved connections yet...\nNo worries though, once you successfully connected to an OBS instance you can save one for later! :)', 34 | ), 35 | ), 36 | ), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"obs_blade", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /lib/views/dashboard/widgets/dashboard_content/dashboard_content_streaming.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:obs_blade/views/dashboard/widgets/dashboard_content/resizeable_scene_preview.dart'; 5 | import 'package:obs_blade/views/dashboard/widgets/dashboard_content/scene_buttons/scene_buttons.dart'; 6 | import 'package:obs_blade/views/dashboard/widgets/obs_widgets/stream_chat/stream_chat.dart'; 7 | 8 | class DashboardContentStreaming extends StatelessWidget { 9 | const DashboardContentStreaming({ 10 | super.key, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SliverFillRemaining( 16 | child: Column( 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | const ResizeableScenePreview( 20 | resizeable: false, 21 | ), 22 | const SceneButtons( 23 | size: 64, 24 | mode: SceneButtonsMode.horizontalScroll, 25 | ), 26 | const Flexible( 27 | child: StreamChat( 28 | usernameRowExpandable: true, 29 | usernameRowBeneath: true, 30 | usernameRowPadding: true, 31 | ), 32 | ), 33 | SizedBox( 34 | height: MediaQuery.of(context).padding.bottom, 35 | ), 36 | ], 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/types/extensions/color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Credit to: Natesh bhat 4 | /// Taken from: https://stackoverflow.com/a/61186997/11440449 5 | extension ColorStuff on Color { 6 | /// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`). 7 | String toHex({bool leadingHashSign = false, bool withAlpha = false}) => 8 | '${leadingHashSign ? '#' : ''}' 9 | '${withAlpha ? alpha.toRadixString(16).padLeft(2, '0') : ''}' 10 | '${red.toRadixString(16).padLeft(2, '0')}' 11 | '${green.toRadixString(16).padLeft(2, '0')}' 12 | '${blue.toRadixString(16).padLeft(2, '0')}'; 13 | 14 | /// Darken and lighten from: https://stackoverflow.com/a/60191441 15 | /// Darken a color by [percent] amount (100 = black) 16 | Color darken([int percent = 10]) { 17 | assert(1 <= percent && percent <= 100); 18 | var f = 1 - percent / 100; 19 | return Color.fromARGB(this.alpha, (this.red * f).round(), 20 | (this.green * f).round(), (this.blue * f).round()); 21 | } 22 | 23 | /// Lighten a color by [percent] amount (100 = white) 24 | Color lighten([int percent = 10]) { 25 | assert(1 <= percent && percent <= 100); 26 | var p = percent / 100; 27 | return Color.fromARGB( 28 | this.alpha, 29 | this.red + ((255 - this.red) * p).round(), 30 | this.green + ((255 - this.green) * p).round(), 31 | this.blue + ((255 - this.blue) * p).round()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | 11 | /** 12 | Was initially added manually to enable ProMotion (partially) 13 | Is now supported natively by Flutter 14 | */ 15 | 16 | // Check whether it's iOS 15.0 or above (since this API is only available there) 17 | // if #available(iOS 15.0, *) { 18 | // // Instance of CADisplayLink which exposes several frame rate related options 19 | // let displayLink = CADisplayLink(target: self, selector: #selector(step)) 20 | // // Set the preferred range of frames (in this case "High-impact animations" based on the documentation) 21 | // displayLink.preferredFrameRateRange = CAFrameRateRange(minimum:80, maximum:120, preferred:120) 22 | // // Enable it by adding it to the main runloop 23 | // displayLink.add(to: .current, forMode: .default) 24 | // } 25 | 26 | GeneratedPluginRegistrant.register(with: self) 27 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 28 | } 29 | @objc func step(displaylink: CADisplayLink) { 30 | // Will be called once a frame has been built while matching desired frame rate 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/stores/views/logs.dart: -------------------------------------------------------------------------------- 1 | import 'package:mobx/mobx.dart'; 2 | import '../../types/enums/order.dart'; 3 | 4 | import '../../models/enums/log_level.dart'; 5 | 6 | part 'logs.g.dart'; 7 | 8 | enum AmountLogEntries { 9 | Ten, 10 | TwentyFive, 11 | Fifty, 12 | } 13 | 14 | extension AmountLogEntriesFunctions on AmountLogEntries { 15 | int get number => { 16 | AmountLogEntries.Ten: 10, 17 | AmountLogEntries.TwentyFive: 25, 18 | AmountLogEntries.Fifty: 50, 19 | }[this]!; 20 | } 21 | 22 | class LogsStore = _LogsStore with _$LogsStore; 23 | 24 | abstract class _LogsStore with Store { 25 | @observable 26 | LogLevel? logLevel; 27 | 28 | @observable 29 | DateTime? fromDate; 30 | 31 | @observable 32 | DateTime? toDate; 33 | 34 | @observable 35 | AmountLogEntries? amountLogEntries = AmountLogEntries.Ten; 36 | 37 | @observable 38 | Order filterOrder = Order.Descending; 39 | 40 | @action 41 | void setLogLevel(LogLevel? logLevel) => this.logLevel = logLevel; 42 | 43 | @action 44 | void setFromDate(DateTime? fromDate) => this.fromDate = fromDate; 45 | 46 | @action 47 | void setToDate(DateTime? toDate) => this.toDate = toDate; 48 | 49 | @action 50 | void setAmountLogEntries(AmountLogEntries? amountLogEntries) => 51 | this.amountLogEntries = amountLogEntries; 52 | 53 | @action 54 | void toggleFilterOrder() => this.filterOrder = 55 | this.filterOrder == Order.Ascending ? Order.Descending : Order.Ascending; 56 | } 57 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import connectivity_plus 9 | import file_selector_macos 10 | import in_app_purchase_storekit 11 | import network_info_plus 12 | import package_info_plus 13 | import path_provider_foundation 14 | import share_plus 15 | import url_launcher_macos 16 | import wakelock_plus 17 | import webview_flutter_wkwebview 18 | 19 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 20 | ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) 21 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 22 | InAppPurchasePlugin.register(with: registry.registrar(forPlugin: "InAppPurchasePlugin")) 23 | NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) 24 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 25 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 26 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 27 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 28 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 29 | FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) 30 | } 31 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/utils/authentication_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:crypto/crypto.dart'; 4 | import 'package:obs_blade/types/classes/session.dart'; 5 | 6 | import '../models/connection.dart'; 7 | import '../types/enums/web_socket_codes/web_socket_op_code.dart'; 8 | 9 | class AuthenticationHelper { 10 | /// This is the content of the auth field which is needed to correctly 11 | /// authenticate with the OBS WebSocket if a password has been set 12 | static String _getAuthRequestContent(Connection connection) { 13 | String secretString = '${connection.pw}${connection.salt}'; 14 | Digest secretHash = sha256.convert(utf8.encode(secretString)); 15 | String secret = const Base64Codec().encode(secretHash.bytes); 16 | 17 | String authResponseString = '$secret${connection.challenge}'; 18 | Digest authResponseHash = sha256.convert(utf8.encode(authResponseString)); 19 | String authResponse = const Base64Codec().encode(authResponseHash.bytes); 20 | 21 | return authResponse; 22 | } 23 | 24 | static void identify(Session activeSession) { 25 | activeSession.socket.sink.add( 26 | jsonEncode( 27 | { 28 | 'op': WebSocketOpCode.Identify.identifier, 29 | 'd': { 30 | 'rpcVersion': 1, 31 | 'authentication': AuthenticationHelper._getAuthRequestContent( 32 | activeSession.connection), 33 | 'eventSubscriptions': 131071 34 | } 35 | }, 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/types/classes/api/input.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'input.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$InputImpl _$$InputImplFromJson(Map json) => _$InputImpl( 10 | inputKind: json['inputKind'] as String?, 11 | inputName: json['inputName'] as String?, 12 | unversionedInputKind: json['unversionedInputKind'] as String?, 13 | inputVolumeMul: (json['inputVolumeMul'] as num?)?.toDouble(), 14 | inputVolumeDb: (json['inputVolumeDb'] as num?)?.toDouble(), 15 | inputLevelsMul: (json['inputLevelsMul'] as List?) 16 | ?.map((e) => InputChannel.fromJson(e as Map)) 17 | .toList(), 18 | syncOffset: (json['syncOffset'] as num?)?.toInt(), 19 | inputMuted: json['inputMuted'] as bool? ?? false, 20 | ); 21 | 22 | Map _$$InputImplToJson(_$InputImpl instance) => 23 | { 24 | 'inputKind': instance.inputKind, 25 | 'inputName': instance.inputName, 26 | 'unversionedInputKind': instance.unversionedInputKind, 27 | 'inputVolumeMul': instance.inputVolumeMul, 28 | 'inputVolumeDb': instance.inputVolumeDb, 29 | 'inputLevelsMul': instance.inputLevelsMul, 30 | 'syncOffset': instance.syncOffset, 31 | 'inputMuted': instance.inputMuted, 32 | }; 33 | -------------------------------------------------------------------------------- /lib/stores/shared/tabs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:mobx/mobx.dart'; 3 | import '../../utils/routing_helper.dart'; 4 | 5 | part 'tabs.g.dart'; 6 | 7 | class TabsStore = _TabsStore with _$TabsStore; 8 | 9 | abstract class _TabsStore with Store { 10 | @observable 11 | Tabs activeTab = Tabs.Home; 12 | 13 | Map> navigatorKeys = {}; 14 | Map activeRoutePerNavigator = {}; 15 | 16 | /// Might later be used as an indicator which says that the user tapped 17 | /// on a tab which is already active and in our views we can react to 18 | /// this (via MobX reaction) to do stuff like scroll up, open search etc. 19 | /// Right now not used since tapping on the current active tab will either 20 | /// pop the route if canPop() returns true or scroll up if its the root view 21 | /// of the tab and it attached the ScrollController (each tab gets a 22 | /// ScrollController as a navigation argument - used or not) 23 | @observable 24 | bool performTabClickAction = false; 25 | 26 | GlobalKey keyForCurrentTab() => 27 | this.navigatorKeys[this.activeTab]!; 28 | 29 | String activeRouteForCurrentTab() => 30 | this.activeRoutePerNavigator[this.activeTab]!; 31 | 32 | @action 33 | void setActiveTab(Tabs activeTab) => this.activeTab = activeTab; 34 | 35 | @action 36 | void setPerformTabClickAction(bool performTabClickAction) => 37 | this.performTabClickAction = performTabClickAction; 38 | } 39 | -------------------------------------------------------------------------------- /lib/views/settings/dashboard_customisation/order/widgets/scene_buttons_preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:obs_blade/shared/animator/selectable_box.dart'; 3 | 4 | class SceneButtonsPreview extends StatefulWidget { 5 | const SceneButtonsPreview({super.key}); 6 | 7 | @override 8 | State createState() => _SceneButtonsPreviewState(); 9 | } 10 | 11 | class _SceneButtonsPreviewState extends State { 12 | List _selected = [true, false, false]; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return FittedBox( 17 | child: Row( 18 | children: [ 19 | SelectableBox( 20 | text: '', 21 | selected: _selected[0], 22 | onTap: () => setState(() { 23 | _selected = [true, false, false]; 24 | }), 25 | ), 26 | const SizedBox(width: 12.0), 27 | SelectableBox( 28 | text: '', 29 | selected: _selected[1], 30 | onTap: () => setState(() { 31 | _selected = [false, true, false]; 32 | }), 33 | ), 34 | const SizedBox(width: 12.0), 35 | SelectableBox( 36 | text: '', 37 | selected: _selected[2], 38 | onTap: () => setState(() { 39 | _selected = [false, false, true]; 40 | }), 41 | ), 42 | ], 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/shared/general/base/adaptive_dialog/adaptive_dialog_action.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AdaptiveDialogAction extends StatelessWidget { 5 | final Widget child; 6 | 7 | final bool isDestructiveAction; 8 | final bool isDefaultAction; 9 | 10 | final void Function()? onPressed; 11 | 12 | const AdaptiveDialogAction({ 13 | super.key, 14 | required this.child, 15 | this.isDefaultAction = false, 16 | this.isDestructiveAction = false, 17 | this.onPressed, 18 | }); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return switch (Theme.of(context).platform) { 23 | TargetPlatform.iOS || TargetPlatform.macOS => CupertinoDialogAction( 24 | onPressed: this.onPressed, 25 | isDefaultAction: this.isDefaultAction, 26 | isDestructiveAction: this.isDestructiveAction, 27 | child: this.child, 28 | ), 29 | _ => TextButton( 30 | onPressed: this.onPressed, 31 | style: this.isDestructiveAction 32 | ? ButtonStyle( 33 | foregroundColor: const MaterialStatePropertyAll( 34 | CupertinoColors.destructiveRed), 35 | overlayColor: MaterialStatePropertyAll( 36 | CupertinoColors.destructiveRed.withOpacity(0.1), 37 | ), 38 | ) 39 | : null, 40 | child: this.child, 41 | ), 42 | }; 43 | } 44 | } 45 | --------------------------------------------------------------------------------